]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
mime: use percent-escaping for multipart form field and file names
authorPatrick Monnerat <patrick@monnerat.net>
Mon, 25 Oct 2021 10:58:37 +0000 (12:58 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 15 Nov 2021 09:40:03 +0000 (10:40 +0100)
Until now, form field and file names where escaped using the
backslash-escaping algorithm defined for multipart mails. This commit
replaces this with the percent-escaping method for URLs.

As this may introduce incompatibilities with server-side applications, a
new libcurl option CURLOPT_MIME_OPTIONS with bitmask
CURLMIMEOPT_FORMESCAPE is introduced to revert to legacy use of
backslash-escaping. This is controlled by new cli tool option
--form-escape.

New tests and documentation are provided for this feature.

Reported by: Ryan Sleevi
Fixes #7789
Closes #7805

23 files changed:
docs/cmdline-opts/Makefile.inc
docs/cmdline-opts/form-escape.d [new file with mode: 0644]
docs/libcurl/curl_easy_setopt.3
docs/libcurl/opts/CURLOPT_MIME_OPTIONS.3 [new file with mode: 0644]
docs/libcurl/opts/Makefile.inc
docs/libcurl/symbols-in-versions
docs/options-in-versions
include/curl/curl.h
lib/easyoptions.c
lib/mime.c
lib/setopt.c
lib/urldata.h
packages/OS400/curl.inc.in
src/tool_cfgable.h
src/tool_getparam.c
src/tool_listhelp.c
src/tool_operate.c
tests/data/Makefile.inc
tests/data/test1158
tests/data/test1186 [new file with mode: 0644]
tests/data/test1187 [new file with mode: 0644]
tests/data/test1189 [new file with mode: 0644]
tests/data/test39

index 506025a753373fe27ecb9e6aa8fcfa338c5a8b36..f8b5711271c454d2ea1910b85813ab477f2de41e 100644 (file)
@@ -75,6 +75,7 @@ DPAGES = \
   fail-with-body.d \
   fail.d \
   false-start.d \
+  form-escape.d \
   form-string.d \
   form.d \
   ftp-account.d \
diff --git a/docs/cmdline-opts/form-escape.d b/docs/cmdline-opts/form-escape.d
new file mode 100644 (file)
index 0000000..5fcd9ac
--- /dev/null
@@ -0,0 +1,10 @@
+Long: form-escape
+Help: Escape multipart form field/file names using backslash
+Protocols: HTTP
+See-also: form
+Added: 7.81.0
+Category: http post
+Example: --form-escape --form 'field\\name=curl' 'file=@load"this' $URL
+---
+Tells curl to pass on names of multipart form fields and files using
+backslash-escaping instead of percent-encoding.
index 4171b789e2dd550f1df02564f20e7307f57b0307..2fcfe2e75058eb5bbceba0cf64b960eeda87fbb8 100644 (file)
@@ -462,6 +462,8 @@ Upload data. See \fICURLOPT_UPLOAD(3)\fP
 Set upload buffer size. See \fICURLOPT_UPLOAD_BUFFERSIZE(3)\fP
 .IP CURLOPT_MIMEPOST
 Post/send MIME data. See \fICURLOPT_MIMEPOST(3)\fP
+.IP CURLOPT_MIME_OPTIONS
+Set MIME option flags. See \fICURLOPT_MIME_OPTIONS(3)\fP
 .IP CURLOPT_MAXFILESIZE
 Maximum file size to get. See \fICURLOPT_MAXFILESIZE(3)\fP
 .IP CURLOPT_MAXFILESIZE_LARGE
diff --git a/docs/libcurl/opts/CURLOPT_MIME_OPTIONS.3 b/docs/libcurl/opts/CURLOPT_MIME_OPTIONS.3
new file mode 100644 (file)
index 0000000..79210e3
--- /dev/null
@@ -0,0 +1,89 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_MIME_OPTIONS 3 "2 Oct 2021" "libcurl 7.81.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_MIME_OPTIONS \- set MIME option flags
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_MIME_OPTIONS, long options);
+.SH DESCRIPTION
+Pass a long that holds a bitmask of CURLMIMEOPT_* defines. Each bit is a
+Boolean flag used while encoding a MIME tree or multipart form data.
+
+Available bits are:
+.IP CURLMIMEOPT_FORMESCAPE
+Tells libcurl to escape multipart form field and file names using the
+backslash-escaping algorithm rather than percent-encoding (HTTP only).
+
+Backslash-escaping consists in preceding backslashes and double quotes with
+a backslash. Percent encoding maps all occurrences of double quote,
+carriage return and line feed to %22, %0D and %0A respectively.
+
+Before version 7.81.0, percent-encoding was never applied.
+
+HTTP browsers used to do backslash-escaping in the past but have over time
+transitioned to use percent-encoding. This option allows to address
+server-side applications that have not yet have been converted.
+
+As an example, consider field or file name \fIstrange\\name"kind\fP.
+When the containing multipart form is sent, this is normally transmitted as
+\fIstrange\\name%22kind\fP. When this option is set, it is sent as
+\fIstrange\\\\name\\"kind\fP.
+.SH DEFAULT
+0, meaning disabled.
+.SH PROTOCOLS
+HTTP, IMAP, SMTP
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+curl_mime *form = NULL;
+
+if(curl) {
+  curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
+  curl_easy_setopt(curl, CURLOPT_MIME_OPTIONS, CURLMIMEOPT_FORMESCAPE);
+
+  form = curl_mime_init(curl);
+  if(form) {
+    curl_mimepart *part = curl_mime_addpart(form);
+
+    if(part) {
+      curl_mime_filedata(part, "strange\\\\file\\\\name");
+      curl_mime_name(part, "strange\\"field\\"name");
+      curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);
+
+      /* Perform the request */
+      curl_easy_perform(curl);
+    }
+  }
+
+  curl_easy_cleanup(curl);
+  curl_mime_free(mime);
+}
+.fi
+.SH AVAILABILITY
+Option added in 7.81.0.
+.SH RETURN VALUE
+Returns CURLE_OK
+.SH "SEE ALSO"
+.BR CURLOPT_MIMEPOST "(3), " CURLOPT_HTTPPOST "(3)"
index 6b153c13ff5b840e9b2379483fd04f13b78ee9b8..5a270a8a9a1570bc60d8495e4c206e2f950146eb 100644 (file)
@@ -233,6 +233,7 @@ man_MANS =                                      \
   CURLOPT_MAX_RECV_SPEED_LARGE.3                \
   CURLOPT_MAX_SEND_SPEED_LARGE.3                \
   CURLOPT_MIMEPOST.3                            \
+  CURLOPT_MIME_OPTIONS.3                        \
   CURLOPT_NETRC.3                               \
   CURLOPT_NETRC_FILE.3                          \
   CURLOPT_NEW_DIRECTORY_PERMS.3                 \
index a8f2e08beac2ea475ebdc405e70965850bccbfc1..791834d6e6cf69253297c27465be99f1d6424fd1 100644 (file)
@@ -324,6 +324,7 @@ CURLKHTYPE_ED25519              7.58.0
 CURLKHTYPE_RSA                  7.19.6
 CURLKHTYPE_RSA1                 7.19.6
 CURLKHTYPE_UNKNOWN              7.19.6
+CURLMIMEOPT_FORMESCAPE          7.81.0
 CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE 7.30.0
 CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE 7.30.0
 CURLMOPT_MAXCONNECTS            7.16.3
@@ -504,6 +505,7 @@ CURLOPT_MAXREDIRS               7.5
 CURLOPT_MAX_RECV_SPEED_LARGE    7.15.5
 CURLOPT_MAX_SEND_SPEED_LARGE    7.15.5
 CURLOPT_MIMEPOST                7.56.0
+CURLOPT_MIME_OPTIONS            7.81.0
 CURLOPT_MUTE                    7.1           7.8         7.15.5
 CURLOPT_NETRC                   7.1
 CURLOPT_NETRC_FILE              7.11.0
index ac087a1ef313e355131f03055f3e2bad4a6f4ab5..e75df6e06013ab7b16de4d64ceea4fe52f85973a 100644 (file)
@@ -64,6 +64,7 @@
 --fail-with-body                     7.76.0
 --false-start                        7.42.0
 --form (-F)                          5.0
+--form-escape                        7.81.0
 --form-string                        7.13.2
 --ftp-account                        7.13.0
 --ftp-alternative-to-user            7.15.5
index 6b6ac8a05e888fe625581d304335fb5efa079515..7b69ce2d673a0743dfad89f7246840ec0d7d76cd 100644 (file)
@@ -2132,6 +2132,9 @@ typedef enum {
    * (in seconds) */
   CURLOPT(CURLOPT_MAXLIFETIME_CONN, CURLOPTTYPE_LONG, 314),
 
+  /* Set MIME option flags. */
+  CURLOPT(CURLOPT_MIME_OPTIONS, CURLOPTTYPE_LONG, 315),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
@@ -2291,6 +2294,9 @@ CURL_EXTERN int curl_strnequal(const char *s1, const char *s2, size_t n);
 typedef struct curl_mime      curl_mime;      /* Mime context. */
 typedef struct curl_mimepart  curl_mimepart;  /* Mime part context. */
 
+/* CURLMIMEOPT_ defines are for the CURLOPT_MIME_OPTIONS option. */
+#define CURLMIMEOPT_FORMESCAPE  (1<<0) /* Use backslash-escaping for forms. */
+
 /*
  * NAME curl_mime_init()
  *
index b6131d4321ca0502fe11ae17a0b2981963398794..04871ad1e3b7891ae70847102f046b46a64adefa 100644 (file)
@@ -170,6 +170,7 @@ struct curl_easyoption Curl_easyopts[] = {
   {"MAX_RECV_SPEED_LARGE", CURLOPT_MAX_RECV_SPEED_LARGE, CURLOT_OFF_T, 0},
   {"MAX_SEND_SPEED_LARGE", CURLOPT_MAX_SEND_SPEED_LARGE, CURLOT_OFF_T, 0},
   {"MIMEPOST", CURLOPT_MIMEPOST, CURLOT_OBJECT, 0},
+  {"MIME_OPTIONS", CURLOPT_MIME_OPTIONS, CURLOT_LONG, 0},
   {"NETRC", CURLOPT_NETRC, CURLOT_VALUES, 0},
   {"NETRC_FILE", CURLOPT_NETRC_FILE, CURLOT_STRING, 0},
   {"NEW_DIRECTORY_PERMS", CURLOPT_NEW_DIRECTORY_PERMS, CURLOT_LONG, 0},
@@ -359,6 +360,6 @@ struct curl_easyoption Curl_easyopts[] = {
  */
 int Curl_easyopts_check(void)
 {
-  return ((CURLOPT_LASTENTRY%10000) != (314 + 1));
+  return ((CURLOPT_LASTENTRY%10000) != (315 + 1));
 }
 #endif
index f40cc1a618ac5339d4652c2e9ba2d92c3b38fd5c..7783b8990a03e4350e2d53ddb5f72a603eb13fda 100644 (file)
@@ -40,6 +40,7 @@
 #include "rand.h"
 #include "slist.h"
 #include "strcase.h"
+#include "dynbuf.h"
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
 #include "curl_memory.h"
@@ -279,29 +280,52 @@ static void mimesetstate(struct mime_state *state,
 
 
 /* Escape header string into allocated memory. */
-static char *escape_string(const char *src)
-{
-  size_t bytecount = 0;
-  size_t i;
-  char *dst;
+static char *escape_string(struct Curl_easy *data,
+                           const char *src, enum mimestrategy strategy)
+{
+  CURLcode result;
+  struct dynbuf db;
+  const char * const *table;
+  const char * const *p;
+  /* replace first character by rest of string. */
+  static const char * const mimetable[] = {
+    "\\\\\\",
+    "\"\\\"",
+    NULL
+  };
+  /* WHATWG HTML living standard 4.10.21.8 2 specifies:
+     For field names and filenames for file fields, the result of the
+     encoding in the previous bullet point must be escaped by replacing
+     any 0x0A (LF) bytes with the byte sequence `%0A`, 0x0D (CR) with `%0D`
+     and 0x22 (") with `%22`.
+     The user agent must not perform any other escapes. */
+  static const char * const formtable[] = {
+    "\"%22",
+    "\r%0D",
+    "\n%0A",
+    NULL
+  };
 
-  for(i = 0; src[i]; i++)
-    if(src[i] == '"' || src[i] == '\\')
-      bytecount++;
+  table = formtable;
+  /* data can be NULL when this function is called indirectly from
+     curl_formget(). */
+  if(strategy == MIMESTRATEGY_MAIL ||
+     (data && (data->set.mime_options & CURLMIMEOPT_FORMESCAPE)))
+    table = mimetable;
 
-  bytecount += i;
-  dst = malloc(bytecount + 1);
-  if(!dst)
-    return NULL;
+  Curl_dyn_init(&db, CURL_MAX_INPUT_LENGTH);
 
-  for(i = 0; *src; src++) {
-    if(*src == '"' || *src == '\\')
-      dst[i++] = '\\';
-    dst[i++] = *src;
+  for(result = Curl_dyn_add(&db, ""); !result && *src; src++) {
+    for(p = table; *p && **p != *src; p++)
+      ;
+
+    if(*p)
+      result = Curl_dyn_add(&db, *p + 1);
+    else
+      result = Curl_dyn_addn(&db, src, 1);
   }
 
-  dst[i] = '\0';
-  return dst;
+  return Curl_dyn_ptr(&db);
 }
 
 /* Check if header matches. */
@@ -1866,12 +1890,12 @@ CURLcode Curl_mime_prepare_headers(curl_mimepart *part,
       char *filename = NULL;
 
       if(part->name) {
-        name = escape_string(part->name);
+        name = escape_string(part->easy, part->name, strategy);
         if(!name)
           ret = CURLE_OUT_OF_MEMORY;
       }
       if(!ret && part->filename) {
-        filename = escape_string(part->filename);
+        filename = escape_string(part->easy, part->filename, strategy);
         if(!filename)
           ret = CURLE_OUT_OF_MEMORY;
       }
index 56d9c49926c06edc8e95264807148cdab3f4167c..ddb010259f97c1a0a6b8abb8523c79210814b234 100644 (file)
@@ -2609,6 +2609,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     break;
 #endif
 
+#if (!defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_MIME)) || \
+  !defined(CURL_DISABLE_SMTP) || !defined(CURL_DISABLE_IMAP)
+  case CURLOPT_MIME_OPTIONS:
+    data->set.mime_options = va_arg(param, long);
+    break;
+#endif
+
   case CURLOPT_SASL_AUTHZID:
     /* Authorisation identity (identity to act as) */
     result = Curl_setstropt(&data->set.str[STRING_SASL_AUTHZID],
index 22068882f04d066994e5322971c03d068fb243ba..f12e99b8d75a6c40af3e194fb1c65638f8d94f77 100644 (file)
@@ -1749,6 +1749,7 @@ struct UserDefined {
   unsigned int scope_id;  /* Scope id for IPv6 */
   long allowed_protocols;
   long redir_protocols;
+  long mime_options;      /* Mime option flags. */
   struct curl_slist *mail_rcpt; /* linked list of mail recipients */
   /* Common RTSP header options */
   Curl_RtspReq rtspreq; /* RTSP request type */
index b6a37a60af02d6362da081ac42cd52ed8e9b73b7..d0990a76a5c83cb8c904590d79837160607e6e7c 100644 (file)
      d                 c                   40310
      d  CURLOPT_MAXLIFETIME_CONN...
      d                 c                   00314
+     d  CURLOPT_MIME_OPTIONS...
+     d                 c                   00315
       *
       /if not defined(CURL_NO_OLDIES)
      d  CURLOPT_FILE   c                   10001
      d  CURLFORM_CONTENTLEN...
      d                 c                   20
       *
+     d CURLMIMEOPT_FORMESCAPE...
+     d                                     X'0000000'
+      *
      d CURLINFO        s             10i 0 based(######ptr######)               Enum
      d  CURLINFO_EFFECTIVE_URL...                                               CURLINFO_STRING + 1
      d                 c                   X'00100001'
index eff55f95dbae696a669ba36948b71f42ce081515..227b914e3379040a3ef3e9e643f0c478fb22d406 100644 (file)
@@ -239,6 +239,7 @@ struct OperationConfig {
   char *ftp_account;        /* for ACCT */
   char *ftp_alternative_to_user;  /* send command if USER/PASS fails */
   int ftp_filemethod;
+  long mime_options;        /* Mime option flags. */
   long tftp_blksize;        /* TFTP BLKSIZE option */
   bool tftp_no_options;     /* do not send TFTP options requests */
   bool ignorecl;            /* --ignore-content-length */
index 1fe7d5d09a0d0e33f65bbab47b36a9b570e8de88..7abbcc639de38bc94492528c5c9e2b1d769f5ec3 100644 (file)
@@ -138,6 +138,7 @@ static const struct LongShort aliases[]= {
   {"$h", "retry-delay",              ARG_STRING},
   {"$i", "retry-max-time",           ARG_STRING},
   {"$k", "proxy-negotiate",          ARG_BOOL},
+  {"$l", "form-escape",              ARG_BOOL},
   {"$m", "ftp-account",              ARG_STRING},
   {"$n", "proxy-anyauth",            ARG_BOOL},
   {"$o", "trace-time",               ARG_BOOL},
@@ -988,6 +989,12 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
           return PARAM_LIBCURL_DOESNT_SUPPORT;
         break;
 
+      case 'l': /* --form-escape */
+        config->mime_options &= ~CURLMIMEOPT_FORMESCAPE;
+        if(toggle)
+          config->mime_options |= CURLMIMEOPT_FORMESCAPE;
+        break;
+
       case 'm': /* --ftp-account */
         GetStr(&config->ftp_account, nextarg);
         break;
index ce92ec02c476ef95bdf4bec4420ec5915598735b..4bb9fd4b51bded71ecaafdc3841fb0e236ade8fc 100644 (file)
@@ -193,6 +193,9 @@ const struct helptxt helptext[] = {
   {"-F, --form <name=content>",
    "Specify multipart MIME data",
    CURLHELP_HTTP | CURLHELP_UPLOAD},
+  {"    --form-escape",
+   "Escape multipart form field/file names using backslash",
+   CURLHELP_HTTP | CURLHELP_POST},
   {"    --form-string <name=string>",
    "Specify multipart MIME data",
    CURLHELP_HTTP | CURLHELP_UPLOAD},
index ed3b2f56ad5c075f7e7aad140ebfcee5c58215eb..bbf743dabb40b1530d365a296063747550319173 100644 (file)
@@ -1325,6 +1325,10 @@ static CURLcode single_transfer(struct GlobalConfig *global,
         if(result)
           break;
 
+        /* new in libcurl 7.81.0 */
+        if(config->mime_options)
+          my_setopt(curl, CURLOPT_MIME_OPTIONS, config->mime_options);
+
         /* new in libcurl 7.10.6 (default is Basic) */
         if(config->authtype)
           my_setopt_bitmask(curl, CURLOPT_HTTPAUTH, (long)config->authtype);
index b6a503e72adcaf3e4132848567209da819794e25..cbc709d63e68954f4a8485cb4294c7472f147ea5 100644 (file)
@@ -144,8 +144,7 @@ test1152 test1153 test1154 test1155 test1156 test1157 test1158 test1159 \
 test1160 test1161 test1162 test1163 test1164 test1165 test1166 test1167 \
 test1168 test1169 test1170 test1171 test1172 test1173 test1174 test1175 \
 test1176 test1177 test1178 test1179 test1180 test1181 test1182 test1183 \
-test1184 test1185 \
-test1188 \
+test1184 test1185 test1186 test1187 test1188 test1189 \
 \
 test1190 test1191 test1192 test1193 test1194 test1195 test1196 test1197 \
 test1198 test1199 \
index fd21d74bb15d5e53a3ff728eda112a8c8fb08cfe..3771f5d0bb0156f7891131b122054eba464d5be0 100644 (file)
@@ -50,11 +50,11 @@ POST /we/want/%TESTNUMBER HTTP/1.1
 Host: %HOSTIP:%HTTPPORT\r
 User-Agent: curl/%VERSION\r
 Accept: */*\r
-Content-Length: 954\r
+Content-Length: 958\r
 Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32\r
 \r
 ------------------------------24e78000bd32\r
-Content-Disposition: form-data; name="file"; filename="test%TESTNUMBER\".txt"\r
+Content-Disposition: form-data; name="file"; filename="test%TESTNUMBER%22.txt"\r
 Content-Type: mo/foo\r
 \r
 foo bar
@@ -63,7 +63,7 @@ bar
 foo
 \r
 ------------------------------24e78000bd32\r
-Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER\".txt"\r
+Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER%22.txt"\r
 Content-Type: text/plain\r
 \r
 foo bar
@@ -75,7 +75,7 @@ foo
 Content-Disposition: form-data; name="file3"\r
 Content-Type: multipart/mixed; boundary=----------------------------7f0e85a48b0b\r
 \r
-Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"\r
+Content-Disposition: attachment; filename="test%TESTNUMBER%22.txt"\r
 Content-Type: m/f\r
 \r
 foo bar
@@ -83,7 +83,7 @@ This is a bar foo
 bar
 foo
 \r
-Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"\r
+Content-Disposition: attachment; filename="test%TESTNUMBER%22.txt"\r
 Content-Type: text/plain\r
 \r
 foo bar
diff --git a/tests/data/test1186 b/tests/data/test1186
new file mode 100644 (file)
index 0000000..e4662b6
--- /dev/null
@@ -0,0 +1,98 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP FORMPOST
+</keywords>
+</info>
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK\r
+Date: Tue, 09 Nov 2010 14:49:00 GMT\r
+Server: test-server/fake\r
+Content-Length: 10\r
+\r
+blablabla
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<name>
+Multipart formposting with backslash-escaping filename containing '"'
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER --form-escape -F "file=@\"log/test%TESTNUMBER\\\".txt\";type=mo/foo;filename=\"test%TESTNUMBER\\\".txt\"" -F 'file2=@"log/test%TESTNUMBER\".txt"' -F 'file3=@"log/test%TESTNUMBER\".txt";type=m/f,"log/test%TESTNUMBER\".txt"'
+</command>
+<precheck>
+perl -e "print 'Test requires a system supporting double quotes in file names' if ($^O eq 'msys');"
+</precheck>
+# We create this file before the command is invoked!
+<file name=log/test%TESTNUMBER".txt>
+foo bar
+This is a bar foo
+bar
+foo
+</file>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^(Content-Type: multipart/form-data;|Content-Type: multipart/mixed; boundary=|-------).*
+</strip>
+<protocol>
+POST /we/want/%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Content-Length: 954\r
+Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32\r
+\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="file"; filename="test%TESTNUMBER\".txt"\r
+Content-Type: mo/foo\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER\".txt"\r
+Content-Type: text/plain\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="file3"\r
+Content-Type: multipart/mixed; boundary=----------------------------7f0e85a48b0b\r
+\r
+Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"\r
+Content-Type: m/f\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"\r
+Content-Type: text/plain\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+\r
+------------------------------24e78000bd32--\r
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test1187 b/tests/data/test1187
new file mode 100644 (file)
index 0000000..53abf90
--- /dev/null
@@ -0,0 +1,63 @@
+<testcase>
+<info>
+<keywords>
+SMTP
+MULTIPART
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+smtp
+</server>
+ <name>
+SMTP multipart with file name escaping
+ </name>
+<stdin>
+From: different\r
+To: another\r
+\r
+body\r
+</stdin>
+ <command>
+smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-from sender@example.com -F  "=This is the mail text" -F '=File content;filename="strange\file\"name"'
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strippart>
+s/^--------------------------[a-z0-9]*/------------------------------/
+s/boundary=------------------------[a-z0-9]*/boundary=----------------------------/
+</strippart>
+<protocol>
+EHLO %TESTNUMBER\r
+MAIL FROM:<sender@example.com>\r
+RCPT TO:<recipient@example.com>\r
+DATA\r
+QUIT\r
+</protocol>
+<upload>
+Content-Type: multipart/mixed; boundary=----------------------------\r
+Mime-Version: 1.0\r
+\r
+------------------------------\r
+\r
+This is the mail text\r
+------------------------------\r
+Content-Disposition: attachment; filename="strange\\file\"name"\r
+\r
+File content\r
+--------------------------------\r
+.\r
+</upload>
+</verify>
+</testcase>
diff --git a/tests/data/test1189 b/tests/data/test1189
new file mode 100644 (file)
index 0000000..229e443
--- /dev/null
@@ -0,0 +1,108 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP FORMPOST
+</keywords>
+</info>
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK\r
+Date: Tue, 09 Nov 2010 14:49:00 GMT\r
+Server: test-server/fake\r
+Content-Length: 10\r
+\r
+blablabla
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+ <name>
+Multipart formposting with backslash-escaping of name= and filename=
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER --form-escape -F name=daniel -F tool=curl --form-string "str1=@literal" --form-string "str2=<verbatim;type=xxx/yyy" -F "file=@log/test%TESTNUMBER.txt;type=moo/foobar;filename=fakerfile" -F file2=@log/test%TESTNUMBER.txt -F "file3=@\"log/test%TESTNUMBER.txt\";type=mo/foo;filename=\"f\\\\\\\\ak\\\\\\er,\\\\an\\d;.t\\\"xt\"" -F 'file4=@"log/test%TESTNUMBER.txt"; filename="A\\AA\"\"\\\"ZZZ"'
+</command>
+# We create this file before the command is invoked!
+<file name="log/test%TESTNUMBER.txt">
+foo bar
+This is a bar foo
+bar
+foo
+</file>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^(Content-Type: multipart/form-data;|-------).*
+</strip>
+<protocol>
+POST /we/want/%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+Content-Length: 1186\r
+Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32\r
+\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="name"\r
+\r
+daniel\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="tool"\r
+\r
+curl\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="str1"\r
+\r
+@literal\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="str2"\r
+\r
+<verbatim;type=xxx/yyy\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="file"; filename="fakerfile"\r
+Content-Type: moo/foobar\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER.txt"\r
+Content-Type: text/plain\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="file3"; filename="f\\\\ak\\\\er,\\an\\d;.t\"xt"\r
+Content-Type: mo/foo\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+------------------------------24e78000bd32\r
+Content-Disposition: form-data; name="file4"; filename="A\\AA\"\"\\\"ZZZ"\r
+Content-Type: text/plain\r
+\r
+foo bar
+This is a bar foo
+bar
+foo
+\r
+------------------------------24e78000bd32--\r
+</protocol>
+</verify>
+</testcase>
index 8b00ada8c8fa9b5ce73bc297090a92a17e9ff0c7..81c7119bc721cde45480363b771be78bbbde12ea 100644 (file)
@@ -47,7 +47,7 @@ POST /we/want/%TESTNUMBER HTTP/1.1
 Host: %HOSTIP:%HTTPPORT\r
 User-Agent: curl/%VERSION\r
 Accept: */*\r
-Content-Length: 1184\r
+Content-Length: 1180\r
 Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32\r
 \r
 ------------------------------24e78000bd32\r
@@ -85,7 +85,7 @@ bar
 foo
 \r
 ------------------------------24e78000bd32\r
-Content-Disposition: form-data; name="file3"; filename="f\\\\ak\\\\er,\\an\\d;.t\"xt"\r
+Content-Disposition: form-data; name="file3"; filename="f\\ak\\er,\an\d;.t%22xt"\r
 Content-Type: mo/foo\r
 \r
 foo bar
@@ -94,7 +94,7 @@ bar
 foo
 \r
 ------------------------------24e78000bd32\r
-Content-Disposition: form-data; name="file4"; filename="A\\AA\"\"\\\"ZZZ"\r
+Content-Disposition: form-data; name="file4"; filename="A\AA%22%22\%22ZZZ"\r
 Content-Type: text/plain\r
 \r
 foo bar