httpbase.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. {
  2. HTTPBase: Common HTTP utility declarations and classes
  3. Copyright (C) 2000-2003 by Sebastian Guenther ([email protected])
  4. See the file COPYING.FPC, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. }
  10. unit HTTPBase;
  11. interface
  12. uses Classes, fpAsync;
  13. const
  14. fieldAccept = 'Accept';
  15. fieldAcceptCharset = 'Accept-Charset';
  16. fieldAcceptEncoding = 'Accept-Encoding';
  17. fieldAcceptLanguage = 'Accept-Language';
  18. fieldAuthorization = 'Authorization';
  19. fieldConnection = 'Connection';
  20. fieldContentEncoding = 'Content-Encoding';
  21. fieldContentLanguage = 'Content-Language';
  22. fieldContentLength = 'Content-Length';
  23. fieldContentType = 'Content-Type';
  24. fieldCookie = 'Cookie';
  25. fieldDate = 'Date';
  26. fieldExpires = 'Expires';
  27. fieldFrom = 'From';
  28. fieldIfModifiedSince = 'If-Modified-Since';
  29. fieldLastModified = 'Last-Modified';
  30. fieldLocation = 'Location';
  31. fieldPragma = 'Pragma';
  32. fieldReferer = 'Referer';
  33. fieldRetryAfter = 'Retry-After';
  34. fieldServer = 'Server';
  35. fieldSetCookie = 'Set-Cookie';
  36. fieldUserAgent = 'User-Agent';
  37. fieldWWWAuthenticate = 'WWW-Authenticate';
  38. type
  39. PHttpField = ^THttpField;
  40. THttpField = record
  41. Name, Value: String;
  42. end;
  43. THttpHeader = class
  44. protected
  45. FReader: TAsyncStreamLineReader;
  46. FWriter: TAsyncWriteStream;
  47. FOnCompleted: TNotifyEvent;
  48. FOnEOF: TNotifyEvent;
  49. FFields: TList;
  50. procedure ParseFirstHeaderLine(const line: String); virtual; abstract;
  51. procedure LineReceived(const ALine: String);
  52. procedure ReaderEOF(Sender: TObject);
  53. function GetFirstHeaderLine: String; virtual; abstract;
  54. procedure WriterCompleted(ASender: TObject);
  55. function GetFieldCount: Integer;
  56. function GetFields(AIndex: Integer): String;
  57. function GetFieldNames(AIndex: Integer): String;
  58. procedure SetFieldNames(AIndex: Integer; const AName: String);
  59. function GetFieldValues(AIndex: Integer): String;
  60. procedure SetFieldValues(AIndex: Integer; const AValue: String);
  61. function GetAccept: String;
  62. procedure SetAccept(const AValue: String);
  63. function GetAcceptCharset: String;
  64. procedure SetAcceptCharset(const AValue: String);
  65. function GetAcceptEncoding: String;
  66. procedure SetAcceptEncoding(const AValue: String);
  67. function GetAcceptLanguage: String;
  68. procedure SetAcceptLanguage(const AValue: String);
  69. function GetAuthorization: String;
  70. procedure SetAuthorization(const AValue: String);
  71. function GetConnection: String;
  72. procedure SetConnection(const AValue: String);
  73. function GetContentEncoding: String;
  74. procedure SetContentEncoding(const AValue: String);
  75. function GetContentLanguage: String;
  76. procedure SetContentLanguage(const AValue: String);
  77. function GetContentLength: Integer;
  78. procedure SetContentLength(AValue: Integer);
  79. function GetContentType: String;
  80. procedure SetContentType(const AValue: String);
  81. function Get_Cookie: String;
  82. procedure Set_Cookie(const AValue: String);
  83. function GetDate: String;
  84. procedure SetDate(const AValue: String);
  85. function GetExpires: String;
  86. procedure SetExpires(const AValue: String);
  87. function GetFrom: String;
  88. procedure SetFrom(const AValue: String);
  89. function GetIfModifiedSince: String;
  90. procedure SetIfModifiedSince(const AValue: String);
  91. function GetLastModified: String;
  92. procedure SetLastModified(const AValue: String);
  93. function GetLocation: String;
  94. procedure SetLocation(const AValue: String);
  95. function GetPragma: String;
  96. procedure SetPragma(const AValue: String);
  97. function GetReferer: String;
  98. procedure SetReferer(const AValue: String);
  99. function GetRetryAfter: String;
  100. procedure SetRetryAfter(const AValue: String);
  101. function GetServer: String;
  102. procedure SetServer(const AValue: String);
  103. function Get_SetCookie: String;
  104. procedure Set_SetCookie(const AValue: String);
  105. function GetUserAgent: String;
  106. procedure SetUserAgent(const AValue: String);
  107. function GetWWWAuthenticate: String;
  108. procedure SetWWWAuthenticate(const AValue: String);
  109. public
  110. DataReceived, CmdReceived: Boolean; // !!!: Only temporarily here
  111. HttpVersion: String;
  112. constructor Create;
  113. destructor Destroy; override;
  114. procedure SetFieldByName(const AName, AValue: String);
  115. function GetFieldByName(const AName: String): String;
  116. procedure AsyncSend(AManager: TEventLoop; AStream: THandleStream);
  117. procedure AsyncReceive(AManager: TEventLoop; AStream: THandleStream);
  118. property Reader: TAsyncStreamLineReader read FReader;
  119. property Writer: TAsyncWriteStream read FWriter;
  120. property FieldCount: Integer read GetFieldCount;
  121. property Fields[AIndex: Integer]: String read GetFields;
  122. property FieldNames[AIndex: Integer]: String read GetFieldNames write SetFieldNames;
  123. property FieldValues[AIndex: Integer]: String read GetFieldValues write SetFieldValues;
  124. property OnCompleted: TNotifyEvent read FOnCompleted write FOnCompleted;
  125. property OnEOF: TNotifyEvent read FOnEOF write FOnEOF;
  126. property Accept: String read GetAccept write SetAccept;
  127. property AcceptCharset: String read GetAcceptCharset write SetAcceptCharset;
  128. property AcceptEncoding: String read GetAcceptEncoding write SetAcceptEncoding;
  129. property AcceptLanguage: String read GetAcceptLanguage write SetAcceptLanguage;
  130. property Authorization: String read GetAuthorization write SetAuthorization;
  131. property Connection: String read GetConnection write SetConnection;
  132. property ContentEncoding: String read GetContentEncoding write SetContentEncoding;
  133. property ContentLanguage: String read GetContentLanguage write SetContentLanguage;
  134. property ContentLength: Integer read GetContentLength write SetContentLength;
  135. property ContentType: String read GetContentType write SetContentType;
  136. property Cookie: String read Get_Cookie write Set_Cookie;
  137. property Date: String read GetDate write SetDate;
  138. property Expires: String read GetExpires write SetExpires;
  139. property From: String read GetFrom write SetFrom;
  140. property IfModifiedSince: String read GetIfModifiedSince write SetIfModifiedSince;
  141. property LastModified: String read GetLastModified write SetLastModified;
  142. property Location: String read GetLocation write SetLocation;
  143. property Pragma: String read GetPragma write SetPragma;
  144. property Referer: String read GetReferer write SetReferer;
  145. property RetryAfter: String read GetRetryAfter write SetRetryAfter;
  146. property Server: String read GetServer write SetServer;
  147. property SetCookie: String read Get_SetCookie write Set_SetCookie;
  148. property UserAgent: String read GetUserAgent write SetUserAgent;
  149. property WWWAuthenticate: String read GetWWWAuthenticate write SetWWWAuthenticate;
  150. end;
  151. THttpRequestHeader = class(THttpHeader)
  152. protected
  153. procedure ParseFirstHeaderLine(const line: String); override;
  154. function GetFirstHeaderLine: String; override;
  155. public
  156. CommandLine: String;
  157. Command: String;
  158. URI: String; // Uniform Resource Identifier
  159. QueryString: String;
  160. end;
  161. THttpResponseHeader = class(THttpHeader)
  162. protected
  163. procedure ParseFirstHeaderLine(const line: String); override;
  164. function GetFirstHeaderLine: String; override;
  165. public
  166. Code: Integer;
  167. CodeText: String;
  168. constructor Create;
  169. end;
  170. implementation
  171. uses SysUtils;
  172. // THttpHeader
  173. procedure THttpHeader.LineReceived(const ALine: String);
  174. var
  175. i: Integer;
  176. begin
  177. if Length(ALine) = 0 then
  178. begin
  179. FReader.OnLine := nil; // Stop receiving
  180. FReader.StopAndFree;
  181. if Assigned(FOnCompleted) then
  182. FOnCompleted(Self);
  183. FReader := nil;
  184. end else
  185. DataReceived := True;
  186. if not CmdReceived then
  187. begin
  188. CmdReceived := True;
  189. ParseFirstHeaderLine(ALine);
  190. end else
  191. begin
  192. i := Pos(':', ALine);
  193. SetFieldByName(Trim(Copy(ALine, 1, i - 1)),
  194. Trim(Copy(ALine, i + 1, Length(ALine))));
  195. end;
  196. end;
  197. procedure THttpHeader.ReaderEOF(Sender: TObject);
  198. begin
  199. if Assigned(OnEOF) then
  200. OnEOF(Self);
  201. end;
  202. procedure THttpHeader.WriterCompleted(ASender: TObject);
  203. begin
  204. if Assigned(FOnCompleted) then
  205. FOnCompleted(Self);
  206. FreeAndNil(FWriter);
  207. end;
  208. function THttpHeader.GetFieldCount: Integer;
  209. begin
  210. Result := FFields.Count;
  211. end;
  212. function THttpHeader.GetFields(AIndex: Integer): String;
  213. begin
  214. Result := FieldNames[AIndex] + ': ' + FieldValues[AIndex];
  215. end;
  216. function THttpHeader.GetFieldNames(AIndex: Integer): String;
  217. begin
  218. Result := PHttpField(FFields.Items[AIndex])^.Name;
  219. end;
  220. procedure THttpHeader.SetFieldNames(AIndex: Integer; const AName: String);
  221. begin
  222. PHttpField(FFields.Items[AIndex])^.Name := AName;
  223. end;
  224. function THttpHeader.GetFieldValues(AIndex: Integer): String;
  225. begin
  226. Result := PHttpField(FFields.Items[AIndex])^.Value;
  227. end;
  228. procedure THttpHeader.SetFieldValues(AIndex: Integer; const AValue: String);
  229. begin
  230. PHttpField(FFields.Items[AIndex])^.Value := AValue;
  231. end;
  232. function THttpHeader.GetAccept: String; begin Result := GetFieldByName(fieldAccept) end;
  233. procedure THttpHeader.SetAccept(const AValue: String); begin SetFieldByName(fieldAccept, AValue) end;
  234. function THttpHeader.GetAcceptCharset: String; begin Result := GetFieldByName(fieldAcceptCharset) end;
  235. procedure THttpHeader.SetAcceptCharset(const AValue: String); begin SetFieldByName(fieldAcceptCharset, AValue) end;
  236. function THttpHeader.GetAcceptEncoding: String; begin Result := GetFieldByName(fieldAcceptEncoding) end;
  237. procedure THttpHeader.SetAcceptEncoding(const AValue: String); begin SetFieldByName(fieldAcceptEncoding, AValue) end;
  238. function THttpHeader.GetAcceptLanguage: String; begin Result := GetFieldByName(fieldAcceptLanguage) end;
  239. procedure THttpHeader.SetAcceptLanguage(const AValue: String); begin SetFieldByName(fieldAcceptLanguage, AValue) end;
  240. function THttpHeader.GetAuthorization: String; begin Result := GetFieldByName(fieldAuthorization) end;
  241. procedure THttpHeader.SetAuthorization(const AValue: String); begin SetFieldByName(fieldAuthorization, AValue) end;
  242. function THttpHeader.GetConnection: String; begin Result := GetFieldByName(fieldConnection) end;
  243. procedure THttpHeader.SetConnection(const AValue: String); begin SetFieldByName(fieldConnection, AValue) end;
  244. function THttpHeader.GetContentEncoding: String; begin Result := GetFieldByName(fieldContentEncoding) end;
  245. procedure THttpHeader.SetContentEncoding(const AValue: String); begin SetFieldByName(fieldContentEncoding, AValue) end;
  246. function THttpHeader.GetContentLanguage: String; begin Result := GetFieldByName(fieldContentLanguage) end;
  247. procedure THttpHeader.SetContentLanguage(const AValue: String); begin SetFieldByName(fieldContentLanguage, AValue) end;
  248. function THttpHeader.GetContentLength: Integer; var s: String; begin s := GetFieldByName(fieldContentLength); if Length(s) = 0 then Result := -1 else Result := StrToInt(s) end;
  249. procedure THttpHeader.SetContentLength(AValue: Integer); begin SetFieldByName(fieldContentLength, IntToStr(AValue)) end;
  250. function THttpHeader.GetContentType: String; begin Result := GetFieldByName(fieldContentType) end;
  251. procedure THttpHeader.SetContentType(const AValue: String); begin SetFieldByName(fieldContentType, AValue) end;
  252. function THttpHeader.Get_Cookie: String; begin Result := GetFieldByName(fieldCookie) end;
  253. procedure THttpHeader.Set_Cookie(const AValue: String); begin SetFieldByName(fieldCookie, AValue) end;
  254. function THttpHeader.GetDate: String; begin Result := GetFieldByName(fieldDate) end;
  255. procedure THttpHeader.SetDate(const AValue: String); begin SetFieldByName(fieldDate, AValue) end;
  256. function THttpHeader.GetExpires: String; begin Result := GetFieldByName(fieldExpires) end;
  257. procedure THttpHeader.SetExpires(const AValue: String); begin SetFieldByName(fieldExpires, AValue) end;
  258. function THttpHeader.GetFrom: String; begin Result := GetFieldByName(fieldFrom) end;
  259. procedure THttpHeader.SetFrom(const AValue: String); begin SetFieldByName(fieldFrom, AValue) end;
  260. function THttpHeader.GetIfModifiedSince: String; begin Result := GetFieldByName(fieldIfModifiedSince) end;
  261. procedure THttpHeader.SetIfModifiedSince(const AValue: String); begin SetFieldByName(fieldIfModifiedSince, AValue) end;
  262. function THttpHeader.GetLastModified: String; begin Result := GetFieldByName(fieldLastModified) end;
  263. procedure THttpHeader.SetLastModified(const AValue: String); begin SetFieldByName(fieldLastModified, AValue) end;
  264. function THttpHeader.GetLocation: String; begin Result := GetFieldByName(fieldLocation) end;
  265. procedure THttpHeader.SetLocation(const AValue: String); begin SetFieldByName(fieldLocation, AValue) end;
  266. function THttpHeader.GetPragma: String; begin Result := GetFieldByName(fieldPragma) end;
  267. procedure THttpHeader.SetPragma(const AValue: String); begin SetFieldByName(fieldPragma, AValue) end;
  268. function THttpHeader.GetReferer: String; begin Result := GetFieldByName(fieldReferer) end;
  269. procedure THttpHeader.SetReferer(const AValue: String); begin SetFieldByName(fieldReferer, AValue) end;
  270. function THttpHeader.GetRetryAfter: String; begin Result := GetFieldByName(fieldRetryAfter) end;
  271. procedure THttpHeader.SetRetryAfter(const AValue: String); begin SetFieldByName(fieldRetryAfter, AValue) end;
  272. function THttpHeader.GetServer: String; begin Result := GetFieldByName(fieldServer) end;
  273. procedure THttpHeader.SetServer(const AValue: String); begin SetFieldByName(fieldServer, AValue) end;
  274. function THttpHeader.Get_SetCookie: String; begin Result := GetFieldByName(fieldSetCookie) end;
  275. procedure THttpHeader.Set_SetCookie(const AValue: String); begin SetFieldByName(fieldSetCookie, AValue) end;
  276. function THttpHeader.GetUserAgent: String; begin Result := GetFieldByName(fieldUserAgent) end;
  277. procedure THttpHeader.SetUserAgent(const AValue: String); begin SetFieldByName(fieldUserAgent, AValue) end;
  278. function THttpHeader.GetWWWAuthenticate: String; begin Result := GetFieldByName(fieldWWWAuthenticate) end;
  279. procedure THttpHeader.SetWWWAuthenticate(const AValue: String); begin SetFieldByName(fieldWWWAuthenticate, AValue) end;
  280. constructor THttpHeader.Create;
  281. begin
  282. inherited Create;
  283. FFields := TList.Create;
  284. HttpVersion := '1.1';
  285. end;
  286. destructor THttpHeader.Destroy;
  287. var
  288. i: Integer;
  289. Field: PHttpField;
  290. begin
  291. if Assigned(FReader) then
  292. FReader.StopAndFree;
  293. if Assigned(FWriter) then
  294. FWriter.StopAndFree;
  295. for i := 0 to FFields.Count - 1 do
  296. begin
  297. Field := PHttpField(FFields.Items[i]);
  298. { SetLength(Field^.Name, 0);
  299. SetLength(Field^.Value, 0);}
  300. Dispose(Field);
  301. end;
  302. FFields.Free;
  303. inherited Destroy;
  304. end;
  305. function THttpHeader.GetFieldByName(const AName: String): String;
  306. var
  307. i: Integer;
  308. Name: String;
  309. begin
  310. Name := UpperCase(AName);
  311. for i := 0 to FFields.Count - 1 do
  312. if UpperCase(FieldNames[i]) = Name then
  313. begin
  314. Result := FieldValues[i];
  315. exit;
  316. end;
  317. SetLength(Result, 0);
  318. end;
  319. procedure THttpHeader.SetFieldByName(const AName, AValue: String);
  320. var
  321. i: Integer;
  322. Name: String;
  323. Field: PHttpField;
  324. begin
  325. Name := UpperCase(AName);
  326. for i := 0 to FFields.Count - 1 do
  327. if UpperCase(FieldNames[i]) = Name then
  328. begin
  329. FieldNames[i] := AName; // preserve case
  330. FieldValues[i] := AValue;
  331. exit;
  332. end;
  333. New(Field);
  334. FillChar(Field^, SizeOf(Field^), 0);
  335. Field^.Name := AName;
  336. Field^.Value := AValue;
  337. FFields.Add(field);
  338. end;
  339. procedure THttpHeader.AsyncSend(AManager: TEventLoop; AStream: THandleStream);
  340. var
  341. i: Integer;
  342. begin
  343. if Assigned(FWriter) then
  344. FWriter.StopAndFree;
  345. FWriter := TAsyncWriteStream.Create(AManager, AStream);
  346. FWriter.OnBufferSent := @WriterCompleted;
  347. FWriter.EndOfLineMarker := #13#10;
  348. FWriter.WriteLine(GetFirstHeaderLine);
  349. for i := 0 to FFields.Count - 1 do
  350. FWriter.WriteLine(Fields[i]);
  351. FWriter.WriteLine('');
  352. end;
  353. procedure THttpHeader.AsyncReceive(AManager: TEventLoop; AStream: THandleStream);
  354. begin
  355. CmdReceived := False;
  356. FReader.Free;
  357. FReader := TAsyncStreamLineReader.Create(AManager, AStream);
  358. FReader.OnLine := @LineReceived;
  359. FReader.OnEOF := @ReaderEOF;
  360. end;
  361. // -------------------------------------------------------------------
  362. // THttpRequestHeader
  363. // -------------------------------------------------------------------
  364. procedure THttpRequestHeader.ParseFirstHeaderLine(const line: String);
  365. var
  366. i: Integer;
  367. begin
  368. CommandLine := line;
  369. i := Pos(' ', line);
  370. Command := UpperCase(Copy(line, 1, i - 1));
  371. URI := Copy(line, i + 1, Length(line));
  372. // Extract HTTP version
  373. i := Pos(' ', URI);
  374. if i > 0 then
  375. begin
  376. HttpVersion := Copy(URI, i + 1, Length(URI));
  377. URI := Copy(URI, 1, i - 1);
  378. HttpVersion := Copy(HttpVersion, Pos('/', HttpVersion) + 1, Length(HttpVersion));
  379. end;
  380. // Extract query string
  381. i := Pos('?', URI);
  382. if i > 0 then
  383. begin
  384. QueryString := Copy(URI, i + 1, Length(URI));
  385. URI := Copy(URI, 1, i - 1);
  386. end;
  387. end;
  388. function THttpRequestHeader.GetFirstHeaderLine: String;
  389. begin
  390. Result := Command + ' ' + URI;
  391. if Length(HttpVersion) > 0 then
  392. Result := Result + ' HTTP/' + HttpVersion;
  393. end;
  394. // -------------------------------------------------------------------
  395. // THttpResponseHeader
  396. // -------------------------------------------------------------------
  397. procedure THttpResponseHeader.ParseFirstHeaderLine(const line: String);
  398. var
  399. i: Integer;
  400. s: String;
  401. begin
  402. i := Pos('/', line);
  403. s := Copy(line, i + 1, Length(line));
  404. i := Pos(' ', s);
  405. HttpVersion := Copy(s, 1, i - 1);
  406. s := Copy(s, i + 1, Length(s));
  407. i := Pos(' ', s);
  408. if i > 0 then begin
  409. CodeText := Copy(s, i + 1, Length(s));
  410. s := Copy(s, 1, i - 1);
  411. end;
  412. Code := StrToInt(s);
  413. end;
  414. function THttpResponseHeader.GetFirstHeaderLine: String;
  415. begin
  416. Result := Format('HTTP/%s %d %s', [HttpVersion, Code, CodeText]);
  417. end;
  418. constructor THttpResponseHeader.Create;
  419. begin
  420. inherited Create;
  421. Code := 200;
  422. CodeText := 'OK';
  423. end;
  424. end.