fail-with-body.d \
fail.d \
false-start.d \
+ form-escape.d \
form-string.d \
form.d \
ftp-account.d \
--- /dev/null
+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.
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
--- /dev/null
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * 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)"
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 \
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
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
--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
* (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;
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()
*
{"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},
*/
int Curl_easyopts_check(void)
{
- return ((CURLOPT_LASTENTRY%10000) != (314 + 1));
+ return ((CURLOPT_LASTENTRY%10000) != (315 + 1));
}
#endif
#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"
/* 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. */
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;
}
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],
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 */
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'
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 */
{"$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},
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;
{"-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},
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);
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 \
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
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
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
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
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
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
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
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