httpsend.pas 28 KB

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