Browse Source

* Parse the ContentType according to rfc1341

git-svn-id: trunk@49360 -
joost 4 years ago
parent
commit
20039e3b77

+ 132 - 13
packages/fcl-web/src/base/httpdefs.pp

@@ -363,6 +363,7 @@ type
     // Variables
     Class Function GetVariableHeaderName(AVariable : THTTPVariableType) : String;
     Function  GetHTTPVariable(AVariable : THTTPVariableType) : String;
+    Class Function ParseContentType(const AContentType: String; Parameters: TStrings) : String;
     // Get/Set custom headers.
     Function GetCustomHeader(const Name: String) : String; virtual;
     Procedure SetCustomHeader(const Name, Value: String); virtual;
@@ -1395,6 +1396,125 @@ begin
   Result:=FVariables[AVariable];
 end;
 
+type
+  TParseState =
+    (psStart, psContentType, psSearchParam, psParam, psSearchParamEqual, psSearchParamValue, psParamValueQuoted, psParamValue);
+
+Class Function THTTPHeader.ParseContentType(const AContentType: String; Parameters: TStrings): String;
+var
+  len: Integer;
+  ind: Integer;
+  state: TParseState;
+  start: Integer;
+  paramname: string;
+  paramvalue: string;
+begin
+  // See rfc1341, Content-Type
+  Result := '';
+  len := Length(AContentType);
+  if len=0 then
+    Exit;
+
+  ind := 1;
+  state := psStart;
+  while ind <= len do
+    begin
+    case state of
+      psStart:
+        begin
+        if not (AContentType[ind] in [' ']) then
+          begin
+          state := psContentType;
+          start := ind;
+          end;
+        end;
+      psContentType:
+        begin
+        if (AContentType[ind] in [' ', ';']) then
+          begin
+            Result := Copy(AContentType, start, ind-start);
+          if not Assigned(Parameters) then
+            Exit;
+          state := psSearchParam;
+          end;
+        if (ind = len) then
+          begin
+          Result := Copy(AContentType, start, ind-start+1);
+          end;
+        end;
+      psSearchParam:
+        begin
+        if not (AContentType[ind] in [' ', ';']) then
+          begin
+          state := psParam;
+          start := ind;
+          end;
+        end;
+      psParam:
+        begin
+        if (AContentType[ind] in [' ', '=']) then
+          begin
+            paramname := Copy(AContentType, start, ind-start);
+          if AContentType[ind] = '=' then
+            state := psSearchParamValue
+          else
+            state := psSearchParamEqual
+          end;
+        end;
+      psSearchParamEqual:
+        begin
+        if (AContentType[ind] = '=') then
+          state := psSearchParamValue;
+        end;
+      psSearchParamValue:
+        begin
+        if (AContentType[ind] = '"') then
+          begin
+          state := psParamValueQuoted;
+          start := ind +1;
+          end
+        else if (AContentType[ind] <> ' ') then
+          begin
+          state := psParamValue;
+          start := ind;
+          end;
+        end;
+      psParamValue:
+        begin
+        if (AContentType[ind] in [' ', ';']) then
+          begin
+          paramvalue := Copy(AContentType, start, ind-start);
+          Parameters.Values[paramname] := paramvalue;
+
+          state := psSearchParam;
+          end
+        else if (ind = len) then
+          begin
+          paramvalue := Copy(AContentType, start, ind-start+1);
+          Parameters.Values[paramname] := paramvalue;
+          end
+        end;
+      psParamValueQuoted:
+        begin
+        if AContentType[ind] = '"' then
+          begin
+          paramvalue := Copy(AContentType, start, ind-start);
+          Parameters.Values[paramname] := paramvalue;
+
+          state := psSearchParam;
+          end
+        else if (ind = len) then
+          begin
+          paramvalue := Copy(AContentType, start, ind-start+1);
+          Parameters.Values[paramname] := paramvalue;
+          end
+        end;
+    end;
+
+    inc(ind);
+    end;
+end;
+
 function THTTPHeader.GetHTTPVariable(AIndex: Integer): String;
 begin
   if (AIndex>=0) and (AIndex<=Ord(High(THTTPVariableType))) then
@@ -2385,10 +2505,10 @@ function TRequest.DerriveStreamingContentType(): TStreamingContentType;
 var
   CT: string;
 begin
-  CT:=ContentType;
-  if Pos('MULTIPART/FORM-DATA',Uppercase(CT))<>0 then
+  CT:=Uppercase(ParseContentType(ContentType, nil));
+  if CT='MULTIPART/FORM-DATA' then
     Result := sctMultipart
-  else if Pos('APPLICATION/X-WWW-FORM-URLENCODED',Uppercase(CT))<>0 then
+  else if CT='APPLICATION/X-WWW-FORM-URLENCODED' then
     Result := sctFormUrlEncoded
   else
     Result := sctUnknown
@@ -2397,20 +2517,19 @@ end;
 procedure TRequest.ProcessStreamingMultiPart(const State: TContentStreamingState; const Buf; const Size: Integer);
 Var
   ST: TStrings;
-  S, CT, B: String;
-  I: Integer;
+  S: String;
 begin
 {$ifdef CGIDEBUG} SendMethodEnter('ProcessStreamingMultiPart');{$endif CGIDEBUG}
   if State=cssStart then
     begin
-    CT := ContentType;
-    i:=Pos('=',CT);
-    B:=Copy(CT,I+1,Length(CT)-I);
-    I:=Length(B);
-    If (I>0) and (B[1]='"') then
-      B:=Copy(B,2,I-2);
-    FMimeItems := CreateMimeItems;
-    FMimeItems.Boundary := B;
+    ST := TStringList.Create;
+    try
+      ParseContentType(ContentType, ST);
+      FMimeItems := CreateMimeItems;
+      FMimeItems.Boundary := ST.Values['boundary'];
+    finally
+      ST.Free;
+    end;
     end;
 
   if FKeepFullContents or not FMimeItems.SupportsStreamingProcessing then

+ 99 - 0
packages/fcl-web/tests/integrationtests/fcl-web_integrationtests.jmx

@@ -253,6 +253,105 @@ Small&#xd;
             <hashTree/>
           </hashTree>
         </hashTree>
+        <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Post small file (rfc1341)" enabled="true"/>
+        <hashTree>
+          <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
+            <collectionProp name="HeaderManager.headers">
+              <elementProp name="" elementType="Header">
+                <stringProp name="Header.name">Content-Type</stringProp>
+                <stringProp name="Header.value">multipart/form-data; boundary=&quot;------------------------e6d533842bd07008&quot;; type=Text/HTML</stringProp>
+              </elementProp>
+            </collectionProp>
+          </HeaderManager>
+          <hashTree/>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Post small file - test rfc1341 content type " enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">--------------------------e6d533842bd07008&#xd;
+Content-Disposition: form-data; name=&quot;upload&quot;; filename=&quot;UploadFileSmall.txt&quot;&#xd;
+Content-Type: text/plain&#xd;
+&#xd;
+Small&#xd;
+&#xd;
+--------------------------e6d533842bd07008--</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain"></stringProp>
+            <stringProp name="HTTPSampler.port"></stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path"></stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.files[0].size</stringProp>
+              <stringProp name="EXPECTED_VALUE">7</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.files[0].sha1</stringProp>
+              <stringProp name="EXPECTED_VALUE">d9ce7590765717c47c737add2ac11e0665eabbea</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.files[0].contentType</stringProp>
+              <stringProp name="EXPECTED_VALUE">text/plain</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.files[0].fieldname</stringProp>
+              <stringProp name="EXPECTED_VALUE">upload</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.files[0].filename</stringProp>
+              <stringProp name="EXPECTED_VALUE">UploadFileSmall.txt</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.content.raw</stringProp>
+              <stringProp name="EXPECTED_VALUE"></stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+        </hashTree>
         <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Post UploadFile1.txt" enabled="true">
           <elementProp name="HTTPsampler.Files" elementType="HTTPFileArgs">
             <collectionProp name="HTTPFileArgs.files">