formpost: support quotes, commas and semicolon in file names

- document the double-quote and backslash need be escaped if quoting.
- libcurl formdata escape double-quote in filename by backslash.
- curl formparse can parse filename both contains '"' and ',' or ';'.
- curl now can uploading file with ',' or ';' in filename.

Bug: http://curl.haxx.se/bug/view.cgi?id=1171
This commit is contained in:
Ulion
2013-01-21 23:20:09 +01:00
committed by Daniel Stenberg
parent fa176376c8
commit 2698520aef
7 changed files with 316 additions and 113 deletions

View File

@@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2013, 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
@@ -34,13 +34,73 @@
#include "memdebug.h" /* keep this as LAST include */
/*
* helper function to get a word from form param
* after call get_parm_word, str either point to string end
* or point to any of end chars.
*/
static char *get_param_word(char **str, char **end_pos)
{
char *ptr = *str;
char *word_begin = NULL;
char *ptr2;
char *escape = NULL;
const char *end_chars = ";,";
/* the first non-space char is here */
word_begin = ptr;
if(*ptr == '"') {
++ptr;
while(*ptr) {
if(*ptr == '\\') {
if(ptr[1] == '\\' || ptr[1] == '"') {
/* remember the first escape position */
if(!escape)
escape = ptr;
/* skip escape of back-slash or double-quote */
ptr += 2;
continue;
}
}
if(*ptr == '"') {
*end_pos = ptr;
if(escape) {
/* has escape, we restore the unescaped string here */
ptr = ptr2 = escape;
do {
if(*ptr == '\\' && (ptr[1] == '\\' || ptr[1] == '"'))
++ptr;
*ptr2++ = *ptr++;
}
while(ptr < *end_pos);
*end_pos = ptr2;
}
while(*ptr && NULL==strchr(end_chars, *ptr))
++ptr;
*str = ptr;
return word_begin+1;
}
++ptr;
}
/* end quote is missing, treat it as non-quoted. */
ptr = word_begin;
}
while(*ptr && NULL==strchr(end_chars, *ptr))
++ptr;
*str = *end_pos = ptr;
return word_begin;
}
/***************************************************************************
*
* formparse()
*
* Reads a 'name=value' parameter and builds the appropriate linked list.
*
* Specify files to upload with 'name=@filename'. Supports specified
* Specify files to upload with 'name=@filename', or 'name=@"filename"'
* in case the filename contain ',' or ';'. Supports specified
* given Content-Type of the files. Such as ';type=<content-type>'.
*
* If literal_value is set, any initial '@' or '<' in the value string
@@ -51,6 +111,10 @@
*
* 'name=@filename,filename2,filename3'
*
* or use double-quotes quote the filename:
*
* 'name=@"filename","filename2","filename3"'
*
* If you want content-types specified for each too, write them like:
*
* 'name=@filename;type=image/gif,filename2,filename3'
@@ -64,7 +128,12 @@
* To upload a file, but to fake the file name that will be included in the
* formpost, do like this:
*
* 'name=@filename;filename=/dev/null'
* 'name=@filename;filename=/dev/null' or quote the faked filename like:
* 'name=@filename;filename="play, play, and play.txt"'
*
* If filename/path contains ',' or ';', it must be quoted by double-quotes,
* else curl will fail to figure out the correct filename. if the filename
* tobe quoted contains '"' or '\', '"' and '\' must be escaped by backslash.
*
* This function uses curl_formadd to fulfill it's job. Is heavily based on
* the old curl_formparse code.
@@ -86,7 +155,6 @@ int formparse(struct Configurable *config,
char *contp;
const char *type = NULL;
char *sep;
char *sep2;
if((1 == sscanf(input, "%255[^=]=", name)) &&
((contp = strchr(input, '=')) != NULL)) {
@@ -107,118 +175,104 @@ int formparse(struct Configurable *config,
struct multi_files *multi_start = NULL;
struct multi_files *multi_current = NULL;
contp++;
char *ptr = contp;
char *end = ptr + strlen(ptr);
do {
/* since this was a file, it may have a content-type specifier
at the end too, or a filename. Or both. */
char *ptr;
char *filename = NULL;
sep = strchr(contp, ';');
sep2 = strchr(contp, ',');
/* pick the closest */
if(sep2 && (sep2 < sep)) {
sep = sep2;
/* no type was specified! */
}
char *word_end;
bool semicolon;
type = NULL;
if(sep) {
bool semicolon = (';' == *sep) ? TRUE : FALSE;
++ptr;
contp = get_param_word(&ptr, &word_end);
semicolon = (';' == *ptr) ? TRUE : FALSE;
*word_end = '\0'; /* terminate the contp */
*sep = '\0'; /* terminate file name at separator */
/* have other content, continue parse */
while(semicolon) {
/* have type or filename field */
++ptr;
while(*ptr && (ISSPACE(*ptr)))
++ptr;
ptr = sep+1; /* point to the text following the separator */
if(checkprefix("type=", ptr)) {
/* set type pointer */
type = &ptr[5];
while(semicolon && ptr && (','!= *ptr)) {
/* pass all white spaces */
while(ISSPACE(*ptr))
ptr++;
if(checkprefix("type=", ptr)) {
/* set type pointer */
type = &ptr[5];
/* verify that this is a fine type specifier */
if(2 != sscanf(type, "%127[^/]/%127[^;,\n]",
type_major, type_minor)) {
warnf(config, "Illegally formatted content-type field!\n");
Curl_safefree(contents);
FreeMultiInfo(&multi_start, &multi_current);
return 2; /* illegal content-type syntax! */
}
/* now point beyond the content-type specifier */
sep = (char *)type + strlen(type_major)+strlen(type_minor)+1;
/* there's a semicolon following - we check if it is a filename
specified and if not we simply assume that it is text that
the user wants included in the type and include that too up
to the next zero or semicolon. */
if(*sep==';') {
if(!checkprefix(";filename=", sep)) {
sep2 = strchr(sep+1, ';');
if(sep2)
sep = sep2;
else
sep = sep + strlen(sep); /* point to end of string */
}
}
else
semicolon = FALSE;
if(*sep) {
*sep = '\0'; /* zero terminate type string */
ptr = sep+1;
}
else
ptr = NULL; /* end */
/* verify that this is a fine type specifier */
if(2 != sscanf(type, "%127[^/]/%127[^;,\n]",
type_major, type_minor)) {
warnf(config, "Illegally formatted content-type field!\n");
Curl_safefree(contents);
FreeMultiInfo(&multi_start, &multi_current);
return 2; /* illegal content-type syntax! */
}
else if(checkprefix("filename=", ptr)) {
filename = &ptr[9];
ptr = strchr(filename, ';');
if(!ptr) {
ptr = strchr(filename, ',');
}
if(ptr) {
*ptr = '\0'; /* zero terminate */
ptr++;
/* now point beyond the content-type specifier */
sep = (char *)type + strlen(type_major)+strlen(type_minor)+1;
/* there's a semicolon following - we check if it is a filename
specified and if not we simply assume that it is text that
the user wants included in the type and include that too up
to the next sep. */
ptr = sep;
if(*sep==';') {
if(!checkprefix(";filename=", sep)) {
ptr = sep + 1;
(void)get_param_word(&ptr, &sep);
semicolon = (';' == *ptr) ? TRUE : FALSE;
}
}
else
/* confusion, bail out of loop */
break;
}
semicolon = FALSE;
sep = ptr;
if(*sep)
*sep = '\0'; /* zero terminate type string */
}
else if(checkprefix("filename=", ptr)) {
ptr += 9;
filename = get_param_word(&ptr, &word_end);
semicolon = (';' == *ptr) ? TRUE : FALSE;
*word_end = '\0';
}
else {
/* unknown prefix, skip to next block */
char *unknown = NULL;
unknown = get_param_word(&ptr, &word_end);
semicolon = (';' == *ptr) ? TRUE : FALSE;
if(*unknown) {
*word_end = '\0';
warnf(config, "skip unknown form field: %s\n", unknown);
}
}
}
/* now ptr point to comma or string end */
/* if type == NULL curl_formadd takes care of the problem */
if(!AddMultiFiles(contp, type, filename, &multi_start,
if(*contp && !AddMultiFiles(contp, type, filename, &multi_start,
&multi_current)) {
warnf(config, "Error building form post!\n");
Curl_safefree(contents);
FreeMultiInfo(&multi_start, &multi_current);
return 3;
}
contp = sep; /* move the contents pointer to after the separator */
} while(sep && *sep); /* loop if there's another file name */
/* *ptr could be '\0', so we just check with the string end */
} while(ptr < end); /* loop if there's another file name */
/* now we add the multiple files section */
if(multi_start) {
struct curl_forms *forms = NULL;
struct multi_files *ptr = multi_start;
struct multi_files *start = multi_start;
unsigned int i, count = 0;
while(ptr) {
ptr = ptr->next;
while(start) {
start = start->next;
++count;
}
forms = malloc((count+1)*sizeof(struct curl_forms));
@@ -228,9 +282,9 @@ int formparse(struct Configurable *config,
FreeMultiInfo(&multi_start, &multi_current);
return 4;
}
for(i = 0, ptr = multi_start; i < count; ++i, ptr = ptr->next) {
forms[i].option = ptr->form.option;
forms[i].value = ptr->form.value;
for(i = 0, start = multi_start; i < count; ++i, start = start->next) {
forms[i].option = start->form.option;
forms[i].value = start->form.value;
}
forms[count].option = CURLFORM_END;
FreeMultiInfo(&multi_start, &multi_current);