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