aboutsummaryrefslogtreecommitdiff
path: root/src/tool_formparse.c
diff options
context:
space:
mode:
authorUlion <ulion2002@gmail.com>2013-01-21 23:20:09 +0100
committerDaniel Stenberg <daniel@haxx.se>2013-01-22 15:43:29 +0100
commit2698520aef593cbd746a64f79021a4c8d7c83d65 (patch)
tree69cccacf56e002c7413d8ab7356a92ecfc94212c /src/tool_formparse.c
parentfa176376c8af90fcce632db63ac691a471577e8e (diff)
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
Diffstat (limited to 'src/tool_formparse.c')
-rw-r--r--src/tool_formparse.c234
1 files changed, 144 insertions, 90 deletions
diff --git a/src/tool_formparse.c b/src/tool_formparse.c
index 12b1a9d46..fe357f504 100644
--- a/src/tool_formparse.c
+++ b/src/tool_formparse.c
@@ -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;
-
- *sep = '\0'; /* terminate file name at separator */
-
- ptr = sep+1; /* point to the text following the separator */
-
- 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 */
+ ++ptr;
+ contp = get_param_word(&ptr, &word_end);
+ semicolon = (';' == *ptr) ? TRUE : FALSE;
+ *word_end = '\0'; /* terminate the contp */
+
+ /* have other content, continue parse */
+ while(semicolon) {
+ /* have type or filename field */
+ ++ptr;
+ while(*ptr && (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! */
}
- 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);