summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Monnerat <patrick@monnerat.net>2021-10-25 12:58:37 +0200
committerDaniel Stenberg <daniel@haxx.se>2021-11-15 10:40:03 +0100
commitb20b364764cca2d577640126494fe2b09ae22e5d (patch)
tree78846a017865e51309fa64bc9bed29c8c6a63b09
parent6ec28eb68729240cd359803dd2f65cb3d5b03f42 (diff)
downloadcurl-b20b364764cca2d577640126494fe2b09ae22e5d.tar.gz
mime: use percent-escaping for multipart form field and file names
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
-rw-r--r--docs/cmdline-opts/Makefile.inc1
-rw-r--r--docs/cmdline-opts/form-escape.d10
-rw-r--r--docs/libcurl/curl_easy_setopt.32
-rw-r--r--docs/libcurl/opts/CURLOPT_MIME_OPTIONS.389
-rw-r--r--docs/libcurl/opts/Makefile.inc1
-rw-r--r--docs/libcurl/symbols-in-versions2
-rw-r--r--docs/options-in-versions1
-rw-r--r--include/curl/curl.h6
-rw-r--r--lib/easyoptions.c3
-rw-r--r--lib/mime.c64
-rw-r--r--lib/setopt.c7
-rw-r--r--lib/urldata.h1
-rw-r--r--packages/OS400/curl.inc.in5
-rw-r--r--src/tool_cfgable.h1
-rw-r--r--src/tool_getparam.c7
-rw-r--r--src/tool_listhelp.c3
-rw-r--r--src/tool_operate.c4
-rw-r--r--tests/data/Makefile.inc3
-rw-r--r--tests/data/test115810
-rw-r--r--tests/data/test118698
-rw-r--r--tests/data/test118763
-rw-r--r--tests/data/test1189108
-rw-r--r--tests/data/test396
23 files changed, 464 insertions, 31 deletions
diff --git a/docs/cmdline-opts/Makefile.inc b/docs/cmdline-opts/Makefile.inc
index 506025a75..f8b571127 100644
--- a/docs/cmdline-opts/Makefile.inc
+++ b/docs/cmdline-opts/Makefile.inc
@@ -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
index 000000000..5fcd9ac1d
--- /dev/null
+++ b/docs/cmdline-opts/form-escape.d
@@ -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.
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index 4171b789e..2fcfe2e75 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -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
index 000000000..79210e3d0
--- /dev/null
+++ b/docs/libcurl/opts/CURLOPT_MIME_OPTIONS.3
@@ -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)"
diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc
index 6b153c13f..5a270a8a9 100644
--- a/docs/libcurl/opts/Makefile.inc
+++ b/docs/libcurl/opts/Makefile.inc
@@ -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 \
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index a8f2e08be..791834d6e 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -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
diff --git a/docs/options-in-versions b/docs/options-in-versions
index ac087a1ef..e75df6e06 100644
--- a/docs/options-in-versions
+++ b/docs/options-in-versions
@@ -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
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 6b6ac8a05..7b69ce2d6 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -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()
*
diff --git a/lib/easyoptions.c b/lib/easyoptions.c
index b6131d432..04871ad1e 100644
--- a/lib/easyoptions.c
+++ b/lib/easyoptions.c
@@ -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
diff --git a/lib/mime.c b/lib/mime.c
index f40cc1a61..7783b8990 100644
--- a/lib/mime.c
+++ b/lib/mime.c
@@ -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;
}
diff --git a/lib/setopt.c b/lib/setopt.c
index 56d9c4992..ddb010259 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -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],
diff --git a/lib/urldata.h b/lib/urldata.h
index 22068882f..f12e99b8d 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -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 */
diff --git a/packages/OS400/curl.inc.in b/packages/OS400/curl.inc.in
index b6a37a60a..d0990a76a 100644
--- a/packages/OS400/curl.inc.in
+++ b/packages/OS400/curl.inc.in
@@ -1585,6 +1585,8 @@
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
@@ -1676,6 +1678,9 @@
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'
diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h
index eff55f95d..227b914e3 100644
--- a/src/tool_cfgable.h
+++ b/src/tool_cfgable.h
@@ -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 */
diff --git a/src/tool_getparam.c b/src/tool_getparam.c
index 1fe7d5d09..7abbcc639 100644
--- a/src/tool_getparam.c
+++ b/src/tool_getparam.c
@@ -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;
diff --git a/src/tool_listhelp.c b/src/tool_listhelp.c
index ce92ec02c..4bb9fd4b5 100644
--- a/src/tool_listhelp.c
+++ b/src/tool_listhelp.c
@@ -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},
diff --git a/src/tool_operate.c b/src/tool_operate.c
index ed3b2f56a..bbf743dab 100644
--- a/src/tool_operate.c
+++ b/src/tool_operate.c
@@ -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);
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index b6a503e72..cbc709d63 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -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 \
diff --git a/tests/data/test1158 b/tests/data/test1158
index fd21d74bb..3771f5d0b 100644
--- a/tests/data/test1158
+++ b/tests/data/test1158
@@ -50,11 +50,11 @@ POST /we/want/%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
-Content-Length: 954
+Content-Length: 958
Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32
------------------------------24e78000bd32
-Content-Disposition: form-data; name="file"; filename="test%TESTNUMBER\".txt"
+Content-Disposition: form-data; name="file"; filename="test%TESTNUMBER%22.txt"
Content-Type: mo/foo
foo bar
@@ -63,7 +63,7 @@ bar
foo
------------------------------24e78000bd32
-Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER\".txt"
+Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER%22.txt"
Content-Type: text/plain
foo bar
@@ -75,7 +75,7 @@ foo
Content-Disposition: form-data; name="file3"
Content-Type: multipart/mixed; boundary=----------------------------7f0e85a48b0b
-Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"
+Content-Disposition: attachment; filename="test%TESTNUMBER%22.txt"
Content-Type: m/f
foo bar
@@ -83,7 +83,7 @@ This is a bar foo
bar
foo
-Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"
+Content-Disposition: attachment; filename="test%TESTNUMBER%22.txt"
Content-Type: text/plain
foo bar
diff --git a/tests/data/test1186 b/tests/data/test1186
new file mode 100644
index 000000000..e4662b697
--- /dev/null
+++ b/tests/data/test1186
@@ -0,0 +1,98 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP FORMPOST
+</keywords>
+</info>
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 10
+
+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
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+Content-Length: 954
+Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32
+
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="file"; filename="test%TESTNUMBER\".txt"
+Content-Type: mo/foo
+
+foo bar
+This is a bar foo
+bar
+foo
+
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER\".txt"
+Content-Type: text/plain
+
+foo bar
+This is a bar foo
+bar
+foo
+
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="file3"
+Content-Type: multipart/mixed; boundary=----------------------------7f0e85a48b0b
+
+Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"
+Content-Type: m/f
+
+foo bar
+This is a bar foo
+bar
+foo
+
+Content-Disposition: attachment; filename="test%TESTNUMBER\".txt"
+Content-Type: text/plain
+
+foo bar
+This is a bar foo
+bar
+foo
+
+
+------------------------------24e78000bd32--
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test1187 b/tests/data/test1187
new file mode 100644
index 000000000..53abf908f
--- /dev/null
+++ b/tests/data/test1187
@@ -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
+To: another
+
+body
+</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
+MAIL FROM:<sender@example.com>
+RCPT TO:<recipient@example.com>
+DATA
+QUIT
+</protocol>
+<upload>
+Content-Type: multipart/mixed; boundary=----------------------------
+Mime-Version: 1.0
+
+------------------------------
+
+This is the mail text
+------------------------------
+Content-Disposition: attachment; filename="strange\\file\"name"
+
+File content
+--------------------------------
+.
+</upload>
+</verify>
+</testcase>
diff --git a/tests/data/test1189 b/tests/data/test1189
new file mode 100644
index 000000000..229e443d9
--- /dev/null
+++ b/tests/data/test1189
@@ -0,0 +1,108 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP FORMPOST
+</keywords>
+</info>
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 10
+
+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
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+Content-Length: 1186
+Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32
+
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="name"
+
+daniel
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="tool"
+
+curl
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="str1"
+
+@literal
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="str2"
+
+<verbatim;type=xxx/yyy
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="file"; filename="fakerfile"
+Content-Type: moo/foobar
+
+foo bar
+This is a bar foo
+bar
+foo
+
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="file2"; filename="test%TESTNUMBER.txt"
+Content-Type: text/plain
+
+foo bar
+This is a bar foo
+bar
+foo
+
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="file3"; filename="f\\\\ak\\\\er,\\an\\d;.t\"xt"
+Content-Type: mo/foo
+
+foo bar
+This is a bar foo
+bar
+foo
+
+------------------------------24e78000bd32
+Content-Disposition: form-data; name="file4"; filename="A\\AA\"\"\\\"ZZZ"
+Content-Type: text/plain
+
+foo bar
+This is a bar foo
+bar
+foo
+
+------------------------------24e78000bd32--
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test39 b/tests/data/test39
index 8b00ada8c..81c7119bc 100644
--- a/tests/data/test39
+++ b/tests/data/test39
@@ -47,7 +47,7 @@ POST /we/want/%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
-Content-Length: 1184
+Content-Length: 1180
Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32
------------------------------24e78000bd32
@@ -85,7 +85,7 @@ bar
foo
------------------------------24e78000bd32
-Content-Disposition: form-data; name="file3"; filename="f\\\\ak\\\\er,\\an\\d;.t\"xt"
+Content-Disposition: form-data; name="file3"; filename="f\\ak\\er,\an\d;.t%22xt"
Content-Type: mo/foo
foo bar
@@ -94,7 +94,7 @@ bar
foo
------------------------------24e78000bd32
-Content-Disposition: form-data; name="file4"; filename="A\\AA\"\"\\\"ZZZ"
+Content-Disposition: form-data; name="file4"; filename="A\AA%22%22\%22ZZZ"
Content-Type: text/plain
foo bar