Commits

gustafn  committed 8eda5d7

Fix handling of escaped quotes in multi-input names (e.g. file-names
in content disposition). Extend regression test.

  • Participants
  • Parent commits 89be3b7

Comments (0)

Files changed (2)

 
 #include "nsd.h"
 
-NS_RCSID("@(#) $Header$");
-
-
 /*
  * Local functions defined in this file.
  */
 
 static void ParseQuery(char *form, Ns_Set *set, Tcl_Encoding encoding);
 static void ParseMultiInput(Conn *connPtr, char *start, char *end);
-static char *Ext2Utf(Tcl_DString *dsPtr, char *s, int len, Tcl_Encoding encoding);
+static char *Ext2Utf(Tcl_DString *dsPtr, char *s, int len, Tcl_Encoding encoding, char unescape);
 static int GetBoundary(Tcl_DString *dsPtr, Ns_Conn *conn);
 static char *NextBoundry(Tcl_DString *dsPtr, char *s, char *e);
-static int GetValue(char *hdr, char *att, char **vsPtr, char **vePtr);
+static int GetValue(char *hdr, char *att, char **vsPtr, char **vePtr, char *unescape);
 
 
 /*
     Tcl_HashEntry *hPtr;
     FormFile      *filePtr;
     char *s, *e, *ks, *ke, *fs, *fe, save, saveend;
-    char *key, *value, *disp;
+    char *key, *value, *disp, unescape;
     Ns_Set *set;
     int new;
 
      */
 
     disp = Ns_SetGet(set, "content-disposition");
-    if (disp != NULL && GetValue(disp, "name=", &ks, &ke)) {
-        key = Ext2Utf(&kds, ks, ke-ks, encoding);
-        if (!GetValue(disp, "filename=", &fs, &fe)) {
-            value = Ext2Utf(&vds, start, end-start, encoding);
+    if (disp != NULL && GetValue(disp, "name=", &ks, &ke, &unsscape)) {
+        key = Ext2Utf(&kds, ks, ke-ks, encoding, unsscape);
+        if (!GetValue(disp, "filename=", &fs, &fe, &unsscape)) {
+	    value = Ext2Utf(&vds, start, end-start, encoding, unescape);
         } else {
-            value = Ext2Utf(&vds, fs, fe-fs, encoding);
+            value = Ext2Utf(&vds, fs, fe-fs, encoding, unescape);
             hPtr = Tcl_CreateHashEntry(&connPtr->files, key, &new);
             if (new) {
                 filePtr = ns_malloc(sizeof(FormFile));
  *      1 if attribute found and value parsed, 0 otherwise.
  *
  * Side effects:
- *      Start and end are stored in given pointers.
+ *      Start and end are stored in given pointers, quoted character, 
+ *      when it was preceded by a backslash.
  *
  *----------------------------------------------------------------------
  */
 
 static int
-GetValue(char *hdr, char *att, char **vsPtr, char **vePtr)
+GetValue(char *hdr, char *att, char **vsPtr, char **vePtr, char *uPtr)
 {
     CONST char *s, *e;
 
         while (*e && !isspace(UCHAR(*e))) {
             ++e;
         }
+	*uPtr = 0;
     } else {
-        /* NB: End of quoted att="value" is next quote. */
+        int escaped = 0;
+
+	*uPtr = 0;
+        /* 
+	 * End of quoted att="value" is next quote.  A quote within
+	 * the quoted string could be escaped with a backslash. In
+	 * case, an escaped quote was detected, report the quote
+	 * character as result.
+	 */
         ++e;
-        while (*e && *e != *s) {
+        while (*e && (escaped || *e != *s)) {
+	    if (escaped) {
+	        escaped = 0;
+	    } else if (*e == '\\') {
+	        *uPtr = *s;
+	        escaped = 1;
+	    }
             ++e;
         }
         ++s;
  */
 
 static char *
-Ext2Utf(Tcl_DString *dsPtr, char *start, int len, Tcl_Encoding encoding)
+Ext2Utf(Tcl_DString *dsPtr, char *start, int len, Tcl_Encoding encoding, char unescape)
 {
+
     if (encoding == NULL) {
         Tcl_DStringSetLength(dsPtr, 0);
         Tcl_DStringAppend(dsPtr, start, len);
         Tcl_DStringFree(dsPtr);
         Tcl_ExternalToUtfDString(encoding, start, len, dsPtr);
     }
+
+    /*
+     * In case the string contains backslash escaped characters, the
+     * backslashes have to be removed. This will shorten the resulting
+     * string.
+     */
+    if (unescape) {
+      int i, j;
+      char *buffer = dsPtr->string;
+
+      for (i = 0; i<len; i++) {
+	if (buffer[i] == '\\' && buffer[i+1] == unescape) {
+	  for (j = i; j < len; j++) {
+	    buffer[j] = buffer[j+1];
+	  }
+	  len --;
+	}
+      }
+      Tcl_DStringSetLength(dsPtr, len);
+    }
     return dsPtr->string;
 }

File tests/http.test

     unset -nocomplain r
 } -result {401 0}
 
+
+#
+# Test content-disposition for file uploads
+#
+test http-6.0 {
+    content disposition with 1 plain field and one file
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  set r [nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+	     -getbody t \
+	     POST /form {--AA
+content-disposition: form-data; name="field1"
+
+f1
+--AA
+content-disposition: form-data; name="file"; filename=error.log
+Content-Type: text/plain
+
+content of file
+--AA--}]
+} -cleanup {
+    ns_unregister_proc POST /form
+    unset -nocomplain r
+} -result {200 {4 field1 f1 file error.log file.content-type text/plain}}
+
+#
+# Test content-disposition for file uploads with quoted file name
+#
+test http-6.1 {
+  Content disposition with one file with quoted file name
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  set r [nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+	     -getbody t \
+	     POST /form {--AA
+content-disposition: form-data; name="file"; filename="error.log"
+Content-Type: text/plain
+
+content of file
+--AA--}]
+} -cleanup {
+    ns_unregister_proc POST /form
+    unset -nocomplain r
+} -result {200 {3 file error.log file.content-type text/plain}}
+
+#
+# Test content-disposition for file uploads with quoted file name containing escaped quote
+#
+test http-6.2a {
+  Content disposition with one file with quoted file name containing escaped quote
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  set r [nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+	     -getbody t \
+	     POST /form {--AA
+content-disposition: form-data; name="file"; filename="error \"test\".log"
+Content-Type: text/plain
+
+content of file
+--AA--}]
+} -cleanup {
+    ns_unregister_proc POST /form
+    unset -nocomplain r
+} -result {200 {3 file {error "test".log} file.content-type text/plain}}
+
+test http-6.2b {
+  Content disposition with one file with quoted file name containing escaped single quote
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  set r [nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+	     -getbody t \
+	     POST /form {--AA
+content-disposition: form-data; name="file"; filename='error \'test\'.log'
+Content-Type: text/plain
+
+content of file
+--AA--}]
+} -cleanup {
+    ns_unregister_proc POST /form
+    unset -nocomplain r
+} -result {200 {3 file {error 'test'.log} file.content-type text/plain}}
+
+test http-6.2c {
+  Content disposition with one file with single quoted file name containing double quote
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+      -getbody t \
+      POST /form {--AA
+content-disposition: form-data; name="file"; filename='error "test.log'
+Content-Type: text/plain
+
+content of file
+--AA--}
+} -cleanup {
+    ns_unregister_proc POST /form
+} -result {200 {3 file {error "test.log} file.content-type text/plain}}
+
+test http-6.2d {
+  Content disposition with one file with double quoted file name containing single quote
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+      -getbody t \
+      POST /form {--AA
+content-disposition: form-data; name="file"; filename="error 'test.log"
+Content-Type: text/plain
+
+content of file
+--AA--}
+} -cleanup {
+    ns_unregister_proc POST /form
+} -result {200 {3 file {error 'test.log} file.content-type text/plain}}
+
+
+#
+# Test content-disposition for file uploads with quoted file name containing space
+#
+test http-6.3 {
+  Content disposition with one file with quoted file name containing space
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  set r [nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+	     -getbody t \
+	     POST /form {--AA
+content-disposition: form-data; name="file"; filename="error test.log"
+Content-Type: text/plain
+
+content of file
+--AA--}]
+} -cleanup {
+    ns_unregister_proc POST /form
+    unset -nocomplain r
+} -result {200 {3 file {error test.log} file.content-type text/plain}}
+
+#
+# Test content-disposition for file uploads with unquoted file name containing space
+#
+test http-6.4 {
+  Content disposition with one file and unquoted file name containing space
+} -constraints serverListen -setup {
+    ns_register_proc POST /form {
+      set s [ns_getform] 
+      lappend result [ns_set size $s]
+      for {set i 0} {$i < [ns_set size $s]-1} {incr i} {
+	lappend result [ns_set key $s $i] [ns_set value $s $i]
+      }
+      ns_return 200 text/plain $result
+    }
+} -body {
+  set r [nstest::http -http 1.0 -setheaders {Content-Type multipart/form-data;boundary=AA} \
+	     -getbody t \
+	     POST /form {--AA
+content-disposition: form-data; name="file"; filename=error test.log
+Content-Type: text/plain
+
+content of file
+--AA--}]
+} -cleanup {
+    ns_unregister_proc POST /form
+    unset -nocomplain r
+} -result {200 {3 file error file.content-type text/plain}}
+
+
 cleanupTests