httpsend.pas 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. {==============================================================================|
  2. | Project : Ararat Synapse | 003.012.009 |
  3. |==============================================================================|
  4. | Content: HTTP client |
  5. |==============================================================================|
  6. | Copyright (c)1999-2015, Lukas Gebauer |
  7. | All rights reserved. |
  8. | |
  9. | Redistribution and use in source and binary forms, with or without |
  10. | modification, are permitted provided that the following conditions are met: |
  11. | |
  12. | Redistributions of source code must retain the above copyright notice, this |
  13. | list of conditions and the following disclaimer. |
  14. | |
  15. | Redistributions in binary form must reproduce the above copyright notice, |
  16. | this list of conditions and the following disclaimer in the documentation |
  17. | and/or other materials provided with the distribution. |
  18. | |
  19. | Neither the name of Lukas Gebauer nor the names of its contributors may |
  20. | be used to endorse or promote products derived from this software without |
  21. | specific prior written permission. |
  22. | |
  23. | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
  24. | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
  25. | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
  26. | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
  27. | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
  28. | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
  29. | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
  30. | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  31. | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
  32. | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
  33. | DAMAGE. |
  34. |==============================================================================|
  35. | The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
  36. | Portions created by Lukas Gebauer are Copyright (c) 1999-2015. |
  37. | All Rights Reserved. |
  38. |==============================================================================|
  39. | Contributor(s): |
  40. |==============================================================================|
  41. | History: see HISTORY.HTM from distribution package |
  42. | (Found at URL: http://www.ararat.cz/synapse/) |
  43. |==============================================================================}
  44. {:@abstract(HTTP protocol client)
  45. Used RFC: RFC-1867, RFC-1947, RFC-2388, RFC-2616
  46. }
  47. {$IFDEF FPC}
  48. {$MODE DELPHI}
  49. {$ENDIF}
  50. {$H+}
  51. //old Delphi does not have MSWINDOWS define.
  52. {$IFDEF WIN32}
  53. {$IFNDEF MSWINDOWS}
  54. {$DEFINE MSWINDOWS}
  55. {$ENDIF}
  56. {$ENDIF}
  57. {$IFDEF UNICODE}
  58. {$WARN IMPLICIT_STRING_CAST OFF}
  59. {$WARN IMPLICIT_STRING_CAST_LOSS OFF}
  60. {$ENDIF}
  61. unit httpsend;
  62. interface
  63. uses
  64. SysUtils, Classes,
  65. blcksock, synautil, synaip, synacode, synsock;
  66. const
  67. cHttpProtocol = '80';
  68. type
  69. {:These encoding types are used internally by the THTTPSend object to identify
  70. the transfer data types.}
  71. TTransferEncoding = (TE_UNKNOWN, TE_IDENTITY, TE_CHUNKED);
  72. {:abstract(Implementation of HTTP protocol.)}
  73. THTTPSend = class(TSynaClient)
  74. protected
  75. FSock: TTCPBlockSocket;
  76. FTransferEncoding: TTransferEncoding;
  77. FAliveHost: string;
  78. FAlivePort: string;
  79. FHeaders: TStringList;
  80. FDocument: TMemoryStream;
  81. FMimeType: string;
  82. FProtocol: string;
  83. FKeepAlive: Boolean;
  84. FKeepAliveTimeout: integer;
  85. FStatus100: Boolean;
  86. FProxyHost: string;
  87. FProxyPort: string;
  88. FProxyUser: string;
  89. FProxyPass: string;
  90. FResultCode: Integer;
  91. FResultString: string;
  92. FUserAgent: string;
  93. FCookies: TStringList;
  94. FDownloadSize: integer;
  95. FUploadSize: integer;
  96. FRangeStart: integer;
  97. FRangeEnd: integer;
  98. FAddPortNumberToHost: Boolean;
  99. function ReadUnknown: Boolean; virtual;
  100. function ReadIdentity(Size: Integer): Boolean; virtual;
  101. function ReadChunked: Boolean; virtual;
  102. procedure ParseCookies;
  103. function PrepareHeaders: AnsiString;
  104. function InternalDoConnect(needssl: Boolean): Boolean;
  105. function InternalConnect(needssl: Boolean): Boolean;
  106. public
  107. constructor Create;
  108. destructor Destroy; override;
  109. {:Reset headers, document and Mimetype.}
  110. procedure Clear;
  111. {:Decode ResultCode and ResultString from Value.}
  112. procedure DecodeStatus(const Value: string);
  113. {:Connects to host defined in URL and accesses resource defined in URL by
  114. method. If Document is not empty, send it to the server as part of the HTTP
  115. request. Server response is in Document and headers. Connection may be
  116. authorised by username and password in URL. If you define proxy properties,
  117. connection is made by this proxy.
  118. If all OK, result is @true, else result is @false.
  119. If you use 'https:' instead of 'http:' in the URL, your request is made
  120. by SSL/TLS connection (if you do not specify port, then port 443 is used
  121. instead of standard port 80). If you use SSL/TLS request and you have
  122. defined HTTP proxy, then HTTP-tunnel mode is automatically used .}
  123. function HTTPMethod(const Method, URL: string): Boolean;
  124. {:You can call this method from OnStatus event to break current data
  125. transfer. (or from another thread.)}
  126. procedure Abort;
  127. published
  128. {:Before HTTP operation you may define any non-standard headers for HTTP
  129. request, except: 'Expect: 100-continue', 'Content-Length', 'Content-Type',
  130. 'Connection', 'Authorization', 'Proxy-Authorization' and 'Host' headers.
  131. After HTTP operation, it contains full headers of the returned document.}
  132. property Headers: TStringList read FHeaders;
  133. {:Stringlist with name-value stringlist pairs. Each pair is one cookie.
  134. After the HTTP request is returned, cookies are parsed to this stringlist.
  135. You can leave these cookies untouched for next HTTP requests. You can also
  136. save this stringlist for later use.}
  137. property Cookies: TStringList read FCookies;
  138. {:Stream with document to send (before request), or with document received
  139. from HTTP server (after request).}
  140. property Document: TMemoryStream read FDocument;
  141. {:If you need to download only part of a requested document, specify here
  142. the position of subpart begin. If 0, the full document is requested.}
  143. property RangeStart: integer read FRangeStart Write FRangeStart;
  144. {:If you need to download only part of a requested document, specify here
  145. the position of subpart end. If 0, the document from rangeStart to end of
  146. document is requested.
  147. (Useful for resuming broken downloads, for example.)}
  148. property RangeEnd: integer read FRangeEnd Write FRangeEnd;
  149. {:Mime type of sending data. Default is: 'text/html'.}
  150. property MimeType: string read FMimeType Write FMimeType;
  151. {:Define protocol version. Possible values are: '1.1', '1.0' (default)
  152. and '0.9'.}
  153. property Protocol: string read FProtocol Write FProtocol;
  154. {:If @true (default value), keepalives in HTTP protocol 1.1 is enabled.}
  155. property KeepAlive: Boolean read FKeepAlive Write FKeepAlive;
  156. {:Define timeout for keepalives in seconds!}
  157. property KeepAliveTimeout: integer read FKeepAliveTimeout Write FKeepAliveTimeout;
  158. {:if @true, then the server is requested for 100status capability when
  159. uploading data. Default is @false (off).}
  160. property Status100: Boolean read FStatus100 Write FStatus100;
  161. {:Address of proxy server (IP address or domain name) where you want to
  162. connect in @link(HTTPMethod) method.}
  163. property ProxyHost: string read FProxyHost Write FProxyHost;
  164. {:Port number for proxy connection. Default value is 8080.}
  165. property ProxyPort: string read FProxyPort Write FProxyPort;
  166. {:Username for connection to proxy server used in HTTPMethod method.}
  167. property ProxyUser: string read FProxyUser Write FProxyUser;
  168. {:Password for connection to proxy server used in HTTPMethod method.}
  169. property ProxyPass: string read FProxyPass Write FProxyPass;
  170. {:Here you can specify custom User-Agent identification.
  171. Default: 'Mozilla/4.0 (compatible; Synapse)'}
  172. property UserAgent: string read FUserAgent Write FUserAgent;
  173. {:Operation result code after successful @link(HTTPMethod) method.}
  174. property ResultCode: Integer read FResultCode;
  175. {:Operation result string after successful @link(HTTPMethod) method.}
  176. property ResultString: string read FResultString;
  177. {:if this value is not 0, then data download is pending. In this case you
  178. have here the total size of downloaded data. Useful for drawing download
  179. progressbar from OnStatus event.}
  180. property DownloadSize: integer read FDownloadSize;
  181. {:if this value is not 0, then data upload is pending. In this case you have
  182. here the total size of uploaded data. Useful for drawing upload progressbar
  183. from OnStatus event.}
  184. property UploadSize: integer read FUploadSize;
  185. {:Socket object used for TCP/IP operation.
  186. Good for setting OnStatus hook, etc.}
  187. property Sock: TTCPBlockSocket read FSock;
  188. {:Allows to switch off port number in 'Host:' HTTP header. By default @TRUE.
  189. Some buggy servers do not like port informations in this header.}
  190. property AddPortNumberToHost: Boolean read FAddPortNumberToHost write FAddPortNumberToHost;
  191. end;
  192. {:A very useful function, and example of use can be found in the THTTPSend
  193. object. It implements the GET method of the HTTP protocol. This function sends
  194. the GET method for URL document to an HTTP server. Returned document is in the
  195. "Response" stringlist (without any headers). Returns boolean TRUE if all went
  196. well.}
  197. function HttpGetText(const URL: string; const Response: TStrings): Boolean;
  198. {:A very useful function, and example of use can be found in the THTTPSend
  199. object. It implements the GET method of the HTTP protocol. This function sends
  200. the GET method for URL document to an HTTP server. Returned document is in the
  201. "Response" stream. Returns boolean TRUE if all went well.}
  202. function HttpGetBinary(const URL: string; const Response: TStream): Boolean;
  203. {:A very useful function, and example of use can be found in the THTTPSend
  204. object. It implements the POST method of the HTTP protocol. This function sends
  205. the SEND method for a URL document to an HTTP server. The document to be sent
  206. is located in the "Data" stream. The returned document is in the "Data" stream.
  207. Returns boolean TRUE if all went well.}
  208. function HttpPostBinary(const URL: string; const Data: TStream): Boolean;
  209. {:A very useful function, and example of use can be found in the THTTPSend
  210. object. It implements the POST method of the HTTP protocol. This function is
  211. good for POSTing form data. It sends the POST method for a URL document to
  212. an HTTP server. You must prepare the form data in the same manner as you would
  213. the URL data, and pass this prepared data to "URLdata". The following is
  214. a sample of how the data would appear: 'name=Lukas&field1=some%20data'.
  215. The information in the field must be encoded by the EncodeURLElement function.
  216. The returned document is in the "Data" stream. Returns boolean TRUE if all
  217. went well.}
  218. function HttpPostURL(const URL, URLData: string; const Data: TStream): Boolean;
  219. {:A very useful function, and example of use can be found in the THTTPSend
  220. object. It implements the POST method of the HTTP protocol. This function sends
  221. the POST method for a URL document to an HTTP server. This function simulates
  222. posting of file by HTML form using the 'multipart/form-data' method. The posted
  223. file is in the DATA stream. Its name is Filename string. Fieldname is for the
  224. name of the form field with the file. (simulates HTML INPUT FILE) The returned
  225. document is in the ResultData Stringlist. Returns boolean TRUE if all
  226. went well.}
  227. function HttpPostFile(const URL, FieldName, FileName: string;
  228. const Data: TStream; const ResultData: TStrings): Boolean;
  229. implementation
  230. constructor THTTPSend.Create;
  231. begin
  232. inherited Create;
  233. FHeaders := TStringList.Create;
  234. FCookies := TStringList.Create;
  235. FDocument := TMemoryStream.Create;
  236. FSock := TTCPBlockSocket.Create;
  237. FSock.Owner := self;
  238. FSock.ConvertLineEnd := True;
  239. FSock.SizeRecvBuffer := c64k;
  240. FSock.SizeSendBuffer := c64k;
  241. FTimeout := 90000;
  242. FTargetPort := cHttpProtocol;
  243. FProxyHost := '';
  244. FProxyPort := '8080';
  245. FProxyUser := '';
  246. FProxyPass := '';
  247. FAliveHost := '';
  248. FAlivePort := '';
  249. FProtocol := '1.0';
  250. FKeepAlive := True;
  251. FStatus100 := False;
  252. FUserAgent := 'Mozilla/4.0 (compatible; Synapse)';
  253. FDownloadSize := 0;
  254. FUploadSize := 0;
  255. FAddPortNumberToHost := true;
  256. FKeepAliveTimeout := 300;
  257. Clear;
  258. end;
  259. destructor THTTPSend.Destroy;
  260. begin
  261. FSock.Free;
  262. FDocument.Free;
  263. FCookies.Free;
  264. FHeaders.Free;
  265. inherited Destroy;
  266. end;
  267. procedure THTTPSend.Clear;
  268. begin
  269. FRangeStart := 0;
  270. FRangeEnd := 0;
  271. FDocument.Clear;
  272. FHeaders.Clear;
  273. FMimeType := 'text/html';
  274. end;
  275. procedure THTTPSend.DecodeStatus(const Value: string);
  276. var
  277. s, su: string;
  278. begin
  279. s := Trim(SeparateRight(Value, ' '));
  280. su := Trim(SeparateLeft(s, ' '));
  281. FResultCode := StrToIntDef(su, 0);
  282. FResultString := Trim(SeparateRight(s, ' '));
  283. if FResultString = s then
  284. FResultString := '';
  285. end;
  286. function THTTPSend.PrepareHeaders: AnsiString;
  287. begin
  288. if FProtocol = '0.9' then
  289. Result := FHeaders[0] + CRLF
  290. else
  291. {$IFNDEF MSWINDOWS}
  292. Result := {$IFDEF UNICODE}AnsiString{$ENDIF}(AdjustLineBreaks(FHeaders.Text, tlbsCRLF));
  293. {$ELSE}
  294. Result := FHeaders.Text;
  295. {$ENDIF}
  296. end;
  297. function THTTPSend.InternalDoConnect(needssl: Boolean): Boolean;
  298. begin
  299. Result := False;
  300. FSock.CloseSocket;
  301. FSock.Bind(FIPInterface, cAnyPort);
  302. if FSock.LastError <> 0 then
  303. Exit;
  304. FSock.Connect(FTargetHost, FTargetPort);
  305. if FSock.LastError <> 0 then
  306. Exit;
  307. if needssl then
  308. begin
  309. if (FSock.SSL.SNIHost='') then
  310. FSock.SSL.SNIHost:=FTargetHost;
  311. FSock.SSLDoConnect;
  312. FSock.SSL.SNIHost:=''; //don't need it anymore and don't wan't to reuse it in next connection
  313. if FSock.LastError <> 0 then
  314. Exit;
  315. end;
  316. FAliveHost := FTargetHost;
  317. FAlivePort := FTargetPort;
  318. Result := True;
  319. end;
  320. function THTTPSend.InternalConnect(needssl: Boolean): Boolean;
  321. begin
  322. if FSock.Socket = INVALID_SOCKET then
  323. Result := InternalDoConnect(needssl)
  324. else
  325. if (FAliveHost <> FTargetHost) or (FAlivePort <> FTargetPort)
  326. or FSock.CanRead(0) then
  327. Result := InternalDoConnect(needssl)
  328. else
  329. Result := True;
  330. end;
  331. function THTTPSend.HTTPMethod(const Method, URL: string): Boolean;
  332. var
  333. Sending, Receiving: Boolean;
  334. status100: Boolean;
  335. status100error: string;
  336. ToClose: Boolean;
  337. Size: Integer;
  338. Prot, User, Pass, Host, Port, Path, Para, URI: string;
  339. s, su: AnsiString;
  340. HttpTunnel: Boolean;
  341. n: integer;
  342. pp: string;
  343. UsingProxy: boolean;
  344. l: TStringList;
  345. x: integer;
  346. begin
  347. {initial values}
  348. Result := False;
  349. FResultCode := 500;
  350. FResultString := '';
  351. FDownloadSize := 0;
  352. FUploadSize := 0;
  353. URI := ParseURL(URL, Prot, User, Pass, Host, Port, Path, Para);
  354. User := DecodeURL(user);
  355. Pass := DecodeURL(pass);
  356. if User = '' then
  357. begin
  358. User := FUsername;
  359. Pass := FPassword;
  360. end;
  361. if UpperCase(Prot) = 'HTTPS' then
  362. begin
  363. HttpTunnel := FProxyHost <> '';
  364. FSock.HTTPTunnelIP := FProxyHost;
  365. FSock.HTTPTunnelPort := FProxyPort;
  366. FSock.HTTPTunnelUser := FProxyUser;
  367. FSock.HTTPTunnelPass := FProxyPass;
  368. end
  369. else
  370. begin
  371. HttpTunnel := False;
  372. FSock.HTTPTunnelIP := '';
  373. FSock.HTTPTunnelPort := '';
  374. FSock.HTTPTunnelUser := '';
  375. FSock.HTTPTunnelPass := '';
  376. end;
  377. UsingProxy := (FProxyHost <> '') and not(HttpTunnel);
  378. Sending := FDocument.Size > 0;
  379. {Headers for Sending data}
  380. status100 := FStatus100 and Sending and (FProtocol = '1.1');
  381. if status100 then
  382. FHeaders.Insert(0, 'Expect: 100-continue');
  383. if Sending then
  384. begin
  385. FHeaders.Insert(0, 'Content-Length: ' + IntToStr(FDocument.Size));
  386. if FMimeType <> '' then
  387. FHeaders.Insert(0, 'Content-Type: ' + FMimeType);
  388. end;
  389. { setting User-agent }
  390. if FUserAgent <> '' then
  391. FHeaders.Insert(0, 'User-Agent: ' + FUserAgent);
  392. { setting Ranges }
  393. if (FRangeStart > 0) or (FRangeEnd > 0) then
  394. begin
  395. if FRangeEnd >= FRangeStart then
  396. FHeaders.Insert(0, 'Range: bytes=' + IntToStr(FRangeStart) + '-' + IntToStr(FRangeEnd))
  397. else
  398. FHeaders.Insert(0, 'Range: bytes=' + IntToStr(FRangeStart) + '-');
  399. end;
  400. { setting Cookies }
  401. s := '';
  402. for n := 0 to FCookies.Count - 1 do
  403. begin
  404. if s <> '' then
  405. s := s + '; ';
  406. s := s + FCookies[n];
  407. end;
  408. if s <> '' then
  409. FHeaders.Insert(0, 'Cookie: ' + s);
  410. { setting KeepAlives }
  411. pp := '';
  412. if UsingProxy then
  413. pp := 'Proxy-';
  414. if FKeepAlive then
  415. begin
  416. FHeaders.Insert(0, pp + 'Connection: keep-alive');
  417. FHeaders.Insert(0, 'Keep-Alive: ' + IntToStr(FKeepAliveTimeout));
  418. end
  419. else
  420. FHeaders.Insert(0, pp + 'Connection: close');
  421. { set target servers/proxy, authorizations, etc... }
  422. if User <> '' then
  423. FHeaders.Insert(0, 'Authorization: Basic ' + EncodeBase64(User + ':' + Pass));
  424. if UsingProxy and (FProxyUser <> '') then
  425. FHeaders.Insert(0, 'Proxy-Authorization: Basic ' +
  426. EncodeBase64(FProxyUser + ':' + FProxyPass));
  427. if isIP6(Host) then
  428. s := '[' + Host + ']'
  429. else
  430. s := Host;
  431. if FAddPortNumberToHost
  432. and (((Port <> '80') and (UpperCase(Prot) = 'HTTP'))
  433. or ((Port <> '443') and (UpperCase(Prot) = 'HTTPS'))) then
  434. FHeaders.Insert(0, 'Host: ' + s + ':' + Port)
  435. else
  436. FHeaders.Insert(0, 'Host: ' + s);
  437. if UsingProxy then
  438. URI := Prot + '://' + s + ':' + Port + URI;
  439. if URI = '/*' then
  440. URI := '*';
  441. if FProtocol = '0.9' then
  442. FHeaders.Insert(0, UpperCase(Method) + ' ' + URI)
  443. else
  444. FHeaders.Insert(0, UpperCase(Method) + ' ' + URI + ' HTTP/' + FProtocol);
  445. if UsingProxy then
  446. begin
  447. FTargetHost := FProxyHost;
  448. FTargetPort := FProxyPort;
  449. end
  450. else
  451. begin
  452. FTargetHost := Host;
  453. FTargetPort := Port;
  454. end;
  455. if FHeaders[FHeaders.Count - 1] <> '' then
  456. FHeaders.Add('');
  457. { connect }
  458. if not InternalConnect(UpperCase(Prot) = 'HTTPS') then
  459. begin
  460. FAliveHost := '';
  461. FAlivePort := '';
  462. Exit;
  463. end;
  464. { reading Status }
  465. FDocument.Position := 0;
  466. Status100Error := '';
  467. if status100 then
  468. begin
  469. { send Headers }
  470. FSock.SendString(PrepareHeaders);
  471. if FSock.LastError <> 0 then
  472. Exit;
  473. repeat
  474. s := FSock.RecvString(FTimeout);
  475. if s <> '' then
  476. Break;
  477. until FSock.LastError <> 0;
  478. DecodeStatus(s);
  479. Status100Error := s;
  480. repeat
  481. s := FSock.recvstring(FTimeout);
  482. if s = '' then
  483. Break;
  484. until FSock.LastError <> 0;
  485. if (FResultCode >= 100) and (FResultCode < 200) then
  486. begin
  487. { we can upload content }
  488. Status100Error := '';
  489. FUploadSize := FDocument.Size;
  490. FSock.SendBuffer(FDocument.Memory, FDocument.Size);
  491. end;
  492. end
  493. else
  494. { upload content }
  495. if sending then
  496. begin
  497. if FDocument.Size >= c64k then
  498. begin
  499. FSock.SendString(PrepareHeaders);
  500. FUploadSize := FDocument.Size;
  501. FSock.SendBuffer(FDocument.Memory, FDocument.Size);
  502. end
  503. else
  504. begin
  505. s := PrepareHeaders + ReadStrFromStream(FDocument, FDocument.Size);
  506. FUploadSize := Length(s);
  507. FSock.SendString(s);
  508. end;
  509. end
  510. else
  511. begin
  512. { we not need to upload document, send headers only }
  513. FSock.SendString(PrepareHeaders);
  514. end;
  515. if FSock.LastError <> 0 then
  516. Exit;
  517. Clear;
  518. Size := -1;
  519. FTransferEncoding := TE_UNKNOWN;
  520. { read status }
  521. if Status100Error = '' then
  522. begin
  523. repeat
  524. repeat
  525. s := FSock.RecvString(FTimeout);
  526. if s <> '' then
  527. Break;
  528. until FSock.LastError <> 0;
  529. if Pos('HTTP/', UpperCase(s)) = 1 then
  530. begin
  531. FHeaders.Add(s);
  532. DecodeStatus(s);
  533. end
  534. else
  535. begin
  536. { old HTTP 0.9 and some buggy servers not send result }
  537. s := s + CRLF;
  538. WriteStrToStream(FDocument, s);
  539. FResultCode := 0;
  540. end;
  541. until (FSock.LastError <> 0) or (FResultCode <> 100);
  542. end
  543. else
  544. FHeaders.Add(Status100Error);
  545. { if need receive headers, receive and parse it }
  546. ToClose := FProtocol <> '1.1';
  547. if FHeaders.Count > 0 then
  548. begin
  549. l := TStringList.Create;
  550. try
  551. repeat
  552. s := FSock.RecvString(FTimeout);
  553. l.Add(s);
  554. if s = '' then
  555. Break;
  556. until FSock.LastError <> 0;
  557. x := 0;
  558. while l.Count > x do
  559. begin
  560. s := NormalizeHeader(l, x);
  561. FHeaders.Add(s);
  562. su := UpperCase(s);
  563. if Pos('CONTENT-LENGTH:', su) = 1 then
  564. begin
  565. Size := StrToIntDef(Trim(SeparateRight(s, ':')), -1);
  566. if (Size <> -1) and (FTransferEncoding = TE_UNKNOWN) then
  567. FTransferEncoding := TE_IDENTITY;
  568. end;
  569. if Pos('CONTENT-TYPE:', su) = 1 then
  570. FMimeType := Trim(SeparateRight(s, ':'));
  571. if Pos('TRANSFER-ENCODING:', su) = 1 then
  572. begin
  573. s := Trim(SeparateRight(su, ':'));
  574. if Pos('CHUNKED', s) > 0 then
  575. FTransferEncoding := TE_CHUNKED;
  576. end;
  577. if UsingProxy then
  578. begin
  579. if Pos('PROXY-CONNECTION:', su) = 1 then
  580. if Pos('CLOSE', su) > 0 then
  581. ToClose := True;
  582. end
  583. else
  584. begin
  585. if Pos('CONNECTION:', su) = 1 then
  586. if Pos('CLOSE', su) > 0 then
  587. ToClose := True;
  588. end;
  589. end;
  590. finally
  591. l.free;
  592. end;
  593. end;
  594. Result := FSock.LastError = 0;
  595. if not Result then
  596. begin
  597. FSock.CloseSocket;
  598. FAliveHost := '';
  599. FAlivePort := '';
  600. Exit;
  601. end;
  602. {if need receive response body, read it}
  603. Receiving := Method <> 'HEAD';
  604. Receiving := Receiving and (FResultCode <> 204);
  605. Receiving := Receiving and (FResultCode <> 304);
  606. if Receiving then
  607. case FTransferEncoding of
  608. TE_UNKNOWN:
  609. Result := ReadUnknown;
  610. TE_IDENTITY:
  611. Result := ReadIdentity(Size);
  612. TE_CHUNKED:
  613. Result := ReadChunked;
  614. end;
  615. FDocument.Seek(0, soFromBeginning);
  616. if ToClose then
  617. begin
  618. FSock.CloseSocket;
  619. FAliveHost := '';
  620. FAlivePort := '';
  621. end;
  622. ParseCookies;
  623. end;
  624. function THTTPSend.ReadUnknown: Boolean;
  625. var
  626. s: ansistring;
  627. begin
  628. Result := false;
  629. repeat
  630. s := FSock.RecvPacket(FTimeout);
  631. if FSock.LastError = 0 then
  632. WriteStrToStream(FDocument, s);
  633. until FSock.LastError <> 0;
  634. if FSock.LastError = WSAECONNRESET then
  635. begin
  636. Result := true;
  637. FSock.ResetLastError;
  638. end;
  639. end;
  640. function THTTPSend.ReadIdentity(Size: Integer): Boolean;
  641. begin
  642. if Size > 0 then
  643. begin
  644. FDownloadSize := Size;
  645. FSock.RecvStreamSize(FDocument, FTimeout, Size);
  646. FDocument.Position := FDocument.Size;
  647. Result := FSock.LastError = 0;
  648. end
  649. else
  650. Result := true;
  651. end;
  652. function THTTPSend.ReadChunked: Boolean;
  653. var
  654. s: ansistring;
  655. Size: Integer;
  656. begin
  657. repeat
  658. repeat
  659. s := FSock.RecvString(FTimeout);
  660. until (s <> '') or (FSock.LastError <> 0);
  661. if FSock.LastError <> 0 then
  662. Break;
  663. s := Trim(SeparateLeft(s, ' '));
  664. s := Trim(SeparateLeft(s, ';'));
  665. Size := StrToIntDef('$' + s, 0);
  666. if Size = 0 then
  667. Break;
  668. if not ReadIdentity(Size) then
  669. break;
  670. until False;
  671. Result := FSock.LastError = 0;
  672. end;
  673. procedure THTTPSend.ParseCookies;
  674. var
  675. n: integer;
  676. s: string;
  677. sn, sv: string;
  678. begin
  679. for n := 0 to FHeaders.Count - 1 do
  680. if Pos('set-cookie:', lowercase(FHeaders[n])) = 1 then
  681. begin
  682. s := SeparateRight(FHeaders[n], ':');
  683. s := trim(SeparateLeft(s, ';'));
  684. sn := trim(SeparateLeft(s, '='));
  685. sv := trim(SeparateRight(s, '='));
  686. FCookies.Values[sn] := sv;
  687. end;
  688. end;
  689. procedure THTTPSend.Abort;
  690. begin
  691. FSock.StopFlag := True;
  692. end;
  693. {==============================================================================}
  694. function HttpGetText(const URL: string; const Response: TStrings): Boolean;
  695. var
  696. HTTP: THTTPSend;
  697. begin
  698. HTTP := THTTPSend.Create;
  699. try
  700. Result := HTTP.HTTPMethod('GET', URL);
  701. if Result then
  702. Response.LoadFromStream(HTTP.Document);
  703. finally
  704. HTTP.Free;
  705. end;
  706. end;
  707. function HttpGetBinary(const URL: string; const Response: TStream): Boolean;
  708. var
  709. HTTP: THTTPSend;
  710. begin
  711. HTTP := THTTPSend.Create;
  712. try
  713. Result := HTTP.HTTPMethod('GET', URL);
  714. if Result then
  715. begin
  716. Response.Seek(0, soFromBeginning);
  717. Response.CopyFrom(HTTP.Document, 0);
  718. end;
  719. finally
  720. HTTP.Free;
  721. end;
  722. end;
  723. function HttpPostBinary(const URL: string; const Data: TStream): Boolean;
  724. var
  725. HTTP: THTTPSend;
  726. begin
  727. HTTP := THTTPSend.Create;
  728. try
  729. HTTP.Document.CopyFrom(Data, 0);
  730. HTTP.MimeType := 'Application/octet-stream';
  731. Result := HTTP.HTTPMethod('POST', URL);
  732. Data.Size := 0;
  733. if Result then
  734. begin
  735. Data.Seek(0, soFromBeginning);
  736. Data.CopyFrom(HTTP.Document, 0);
  737. end;
  738. finally
  739. HTTP.Free;
  740. end;
  741. end;
  742. function HttpPostURL(const URL, URLData: string; const Data: TStream): Boolean;
  743. var
  744. HTTP: THTTPSend;
  745. begin
  746. HTTP := THTTPSend.Create;
  747. try
  748. WriteStrToStream(HTTP.Document, URLData);
  749. HTTP.MimeType := 'application/x-www-form-urlencoded';
  750. Result := HTTP.HTTPMethod('POST', URL);
  751. if Result then
  752. Data.CopyFrom(HTTP.Document, 0);
  753. finally
  754. HTTP.Free;
  755. end;
  756. end;
  757. function HttpPostFile(const URL, FieldName, FileName: string;
  758. const Data: TStream; const ResultData: TStrings): Boolean;
  759. var
  760. HTTP: THTTPSend;
  761. Bound, s: string;
  762. begin
  763. Bound := IntToHex(Random(MaxInt), 8) + '_Synapse_boundary';
  764. HTTP := THTTPSend.Create;
  765. try
  766. s := '--' + Bound + CRLF;
  767. s := s + 'content-disposition: form-data; name="' + FieldName + '";';
  768. s := s + ' filename="' + FileName +'"' + CRLF;
  769. s := s + 'Content-Type: Application/octet-string' + CRLF + CRLF;
  770. WriteStrToStream(HTTP.Document, s);
  771. HTTP.Document.CopyFrom(Data, 0);
  772. s := CRLF + '--' + Bound + '--' + CRLF;
  773. WriteStrToStream(HTTP.Document, s);
  774. HTTP.MimeType := 'multipart/form-data; boundary=' + Bound;
  775. Result := HTTP.HTTPMethod('POST', URL);
  776. if Result then
  777. ResultData.LoadFromStream(HTTP.Document);
  778. finally
  779. HTTP.Free;
  780. end;
  781. end;
  782. end.