Repository URL to install this package:
|
Version:
3.2.0 ▾
|
{
This file is part of the Free Pascal run time library.
Copyright (c) 1999-2014 by the Free Pascal development team
TCGIApplication class.
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
{ $define CGIDEBUG}
{$mode objfpc}
{$H+}
unit custcgi;
Interface
uses
CustWeb, Classes,SysUtils, httpdefs, cgiprotocol, httpprotocol;
Type
{ TCGIRequest }
TCGIHandler = Class;
// Content read handler. PByte points to read content, len is length.
// Return False in ContinueReading to abort reading.
TCGIContentReadEvent = Procedure (Sender : TRequest; Content : PByte; Len : Integer; Var ContinueReading : Boolean) of object;
TCGIRequest = Class(TRequest)
Private
FCGI : TCGIHandler;
FOnContentRead: TCGIContentReadEvent;
function GetCGIVar(Index: integer): String;
Protected
Function DoMapCgiToHTTP(Const AVariableName : String; Out AHeaderType : THeader; Out AVariableType : THTTPVariableType) : Boolean;
function DoGetCGIVar(AVarName: String): String; virtual;
Procedure InitFromEnvironment; virtual;
// Read content from stdin. Calls DoContentRead to see if reading must be aborted.
procedure ReadContent; override;
// Called whenever input is read from stdin. Calls OnContentRead.
// Returns True to continue reading, false to abort reading.
Function DoContentRead(B : PByte; Len : Integer) : Boolean; virtual;
Public
Constructor CreateCGI(ACGI : TCGIHandler);
Function GetCustomHeader(const Name: String) : String; override;
Property OnContentRead : TCGIContentReadEvent Read FOnContentRead Write FOnContentRead;
// Index is index in CGIVarnames array.
Property GatewayInterface : String Index 4 Read GetCGIVar;
Property RemoteIdent : String Index 10 read GetCGIVar;
Property RemoteUser : String Index 11 read GetCGIVar;
Property RequestMethod : String Index 12 read GetCGIVar;
Property ServerName : String Index 14 read GetCGIVar;
Property ServerProtocol : String Index 16 read GetCGIVar;
Property ServerSoftware : String Index 17 read GetCGIVar;
Property ServerSignature : String Index 28 Read GetCGIVar;
Property ServerAddr : String Index 29 Read GetCGIVar;
Property DocumentRoot : String Index 30 Read GetCGIVar;
Property ServerAdmin : String Index 31 Read GetCGIVar;
Property ScriptFileName : String Index 32 Read GetCGIVar;
Property RemotePort : String Index 33 Read GetCGIVar;
Property RequestURI : String Index 34 Read GetCGIVar;
Property ScriptURI : String Index 38 Read GetCGIVar;
Property ContextDocumentRoot : String Index 40 Read GetCGIVar;
Property ContextPrefix : String Index 41 Read GetCGIVar;
Property RequestScheme : String Index 44 Read GetCGIVar;
end;
TCGIRequestClass = Class of TCGIRequest;
{ TCGIResponse }
TCGIResponse = Class(TResponse)
private
FCGI : TCGIHandler;
FOutput : TStream;
Protected
Procedure DoSendHeaders(Headers : TStrings); override;
Procedure DoSendContent; override;
Public
Constructor CreateCGI(ACGI : TCGIHandler; AStream : TStream);
end;
TCGIResponseClass = Class of TCGIResponse;
{ TCustomCgiApplication }
{ TCgiHandler }
TCgiHandler = Class(TWebHandler)
Private
FResponse : TCGIResponse;
FRequest : TCGIRequest;
FOutput : TStream;
protected
Function GetEmail : String; override;
Function GetAdministrator : String; override;
Function CreateResponse(AOutput : TStream) : TCGIResponse; virtual;
Function CreateRequest : TCGIRequest; virtual;
Procedure InitRequest(ARequest : TRequest); override;
function WaitForRequest(out ARequest : TRequest; out AResponse : TResponse) : boolean; override;
procedure EndRequest(ARequest : TRequest;AResponse : TResponse); override;
Public
Procedure GetCGIVarList(List : TStrings);
Property Request : TCGIRequest read FRequest;
Property Response: TCGIResponse Read FResponse;
end;
TCgiHandlerClass = Class of TCgiHandler;
{ TCustomCgiApplication }
TCustomCGIApplication = Class(TCustomWebApplication)
private
function GetRequest: TCGIRequest;
function GetRequestVariable(VarName : String): String;
function GetRequestVariableCount: Integer;
function GetResponse: TCGIResponse;
protected
function InitializeWebHandler: TWebHandler; override;
public
Procedure ShowException(E: Exception);override;
Property Request : TCGIRequest read GetRequest;
Property Response: TCGIResponse Read GetResponse;
Procedure AddResponse(Const S : String);
Procedure AddResponse(Const Fmt : String; Args : Array of const);
Procedure AddResponseLn(Const S : String);
Procedure AddResponseLn(Const Fmt : String; Args : Array of const);
Procedure GetCGIVarList(List : TStrings);
Function VariableIsUploadedFile(Const VarName : String) : boolean;
Function UploadedFileName(Const VarName : String) : String;
Property RequestVariables[VarName : String] : String Read GetRequestVariable;
Property RequestVariableCount : Integer Read GetRequestVariableCount;
end;
ECGI = Class(EFPWebError);
Var
CGIRequestClass : TCGIRequestClass = TCGIRequest;
CGIResponseClass : TCGIResponseClass = TCGIResponse;
CGIWebHandlerClass : TCgiHandlerClass = TCgiHandler;
ContentReadRetryInterval : Word = 100; // wait x milliseconds before retrying read
ContentReadMaxRetryCount : Word = 150; // wait x times before aborting retry
ResourceString
SWebMaster = 'webmaster';
SErrNoContentLength = 'No content length passed from server!';
Implementation
uses
{$ifdef CGIDEBUG}
dbugintf,
{$endif}
iostream;
Type
TMap = record
h : THeader;
v : THTTPVariableType;
end;
TCGIHeaderMap = Array[1..CGIVarCount] of TMap;
Const
MapCgiToHTTP : TCGIHeaderMap =
({ 1: 'AUTH_TYPE' } (h : hhWWWAuthenticate; v : hvUnknown), // ?
{ 2: 'CONTENT_LENGTH' } (h : hhContentLength; v : hvUnknown),
{ 3: 'CONTENT_TYPE' } (h : hhContentType; v : hvUnknown),
{ 4: 'GATEWAY_INTERFACE' } (h:hhUnknown; v : hvUnknown),
{ 5: 'PATH_INFO' } (h:hhUnknown; v : hvPathInfo),
{ 6: 'PATH_TRANSLATED' } (h:hhUnknown; v : hvPathTranslated),
{ 7: 'QUERY_STRING' } (h:hhUnknown; v : hvQuery),
{ 8: 'REMOTE_ADDR' } (h:hhUnknown; v : hvRemoteAddress),
{ 9: 'REMOTE_HOST' } (h:hhUnknown; v : hvRemoteHost),
{ 10: 'REMOTE_IDENT' } (h:hhUnknown; v : hvUnknown),
{ 11: 'REMOTE_USER' } (h:hhUnknown; v : hvUnknown),
{ 12: 'REQUEST_METHOD' } (h:hhUnknown; v : hvMethod),
{ 13: 'SCRIPT_NAME' } (h:hhUnknown; v : hvScriptName),
{ 14: 'SERVER_NAME' } (h:hhServer; v : hvUnknown),
{ 15: 'SERVER_PORT' } (h:hhUnknown; v : hvServerPort),
{ 16: 'SERVER_PROTOCOL' } (h:hhUnknown; v : hvUnknown),
{ 17: 'SERVER_SOFTWARE' } (h:hhUnknown; v : hvUnknown),
{ 18: 'HTTP_ACCEPT' } (h:hhAccept; v : hvUnknown),
{ 19: 'HTTP_ACCEPT_CHARSET' } (h:hhAcceptCharset; v : hvUnknown),
{ 20: 'HTTP_ACCEPT_ENCODING' } (h:hhAcceptEncoding; v : hvUnknown),
{ 21: 'HTTP_IF_MODIFIED_SINCE' } (h:hhIfModifiedSince; v : hvUnknown),
{ 22: 'HTTP_REFERER' } (h:hhReferer; v : hvUnknown),
{ 23: 'HTTP_USER_AGENT' } (h:hhUserAgent; v : hvUnknown),
{ 24: 'HTTP_COOKIE' } (h:hhUnknown; v : hvCookie),
{ 25: 'HTTP_IF_NONE_MATCH } (h:hhIfNoneMatch; v : hvUnknown),
// Additional Apache vars
{ 26: 'HTTP_CONNECTION' } (h:hhConnection; v : hvUnknown),
{ 27: 'HTTP_ACCEPT_LANGUAGE' } (h:hhAcceptLanguage; v : hvUnknown),
{ 28: 'HTTP_HOST' } (h:hhHost; v : hvUnknown),
{ 29: 'SERVER_SIGNATURE' } (h:hhUnknown; v : hvUnknown),
{ 30: 'SERVER_ADDR' } (h:hhUnknown; v : hvUnknown),
{ 31: 'DOCUMENT_ROOT' } (h:hhUnknown; v : hvUnknown),
{ 32: 'SERVER_ADMIN' } (h:hhUnknown; v : hvUnknown),
{ 33: 'SCRIPT_FILENAME' } (h:hhUnknown; v : hvUnknown),
{ 34: 'REMOTE_PORT' } (h:hhUnknown; v : hvUnknown),
{ 35: 'REQUEST_URI' } (h:hhUnknown; v : hvUnknown),
{ 36: 'CONTENT' } (h:hhUnknown; v : hvContent),
{ 37: 'XHTTPREQUESTEDWITH' } (h:hhUnknown; v : hvXRequestedWith),
{ 38: 'HTTP_AUTHORIZATION' } (h:hhAuthorization; v : hvUnknown),
{ 39: 'SCRIPT_URI' } (h:hhUnknown; v : hvUnknown),
{ 40: 'SCRIPT_URL' } (h:hhUnknown; v : hvURL),
{ 41: 'CONTEXT_DOCUMENT_ROOT' } (h:hhUnknown; v : hvUnknown),
{ 42: 'CONTEXT_PREFIX' } (h:hhUnknown; v : hvUnknown),
{ 43: 'HTTP_CACHE_CONTROL' } (h:hhCacheControl; v : hvUnknown),
{ 44: 'HTTP_PRAGMA' } (h:hhPragma; v : hvUnknown),
{ 45: 'REQUEST_SCHEME' } (h:hhUnknown; v : hvUnknown)
);
procedure TCgiHandler.GetCGIVarList(List: TStrings);
Var
I : Integer;
begin
List.Clear;
For I:=1 to cgiVarCount do
List.Add(CGIVarNames[i]+'='+GetEnvironmentVariable(CGIVarNames[i]));
end;
function TCgiHandler.GetEmail: String;
Var
H : String;
begin
Result:=inherited GetEmail;
If (Result='') then
begin
H:=Request.ServerName;
If (H<>'') then
Result:=Administrator+'@'+H
end;
end;
function TCgiHandler.GetAdministrator: String;
begin
Result:=Inherited GetAdministrator;
If (result='') then
Result:=SWebMaster;
end;
function TCgiHandler.CreateResponse(AOutput : TStream): TCGIResponse;
Var
C : TCGIResponseClass;
begin
C:=CGIResponseClass;
if (C=Nil) then
C:=TCGIResponse;
Result:=TCGIResponse.CreateCGI(Self,AOutput);
end;
function TCgiHandler.CreateRequest: TCGIRequest;
Var
C : TCGIRequestClass;
begin
C:=CGIRequestClass;
if (C=Nil) then
C:=TCGIRequest;
Result:=C.CreateCGI(Self);
end;
procedure TCgiHandler.InitRequest(ARequest: TRequest);
begin
inherited InitRequest(ARequest);
if (ARequest is TCGIRequest) then
With (ARequest as TCGIRequest) do
begin
InitFromEnvironment;
InitRequestVars;
end;
end;
function TCgiHandler.WaitForRequest(out ARequest: TRequest; out AResponse: TResponse): boolean;
begin
FOutput:=TIOStream.Create(iosOutput);
FRequest:=CreateRequest;
FResponse:=CreateResponse(FOutput);
InitRequest(FRequest);
InitResponse(FResponse);
ARequest:=FRequest;
AResponse:=FResponse;
Result := True;
end;
procedure TCgiHandler.EndRequest(ARequest: TRequest; AResponse: TResponse);
begin
inherited;
FreeAndNil(FOutPut);
Terminate;
end;
constructor TCGIRequest.CreateCGI(ACGI: TCGIHandler);
begin
Inherited Create;
FCGI:=ACGI;
end;
function TCGIRequest.GetCustomHeader(const Name: String): String;
begin
Result:=inherited GetCustomHeader(Name);
// Check environment
if (Result='') then
Result:=DoGetCGIVAr('HTTP_'+StringReplace(Uppercase(Name),'-','_',[rfReplaceAll]));
end;
{ TCGIHTTPRequest }
function TCGIRequest.DoGetCGIVar(AVarName : String) : String;
begin
Result := GetEnvironmentVariable(AVarName);
end;
function TCGIRequest.GetCGIVar(Index: integer): String;
Var
R : String;
begin
if Index in [1..CGIVarCount] then
R:=DoGetCGIVar(CGIVarNames[Index])
else
R:='';
Result:=HTTPDecode(R);
end;
function TCGIRequest.DoMapCgiToHTTP(const AVariableName: String; out
AHeaderType: THeader; Out AVariableType: THTTPVariableType): Boolean;
Var
I : Integer;
begin
I:=IndexOfCGIVar(AVariableName);
Result:=I<>-1;
if Result then
begin
AHeaderType:=MapCgiToHTTP[i].H;
AVariableType:=MapCgiToHTTP[i].V;
end;
end;
procedure TCGIRequest.InitFromEnvironment;
Var
I : Integer;
R,V : String;
M : TMap;
begin
For I:=1 to CGIVarCount do
begin
V:=GetEnvironmentVariable(CGIVarNames[I]);
if (V<>'') then
begin
M:=MapCgiToHTTP[i];
if M.H<>hhUnknown then
SetHeader(M.H,HTTPDecode(V))
else if M.V<>hvUnknown then
begin
if M.V<>hvQuery then
V:=HTTPDecode(V);
SetHTTPVariable(M.V,V)
end;
end;
end;
// Microsoft-IIS hack. IIS includes the script name in the PATH_INFO
if Pos('IIS', ServerSoftware) > 0 then
SetHTTPVariable(hvPathInfo,StringReplace(PathInfo, ScriptName, '', [rfReplaceAll, rfIgnoreCase]));
R:=UpCase(Method);
if (R='POST') or (R='PUT') or (ContentLength>0) then
ReadContent;
end;
procedure TCGIRequest.ReadContent;
var
I : TIOStream;
Cl : Integer;
B : Byte;
retrycount: integer;
BytesRead, a: longint;
AbortRead : Boolean;
S : String;
begin
S:='';
Cl := ContentLength;
I:=TIOStream.Create(iosInput);
Try
if (Cl<>0) then
begin
// It can be that the complete content is not yet sent by the server so repeat the read
// until all data is really read
SetLength(S,Cl);
BytesRead:=0;
RetryCount:=0;
AbortRead:=False;
repeat
a := I.Read(S[BytesRead+1],Cl-BytesRead);
BytesRead:=BytesRead+a;
if (A=0) then // In fact this should not happen, but the content could be delayed...
begin
Inc(RetryCount);
AbortRead:=RetryCount>ContentReadMaxRetryCount;
if not AbortRead then
Sleep(ContentReadRetryInterval);
end
else
begin
RetryCount:=0; // We got data, so let's reset this.
AbortRead:=Not DoContentRead(PByte(@S[BytesRead+1]),A);
end;
until (BytesRead>=Cl) or (AbortRead);
// In fact the request is incomplete, but this is not the place to throw an error for that
if BytesRead<Cl then
SetLength(S,BytesRead);
end
else
begin
B:=0;
While (I.Read(B,1)>0) do
S:=S + chr(B);
end;
InitContent(S);
Finally
I.Free;
end;
end;
function TCGIRequest.DoContentRead(B: PByte; Len: Integer): Boolean;
begin
Result:=True;
if Assigned(FOnContentRead) then
FOnContentRead(Self,B,Len,Result);
end;
{ TCGIResponse }
procedure TCGIResponse.DoSendHeaders(Headers : TStrings);
begin
{$ifdef CGIDEBUG}
SendMethodEnter('TCGIResponse.DoSendHeaders');
SendDebug('Headers: '+Headers.Text);
{$endif}
if Assigned(FOutput) then
Headers.SaveToStream(FOutput);
{$ifdef CGIDEBUG}
SendMethodExit('TCGIResponse.DoSendHeaders');
{$endif}
end;
procedure TCGIResponse.DoSendContent;
begin
{$ifdef CGIDEBUG}
SendMethodEnter('TCGIResponse.DoSendContent');
{$endif}
If Assigned(ContentStream) then
FOutput.CopyFrom(ContentStream,0)
else
Contents.SaveToStream(FOutput);
{$ifdef CGIDEBUG}
SendMethodExit('TCGIResponse.DoSendContent');
{$endif}
end;
constructor TCGIResponse.CreateCGI(ACGI: TCgiHandler; AStream: TStream);
begin
inherited Create(ACGI.Request);
FCGI:=ACGI;
FOutput:=AStream;
end;
{ TCustomCGIApplication }
function TCustomCGIApplication.GetRequest: TCGIRequest;
begin
result := TCgiHandler(WebHandler).Request;
end;
function TCustomCGIApplication.GetRequestVariable(VarName : String): String;
begin
If Assigned(Request) then
Result:=Request.QueryFields.Values[VarName]
else
Result:='';
end;
function TCustomCGIApplication.GetRequestVariableCount: Integer;
begin
If Assigned(Request) then
Result:=Request.QueryFields.Count
else
Result:=0;
end;
function TCustomCGIApplication.GetResponse: TCGIResponse;
begin
Result:=TCgiHandler(WebHandler).Response;
end;
function TCustomCGIApplication.InitializeWebHandler: TWebHandler;
Var
C : TCGIHandlerClass;
begin
C:=CGIWebHandlerClass;
if C=Nil then
C:=TCgiHandler;
Result:=C.Create(self);
end;
Procedure TCustomCGIApplication.ShowException(E: Exception);
var
CgiHandler: TCgiHandler;
begin
CgiHandler := WebHandler as TCgiHandler;
if assigned(CgiHandler.FResponse) then
CgiHandler.ShowRequestException(CgiHandler.FResponse,E)
else
inherited ShowException(E);
end;
procedure TCustomCGIApplication.AddResponse(const S: String);
Var
L : Integer;
begin
L:=Length(S);
If L>0 then
Response.Content:=Response.Content+S;
end;
procedure TCustomCGIApplication.AddResponse(const Fmt: String; Args: array of const);
begin
AddResponse(Format(Fmt,Args));
end;
procedure TCustomCGIApplication.AddResponseLn(const S: String);
begin
AddResponse(S+LineEnding);
end;
procedure TCustomCGIApplication.AddResponseLn(const Fmt: String; Args: array of const);
begin
AddResponseLN(Format(Fmt,Args));
end;
procedure TCustomCGIApplication.GetCGIVarList(List: TStrings);
begin
TCgiHandler(WebHandler).GetCGIVarList(list);
end;
function TCustomCGIApplication.VariableIsUploadedFile(const VarName: String): boolean;
begin
Result:=Request.Files.IndexOfFile(VarName)<>-1;
end;
function TCustomCGIApplication.UploadedFileName(const VarName: String): String;
begin
If VariableIsUploadedFile(VarName) then
Result:=Request.Files.FileByName(VarName).LocalFileName
else
Result:='';
end;
initialization
finalization
{$ifdef CGIDEBUG}
if (SendError<>'') then
Writeln('Debug failed: ',SendError);
{$endif}
end.