httpsend.pas 26 KB

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