webmodule.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. unit webmodule;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. SysUtils, Classes, HTTPDefs, fpHTTP, fpWeb;
  6. type
  7. { TFPWebModule1 }
  8. TFPWebModule1 = class(TFPWebModule)
  9. procedure DataModuleAfterResponse(Sender: TObject; AResponse: TResponse);
  10. procedure DataModuleCreate(Sender: TObject);
  11. procedure loginRequest(Sender: TObject; ARequest: TRequest;
  12. AResponse: TResponse; var Handled: Boolean);
  13. procedure logoutRequest(Sender: TObject; ARequest: TRequest;
  14. AResponse: TResponse; var Handled: Boolean);
  15. procedure someactionRequest(Sender: TObject; ARequest: TRequest;
  16. AResponse: TResponse; var Handled: Boolean);
  17. private
  18. { private declarations }
  19. LoggedInLoginName : String;
  20. SessionID: String;
  21. SessionDBFile : String;
  22. UserDBFile : String;
  23. SessionVariable: String;
  24. TimeoutMinutes: Integer;
  25. function RemoveExpiredSessions(SL:TStringList; const SIDToDelete:String):Boolean;
  26. function NotLoggedIn:Boolean;
  27. function CommonTemplateTagReplaces(const TagString:String;
  28. TagParams: TStringList; Out ReplaceText: String):Boolean;
  29. procedure loginReplaceTag(Sender: TObject; const TagString:String;
  30. TagParams: TStringList; Out ReplaceText: String);
  31. procedure logoutReplaceTag(Sender: TObject; const TagString:String;
  32. TagParams: TStringList; Out ReplaceText: String);
  33. procedure welcomeReplaceTag(Sender: TObject; const TagString:String;
  34. TagParams: TStringList; Out ReplaceText: String);
  35. procedure someactionReplaceTag(Sender: TObject; const TagString:String;
  36. TagParams: TStringList; Out ReplaceText: String);
  37. public
  38. { public declarations }
  39. end;
  40. var
  41. FPWebModule1: TFPWebModule1;
  42. implementation
  43. {$R *.lfm}
  44. { TFPWebModule1 }
  45. procedure TFPWebModule1.DataModuleAfterResponse(Sender: TObject;
  46. AResponse: TResponse);
  47. var
  48. sessiondatabase:TStringList;
  49. SIDLastRefresh:String;
  50. begin
  51. //update the session DB for the current session
  52. if (SessionID <> '')and(LoggedinLoginName <> '') then
  53. begin//for many concurrent request websites this part needs to be modified to have some kind of locking while writing into the file/relational database
  54. SIDLastRefresh := '';
  55. sessiondatabase := TStringList.Create;
  56. if FileExists(sessiondbfile) then
  57. sessiondatabase.LoadFromFile(sessiondbfile);
  58. SIDLastRefresh := sessiondatabase.Values[SessionID];
  59. if SIDLastRefresh <> '' then
  60. begin
  61. sessiondatabase.Values[SessionID] := DateTimeToStr(Now) + LoggedinLoginName;//update the Last refresh time
  62. sessiondatabase.SaveToFile(sessiondbfile);
  63. end;
  64. sessiondatabase.Free;
  65. end;
  66. //reset global variables for apache modules for the next incoming request
  67. LoggedInLoginName := '';
  68. SessionID := '';
  69. //
  70. end;
  71. procedure TFPWebModule1.DataModuleCreate(Sender: TObject);
  72. begin
  73. ModuleTemplate.AllowTagParams := true;
  74. ModuleTemplate.StartDelimiter := '{+'; //The default is { and } which is usually not good if we use Javascript in our templates
  75. ModuleTemplate.EndDelimiter := '+}';
  76. sessiondbfile := 'session-db.txt';//This will contain the sessionID=expiration pairs
  77. userdbfile := 'userdb.txt'; //This simulates a user database with passwords
  78. TimeOutMinutes := 2; //With a session timeout of 2 minutes
  79. SessionVariable := 'sid'; //Query parameter name for the session ID, for all links in the templates
  80. LongTimeFormat := 'hh:mm:ss'; //to save on date time conversion code
  81. ShortDateFormat := 'YYYY/MM/DD'; //to save on date time conversion code
  82. end;
  83. function FindNameInList(const SL:TStrings; const N:String):String;
  84. var
  85. i : Integer;
  86. begin
  87. Result := '';
  88. for i := 0 to SL.Count - 1 do
  89. if SL.Names[i] = N then
  90. begin
  91. Result := SL.Values[SL.Names[i]];
  92. break;
  93. end;
  94. end;
  95. function TFPWebModule1.RemoveExpiredSessions(SL:TStringList; const SIDToDelete:String):Boolean;
  96. var
  97. DT:TDateTime;
  98. i, j: Integer;
  99. s, SIDLastRefresh: String;
  100. begin
  101. Result := false;
  102. if SL.Count <= 0 then Exit;
  103. i := 0;
  104. repeat
  105. s := SL[i];
  106. j := pos('=', s);
  107. if j > 0 then
  108. begin
  109. if copy(s, 1, j - 1) = SIDToDelete then
  110. begin
  111. SL.Delete(i);
  112. dec(i);
  113. end else begin
  114. SIDLastRefresh := copy(s, j + 1, 19);{YYYY/MM/DD hh:mm:ss}
  115. DT := StrToDateTime(SIDLastRefresh);
  116. if ((Now - DT) > (TimeOutMinutes/1440)) then
  117. begin
  118. Result := true;
  119. SL.Delete(i);
  120. dec(i);
  121. end;
  122. end;
  123. end;
  124. inc(i);
  125. until i >= SL.Count;
  126. end;
  127. function TFPWebModule1.NotLoggedIn:Boolean;
  128. var
  129. sessiondatabase:TStringlist;
  130. SIDLastRefresh:String;
  131. begin
  132. Result := false;
  133. //check if the current sessionID is valid
  134. SessionID := UpperCase(Request.QueryFields.Values[SessionVariable]);
  135. if SessionID <> '' then
  136. begin
  137. sessiondatabase := TStringList.Create;
  138. if FileExists(sessiondbfile) then
  139. sessiondatabase.LoadFromFile(sessiondbfile);
  140. // if RemoveExpiredSessions(sessiondatabase, '') then //Remove all expired sessions
  141. // sessiondatabase.SaveToFile(sessiondbfile); {enough to purge only at logout events}
  142. RemoveExpiredSessions(sessiondatabase, ''); { }
  143. SIDLastRefresh := sessiondatabase.Values[SessionID];
  144. sessiondatabase.Free;
  145. if SIDLastRefresh <> '' then
  146. begin
  147. LoggedinLoginName := copy(SIDLastRefresh, 20, 1024);
  148. Exit;//OK
  149. end;
  150. end;
  151. //show the login screen again with the expired session message
  152. ModuleTemplate.FileName := 'testurllogin.html';
  153. ModuleTemplate.OnReplaceTag := @loginReplaceTag;
  154. Request.QueryFields.Add('MSG=SESSIONEXPIRED');
  155. Response.Content := ModuleTemplate.GetContent;
  156. Result := true;
  157. end;
  158. procedure TFPWebModule1.loginRequest(Sender: TObject; ARequest: TRequest;
  159. AResponse: TResponse; var Handled: Boolean);
  160. var
  161. loginname, pwd, pwd1 : String;
  162. userdatabase, sessiondatabase : TStringlist;
  163. G : TGUID;
  164. begin
  165. Handled := true;
  166. ModuleTemplate.FileName := 'testurllogin.html';
  167. ModuleTemplate.OnReplaceTag := @loginReplaceTag;
  168. AResponse.CustomHeaders.Add('Pragma=no-cache');//do not cache the response in the web browser
  169. if FindNameInList(ARequest.ContentFields, 'LoginName') = '' then
  170. begin//called the login action without parameters -> display the login page
  171. ARequest.QueryFields.Add('MSG=NORMAL');
  172. AResponse.Content := ModuleTemplate.GetContent;
  173. Exit;
  174. end;
  175. loginname := Trim(ARequest.ContentFields.Values['LoginName']);
  176. pwd := Trim(ARequest.ContentFields.Values['Password']);
  177. if (pwd = '') or (loginname = '') then
  178. begin//empty login name or password -> return to the login screen
  179. ARequest.QueryFields.Add('MSG=MISSING');
  180. AResponse.Content := ModuleTemplate.GetContent;
  181. Exit;
  182. end;
  183. //simulate a user database loaded into a stringlist
  184. userdatabase := TStringlist.Create;
  185. userdatabase.LoadFromFile(userdbfile);
  186. pwd1 := userdatabase.values[LoginName];//get the correct password for the LoginName if it is there
  187. userdatabase.free;
  188. //
  189. if pwd <> pwd1 then
  190. begin//either the password or the login name was invalid
  191. ARequest.QueryFields.Add('MSG=INVLOGIN');
  192. AResponse.Content := ModuleTemplate.GetContent;
  193. Exit;
  194. end;
  195. //succesful login
  196. LoggedInLoginName := loginname;
  197. //session starting, need to store it somewhere next to the name of the logged in person
  198. sessiondatabase := TStringList.Create;
  199. if FileExists(sessiondbfile) then
  200. sessiondatabase.LoadFromFile(sessiondbfile);
  201. CreateGUID(G);
  202. SessionID:=UpperCase(GuiDToString(G));
  203. sessiondatabase.Add(SessionID + '=' + DateTimeToStr(Now) + LoggedinLoginName);//create a new entry for this session
  204. sessiondatabase.SaveToFile(sessiondbfile);//for many concurrent request websites this part needs to be modified to have some kind of locking while writing into the file/relational database
  205. sessiondatabase.Free;
  206. //generate the Welcome page content
  207. ModuleTemplate.FileName := 'testurlwelcome.html';
  208. ModuleTemplate.OnReplaceTag := @welcomeReplaceTag;
  209. AResponse.Content := ModuleTemplate.GetContent;
  210. end;
  211. procedure TFPWebModule1.loginReplaceTag(Sender: TObject; const TagString:
  212. String; TagParams: TStringList; Out ReplaceText: String);
  213. begin
  214. {Handle tags used in multiple templates}
  215. if CommonTemplateTagReplaces(TagString, TagParams, ReplaceText) then
  216. Exit;
  217. {Handle tags specific to this template if there are any}
  218. if AnsiCompareText(TagString, 'MESSAGE') = 0 then
  219. begin
  220. ReplaceText := TagParams.Values[Request.QueryFields.Values['MSG']];
  221. end else
  222. {Message for tags not handled}
  223. begin
  224. ReplaceText := '[Template tag {+' + TagString + '+} is not implemented yet.]';
  225. end;
  226. end;
  227. procedure TFPWebModule1.welcomeReplaceTag(Sender: TObject; const TagString:String;
  228. TagParams: TStringList; Out ReplaceText: String);
  229. begin
  230. {Handle tags used in multiple templates}
  231. if CommonTemplateTagReplaces(TagString, TagParams, ReplaceText) then
  232. Exit;
  233. {Handle tags specific to this template if there are any}
  234. {Message for tags not handled}
  235. begin
  236. ReplaceText := '[Template tag {+' + TagString + '+} is not implemented yet.]';
  237. end;
  238. end;
  239. procedure TFPWebModule1.logoutRequest(Sender: TObject; ARequest: TRequest;
  240. AResponse: TResponse; var Handled: Boolean);
  241. var
  242. sessiondatabase : TStringList;
  243. begin
  244. Handled := true;
  245. if NotLoggedIn then Exit;
  246. //delete the sessionID from the sessiondb with all expired sessions
  247. sessiondatabase := TStringList.Create;
  248. if FileExists(sessiondbfile) then
  249. sessiondatabase.LoadFromFile(sessiondbfile);
  250. RemoveExpiredSessions(sessiondatabase, SessionID);
  251. sessiondatabase.SaveToFile(sessiondbfile);//for many concurrent request websites this part needs to be modified to have some kind of locking while writing into the file/relational database
  252. sessiondatabase.Free;
  253. //
  254. ModuleTemplate.FileName := 'testurllogout.html';
  255. ModuleTemplate.OnReplaceTag := @logoutReplaceTag;
  256. AResponse.Content := ModuleTemplate.GetContent;//generate the Logout page content.
  257. end;
  258. procedure TFPWebModule1.logoutReplaceTag(Sender: TObject; const TagString:String;
  259. TagParams: TStringList; Out ReplaceText: String);
  260. begin
  261. {Handle tags used in multiple templates}
  262. if CommonTemplateTagReplaces(TagString, TagParams, ReplaceText) then
  263. Exit;
  264. {Handle tags specific to this template if there are any}
  265. {Message for tags not handled}
  266. begin
  267. ReplaceText := '[Template tag {+' + TagString + '+} is not implemented yet.]';
  268. end;
  269. end;
  270. procedure TFPWebModule1.someactionRequest(Sender: TObject; ARequest: TRequest;
  271. AResponse: TResponse; var Handled: Boolean);
  272. begin
  273. Handled := true;
  274. if NotLoggedIn then Exit;
  275. ModuleTemplate.FileName := 'testurlsomepage.html';
  276. ModuleTemplate.OnReplaceTag := @someactionReplaceTag;
  277. AResponse.Content := ModuleTemplate.GetContent;
  278. end;
  279. procedure TFPWebModule1.someactionReplaceTag(Sender: TObject; const TagString:
  280. String; TagParams: TStringList; Out ReplaceText: String);
  281. begin
  282. {Handle tags used in multiple templates}
  283. if CommonTemplateTagReplaces(TagString, TagParams, ReplaceText) then
  284. Exit;
  285. {Handle tags specific to this template if there are any}
  286. {Message for tags not handled}
  287. begin
  288. ReplaceText := '[Template tag {+' + TagString + '+} is not implemented yet.]';
  289. end;
  290. end;
  291. function TFPWebModule1.CommonTemplateTagReplaces(const TagString:String;
  292. TagParams: TStringList; out ReplaceText: String):Boolean;
  293. begin
  294. Result := true;
  295. if AnsiCompareText(TagString, 'SESSION-VARIABLE') = 0 then
  296. begin
  297. ReplaceText := SessionVariable + '=' + SessionID;
  298. end else
  299. if AnsiCompareText(TagString, 'DATETIME') = 0 then
  300. begin
  301. ReplaceText := FormatDateTime(TagParams.Values['FORMAT'], Now);
  302. end else
  303. if AnsiCompareText(TagString, 'SESSIONID') = 0 then
  304. begin
  305. ReplaceText := SessionID;
  306. end else
  307. if AnsiCompareText(TagString, 'MINUTESLEFT') = 0 then
  308. begin
  309. ReplaceText := IntToStr(TimeOutMinutes);
  310. end else
  311. if AnsiCompareText(TagString, 'LOGINNAME') = 0 then
  312. begin
  313. ReplaceText := LoggedInLoginName;
  314. end else
  315. Result := false;
  316. end;
  317. initialization
  318. RegisterHTTPModule('TFPWebModule1', TFPWebModule1);
  319. end.