fphttpclient.pp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2011 by the Free Pascal development team
  4. HTTP client component.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit fphttpclient;
  12. { ---------------------------------------------------------------------
  13. Todo:
  14. * Proxy support ?
  15. * Easy calls for POST/DELETE/etc.
  16. ---------------------------------------------------------------------}
  17. {$mode objfpc}{$H+}
  18. interface
  19. uses
  20. Classes, SysUtils, ssockets, httpdefs, uriparser, base64;
  21. Const
  22. ReadBufLen = 4096;
  23. Type
  24. { TFPCustomHTTPClient }
  25. TFPCustomHTTPClient = Class(TComponent)
  26. private
  27. FCookies: TStrings;
  28. FHTTPVersion: String;
  29. FRequestBody: TStream;
  30. FRequestHeaders: TStrings;
  31. FResponseHeaders: TStrings;
  32. FResponseStatusCode: Integer;
  33. FResponseStatusText: String;
  34. FServerHTTPVersion: String;
  35. FSocket : TInetSocket;
  36. FBuffer : Ansistring;
  37. function CheckContentLength: Integer;
  38. function CheckTransferEncoding: string;
  39. function GetCookies: TStrings;
  40. procedure SetCookies(const AValue: TStrings);
  41. procedure SetRequestHeaders(const AValue: TStrings);
  42. protected
  43. // Parse response status line. Saves status text and protocol, returns numerical code. Exception if invalid line.
  44. Function ParseStatusLine(AStatusLine : String) : Integer;
  45. // Construct server URL for use in request line.
  46. function GetServerURL(URI: TURI): String;
  47. // Read 1 line of response. Fills FBuffer
  48. function ReadString: String;
  49. // Check if response code is in AllowedResponseCodes. if not, an exception is raised.
  50. function CheckResponseCode(ACode: Integer; const AllowedResponseCodes: array of Integer): Boolean; virtual;
  51. // Read response from server, and write any document to Stream.
  52. procedure ReadResponse(Stream: TStream; const AllowedResponseCodes: array of Integer); virtual;
  53. // Read server response line and headers. Returns status code.
  54. Function ReadResponseHeaders : integer; virtual;
  55. // Allow header in request ? (currently checks only if non-empty and contains : token)
  56. function AllowHeader(var AHeader: String): Boolean; virtual;
  57. // Connect to the server. Must initialize FSocket.
  58. procedure ConnectToServer(const AHost: String; APort: Integer); virtual;
  59. // Disconnect from server. Must free FSocket.
  60. procedure DisconnectFromServer; virtual;
  61. // Run method AMethod, using request URL AURL. Write Response to Stream, and headers in ResponseHeaders.
  62. // If non-empty, AllowedResponseCodes contains an array of response codes considered valid responses.
  63. Procedure DoMethod(Const AMethod,AURL : String; Stream : TStream; Const AllowedResponseCodes : Array of Integer); virtual;
  64. // Send request to server: construct request line and send headers and request body.
  65. procedure SendRequest(const AMethod: String; URI: TURI); virtual;
  66. Public
  67. Constructor Create(AOwner: TComponent); override;
  68. Destructor Destroy; override;
  69. // Request Header management
  70. // Return index of header, -1 if not present.
  71. Function IndexOfHeader(Const AHeader : String) : Integer;
  72. // Add header, replacing an existing one if it exists.
  73. Procedure AddHeader(Const AHeader,AValue : String);
  74. // Return header value, empty if not present.
  75. Function GetHeader(Const AHeader : String) : String;
  76. // General-purpose call.
  77. Procedure HTTPMethod(Const AMethod,AURL : String; Stream : TStream; Const AllowedResponseCodes : Array of Integer); virtual;
  78. // Execute GET on server, store result in Stream, File, StringList or string
  79. Procedure Get(Const AURL : String; Stream : TStream);
  80. Procedure Get(Const AURL : String; const LocalFileName : String);
  81. Procedure Get(Const AURL : String; Response : TStrings);
  82. Function Get(Const AURL : String) : String;
  83. // Simple post
  84. // Post URL, and Requestbody. Return response in Stream, File, TstringList or String;
  85. procedure Post(const URL: string; const Response: TStream);
  86. procedure Post(const URL: string; Response : TStrings);
  87. procedure Post(const URL: string; const LocalFileName: String);
  88. function Post(const URL: string) : String;
  89. // Post Form data (www-urlencoded).
  90. // Formdata in string (urlencoded) or TStrings (plain text) format.
  91. // Form data will be inserted in the requestbody.
  92. // Return response in Stream, File, TStringList or String;
  93. Procedure FormPost(const URL, FormData: string; const Response: TStream);
  94. Procedure FormPost(const URL : string; FormData: TStrings; const Response: TStream);
  95. Procedure FormPost(const URL, FormData: string; const Response: TStrings);
  96. Procedure FormPost(const URL : string; FormData: TStrings; const Response: TStrings);
  97. function FormPost(const URL, FormData: string): String;
  98. function FormPost(const URL: string; FormData : TStrings): String;
  99. // Post a file
  100. Procedure FileFormPost(const AURL, AFieldName, AFileName: string; const Response: TStream);
  101. Protected
  102. // Before request properties.
  103. // Additional headers for request. Host; and Authentication are automatically added.
  104. Property RequestHeaders : TStrings Read FRequestHeaders Write SetRequestHeaders;
  105. // Cookies. Set before request to send cookies to server.
  106. // After request the property is filled with the cookies sent by the server.
  107. Property Cookies : TStrings Read GetCookies Write SetCookies;
  108. // Optional body to send (mainly in POST request)
  109. Property RequestBody : TStream read FRequestBody Write FRequestBody;
  110. // used HTTP version when constructing the request.
  111. Property HTTPversion : String Read FHTTPVersion Write FHTTPVersion;
  112. // After request properties.
  113. // After request, this contains the headers sent by server.
  114. Property ResponseHeaders : TStrings Read FResponseHeaders;
  115. // After request, HTTP version of server reply.
  116. Property ServerHTTPVersion : String Read FServerHTTPVersion;
  117. // After request, HTTP response status of the server.
  118. Property ResponseStatusCode : Integer Read FResponseStatusCode;
  119. // After request, HTTP response status text of the server.
  120. Property ResponseStatusText : String Read FResponseStatusText;
  121. end;
  122. TFPHTTPClient = Class(TFPCustomHTTPClient)
  123. Public
  124. Property RequestHeaders;
  125. Property RequestBody;
  126. Property ResponseHeaders;
  127. Property HTTPversion;
  128. Property ServerHTTPVersion;
  129. Property ResponseStatusCode;
  130. Property ResponseStatusText;
  131. Property Cookies;
  132. end;
  133. EHTTPClient = Class(Exception);
  134. Function EncodeURLElement(S : String) : String;
  135. Function DecodeURLElement(Const S : String) : String;
  136. implementation
  137. resourcestring
  138. SErrInvalidProtocol = 'Invalid protocol : "%s"';
  139. SErrReadingSocket = 'Error reading data from socket';
  140. SErrInvalidProtocolVersion = 'Invalid protocol version in response: "%s"';
  141. SErrInvalidStatusCode = 'Invalid response status code: %s';
  142. SErrUnexpectedResponse = 'Unexpected response status code: %d';
  143. SErrChunkTooBig = 'Chunk too big';
  144. SErrChunkLineEndMissing = 'Chunk line end missing';
  145. Const
  146. CRLF = #13#10;
  147. function EncodeURLElement(S: String): String;
  148. Const
  149. NotAllowed = [ ';', '/', '?', ':', '@', '=', '&', '#', '+', '_', '<', '>',
  150. '"', '%', '{', '}', '|', '\', '^', '~', '[', ']', '`' ];
  151. var
  152. i, o, l : Integer;
  153. h: string[2];
  154. P : PChar;
  155. c: AnsiChar;
  156. begin
  157. l:=Length(S);
  158. If (l=0) then Exit;
  159. SetLength(Result,l*3);
  160. P:=Pchar(Result);
  161. for I:=1 to L do
  162. begin
  163. C:=S[i];
  164. O:=Ord(c);
  165. if (O<=$20) or (O>=$7F) or (c in NotAllowed) then
  166. begin
  167. P^ := '%';
  168. Inc(P);
  169. h := IntToHex(Ord(c), 2);
  170. p^ := h[1];
  171. Inc(P);
  172. p^ := h[2];
  173. Inc(P);
  174. end
  175. else
  176. begin
  177. P^ := c;
  178. Inc(p);
  179. end;
  180. end;
  181. SetLength(Result,P-PChar(Result));
  182. end;
  183. function DecodeURLElement(Const S: AnsiString): AnsiString;
  184. var
  185. i,l,o : Integer;
  186. c: AnsiChar;
  187. p : pchar;
  188. h : string;
  189. begin
  190. l := Length(S);
  191. if l=0 then exit;
  192. SetLength(Result, l);
  193. P:=PChar(Result);
  194. i:=1;
  195. While (I<=L) do
  196. begin
  197. c := S[i];
  198. if (c<>'%') then
  199. begin
  200. P^:=c;
  201. Inc(P);
  202. end
  203. else if (I<L-1) then
  204. begin
  205. H:='$'+Copy(S,I+1,2);
  206. o:=StrToIntDef(H,-1);
  207. If (O>=0) and (O<=255) then
  208. begin
  209. P^:=char(O);
  210. Inc(P);
  211. Inc(I,2);
  212. end;
  213. end;
  214. Inc(i);
  215. end;
  216. SetLength(Result, P-Pchar(Result));
  217. end;
  218. { TFPCustomHTTPClient }
  219. procedure TFPCustomHTTPClient.SetRequestHeaders(const AValue: TStrings);
  220. begin
  221. if FRequestHeaders=AValue then exit;
  222. FRequestHeaders.Assign(AValue);
  223. end;
  224. function TFPCustomHTTPClient.IndexOfHeader(const AHeader: String): Integer;
  225. Var
  226. L : Integer;
  227. H : String;
  228. begin
  229. H:=LowerCase(Aheader);
  230. l:=Length(AHeader);
  231. Result:=Requestheaders.Count-1;
  232. While (Result>=0) and ((LowerCase(Copy(RequestHeaders[Result],1,l)))<>h) do
  233. Dec(Result);
  234. end;
  235. procedure TFPCustomHTTPClient.AddHeader(const AHeader, AValue: String);
  236. Var
  237. J: Integer;
  238. begin
  239. j:=IndexOfHeader(Aheader);
  240. if (J<>-1) then
  241. RequestHeaders.Delete(j);
  242. RequestHeaders.Add(AHeader+': '+Avalue);
  243. end;
  244. function TFPCustomHTTPClient.GetHeader(const AHeader: String): String;
  245. Var
  246. I : Integer;
  247. begin
  248. I:=indexOfHeader(AHeader);
  249. Result:=RequestHeaders[i];
  250. I:=Pos(':',Result);
  251. if (I=0) then
  252. I:=Length(Result);
  253. Delete(Result,1,I);
  254. end;
  255. Function TFPCustomHTTPClient.GetServerURL(URI : TURI) : String;
  256. Var
  257. D : String;
  258. begin
  259. D:=URI.Path;
  260. If (D[1]<>'/') then
  261. D:='/'+D;
  262. If (D[Length(D)]<>'/') then
  263. D:=D+'/';
  264. Result:=D+URI.Document;
  265. if (URI.Params<>'') then
  266. Result:=Result+'?'+URI.Params;
  267. end;
  268. procedure TFPCustomHTTPClient.ConnectToServer(Const AHost : String; APort : Integer);
  269. begin
  270. if Aport=0 then
  271. Aport:=80;
  272. FSocket:=TInetSocket.Create(AHost,APort);
  273. end;
  274. procedure TFPCustomHTTPClient.DisconnectFromServer;
  275. begin
  276. FreeAndNil(FSocket);
  277. end;
  278. function TFPCustomHTTPClient.AllowHeader(Var AHeader : String) : Boolean;
  279. begin
  280. Result:=(AHeader<>'') and (Pos(':',AHeader)<>0);
  281. end;
  282. procedure TFPCustomHTTPClient.SendRequest(Const AMethod : String; URI : TURI);
  283. Var
  284. S,L : String;
  285. I : Integer;
  286. begin
  287. S:=Uppercase(AMethod)+' '+GetServerURL(URI)+' '+'HTTP/'+FHTTPVersion+CRLF;
  288. If (URI.Username<>'') then
  289. S:=S+'Authorization: Basic ' + EncodeStringBase64(URI.UserName+ ':' + URI.Password)+CRLF;
  290. S:=S+'Host: '+URI.Host;
  291. If (URI.Port<>0) then
  292. S:=S+':'+IntToStr(URI.Port);
  293. S:=S+CRLF;
  294. If Assigned(RequestBody) and (IndexOfHeader('Content-length')=-1) then
  295. AddHeader('Content-length',IntToStr(RequestBody.Size));
  296. For I:=0 to FRequestHeaders.Count-1 do
  297. begin
  298. l:=FRequestHeaders[i];
  299. If AllowHeader(L) then
  300. S:=S+L+CRLF;
  301. end;
  302. if Assigned(FCookies) then
  303. begin
  304. L:='Cookie:';
  305. For I:=0 to FCookies.Count-1 do
  306. begin
  307. If (I>0) then
  308. L:=L+'; ';
  309. L:=L+FCookies[i];
  310. end;
  311. if AllowHeader(L) then
  312. S:=S+L+CRLF;
  313. end;
  314. S:=S+CRLF;
  315. FSocket.WriteBuffer(S[1],Length(S));
  316. If Assigned(FRequestBody) then
  317. FSocket.CopyFrom(FRequestBody,FRequestBody.Size);
  318. end;
  319. function TFPCustomHTTPClient.ReadString : String;
  320. Procedure FillBuffer;
  321. Var
  322. R : Integer;
  323. begin
  324. SetLength(FBuffer,ReadBufLen);
  325. r:=FSocket.Read(FBuffer[1],ReadBufLen);
  326. If r<0 then
  327. Raise EHTTPClient.Create(SErrReadingSocket);
  328. if (r<ReadBuflen) then
  329. SetLength(FBuffer,r);
  330. end;
  331. Var
  332. CheckLF,Done : Boolean;
  333. P,L : integer;
  334. begin
  335. Result:='';
  336. Done:=False;
  337. CheckLF:=False;
  338. Repeat
  339. if Length(FBuffer)=0 then
  340. FillBuffer;
  341. if Length(FBuffer)=0 then
  342. Done:=True
  343. else if CheckLF then
  344. begin
  345. If (FBuffer[1]<>#10) then
  346. Result:=Result+#13
  347. else
  348. begin
  349. Delete(FBuffer,1,1);
  350. Done:=True;
  351. end;
  352. end;
  353. if not Done then
  354. begin
  355. P:=Pos(#13#10,FBuffer);
  356. If P=0 then
  357. begin
  358. L:=Length(FBuffer);
  359. CheckLF:=FBuffer[L]=#13;
  360. if CheckLF then
  361. Result:=Result+Copy(FBuffer,1,L-1)
  362. else
  363. Result:=Result+FBuffer;
  364. FBuffer:='';
  365. end
  366. else
  367. begin
  368. Result:=Result+Copy(FBuffer,1,P-1);
  369. Delete(FBuffer,1,P+1);
  370. Done:=True;
  371. end;
  372. end;
  373. until Done;
  374. end;
  375. Function GetNextWord(Var S : String) : string;
  376. Const
  377. WhiteSpace = [' ',#9];
  378. Var
  379. P : Integer;
  380. begin
  381. While (Length(S)>0) and (S[1] in WhiteSpace) do
  382. Delete(S,1,1);
  383. P:=Pos(' ',S);
  384. If (P=0) then
  385. P:=Pos(#9,S);
  386. If (P=0) then
  387. P:=Length(S)+1;
  388. Result:=Copy(S,1,P-1);
  389. Delete(S,1,P);
  390. end;
  391. Function TFPCustomHTTPClient.ParseStatusLine(AStatusLine : String) : Integer;
  392. Var
  393. S : String;
  394. begin
  395. S:=Uppercase(GetNextWord(AStatusLine));
  396. If (Copy(S,1,5)<>'HTTP/') then
  397. Raise EHTTPClient.CreateFmt(SErrInvalidProtocolVersion,[S]);
  398. Delete(S,1,5);
  399. FServerHTTPVersion:=S;
  400. S:=GetNextWord(AStatusLine);
  401. Result:=StrToIntDef(S,-1);
  402. if Result=-1 then
  403. Raise EHTTPClient.CreateFmt(SErrInvalidStatusCode,[S]);
  404. FResponseStatusText:=AStatusLine;
  405. end;
  406. Function TFPCustomHTTPClient.ReadResponseHeaders : Integer;
  407. Procedure DoCookies(S : String);
  408. Var
  409. P : Integer;
  410. C : String;
  411. begin
  412. If Assigned(FCookies) then
  413. FCookies.Clear;
  414. P:=Pos(':',S);
  415. Delete(S,1,P);
  416. Repeat
  417. P:=Pos(';',S);
  418. If (P=0) then
  419. P:=Length(S)+1;
  420. C:=Trim(Copy(S,1,P-1));
  421. Cookies.Add(C);
  422. Delete(S,1,P);
  423. Until (S='');
  424. end;
  425. Const
  426. SetCookie = 'set-cookie';
  427. Var
  428. StatusLine,S : String;
  429. begin
  430. StatusLine:=ReadString;
  431. Result:=ParseStatusLine(StatusLine);
  432. Repeat
  433. S:=ReadString;
  434. if (S<>'') then
  435. begin
  436. ResponseHeaders.Add(S);
  437. If (LowerCase(Copy(S,1,Length(SetCookie)))=SetCookie) then
  438. DoCookies(S);
  439. end
  440. Until (S='');
  441. end;
  442. Function TFPCustomHTTPClient.CheckResponseCode(ACode : Integer; Const AllowedResponseCodes : Array of Integer) : Boolean;
  443. Var
  444. I : Integer;
  445. begin
  446. Result:=(High(AllowedResponseCodes)=-1);
  447. if not Result then
  448. begin
  449. I:=Low(AllowedResponseCodes);
  450. While (Not Result) and (I<=High(AllowedResponseCodes)) do
  451. begin
  452. Result:=(AllowedResponseCodes[i]=ACode);
  453. Inc(I);
  454. end
  455. end;
  456. end;
  457. Function TFPCustomHTTPClient.CheckContentLength: Integer;
  458. Const CL ='content-length:';
  459. Var
  460. S : String;
  461. I : integer;
  462. begin
  463. Result:=-1;
  464. I:=0;
  465. While (Result=-1) and (I<FResponseHeaders.Count) do
  466. begin
  467. S:=Trim(LowerCase(FResponseHeaders[i]));
  468. If (Copy(S,1,Length(Cl))=Cl) then
  469. begin
  470. Delete(S,1,Length(CL));
  471. Result:=StrToIntDef(Trim(S),-1);
  472. end;
  473. Inc(I);
  474. end;
  475. end;
  476. Function TFPCustomHTTPClient.CheckTransferEncoding: string;
  477. Const CL ='transfer-encoding:';
  478. Var
  479. S : String;
  480. I : integer;
  481. begin
  482. Result:='';
  483. I:=0;
  484. While (I<FResponseHeaders.Count) do
  485. begin
  486. S:=Trim(LowerCase(FResponseHeaders[i]));
  487. If (Copy(S,1,Length(Cl))=Cl) then
  488. begin
  489. Delete(S,1,Length(CL));
  490. Result:=Trim(S);
  491. exit;
  492. end;
  493. Inc(I);
  494. end;
  495. end;
  496. function TFPCustomHTTPClient.GetCookies: TStrings;
  497. begin
  498. If (FCookies=Nil) then
  499. FCookies:=TStringList.Create;
  500. Result:=FCookies;
  501. end;
  502. procedure TFPCustomHTTPClient.SetCookies(const AValue: TStrings);
  503. begin
  504. if GetCookies=AValue then exit;
  505. GetCookies.Assign(AValue);
  506. end;
  507. procedure TFPCustomHTTPClient.ReadResponse(Stream: TStream; Const AllowedResponseCodes : Array of Integer);
  508. Function Transfer(LB : Integer) : Integer;
  509. begin
  510. Result:=FSocket.Read(FBuffer[1],LB);
  511. If Result<0 then
  512. Raise EHTTPClient.Create(SErrReadingSocket);
  513. if (Result>0) then
  514. Stream.Write(FBuffer[1],Result);
  515. end;
  516. Procedure ReadChunkedResponse;
  517. { HTTP 1.1 chunked response:
  518. There is no content-length. The response consists of several chunks of
  519. data, each
  520. - beginning with a line
  521. - starting with a hex number DataSize,
  522. - an optional parameter,
  523. - ending with #13#10,
  524. - followed by the data,
  525. - ending with #13#10 (not in DataSize),
  526. It ends when the DataSize is 0.
  527. After the last chunk there can be a some optional entity header fields.
  528. This trailer is not yet implemented. }
  529. var
  530. BufPos: Integer;
  531. function FetchData(out Cnt: integer): boolean;
  532. begin
  533. SetLength(FBuffer,ReadBuflen);
  534. Cnt:=FSocket.Read(FBuffer[1],length(FBuffer));
  535. If Cnt<0 then
  536. Raise EHTTPClient.Create(SErrReadingSocket);
  537. SetLength(FBuffer,Cnt);
  538. BufPos:=1;
  539. Result:=Cnt>0;
  540. end;
  541. Function ReadData(Data: PByte; Cnt: integer): integer;
  542. var
  543. l: Integer;
  544. begin
  545. Result:=0;
  546. while Cnt>0 do
  547. begin
  548. l:=length(FBuffer)-BufPos+1;
  549. if l=0 then
  550. if not FetchData(l) then
  551. exit; // end of stream
  552. if l>Cnt then
  553. l:=Cnt;
  554. System.Move(FBuffer[BufPos],Data^,l);
  555. inc(BufPos,l);
  556. inc(Data,l);
  557. inc(Result,l);
  558. dec(Cnt,l);
  559. end;
  560. end;
  561. var
  562. c: char;
  563. ChunkSize: Integer;
  564. l: Integer;
  565. begin
  566. BufPos:=1;
  567. repeat
  568. // read ChunkSize
  569. ChunkSize:=0;
  570. repeat
  571. if ReadData(@c,1)<1 then exit;
  572. case c of
  573. '0'..'9': ChunkSize:=ChunkSize*16+ord(c)-ord('0');
  574. 'a'..'f': ChunkSize:=ChunkSize*16+ord(c)-ord('a')+10;
  575. 'A'..'F': ChunkSize:=ChunkSize*16+ord(c)-ord('A')+10;
  576. else break;
  577. end;
  578. if ChunkSize>1000000 then
  579. Raise EHTTPClient.Create(SErrChunkTooBig);
  580. until false;
  581. // read till line end
  582. while (c<>#10) do
  583. if ReadData(@c,1)<1 then exit;
  584. if ChunkSize=0 then exit;
  585. // read data
  586. repeat
  587. l:=length(FBuffer)-BufPos+1;
  588. if l=0 then
  589. if not FetchData(l) then
  590. exit; // end of stream
  591. if l>ChunkSize then
  592. l:=ChunkSize;
  593. if l>0 then
  594. begin
  595. // copy chunk data to output
  596. Stream.Write(FBuffer[BufPos],l);
  597. inc(BufPos,l);
  598. dec(ChunkSize,l);
  599. end;
  600. until ChunkSize=0;
  601. // read #13#10
  602. if ReadData(@c,1)<1 then exit;
  603. if c<>#13 then
  604. Raise EHTTPClient.Create(SErrChunkLineEndMissing);
  605. if ReadData(@c,1)<1 then exit;
  606. if c<>#10 then
  607. Raise EHTTPClient.Create(SErrChunkLineEndMissing);
  608. // next chunk
  609. until false;
  610. end;
  611. Var
  612. L,LB,R : Integer;
  613. begin
  614. SetLength(FBuffer,0);
  615. FResponseStatusCode:=ReadResponseHeaders;
  616. if not CheckResponseCode(FResponseStatusCode,AllowedResponseCodes) then
  617. Raise EHTTPClient.CreateFmt(SErrUnexpectedResponse,[ResponseStatusCode]);
  618. if CompareText(CheckTransferEncoding,'chunked')=0 then
  619. ReadChunkedResponse
  620. else
  621. begin
  622. // Write remains of buffer to output.
  623. LB:=Length(FBuffer);
  624. If (LB>0) then
  625. Stream.WriteBuffer(FBuffer[1],LB);
  626. // Now read the rest, if any.
  627. SetLength(FBuffer,ReadBuflen);
  628. L:=CheckContentLength;
  629. If (L>LB) then
  630. begin
  631. // We cannot use copyfrom, it uses ReadBuffer, and this is dangerous with sockets
  632. L:=L-LB;
  633. Repeat
  634. LB:=ReadBufLen;
  635. If (LB>L) then
  636. LB:=L;
  637. R:=Transfer(LB);
  638. L:=L-R;
  639. until (L=0) or (R=0);
  640. end
  641. else if L<0 then
  642. begin
  643. // No content-length, so we read till no more data available.
  644. Repeat
  645. R:=Transfer(ReadBufLen);
  646. until (R=0);
  647. end;
  648. end;
  649. end;
  650. procedure TFPCustomHTTPClient.DoMethod(Const AMethod,AURL: String; Stream: TStream; Const AllowedResponseCodes : Array of Integer);
  651. Var
  652. URI : TURI;
  653. begin
  654. FResponseHeaders.Clear;
  655. URI:=ParseURI(AURL,False);
  656. If (Lowercase(URI.Protocol)<>'http') then
  657. Raise EHTTPClient.CreateFmt(SErrInvalidProtocol,[URI.Protocol]);
  658. ConnectToServer(URI.Host,URI.Port);
  659. try
  660. SendRequest(AMethod,URI);
  661. ReadResponse(Stream,AllowedResponseCodes);
  662. finally
  663. DisconnectFromServer;
  664. end;
  665. end;
  666. constructor TFPCustomHTTPClient.Create(AOwner: TComponent);
  667. begin
  668. inherited Create(AOwner);
  669. FRequestHeaders:=TStringList.Create;
  670. FResponseHeaders:=TStringList.Create;
  671. FHTTPVersion:='1.1';
  672. end;
  673. destructor TFPCustomHTTPClient.Destroy;
  674. begin
  675. FreeAndNil(FRequestHeaders);
  676. FreeAndNil(FResponseHeaders);
  677. inherited Destroy;
  678. end;
  679. procedure TFPCustomHTTPClient.HTTPMethod(const AMethod, AURL: String;
  680. Stream: TStream; const AllowedResponseCodes: array of Integer);
  681. begin
  682. DoMethod(AMethod,AURL,Stream,AllowedResponseCodes);
  683. end;
  684. procedure TFPCustomHTTPClient.Get(Const AURL: String; Stream: TStream);
  685. begin
  686. DoMethod('GET',AURL,Stream,[200]);
  687. end;
  688. procedure TFPCustomHTTPClient.Get(Const AURL: String; const LocalFileName: String);
  689. Var
  690. F : TFileStream;
  691. begin
  692. F:=TFileStream.Create(LocalFileName,fmCreate);
  693. try
  694. Get(AURL,F);
  695. finally
  696. F.Free;
  697. end;
  698. end;
  699. procedure TFPCustomHTTPClient.Get(const AURL: String; Response: TStrings);
  700. begin
  701. Response.Text:=Get(AURL);
  702. end;
  703. function TFPCustomHTTPClient.Get(Const AURL: String): String;
  704. Var
  705. SS : TStringStream;
  706. begin
  707. SS:=TStringStream.Create('');
  708. try
  709. Get(AURL,SS);
  710. Result:=SS.Datastring;
  711. finally
  712. SS.Free;
  713. end;
  714. end;
  715. procedure TFPCustomHTTPClient.Post(const URL: string; const Response: TStream);
  716. begin
  717. DoMethod('POST',URL,Response,[]);
  718. end;
  719. procedure TFPCustomHTTPClient.Post(const URL: string; Response: TStrings);
  720. begin
  721. Response.Text:=Post(URL);
  722. end;
  723. procedure TFPCustomHTTPClient.Post(const URL: string;
  724. const LocalFileName: String);
  725. Var
  726. F : TFileStream;
  727. begin
  728. F:=TFileStream.Create(LocalFileName,fmCreate);
  729. try
  730. Post(URL,F);
  731. finally
  732. F.Free;
  733. end;
  734. end;
  735. function TFPCustomHTTPClient.Post(const URL: string): String;
  736. Var
  737. SS : TStringStream;
  738. begin
  739. SS:=TStringStream.Create('');
  740. try
  741. Post(URL,SS);
  742. Result:=SS.Datastring;
  743. finally
  744. SS.Free;
  745. end;
  746. end;
  747. procedure TFPCustomHTTPClient.FormPost(const URL, FormData: string;
  748. const Response: TStream);
  749. begin
  750. RequestBody:=TStringStream.Create(FormData);
  751. try
  752. AddHeader('Content-Type','application/x-www-form-urlencoded');
  753. Post(URL,Response);
  754. finally
  755. RequestBody.Free;
  756. RequestBody:=Nil;
  757. end;
  758. end;
  759. procedure TFPCustomHTTPClient.FormPost(const URL: string; FormData: TStrings;
  760. const Response: TStream);
  761. Var
  762. I : Integer;
  763. S,N,V : String;
  764. begin
  765. S:='';
  766. For I:=0 to FormData.Count-1 do
  767. begin
  768. If (S<>'') then
  769. S:=S+'&';
  770. FormData.GetNameValue(i,n,v);
  771. S:=S+EncodeURLElement(N)+'='+EncodeURLElement(V);
  772. end;
  773. FormPost(URL,S,Response);
  774. end;
  775. procedure TFPCustomHTTPClient.FormPost(const URL, FormData: string;
  776. const Response: TStrings);
  777. begin
  778. Response.Text:=FormPost(URL,FormData);
  779. end;
  780. procedure TFPCustomHTTPClient.FormPost(const URL: string; FormData: TStrings;
  781. const Response: TStrings);
  782. begin
  783. Response.Text:=FormPost(URL,FormData);
  784. end;
  785. function TFPCustomHTTPClient.FormPost(const URL, FormData: string): String;
  786. Var
  787. SS : TStringStream;
  788. begin
  789. SS:=TStringStream.Create('');
  790. try
  791. FormPost(URL,FormData,SS);
  792. Result:=SS.Datastring;
  793. finally
  794. SS.Free;
  795. end;
  796. end;
  797. function TFPCustomHTTPClient.FormPost(const URL: string; FormData: TStrings
  798. ): String;
  799. Var
  800. SS : TStringStream;
  801. begin
  802. SS:=TStringStream.Create('');
  803. try
  804. FormPost(URL,FormData,SS);
  805. Result:=SS.Datastring;
  806. finally
  807. SS.Free;
  808. end;
  809. end;
  810. procedure TFPCustomHTTPClient.FileFormPost(const AURL, AFieldName, AFileName: string; const Response: TStream);
  811. Var
  812. S, Sep : string;
  813. SS : TStringStream;
  814. F : TFileStream;
  815. begin
  816. Sep:=Format('%.8x_multipart_boundary',[Random($ffffff)]);
  817. AddHeader('Content-type','multipart/form-data; boundary='+Sep);
  818. S:='--'+Sep+CRLF;
  819. s:=s+Format('content-disposition: form-data; name="%s"; filename="%s"'+CRLF,[AFieldName,AFileName]);
  820. s:=s+'Content-Type: Application/octet-string'+CRLF+CRLF;
  821. SS:=TStringStream.Create(s);
  822. try
  823. SS.Seek(0,soFromEnd);
  824. F:=TFileStream.Create(AFileName,fmOpenRead or fmShareDenyWrite);
  825. try
  826. SS.CopyFrom(F,F.Size);
  827. finally
  828. F.Free;
  829. end;
  830. S:=CRLF+'--'+Sep+'--'+CRLF;
  831. SS.WriteBuffer(S[1],Length(S));
  832. SS.Position:=0;
  833. RequestBody:=SS;
  834. Post(AURL,Response);
  835. finally
  836. RequestBody:=Nil;
  837. SS.Free;
  838. end;
  839. end;
  840. end.