IdCookie.pas 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.6 2004.10.27 9:17:46 AM czhower
  18. For TIdStrings
  19. Rev 1.5 10/26/2004 11:08:08 PM JPMugaas
  20. Updated refs.
  21. Rev 1.4 13.04.2004 12:56:44 ARybin
  22. M$ IE behavior
  23. Rev 1.3 2004.02.03 5:45:00 PM czhower
  24. Name changes
  25. Rev 1.2 2004.01.22 6:09:02 PM czhower
  26. IdCriticalSection
  27. Rev 1.1 1/22/2004 7:09:58 AM JPMugaas
  28. Tried to fix AnsiSameText depreciation.
  29. Rev 1.0 11/14/2002 02:16:20 PM JPMugaas
  30. Mar-31-2001 Doychin Bondzhev
  31. - Changes in the class heirarchy to implement Netscape specification[Netscape],
  32. RFC 2109[RFC2109] & 2965[RFC2965]
  33. Feb-2001 Doychin Bondzhev
  34. - Initial release
  35. }
  36. unit IdCookie;
  37. {
  38. Implementation of the HTTP State Management Mechanism as specified in RFC 6265.
  39. Author: Remy Lebeau ([email protected])
  40. Copyright: (c) Chad Z. Hower and The Indy Team.
  41. TIdCookie - The base code used in all cookies.
  42. REFERENCES
  43. -------------------
  44. [RFC6265] Barth, A, "HTTP State Management Mechanism",
  45. RFC 6265, April 2011.
  46. [DRAFT-ORIGIN-01] Pettersen, Y, "Identifying origin server of HTTP Cookies",
  47. Internet-Draft, March 07, 2010.
  48. http://www.ietf.org/id/draft-pettersen-cookie-origin-01.txt
  49. [DRAFT-COOKIEv2-05] Pettersen, Y, "HTTP State Management Mechanism v2",
  50. Internet-Draft, March 07, 2010.
  51. http://www.ietf.org/id/draft-pettersen-cookie-v2-05.txt
  52. }
  53. interface
  54. {$I IdCompilerDefines.inc}
  55. uses
  56. Classes,
  57. {$IFDEF HAS_UNIT_Generics_Collections}
  58. System.Generics.Collections,
  59. {$ENDIF}
  60. IdGlobal, IdException, IdGlobalProtocols, IdURI,
  61. SysUtils;
  62. type
  63. { Base Cookie class as described in [RFC6265] }
  64. TIdCookie = class(TCollectionItem)
  65. protected
  66. FDomain: String;
  67. FExpires: TDateTime;
  68. FHttpOnly: Boolean;
  69. FName: String;
  70. FPath: String;
  71. FSecure: Boolean;
  72. FValue: String;
  73. FCreatedAt: TDateTime;
  74. FHostOnly: Boolean;
  75. FLastAccessed: TDateTime;
  76. FPersistent: Boolean;
  77. FSameSite: String;
  78. function GetIsExpired: Boolean;
  79. function GetServerCookie: String; virtual;
  80. function GetClientCookie: String; virtual;
  81. function GetMaxAge: Int64;
  82. public
  83. constructor Create(ACollection: TCollection); override;
  84. destructor Destroy; override;
  85. procedure Assign(Source: TPersistent); override;
  86. function IsAllowed(AURI: TIdURI; SecureOnly: Boolean): Boolean; virtual;
  87. function ParseClientCookie(const ACookieText: String): Boolean; virtual;
  88. function ParseServerCookie(const ACookieText: String; AURI: TIdURI): Boolean; virtual;
  89. property ClientCookie: String read GetClientCookie;
  90. property CookieName: String read FName write FName;
  91. property CookieText: String read GetServerCookie; // {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPECATED_MSG} 'Use ServerCookie property instead'{$ENDIF};{$ENDIF}
  92. property Domain: String read FDomain write FDomain;
  93. property Expires: TDateTime read FExpires write FExpires;
  94. property HttpOnly: Boolean read FHttpOnly write FHttpOnly;
  95. property Path: String read FPath write FPath;
  96. property Secure: Boolean read FSecure write FSecure;
  97. property ServerCookie: String read GetServerCookie;
  98. property Value: String read FValue write FValue;
  99. property MaxAge: Int64 read GetMaxAge;
  100. property CreatedAt: TDateTime read FCreatedAt write FCreatedAt;
  101. property IsExpired: Boolean read GetIsExpired;
  102. property HostOnly: Boolean read FHostOnly write FHostOnly;
  103. property LastAccessed: TDateTime read FLastAccessed write FLastAccessed;
  104. property Persistent: Boolean read FPersistent write FPersistent;
  105. property SameSite: String read FSameSite write FSameSite;
  106. // TODO: add property for user-defined attributes...
  107. end;
  108. TIdCookieClass = class of TIdCookie;
  109. { The Cookie collection }
  110. {$IFDEF HAS_GENERICS_TList}
  111. TIdCookieList = TList<TIdCookie>;
  112. {$ELSE}
  113. TIdCookieList = class(TList)
  114. protected
  115. function GetCookie(Index: Integer): TIdCookie;
  116. procedure SetCookie(Index: Integer; AValue: TIdCookie);
  117. public
  118. function IndexOfCookie(ACookie: TIdCookie): Integer;
  119. property Cookies[Index: Integer]: TIdCookie read GetCookie write SetCookie; default;
  120. end;
  121. {$ENDIF}
  122. TIdCookieAccess = (caRead, caReadWrite);
  123. TIdCookies = class(TOwnedCollection)
  124. protected
  125. FCookieList: TIdCookieList;
  126. FRWLock: TMultiReadExclusiveWriteSynchronizer;
  127. function GetCookieByNameAndDomain(const AName, ADomain: string): TIdCookie;
  128. function GetCookie(Index: Integer): TIdCookie;
  129. procedure SetCookie(Index: Integer; const Value: TIdCookie);
  130. public
  131. constructor Create(AOwner: TPersistent);
  132. destructor Destroy; override;
  133. function Add: TIdCookie; reintroduce;
  134. function AddCookie(ACookie: TIdCookie; AURI: TIdURI; AReplaceOld: Boolean = True): Boolean;
  135. function AddClientCookie(const ACookie: string): TIdCookie;
  136. procedure AddClientCookies(const ACookie: string); overload;
  137. procedure AddClientCookies(const ACookies: TStrings); overload;
  138. function AddServerCookie(const ACookie: string; AURI: TIdURI): TIdCookie;
  139. procedure AddServerCookies(const ACookies: TStrings; AURI: TIdURI);
  140. procedure AddCookies(ASource: TIdCookies);
  141. procedure Assign(ASource: TPersistent); override;
  142. procedure Clear; reintroduce;
  143. function GetCookieIndex(const AName: string; FirstIndex: Integer = 0): Integer; overload;
  144. function GetCookieIndex(const AName, ADomain: string; FirstIndex: integer = 0): Integer; overload;
  145. function LockCookieList(AAccessType: TIdCookieAccess): TIdCookieList;
  146. procedure UnlockCookieList(AAccessType: TIdCookieAccess);
  147. property Cookie[const AName, ADomain: string]: TIdCookie read GetCookieByNameAndDomain;
  148. property Cookies[Index: Integer]: TIdCookie read GetCookie write SetCookie; Default;
  149. end;
  150. EIdCookieError = class(EIdException);
  151. function IsDomainMatch(const AUriHost, ACookieDomain: String): Boolean;
  152. function IsPathMatch(const AUriPath, ACookiePath: String): Boolean;
  153. function CanonicalizeHostName(const AHost: String): String;
  154. implementation
  155. {$IFDEF VCL_XE3_OR_ABOVE}
  156. uses
  157. //facilitate inlining only.
  158. System.Types;
  159. {$ENDIF}
  160. function GetDefaultPath(const AURL: TIdURI): String;
  161. var
  162. LUrlPath: string;
  163. Idx: Integer;
  164. begin
  165. {
  166. Per RFC 6265, Section 5.1.4:
  167. The user agent MUST use an algorithm equivalent to the following
  168. algorithm to compute the default-path of a cookie:
  169. 1. Let uri-path be the path portion of the request-uri if such a
  170. portion exists (and empty otherwise). For example, if the
  171. request-uri contains just a path (and optional query string),
  172. then the uri-path is that path (without the %x3F ("?") character
  173. or query string), and if the request-uri contains a full
  174. absoluteURI, the uri-path is the path component of that URI.
  175. 2. If the uri-path is empty or if the first character of the uri-
  176. path is not a %x2F ("/") character, output %x2F ("/") and skip
  177. the remaining steps.
  178. 3. If the uri-path contains no more than one %x2F ("/") character,
  179. output %x2F ("/") and skip the remaining steps.
  180. 4. Output the characters of the uri-path from the first character up
  181. to, but not including, the right-most %x2F ("/").
  182. }
  183. LUrlPath := AURL.Path + AURL.Document;
  184. if TextStartsWith(LUrlPath, '/') then begin {do not localize}
  185. Idx := RPos('/', LUrlPath); {do not localize}
  186. if Idx > 1 then begin
  187. Result := Copy(LUrlPath, 1, Idx-1);
  188. Exit;
  189. end;
  190. end;
  191. Result := '/'; {do not localize}
  192. end;
  193. function CanonicalizeHostName(const AHost: String): String;
  194. begin
  195. // TODO: implement this
  196. {
  197. Per RFC 6265 Section 5.1.2:
  198. A canonicalized host name is the string generated by the following
  199. algorithm:
  200. 1. Convert the host name to a sequence of individual domain name
  201. labels.
  202. 2. Convert each label that is not a Non-Reserved LDH (NR_LDH) label,
  203. to an A-label (see Section 2.3.2.1 of [RFC5890] for the fomer
  204. and latter), or to a "punycode label" (a label resulting from the
  205. "ToASCII" conversion in Section 4 of [RFC3490]), as appropriate
  206. (see Section 6.3 of this specification).
  207. 3. Concatentate the resulting labels, separated by a %x2E (".")
  208. character.
  209. }
  210. Result := AHost;
  211. end;
  212. function IsDomainMatch(const AUriHost, ACookieDomain: String): Boolean;
  213. var
  214. LHost, LDomain: String;
  215. begin
  216. {
  217. Per RFC 6265 Section 5.1.3:
  218. A string domain-matches a given domain string if at least one of the
  219. following conditions hold:
  220. o The domain string and the string are identical. (Note that both
  221. the domain string and the string will have been canonicalized to
  222. lower case at this point.)
  223. o All of the following conditions hold:
  224. * The domain string is a suffix of the string.
  225. * The last character of the string that is not included in the
  226. domain string is a %x2E (".") character.
  227. * The string is a host name (i.e., not an IP address).
  228. }
  229. Result := False;
  230. LHost := CanonicalizeHostName(AUriHost);
  231. LDomain := CanonicalizeHostName(ACookieDomain);
  232. if (LHost <> '') and (LDomain <> '') then begin
  233. if TextIsSame(LHost, LDomain) then begin
  234. Result := True;
  235. end
  236. else if TextEndsWith(LHost, LDomain) then
  237. begin
  238. if TextEndsWith(Copy(LHost, 1, Length(LHost)-Length(LDomain)), '.') then begin
  239. Result := IsHostName(LHost);
  240. end;
  241. end;
  242. end;
  243. end;
  244. function IsPathMatch(const AUriPath, ACookiePath: String): Boolean;
  245. begin
  246. {
  247. Per RFC 6265 Section 5.1.4:
  248. A request-path path-matches a given cookie-path if at least one of
  249. the following conditions hold:
  250. o The cookie-path and the request-path are identical.
  251. o The cookie-path is a prefix of the request-path and the last
  252. character of the cookie-path is %x2F ("/").
  253. o The cookie-path is a prefix of the request-path and the first
  254. character of the request-path that is not included in the cookie-
  255. path is a %x2F ("/") character.
  256. }
  257. Result := TextIsSame(AUriPath, ACookiePath) or
  258. (
  259. TextStartsWith(AUriPath, ACookiePath) and
  260. (
  261. TextEndsWith(ACookiePath, '/') or
  262. CharEquals(AUriPath, Length(ACookiePath)+1, '/')
  263. )
  264. );
  265. end;
  266. function IsHTTP(const AProtocol: String): Boolean;
  267. begin
  268. Result := PosInStrArray(AProtocol, ['http', 'https', 'shttp'], False) <> -1; {do not localize}
  269. end;
  270. function IsSecure(const AProtocol: String): Boolean;
  271. begin
  272. Result := PosInStrArray(AProtocol, ['https', 'shttp'{, ...}], False) <> -1; {do not localize}
  273. end;
  274. { base functions used for construction of Cookie text }
  275. procedure AddCookieProperty(var VCookie: String;
  276. const AProperty, AValue: String);
  277. begin
  278. if Length(AValue) > 0 then
  279. begin
  280. if Length(VCookie) > 0 then begin
  281. VCookie := VCookie + '; '; {Do not Localize}
  282. end;
  283. // TODO: encode illegal characters?
  284. VCookie := VCookie + AProperty + '=' + AValue; {Do not Localize}
  285. end;
  286. end;
  287. procedure AddCookieFlag(var VCookie: String; const AFlag: String);
  288. begin
  289. if Length(VCookie) > 0 then begin
  290. VCookie := VCookie + '; '; { Do not Localize }
  291. end;
  292. VCookie := VCookie + AFlag;
  293. end;
  294. { TIdCookieList }
  295. {$IFNDEF HAS_GENERICS_TList}
  296. function TIdCookieList.GetCookie(Index: Integer): TIdCookie;
  297. begin
  298. Result := TIdCookie(Items[Index]);
  299. end;
  300. procedure TIdCookieList.SetCookie(Index: Integer; AValue: TIdCookie);
  301. begin
  302. Items[Index] := AValue;
  303. end;
  304. function TIdCookieList.IndexOfCookie(ACookie: TIdCookie): Integer;
  305. begin
  306. for Result := 0 to Count - 1 do
  307. begin
  308. if GetCookie(Result) = ACookie then begin
  309. Exit;
  310. end;
  311. end;
  312. Result := -1;
  313. end;
  314. {$ENDIF}
  315. { TIdCookie }
  316. constructor TIdCookie.Create(ACollection: TCollection);
  317. begin
  318. inherited Create(ACollection);
  319. FCreatedAt := Now;
  320. FLastAccessed := FCreatedAt;
  321. end;
  322. destructor TIdCookie.Destroy;
  323. var
  324. LCookieList: TIdCookieList;
  325. begin
  326. try
  327. if Assigned(Collection) then
  328. begin
  329. LCookieList := TIdCookies(Collection).LockCookieList(caReadWrite);
  330. try
  331. LCookieList.Remove(Self);
  332. finally
  333. TIdCookies(Collection).UnlockCookieList(caReadWrite);
  334. end;
  335. end;
  336. finally
  337. inherited Destroy;
  338. end;
  339. end;
  340. procedure TIdCookie.Assign(Source: TPersistent);
  341. var
  342. LSource: TIdCookie;
  343. begin
  344. if Source is TIdCookie then
  345. begin
  346. LSource := TIdCookie(Source);
  347. FDomain := LSource.FDomain;
  348. FExpires := LSource.FExpires;
  349. FHttpOnly := LSource.FHttpOnly;
  350. FName := LSource.FName;
  351. FPath := LSource.FPath;
  352. FSecure := LSource.FSecure;
  353. FValue := LSource.FValue;
  354. FCreatedAt := LSource.FCreatedAt;
  355. FHostOnly := LSource.FHostOnly;
  356. FLastAccessed := LSource.FLastAccessed;
  357. FPersistent := LSource.FPersistent;
  358. FSameSite := LSource.FSameSite;
  359. end else
  360. begin
  361. inherited Assign(Source);
  362. end;
  363. end;
  364. function TIdCookie.IsAllowed(AURI: TIdURI; SecureOnly: Boolean): Boolean;
  365. function MatchesHost: Boolean;
  366. begin
  367. if HostOnly then begin
  368. Result := TextIsSame(CanonicalizeHostName(AURI.Host), Domain);
  369. end else begin
  370. Result := IsDomainMatch(AURI.Host, Domain);
  371. end;
  372. end;
  373. begin
  374. // using the algorithm defined in RFC 6265 section 5.4...
  375. Result := MatchesHost and IsPathMatch(AURI.Path + AURI.Document, Path) and
  376. ((not Secure) or (Secure and SecureOnly)) and
  377. ((not HttpOnly) or (HttpOnly and IsHTTP(AURI.Protocol)))
  378. // TODO:
  379. //and ((SameSite = 'None') or (not CrossSite) or ((SameSite = 'Lax') and (Request.Method is safe) and (Request.TargetBrowsingContext = TopLevelBrowsingContext)))
  380. ;
  381. end;
  382. {$IFNDEF HAS_TryStrToInt64}
  383. // TODO: move this to IdGlobalProtocols...
  384. function TryStrToInt64(const S: string; out Value: Int64): Boolean;
  385. {$IFDEF USE_INLINE}inline;{$ENDIF}
  386. var
  387. E: Integer;
  388. begin
  389. Val(S, Value, E);
  390. Result := E = 0;
  391. end;
  392. {$ENDIF}
  393. function TIdCookie.ParseServerCookie(const ACookieText: String; AURI: TIdURI): Boolean;
  394. const
  395. cTokenSeparators = '()<>@,;:\"/[]?={} '#9;
  396. procedure SplitCookieText(const CookieProp: TStringList; const S: string);
  397. var
  398. LNameValue, LAttrs, LAttr, LName, LValue: String;
  399. LSecs: Int64;
  400. LExpiryTime: TDateTime;
  401. i: Integer;
  402. begin
  403. I := Pos(';', ACookieText);
  404. if I > 0 then
  405. begin
  406. LNameValue := Copy(ACookieText, 1, I-1);
  407. LAttrs := Copy(ACookieText, I, MaxInt);
  408. end else
  409. begin
  410. LNameValue := ACookieText;
  411. LAttrs := '';
  412. end;
  413. I := Pos('=', LNameValue);
  414. if I = 0 then begin
  415. Exit;
  416. end;
  417. LName := Trim(Copy(LNameValue, 1, I-1));
  418. if LName = '' then begin
  419. Exit;
  420. end;
  421. LValue := Trim(Copy(LNameValue, I+1, MaxInt));
  422. // RLebeau 11/17/2020: no longer stripping off quotes here!
  423. // Some servers require them to remain intact....
  424. {
  425. if TextStartsWith(LValue, '"') then begin
  426. IdDelete(LValue, 1, 1);
  427. LNameValue := LValue;
  428. LValue := Fetch(LNameValue, '"');
  429. end;
  430. }
  431. IndyAddPair(CookieProp, LName, LValue);
  432. while LAttrs <> '' do
  433. begin
  434. IdDelete(LAttrs, 1, 1); // remove the leading ';'
  435. I := Pos(';', LAttrs);
  436. if I > 0 then begin
  437. LAttr := Copy(LAttrs, 1, I-1);
  438. LAttrs := Copy(LAttrs, I, MaxInt);
  439. end else begin
  440. LAttr := LAttrs;
  441. LAttrs := '';
  442. end;
  443. I := Pos('=', LAttr);
  444. if I > 0 then begin
  445. LName := Trim(Copy(LAttr, 1, I-1));
  446. LValue := Trim(Copy(LAttr, I+1, MaxInt));
  447. // RLebeau: RFC 6265 does not account for quoted attribute values,
  448. // despite several complaints asking for it. We'll do it anyway in
  449. // the hopes that the RFC will be updated to "do the right thing"...
  450. // RLebeau 11/17/2020: leaving this intact, for now...
  451. if TextStartsWith(LValue, '"') then begin
  452. IdDelete(LValue, 1, 1);
  453. LNameValue := LValue;
  454. LValue := Fetch(LNameValue, '"');
  455. end;
  456. end else begin
  457. LName := Trim(LAttr);
  458. LValue := '';
  459. end;
  460. case PosInStrArray(LName, ['Expires', 'Max-Age', 'Domain', 'Path', 'Secure', 'HttpOnly', 'SameSite'], False) of
  461. 0: begin
  462. if TryStrToInt64(LValue, LSecs) then begin
  463. // Not in the RFCs, but some servers specify Expires as an
  464. // integer number in seconds instead of using Max-Age...
  465. if LSecs >= 0 then begin
  466. // TODO: use SecsPerDay instead:
  467. // LExpiryTime := (Now + (LSecs / SecsPerDay));
  468. LExpiryTime := (Now + LSecs * 1000 / MSecsPerDay);
  469. end else begin
  470. LExpiryTime := EncodeDate(1, 1, 1);
  471. end;
  472. IndyAddPair(CookieProp, 'EXPIRES', FloatToStr(LExpiryTime)); {do not localize}
  473. end else
  474. begin
  475. LExpiryTime := CookieStrToLocalDateTime(LValue);
  476. if LExpiryTime <> 0.0 then begin
  477. IndyAddPair(CookieProp, 'EXPIRES', FloatToStr(LExpiryTime)); {do not localize}
  478. end;
  479. end;
  480. end;
  481. 1: begin
  482. if TryStrToInt64(LValue, LSecs) then begin
  483. if LSecs >= 0 then begin
  484. // TODO: use SecsPerDay instead:
  485. // LExpiryTime := (Now + (LSecs / SecsPerDay));
  486. LExpiryTime := (Now + LSecs * 1000 / MSecsPerDay);
  487. end else begin
  488. LExpiryTime := EncodeDate(1, 1, 1);
  489. end;
  490. IndyAddPair(CookieProp, 'MAX-AGE', FloatToStr(LExpiryTime)); {do not localize}
  491. end;
  492. end;
  493. 2: begin
  494. if LValue <> '' then begin
  495. if TextStartsWith(LValue, '.') then begin {do not localize}
  496. LValue := Copy(LValue, 2, MaxInt);
  497. end;
  498. // RLebeau: have encountered one cookie in the 'Set-Cookie' header that
  499. // includes a port number in the domain, though the RFCs do not indicate
  500. // this is allowed. RFC 2965 defines an explicit "port" attribute in the
  501. // 'Set-Cookie2' header for that purpose instead. We'll just strip it off
  502. // here if present...
  503. I := Pos(':', LValue);
  504. if I > 0 then begin
  505. LValue := Copy(S, 1, I-1);
  506. end;
  507. IndyAddPair(CookieProp, 'DOMAIN', LowerCase(LValue)); {do not localize}
  508. end;
  509. end;
  510. 3: begin
  511. if (LValue = '') or (not TextStartsWith(LValue, '/')) then begin
  512. LValue := GetDefaultPath(AURI);
  513. end;
  514. IndyAddPair(CookieProp, 'PATH', LValue); {do not localize}
  515. end;
  516. 4: begin
  517. IndyAddPair(CookieProp, 'SECURE', ''); {do not localize}
  518. end;
  519. 5: begin
  520. IndyAddPair(CookieProp, 'HTTPONLY', ''); {do not localize}
  521. end;
  522. 6: begin
  523. if TextIsSame(LValue, 'Strict') then begin {do not localize}
  524. IndyAddPair(CookieProp, 'SAMESITE', 'Strict'); {do not localize}
  525. end
  526. else if TextIsSame(LValue, 'Lax') then begin {do not localize}
  527. IndyAddPair(CookieProp, 'SAMESITE', 'Lax'); {do not localize}
  528. end else begin
  529. IndyAddPair(CookieProp, 'SAMESITE', 'None'); {do not localize}
  530. end;
  531. end;
  532. end;
  533. end;
  534. end;
  535. function GetLastValueOf(const CookieProp: TStringList; const AName: String; var VValue: String): Boolean;
  536. var
  537. I: Integer;
  538. begin
  539. Result := False;
  540. for I := CookieProp.Count-1 downto 0 do
  541. begin
  542. if TextIsSame(CookieProp.Names[I], AName) then
  543. begin
  544. VValue := IndyValueFromIndex(CookieProp, I);
  545. Result := True;
  546. Exit;
  547. end;
  548. end;
  549. end;
  550. //Darcy: moved down the variables! Android compiler... bad boy!
  551. var
  552. CookieProp: TStringList;
  553. S, LPathFromProps: string;
  554. begin
  555. Result := False;
  556. // using the algorithm defined in RFC 6265 section 5.2...
  557. CookieProp := TStringList.Create;
  558. try
  559. SplitCookieText(CookieProp, S);
  560. if CookieProp.Count = 0 then begin
  561. Exit;
  562. end;
  563. FName := CookieProp.Names[0];
  564. FValue := IndyValueFromIndex(CookieProp, 0);
  565. CookieProp.Delete(0);
  566. FCreatedAt := Now;
  567. FLastAccessed := FCreatedAt;
  568. // using the algorithms defined in RFC 6265 section 5.3...
  569. if GetLastValueOf(CookieProp, 'MAX-AGE', S) then begin {Do not Localize}
  570. FPersistent := True;
  571. FExpires := StrToFloat(S);
  572. end
  573. else if GetLastValueOf(CookieProp, 'EXPIRES', S) then {Do not Localize}
  574. begin
  575. FPersistent := True;
  576. FExpires := StrToFloat(S);
  577. end else
  578. begin
  579. FPersistent := False;
  580. FExpires := EncodeDate(9999, 12, 31) + EncodeTime(23, 59, 59, 999);
  581. end;
  582. S := '';
  583. if GetLastValueOf(CookieProp, 'DOMAIN', S) then {Do not Localize}
  584. begin
  585. // TODO
  586. {
  587. If the user agent is configured to reject "public suffixes" and
  588. the domain-attribute is a public suffix:
  589. If the domain-attribute is identical to the canonicalized
  590. request-host:
  591. Let the domain-attribute be the empty string.
  592. Otherwise:
  593. Ignore the cookie entirely and abort these steps.
  594. NOTE: A "public suffix" is a domain that is controlled by a
  595. public registry, such as "com", "co.uk", and "pvt.k12.wy.us".
  596. This step is essential for preventing attacker.com from
  597. disrupting the integrity of example.com by setting a cookie
  598. with a Domain attribute of "com". Unfortunately, the set of
  599. public suffixes (also known as "registry controlled domains")
  600. changes over time. If feasible, user agents SHOULD use an
  601. up-to-date public suffix list, such as the one maintained by
  602. the Mozilla project at <http://publicsuffix.org/>.
  603. }
  604. {
  605. if RejectPublicSuffixes and IsPublicSuffix(S) then begin
  606. if S <> CanonicalizeHostName(AURI.Host) then begin
  607. Exit;
  608. end;
  609. S := '';
  610. end;
  611. }
  612. end;
  613. if Length(S) > 0 then
  614. begin
  615. if not IsDomainMatch(AURI.Host, S) then begin
  616. Exit;
  617. end;
  618. FHostOnly := False;
  619. FDomain := S;
  620. end else
  621. begin
  622. FHostOnly := True;
  623. FDomain := CanonicalizeHostName(AURI.Host);
  624. end;
  625. if GetLastValueOf(CookieProp, 'PATH', LPathFromProps) then begin {Do not Localize}
  626. FPath := LPathFromProps;
  627. end else begin
  628. FPath := GetDefaultPath(AURI);
  629. end;
  630. FSecure := CookieProp.IndexOfName('SECURE') <> -1; { Do not Localize }
  631. if FSecure and (not IsSecure(AURI.Protocol)) then begin
  632. Exit;
  633. end;
  634. FHttpOnly := CookieProp.IndexOfName('HTTPONLY') <> -1; { Do not Localize }
  635. if FHttpOnly and (not IsHTTP(AURI.Protocol)) then begin
  636. Exit;
  637. end;
  638. if (not FSecure) and (not IsSecure(AURI.Protocol)) then begin
  639. // TODO
  640. {
  641. If the cookie's secure-only-flag is not set, and the scheme
  642. component of request-uri does not denote a "secure" protocol,
  643. then abort these steps and ignore the cookie entirely if the
  644. cookie store contains one or more cookies that meet all of the
  645. following criteria:
  646. 1. Their name matches the name of the newly-created cookie.
  647. 2. Their secure-only-flag is true.
  648. 3. Their domain domain-matches the domain of the newly-created
  649. cookie, or vice-versa.
  650. 4. The path of the newly-created cookie path-matches the path
  651. of the existing cookie.
  652. Note: The path comparison is not symmetric, ensuring only that a
  653. newly-created, non-secure cookie does not overlay an existing
  654. secure cookie, providing some mitigation against cookie-fixing
  655. attacks. That is, given an existing secure cookie named 'a'
  656. with a path of '/login', a non-secure cookie named 'a' could be
  657. set for a path of '/' or '/foo', but not for a path of '/login'
  658. or '/login/en'. }
  659. {
  660. for I := 0 to CookieList.Count-1 do
  661. begin
  662. LCookie := CookieList[I];
  663. if TextIsSame(LCookie.CookieName, FName) and
  664. LCookie.Secure and
  665. (IsDomainMatch(LCookie.Domain, FDomain) or IsDomainMatch(FDomain, LCookie.Domain)) and
  666. IsPathMatch(FPath, LCookie.Path) then
  667. begin
  668. Exit;
  669. end;
  670. end;
  671. }
  672. end;
  673. // TODO: implement https://tools.ietf.org/html/draft-west-cookie-incrementalism-01
  674. if GetLastValueOf(CookieProp, 'SAMESITE', S) then begin {Do not Localize}
  675. FSameSite := S;
  676. end else begin
  677. FSameSite := 'None'; {Do not Localize}
  678. end;
  679. if FSameSite <> 'None' then
  680. begin
  681. // TODO
  682. {
  683. 1. If the cookie was received from a "non-HTTP" API, and the
  684. API was called from a context whose "site for cookies" is
  685. not an exact match for request-uri's host's registrable
  686. domain, then abort these steps and ignore the newly created
  687. cookie entirely.
  688. 2. If the cookie was received from a "same-site" request (as
  689. defined in Section 5.2), skip the remaining substeps and
  690. continue processing the cookie.
  691. 3. If the cookie was received from a request which is
  692. navigating a top-level browsing context [HTML] (e.g. if the
  693. request's "reserved client" is either "null" or an
  694. environment whose "target browsing context" is a top-level
  695. browing context), skip the remaining substeps and continue
  696. processing the cookie.
  697. Note: Top-level navigations can create a cookie with any
  698. "SameSite" value, even if the new cookie wouldn't have been
  699. sent along with the request had it already existed prior to
  700. the navigation.
  701. 4. Abort these steps and ignore the newly created cookie
  702. entirely.
  703. }
  704. {
  705. if ((not IsHTTP(AURI.Protocol)) and (SiteForCookies <> RegistrableDomain(AURI.Host))) or
  706. ((IsCrossSite) and (not TopLevelBrowsingContext)) then
  707. begin
  708. Exit;
  709. end;
  710. }
  711. end;
  712. if TextStartsWith(FName, '__Secure-') and (not FSecure) then begin {do not localize}
  713. Exit;
  714. end;
  715. if TextStartsWith(FName, '__Host-') and not (FSecure and FHostOnly and (LPathFromProps = '/')) then begin {do not localize}
  716. Exit;
  717. end;
  718. Result := True;
  719. finally
  720. FreeAndNil(CookieProp);
  721. end;
  722. end;
  723. function TIdCookie.GetIsExpired: Boolean;
  724. begin
  725. Result := (FExpires <> 0.0) and (FExpires < Now);
  726. end;
  727. function TIdCookie.GetMaxAge: Int64;
  728. begin
  729. if FExpires <> 0.0 then begin
  730. Result := Trunc((FExpires - Now) * MSecsPerDay / 1000);
  731. end else begin
  732. Result := -1;
  733. end;
  734. end;
  735. {
  736. set-cookie-header = "Set-Cookie:" SP set-cookie-string
  737. set-cookie-string = cookie-pair *( ";" SP cookie-av )
  738. cookie-pair = cookie-name "=" cookie-value
  739. cookie-name = token
  740. cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
  741. cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
  742. ; US-ASCII characters excluding CTLs,
  743. ; whitespace DQUOTE, comma, semicolon,
  744. ; and backslash
  745. token = <token, defined in [RFC2616], Section 2.2>
  746. cookie-av = expires-av / max-age-av / domain-av /
  747. path-av / secure-av / httponly-av /
  748. extension-av
  749. expires-av = "Expires=" sane-cookie-date
  750. sane-cookie-date = <rfc1123-date, defined in [RFC2616], Section 3.3.1>
  751. max-age-av = "Max-Age=" non-zero-digit *DIGIT
  752. ; In practice, both expires-av and max-age-av
  753. ; are limited to dates representable by the
  754. ; user agent.
  755. non-zero-digit = %x31-39
  756. ; digits 1 through 9
  757. domain-av = "Domain=" domain-value
  758. domain-value = <subdomain>
  759. ; defined in [RFC1034], Section 3.5, as
  760. ; enhanced by [RFC1123], Section 2.1
  761. path-av = "Path=" path-value
  762. path-value = <any CHAR except CTLs or ";">
  763. secure-av = "Secure"
  764. httponly-av = "HttpOnly"
  765. extension-av = <any CHAR except CTLs or ";">
  766. }
  767. function TIdCookie.GetServerCookie: String;
  768. var
  769. LExpires: TDateTime;
  770. LMaxAge: Int64;
  771. begin
  772. Result := FName + '=' + FValue; {Do not Localize}
  773. AddCookieProperty(Result, 'Path', FPath); {Do not Localize}
  774. AddCookieProperty(Result, 'Domain', FDomain); {Do not Localize}
  775. if FSecure then begin
  776. AddCookieFlag(Result, 'Secure'); {Do not Localize}
  777. end;
  778. if FHttpOnly then begin
  779. AddCookieFlag(Result, 'HttpOnly'); {Do not Localize}
  780. end;
  781. LMaxAge := MaxAge;
  782. if LMaxAge >= 0 then begin
  783. AddCookieProperty(Result, 'Max-Age', IntToStr(LMaxAge)); {Do not Localize}
  784. end;
  785. LExpires := Expires;
  786. if LExpires <> 0.0 then begin
  787. AddCookieProperty(Result, 'Expires', LocalDateTimeToCookieStr(LExpires)); {Do not Localize}
  788. end;
  789. AddCookieProperty(Result, 'SameSite', FSameSite); {Do not Localize}
  790. end;
  791. {
  792. Cookie: NAME1=OPAQUE_STRING1; NAME2=OPAQUE_STRING2 ...
  793. }
  794. function TIdCookie.GetClientCookie: String;
  795. begin
  796. Result := FName + '=' + FValue;
  797. end;
  798. {
  799. cookie-header = "Cookie:" OWS cookie-string OWS
  800. cookie-string = cookie-pair *( ";" SP cookie-pair )
  801. }
  802. function TIdCookie.ParseClientCookie(const ACookieText: String): Boolean;
  803. var
  804. CookieProp: TStringList;
  805. procedure SplitCookieText;
  806. var
  807. LTemp, LName, LValue: String;
  808. i: Integer;
  809. IsFlag: Boolean;
  810. begin
  811. LTemp := Trim(ACookieText);
  812. while LTemp <> '' do {Do not Localize}
  813. begin
  814. i := FindFirstOf('=;', LTemp); {Do not Localize}
  815. if i = 0 then begin
  816. CookieProp.Add(LTemp);
  817. Break;
  818. end;
  819. IsFlag := (LTemp[i] = ';'); {Do not Localize}
  820. LName := TrimRight(Copy(LTemp, 1, i-1));
  821. LTemp := TrimLeft(Copy(LTemp, i+1, MaxInt));
  822. LValue := '';
  823. if (not IsFlag) and (LTemp <> '') then
  824. begin
  825. if TextStartsWith(LTemp, '"') then {Do not Localize}
  826. begin
  827. IdDelete(LTemp, 1, 1);
  828. LValue := Fetch(LTemp, '"'); {Do not Localize}
  829. Fetch(LTemp, ';'); {Do not Localize}
  830. end else begin
  831. LValue := Trim(Fetch(LTemp, ';')); {Do not Localize}
  832. end;
  833. LTemp := TrimLeft(LTemp);
  834. end;
  835. if LName <> '' then begin
  836. IndyAddPair(CookieProp, LName, LValue);
  837. end;
  838. end;
  839. end;
  840. begin
  841. Result := False;
  842. CookieProp := TStringList.Create;
  843. try
  844. SplitCookieText;
  845. if CookieProp.Count = 0 then begin
  846. Exit;
  847. end;
  848. FName := CookieProp.Names[0];
  849. FValue := IndyValueFromIndex(CookieProp, 0);
  850. Result := True;
  851. finally
  852. FreeAndNil(CookieProp);
  853. end;
  854. end;
  855. { TIdCookies }
  856. constructor TIdCookies.Create(AOwner: TPersistent);
  857. begin
  858. inherited Create(AOwner, TIdCookie);
  859. FRWLock := TMultiReadExclusiveWriteSynchronizer.Create;
  860. FCookieList := TIdCookieList.Create;
  861. end;
  862. destructor TIdCookies.Destroy;
  863. begin
  864. // This will force the Cookie removing process before we free FCookieList and FRWLock
  865. Self.Clear;
  866. FreeAndNil(FCookieList);
  867. FreeAndNil(FRWLock);
  868. inherited Destroy;
  869. end;
  870. function TIdCookies.Add: TIdCookie;
  871. begin
  872. Result := TIdCookie(inherited Add);
  873. end;
  874. function TIdCookies.AddCookie(ACookie: TIdCookie; AURI: TIdURI; AReplaceOld: Boolean = True): Boolean;
  875. var
  876. LOldCookie: TIdCookie;
  877. I: Integer;
  878. begin
  879. Result := False;
  880. LockCookieList(caReadWrite);
  881. try
  882. if AReplaceOld then
  883. begin
  884. for I := 0 to FCookieList.Count-1 do
  885. begin
  886. LOldCookie := FCookieList[I];
  887. if not TextIsSame(LOldCookie.CookieName, ACookie.CookieName) then begin
  888. Continue;
  889. end;
  890. if not TextIsSame(LOldCookie.Domain, ACookie.Domain) then begin
  891. Continue;
  892. end;
  893. if LOldCookie.HostOnly <> ACookie.HostOnly then begin
  894. Continue;
  895. end;
  896. if not TextIsSame(LOldCookie.Path, ACookie.Path) then begin
  897. Continue;
  898. end;
  899. if ((AURI <> nil) and (not IsHTTP(AURI.Protocol))) and LOldCookie.HttpOnly then begin
  900. Exit;
  901. end;
  902. ACookie.FCreatedAt := LOldCookie.CreatedAt;
  903. FCookieList.Delete(I);
  904. LOldCookie.Collection := nil;
  905. LOldCookie.Free;
  906. Break;
  907. end;
  908. end;
  909. if not ACookie.IsExpired then begin
  910. FCookieList.Add(ACookie);
  911. Result := True;
  912. end;
  913. finally
  914. UnlockCookieList(caReadWrite);
  915. end;
  916. end;
  917. procedure TIdCookies.Assign(ASource: TPersistent);
  918. begin
  919. if (ASource = nil) or (ASource is TIdCookies) then
  920. begin
  921. LockCookieList(caReadWrite);
  922. try
  923. Clear;
  924. AddCookies(TIdCookies(ASource));
  925. finally
  926. UnlockCookieList(caReadWrite);
  927. end;
  928. end else
  929. begin
  930. inherited Assign(ASource);
  931. end;
  932. end;
  933. function TIdCookies.GetCookie(Index: Integer): TIdCookie;
  934. begin
  935. Result := inherited GetItem(Index) as TIdCookie;
  936. end;
  937. procedure TIdCookies.SetCookie(Index: Integer; const Value: TIdCookie);
  938. begin
  939. inherited SetItem(Index, Value);
  940. end;
  941. function TIdCookies.AddClientCookie(const ACookie: string): TIdCookie;
  942. var
  943. LCookie: TIdCookie;
  944. begin
  945. Result := nil;
  946. LCookie := Add;
  947. try
  948. if LCookie.ParseClientCookie(ACookie) then
  949. begin
  950. LockCookieList(caReadWrite);
  951. try
  952. FCookieList.Add(LCookie);
  953. Result := LCookie;
  954. LCookie := nil;
  955. finally
  956. UnlockCookieList(caReadWrite);
  957. end;
  958. end;
  959. finally
  960. if LCookie <> nil then
  961. begin
  962. LCookie.Collection := nil;
  963. LCookie.Free;
  964. end;
  965. end;
  966. end;
  967. procedure TIdCookies.AddClientCookies(const ACookie: string);
  968. var
  969. Temp: TStringList;
  970. LCookie, S: String;
  971. I: Integer;
  972. begin
  973. S := Trim(ACookie);
  974. if S <> '' then begin
  975. Temp := TStringList.Create;
  976. try
  977. repeat
  978. LCookie := Fetch(S, ';');
  979. if LCookie <> '' then begin
  980. Temp.Add(LCookie);
  981. end;
  982. until S = '';
  983. for I := 0 to Temp.Count-1 do begin
  984. AddClientCookie(Temp[I]);
  985. end;
  986. finally
  987. Temp.Free;
  988. end;
  989. end;
  990. end;
  991. procedure TIdCookies.AddClientCookies(const ACookies: TStrings);
  992. var
  993. i: Integer;
  994. begin
  995. for i := 0 to ACookies.Count - 1 do begin
  996. AddClientCookies(ACookies[i]);
  997. end;
  998. end;
  999. function TIdCookies.AddServerCookie(const ACookie: string; AURI: TIdURI): TIdCookie;
  1000. var
  1001. LCookie: TIdCookie;
  1002. begin
  1003. Result := nil;
  1004. LCookie := Add;
  1005. try
  1006. if LCookie.ParseServerCookie(ACookie, AURI) then begin
  1007. if AddCookie(LCookie, AURI) then
  1008. begin
  1009. Result := LCookie;
  1010. LCookie := nil;
  1011. end;
  1012. end;
  1013. finally
  1014. if LCookie <> nil then begin
  1015. LCookie.Collection := nil;
  1016. LCookie.Free;
  1017. end;
  1018. end;
  1019. end;
  1020. procedure TIdCookies.AddServerCookies(const ACookies: TStrings; AURI: TIdURI);
  1021. var
  1022. i: Integer;
  1023. begin
  1024. for i := 0 to ACookies.Count - 1 do begin
  1025. AddServerCookie(ACookies[i], AURI);
  1026. end;
  1027. end;
  1028. procedure TIdCookies.AddCookies(ASource: TIdCookies);
  1029. var
  1030. LSrcCookies: TIdCookieList;
  1031. LSrcCookie, LDestCookie: TIdCookie;
  1032. i: Integer;
  1033. begin
  1034. if (ASource <> nil) and (ASource <> Self) then
  1035. begin
  1036. LSrcCookies := ASource.LockCookieList(caRead);
  1037. try
  1038. LockCookieList(caReadWrite);
  1039. try
  1040. for i := 0 to LSrcCookies.Count - 1 do
  1041. begin
  1042. LSrcCookie := LSrcCookies[i];
  1043. LDestCookie := TIdCookieClass(LSrcCookie.ClassType).Create(Self);
  1044. try
  1045. LDestCookie.Assign(LSrcCookie);
  1046. FCookieList.Add(LDestCookie);
  1047. except
  1048. LDestCookie.Collection := nil;
  1049. LDestCookie.Free;
  1050. raise;
  1051. end;
  1052. end;
  1053. finally
  1054. UnlockCookieList(caReadWrite);
  1055. end;
  1056. finally
  1057. ASource.UnlockCookieList(caRead);
  1058. end;
  1059. end;
  1060. end;
  1061. function TIdCookies.GetCookieByNameAndDomain(const AName, ADomain: string): TIdCookie;
  1062. var
  1063. i: Integer;
  1064. begin
  1065. i := GetCookieIndex(AName, ADomain);
  1066. if i = -1 then begin
  1067. Result := nil;
  1068. end else begin
  1069. Result := Cookies[i];
  1070. end;
  1071. end;
  1072. function TIdCookies.GetCookieIndex(const AName: string; FirstIndex: Integer = 0): Integer;
  1073. var
  1074. i: Integer;
  1075. begin
  1076. Result := -1;
  1077. for i := FirstIndex to Count - 1 do
  1078. begin
  1079. if TextIsSame(Cookies[i].CookieName, AName) then
  1080. begin
  1081. Result := i;
  1082. Exit;
  1083. end;
  1084. end;
  1085. end;
  1086. function TIdCookies.GetCookieIndex(const AName, ADomain: string; FirstIndex: Integer = 0): Integer;
  1087. var
  1088. LCookie: TIdCookie;
  1089. i: Integer;
  1090. begin
  1091. Result := -1;
  1092. for i := FirstIndex to Count - 1 do
  1093. begin
  1094. LCookie := Cookies[i];
  1095. if TextIsSame(LCookie.CookieName, AName) and
  1096. TextIsSame(CanonicalizeHostName(LCookie.Domain), CanonicalizeHostName(ADomain)) then
  1097. begin
  1098. Result := i;
  1099. Exit;
  1100. end;
  1101. end;
  1102. end;
  1103. procedure TIdCookies.Clear;
  1104. begin
  1105. LockCookieList(caReadWrite);
  1106. try
  1107. FCookieList.Clear;
  1108. inherited Clear;
  1109. finally
  1110. UnlockCookieList(caReadWrite);
  1111. end;
  1112. end;
  1113. function TIdCookies.LockCookieList(AAccessType: TIdCookieAccess): TIdCookieList;
  1114. begin
  1115. case AAccessType of
  1116. caRead:
  1117. begin
  1118. FRWLock.BeginRead;
  1119. end;
  1120. caReadWrite:
  1121. begin
  1122. FRWLock.BeginWrite;
  1123. end;
  1124. end;
  1125. Result := FCookieList;
  1126. end;
  1127. procedure TIdCookies.UnlockCookieList(AAccessType: TIdCookieAccess);
  1128. begin
  1129. case AAccessType of
  1130. caRead:
  1131. begin
  1132. FRWLock.EndRead;
  1133. end;
  1134. caReadWrite:
  1135. begin
  1136. FRWLock.EndWrite;
  1137. end;
  1138. end;
  1139. end;
  1140. end.