brooksession.pas 13 KB

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