Home | History | Annotate | Download | only in src
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel (at) haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.haxx.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  ***************************************************************************/
     22 #include "tool_setup.h"
     23 
     24 #include "rawstr.h"
     25 
     26 #define ENABLE_CURLX_PRINTF
     27 /* use our own printf() functions */
     28 #include "curlx.h"
     29 
     30 #include "tool_cfgable.h"
     31 #include "tool_mfiles.h"
     32 #include "tool_msgs.h"
     33 #include "tool_formparse.h"
     34 
     35 #include "memdebug.h" /* keep this as LAST include */
     36 
     37 
     38 /*
     39  * helper function to get a word from form param
     40  * after call get_parm_word, str either point to string end
     41  * or point to any of end chars.
     42  */
     43 static char *get_param_word(char **str, char **end_pos)
     44 {
     45   char *ptr = *str;
     46   char *word_begin = NULL;
     47   char *ptr2;
     48   char *escape = NULL;
     49   const char *end_chars = ";,";
     50 
     51   /* the first non-space char is here */
     52   word_begin = ptr;
     53   if(*ptr == '"') {
     54     ++ptr;
     55     while(*ptr) {
     56       if(*ptr == '\\') {
     57         if(ptr[1] == '\\' || ptr[1] == '"') {
     58           /* remember the first escape position */
     59           if(!escape)
     60             escape = ptr;
     61           /* skip escape of back-slash or double-quote */
     62           ptr += 2;
     63           continue;
     64         }
     65       }
     66       if(*ptr == '"') {
     67         *end_pos = ptr;
     68         if(escape) {
     69           /* has escape, we restore the unescaped string here */
     70           ptr = ptr2 = escape;
     71           do {
     72             if(*ptr == '\\' && (ptr[1] == '\\' || ptr[1] == '"'))
     73               ++ptr;
     74             *ptr2++ = *ptr++;
     75           }
     76           while(ptr < *end_pos);
     77           *end_pos = ptr2;
     78         }
     79         while(*ptr && NULL==strchr(end_chars, *ptr))
     80           ++ptr;
     81         *str = ptr;
     82         return word_begin+1;
     83       }
     84       ++ptr;
     85     }
     86     /* end quote is missing, treat it as non-quoted. */
     87     ptr = word_begin;
     88   }
     89 
     90   while(*ptr && NULL==strchr(end_chars, *ptr))
     91     ++ptr;
     92   *str = *end_pos = ptr;
     93   return word_begin;
     94 }
     95 
     96 /***************************************************************************
     97  *
     98  * formparse()
     99  *
    100  * Reads a 'name=value' parameter and builds the appropriate linked list.
    101  *
    102  * Specify files to upload with 'name=@filename', or 'name=@"filename"'
    103  * in case the filename contain ',' or ';'. Supports specified
    104  * given Content-Type of the files. Such as ';type=<content-type>'.
    105  *
    106  * If literal_value is set, any initial '@' or '<' in the value string
    107  * loses its special meaning, as does any embedded ';type='.
    108  *
    109  * You may specify more than one file for a single name (field). Specify
    110  * multiple files by writing it like:
    111  *
    112  * 'name=@filename,filename2,filename3'
    113  *
    114  * or use double-quotes quote the filename:
    115  *
    116  * 'name=@"filename","filename2","filename3"'
    117  *
    118  * If you want content-types specified for each too, write them like:
    119  *
    120  * 'name=@filename;type=image/gif,filename2,filename3'
    121  *
    122  * If you want custom headers added for a single part, write them in a separate
    123  * file and do like this:
    124  *
    125  * 'name=foo;headers=@headerfile' or why not
    126  * 'name=@filemame;headers=@headerfile'
    127  *
    128  * To upload a file, but to fake the file name that will be included in the
    129  * formpost, do like this:
    130  *
    131  * 'name=@filename;filename=/dev/null' or quote the faked filename like:
    132  * 'name=@filename;filename="play, play, and play.txt"'
    133  *
    134  * If filename/path contains ',' or ';', it must be quoted by double-quotes,
    135  * else curl will fail to figure out the correct filename. if the filename
    136  * tobe quoted contains '"' or '\', '"' and '\' must be escaped by backslash.
    137  *
    138  * This function uses curl_formadd to fulfill it's job. Is heavily based on
    139  * the old curl_formparse code.
    140  *
    141  ***************************************************************************/
    142 
    143 int formparse(struct OperationConfig *config,
    144               const char *input,
    145               struct curl_httppost **httppost,
    146               struct curl_httppost **last_post,
    147               bool literal_value)
    148 {
    149   /* nextarg MUST be a string in the format 'name=contents' and we'll
    150      build a linked list with the info */
    151   char name[256];
    152   char *contents = NULL;
    153   char type_major[128] = "";
    154   char type_minor[128] = "";
    155   char *contp;
    156   const char *type = NULL;
    157   char *sep;
    158 
    159   if((1 == sscanf(input, "%255[^=]=", name)) &&
    160      ((contp = strchr(input, '=')) != NULL)) {
    161     /* the input was using the correct format */
    162 
    163     /* Allocate the contents */
    164     contents = strdup(contp+1);
    165     if(!contents) {
    166       fprintf(config->global->errors, "out of memory\n");
    167       return 1;
    168     }
    169     contp = contents;
    170 
    171     if('@' == contp[0] && !literal_value) {
    172 
    173       /* we use the @-letter to indicate file name(s) */
    174 
    175       struct multi_files *multi_start = NULL;
    176       struct multi_files *multi_current = NULL;
    177 
    178       char *ptr = contp;
    179       char *end = ptr + strlen(ptr);
    180 
    181       do {
    182         /* since this was a file, it may have a content-type specifier
    183            at the end too, or a filename. Or both. */
    184         char *filename = NULL;
    185         char *word_end;
    186         bool semicolon;
    187 
    188         type = NULL;
    189 
    190         ++ptr;
    191         contp = get_param_word(&ptr, &word_end);
    192         semicolon = (';' == *ptr) ? TRUE : FALSE;
    193         *word_end = '\0'; /* terminate the contp */
    194 
    195         /* have other content, continue parse */
    196         while(semicolon) {
    197           /* have type or filename field */
    198           ++ptr;
    199           while(*ptr && (ISSPACE(*ptr)))
    200             ++ptr;
    201 
    202           if(checkprefix("type=", ptr)) {
    203             /* set type pointer */
    204             type = &ptr[5];
    205 
    206             /* verify that this is a fine type specifier */
    207             if(2 != sscanf(type, "%127[^/]/%127[^;,\n]",
    208                            type_major, type_minor)) {
    209               warnf(config->global,
    210                     "Illegally formatted content-type field!\n");
    211               Curl_safefree(contents);
    212               FreeMultiInfo(&multi_start, &multi_current);
    213               return 2; /* illegal content-type syntax! */
    214             }
    215 
    216             /* now point beyond the content-type specifier */
    217             sep = (char *)type + strlen(type_major)+strlen(type_minor)+1;
    218 
    219             /* there's a semicolon following - we check if it is a filename
    220                specified and if not we simply assume that it is text that
    221                the user wants included in the type and include that too up
    222                to the next sep. */
    223             ptr = sep;
    224             if(*sep==';') {
    225               if(!checkprefix(";filename=", sep)) {
    226                 ptr = sep + 1;
    227                 (void)get_param_word(&ptr, &sep);
    228                 semicolon = (';' == *ptr) ? TRUE : FALSE;
    229               }
    230             }
    231             else
    232               semicolon = FALSE;
    233 
    234             if(*sep)
    235               *sep = '\0'; /* zero terminate type string */
    236           }
    237           else if(checkprefix("filename=", ptr)) {
    238             ptr += 9;
    239             filename = get_param_word(&ptr, &word_end);
    240             semicolon = (';' == *ptr) ? TRUE : FALSE;
    241             *word_end = '\0';
    242           }
    243           else {
    244             /* unknown prefix, skip to next block */
    245             char *unknown = NULL;
    246             unknown = get_param_word(&ptr, &word_end);
    247             semicolon = (';' == *ptr) ? TRUE : FALSE;
    248             if(*unknown) {
    249               *word_end = '\0';
    250               warnf(config->global, "skip unknown form field: %s\n", unknown);
    251             }
    252           }
    253         }
    254         /* now ptr point to comma or string end */
    255 
    256 
    257         /* if type == NULL curl_formadd takes care of the problem */
    258 
    259         if(*contp && !AddMultiFiles(contp, type, filename, &multi_start,
    260                           &multi_current)) {
    261           warnf(config->global, "Error building form post!\n");
    262           Curl_safefree(contents);
    263           FreeMultiInfo(&multi_start, &multi_current);
    264           return 3;
    265         }
    266 
    267         /* *ptr could be '\0', so we just check with the string end */
    268       } while(ptr < end); /* loop if there's another file name */
    269 
    270       /* now we add the multiple files section */
    271       if(multi_start) {
    272         struct curl_forms *forms = NULL;
    273         struct multi_files *start = multi_start;
    274         unsigned int i, count = 0;
    275         while(start) {
    276           start = start->next;
    277           ++count;
    278         }
    279         forms = malloc((count+1)*sizeof(struct curl_forms));
    280         if(!forms) {
    281           fprintf(config->global->errors, "Error building form post!\n");
    282           Curl_safefree(contents);
    283           FreeMultiInfo(&multi_start, &multi_current);
    284           return 4;
    285         }
    286         for(i = 0, start = multi_start; i < count; ++i, start = start->next) {
    287           forms[i].option = start->form.option;
    288           forms[i].value = start->form.value;
    289         }
    290         forms[count].option = CURLFORM_END;
    291         FreeMultiInfo(&multi_start, &multi_current);
    292         if(curl_formadd(httppost, last_post,
    293                         CURLFORM_COPYNAME, name,
    294                         CURLFORM_ARRAY, forms, CURLFORM_END) != 0) {
    295           warnf(config->global, "curl_formadd failed!\n");
    296           Curl_safefree(forms);
    297           Curl_safefree(contents);
    298           return 5;
    299         }
    300         Curl_safefree(forms);
    301       }
    302     }
    303     else {
    304       struct curl_forms info[4];
    305       int i = 0;
    306       char *ct = literal_value ? NULL : strstr(contp, ";type=");
    307 
    308       info[i].option = CURLFORM_COPYNAME;
    309       info[i].value = name;
    310       i++;
    311 
    312       if(ct) {
    313         info[i].option = CURLFORM_CONTENTTYPE;
    314         info[i].value = &ct[6];
    315         i++;
    316         ct[0] = '\0'; /* zero terminate here */
    317       }
    318 
    319       if(contp[0]=='<' && !literal_value) {
    320         info[i].option = CURLFORM_FILECONTENT;
    321         info[i].value = contp+1;
    322         i++;
    323         info[i].option = CURLFORM_END;
    324 
    325         if(curl_formadd(httppost, last_post,
    326                         CURLFORM_ARRAY, info, CURLFORM_END) != 0) {
    327           warnf(config->global, "curl_formadd failed, possibly the file %s is "
    328                 "bad!\n", contp + 1);
    329           Curl_safefree(contents);
    330           return 6;
    331         }
    332       }
    333       else {
    334 #ifdef CURL_DOES_CONVERSIONS
    335         if(convert_to_network(contp, strlen(contp))) {
    336           warnf(config->global, "curl_formadd failed!\n");
    337           Curl_safefree(contents);
    338           return 7;
    339         }
    340 #endif
    341         info[i].option = CURLFORM_COPYCONTENTS;
    342         info[i].value = contp;
    343         i++;
    344         info[i].option = CURLFORM_END;
    345         if(curl_formadd(httppost, last_post,
    346                         CURLFORM_ARRAY, info, CURLFORM_END) != 0) {
    347           warnf(config->global, "curl_formadd failed!\n");
    348           Curl_safefree(contents);
    349           return 8;
    350         }
    351       }
    352     }
    353 
    354   }
    355   else {
    356     warnf(config->global, "Illegally formatted input field!\n");
    357     return 1;
    358   }
    359   Curl_safefree(contents);
    360   return 0;
    361 }
    362