Imported Upstream version 7.44.0
[platform/upstream/curl.git] / src / tool_formparse.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2015, 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  */
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 }