diff options
author | Patrick Monnerat <patrick@monnerat.net> | 2021-10-25 12:58:37 +0200 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2021-11-15 10:40:03 +0100 |
commit | b20b364764cca2d577640126494fe2b09ae22e5d (patch) | |
tree | 78846a017865e51309fa64bc9bed29c8c6a63b09 | |
parent | 6ec28eb68729240cd359803dd2f65cb3d5b03f42 (diff) | |
download | curl-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.inc | 1 | ||||
-rw-r--r-- | docs/cmdline-opts/form-escape.d | 10 | ||||
-rw-r--r-- | docs/libcurl/curl_easy_setopt.3 | 2 | ||||
-rw-r--r-- | docs/libcurl/opts/CURLOPT_MIME_OPTIONS.3 | 89 | ||||
-rw-r--r-- | docs/libcurl/opts/Makefile.inc | 1 | ||||
-rw-r--r-- | docs/libcurl/symbols-in-versions | 2 | ||||
-rw-r--r-- | docs/options-in-versions | 1 | ||||
-rw-r--r-- | include/curl/curl.h | 6 | ||||
-rw-r--r-- | lib/easyoptions.c | 3 | ||||
-rw-r--r-- | lib/mime.c | 64 | ||||
-rw-r--r-- | lib/setopt.c | 7 | ||||
-rw-r--r-- | lib/urldata.h | 1 | ||||
-rw-r--r-- | packages/OS400/curl.inc.in | 5 | ||||
-rw-r--r-- | src/tool_cfgable.h | 1 | ||||
-rw-r--r-- | src/tool_getparam.c | 7 | ||||
-rw-r--r-- | src/tool_listhelp.c | 3 | ||||
-rw-r--r-- | src/tool_operate.c | 4 | ||||
-rw-r--r-- | tests/data/Makefile.inc | 3 | ||||
-rw-r--r-- | tests/data/test1158 | 10 | ||||
-rw-r--r-- | tests/data/test1186 | 98 | ||||
-rw-r--r-- | tests/data/test1187 | 63 | ||||
-rw-r--r-- | tests/data/test1189 | 108 | ||||
-rw-r--r-- | tests/data/test39 | 6 |
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 |