Permalink
Browse files

curl formparse can parse filename both contains '"' and ',' or ';'.

  • Loading branch information...
1 parent 1e99100 commit 850b14c9c14e0d767bb3b99f79baeac5ff84ce15 @ulion committed Jan 20, 2013
Showing with 136 additions and 130 deletions.
  1. +125 −119 src/tool_formparse.c
  2. +6 −6 tests/data/test1133
  3. +4 −4 tests/data/test39
  4. +1 −1 tests/getpart.pm
View
@@ -34,6 +34,66 @@
#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.
+ */
+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 = ";,";
+ char tmp;
+
+ /* 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()
@@ -95,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)) {
@@ -116,148 +175,95 @@ 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;
-
- /* check whether the file part is quoted */
- sep = NULL;
- if ('"' == contp[0]) {
- sep = strchr(contp+1, '"');
- }
- if (sep) {
- contp += 1;
- *sep = '\0'; /* terminate file name at the end quot */
- ptr = sep+1; /* point to the text following the quot */
-
- sep = strchr(ptr, ';');
- sep2 = strchr(ptr, ',');
-
- /* pick the closest */
- if(sep2 && (sep2 < sep)) {
- sep = sep2;
-
- /* no type was specified! */
- }
- }
- else {
- sep = strchr(contp, ';');
- sep2 = strchr(contp, ',');
-
- /* pick the closest */
- if(sep2 && (sep2 < sep)) {
- sep = sep2;
-
- /* no type was specified! */
- }
- }
+ char *word_end;
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);
+ bool 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 = NULL;
- if ('"' == filename[0]) {
- ptr = strchr(filename+1, '"');
- }
- if (ptr) {
- filename += 1;
- *ptr = '\0'; /* zero terminate */
- ptr++;
- sep = strchr(ptr, ';');
- if(!sep) {
- sep = strchr(ptr, ',');
- }
- ptr = sep ? (sep + 1) : NULL;
- }
- else {
- 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)) {
+ char *content = NULL;
+ ptr = sep + 1;
+ content = 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) {
View
@@ -26,10 +26,10 @@ http
HTTP RFC1867-type formposting with filename contains ',' and ';'
</name>
<command>
-http://%HOSTIP:%HTTPPORT/we/want/1133 -F "file=@\"log/test1133,and;.txt\";type=mo/foo;filename=\"faker,and;.txt\"" -F 'file2=@"log/test1133,and;.txt"' -F 'file3=@"log/test1133,and;.txt";type=m/f,"log/test1133,and;.txt"'
+http://%HOSTIP:%HTTPPORT/we/want/1133 -F "file=@\"log/test1133,a\\\"nd;.txt\";type=mo/foo;filename=\"faker,and;.txt\"" -F 'file2=@"log/test1133,a\"nd;.txt"' -F 'file3=@"log/test1133,a\"nd;.txt";type=m/f,"log/test1133,a\"nd;.txt"'
</command>
# We create this file before the command is invoked!
-<file name="log/test1133,and;.txt">
+<file name=log/test1133,a"nd;.txt>
foo bar
This is a bar foo
bar
@@ -47,7 +47,7 @@ POST /we/want/1133 HTTP/1.1
User-Agent: curl/7.10.4 (i686-pc-linux-gnu) libcurl/7.10.4 OpenSSL/0.9.7a ipv6 zlib/1.1.3
Host: %HOSTIP:%HTTPPORT
Accept: */*
-Content-Length: 961
+Content-Length: 964
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32
@@ -61,7 +61,7 @@ bar
foo
------------------------------24e78000bd32
-Content-Disposition: form-data; name="file2"; filename="test1133,and;.txt"
+Content-Disposition: form-data; name="file2"; filename="test1133,a"nd;.txt"
Content-Type: text/plain
foo bar
@@ -73,15 +73,15 @@ foo
Content-Disposition: form-data; name="file3"
Content-Type: multipart/mixed, boundary=----------------------------7f0e85a48b0b
-Content-Disposition: attachment; filename="test1133,and;.txt"
+Content-Disposition: attachment; filename="test1133,a"nd;.txt"
Content-Type: m/f
foo bar
This is a bar foo
bar
foo
-Content-Disposition: attachment; filename="test1133,and;.txt"
+Content-Disposition: attachment; filename="test1133,a"nd;.txt"
Content-Type: text/plain
foo bar
View
@@ -26,7 +26,7 @@ http
HTTP RFC1867-type formposting with filename= and type=
</name>
<command>
-http://%HOSTIP:%HTTPPORT/we/want/39 -F name=daniel -F tool=curl --form-string "str1=@literal" --form-string "str2=<verbatim;type=xxx/yyy" -F "file=@log/test39.txt;type=moo/foobar;filename=fakerfile" -F file2=@log/test39.txt -F "file3=@\"log/test39.txt\";type=mo/foo;filename=\"faker,and;.txt\"" -F 'file4=@"log/test39.txt"'
+http://%HOSTIP:%HTTPPORT/we/want/39 -F name=daniel -F tool=curl --form-string "str1=@literal" --form-string "str2=<verbatim;type=xxx/yyy" -F "file=@log/test39.txt;type=moo/foobar;filename=fakerfile" -F file2=@log/test39.txt -F "file3=@\"log/test39.txt\";type=mo/foo;filename=\"f\\\\\\\\ak\\\\\\er,\\\\an\\d;.t\\\"xt\"" -F 'file4=@"log/test39.txt"; filename="A\\AA\"\"\\\"ZZZ"'
</command>
# We create this file before the command is invoked!
<file name="log/test39.txt">
@@ -47,7 +47,7 @@ POST /we/want/39 HTTP/1.1
User-Agent: curl/7.10.4 (i686-pc-linux-gnu) libcurl/7.10.4 OpenSSL/0.9.7a ipv6 zlib/1.1.3
Host: %HOSTIP:%HTTPPORT
Accept: */*
-Content-Length: 1164
+Content-Length: 1172
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------24e78000bd32
@@ -86,7 +86,7 @@ bar
foo
------------------------------24e78000bd32
-Content-Disposition: form-data; name="file3"; filename="faker,and;.txt"
+Content-Disposition: form-data; name="file3"; filename="f\\ak\\er,\an\d;.t"xt"
Content-Type: mo/foo
foo bar
@@ -95,7 +95,7 @@ bar
foo
------------------------------24e78000bd32
-Content-Disposition: form-data; name="file4"; filename="test39.txt"
+Content-Disposition: form-data; name="file4"; filename="A\AA""\"ZZZ"
Content-Type: text/plain
foo bar
View
@@ -56,7 +56,7 @@ sub getpartattr {
$inside++;
my $attr=$1;
- while($attr =~ s/ *([^=]*)= *(\"([^\"]*)\"|([^\"> ]*))//) {
+ while($attr =~ s/ *([^=]*)= *(\"([^\"]*)\"|([^\> ]*))//) {
my ($var, $cont)=($1, $2);
$cont =~ s/^\"(.*)\"$/$1/;
$hash{$var}=$cont;

0 comments on commit 850b14c

Please sign in to comment.