1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@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 http://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 */
43static 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
143int formparse(struct Configurable *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->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, "Illegally formatted content-type field!\n");
210              Curl_safefree(contents);
211              FreeMultiInfo(&multi_start, &multi_current);
212              return 2; /* illegal content-type syntax! */
213            }
214
215            /* now point beyond the content-type specifier */
216            sep = (char *)type + strlen(type_major)+strlen(type_minor)+1;
217
218            /* there's a semicolon following - we check if it is a filename
219               specified and if not we simply assume that it is text that
220               the user wants included in the type and include that too up
221               to the next sep. */
222            ptr = sep;
223            if(*sep==';') {
224              if(!checkprefix(";filename=", sep)) {
225                ptr = sep + 1;
226                (void)get_param_word(&ptr, &sep);
227                semicolon = (';' == *ptr) ? TRUE : FALSE;
228              }
229            }
230            else
231              semicolon = FALSE;
232
233            if(*sep)
234              *sep = '\0'; /* zero terminate type string */
235          }
236          else if(checkprefix("filename=", ptr)) {
237            ptr += 9;
238            filename = get_param_word(&ptr, &word_end);
239            semicolon = (';' == *ptr) ? TRUE : FALSE;
240            *word_end = '\0';
241          }
242          else {
243            /* unknown prefix, skip to next block */
244            char *unknown = NULL;
245            unknown = get_param_word(&ptr, &word_end);
246            semicolon = (';' == *ptr) ? TRUE : FALSE;
247            if(*unknown) {
248              *word_end = '\0';
249              warnf(config, "skip unknown form field: %s\n", unknown);
250            }
251          }
252        }
253        /* now ptr point to comma or string end */
254
255
256        /* if type == NULL curl_formadd takes care of the problem */
257
258        if(*contp && !AddMultiFiles(contp, type, filename, &multi_start,
259                          &multi_current)) {
260          warnf(config, "Error building form post!\n");
261          Curl_safefree(contents);
262          FreeMultiInfo(&multi_start, &multi_current);
263          return 3;
264        }
265
266        /* *ptr could be '\0', so we just check with the string end */
267      } while(ptr < end); /* loop if there's another file name */
268
269      /* now we add the multiple files section */
270      if(multi_start) {
271        struct curl_forms *forms = NULL;
272        struct multi_files *start = multi_start;
273        unsigned int i, count = 0;
274        while(start) {
275          start = start->next;
276          ++count;
277        }
278        forms = malloc((count+1)*sizeof(struct curl_forms));
279        if(!forms) {
280          fprintf(config->errors, "Error building form post!\n");
281          Curl_safefree(contents);
282          FreeMultiInfo(&multi_start, &multi_current);
283          return 4;
284        }
285        for(i = 0, start = multi_start; i < count; ++i, start = start->next) {
286          forms[i].option = start->form.option;
287          forms[i].value = start->form.value;
288        }
289        forms[count].option = CURLFORM_END;
290        FreeMultiInfo(&multi_start, &multi_current);
291        if(curl_formadd(httppost, last_post,
292                        CURLFORM_COPYNAME, name,
293                        CURLFORM_ARRAY, forms, CURLFORM_END) != 0) {
294          warnf(config, "curl_formadd failed!\n");
295          Curl_safefree(forms);
296          Curl_safefree(contents);
297          return 5;
298        }
299        Curl_safefree(forms);
300      }
301    }
302    else {
303      struct curl_forms info[4];
304      int i = 0;
305      char *ct = literal_value ? NULL : strstr(contp, ";type=");
306
307      info[i].option = CURLFORM_COPYNAME;
308      info[i].value = name;
309      i++;
310
311      if(ct) {
312        info[i].option = CURLFORM_CONTENTTYPE;
313        info[i].value = &ct[6];
314        i++;
315        ct[0] = '\0'; /* zero terminate here */
316      }
317
318      if(contp[0]=='<' && !literal_value) {
319        info[i].option = CURLFORM_FILECONTENT;
320        info[i].value = contp+1;
321        i++;
322        info[i].option = CURLFORM_END;
323
324        if(curl_formadd(httppost, last_post,
325                        CURLFORM_ARRAY, info, CURLFORM_END ) != 0) {
326          warnf(config, "curl_formadd failed, possibly the file %s is bad!\n",
327                contp+1);
328          Curl_safefree(contents);
329          return 6;
330        }
331      }
332      else {
333#ifdef CURL_DOES_CONVERSIONS
334        if(convert_to_network(contp, strlen(contp))) {
335          warnf(config, "curl_formadd failed!\n");
336          Curl_safefree(contents);
337          return 7;
338        }
339#endif
340        info[i].option = CURLFORM_COPYCONTENTS;
341        info[i].value = contp;
342        i++;
343        info[i].option = CURLFORM_END;
344        if(curl_formadd(httppost, last_post,
345                        CURLFORM_ARRAY, info, CURLFORM_END) != 0) {
346          warnf(config, "curl_formadd failed!\n");
347          Curl_safefree(contents);
348          return 8;
349        }
350      }
351    }
352
353  }
354  else {
355    warnf(config, "Illegally formatted input field!\n");
356    return 1;
357  }
358  Curl_safefree(contents);
359  return 0;
360}
361
362