custcgi.pp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 1999-2000 by the Free Pascal development team
  4. TCGIApplication class.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$define CGIDEBUG}
  12. {$mode objfpc}
  13. {$H+}
  14. unit custcgi;
  15. Interface
  16. uses
  17. CustApp,Classes,SysUtils, httpdefs;
  18. Const
  19. CGIVarCount = 34;
  20. Type
  21. TCGIVarArray = Array[1..CGIVarCount] of String;
  22. Const
  23. CgiVarNames : TCGIVarArray =
  24. ({ 1 } 'AUTH_TYPE',
  25. { 2 } 'CONTENT_LENGTH',
  26. { 3 } 'CONTENT_TYPE',
  27. { 4 } 'GATEWAY_INTERFACE',
  28. { 5 } 'PATH_INFO',
  29. { 6 } 'PATH_TRANSLATED',
  30. { 7 } 'QUERY_STRING',
  31. { 8 } 'REMOTE_ADDR',
  32. { 9 } 'REMOTE_HOST',
  33. { 10 } 'REMOTE_IDENT',
  34. { 11 } 'REMOTE_USER',
  35. { 12 } 'REQUEST_METHOD',
  36. { 13 } 'SCRIPT_NAME',
  37. { 14 } 'SERVER_NAME',
  38. { 15 } 'SERVER_PORT',
  39. { 16 } 'SERVER_PROTOCOL',
  40. { 17 } 'SERVER_SOFTWARE',
  41. { 18 } 'HTTP_ACCEPT',
  42. { 19 } 'HTTP_ACCEPT_CHARSET',
  43. { 20 } 'HTTP_ACCEPT_ENCODING',
  44. { 21 } 'HTTP_IF_MODIFIED_SINCE',
  45. { 22 } 'HTTP_REFERER',
  46. { 23 } 'HTTP_USER_AGENT',
  47. { 24 } 'HTTP_COOKIE',
  48. // Additional Apache vars
  49. { 24 } 'HTTP_CONNECTION',
  50. { 25 } 'HTTP_ACCEPT_LANGUAGE',
  51. { 26 } 'HTTP_HOST',
  52. { 27 } 'SERVER_SIGNATURE',
  53. { 28 } 'SERVER_ADDR',
  54. { 29 } 'DOCUMENT_ROOT',
  55. { 30 } 'SERVER_ADMIN',
  56. { 31 } 'SCRIPT_FILENAME',
  57. { 32 } 'REMOTE_PORT',
  58. { 33 } 'REQUEST_URI'
  59. );
  60. Type
  61. { TCGIRequest }
  62. TCustomCGIApplication = Class;
  63. TCGIRequest = Class(TRequest)
  64. Private
  65. FCGI : TCustomCGIApplication;
  66. function GetCGIVar(Index: integer): String;
  67. Protected
  68. Function GetFieldValue(Index : Integer) : String; override;
  69. Procedure InitFromEnvironment;
  70. Procedure InitPostVars;
  71. Procedure InitGetVars;
  72. Public
  73. Constructor CreateCGI(ACGI : TCustomCGIApplication);
  74. Property GatewayInterface : String Index 1 Read GetCGIVar;
  75. Property RemoteIdent : String Index 2 read GetCGIVar;
  76. Property RemoteUser : String Index 3 read GetCGIVar;
  77. Property RequestMethod : String Index 4 read GetCGIVar;
  78. Property ServerName : String Index 5 read GetCGIVar;
  79. Property ServerProtocol : String Index 6 read GetCGIVar;
  80. Property ServerSoftware : String Index 7 read GetCGIVar;
  81. end;
  82. { TCGIResponse }
  83. TCGIResponse = Class(TResponse)
  84. private
  85. FCGI : TCustomCGIApplication;
  86. FOutput : TStream;
  87. Protected
  88. Procedure DoSendHeaders(Headers : TStrings); override;
  89. Procedure DoSendContent; override;
  90. Public
  91. Constructor CreateCGI(ACGI : TCustomCGIApplication; AStream : TStream);
  92. end;
  93. { TCustomCgiApplication }
  94. TCustomCGIApplication = Class(TCustomApplication)
  95. Private
  96. FResponse : TCGIResponse;
  97. FRequest : TCGIRequest;
  98. FEmail : String;
  99. FAdministrator : String;
  100. FOutput : TStream;
  101. Procedure InitRequestVars;
  102. Function GetEmail : String;
  103. Function GetAdministrator : String;
  104. Function GetRequestVariable(Const VarName : String) : String;
  105. Function GetRequestVariableCount : Integer;
  106. Public
  107. Destructor Destroy; override;
  108. Property Request : TCGIRequest read FRequest;
  109. Property Response: TCGIResponse Read FResponse;
  110. Procedure AddResponse(Const S : String);
  111. Procedure AddResponse(Const Fmt : String; Args : Array of const);
  112. Procedure AddResponseLn(Const S : String);
  113. Procedure AddResponseLn(Const Fmt : String; Args : Array of const);
  114. Procedure Initialize; override;
  115. Procedure GetCGIVarList(List : TStrings);
  116. Procedure ShowException(E: Exception);override;
  117. Procedure DeleteFormFiles;
  118. Procedure DoRun; override;
  119. Procedure handleRequest(ARequest : TRequest; AResponse : TResponse); virtual;
  120. Function GetTempCGIFileName : String;
  121. Function VariableIsUploadedFile(Const VarName : String) : boolean;
  122. Function UploadedFileName(Const VarName : String) : String;
  123. Property Email : String Read GetEmail Write FEmail;
  124. Property Administrator : String Read GetAdministrator Write FAdministrator;
  125. Property RequestVariables[VarName : String] : String Read GetRequestVariable;
  126. Property RequestVariableCount : Integer Read GetRequestVariableCount;
  127. end;
  128. ResourceString
  129. SWebMaster = 'webmaster';
  130. SCGIError = 'CGI Error';
  131. SAppEncounteredError = 'The application encountered the following error:';
  132. SError = 'Error: ';
  133. SNotify = 'Notify: ';
  134. SErrNoContentLength = 'No content length passed from server!';
  135. SErrUnsupportedContentType = 'Unsupported content type: "%s"';
  136. SErrNoRequestMethod = 'No REQUEST_METHOD passed from server.';
  137. SErrInvalidRequestMethod = 'Invalid REQUEST_METHOD passed from server.';
  138. Implementation
  139. uses
  140. {$ifdef CGIDEBUG}
  141. dbugintf,
  142. {$endif}
  143. iostream;
  144. Const
  145. MapCgiToHTTP : TCGIVarArray =
  146. ({ 1: 'AUTH_TYPE' } fieldWWWAuthenticate, // ?
  147. { 2: 'CONTENT_LENGTH' } FieldContentLength,
  148. { 3: 'CONTENT_TYPE' } FieldContentType,
  149. { 4: 'GATEWAY_INTERFACE' } '',
  150. { 5: 'PATH_INFO' } '',
  151. { 6: 'PATH_TRANSLATED' } '',
  152. { 7: 'QUERY_STRING' } '',
  153. { 8: 'REMOTE_ADDR' } '',
  154. { 9: 'REMOTE_HOST' } '',
  155. { 10: 'REMOTE_IDENT' } '',
  156. { 11: 'REMOTE_USER' } '',
  157. { 12: 'REQUEST_METHOD' } '',
  158. { 13: 'SCRIPT_NAME' } '',
  159. { 14: 'SERVER_NAME' } '',
  160. { 15: 'SERVER_PORT' } '',
  161. { 16: 'SERVER_PROTOCOL' } '',
  162. { 17: 'SERVER_SOFTWARE' } '',
  163. { 18: 'HTTP_ACCEPT' } FieldAccept,
  164. { 19: 'HTTP_ACCEPT_CHARSET' } FieldAcceptCharset,
  165. { 20: 'HTTP_ACCEPT_ENCODING' } FieldAcceptEncoding,
  166. { 21: 'HTTP_IF_MODIFIED_SINCE' } FieldIfModifiedSince,
  167. { 22: 'HTTP_REFERER' } FieldReferer,
  168. { 23: 'HTTP_USER_AGENT' } FieldUserAgent,
  169. { 23: 'HTTP_USER_AGENT' } FieldCookie,
  170. // Additional Apache vars
  171. { 24: 'HTTP_CONNECTION' } FieldConnection,
  172. { 25: 'HTTP_ACCEPT_LANGUAGE' } FieldAcceptLanguage,
  173. { 26: 'HTTP_HOST' } '',
  174. { 27: 'SERVER_SIGNATURE' } '',
  175. { 28: 'SERVER_ADDR' } '',
  176. { 29: 'DOCUMENT_ROOT' } '',
  177. { 30: 'SERVER_ADMIN' } '',
  178. { 31: 'SCRIPT_FILENAME' } '',
  179. { 32: 'REMOTE_PORT' } '',
  180. { 33: 'REQUEST_URI' } ''
  181. );
  182. Destructor TCustomCGIApplication.Destroy;
  183. begin
  184. DeleteFormFiles;
  185. FreeAndNil(FRequest);
  186. FreeAndNil(FResponse);
  187. FreeAndNil(FOutPut);
  188. Inherited;
  189. end;
  190. Function TCustomCGIApplication.GetTempCGIFileName : String;
  191. begin
  192. Result:=GetTempFileName('/tmp/','CGI')
  193. end;
  194. Procedure TCustomCGIApplication.DeleteFormFiles;
  195. Var
  196. I : Integer;
  197. FN : String;
  198. begin
  199. If Assigned(FRequest) then
  200. For I:=0 to FRequest.Files.Count-1 do
  201. begin
  202. FN:=FRequest.Files[I].LocalFileName;
  203. If FileExists(FN) then
  204. DeleteFile(FN);
  205. end;
  206. end;
  207. procedure TCustomCGIApplication.DoRun;
  208. begin
  209. HandleRequest(FRequest,FResponse);
  210. If Not FResponse.ContentSent then
  211. begin
  212. FResponse.SendContent;
  213. end;
  214. Terminate;
  215. end;
  216. procedure TCustomCGIApplication.HandleRequest(ARequest: TRequest; AResponse: TResponse);
  217. begin
  218. // Needs overriding;
  219. end;
  220. Procedure TCustomCGIApplication.Initialize;
  221. begin
  222. StopOnException:=True;
  223. Inherited;
  224. FRequest:=TCGIRequest.CreateCGI(Self);
  225. InitRequestVars;
  226. FOutput:=TIOStream.Create(iosOutput);
  227. FResponse:=TCGIResponse.CreateCGI(Self,Self.FOutput);
  228. end;
  229. Procedure TCustomCGIApplication.GetCGIVarList(List : TStrings);
  230. Var
  231. I : Integer;
  232. begin
  233. List.Clear;
  234. For I:=1 to cgiVarCount do
  235. List.Add(CGIVarNames[i]+'='+GetEnvironmentVariable(CGIVarNames[i]));
  236. end;
  237. Procedure TCustomCGIApplication.ShowException(E: Exception);
  238. Var
  239. TheEmail : String;
  240. FrameCount: integer;
  241. Frames: PPointer;
  242. FrameNumber:Integer;
  243. S : TStrings;
  244. begin
  245. If not FResponse.HeadersSent then
  246. FResponse.ContentType:='text/html';
  247. If (FResponse.ContentType='text/html') then
  248. begin
  249. S:=TStringList.Create;
  250. Try
  251. With S do
  252. begin
  253. Add('<html><head><title>'+Title+': '+SCGIError+'</title></head>'+LineEnding);
  254. Add('<body>');
  255. Add('<center><hr><h1>'+Title+': ERROR</h1><hr></center><br><br>');
  256. Add(SAppEncounteredError+'<br>');
  257. Add('<ul>');
  258. Add('<li>'+SError+' <b>'+E.Message+'</b>');
  259. Add('<li> Stack trace:<br>');
  260. Add(BackTraceStrFunc(ExceptAddr)+'<br>');
  261. FrameCount:=ExceptFrameCount;
  262. Frames:=ExceptFrames;
  263. for FrameNumber := 0 to FrameCount-1 do
  264. Add(BackTraceStrFunc(Frames[FrameNumber])+'<br>');
  265. Add('</ul><hr>');
  266. TheEmail:=Email;
  267. If (TheEmail<>'') then
  268. Add('<h5><p><i>'+SNotify+Administrator+': <a href="mailto:'+TheEmail+'">'+TheEmail+'</a></i></p></h5>');
  269. Add('</body></html>');
  270. end;
  271. FResponse.Content:=S.Text;
  272. FResponse.SendContent;
  273. Finally
  274. FreeAndNil(S);
  275. end;
  276. end;
  277. end;
  278. Function TCustomCGIApplication.GetEmail : String;
  279. Var
  280. H : String;
  281. begin
  282. If (FEmail='') then
  283. begin
  284. H:=Request.ServerName;
  285. If (H<>'') then
  286. Result:=Administrator+'@'+H
  287. else
  288. Result:='';
  289. end
  290. else
  291. Result:=Email;
  292. end;
  293. Function TCustomCGIApplication.GetAdministrator : String;
  294. begin
  295. If (FADministrator<>'') then
  296. Result:=FAdministrator
  297. else
  298. Result:=SWebMaster;
  299. end;
  300. Procedure TCustomCGIApplication.InitRequestVars;
  301. var
  302. R : String;
  303. begin
  304. R:=GetEnvironmentVariable('REQUEST_METHOD');
  305. if (R='') then
  306. Raise Exception.Create(SErrNoRequestMethod);
  307. FRequest.InitFromEnvironment;
  308. if CompareText(R,'POST')=0 then
  309. Request.InitPostVars
  310. else if CompareText(R,'GET')=0 then
  311. Request.InitGetVars
  312. else
  313. Raise Exception.CreateFmt(SErrInvalidRequestMethod,[R]);
  314. end;
  315. constructor TCGIRequest.CreateCGI(ACGI: TCustomCGIApplication);
  316. begin
  317. Inherited Create;
  318. FCGI:=ACGI;
  319. end;
  320. Type
  321. TCapacityStream = Class(TMemoryStream)
  322. Public
  323. Property Capacity;
  324. end;
  325. Procedure TCGIRequest.InitPostVars;
  326. Var
  327. M : TCapacityStream;
  328. I : TIOStream;
  329. Cl : Integer;
  330. B : Byte;
  331. CT : String;
  332. begin
  333. {$ifdef CGIDEBUG}
  334. SendMethodEnter('InitPostVars');
  335. {$endif}
  336. CL:=ContentLength;
  337. M:=TCapacityStream.Create;
  338. Try
  339. I:=TIOStream.Create(iosInput);
  340. Try
  341. if (CL<>0) then
  342. begin
  343. M.Capacity:=Cl;
  344. M.CopyFrom(I,Cl);
  345. end
  346. else
  347. begin
  348. While (I.Read(B,1)>0) do
  349. M.Write(B,1)
  350. end;
  351. Finally
  352. I.Free;
  353. end;
  354. M.Position:=0;
  355. With TFileStream.Create('/tmp/query',fmCreate) do
  356. try
  357. CopyFrom(M,0);
  358. M.Position:=0;
  359. Finally
  360. Free;
  361. end;
  362. CT:=ContentType;
  363. if Pos('MULTIPART/FORM-DATA',Uppercase(CT))<>0 then
  364. ProcessMultiPart(M,CT)
  365. else if CompareText('APPLICATION/X-WWW-FORM-URLENCODED',CT)=0 then
  366. ProcessUrlEncoded(M)
  367. else
  368. begin
  369. {$ifdef CGIDEBUG}
  370. SendDebug('InitPostVars: unsupported content type:'+CT);
  371. {$endif}
  372. Raise Exception.CreateFmt(SErrUnsupportedContentType,[CT]);
  373. end;
  374. finally
  375. M.Free;
  376. end;
  377. {$ifdef CGIDEBUG}
  378. SendMethodExit('InitPostVars');
  379. {$endif}
  380. end;
  381. Procedure TCGIRequest.InitGetVars;
  382. Var
  383. FQueryString : String;
  384. begin
  385. {$ifdef CGIDEBUG}
  386. SendMethodEnter('InitGetVars');
  387. {$endif}
  388. FQueryString:=GetEnvironmentVariable('QUERY_STRING');
  389. If (FQueryString<>'') then
  390. ProcessQueryString(FQueryString);
  391. {$ifdef CGIDEBUG}
  392. SendMethodExit('InitGetVars');
  393. {$endif}
  394. end;
  395. Function TCustomCGIApplication.GetRequestVariable(Const VarName : String) : String;
  396. begin
  397. If Assigned(Request) then
  398. Result:=FRequest.QueryFields.Values[VarName]
  399. else
  400. Result:='';
  401. end;
  402. Function TCustomCGIApplication.GetRequestVariableCount : Integer;
  403. begin
  404. If Assigned(Request) then
  405. Result:=FRequest.QueryFields.Count
  406. else
  407. Result:=0;
  408. end;
  409. Procedure TCustomCGIApplication.AddResponse(Const S : String);
  410. Var
  411. L : Integer;
  412. begin
  413. L:=Length(S);
  414. If L>0 then
  415. Response.Content:=Response.Content+S;
  416. end;
  417. Procedure TCustomCGIApplication.AddResponse(Const Fmt : String; Args : Array of const);
  418. begin
  419. AddResponse(Format(Fmt,Args));
  420. end;
  421. Procedure TCustomCGIApplication.AddResponseLN(Const S : String);
  422. begin
  423. AddResponse(S+LineEnding);
  424. end;
  425. Procedure TCustomCGIApplication.AddResponseLN(Const Fmt : String; Args : Array of const);
  426. begin
  427. AddResponseLN(Format(Fmt,Args));
  428. end;
  429. Function TCustomCGIApplication.VariableIsUploadedFile(Const VarName : String) : boolean;
  430. begin
  431. Result:=FRequest.Files.IndexOfFile(VarName)<>-1;
  432. end;
  433. Function TCustomCGIApplication.UploadedFileName(Const VarName : String) : String;
  434. begin
  435. If VariableIsUploadedFile(VarName) then
  436. Result:=FRequest.Files.FileByName(VarName).LocalFileName
  437. else
  438. Result:='';
  439. end;
  440. { TCGIHTTPRequest }
  441. function TCGIRequest.GetCGIVar(Index: integer): String;
  442. Var
  443. R : String;
  444. begin
  445. Case Index of
  446. 1 : R:=GetEnvironmentVariable(CGIVarNames[4]); // Property GatewayInterface : String Index 1 Read GetCGIVar;
  447. 2 : R:=GetEnvironmentVariable(CGIVarNames[10]); // Property RemoteIdent : String Index 2 read GetCGIVar;
  448. 3 : R:=GetEnvironmentVariable(CGIVarNames[11]); // Property RemoteUser : String Index 3 read GetCGIVar;
  449. 4 : R:=GetEnvironmentVariable(CGIVarNames[12]); // Property RequestMethod : String Index 4 read GetCGIVar;
  450. 5 : R:=GetEnvironmentVariable(CGIVarNames[14]); // Property ServerName : String Index 5 read GetCGIVar;
  451. 6 : R:=GetEnvironmentVariable(CGIVarNames[16]); // Property ServerProtocol : String Index 6 read GetCGIVar;
  452. 7 : R:=GetEnvironmentVariable(CGIVarNames[17]); // Property ServerSoftware : String Index 7 read GetCGIVar;
  453. end;
  454. Result:=HTTPDecode(R);
  455. end;
  456. Procedure TCGIRequest.InitFromEnvironment;
  457. Var
  458. I : Integer;
  459. N,V,OV : String;
  460. begin
  461. For I:=1 to CGIVarCount do
  462. begin
  463. N:=MapCgiToHTTP[i];
  464. if (N<>'') then
  465. begin
  466. OV:=GetFieldByName(N);
  467. V:=GetEnvironmentVariable(CGIVarNames[I]);
  468. If (OV='') or (V<>'') then
  469. SetFieldByName(N,HTTPDecode(V));
  470. end;
  471. end;
  472. end;
  473. Function TCGIRequest.GetFieldValue(Index : Integer) : String;
  474. Function DecodeVar(I : Integer) : String;
  475. begin
  476. Result:=HTTPDecode(GetEnvironmentVariable(CGIVarNames[I]));
  477. end;
  478. begin
  479. Case Index of
  480. 25 : Result:=Decodevar(5); // Property PathInfo
  481. 26 : Result:=DecodeVar(6); // Property PathTranslated
  482. 27 : Result:=DecodeVar(8); // Property RemoteAddress
  483. 28 : Result:=DecodeVar(9); // Property RemoteHost
  484. 29 : Result:=DecodeVar(13); // Property ScriptName
  485. 30 : Result:=DecodeVar(15); // Property ServerPort
  486. else
  487. Result:=Inherited GetFieldValue(Index);
  488. end;
  489. end;
  490. { TCGIResponse }
  491. procedure TCGIResponse.DoSendHeaders(Headers : TStrings);
  492. begin
  493. {$ifdef CGIDEBUG}
  494. SendMethodEnter('TCGIResponse.DoSendHeaders');
  495. SendDebug('Headers: '+Headers.Text);
  496. {$endif}
  497. if Assigned(FOutput) then
  498. Headers.SaveToStream(FOutput);
  499. {$ifdef CGIDEBUG}
  500. SendMethodExit('TCGIResponse.DoSendHeaders');
  501. {$endif}
  502. end;
  503. procedure TCGIResponse.DoSendContent;
  504. begin
  505. {$ifdef CGIDEBUG}
  506. SendMethodEnter('TCGIResponse.DoSendContent');
  507. {$endif}
  508. If Assigned(ContentStream) then
  509. FOutput.CopyFrom(ContentStream,0)
  510. else
  511. Contents.SaveToStream(FOutput);
  512. {$ifdef CGIDEBUG}
  513. SendMethodExit('TCGIResponse.DoSendContent');
  514. {$endif}
  515. end;
  516. constructor TCGIResponse.CreateCGI(ACGI: TCustomCGIApplication; AStream: TStream);
  517. begin
  518. inherited Create(ACGI.Request);
  519. FCGI:=ACGI;
  520. FOutput:=AStream;
  521. end;
  522. initialization
  523. finalization
  524. {$ifdef CGIDEBUG}
  525. if (SendError<>'') then
  526. Writeln('Debug failed: ',SendError);
  527. {$endif}
  528. end.