| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) 1998 - 2016, Daniel Stenberg, <[email protected]>, 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.haxx.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. |
| * |
| ***************************************************************************/ |
| #include "tool_setup.h" |
| |
| #include "strcase.h" |
| |
| #define ENABLE_CURLX_PRINTF |
| /* use our own printf() functions */ |
| #include "curlx.h" |
| |
| #include "tool_cfgable.h" |
| #include "tool_mfiles.h" |
| #include "tool_msgs.h" |
| #include "tool_formparse.h" |
| |
| #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', 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 |
| * loses its special meaning, as does any embedded ';type='. |
| * |
| * You may specify more than one file for a single name (field). Specify |
| * multiple files by writing it like: |
| * |
| * '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' |
| * |
| * If you want custom headers added for a single part, write them in a separate |
| * file and do like this: |
| * |
| * 'name=foo;headers=@headerfile' or why not |
| * 'name=@filemame;headers=@headerfile' |
| * |
| * 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' 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. |
| * |
| ***************************************************************************/ |
| |
| int formparse(struct OperationConfig *config, |
| const char *input, |
| struct curl_httppost **httppost, |
| struct curl_httppost **last_post, |
| bool literal_value) |
| { |
| /* nextarg MUST be a string in the format 'name=contents' and we'll |
| build a linked list with the info */ |
| char name[256]; |
| char *contents = NULL; |
| char type_major[128] = ""; |
| char type_minor[128] = ""; |
| char *contp; |
| const char *type = NULL; |
| char *sep; |
| |
| if((1 == sscanf(input, "%255[^=]=", name)) && |
| ((contp = strchr(input, '=')) != NULL)) { |
| /* the input was using the correct format */ |
| |
| /* Allocate the contents */ |
| contents = strdup(contp+1); |
| if(!contents) { |
| fprintf(config->global->errors, "out of memory\n"); |
| return 1; |
| } |
| contp = contents; |
| |
| if('@' == contp[0] && !literal_value) { |
| |
| /* we use the @-letter to indicate file name(s) */ |
| |
| struct multi_files *multi_start = NULL; |
| struct multi_files *multi_current = NULL; |
| |
| 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 *filename = NULL; |
| char *word_end; |
| bool semicolon; |
| |
| type = NULL; |
| |
| ++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->global, |
| "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 sep. */ |
| ptr = sep; |
| if(*sep==';') { |
| if(!checkprefix(";filename=", sep)) { |
| ptr = sep + 1; |
| (void)get_param_word(&ptr, &sep); |
| semicolon = (';' == *ptr) ? TRUE : FALSE; |
| } |
| } |
| else |
| semicolon = FALSE; |
| |
| 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->global, "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(*contp && !AddMultiFiles(contp, type, filename, &multi_start, |
| &multi_current)) { |
| warnf(config->global, "Error building form post!\n"); |
| Curl_safefree(contents); |
| FreeMultiInfo(&multi_start, &multi_current); |
| return 3; |
| } |
| |
| /* *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 *start = multi_start; |
| unsigned int i, count = 0; |
| while(start) { |
| start = start->next; |
| ++count; |
| } |
| forms = malloc((count+1)*sizeof(struct curl_forms)); |
| if(!forms) { |
| fprintf(config->global->errors, "Error building form post!\n"); |
| Curl_safefree(contents); |
| FreeMultiInfo(&multi_start, &multi_current); |
| return 4; |
| } |
| 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); |
| if(curl_formadd(httppost, last_post, |
| CURLFORM_COPYNAME, name, |
| CURLFORM_ARRAY, forms, CURLFORM_END) != 0) { |
| warnf(config->global, "curl_formadd failed!\n"); |
| Curl_safefree(forms); |
| Curl_safefree(contents); |
| return 5; |
| } |
| Curl_safefree(forms); |
| } |
| } |
| else { |
| struct curl_forms info[4]; |
| int i = 0; |
| char *ct = literal_value ? NULL : strstr(contp, ";type="); |
| |
| info[i].option = CURLFORM_COPYNAME; |
| info[i].value = name; |
| i++; |
| |
| if(ct) { |
| info[i].option = CURLFORM_CONTENTTYPE; |
| info[i].value = &ct[6]; |
| i++; |
| ct[0] = '\0'; /* zero terminate here */ |
| } |
| |
| if(contp[0]=='<' && !literal_value) { |
| info[i].option = CURLFORM_FILECONTENT; |
| info[i].value = contp+1; |
| i++; |
| info[i].option = CURLFORM_END; |
| |
| if(curl_formadd(httppost, last_post, |
| CURLFORM_ARRAY, info, CURLFORM_END) != 0) { |
| warnf(config->global, "curl_formadd failed, possibly the file %s is " |
| "bad!\n", contp + 1); |
| Curl_safefree(contents); |
| return 6; |
| } |
| } |
| else { |
| #ifdef CURL_DOES_CONVERSIONS |
| if(convert_to_network(contp, strlen(contp))) { |
| warnf(config->global, "curl_formadd failed!\n"); |
| Curl_safefree(contents); |
| return 7; |
| } |
| #endif |
| info[i].option = CURLFORM_COPYCONTENTS; |
| info[i].value = contp; |
| i++; |
| info[i].option = CURLFORM_END; |
| if(curl_formadd(httppost, last_post, |
| CURLFORM_ARRAY, info, CURLFORM_END) != 0) { |
| warnf(config->global, "curl_formadd failed!\n"); |
| Curl_safefree(contents); |
| return 8; |
| } |
| } |
| } |
| |
| } |
| else { |
| warnf(config->global, "Illegally formatted input field!\n"); |
| return 1; |
| } |
| Curl_safefree(contents); |
| return 0; |
| } |