brooksession.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. (*
  2. Brook for Free Pascal
  3. Copyright (C) 2014-2019 Silvio Clecio
  4. See the file LICENSE.txt, included in this distribution,
  5. for details about the copyright.
  6. This library 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. { Session class. }
  11. unit BrookSession;
  12. {$i brook.inc}
  13. interface
  14. uses
  15. BrookClasses, BrookHttpDefs, BrookUtils, BrookException, BrookConsts, Classes,
  16. SysUtils, HTTPDefs, DateUtils;
  17. type
  18. { Handles exceptions for @link(TBrookSession). }
  19. EBrookSession = class(EBrook);
  20. { Is a metaclass for @link(TBrookSession) class. }
  21. TBrookSessionClass = class of TBrookSession;
  22. { Is a type to the session start event. }
  23. TBrookSessionStartEvent = procedure(ASender: TObject;
  24. ARequest: TBrookRequest; var AHandled: Boolean) of object;
  25. { Defines a pointer to the session start event.}
  26. PBrookSessionStartEvent = ^TBrookSessionStartEvent;
  27. { Is a type to the session finish event. }
  28. TBrookSessionFinishEvent = procedure(ASender: TObject;
  29. AResponse: TBrookResponse; var AHandled: Boolean) of object;
  30. { Defines a pointer to the session finish event.}
  31. PBrookSessionFinishEvent = ^TBrookSessionFinishEvent;
  32. { Is a type to the session expire event. }
  33. TBrookSessionExpireEvent = procedure(ASender: TObject; ARequest: TBrookRequest;
  34. AResponse: TBrookResponse; var AHandled: Boolean) of object;
  35. { Defines a pointer to the session expire event.}
  36. PBrookSessionExpireEvent = ^TBrookSessionExpireEvent;
  37. { Defines features to the session handling. }
  38. TBrookSession = class(TBrookComponent)
  39. private
  40. FAfterExpire: TBrookSessionExpireEvent;
  41. FAfterFinish: TBrookSessionFinishEvent;
  42. FAfterStart: TBrookSessionStartEvent;
  43. FBeforeExpire: TBrookSessionExpireEvent;
  44. FBeforeFinish: TBrookSessionFinishEvent;
  45. FBeforeStart: TBrookSessionStartEvent;
  46. FCookieDomain: string;
  47. FCookieExpires: TDateTime;
  48. FCookieName: string;
  49. FCookiePath: string;
  50. FCookieSecure: Boolean;
  51. FFields: TStrings;
  52. FDirectory: string;
  53. FFileName: TFileName;
  54. FFilePrefix: ShortString;
  55. FHttpOnly: Boolean;
  56. FIgnoredFields: TStrings;
  57. FSID: string;
  58. FStarted: Boolean;
  59. FTimeout: Integer;
  60. function GetField(const AName: string): string;
  61. procedure SetField(const AName: string; const AValue: string);
  62. procedure SetFields(AValue: TStrings);
  63. procedure SetIgnoredFields(AValue: TStrings);
  64. protected
  65. function IsStarted: Boolean;
  66. procedure MakeSID(ARequest: TBrookRequest); virtual;
  67. procedure SetFileName; virtual;
  68. procedure SetCookie(AResponse: TBrookResponse); virtual;
  69. procedure Load; virtual;
  70. procedure Save; virtual;
  71. public
  72. { Creates an instance of a @link(TBrookSession) class. }
  73. constructor Create(AOwner: TComponent); override;
  74. { Frees an instance of @link(TBrookSession) class. }
  75. destructor Destroy; override;
  76. { Get an object with the fields coming from session. }
  77. procedure GetFields(AObject: TObject);
  78. { Returns @code(True) if the session has expired.}
  79. function IsExpired: Boolean; virtual;
  80. { Returns @code(True) if the session fieds is empty.}
  81. function IsEmpty: Boolean; virtual;
  82. { Creates an ID for the session. }
  83. function GenerateID: string; virtual;
  84. { Starts the session. }
  85. procedure Start(ARequest: TBrookRequest); virtual;
  86. { Terminates the session. }
  87. procedure Finish(AResponse: TBrookResponse); virtual;
  88. { Expires the session. }
  89. procedure Expire(ARequest: TBrookRequest;
  90. AResponse: TBrookResponse); virtual;
  91. { Checks if a name exists in fields. }
  92. function Exists(const AName: string): Boolean;
  93. { Set the session cookie name. }
  94. property CookieName: string read FCookieName write FCookieName;
  95. { Set the session cookie domain. }
  96. property CookieDomain: string read FCookieDomain write FCookieDomain;
  97. { Set the session cookie path. }
  98. property CookiePath: string read FCookiePath write FCookiePath;
  99. { Set the session cookie secure. }
  100. property CookieSecure: Boolean read FCookieSecure write FCookieSecure;
  101. { Set the session cookie expiration. }
  102. property CookieExpires: TDateTime read FCookieExpires write FCookieExpires;
  103. { Handles the session fields. }
  104. property Field[const AName: string]: string read GetField write SetField;
  105. { The session fields. }
  106. property Fields: TStrings read FFields write SetFields;
  107. { The ignored fields by the session. }
  108. property IgnoredFields: TStrings read FIgnoredFields write SetIgnoredFields;
  109. { Set the name of session directory. }
  110. property Directory: string read FDirectory write FDirectory;
  111. { Returns @code(True) if the session has expired.}
  112. property Expired: Boolean read IsExpired;
  113. { Returns @code(True) if the session fieds is empty.}
  114. property Empty: Boolean read IsEmpty;
  115. { Get or set the session ID. }
  116. property SID: string read FSID write FSID;
  117. { Checks if the session has started. }
  118. property Started: Boolean read IsStarted;
  119. { The session file name. }
  120. property FileName: TFileName read FFileName write FFileName;
  121. { The session file prefix. }
  122. property FilePrefix: ShortString read FFilePrefix write FFilePrefix;
  123. { The remaining seconds for the session finish. }
  124. property Timeout: Integer read FTimeout write FTimeout
  125. default BROOK_SESS_DEFAULT_TIMEOUT;
  126. { Informs if the session cookie is accessible only by HTTP requests,
  127. if @code(True), the JavaScript access is not allowed. }
  128. property HttpOnly: Boolean read FHttpOnly write FHttpOnly;
  129. { Is triggered after session start. }
  130. property AfterStart: TBrookSessionStartEvent read FAfterStart
  131. write FAfterStart;
  132. { Is triggered before session start. }
  133. property BeforeStart: TBrookSessionStartEvent read FBeforeStart
  134. write FBeforeStart;
  135. { Is triggered after session finish. }
  136. property AfterFinish: TBrookSessionFinishEvent read FAfterFinish
  137. write FAfterFinish;
  138. { Is triggered before session finish. }
  139. property BeforeFinish: TBrookSessionFinishEvent read FBeforeFinish
  140. write FBeforeFinish;
  141. { Is triggered after session expire. }
  142. property AfterExpire: TBrookSessionExpireEvent read FAfterExpire
  143. write FAfterExpire;
  144. { Is triggered before session expire. }
  145. property BeforeExpire: TBrookSessionExpireEvent read FBeforeExpire
  146. write FBeforeExpire;
  147. end;
  148. { Defines features to the section mapping field values to object. }
  149. generic TBrookGSession<T> = class(TBrookSession)
  150. private
  151. FEntity: T;
  152. protected
  153. function CreateEntity: T; virtual;
  154. procedure FreeEntity; virtual;
  155. procedure FillEntity; virtual;
  156. procedure ReadEntity; virtual;
  157. procedure Load; override;
  158. procedure Save; override;
  159. public
  160. { Creates an instance of a @link(TBrookGSession) class. }
  161. constructor Create(AOwner: TComponent); override;
  162. { Frees an instance of @link(TBrookGSession) class. }
  163. destructor Destroy; override;
  164. { Maps field values to object. }
  165. property Entity: T read FEntity write FEntity;
  166. end;
  167. implementation
  168. { TBrookSession }
  169. constructor TBrookSession.Create(AOwner: TComponent);
  170. begin
  171. inherited Create(AOwner);
  172. FFields := TStringList.Create;
  173. FIgnoredFields := TStringList.Create;
  174. FCookieName := BROOK_SESS_ID;
  175. FCookieExpires := -1;
  176. FFilePrefix := BROOK_SESS_PREFIX;
  177. FDirectory := GetTempDir(False);
  178. if FDirectory = ES then
  179. FDirectory := ExtractFilePath(ParamStr(0));
  180. FTimeout := BROOK_SESS_DEFAULT_TIMEOUT;
  181. FHttpOnly := True;
  182. end;
  183. destructor TBrookSession.Destroy;
  184. begin
  185. FFields.Free;
  186. FIgnoredFields.Free;
  187. inherited Destroy;
  188. end;
  189. procedure TBrookSession.GetFields(AObject: TObject);
  190. begin
  191. BrookSafeStringsToObject(AObject, FFields, FIgnoredFields);
  192. end;
  193. function TBrookSession.IsExpired: Boolean;
  194. begin
  195. Result := FTimeout <> 0;
  196. if not Result then
  197. Exit;
  198. if FileExists(FFileName) then
  199. begin
  200. if FTimeout > 0 then
  201. Result := IncSecond(BrookFileDate(FFileName), FTimeout) < Now
  202. else
  203. Result := False;
  204. end
  205. else
  206. Result := True;
  207. end;
  208. function TBrookSession.IsEmpty: Boolean;
  209. begin
  210. Result := FFields.Count < 1;
  211. end;
  212. {$PUSH}{$WARN 5093 OFF}
  213. function TBrookSession.GenerateID: string;
  214. var
  215. VGuid: TGuid;
  216. begin
  217. CreateGUID(VGuid);
  218. SetLength(Result, 32);
  219. StrLFmt(PChar(Result), 32, BROOK_UUID_MASK, [VGuid.D1, VGuid.D2, VGuid.D3,
  220. VGuid.D4[0], VGuid.D4[1], VGuid.D4[2], VGuid.D4[3], VGuid.D4[4],
  221. VGuid.D4[5], VGuid.D4[6], VGuid.D4[7]]);
  222. end;
  223. {$POP}
  224. procedure TBrookSession.SetFields(AValue: TStrings);
  225. begin
  226. if Assigned(AValue) then
  227. FFields.Assign(AValue);
  228. end;
  229. function TBrookSession.GetField(const AName: string): string;
  230. begin
  231. Result := FFields.Values[AName];
  232. end;
  233. procedure TBrookSession.SetField(const AName: string; const AValue: string);
  234. begin
  235. FFields.Values[AName] := AValue;
  236. end;
  237. procedure TBrookSession.SetIgnoredFields(AValue: TStrings);
  238. begin
  239. if Assigned(AValue) then
  240. FIgnoredFields.Assign(AValue);
  241. end;
  242. function TBrookSession.IsStarted: Boolean;
  243. begin
  244. Result := FStarted;
  245. end;
  246. procedure TBrookSession.MakeSID(ARequest: TBrookRequest);
  247. begin
  248. if FSID = ES then
  249. FSID := ARequest.CookieFields.Values[FCookieName];
  250. if FSID = ES then
  251. FSID := GenerateID;
  252. end;
  253. procedure TBrookSession.SetFileName;
  254. begin
  255. FFileName := IncludeTrailingPathDelimiter(FDirectory) + FFilePrefix + FSID;
  256. end;
  257. procedure TBrookSession.SetCookie(AResponse: TBrookResponse);
  258. var
  259. VCookie: TCookie;
  260. begin
  261. VCookie := AResponse.Cookies.FindCookie(FCookieName);
  262. if not Assigned(VCookie) then
  263. begin
  264. VCookie := AResponse.Cookies.Add;
  265. VCookie.Name := FCookieName;
  266. VCookie.Expires := FCookieExpires;
  267. VCookie.Domain := FCookieDomain;
  268. VCookie.Path := FCookiePath;
  269. VCookie.Secure := FCookieSecure;
  270. VCookie.HttpOnly := FHttpOnly;
  271. end;
  272. VCookie.Value := SID;
  273. end;
  274. procedure TBrookSession.Load;
  275. var
  276. I: Integer;
  277. N, V: string;
  278. VFields: TStrings;
  279. begin
  280. if IsExpired then
  281. Exit;
  282. if FileExists(FFileName) then
  283. if FIgnoredFields.Count > 0 then
  284. begin
  285. VFields := TStringList.Create;
  286. try
  287. VFields.LoadFromFile(FFileName);
  288. for I := 0 to Pred(VFields.Count) do
  289. begin
  290. VFields.GetNameValue(I, N, V);
  291. if FIgnoredFields.IndexOf(N) > -1 then
  292. FFields.Values[N] := ES
  293. else
  294. FFields.Values[N] := V;
  295. end;
  296. finally
  297. VFields.Free;
  298. end;
  299. end
  300. else
  301. FFields.LoadFromFile(FFileName);
  302. end;
  303. procedure TBrookSession.Save;
  304. var
  305. I: Integer;
  306. N, V: string;
  307. VFields: TStrings;
  308. begin
  309. if FFileName <> ES then
  310. if FIgnoredFields.Count > 0 then
  311. begin
  312. VFields := TStringList.Create;
  313. try
  314. for I := 0 to Pred(FFields.Count) do
  315. begin
  316. FFields.GetNameValue(I, N, V);
  317. if FIgnoredFields.IndexOf(N) > -1 then
  318. VFields.Add(N + EQ)
  319. else
  320. VFields.Add(N + EQ + V);
  321. end;
  322. VFields.SaveToFile(FFileName);
  323. finally
  324. VFields.Free;
  325. end;
  326. end
  327. else
  328. FFields.SaveToFile(FFileName);
  329. end;
  330. procedure TBrookSession.Start(ARequest: TBrookRequest);
  331. var
  332. VHandled: Boolean = False;
  333. begin
  334. try
  335. if Assigned(FBeforeStart) then
  336. FBeforeStart(Self, ARequest, VHandled);
  337. if FStarted or VHandled then
  338. Exit;
  339. MakeSID(ARequest);
  340. SetFileName;
  341. FStarted := True;
  342. Load;
  343. finally
  344. if Assigned(FAfterStart) then
  345. FAfterStart(Self, ARequest, VHandled);
  346. end;
  347. end;
  348. procedure TBrookSession.Finish(AResponse: TBrookResponse);
  349. var
  350. VHandled: Boolean = False;
  351. begin
  352. try
  353. if Assigned(FBeforeFinish) then
  354. FBeforeFinish(Self, AResponse, VHandled);
  355. if (not FStarted) or VHandled then
  356. Exit;
  357. SetCookie(AResponse);
  358. Save;
  359. FStarted := False;
  360. finally
  361. if Assigned(FAfterFinish) then
  362. FAfterFinish(Self, AResponse, VHandled);
  363. end;
  364. end;
  365. procedure TBrookSession.Expire(ARequest: TBrookRequest;
  366. AResponse: TBrookResponse);
  367. var
  368. VCookie: TCookie;
  369. VHandled: Boolean = False;
  370. begin
  371. try
  372. if Assigned(FBeforeExpire) then
  373. FBeforeExpire(Self, ARequest, AResponse, VHandled);
  374. if IsExpired or (not FStarted) or VHandled then
  375. Exit;
  376. FSID := ARequest.CookieFields.Values[FCookieName];
  377. if FSID = ES then
  378. Exit;
  379. SetFileName;
  380. DeleteFile(FFileName);
  381. VCookie := AResponse.Cookies.Add;
  382. VCookie.Name := FCookieName;
  383. VCookie.Expire;
  384. FFields.Clear;
  385. finally
  386. if Assigned(FAfterExpire) then
  387. FAfterExpire(Self, ARequest, AResponse, VHandled);
  388. end;
  389. end;
  390. function TBrookSession.Exists(const AName: string): Boolean;
  391. begin
  392. Result := FFields.IndexOfName(AName) > -1;
  393. end;
  394. { TBrookGSession }
  395. constructor TBrookGSession.Create(AOwner: TComponent);
  396. begin
  397. inherited Create(AOwner);
  398. FEntity := CreateEntity;
  399. end;
  400. destructor TBrookGSession.Destroy;
  401. begin
  402. FreeEntity;
  403. inherited Destroy;
  404. end;
  405. function TBrookGSession.CreateEntity: T;
  406. begin
  407. Result := T.Create;
  408. end;
  409. procedure TBrookGSession.FreeEntity;
  410. begin
  411. FreeAndNil(FEntity);
  412. end;
  413. procedure TBrookGSession.FillEntity;
  414. begin
  415. BrookStringsToObject(FEntity, Fields, IgnoredFields);
  416. end;
  417. procedure TBrookGSession.ReadEntity;
  418. begin
  419. Fields.Clear;
  420. BrookObjectToStrings(FEntity, Fields, IgnoredFields);
  421. end;
  422. procedure TBrookGSession.Load;
  423. begin
  424. inherited Load;
  425. FillEntity;
  426. end;
  427. procedure TBrookGSession.Save;
  428. begin
  429. ReadEntity;
  430. inherited Save;
  431. end;
  432. end.