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 */
get_param_word(char ** str,char ** end_pos)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
formparse(struct OperationConfig * config,const char * input,struct curl_httppost ** httppost,struct curl_httppost ** last_post,bool literal_value)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