Browse Source

+ Added support for file uploads

michael 21 years ago
parent
commit
6d6da25c59
1 changed files with 334 additions and 13 deletions
  1. 334 13
      fcl/inc/cgiapp.pp

+ 334 - 13
fcl/inc/cgiapp.pp

@@ -64,7 +64,8 @@ Type
     FAdministrator : String;
     FAdministrator : String;
     FContentTypeEmitted : Boolean;
     FContentTypeEmitted : Boolean;
     FCGIVars : TCGIVarArray;
     FCGIVars : TCGIVarArray;
-    FRequestVars : TStrings;
+    FRequestVars,
+    FFormFiles : TStrings;
     Function GetCGIVar (Index : Integer) : String;
     Function GetCGIVar (Index : Integer) : String;
     Procedure InitCGIVars;
     Procedure InitCGIVars;
     Procedure InitRequestVars;
     Procedure InitRequestVars;
@@ -79,6 +80,8 @@ Type
     Procedure ProcessQueryString(Const FQueryString : String);
     Procedure ProcessQueryString(Const FQueryString : String);
     Function GetRequestVariable(Const VarName : String) : String;
     Function GetRequestVariable(Const VarName : String) : String;
     Function GetRequestVariableCount : Integer;
     Function GetRequestVariableCount : Integer;
+    Procedure ProcessURLEncoded(M : TMemoryStream);
+    Procedure ProcessMultiPart(M : TMemoryStream; Const Boundary : String);
   Public
   Public
     Constructor Create(AOwner : TComponent); override;
     Constructor Create(AOwner : TComponent); override;
     Destructor Destroy; override;
     Destructor Destroy; override;
@@ -91,7 +94,11 @@ Type
     Procedure GetRequestVarList(List : TStrings);
     Procedure GetRequestVarList(List : TStrings);
     Procedure GetRequestVarList(List : TStrings; NamesOnly : Boolean);
     Procedure GetRequestVarList(List : TStrings; NamesOnly : Boolean);
     Procedure ShowException(E: Exception);override;
     Procedure ShowException(E: Exception);override;
+    Procedure DeleteFormFiles;
     Function EmitContentType : Boolean;
     Function EmitContentType : Boolean;
+    Function GetTempCGIFileName : String;
+    Function VariableIsUploadedFile(Const VarName : String) : boolean;
+    Function UploadedFileName(Const VarName : String) : String;
     Property AuthType : String Index 1 Read GetCGIVar;
     Property AuthType : String Index 1 Read GetCGIVar;
     Property ContentLength : Integer Read GetContentLength Write SetContentLength; // Index 2
     Property ContentLength : Integer Read GetContentLength Write SetContentLength; // Index 2
     Property ContentType : String Index 3 Read GetCGIVar Write SetCGIVar;
     Property ContentType : String Index 3 Read GetCGIVar Write SetCGIVar;
@@ -138,16 +145,50 @@ Implementation
 uses 
 uses 
   iostream;
   iostream;
 
 
+{$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);
 Constructor TCgiApplication.Create(AOwner : TComponent);
 
 
 begin
 begin
   Inherited Create(AOwner);
   Inherited Create(AOwner);
   FRequestVars:=TStringList.Create;
   FRequestVars:=TStringList.Create;
+  FFormFiles:=TStringList.Create;
 end;
 end;
 
 
 Destructor TCgiApplication.Destroy;
 Destructor TCgiApplication.Destroy;
 
 
 begin
 begin
+  DeleteFormFiles;
+  FFormFiles.Free;
   FRequestVars.Free;
   FRequestVars.Free;
   Inherited;
   Inherited;
 end;
 end;
@@ -175,6 +216,29 @@ begin
   end;  
   end;  
 end;
 end;
 
 
+Function TCgiApplication.GetTempCGIFileName : String;
+
+begin
+  Result:=GetTempFileName('/tmp/','CGI')
+end;
+
+Procedure TCgiApplication.DeleteFormFiles;
+
+Var
+  I,P : Integer;
+  FN : String;
+  
+begin
+  For I:=0 to FFormFiles.Count-1 do
+    begin
+    FN:=FFormFiles[i];
+    P:=Pos('=',FN);
+    Delete(FN,1,P);
+    If FileExists(FN) then
+      DeleteFile(FN);
+    end;
+end;
+
 Procedure TCgiApplication.Initialize; 
 Procedure TCgiApplication.Initialize; 
 
 
 begin
 begin
@@ -344,25 +408,263 @@ begin
     Raise Exception.CreateFmt(SErrInvalidRequestMethod,[R]);
     Raise Exception.CreateFmt(SErrInvalidRequestMethod,[R]);
 end;
 end;
 
 
-Procedure TCgiApplication.InitPostVars;
+Procedure TCgiApplication.ProcessURLEncoded(M : TMemoryStream);
+
 
 
 var
 var
   FQueryString : String;
   FQueryString : String;
-  i : Integer;
-  ch : Char;
+  
+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
 begin
-  if (FCGIVars[2]='') then
-    Raise Exception.Create(SErrNoContentLength);
-  if CompareText(ContentType,'APPLICATION/X-WWW-FORM-URLENCODED')<>0 then
-    Raise Exception.CreateFmt(SErrUnsupportedContentType,[ContentType]);
-  SetLength(FQueryString,ContentLength);
-  for I:= 1 to contentLength Do
+  Line:=GetLine(Data);
+  While (Line<>'') do
     begin
     begin
-    Read(ch);
-    FQueryString[i]:=ch;
+    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;
     end;
-  ProcessQueryString(FQueryString);
+  // 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 Exception.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.Add(Key+'='+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
+        While (I.Read(B,1)>0) do
+          M.Write(B,1)
+        end;  
+    Finally
+      I.Free;
+    end;  
+    if Pos(ContentType,'MULTIPART/FORM-DATA')=0 then  
+      ProcessMultiPart(M,ContentType)
+    else if CompareText(ContentType,'APPLICATION/X-WWW-FORM-URLENCODED')=0 then
+      ProcessUrlEncoded(M)
+    else  
+      Raise Exception.CreateFmt(SErrUnsupportedContentType,[ContentType]);
+  finally
+    M.Free;
+  end;    
 end;
 end;
 
 
 Procedure TCgiApplication.InitGetVars;
 Procedure TCgiApplication.InitGetVars;
@@ -551,4 +853,23 @@ begin
   AddResponseLN(Format(Fmt,Args));
   AddResponseLN(Format(Fmt,Args));
 end;
 end;
 
 
+Function TCGIApplication.VariableIsUploadedFile(Const VarName : String) : boolean;
+
+begin
+  Result:=FFormFiles.IndexOfName(VarName)<>-1;  
+end;
+
+Function TCGIApplication.UploadedFileName(Const VarName : String) : String;
+
+begin
+  Result:=FFormFiles.Values[VarName];
+end;
+
+{$ifdef cgidebug}
+Initialization
+  initLog;
+  
+Finalization
+  DoneLog;
+{$endif}
 end.
 end.