1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2019, 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 https://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 "strcase.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_convert.h" |
32 | #include "tool_msgs.h" |
33 | #include "tool_binmode.h" |
34 | #include "tool_getparam.h" |
35 | #include "tool_paramhlp.h" |
36 | #include "tool_formparse.h" |
37 | |
38 | #include "memdebug.h" /* keep this as LAST include */ |
39 | |
40 | /* Macros to free const pointers. */ |
41 | #define CONST_FREE(x) free((void *) (x)) |
42 | #define CONST_SAFEFREE(x) Curl_safefree(*((void **) &(x))) |
43 | |
44 | /* tool_mime functions. */ |
45 | static tool_mime *tool_mime_new(tool_mime *parent, toolmimekind kind) |
46 | { |
47 | tool_mime *m = (tool_mime *) calloc(1, sizeof(*m)); |
48 | |
49 | if(m) { |
50 | m->kind = kind; |
51 | m->parent = parent; |
52 | if(parent) { |
53 | m->prev = parent->subparts; |
54 | parent->subparts = m; |
55 | } |
56 | } |
57 | return m; |
58 | } |
59 | |
60 | static tool_mime *tool_mime_new_parts(tool_mime *parent) |
61 | { |
62 | return tool_mime_new(parent, TOOLMIME_PARTS); |
63 | } |
64 | |
65 | static tool_mime *tool_mime_new_data(tool_mime *parent, const char *data) |
66 | { |
67 | tool_mime *m = NULL; |
68 | |
69 | data = strdup(data); |
70 | if(data) { |
71 | m = tool_mime_new(parent, TOOLMIME_DATA); |
72 | if(!m) |
73 | CONST_FREE(data); |
74 | else |
75 | m->data = data; |
76 | } |
77 | return m; |
78 | } |
79 | |
80 | static tool_mime *tool_mime_new_filedata(tool_mime *parent, |
81 | const char *filename, |
82 | bool isremotefile, |
83 | CURLcode *errcode) |
84 | { |
85 | CURLcode result = CURLE_OK; |
86 | tool_mime *m = NULL; |
87 | |
88 | *errcode = CURLE_OUT_OF_MEMORY; |
89 | if(strcmp(filename, "-" )) { |
90 | /* This is a normal file. */ |
91 | filename = strdup(filename); |
92 | if(filename) { |
93 | m = tool_mime_new(parent, TOOLMIME_FILE); |
94 | if(!m) |
95 | CONST_FREE(filename); |
96 | else { |
97 | m->data = filename; |
98 | if(!isremotefile) |
99 | m->kind = TOOLMIME_FILEDATA; |
100 | *errcode = CURLE_OK; |
101 | } |
102 | } |
103 | } |
104 | else { /* Standard input. */ |
105 | int fd = fileno(stdin); |
106 | char *data = NULL; |
107 | curl_off_t size; |
108 | curl_off_t origin; |
109 | struct_stat sbuf; |
110 | |
111 | set_binmode(stdin); |
112 | origin = ftell(stdin); |
113 | /* If stdin is a regular file, do not buffer data but read it |
114 | when needed. */ |
115 | if(fd >= 0 && origin >= 0 && !fstat(fd, &sbuf) && |
116 | #ifdef __VMS |
117 | sbuf.st_fab_rfm != FAB$C_VAR && sbuf.st_fab_rfm != FAB$C_VFC && |
118 | #endif |
119 | S_ISREG(sbuf.st_mode)) { |
120 | size = sbuf.st_size - origin; |
121 | if(size < 0) |
122 | size = 0; |
123 | } |
124 | else { /* Not suitable for direct use, buffer stdin data. */ |
125 | size_t stdinsize = 0; |
126 | |
127 | if(file2memory(&data, &stdinsize, stdin) != PARAM_OK) { |
128 | /* Out of memory. */ |
129 | return m; |
130 | } |
131 | |
132 | if(ferror(stdin)) { |
133 | result = CURLE_READ_ERROR; |
134 | Curl_safefree(data); |
135 | data = NULL; |
136 | } |
137 | else if(!stdinsize) { |
138 | /* Zero-length data has been freed. Re-create it. */ |
139 | data = strdup("" ); |
140 | if(!data) |
141 | return m; |
142 | } |
143 | size = curlx_uztoso(stdinsize); |
144 | origin = 0; |
145 | } |
146 | m = tool_mime_new(parent, TOOLMIME_STDIN); |
147 | if(!m) |
148 | Curl_safefree(data); |
149 | else { |
150 | m->data = data; |
151 | m->origin = origin; |
152 | m->size = size; |
153 | m->curpos = 0; |
154 | if(!isremotefile) |
155 | m->kind = TOOLMIME_STDINDATA; |
156 | *errcode = result; |
157 | } |
158 | } |
159 | return m; |
160 | } |
161 | |
162 | void tool_mime_free(tool_mime *mime) |
163 | { |
164 | if(mime) { |
165 | if(mime->subparts) |
166 | tool_mime_free(mime->subparts); |
167 | if(mime->prev) |
168 | tool_mime_free(mime->prev); |
169 | CONST_SAFEFREE(mime->name); |
170 | CONST_SAFEFREE(mime->filename); |
171 | CONST_SAFEFREE(mime->type); |
172 | CONST_SAFEFREE(mime->encoder); |
173 | CONST_SAFEFREE(mime->data); |
174 | curl_slist_free_all(mime->headers); |
175 | free(mime); |
176 | } |
177 | } |
178 | |
179 | |
180 | /* Mime part callbacks for stdin. */ |
181 | size_t tool_mime_stdin_read(char *buffer, |
182 | size_t size, size_t nitems, void *arg) |
183 | { |
184 | tool_mime *sip = (tool_mime *) arg; |
185 | curl_off_t bytesleft; |
186 | (void) size; /* Always 1: ignored. */ |
187 | |
188 | if(sip->size >= 0) { |
189 | if(sip->curpos >= sip->size) |
190 | return 0; /* At eof. */ |
191 | bytesleft = sip->size - sip->curpos; |
192 | if(curlx_uztoso(nitems) > bytesleft) |
193 | nitems = curlx_sotouz(bytesleft); |
194 | } |
195 | if(nitems) { |
196 | if(sip->data) { |
197 | /* Return data from memory. */ |
198 | memcpy(buffer, sip->data + curlx_sotouz(sip->curpos), nitems); |
199 | } |
200 | else { |
201 | /* Read from stdin. */ |
202 | nitems = fread(buffer, 1, nitems, stdin); |
203 | if(ferror(stdin)) { |
204 | /* Show error only once. */ |
205 | if(sip->config) { |
206 | warnf(sip->config, "stdin: %s\n" , strerror(errno)); |
207 | sip->config = NULL; |
208 | } |
209 | return CURL_READFUNC_ABORT; |
210 | } |
211 | } |
212 | sip->curpos += curlx_uztoso(nitems); |
213 | } |
214 | return nitems; |
215 | } |
216 | |
217 | int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence) |
218 | { |
219 | tool_mime *sip = (tool_mime *) instream; |
220 | |
221 | switch(whence) { |
222 | case SEEK_CUR: |
223 | offset += sip->curpos; |
224 | break; |
225 | case SEEK_END: |
226 | offset += sip->size; |
227 | break; |
228 | } |
229 | if(offset < 0) |
230 | return CURL_SEEKFUNC_CANTSEEK; |
231 | if(!sip->data) { |
232 | if(fseek(stdin, (long) (offset + sip->origin), SEEK_SET)) |
233 | return CURL_SEEKFUNC_CANTSEEK; |
234 | } |
235 | sip->curpos = offset; |
236 | return CURL_SEEKFUNC_OK; |
237 | } |
238 | |
239 | /* Translate an internal mime tree into a libcurl mime tree. */ |
240 | |
241 | static CURLcode tool2curlparts(CURL *curl, tool_mime *m, curl_mime *mime) |
242 | { |
243 | CURLcode ret = CURLE_OK; |
244 | curl_mimepart *part = NULL; |
245 | curl_mime *submime = NULL; |
246 | const char *filename = NULL; |
247 | |
248 | if(m) { |
249 | ret = tool2curlparts(curl, m->prev, mime); |
250 | if(!ret) { |
251 | part = curl_mime_addpart(mime); |
252 | if(!part) |
253 | ret = CURLE_OUT_OF_MEMORY; |
254 | } |
255 | if(!ret) { |
256 | filename = m->filename; |
257 | switch(m->kind) { |
258 | case TOOLMIME_PARTS: |
259 | ret = tool2curlmime(curl, m, &submime); |
260 | if(!ret) { |
261 | ret = curl_mime_subparts(part, submime); |
262 | if(ret) |
263 | curl_mime_free(submime); |
264 | } |
265 | break; |
266 | |
267 | case TOOLMIME_DATA: |
268 | #ifdef CURL_DOES_CONVERSIONS |
269 | /* Our data is always textual: convert it to ASCII. */ |
270 | { |
271 | size_t size = strlen(m->data); |
272 | char *cp = malloc(size + 1); |
273 | |
274 | if(!cp) |
275 | ret = CURLE_OUT_OF_MEMORY; |
276 | else { |
277 | memcpy(cp, m->data, size + 1); |
278 | ret = convert_to_network(cp, size); |
279 | if(!ret) |
280 | ret = curl_mime_data(part, cp, CURL_ZERO_TERMINATED); |
281 | free(cp); |
282 | } |
283 | } |
284 | #else |
285 | ret = curl_mime_data(part, m->data, CURL_ZERO_TERMINATED); |
286 | #endif |
287 | break; |
288 | |
289 | case TOOLMIME_FILE: |
290 | case TOOLMIME_FILEDATA: |
291 | ret = curl_mime_filedata(part, m->data); |
292 | if(!ret && m->kind == TOOLMIME_FILEDATA && !filename) |
293 | ret = curl_mime_filename(part, NULL); |
294 | break; |
295 | |
296 | case TOOLMIME_STDIN: |
297 | if(!filename) |
298 | filename = "-" ; |
299 | /* FALLTHROUGH */ |
300 | case TOOLMIME_STDINDATA: |
301 | ret = curl_mime_data_cb(part, m->size, |
302 | (curl_read_callback) tool_mime_stdin_read, |
303 | (curl_seek_callback) tool_mime_stdin_seek, |
304 | NULL, m); |
305 | break; |
306 | |
307 | default: |
308 | /* Other cases not possible in this context. */ |
309 | break; |
310 | } |
311 | } |
312 | if(!ret && filename) |
313 | ret = curl_mime_filename(part, filename); |
314 | if(!ret) |
315 | ret = curl_mime_type(part, m->type); |
316 | if(!ret) |
317 | ret = curl_mime_headers(part, m->headers, 0); |
318 | if(!ret) |
319 | ret = curl_mime_encoder(part, m->encoder); |
320 | if(!ret) |
321 | ret = curl_mime_name(part, m->name); |
322 | } |
323 | return ret; |
324 | } |
325 | |
326 | CURLcode tool2curlmime(CURL *curl, tool_mime *m, curl_mime **mime) |
327 | { |
328 | CURLcode ret = CURLE_OK; |
329 | |
330 | *mime = curl_mime_init(curl); |
331 | if(!*mime) |
332 | ret = CURLE_OUT_OF_MEMORY; |
333 | else |
334 | ret = tool2curlparts(curl, m->subparts, *mime); |
335 | if(ret) { |
336 | curl_mime_free(*mime); |
337 | *mime = NULL; |
338 | } |
339 | return ret; |
340 | } |
341 | |
342 | /* |
343 | * helper function to get a word from form param |
344 | * after call get_parm_word, str either point to string end |
345 | * or point to any of end chars. |
346 | */ |
347 | static char *get_param_word(char **str, char **end_pos, char endchar) |
348 | { |
349 | char *ptr = *str; |
350 | /* the first non-space char is here */ |
351 | char *word_begin = ptr; |
352 | char *ptr2; |
353 | char *escape = NULL; |
354 | |
355 | if(*ptr == '"') { |
356 | ++ptr; |
357 | while(*ptr) { |
358 | if(*ptr == '\\') { |
359 | if(ptr[1] == '\\' || ptr[1] == '"') { |
360 | /* remember the first escape position */ |
361 | if(!escape) |
362 | escape = ptr; |
363 | /* skip escape of back-slash or double-quote */ |
364 | ptr += 2; |
365 | continue; |
366 | } |
367 | } |
368 | if(*ptr == '"') { |
369 | *end_pos = ptr; |
370 | if(escape) { |
371 | /* has escape, we restore the unescaped string here */ |
372 | ptr = ptr2 = escape; |
373 | do { |
374 | if(*ptr == '\\' && (ptr[1] == '\\' || ptr[1] == '"')) |
375 | ++ptr; |
376 | *ptr2++ = *ptr++; |
377 | } |
378 | while(ptr < *end_pos); |
379 | *end_pos = ptr2; |
380 | } |
381 | while(*ptr && *ptr != ';' && *ptr != endchar) |
382 | ++ptr; |
383 | *str = ptr; |
384 | return word_begin + 1; |
385 | } |
386 | ++ptr; |
387 | } |
388 | /* end quote is missing, treat it as non-quoted. */ |
389 | ptr = word_begin; |
390 | } |
391 | |
392 | while(*ptr && *ptr != ';' && *ptr != endchar) |
393 | ++ptr; |
394 | *str = *end_pos = ptr; |
395 | return word_begin; |
396 | } |
397 | |
398 | /* Append slist item and return -1 if failed. */ |
399 | static int slist_append(struct curl_slist **plist, const char *data) |
400 | { |
401 | struct curl_slist *s = curl_slist_append(*plist, data); |
402 | |
403 | if(!s) |
404 | return -1; |
405 | |
406 | *plist = s; |
407 | return 0; |
408 | } |
409 | |
410 | /* Read headers from a file and append to list. */ |
411 | static int (struct OperationConfig *config, |
412 | const char *filename, FILE *fp, |
413 | struct curl_slist **) |
414 | { |
415 | size_t hdrlen = 0; |
416 | size_t pos = 0; |
417 | bool = FALSE; |
418 | int lineno = 1; |
419 | char hdrbuf[999]; /* Max. header length + 1. */ |
420 | |
421 | for(;;) { |
422 | int c = getc(fp); |
423 | if(c == EOF || (!pos && !ISSPACE(c))) { |
424 | /* Strip and flush the current header. */ |
425 | while(hdrlen && ISSPACE(hdrbuf[hdrlen - 1])) |
426 | hdrlen--; |
427 | if(hdrlen) { |
428 | hdrbuf[hdrlen] = '\0'; |
429 | if(slist_append(pheaders, hdrbuf)) { |
430 | fprintf(config->global->errors, |
431 | "Out of memory for field headers!\n" ); |
432 | return -1; |
433 | } |
434 | hdrlen = 0; |
435 | } |
436 | } |
437 | |
438 | switch(c) { |
439 | case EOF: |
440 | if(ferror(fp)) { |
441 | fprintf(config->global->errors, |
442 | "Header file %s read error: %s\n" , filename, strerror(errno)); |
443 | return -1; |
444 | } |
445 | return 0; /* Done. */ |
446 | case '\r': |
447 | continue; /* Ignore. */ |
448 | case '\n': |
449 | pos = 0; |
450 | incomment = FALSE; |
451 | lineno++; |
452 | continue; |
453 | case '#': |
454 | if(!pos) |
455 | incomment = TRUE; |
456 | break; |
457 | } |
458 | |
459 | pos++; |
460 | if(!incomment) { |
461 | if(hdrlen == sizeof(hdrbuf) - 1) { |
462 | warnf(config->global, "File %s line %d: header too long (truncated)\n" , |
463 | filename, lineno); |
464 | c = ' '; |
465 | } |
466 | if(hdrlen <= sizeof(hdrbuf) - 1) |
467 | hdrbuf[hdrlen++] = (char) c; |
468 | } |
469 | } |
470 | /* NOTREACHED */ |
471 | } |
472 | |
473 | static int get_param_part(struct OperationConfig *config, char endchar, |
474 | char **str, char **pdata, char **ptype, |
475 | char **pfilename, char **pencoder, |
476 | struct curl_slist **) |
477 | { |
478 | char *p = *str; |
479 | char *type = NULL; |
480 | char *filename = NULL; |
481 | char *encoder = NULL; |
482 | char *endpos; |
483 | char *tp; |
484 | char sep; |
485 | char type_major[128] = "" ; |
486 | char type_minor[128] = "" ; |
487 | char *endct = NULL; |
488 | struct curl_slist * = NULL; |
489 | |
490 | if(ptype) |
491 | *ptype = NULL; |
492 | if(pfilename) |
493 | *pfilename = NULL; |
494 | if(pheaders) |
495 | *pheaders = NULL; |
496 | if(pencoder) |
497 | *pencoder = NULL; |
498 | while(ISSPACE(*p)) |
499 | p++; |
500 | tp = p; |
501 | *pdata = get_param_word(&p, &endpos, endchar); |
502 | /* If not quoted, strip trailing spaces. */ |
503 | if(*pdata == tp) |
504 | while(endpos > *pdata && ISSPACE(endpos[-1])) |
505 | endpos--; |
506 | sep = *p; |
507 | *endpos = '\0'; |
508 | while(sep == ';') { |
509 | while(ISSPACE(*++p)) |
510 | ; |
511 | |
512 | if(!endct && checkprefix("type=" , p)) { |
513 | for(p += 5; ISSPACE(*p); p++) |
514 | ; |
515 | /* set type pointer */ |
516 | type = p; |
517 | |
518 | /* verify that this is a fine type specifier */ |
519 | if(2 != sscanf(type, "%127[^/ ]/%127[^;, \n]" , type_major, type_minor)) { |
520 | warnf(config->global, "Illegally formatted content-type field!\n" ); |
521 | curl_slist_free_all(headers); |
522 | return -1; /* illegal content-type syntax! */ |
523 | } |
524 | |
525 | /* now point beyond the content-type specifier */ |
526 | p = type + strlen(type_major) + strlen(type_minor) + 1; |
527 | for(endct = p; *p && *p != ';' && *p != endchar; p++) |
528 | if(!ISSPACE(*p)) |
529 | endct = p + 1; |
530 | sep = *p; |
531 | } |
532 | else if(checkprefix("filename=" , p)) { |
533 | if(endct) { |
534 | *endct = '\0'; |
535 | endct = NULL; |
536 | } |
537 | for(p += 9; ISSPACE(*p); p++) |
538 | ; |
539 | tp = p; |
540 | filename = get_param_word(&p, &endpos, endchar); |
541 | /* If not quoted, strip trailing spaces. */ |
542 | if(filename == tp) |
543 | while(endpos > filename && ISSPACE(endpos[-1])) |
544 | endpos--; |
545 | sep = *p; |
546 | *endpos = '\0'; |
547 | } |
548 | else if(checkprefix("headers=" , p)) { |
549 | if(endct) { |
550 | *endct = '\0'; |
551 | endct = NULL; |
552 | } |
553 | p += 8; |
554 | if(*p == '@' || *p == '<') { |
555 | char *hdrfile; |
556 | FILE *fp; |
557 | /* Read headers from a file. */ |
558 | |
559 | do { |
560 | p++; |
561 | } while(ISSPACE(*p)); |
562 | tp = p; |
563 | hdrfile = get_param_word(&p, &endpos, endchar); |
564 | /* If not quoted, strip trailing spaces. */ |
565 | if(hdrfile == tp) |
566 | while(endpos > hdrfile && ISSPACE(endpos[-1])) |
567 | endpos--; |
568 | sep = *p; |
569 | *endpos = '\0'; |
570 | fp = fopen(hdrfile, FOPEN_READTEXT); |
571 | if(!fp) |
572 | warnf(config->global, "Cannot read from %s: %s\n" , hdrfile, |
573 | strerror(errno)); |
574 | else { |
575 | int i = read_field_headers(config, hdrfile, fp, &headers); |
576 | |
577 | fclose(fp); |
578 | if(i) { |
579 | curl_slist_free_all(headers); |
580 | return -1; |
581 | } |
582 | } |
583 | } |
584 | else { |
585 | char *hdr; |
586 | |
587 | while(ISSPACE(*p)) |
588 | p++; |
589 | tp = p; |
590 | hdr = get_param_word(&p, &endpos, endchar); |
591 | /* If not quoted, strip trailing spaces. */ |
592 | if(hdr == tp) |
593 | while(endpos > hdr && ISSPACE(endpos[-1])) |
594 | endpos--; |
595 | sep = *p; |
596 | *endpos = '\0'; |
597 | if(slist_append(&headers, hdr)) { |
598 | fprintf(config->global->errors, "Out of memory for field header!\n" ); |
599 | curl_slist_free_all(headers); |
600 | return -1; |
601 | } |
602 | } |
603 | } |
604 | else if(checkprefix("encoder=" , p)) { |
605 | if(endct) { |
606 | *endct = '\0'; |
607 | endct = NULL; |
608 | } |
609 | for(p += 8; ISSPACE(*p); p++) |
610 | ; |
611 | tp = p; |
612 | encoder = get_param_word(&p, &endpos, endchar); |
613 | /* If not quoted, strip trailing spaces. */ |
614 | if(encoder == tp) |
615 | while(endpos > encoder && ISSPACE(endpos[-1])) |
616 | endpos--; |
617 | sep = *p; |
618 | *endpos = '\0'; |
619 | } |
620 | else if(endct) { |
621 | /* This is part of content type. */ |
622 | for(endct = p; *p && *p != ';' && *p != endchar; p++) |
623 | if(!ISSPACE(*p)) |
624 | endct = p + 1; |
625 | sep = *p; |
626 | } |
627 | else { |
628 | /* unknown prefix, skip to next block */ |
629 | char *unknown = get_param_word(&p, &endpos, endchar); |
630 | |
631 | sep = *p; |
632 | *endpos = '\0'; |
633 | if(*unknown) |
634 | warnf(config->global, "skip unknown form field: %s\n" , unknown); |
635 | } |
636 | } |
637 | |
638 | /* Terminate content type. */ |
639 | if(endct) |
640 | *endct = '\0'; |
641 | |
642 | if(ptype) |
643 | *ptype = type; |
644 | else if(type) |
645 | warnf(config->global, "Field content type not allowed here: %s\n" , type); |
646 | |
647 | if(pfilename) |
648 | *pfilename = filename; |
649 | else if(filename) |
650 | warnf(config->global, |
651 | "Field file name not allowed here: %s\n" , filename); |
652 | |
653 | if(pencoder) |
654 | *pencoder = encoder; |
655 | else if(encoder) |
656 | warnf(config->global, |
657 | "Field encoder not allowed here: %s\n" , encoder); |
658 | |
659 | if(pheaders) |
660 | *pheaders = headers; |
661 | else if(headers) { |
662 | warnf(config->global, |
663 | "Field headers not allowed here: %s\n" , headers->data); |
664 | curl_slist_free_all(headers); |
665 | } |
666 | |
667 | *str = p; |
668 | return sep & 0xFF; |
669 | } |
670 | |
671 | |
672 | /*************************************************************************** |
673 | * |
674 | * formparse() |
675 | * |
676 | * Reads a 'name=value' parameter and builds the appropriate linked list. |
677 | * |
678 | * If the value is of the form '<filename', field data is read from the |
679 | * given file. |
680 | |
681 | * Specify files to upload with 'name=@filename', or 'name=@"filename"' |
682 | * in case the filename contain ',' or ';'. Supports specified |
683 | * given Content-Type of the files. Such as ';type=<content-type>'. |
684 | * |
685 | * If literal_value is set, any initial '@' or '<' in the value string |
686 | * loses its special meaning, as does any embedded ';type='. |
687 | * |
688 | * You may specify more than one file for a single name (field). Specify |
689 | * multiple files by writing it like: |
690 | * |
691 | * 'name=@filename,filename2,filename3' |
692 | * |
693 | * or use double-quotes quote the filename: |
694 | * |
695 | * 'name=@"filename","filename2","filename3"' |
696 | * |
697 | * If you want content-types specified for each too, write them like: |
698 | * |
699 | * 'name=@filename;type=image/gif,filename2,filename3' |
700 | * |
701 | * If you want custom headers added for a single part, write them in a separate |
702 | * file and do like this: |
703 | * |
704 | * 'name=foo;headers=@headerfile' or why not |
705 | * 'name=@filemame;headers=@headerfile' |
706 | * |
707 | * To upload a file, but to fake the file name that will be included in the |
708 | * formpost, do like this: |
709 | * |
710 | * 'name=@filename;filename=/dev/null' or quote the faked filename like: |
711 | * 'name=@filename;filename="play, play, and play.txt"' |
712 | * |
713 | * If filename/path contains ',' or ';', it must be quoted by double-quotes, |
714 | * else curl will fail to figure out the correct filename. if the filename |
715 | * tobe quoted contains '"' or '\', '"' and '\' must be escaped by backslash. |
716 | * |
717 | ***************************************************************************/ |
718 | |
719 | /* Convenience macros for null pointer check. */ |
720 | #define NULL_CHECK(ptr, init, retcode) { \ |
721 | (ptr) = (init); \ |
722 | if(!(ptr)) { \ |
723 | warnf(config->global, "out of memory!\n"); \ |
724 | curl_slist_free_all(headers); \ |
725 | Curl_safefree(contents); \ |
726 | return retcode; \ |
727 | } \ |
728 | } |
729 | #define SET_TOOL_MIME_PTR(m, field, retcode) { \ |
730 | if(field) \ |
731 | NULL_CHECK((m)->field, strdup(field), retcode); \ |
732 | } |
733 | |
734 | int formparse(struct OperationConfig *config, |
735 | const char *input, |
736 | tool_mime **mimeroot, |
737 | tool_mime **mimecurrent, |
738 | bool literal_value) |
739 | { |
740 | /* input MUST be a string in the format 'name=contents' and we'll |
741 | build a linked list with the info */ |
742 | char *name = NULL; |
743 | char *contents = NULL; |
744 | char *contp; |
745 | char *data; |
746 | char *type = NULL; |
747 | char *filename = NULL; |
748 | char *encoder = NULL; |
749 | struct curl_slist * = NULL; |
750 | tool_mime *part = NULL; |
751 | CURLcode res; |
752 | |
753 | /* Allocate the main mime structure if needed. */ |
754 | if(!*mimecurrent) { |
755 | NULL_CHECK(*mimeroot, tool_mime_new_parts(NULL), 1); |
756 | *mimecurrent = *mimeroot; |
757 | } |
758 | |
759 | /* Make a copy we can overwrite. */ |
760 | NULL_CHECK(contents, strdup(input), 2); |
761 | |
762 | /* Scan for the end of the name. */ |
763 | contp = strchr(contents, '='); |
764 | if(contp) { |
765 | int sep = '\0'; |
766 | if(contp > contents) |
767 | name = contents; |
768 | *contp++ = '\0'; |
769 | |
770 | if(*contp == '(' && !literal_value) { |
771 | /* Starting a multipart. */ |
772 | sep = get_param_part(config, '\0', |
773 | &contp, &data, &type, NULL, NULL, &headers); |
774 | if(sep < 0) { |
775 | Curl_safefree(contents); |
776 | return 3; |
777 | } |
778 | NULL_CHECK(part, tool_mime_new_parts(*mimecurrent), 4); |
779 | *mimecurrent = part; |
780 | part->headers = headers; |
781 | headers = NULL; |
782 | SET_TOOL_MIME_PTR(part, type, 5); |
783 | } |
784 | else if(!name && !strcmp(contp, ")" ) && !literal_value) { |
785 | /* Ending a multipart. */ |
786 | if(*mimecurrent == *mimeroot) { |
787 | warnf(config->global, "no multipart to terminate!\n" ); |
788 | Curl_safefree(contents); |
789 | return 6; |
790 | } |
791 | *mimecurrent = (*mimecurrent)->parent; |
792 | } |
793 | else if('@' == contp[0] && !literal_value) { |
794 | |
795 | /* we use the @-letter to indicate file name(s) */ |
796 | |
797 | tool_mime *subparts = NULL; |
798 | |
799 | do { |
800 | /* since this was a file, it may have a content-type specifier |
801 | at the end too, or a filename. Or both. */ |
802 | ++contp; |
803 | sep = get_param_part(config, ',', &contp, |
804 | &data, &type, &filename, &encoder, &headers); |
805 | if(sep < 0) { |
806 | Curl_safefree(contents); |
807 | return 7; |
808 | } |
809 | |
810 | /* now contp point to comma or string end. |
811 | If more files to come, make sure we have multiparts. */ |
812 | if(!subparts) { |
813 | if(sep != ',') /* If there is a single file. */ |
814 | subparts = *mimecurrent; |
815 | else |
816 | NULL_CHECK(subparts, tool_mime_new_parts(*mimecurrent), 8); |
817 | } |
818 | |
819 | /* Store that file in a part. */ |
820 | NULL_CHECK(part, |
821 | tool_mime_new_filedata(subparts, data, TRUE, &res), 9); |
822 | part->headers = headers; |
823 | headers = NULL; |
824 | part->config = config->global; |
825 | if(res == CURLE_READ_ERROR) { |
826 | /* An error occurred while reading stdin: if read has started, |
827 | issue the error now. Else, delay it until processed by |
828 | libcurl. */ |
829 | if(part->size > 0) { |
830 | warnf(config->global, |
831 | "error while reading standard input\n" ); |
832 | Curl_safefree(contents); |
833 | return 10; |
834 | } |
835 | CONST_SAFEFREE(part->data); |
836 | part->data = NULL; |
837 | part->size = -1; |
838 | res = CURLE_OK; |
839 | } |
840 | SET_TOOL_MIME_PTR(part, filename, 11); |
841 | SET_TOOL_MIME_PTR(part, type, 12); |
842 | SET_TOOL_MIME_PTR(part, encoder, 13); |
843 | |
844 | /* *contp could be '\0', so we just check with the delimiter */ |
845 | } while(sep); /* loop if there's another file name */ |
846 | part = (*mimecurrent)->subparts; /* Set name on group. */ |
847 | } |
848 | else { |
849 | if(*contp == '<' && !literal_value) { |
850 | ++contp; |
851 | sep = get_param_part(config, '\0', &contp, |
852 | &data, &type, NULL, &encoder, &headers); |
853 | if(sep < 0) { |
854 | Curl_safefree(contents); |
855 | return 14; |
856 | } |
857 | |
858 | NULL_CHECK(part, tool_mime_new_filedata(*mimecurrent, data, FALSE, |
859 | &res), 15); |
860 | part->headers = headers; |
861 | headers = NULL; |
862 | part->config = config->global; |
863 | if(res == CURLE_READ_ERROR) { |
864 | /* An error occurred while reading stdin: if read has started, |
865 | issue the error now. Else, delay it until processed by |
866 | libcurl. */ |
867 | if(part->size > 0) { |
868 | warnf(config->global, |
869 | "error while reading standard input\n" ); |
870 | Curl_safefree(contents); |
871 | return 16; |
872 | } |
873 | CONST_SAFEFREE(part->data); |
874 | part->data = NULL; |
875 | part->size = -1; |
876 | res = CURLE_OK; |
877 | } |
878 | } |
879 | else { |
880 | if(literal_value) |
881 | data = contp; |
882 | else { |
883 | sep = get_param_part(config, '\0', &contp, |
884 | &data, &type, &filename, &encoder, &headers); |
885 | if(sep < 0) { |
886 | Curl_safefree(contents); |
887 | return 17; |
888 | } |
889 | } |
890 | |
891 | NULL_CHECK(part, tool_mime_new_data(*mimecurrent, data), 18); |
892 | part->headers = headers; |
893 | headers = NULL; |
894 | } |
895 | |
896 | SET_TOOL_MIME_PTR(part, filename, 19); |
897 | SET_TOOL_MIME_PTR(part, type, 20); |
898 | SET_TOOL_MIME_PTR(part, encoder, 21); |
899 | |
900 | if(sep) { |
901 | *contp = (char) sep; |
902 | warnf(config->global, |
903 | "garbage at end of field specification: %s\n" , contp); |
904 | } |
905 | } |
906 | |
907 | /* Set part name. */ |
908 | SET_TOOL_MIME_PTR(part, name, 22); |
909 | } |
910 | else { |
911 | warnf(config->global, "Illegally formatted input field!\n" ); |
912 | Curl_safefree(contents); |
913 | return 23; |
914 | } |
915 | Curl_safefree(contents); |
916 | return 0; |
917 | } |
918 | |