Imported Upstream version 7.59.0
[platform/upstream/curl.git] / src / tool_formparse.c
index 88352fb..719e341 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -21,6 +21,7 @@
  ***************************************************************************/
 #include "tool_setup.h"
 
+#include "mime.h"
 #include "strcase.h"
 
 #define ENABLE_CURLX_PRINTF
 #include "curlx.h"
 
 #include "tool_cfgable.h"
-#include "tool_mfiles.h"
+#include "tool_convert.h"
 #include "tool_msgs.h"
+#include "tool_binmode.h"
+#include "tool_getparam.h"
+#include "tool_paramhlp.h"
 #include "tool_formparse.h"
 
 #include "memdebug.h" /* keep this as LAST include */
 
+/* Stdin parameters. */
+typedef struct {
+  char *data;  /* Memory data. */
+  curl_off_t origin;  /* File read origin offset. */
+  curl_off_t size; /* Data size. */
+  curl_off_t curpos; /* Current read position. */
+}  standard_input;
+
 
 /*
  * helper function to get a word from form param
  * after call get_parm_word, str either point to string end
  * or point to any of end chars.
  */
-static char *get_param_word(char **str, char **end_pos)
+static char *get_param_word(char **str, char **end_pos, char endchar)
 {
   char *ptr = *str;
   char *word_begin = NULL;
   char *ptr2;
   char *escape = NULL;
-  const char *end_chars = ";,";
 
   /* the first non-space char is here */
   word_begin = ptr;
@@ -76,10 +87,10 @@ static char *get_param_word(char **str, char **end_pos)
           while(ptr < *end_pos);
           *end_pos = ptr2;
         }
-        while(*ptr && NULL==strchr(end_chars, *ptr))
+        while(*ptr && *ptr != ';' && *ptr != endchar)
           ++ptr;
         *str = ptr;
-        return word_begin+1;
+        return word_begin + 1;
       }
       ++ptr;
     }
@@ -87,12 +98,407 @@ static char *get_param_word(char **str, char **end_pos)
     ptr = word_begin;
   }
 
-  while(*ptr && NULL==strchr(end_chars, *ptr))
+  while(*ptr && *ptr != ';' && *ptr != endchar)
     ++ptr;
   *str = *end_pos = ptr;
   return word_begin;
 }
 
+/* Append slist item and return -1 if failed. */
+static int slist_append(struct curl_slist **plist, const char *data)
+{
+  struct curl_slist *s = curl_slist_append(*plist, data);
+
+  if(!s)
+    return -1;
+
+  *plist = s;
+  return 0;
+}
+
+/* Read headers from a file and append to list. */
+static int read_field_headers(struct OperationConfig *config,
+                              const char *filename, FILE *fp,
+                              struct curl_slist **pheaders)
+{
+  size_t hdrlen = 0;
+  size_t pos = 0;
+  int c;
+  bool incomment = FALSE;
+  int lineno = 1;
+  char hdrbuf[999]; /* Max. header length + 1. */
+
+  for(;;) {
+    c = getc(fp);
+    if(c == EOF || (!pos && !ISSPACE(c))) {
+      /* Strip and flush the current header. */
+      while(hdrlen && ISSPACE(hdrbuf[hdrlen - 1]))
+        hdrlen--;
+      if(hdrlen) {
+        hdrbuf[hdrlen] = '\0';
+        if(slist_append(pheaders, hdrbuf)) {
+          fprintf(config->global->errors,
+                  "Out of memory for field headers!\n");
+          return -1;
+        }
+        hdrlen = 0;
+      }
+    }
+
+    switch(c) {
+    case EOF:
+      if(ferror(fp)) {
+        fprintf(config->global->errors,
+                "Header file %s read error: %s\n", filename, strerror(errno));
+        return -1;
+      }
+      return 0;    /* Done. */
+    case '\r':
+      continue;    /* Ignore. */
+    case '\n':
+      pos = 0;
+      incomment = FALSE;
+      lineno++;
+      continue;
+    case '#':
+      if(!pos)
+        incomment = TRUE;
+      break;
+    }
+
+    pos++;
+    if(!incomment) {
+      if(hdrlen == sizeof hdrbuf - 1) {
+        warnf(config->global, "File %s line %d: header too long (truncated)\n",
+              filename, lineno);
+        c = ' ';
+      }
+      if(hdrlen <= sizeof hdrbuf - 1)
+        hdrbuf[hdrlen++] = (char) c;
+    }
+  }
+  /* NOTREACHED */
+}
+
+static int get_param_part(struct OperationConfig *config, char endchar,
+                          char **str, char **pdata, char **ptype,
+                          char **pfilename, char **pencoder,
+                          struct curl_slist **pheaders)
+{
+  char *p = *str;
+  char *type = NULL;
+  char *filename = NULL;
+  char *encoder = NULL;
+  char *endpos;
+  char *tp;
+  char sep;
+  char type_major[128] = "";
+  char type_minor[128] = "";
+  char *endct = NULL;
+  struct curl_slist *headers = NULL;
+
+  if(ptype)
+    *ptype = NULL;
+  if(pfilename)
+    *pfilename = NULL;
+  if(pheaders)
+    *pheaders = NULL;
+  if(pencoder)
+    *pencoder = NULL;
+  while(ISSPACE(*p))
+    p++;
+  tp = p;
+  *pdata = get_param_word(&p, &endpos, endchar);
+  /* If not quoted, strip trailing spaces. */
+  if(*pdata == tp)
+    while(endpos > *pdata && ISSPACE(endpos[-1]))
+      endpos--;
+  sep = *p;
+  *endpos = '\0';
+  while(sep == ';') {
+    while(ISSPACE(*++p))
+      ;
+
+    if(!endct && checkprefix("type=", p)) {
+      for(p += 5; ISSPACE(*p); p++)
+        ;
+      /* set type pointer */
+      type = p;
+
+      /* verify that this is a fine type specifier */
+      if(2 != sscanf(type, "%127[^/ ]/%127[^;, \n]", type_major, type_minor)) {
+        warnf(config->global, "Illegally formatted content-type field!\n");
+        curl_slist_free_all(headers);
+        return -1; /* illegal content-type syntax! */
+      }
+
+      /* now point beyond the content-type specifier */
+      p = type + strlen(type_major) + strlen(type_minor) + 1;
+      for(endct = p; *p && *p != ';' && *p != endchar; p++)
+        if(!ISSPACE(*p))
+          endct = p + 1;
+      sep = *p;
+    }
+    else if(checkprefix("filename=", p)) {
+      if(endct) {
+        *endct = '\0';
+        endct = NULL;
+      }
+      for(p += 9; ISSPACE(*p); p++)
+        ;
+      tp = p;
+      filename = get_param_word(&p, &endpos, endchar);
+      /* If not quoted, strip trailing spaces. */
+      if(filename == tp)
+        while(endpos > filename && ISSPACE(endpos[-1]))
+          endpos--;
+      sep = *p;
+      *endpos = '\0';
+    }
+    else if(checkprefix("headers=", p)) {
+      if(endct) {
+        *endct = '\0';
+        endct = NULL;
+      }
+      p += 8;
+      if(*p == '@' || *p == '<') {
+        char *hdrfile;
+        FILE *fp;
+        /* Read headers from a file. */
+
+        do {
+          p++;
+        } while(ISSPACE(*p));
+        tp = p;
+        hdrfile = get_param_word(&p, &endpos, endchar);
+        /* If not quoted, strip trailing spaces. */
+        if(hdrfile == tp)
+          while(endpos > hdrfile && ISSPACE(endpos[-1]))
+            endpos--;
+        sep = *p;
+        *endpos = '\0';
+        /* TODO: maybe special fopen for VMS? */
+        fp = fopen(hdrfile, FOPEN_READTEXT);
+        if(!fp)
+          warnf(config->global, "Cannot read from %s: %s\n", hdrfile,
+                strerror(errno));
+        else {
+          int i = read_field_headers(config, hdrfile, fp, &headers);
+
+          fclose(fp);
+          if(i) {
+            curl_slist_free_all(headers);
+            return -1;
+          }
+        }
+      }
+      else {
+        char *hdr;
+
+        while(ISSPACE(*p))
+          p++;
+        tp = p;
+        hdr = get_param_word(&p, &endpos, endchar);
+        /* If not quoted, strip trailing spaces. */
+        if(hdr == tp)
+          while(endpos > hdr && ISSPACE(endpos[-1]))
+            endpos--;
+        sep = *p;
+        *endpos = '\0';
+        if(slist_append(&headers, hdr)) {
+          fprintf(config->global->errors, "Out of memory for field header!\n");
+          curl_slist_free_all(headers);
+          return -1;
+        }
+      }
+    }
+    else if(checkprefix("encoder=", p)) {
+      if(endct) {
+        *endct = '\0';
+        endct = NULL;
+      }
+      for(p += 8; ISSPACE(*p); p++)
+        ;
+      tp = p;
+      encoder = get_param_word(&p, &endpos, endchar);
+      /* If not quoted, strip trailing spaces. */
+      if(encoder == tp)
+        while(endpos > encoder && ISSPACE(endpos[-1]))
+          endpos--;
+      sep = *p;
+      *endpos = '\0';
+    }
+    else if(endct) {
+      /* This is part of content type. */
+      for(endct = p; *p && *p != ';' && *p != endchar; p++)
+        if(!ISSPACE(*p))
+          endct = p + 1;
+      sep = *p;
+    }
+    else {
+      /* unknown prefix, skip to next block */
+      char *unknown = get_param_word(&p, &endpos, endchar);
+
+      sep = *p;
+      *endpos = '\0';
+      if(*unknown)
+        warnf(config->global, "skip unknown form field: %s\n", unknown);
+    }
+  }
+
+  /* Terminate content type. */
+  if(endct)
+    *endct = '\0';
+
+  if(ptype)
+    *ptype = type;
+  else if(type)
+    warnf(config->global, "Field content type not allowed here: %s\n", type);
+
+  if(pfilename)
+    *pfilename = filename;
+  else if(filename)
+    warnf(config->global,
+          "Field file name not allowed here: %s\n", filename);
+
+  if(pencoder)
+    *pencoder = encoder;
+  else if(encoder)
+    warnf(config->global,
+          "Field encoder not allowed here: %s\n", encoder);
+
+  if(pheaders)
+    *pheaders = headers;
+  else if(headers) {
+    warnf(config->global,
+          "Field headers not allowed here: %s\n", headers->data);
+    curl_slist_free_all(headers);
+  }
+
+  *str = p;
+  return sep & 0xFF;
+}
+
+
+/* Mime part callbacks for stdin. */
+static size_t stdin_read(char *buffer, size_t size, size_t nitems, void *arg)
+{
+  standard_input *sip = (standard_input *) arg;
+  curl_off_t bytesleft;
+  (void) size;  /* Always 1: ignored. */
+
+  if(sip->curpos >= sip->size)
+    return 0;  /* At eof. */
+  bytesleft = sip->size - sip->curpos;
+  if((curl_off_t) nitems > bytesleft)
+    nitems = (size_t) bytesleft;
+  if(sip->data) {
+    /* Return data from memory. */
+    memcpy(buffer, sip->data + (size_t) sip->curpos, nitems);
+  }
+  else {
+    /* Read from stdin. */
+    nitems = fread(buffer, 1, nitems, stdin);
+  }
+  sip->curpos += nitems;
+  return nitems;
+}
+
+static int stdin_seek(void *instream, curl_off_t offset, int whence)
+{
+  standard_input *sip = (standard_input *) instream;
+
+  switch(whence) {
+  case SEEK_CUR:
+    offset += sip->curpos;
+    break;
+  case SEEK_END:
+    offset += sip->size;
+    break;
+  }
+  if(offset < 0)
+    return CURL_SEEKFUNC_CANTSEEK;
+  if(!sip->data) {
+    if(fseek(stdin, (long) (offset + sip->origin), SEEK_SET))
+      return CURL_SEEKFUNC_CANTSEEK;
+  }
+  sip->curpos = offset;
+  return CURL_SEEKFUNC_OK;
+}
+
+static void stdin_free(void *ptr)
+{
+  standard_input *sip = (standard_input *) ptr;
+
+  Curl_safefree(sip->data);
+  free(sip);
+}
+
+/* Set a part's data from a file, taking care about the pseudo filename "-" as
+ * a shortcut to read stdin: if so, use a callback to read OUR stdin (to
+ * workaround Windows DLL file handle caveat).
+ * If stdin is a regular file opened in binary mode, save current offset as
+ * origin for rewind and do not buffer data. Else read to EOF and keep in
+ * memory. In all cases, compute the stdin data size.
+ */
+static CURLcode file_or_stdin(curl_mimepart *part, const char *file)
+{
+  standard_input *sip = NULL;
+  int fd = -1;
+  CURLcode result = CURLE_OK;
+  struct_stat sbuf;
+
+  if(strcmp(file, "-"))
+    return curl_mime_filedata(part, file);
+
+  sip = (standard_input *) malloc(sizeof *sip);
+  if(!sip)
+    return CURLE_OUT_OF_MEMORY;
+
+  memset((char *) sip, 0, sizeof *sip);
+  set_binmode(stdin);
+
+  /* If stdin is a regular file, do not buffer data but read it when needed. */
+  fd = fileno(stdin);
+  sip->origin = ftell(stdin);
+  if(fd >= 0 && sip->origin >= 0 && !fstat(fd, &sbuf) &&
+#ifdef __VMS
+     sbuf.st_fab_rfm != FAB$C_VAR && sbuf.st_fab_rfm != FAB$C_VFC &&
+#endif
+     S_ISREG(sbuf.st_mode)) {
+    sip->size = sbuf.st_size - sip->origin;
+    if(sip->size < 0)
+      sip->size = 0;
+  }
+  else {  /* Not suitable for direct use, buffer stdin data. */
+    size_t stdinsize = 0;
+
+    sip->origin = 0;
+    if(file2memory(&sip->data, &stdinsize, stdin) != PARAM_OK)
+      result = CURLE_OUT_OF_MEMORY;
+    else {
+      if(!stdinsize)
+        sip->data = NULL;  /* Has been freed if no data. */
+      sip->size = stdinsize;
+      if(ferror(stdin))
+        result = CURLE_READ_ERROR;
+    }
+  }
+
+  /* Set remote file name. */
+  if(!result)
+    result = curl_mime_filename(part, file);
+
+  /* Set part's data from callback. */
+  if(!result)
+    result = curl_mime_data_cb(part, sip->size,
+                               stdin_read, stdin_seek, stdin_free, sip);
+  if(result)
+    stdin_free(sip);
+  return result;
+}
+
+
 /***************************************************************************
  *
  * formparse()
@@ -142,219 +548,316 @@ static char *get_param_word(char **str, char **end_pos)
 
 int formparse(struct OperationConfig *config,
               const char *input,
-              struct curl_httppost **httppost,
-              struct curl_httppost **last_post,
+              curl_mime **mimepost,
+              curl_mime **mimecurrent,
               bool literal_value)
 {
-  /* nextarg MUST be a string in the format 'name=contents' and we'll
+  /* input MUST be a string in the format 'name=contents' and we'll
      build a linked list with the info */
-  char name[256];
+  char *name = NULL;
   char *contents = NULL;
-  char type_major[128] = "";
-  char type_minor[128] = "";
   char *contp;
-  const char *type = NULL;
-  char *sep;
-
-  if((1 == sscanf(input, "%255[^=]=", name)) &&
-     ((contp = strchr(input, '=')) != NULL)) {
-    /* the input was using the correct format */
-
-    /* Allocate the contents */
-    contents = strdup(contp+1);
-    if(!contents) {
-      fprintf(config->global->errors, "out of memory\n");
+  char *data;
+  char *type = NULL;
+  char *filename = NULL;
+  char *encoder = NULL;
+  struct curl_slist *headers = NULL;
+  curl_mimepart *part = NULL;
+  CURLcode res;
+  int sep = '\0';
+
+  /* Allocate the main mime structure if needed. */
+  if(!*mimepost) {
+    *mimepost = curl_mime_init(config->easy);
+    if(!*mimepost) {
+      warnf(config->global, "curl_mime_init failed!\n");
       return 1;
     }
-    contp = contents;
+    *mimecurrent = *mimepost;
+  }
 
-    if('@' == contp[0] && !literal_value) {
+  /* Make a copy we can overwrite. */
+  contents = strdup(input);
+  if(!contents) {
+    fprintf(config->global->errors, "out of memory\n");
+    return 2;
+  }
 
-      /* we use the @-letter to indicate file name(s) */
+  /* Scan for the end of the name. */
+  contp = strchr(contents, '=');
+  if(contp) {
+    if(contp > contents)
+      name = contents;
+    *contp++ = '\0';
+
+    if(*contp == '(' && !literal_value) {
+      curl_mime *subparts;
+
+      /* Starting a multipart. */
+      sep = get_param_part(config, '\0',
+                           &contp, &data, &type, NULL, NULL, &headers);
+      if(sep < 0) {
+        Curl_safefree(contents);
+        return 3;
+      }
+      subparts = curl_mime_init(config->easy);
+      if(!subparts) {
+        warnf(config->global, "curl_mime_init failed!\n");
+        curl_slist_free_all(headers);
+        Curl_safefree(contents);
+        return 4;
+      }
+      part = curl_mime_addpart(*mimecurrent);
+      if(!part) {
+        warnf(config->global, "curl_mime_addpart failed!\n");
+        curl_mime_free(subparts);
+        curl_slist_free_all(headers);
+        Curl_safefree(contents);
+        return 5;
+      }
+      if(curl_mime_subparts(part, subparts)) {
+        warnf(config->global, "curl_mime_subparts failed!\n");
+        curl_mime_free(subparts);
+        curl_slist_free_all(headers);
+        Curl_safefree(contents);
+        return 6;
+      }
+      *mimecurrent = subparts;
+      if(curl_mime_headers(part, headers, 1)) {
+        warnf(config->global, "curl_mime_headers failed!\n");
+        curl_slist_free_all(headers);
+        Curl_safefree(contents);
+        return 7;
+      }
+      if(curl_mime_type(part, type)) {
+        warnf(config->global, "curl_mime_type failed!\n");
+        Curl_safefree(contents);
+        return 8;
+      }
+    }
+    else if(!name && !strcmp(contp, ")") && !literal_value) {
+      /* Ending a mutipart. */
+      if(*mimecurrent == *mimepost) {
+        warnf(config->global, "no multipart to terminate!\n");
+        Curl_safefree(contents);
+        return 9;
+        }
+      *mimecurrent = (*mimecurrent)->parent->parent;
+    }
+    else if('@' == contp[0] && !literal_value) {
 
-      struct multi_files *multi_start = NULL;
-      struct multi_files *multi_current = NULL;
+      /* we use the @-letter to indicate file name(s) */
 
-      char *ptr = contp;
-      char *end = ptr + strlen(ptr);
+      curl_mime *subparts = NULL;
 
       do {
         /* since this was a file, it may have a content-type specifier
            at the end too, or a filename. Or both. */
-        char *filename = NULL;
-        char *word_end;
-        bool semicolon;
-
-        type = NULL;
-
-        ++ptr;
-        contp = get_param_word(&ptr, &word_end);
-        semicolon = (';' == *ptr) ? TRUE : FALSE;
-        *word_end = '\0'; /* terminate the contp */
-
-        /* have other content, continue parse */
-        while(semicolon) {
-          /* have type or filename field */
-          ++ptr;
-          while(*ptr && (ISSPACE(*ptr)))
-            ++ptr;
-
-          if(checkprefix("type=", ptr)) {
-            /* set type pointer */
-            type = &ptr[5];
-
-            /* verify that this is a fine type specifier */
-            if(2 != sscanf(type, "%127[^/]/%127[^;,\n]",
-                           type_major, type_minor)) {
-              warnf(config->global,
-                    "Illegally formatted content-type field!\n");
-              Curl_safefree(contents);
-              FreeMultiInfo(&multi_start, &multi_current);
-              return 2; /* illegal content-type syntax! */
-            }
-
-            /* now point beyond the content-type specifier */
-            sep = (char *)type + strlen(type_major)+strlen(type_minor)+1;
-
-            /* there's a semicolon following - we check if it is a filename
-               specified and if not we simply assume that it is text that
-               the user wants included in the type and include that too up
-               to the next sep. */
-            ptr = sep;
-            if(*sep==';') {
-              if(!checkprefix(";filename=", sep)) {
-                ptr = sep + 1;
-                (void)get_param_word(&ptr, &sep);
-                semicolon = (';' == *ptr) ? TRUE : FALSE;
-              }
-            }
-            else
-              semicolon = FALSE;
+        ++contp;
+        sep = get_param_part(config, ',', &contp,
+                             &data, &type, &filename, &encoder, &headers);
+        if(sep < 0) {
+          if(subparts != *mimecurrent)
+            curl_mime_free(subparts);
+          Curl_safefree(contents);
+          return 10;
+        }
 
-            if(*sep)
-              *sep = '\0'; /* zero terminate type string */
-          }
-          else if(checkprefix("filename=", ptr)) {
-            ptr += 9;
-            filename = get_param_word(&ptr, &word_end);
-            semicolon = (';' == *ptr) ? TRUE : FALSE;
-            *word_end = '\0';
-          }
+        /* now contp point to comma or string end.
+           If more files to come, make sure we have multiparts. */
+        if(!subparts) {
+          if(sep != ',')    /* If there is a single file. */
+            subparts = *mimecurrent;
           else {
-            /* unknown prefix, skip to next block */
-            char *unknown = NULL;
-            unknown = get_param_word(&ptr, &word_end);
-            semicolon = (';' == *ptr) ? TRUE : FALSE;
-            if(*unknown) {
-              *word_end = '\0';
-              warnf(config->global, "skip unknown form field: %s\n", unknown);
+            subparts = curl_mime_init(config->easy);
+            if(!subparts) {
+              warnf(config->global, "curl_mime_init failed!\n");
+              curl_slist_free_all(headers);
+              Curl_safefree(contents);
+              return 11;
             }
           }
         }
-        /* now ptr point to comma or string end */
 
+        /* Allocate a part for that file. */
+        part = curl_mime_addpart(subparts);
+        if(!part) {
+          warnf(config->global, "curl_mime_addpart failed!\n");
+          if(subparts != *mimecurrent)
+            curl_mime_free(subparts);
+          curl_slist_free_all(headers);
+          Curl_safefree(contents);
+          return 12;
+        }
 
-        /* if type == NULL curl_formadd takes care of the problem */
+        /* Set part headers. */
+        if(curl_mime_headers(part, headers, 1)) {
+          warnf(config->global, "curl_mime_headers failed!\n");
+          if(subparts != *mimecurrent)
+            curl_mime_free(subparts);
+          curl_slist_free_all(headers);
+          Curl_safefree(contents);
+          return 13;
+        }
 
-        if(*contp && !AddMultiFiles(contp, type, filename, &multi_start,
-                          &multi_current)) {
-          warnf(config->global, "Error building form post!\n");
+        /* Setup file in part. */
+        res = file_or_stdin(part, data);
+        if(res) {
+          warnf(config->global, "setting file %s  failed!\n", data);
+          if(res != CURLE_READ_ERROR) {
+            if(subparts != *mimecurrent)
+              curl_mime_free(subparts);
+            Curl_safefree(contents);
+            return 14;
+          }
+        }
+        if(filename && curl_mime_filename(part, filename)) {
+          warnf(config->global, "curl_mime_filename failed!\n");
+          if(subparts != *mimecurrent)
+            curl_mime_free(subparts);
+          Curl_safefree(contents);
+          return 15;
+        }
+        if(curl_mime_type(part, type)) {
+          warnf(config->global, "curl_mime_type failed!\n");
+          if(subparts != *mimecurrent)
+            curl_mime_free(subparts);
+          Curl_safefree(contents);
+          return 16;
+        }
+        if(curl_mime_encoder(part, encoder)) {
+          warnf(config->global, "curl_mime_encoder failed!\n");
+          if(subparts != *mimecurrent)
+            curl_mime_free(subparts);
           Curl_safefree(contents);
-          FreeMultiInfo(&multi_start, &multi_current);
-          return 3;
+          return 17;
         }
 
-        /* *ptr could be '\0', so we just check with the string end */
-      } while(ptr < end); /* loop if there's another file name */
+        /* *contp could be '\0', so we just check with the delimiter */
+      } while(sep); /* loop if there's another file name */
 
       /* now we add the multiple files section */
-      if(multi_start) {
-        struct curl_forms *forms = NULL;
-        struct multi_files *start = multi_start;
-        unsigned int i, count = 0;
-        while(start) {
-          start = start->next;
-          ++count;
-        }
-        forms = malloc((count+1)*sizeof(struct curl_forms));
-        if(!forms) {
-          fprintf(config->global->errors, "Error building form post!\n");
+      if(subparts != *mimecurrent) {
+        part = curl_mime_addpart(*mimecurrent);
+        if(!part) {
+          warnf(config->global, "curl_mime_addpart failed!\n");
+          curl_mime_free(subparts);
           Curl_safefree(contents);
-          FreeMultiInfo(&multi_start, &multi_current);
-          return 4;
-        }
-        for(i = 0, start = multi_start; i < count; ++i, start = start->next) {
-          forms[i].option = start->form.option;
-          forms[i].value = start->form.value;
+          return 18;
         }
-        forms[count].option = CURLFORM_END;
-        FreeMultiInfo(&multi_start, &multi_current);
-        if(curl_formadd(httppost, last_post,
-                        CURLFORM_COPYNAME, name,
-                        CURLFORM_ARRAY, forms, CURLFORM_END) != 0) {
-          warnf(config->global, "curl_formadd failed!\n");
-          Curl_safefree(forms);
+        if(curl_mime_subparts(part, subparts)) {
+          warnf(config->global, "curl_mime_subparts failed!\n");
+          curl_mime_free(subparts);
           Curl_safefree(contents);
-          return 5;
+          return 19;
         }
-        Curl_safefree(forms);
       }
     }
     else {
-      struct curl_forms info[4];
-      int i = 0;
-      char *ct = literal_value ? NULL : strstr(contp, ";type=");
-
-      info[i].option = CURLFORM_COPYNAME;
-      info[i].value = name;
-      i++;
-
-      if(ct) {
-        info[i].option = CURLFORM_CONTENTTYPE;
-        info[i].value = &ct[6];
-        i++;
-        ct[0] = '\0'; /* zero terminate here */
-      }
+        /* Allocate a mime part. */
+        part = curl_mime_addpart(*mimecurrent);
+        if(!part) {
+          warnf(config->global, "curl_mime_addpart failed!\n");
+          Curl_safefree(contents);
+          return 20;
+        }
 
-      if(contp[0]=='<' && !literal_value) {
-        info[i].option = CURLFORM_FILECONTENT;
-        info[i].value = contp+1;
-        i++;
-        info[i].option = CURLFORM_END;
+      if(*contp == '<' && !literal_value) {
+        ++contp;
+        sep = get_param_part(config, '\0', &contp,
+                             &data, &type, NULL, &encoder, &headers);
+        if(sep < 0) {
+          Curl_safefree(contents);
+          return 21;
+        }
 
-        if(curl_formadd(httppost, last_post,
-                        CURLFORM_ARRAY, info, CURLFORM_END) != 0) {
-          warnf(config->global, "curl_formadd failed, possibly the file %s is "
-                "bad!\n", contp + 1);
+        /* Set part headers. */
+        if(curl_mime_headers(part, headers, 1)) {
+          warnf(config->global, "curl_mime_headers failed!\n");
+          curl_slist_free_all(headers);
           Curl_safefree(contents);
-          return 6;
+          return 22;
+        }
+
+        /* Setup file in part. */
+        res = file_or_stdin(part, data);
+        if(res) {
+          warnf(config->global, "setting file %s failed!\n", data);
+          if(res != CURLE_READ_ERROR) {
+            Curl_safefree(contents);
+            return 23;
+          }
         }
       }
       else {
+        if(literal_value)
+          data = contp;
+        else {
+          sep = get_param_part(config, '\0', &contp,
+                               &data, &type, &filename, &encoder, &headers);
+          if(sep < 0) {
+            Curl_safefree(contents);
+            return 24;
+          }
+        }
+
+        /* Set part headers. */
+        if(curl_mime_headers(part, headers, 1)) {
+          warnf(config->global, "curl_mime_headers failed!\n");
+          curl_slist_free_all(headers);
+          Curl_safefree(contents);
+          return 25;
+        }
+
 #ifdef CURL_DOES_CONVERSIONS
-        if(convert_to_network(contp, strlen(contp))) {
+        if(convert_to_network(data, strlen(data))) {
           warnf(config->global, "curl_formadd failed!\n");
           Curl_safefree(contents);
-          return 7;
+          return 26;
         }
 #endif
-        info[i].option = CURLFORM_COPYCONTENTS;
-        info[i].value = contp;
-        i++;
-        info[i].option = CURLFORM_END;
-        if(curl_formadd(httppost, last_post,
-                        CURLFORM_ARRAY, info, CURLFORM_END) != 0) {
-          warnf(config->global, "curl_formadd failed!\n");
+
+        if(curl_mime_data(part, data, CURL_ZERO_TERMINATED)) {
+          warnf(config->global, "curl_mime_data failed!\n");
           Curl_safefree(contents);
-          return 8;
+          return 27;
         }
       }
+
+      if(curl_mime_filename(part, filename)) {
+        warnf(config->global, "curl_mime_filename failed!\n");
+        Curl_safefree(contents);
+        return 28;
+      }
+      if(curl_mime_type(part, type)) {
+        warnf(config->global, "curl_mime_type failed!\n");
+        Curl_safefree(contents);
+        return 29;
+      }
+      if(curl_mime_encoder(part, encoder)) {
+        warnf(config->global, "curl_mime_encoder failed!\n");
+        Curl_safefree(contents);
+        return 30;
+      }
+
+      if(sep) {
+        *contp = (char) sep;
+        warnf(config->global,
+              "garbage at end of field specification: %s\n", contp);
+      }
     }
 
+    /* Set part name. */
+    if(name && curl_mime_name(part, name)) {
+      warnf(config->global, "curl_mime_name failed!\n");
+      Curl_safefree(contents);
+      return 31;
+    }
   }
   else {
     warnf(config->global, "Illegally formatted input field!\n");
-    return 1;
+    Curl_safefree(contents);
+    return 32;
   }
   Curl_safefree(contents);
   return 0;