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