{ This file is part of the Free Pascal run time library. Copyright (c) 1999-2000 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. **********************************************************************} {$mode objfpc} {$H+} unit cgiapp; Interface uses CustApp,Classes, SysUtils, httpdefs; Const CGIVarCount = 23 deprecated; Type TCGIVarArray = Array [1..CGIVarCount] of String; Const CgiVarNames : TCGIVarArray = ('AUTH_TYPE', 'CONTENT_LENGTH', 'CONTENT_TYPE', 'GATEWAY_INTERFACE', 'PATH_INFO', 'PATH_TRANSLATED', 'QUERY_STRING', 'REMOTE_ADDR', 'REMOTE_HOST', 'REMOTE_IDENT', 'REMOTE_USER', 'REQUEST_METHOD', 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_PORT', 'SERVER_PROTOCOL', 'SERVER_SOFTWARE', 'HTTP_ACCEPT', 'HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_ENCODING', 'HTTP_IF_MODIFIED_SINCE', 'HTTP_REFERER', 'HTTP_USER_AGENT') deprecated; Type TCgiApplication = Class(TCustomApplication) Private FResponse : TStream; FEmail : String; FAdministrator : String; FContentTypeEmitted : Boolean; FCGIVars : TCGIVarArray; FRequestVars, FFormFiles : TStrings; Function GetCGIVar (Index : Integer) : String; Procedure InitCGIVars; Procedure InitRequestVars; Procedure InitPostVars; Procedure InitGetVars; Procedure SetContentLength (Value : Integer); Procedure SetCGIVar(Index : Integer; Value : String); Function GetContentLength : Integer; Function GetServerPort : Word; Function GetEmail : String; Function GetAdministrator : String; Procedure ProcessQueryString(Const FQueryString : String); Function GetRequestVariable(Const VarName : String) : String; Function GetRequestVariableCount : Integer; Procedure ProcessURLEncoded(M : TMemoryStream); Procedure ProcessMultiPart(M : TMemoryStream; Const Boundary : String); Public Constructor Create(AOwner : TComponent); override; deprecated; Destructor Destroy; override; deprecated; Procedure AddResponse(Const S : String); deprecated; Procedure AddResponse(Const Fmt : String; Args : Array of const); deprecated; Procedure AddResponseLn(Const S : String); deprecated; Procedure AddResponseLn(Const Fmt : String; Args : Array of const); deprecated; Procedure Initialize; override; deprecated; Procedure GetCGIVarList(List : TStrings); deprecated; Procedure GetRequestVarList(List : TStrings); deprecated; Procedure GetRequestVarList(List : TStrings; NamesOnly : Boolean); deprecated; Procedure ShowException(E: Exception);override; deprecated; Procedure DeleteFormFiles; deprecated; Function EmitContentType : Boolean; deprecated; Function GetTempCGIFileName : String; deprecated; Function VariableIsUploadedFile(Const VarName : String) : boolean; deprecated; Function UploadedFileName(Const VarName : String) : String; deprecated; Property AuthType : String Index 1 Read GetCGIVar; deprecated; Property ContentLength : Integer Read GetContentLength Write SetContentLength; deprecated; // Index 2 Property ContentType : String Index 3 Read GetCGIVar Write SetCGIVar; deprecated; Property GatewayInterface : String Index 4 Read GetCGIVar; deprecated; Property PathInfo : String index 5 read GetCGIvar; deprecated; Property PathTranslated : String Index 6 read getCGIVar; deprecated; Property QueryString : String Index 7 read getcgivar; deprecated; Property RemoteAddress : String Index 8 read GetCGIVar; deprecated; Property RemoteHost : String Index 9 read GetCGIVar; deprecated; Property RemoteIdent : String Index 10 read GetCGIVar; deprecated; Property RemoteUser : String Index 11 read GetCGIVar; deprecated; Property RequestMethod : String Index 12 read GetCGIVar; deprecated; Property ScriptName : String Index 13 read GetCGIVar; deprecated; Property ServerName : String Index 14 read GetCGIVar; deprecated; Property ServerPort : Word Read GetServerPort; deprecated; // Index 15 Property ServerProtocol : String Index 16 read GetCGIVar; deprecated; Property ServerSoftware : String Index 17 read GetCGIVar; deprecated; Property HTTPAccept : String Index 18 read GetCGIVar; deprecated; Property HTTPAcceptCharset : String Index 19 read GetCGIVar; deprecated; Property HTTPAcceptEncoding : String Index 20 read GetCGIVar; deprecated; Property HTTPIfModifiedSince : String Index 21 read GetCGIVar; deprecated; // Maybe change to TDateTime ?? Property HTTPReferer : String Index 22 read GetCGIVar; deprecated; Property HTTPUserAgent : String Index 23 read GetCGIVar; deprecated; Property Email : String Read GetEmail Write FEmail; deprecated; Property Administrator : String Read GetAdministrator Write FAdministrator; deprecated; Property RequestVariables[VarName : String] : String Read GetRequestVariable; deprecated; Property RequestVariableCount : Integer Read GetRequestVariableCount; deprecated; Property Response : TStream Read FResponse; deprecated; end; ECGI = Class(Exception); ResourceString SWebMaster = 'webmaster' deprecated; SCGIError = 'CGI Error' deprecated; SAppEncounteredError = 'The application encountered the following error:' deprecated; SError = 'Error: ' deprecated; SNotify = 'Notify: ' deprecated; SErrNoContentLength = 'No content length passed from server!' deprecated; SErrUnsupportedContentType = 'Unsupported content type: "%s"' deprecated; SErrNoRequestMethod = 'No REQUEST_METHOD passed from server.' deprecated; SErrInvalidRequestMethod = 'Invalid REQUEST_METHOD passed from server.' deprecated; Implementation uses iostream; Type TFormFile = Class(TObject) Private FFileName : String; Public Constructor Create(Const AFileName : String); Property FileName : String Read FFileName Write FFileName; end; Constructor TFormFile.Create(Const AFileName : String); begin FFileName:=AFileName; end; {$ifdef cgidebug} Var flog : Text; Procedure Log(Msg : String); begin Writeln(flog,Msg); end; Procedure Log(Msg : String;Args : Array of const); begin Writeln(flog,Format(Msg,Args)); end; Procedure InitLog; begin Assign(flog,'/tmp/cgi.log'); Rewrite(flog); Log('---- Start of log session ---- '); end; Procedure DoneLog; begin Close(Flog); end; {$endif} Constructor TCgiApplication.Create(AOwner : TComponent); begin Inherited Create(AOwner); FRequestVars:=TStringList.Create; FFormFiles:=TStringList.Create; end; Destructor TCgiApplication.Destroy; Var i : Integer; begin DeleteFormFiles; FFormFiles.Free; FRequestVars.Free; Inherited; end; Function TCgiApplication.GetCGIVar (Index : Integer) : String; begin Result:=FCGIVars[Index]; end; Procedure TCgiApplication.InitCGIVars; Var I : Integer; L : TStrings; begin L:=TStringList.Create; Try GetEnvironmentList(L); For I:=1 to CGIVarCount do FCGIVars[i]:=L.Values[CGIVarNames[i]]; Finally L.Free; end; end; Function TCgiApplication.GetTempCGIFileName : String; begin Result:=GetTempFileName('/tmp/','CGI') end; Procedure TCgiApplication.DeleteFormFiles; Var I,P : Integer; FN : String; FF : TFormFile; begin For I:=0 to FFormFiles.Count-1 do begin FF:=TFormFile(FFormFiles.Objects[i]); If Assigned(FF) then begin If FileExists(FF.FileName) then DeleteFile(FF.FileName); FF.Free; end; end; end; Procedure TCgiApplication.Initialize; begin StopOnException:=True; Inherited; InitCGIVars; InitRequestVars; FResponse:=TIOStream.Create(iosOutput); end; Procedure TCgiApplication.GetCGIVarList(List : TStrings); Var I : Integer; begin List.Clear; For I:=1 to cgiVarCount do List.Add(CGIVarNames[i]+'='+FCGIVars[i]); end; Procedure TCgiApplication.GetRequestVarList(List : TStrings); begin GetRequestVarList(List,False); end; Procedure TCgiApplication.GetRequestVarList(List : TStrings; NamesOnly : Boolean); Var I,J : Integer; S : String; begin List.BeginUpdate; Try List.Clear; // Copy one by one, there may be CR/LF in the variables, causing 'Text' to go wrong. If Assigned(FRequestVars) then For I:=0 to FRequestVars.Count-1 do begin S:=FRequestVars[i]; If NamesOnly then begin J:=Pos('=',S); If (J>0) then S:=Copy(S,1,J-1); end; List.Add(S); end; finally List.EndUpdate; end; end; Function TCgiApplication.GetContentLength : Integer; begin Result:=StrToIntDef(GetCGIVar(2),-1); end; Procedure TCgiApplication.SetContentLength (Value : Integer); begin SetCGIVar(2,IntToStr(Value)); end; Procedure TCgiApplication.SetCGIVar(Index : Integer; Value : String); begin If Index in [1..cgiVarCount] then FCGIVars[Index]:=Value; end; Function TCgiApplication.GetServerPort : Word; begin Result:=StrToIntDef(GetCGIVar(15),0); end; Function TCgiApplication.EmitContentType : Boolean; Var S: String; begin Result:=Not FContentTypeEmitted; If result then begin S:=ContentType; If (S='') then S:='text/html'; AddResponseLn('Content-Type: '+ContentType); AddResponseLn(''); FContentTypeEmitted:=True; end; end; Procedure TCgiApplication.ShowException(E: Exception); Var TheEmail : String; FrameCount: integer; Frames: PPointer; FrameNumber:Integer; begin If not FContentTypeEmitted then begin ContentType:='text/html'; EmitContentType; end; If (ContentType='text/html') then begin AddResponseLN('
'+SNotify+Administrator+': '+TheEmail+'
'); AddResponseLN(''); end; end; Function TCgiApplication.GetEmail : String; Var H : String; begin If (FEmail='') then begin H:=ServerName; If (H<>'') then Result:=Administrator+'@'+H else Result:=''; end else Result:=Email; end; Function TCgiApplication.GetAdministrator : String; begin If (FADministrator<>'') then Result:=FAdministrator else Result:=SWebMaster; end; Procedure TCgiApplication.InitRequestVars; var R : String; begin R:=RequestMethod; if (R='') then Raise ECGI.Create(SErrNoRequestMethod); if CompareText(R,'POST')=0 then InitPostVars else if CompareText(R,'GET')=0 then InitGetVars else Raise ECGI.CreateFmt(SErrInvalidRequestMethod,[R]); end; Procedure TCgiApplication.ProcessURLEncoded(M : TMemoryStream); var FQueryString : String; begin SetLength(FQueryString,M.Size); // Skip added Null. M.Read(FQueryString[1],M.Size); ProcessQueryString(FQueryString); end; Type TFormItem = Class(TObject) DisPosition : String; Name : String; isFile : Boolean; FileName : String; ContentType : String; DLen : Integer; Data : String; Procedure Process; end; Procedure TFormItem.Process; Function GetLine(Var S : String) : String; Var P : Integer; begin P:=Pos(#13#10,S); If (P<>0) then begin Result:=Copy(S,1,P-1); Delete(S,1,P+1); end; end; Function GetWord(Var S : String) : String; Var I,len : Integer; Quoted : Boolean; C : Char; begin len:=length(S); quoted:=false; Result:=''; for i:=1 to len do Begin c:=S[i]; if (c='"') then Quoted:=Not Quoted else begin if not (c in [' ','=',';',':']) or Quoted then Result:=Result+C; if (c in [';',':','=']) and (not quoted) then begin Delete(S,1,I); Exit; end; end; end; S:=''; end; Var Line : String; Words : TStringList; i,len : integer; c : char; S : string; quoted : boolean; begin Line:=GetLine(Data); While (Line<>'') do begin S:=GetWord(Line); While (S<>'') do begin If CompareText(S,'Content-Disposition')=0 then Disposition:=GetWord(Line) else if CompareText(S,'name')=0 Then Name:=GetWord(Line) else if CompareText(S,'filename')=0 then begin FileName:=GetWord(Line); isFile:=True; end else if CompareText(S,'Content-Type')=0 then ContentType:=GetWord(Line); S:=GetWord(Line); end; Line:=GetLine(Data); end; // Now Data contains the rest of the data, plus a CR/LF. Strip the CR/LF Len:=Length(Data); If (len>2) then Data:=Copy(Data,1,Len-2); end; Function MakeString(PStart,PEnd : Pchar) : String; begin SetLength(Result,PEnd-PStart); If Length(Result)>0 then Move(PStart^,Result[1],Length(Result)); end; procedure FormSplit(var Cnt : String; boundary: String; List : TList); // Splits the form into items var Sep : string; Clen,slen, p:longint; FI : TFormItem; begin Sep:='--'+boundary+#13+#10; Slen:=length(Sep); CLen:=Pos('--'+Boundary+'--',Cnt); // Cut last marker Cnt:=Copy(Cnt,1,Clen-1); // Cut first marker Delete(Cnt,1,Slen); Clen:=Length(Cnt); While Clen>0 do begin Fi:=TFormItem.Create; List.Add(Fi); P:=pos(Sep,Cnt); If (P=0) then P:=CLen+1; FI.Data:=Copy(Cnt,1,P-1); delete(Cnt,1,P+SLen-1); CLen:=Length(Cnt); end; end; function GetNextLine(Var Data: String):string; Var p : Integer; begin P:=Pos(#13#10,Data); If (P<>0) then begin Result:=Copy(Data,1,P-1); Delete(Data,1,P+1); end; end; Procedure TCgiApplication.ProcessMultiPart(M : TMemoryStream; Const Boundary : String); Var L : TList; B : String; I,Index : Integer; S,FF,key, Value : String; FI : TFormItem; F : TStream; begin i:=Pos('=',Boundary); B:=Copy(Boundary,I+1,Length(Boundary)-I); I:=Length(B); If (I>0) and (B[1]='"') then B:=Copy(B,2,I-2); L:=TList.Create; Try SetLength(S,M.Size); If Length(S)>0 then Move(M.Memory^,S[1],M.Size); FormSplit(S,B,L); For I:=L.Count-1 downto 0 do begin FI:=TFormItem(L[i]); FI.Process; If (FI.Name='') then Raise ECGI.CreateFmt('Invalid multipart encoding: %s',[FI.Data]); Key:=FI.Name; If Not FI.IsFile Then begin Value:=FI.Data end else begin Value:=FI.FileName; FF:=GetTempCGIFileName; FFormFiles.AddObject(Key,TFormFile.Create(FF)); F:=TFileStream.Create(FF,fmCreate); Try if Length(FI.Data)>0 then F.Write(FI.Data[1],Length(FI.Data)); finally F.Free; end; FI.Free; L[i]:=Nil; end; FRequestVars.Add(Key+'='+Value) end; Finally For I:=0 to L.Count-1 do TObject(L[i]).Free; L.Free; end; end; Type TCapacityStream = Class(TMemoryStream) Public Property Capacity; end; Procedure TCgiApplication.InitPostVars; Var M : TCapacityStream; I : TIOStream; Cl : Integer; B : Byte; begin CL:=ContentLength; M:=TCapacityStream.Create; Try I:=TIOStream.Create(iosInput); Try if (CL<>0) then begin M.Capacity:=(Cl); M.CopyFrom(I,Cl); end else begin B:=0; While (I.Read(B,1)>0) do M.Write(B,1) end; Finally I.Free; end; if CompareText(ContentType,'MULTIPART/FORM-DATA')=0 then ProcessMultiPart(M,ContentType) else if CompareText(ContentType,'APPLICATION/X-WWW-FORM-URLENCODED')=0 then ProcessUrlEncoded(M) else Raise ECGI.CreateFmt(SErrUnsupportedContentType,[ContentType]); finally M.Free; end; end; Procedure TCgiApplication.InitGetVars; Var FQueryString : String; begin FQueryString:=QueryString; If (FQueryString<>'') then ProcessQueryString(FQueryString); end; const hexTable = '0123456789ABCDEF'; Procedure TCgiApplication.ProcessQueryString(Const FQueryString : String); var queryItem : String; delimiter : Char; aString : String; aSepStr : String; aPos : Integer; aLenStr : Integer; aLenSep : Integer; function hexConverter(h1, h2 : Char) : Char; var B : Byte; begin B:=(Pos(upcase(h1),hexTable)-1)*16; B:=B+Pos(upcase(h2),hexTable)-1; Result:=chr(B); end; procedure Convert_ESC_Chars; var index : Integer; begin Index:=Length(QueryItem); While (Index>0) do begin If QueryItem[Index]='+' then QueryItem[Index]:=' ' else If (QueryItem[Index]='%') and (Index