fe357f504fb3d7ae4938fad589b6cb119daaa9e1
[platform/upstream/curl.git] / src / tool_formparse.c
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  */
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 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