cgiapp.pp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. {
  2. $Id$
  3. This file is part of the Free Pascal run time library.
  4. Copyright (c) 1999-2000 by the Free Pascal development team
  5. TCGIApplication class.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. {$mode objfpc}
  13. {$H+}
  14. unit cgiapp;
  15. Interface
  16. uses
  17. CustApp,Classes,SysUtils;
  18. Const
  19. CGIVarCount = 23;
  20. Type
  21. TCGIVarArray = Array[1..CGIVarCount] of String;
  22. Const
  23. CgiVarNames : TCGIVarArray =
  24. ('AUTH_TYPE',
  25. 'CONTENT_LENGTH',
  26. 'CONTENT_TYPE',
  27. 'GATEWAY_INTERFACE',
  28. 'PATH_INFO',
  29. 'PATH_TRANSLATED',
  30. 'QUERY_STRING', 'REMOTE_ADDR',
  31. 'REMOTE_HOST',
  32. 'REMOTE_IDENT',
  33. 'REMOTE_USER',
  34. 'REQUEST_METHOD',
  35. 'SCRIPT_NAME',
  36. 'SERVER_NAME',
  37. 'SERVER_PORT',
  38. 'SERVER_PROTOCOL',
  39. 'SERVER_SOFTWARE',
  40. 'HTTP_ACCEPT',
  41. 'HTTP_ACCEPT_CHARSET',
  42. 'HTTP_ACCEPT_ENCODING',
  43. 'HTTP_IF_MODIFIED_SINCE',
  44. 'HTTP_REFERER',
  45. 'HTTP_USER_AGENT');
  46. Type
  47. TCgiApplication = Class(TCustomApplication)
  48. Private
  49. FResponse : TStream;
  50. FEmail : String;
  51. FAdministrator : String;
  52. FContentTypeEmitted : Boolean;
  53. FCGIVars : TCGIVarArray;
  54. FRequestVars : TStrings;
  55. Function GetCGIVar (Index : Integer) : String;
  56. Procedure InitCGIVars;
  57. Procedure InitRequestVars;
  58. Procedure InitPostVars;
  59. Procedure InitGetVars;
  60. Procedure SetContentLength (Value : Integer);
  61. Procedure SetCGIVar(Index : Integer; Value : String);
  62. Function GetContentLength : Integer;
  63. Function GetServerPort : Word;
  64. Function GetEmail : String;
  65. Function GetAdministrator : String;
  66. Procedure ProcessQueryString(Const FQueryString : String);
  67. Function GetRequestVariable(Const VarName : String) : String;
  68. Function GetRequestVariableCount : Integer;
  69. Public
  70. Constructor Create(AOwner : TComponent); override;
  71. Destructor Destroy; override;
  72. Procedure AddResponse(Const S : String);
  73. Procedure AddResponse(Const Fmt : String; Args : Array of const);
  74. Procedure AddResponseLn(Const S : String);
  75. Procedure AddResponseLn(Const Fmt : String; Args : Array of const);
  76. Procedure Initialize; override;
  77. Procedure GetCGIVarList(List : TStrings);
  78. Procedure GetRequestVarList(List : TStrings);
  79. Procedure GetRequestVarList(List : TStrings; NamesOnly : Boolean);
  80. Procedure ShowException(E: Exception);override;
  81. Function EmitContentType : Boolean;
  82. Property AuthType : String Index 1 Read GetCGIVar;
  83. Property ContentLength : Integer Read GetContentLength Write SetContentLength; // Index 2
  84. Property ContentType : String Index 3 Read GetCGIVar Write SetCGIVar;
  85. Property GatewayInterface : String Index 4 Read GetCGIVar;
  86. Property PathInfo : String index 5 read GetCGIvar;
  87. Property PathTranslated : String Index 6 read getCGIVar;
  88. Property QueryString : String Index 7 read getcgivar;
  89. Property RemoteAddress : String Index 8 read GetCGIVar;
  90. Property RemoteHost : String Index 9 read GetCGIVar;
  91. Property RemoteIdent : String Index 10 read GetCGIVar;
  92. Property RemoteUser : String Index 11 read GetCGIVar;
  93. Property RequestMethod : String Index 12 read GetCGIVar;
  94. Property ScriptName : String Index 13 read GetCGIVar;
  95. Property ServerName : String Index 14 read GetCGIVar;
  96. Property ServerPort : Word Read GetServerPort; // Index 15
  97. Property ServerProtocol : String Index 16 read GetCGIVar;
  98. Property ServerSoftware : String Index 17 read GetCGIVar;
  99. Property HTTPAccept : String Index 18 read GetCGIVar;
  100. Property HTTPAcceptCharset : String Index 19 read GetCGIVar;
  101. Property HTTPAcceptEncoding : String Index 20 read GetCGIVar;
  102. Property HTTPIfModifiedSince : String Index 21 read GetCGIVar; // Maybe change to TDateTime ??
  103. Property HTTPReferer : String Index 22 read GetCGIVar;
  104. Property HTTPUserAgent : String Index 23 read GetCGIVar;
  105. Property Email : String Read GetEmail Write FEmail;
  106. Property Administrator : String Read GetAdministrator Write FAdministrator;
  107. Property RequestVariables[VarName : String] : String Read GetRequestVariable;
  108. Property RequestVariableCount : Integer Read GetRequestVariableCount;
  109. Property Response : TStream Read FResponse;
  110. end;
  111. ResourceString
  112. SWebMaster = 'webmaster';
  113. SCGIError = 'CGI Error';
  114. SAppEncounteredError = 'The application encountered the following error:';
  115. SError = 'Error: ';
  116. SNotify = 'Notify: ';
  117. SErrNoContentLength = 'No content length passed from server!';
  118. SErrUnsupportedContentType = 'Unsupported content type: "%s"';
  119. SErrNoRequestMethod = 'No REQUEST_METHOD passed from server.';
  120. SErrInvalidRequestMethod = 'Invalid REQUEST_METHOD passed from server.';
  121. Implementation
  122. uses
  123. iostream;
  124. Constructor TCgiApplication.Create(AOwner : TComponent);
  125. begin
  126. Inherited Create(AOwner);
  127. FRequestVars:=TStringList.Create;
  128. end;
  129. Destructor TCgiApplication.Destroy;
  130. begin
  131. FRequestVars.Free;
  132. Inherited;
  133. end;
  134. Function TCgiApplication.GetCGIVar (Index : Integer) : String;
  135. begin
  136. Result:=FCGIVars[Index];
  137. end;
  138. Procedure TCgiApplication.InitCGIVars;
  139. Var
  140. I : Integer;
  141. L : TStrings;
  142. begin
  143. L:=TStringList.Create;
  144. Try
  145. GetEnvironmentList(L);
  146. For I:=1 to CGIVarCount do
  147. FCGIVars[i]:=L.Values[CGIVarNames[i]];
  148. Finally
  149. L.Free;
  150. end;
  151. end;
  152. Procedure TCgiApplication.Initialize;
  153. begin
  154. StopOnException:=True;
  155. Inherited;
  156. InitCGIVars;
  157. InitRequestVars;
  158. FResponse:=TIOStream.Create(iosOutput);
  159. end;
  160. Procedure TCgiApplication.GetCGIVarList(List : TStrings);
  161. Var
  162. I : Integer;
  163. begin
  164. List.Clear;
  165. For I:=1 to cgiVarCount do
  166. List.Add(CGIVarNames[i]+'='+FCGIVars[i]);
  167. end;
  168. Procedure TCgiApplication.GetRequestVarList(List : TStrings);
  169. begin
  170. GetRequestVarList(List,False);
  171. end;
  172. Procedure TCgiApplication.GetRequestVarList(List : TStrings; NamesOnly : Boolean);
  173. Var
  174. I,J : Integer;
  175. S : String;
  176. begin
  177. List.BeginUpdate;
  178. Try
  179. List.Clear;
  180. // Copy one by one, there may be CR/LF in the variables, causing 'Text' to go wrong.
  181. If Assigned(FRequestVars) then
  182. For I:=0 to FRequestVars.Count-1 do
  183. begin
  184. S:=FRequestVars[i];
  185. If NamesOnly then
  186. begin
  187. J:=Pos('=',S);
  188. If (J>0) then
  189. S:=Copy(S,1,J-1);
  190. end;
  191. List.Add(S);
  192. end;
  193. finally
  194. List.EndUpdate;
  195. end;
  196. end;
  197. Function TCgiApplication.GetContentLength : Integer;
  198. begin
  199. Result:=StrToIntDef(GetCGIVar(2),-1);
  200. end;
  201. Procedure TCgiApplication.SetContentLength (Value : Integer);
  202. begin
  203. SetCGIVar(2,IntToStr(Value));
  204. end;
  205. Procedure TCgiApplication.SetCGIVar(Index : Integer; Value : String);
  206. begin
  207. If Index in [1..cgiVarCount] then
  208. FCGIVars[Index]:=Value;
  209. end;
  210. Function TCgiApplication.GetServerPort : Word;
  211. begin
  212. Result:=StrToIntDef(GetCGIVar(15),0);
  213. end;
  214. Function TCgiApplication.EmitContentType : Boolean;
  215. Var
  216. S: String;
  217. begin
  218. Result:=Not FContentTypeEmitted;
  219. If result then
  220. begin
  221. S:=ContentType;
  222. If (S='') then
  223. S:='text/html';
  224. AddResponseLn('Content-Type: '+ContentType);
  225. AddResponseLn('');
  226. FContentTypeEmitted:=True;
  227. end;
  228. end;
  229. Procedure TCgiApplication.ShowException(E: Exception);
  230. Var
  231. TheEmail : String;
  232. begin
  233. If not FContentTypeEmitted then
  234. begin
  235. ContentType:='text/html';
  236. EmitContentType;
  237. end;
  238. If (ContentType='text/html') then
  239. begin
  240. AddResponseLN('<html><head><title>'+Title+': '+SCGIError+'</title></head>');
  241. AddResponseLN('<body>');
  242. AddResponseLN('<center><hr><h1>'+Title+': ERROR</h1><hr></center><br><br>');
  243. AddResponseLN(SAppEncounteredError+'<br>');
  244. AddResponseLN('<ul>');
  245. AddResponseLN('<li>'+SError+' <b>'+E.Message+'</b></ul><hr>');
  246. TheEmail:=Email;
  247. If (TheEmail<>'') then
  248. AddResponseLN('<h5><p><i>'+SNotify+Administrator+': <a href="mailto:'+TheEmail+'">'+TheEmail+'</a></i></p></h5>');
  249. AddResponseLN('</body></html>');
  250. end;
  251. end;
  252. Function TCgiApplication.GetEmail : String;
  253. Var
  254. H : String;
  255. begin
  256. If (FEmail='') then
  257. begin
  258. H:=ServerName;
  259. If (H<>'') then
  260. Result:=Administrator+'@'+H
  261. else
  262. Result:='';
  263. end
  264. else
  265. Result:=Email;
  266. end;
  267. Function TCgiApplication.GetAdministrator : String;
  268. begin
  269. If (FADministrator<>'') then
  270. Result:=FAdministrator
  271. else
  272. Result:=SWebMaster;
  273. end;
  274. Procedure TCgiApplication.InitRequestVars;
  275. var
  276. R : String;
  277. begin
  278. R:=RequestMethod;
  279. if (R='') then
  280. Raise Exception.Create(SErrNoRequestMethod);
  281. if CompareText(R,'POST')=0 then
  282. InitPostVars
  283. else if CompareText(R,'GET')=0 then
  284. InitGetVars
  285. else
  286. Raise Exception.CreateFmt(SErrInvalidRequestMethod,[R]);
  287. end;
  288. Procedure TCgiApplication.InitPostVars;
  289. var
  290. FQueryString : String;
  291. i : Integer;
  292. ch : Char;
  293. begin
  294. if (FCGIVars[2]='') then
  295. Raise Exception.Create(SErrNoContentLength);
  296. if CompareText(ContentType,'APPLICATION/X-WWW-FORM-URLENCODED')<>0 then
  297. Raise Exception.CreateFmt(SErrUnsupportedContentType,[ContentType]);
  298. SetLength(FQueryString,ContentLength);
  299. for I:= 1 to contentLength Do
  300. begin
  301. Read(ch);
  302. FQueryString[i]:=ch;
  303. end;
  304. ProcessQueryString(FQueryString);
  305. end;
  306. Procedure TCgiApplication.InitGetVars;
  307. Var
  308. FQueryString : String;
  309. begin
  310. FQueryString:=QueryString;
  311. If (FQueryString<>'') then
  312. ProcessQueryString(FQueryString);
  313. end;
  314. const
  315. hexTable = '0123456789ABCDEF';
  316. Procedure TCgiApplication.ProcessQueryString(Const FQueryString : String);
  317. var
  318. queryItem : String;
  319. delimiter : Char;
  320. aString : String;
  321. aSepStr : String;
  322. aPos : Integer;
  323. aLenStr : Integer;
  324. aLenSep : Integer;
  325. function hexConverter(h1, h2 : Char) : Char;
  326. var
  327. B : Byte;
  328. begin
  329. B:=(Pos(upcase(h1),hexTable)-1)*16;
  330. B:=B+Pos(upcase(h2),hexTable)-1;
  331. Result:=chr(B);
  332. end;
  333. procedure Convert_ESC_Chars;
  334. var
  335. index : Integer;
  336. begin
  337. For Index:=1 to Length(QueryItem) do
  338. Index:=Length(QueryItem);
  339. While (Index>0) do
  340. begin
  341. If QueryItem[Index]='+' then
  342. QueryItem[Index]:=' '
  343. else If (QueryItem[Index]='%') and (Index<Length(QueryItem)-1) then
  344. begin
  345. QueryItem[Index]:=hexConverter(QueryItem[Index+1],QueryItem[index+2]);
  346. System.Delete(QueryItem,Index+1,2);
  347. end;
  348. dec(Index);
  349. end;
  350. end;
  351. procedure InitToken(aStr, aSep : String);
  352. begin
  353. aString := aStr;
  354. aSepStr := aSep;
  355. aPos := 1;
  356. aLenStr := Length(aString);
  357. aLenSep := Length(aSepStr);
  358. end;
  359. function NextToken(var aToken : String; var aSepChar : Char) : Boolean;
  360. var
  361. i : Integer;
  362. j : Integer;
  363. BoT : Integer;
  364. EoT : Integer;
  365. isSep : Boolean;
  366. begin
  367. BoT:=aPos;
  368. EoT:=aPos;
  369. for i:=aPos to aLenStr do
  370. begin
  371. IsSep := false;
  372. for j := 1 to aLenSep do
  373. begin
  374. if aString[i] = aSepStr[j] then
  375. begin
  376. IsSep := true;
  377. Break;
  378. end;
  379. end;
  380. if IsSep then
  381. begin
  382. EoT := i;
  383. aPos := i + 1;
  384. aSepChar := aString[i];
  385. Break;
  386. end
  387. else
  388. begin
  389. if i = aLenStr then
  390. begin
  391. EoT := i;
  392. aPos := i;
  393. Break;
  394. end;
  395. end;
  396. end;
  397. if aPos < aLenStr then
  398. begin
  399. aToken := Copy(aString, BoT, EoT - BoT);
  400. Result := true;
  401. end
  402. else
  403. begin
  404. if aPos = aLenStr then
  405. begin
  406. aToken := Copy(aString, BoT, EoT - BoT + 1);
  407. Result := true;
  408. aPos := aPos + 1;
  409. end
  410. else
  411. begin
  412. Result := false;
  413. end;
  414. end;
  415. end;
  416. begin
  417. InitToken(FQueryString, '&');
  418. while NextToken(QueryItem, delimiter) do
  419. begin
  420. if (QueryItem<>'') then
  421. begin
  422. Convert_ESC_Chars;
  423. FRequestVars.Add(QueryItem);
  424. end;
  425. end;
  426. end;
  427. Function TCGIApplication.GetRequestVariable(Const VarName : String) : String;
  428. begin
  429. If Assigned(FRequestVars) then
  430. Result:=FRequestVars.Values[VarName];
  431. end;
  432. Function TCGIApplication.GetRequestVariableCount : Integer;
  433. begin
  434. If Assigned(FRequestVars) then
  435. Result:=FRequestVars.Count
  436. else
  437. Result:=0;
  438. end;
  439. Procedure TCGIApplication.AddResponse(Const S : String);
  440. Var
  441. L : Integer;
  442. begin
  443. L:=Length(S);
  444. If L>0 then
  445. FResponse.Write(S[1],L);
  446. end;
  447. Procedure TCGIApplication.AddResponse(Const Fmt : String; Args : Array of const);
  448. begin
  449. AddResponse(Format(Fmt,Args));
  450. end;
  451. Procedure TCGIApplication.AddResponseLN(Const S : String);
  452. begin
  453. AddResponse(S+LineEnding);
  454. end;
  455. Procedure TCGIApplication.AddResponseLN(Const Fmt : String; Args : Array of const);
  456. begin
  457. AddResponseLN(Format(Fmt,Args));
  458. end;
  459. end.