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