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
This commit is contained in:
Patrick Monnerat 2021-10-25 12:58:37 +02:00 committed by Daniel Stenberg
parent 6ec28eb687
commit b20b364764
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
23 changed files with 463 additions and 30 deletions

View 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

View 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)
static char *escape_string(struct Curl_easy *data,
const char *src, enum mimestrategy strategy)
{
size_t bytecount = 0;
size_t i;
char *dst;
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;
}

View 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],

View 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 */