CURLFORMcode FormAdd()

in extra/curl/curl-8.12.1/lib/formdata.c [209:688]


CURLFORMcode FormAdd(struct curl_httppost **httppost,
                     struct curl_httppost **last_post,
                     va_list params)
{
  struct FormInfo *first_form, *current_form, *form = NULL;
  CURLFORMcode return_value = CURL_FORMADD_OK;
  const char *prevtype = NULL;
  struct curl_httppost *post = NULL;
  CURLformoption option;
  struct curl_forms *forms = NULL;
  char *array_value = NULL; /* value read from an array */

  /* This is a state variable, that if TRUE means that we are parsing an
     array that we got passed to us. If FALSE we are parsing the input
     va_list arguments. */
  bool array_state = FALSE;

  /*
   * We need to allocate the first struct to fill in.
   */
  first_form = calloc(1, sizeof(struct FormInfo));
  if(!first_form)
    return CURL_FORMADD_MEMORY;

  current_form = first_form;

  /*
   * Loop through all the options set. Break if we have an error to report.
   */
  while(return_value == CURL_FORMADD_OK) {

    /* first see if we have more parts of the array param */
    if(array_state && forms) {
      /* get the upcoming option from the given array */
      option = forms->option;
      array_value = (char *)forms->value;

      forms++; /* advance this to next entry */
      if(CURLFORM_END == option) {
        /* end of array state */
        array_state = FALSE;
        continue;
      }
    }
    else {
      /* This is not array-state, get next option. This gets an 'int' with
         va_arg() because CURLformoption might be a smaller type than int and
         might cause compiler warnings and wrong behavior. */
      option = (CURLformoption)va_arg(params, int);
      if(CURLFORM_END == option)
        break;
    }

    switch(option) {
    case CURLFORM_ARRAY:
      if(array_state)
        /* we do not support an array from within an array */
        return_value = CURL_FORMADD_ILLEGAL_ARRAY;
      else {
        forms = va_arg(params, struct curl_forms *);
        if(forms)
          array_state = TRUE;
        else
          return_value = CURL_FORMADD_NULL;
      }
      break;

      /*
       * Set the Name property.
       */
    case CURLFORM_PTRNAME:
      current_form->flags |= HTTPPOST_PTRNAME; /* fall through */

      FALLTHROUGH();
    case CURLFORM_COPYNAME:
      if(current_form->name)
        return_value = CURL_FORMADD_OPTION_TWICE;
      else {
        char *name = array_state ?
          array_value : va_arg(params, char *);
        if(name)
          current_form->name = name; /* store for the moment */
        else
          return_value = CURL_FORMADD_NULL;
      }
      break;
    case CURLFORM_NAMELENGTH:
      if(current_form->namelength)
        return_value = CURL_FORMADD_OPTION_TWICE;
      else
        current_form->namelength =
          array_state ? (size_t)array_value : (size_t)va_arg(params, long);
      break;

      /*
       * Set the contents property.
       */
    case CURLFORM_PTRCONTENTS:
      current_form->flags |= HTTPPOST_PTRCONTENTS;
      FALLTHROUGH();
    case CURLFORM_COPYCONTENTS:
      if(current_form->value)
        return_value = CURL_FORMADD_OPTION_TWICE;
      else {
        char *value =
          array_state ? array_value : va_arg(params, char *);
        if(value)
          current_form->value = value; /* store for the moment */
        else
          return_value = CURL_FORMADD_NULL;
      }
      break;
    case CURLFORM_CONTENTSLENGTH:
      current_form->contentslength =
        array_state ? (size_t)array_value : (size_t)va_arg(params, long);
      break;

    case CURLFORM_CONTENTLEN:
      current_form->flags |= CURL_HTTPPOST_LARGE;
      current_form->contentslength =
        array_state ? (curl_off_t)(size_t)array_value :
        va_arg(params, curl_off_t);
      break;

      /* Get contents from a given filename */
    case CURLFORM_FILECONTENT:
      if(current_form->flags & (HTTPPOST_PTRCONTENTS|HTTPPOST_READFILE))
        return_value = CURL_FORMADD_OPTION_TWICE;
      else {
        const char *filename = array_state ?
          array_value : va_arg(params, char *);
        if(filename) {
          current_form->value = strdup(filename);
          if(!current_form->value)
            return_value = CURL_FORMADD_MEMORY;
          else {
            current_form->flags |= HTTPPOST_READFILE;
            current_form->value_alloc = TRUE;
          }
        }
        else
          return_value = CURL_FORMADD_NULL;
      }
      break;

      /* We upload a file */
    case CURLFORM_FILE:
      {
        const char *filename = array_state ? array_value :
          va_arg(params, char *);

        if(current_form->value) {
          if(current_form->flags & HTTPPOST_FILENAME) {
            if(filename) {
              char *fname = strdup(filename);
              if(!fname)
                return_value = CURL_FORMADD_MEMORY;
              else {
                form = AddFormInfo(fname, NULL, current_form);
                if(!form) {
                  free(fname);
                  return_value = CURL_FORMADD_MEMORY;
                }
                else {
                  form->value_alloc = TRUE;
                  current_form = form;
                  form = NULL;
                }
              }
            }
            else
              return_value = CURL_FORMADD_NULL;
          }
          else
            return_value = CURL_FORMADD_OPTION_TWICE;
        }
        else {
          if(filename) {
            current_form->value = strdup(filename);
            if(!current_form->value)
              return_value = CURL_FORMADD_MEMORY;
            else {
              current_form->flags |= HTTPPOST_FILENAME;
              current_form->value_alloc = TRUE;
            }
          }
          else
            return_value = CURL_FORMADD_NULL;
        }
        break;
      }

    case CURLFORM_BUFFERPTR:
      current_form->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER;
      if(current_form->buffer)
        return_value = CURL_FORMADD_OPTION_TWICE;
      else {
        char *buffer =
          array_state ? array_value : va_arg(params, char *);
        if(buffer) {
          current_form->buffer = buffer; /* store for the moment */
          current_form->value = buffer; /* make it non-NULL to be accepted
                                           as fine */
        }
        else
          return_value = CURL_FORMADD_NULL;
      }
      break;

    case CURLFORM_BUFFERLENGTH:
      if(current_form->bufferlength)
        return_value = CURL_FORMADD_OPTION_TWICE;
      else
        current_form->bufferlength =
          array_state ? (size_t)array_value : (size_t)va_arg(params, long);
      break;

    case CURLFORM_STREAM:
      current_form->flags |= HTTPPOST_CALLBACK;
      if(current_form->userp)
        return_value = CURL_FORMADD_OPTION_TWICE;
      else {
        char *userp =
          array_state ? array_value : va_arg(params, char *);
        if(userp) {
          current_form->userp = userp;
          current_form->value = userp; /* this is not strictly true but we
                                          derive a value from this later on
                                          and we need this non-NULL to be
                                          accepted as a fine form part */
        }
        else
          return_value = CURL_FORMADD_NULL;
      }
      break;

    case CURLFORM_CONTENTTYPE:
      {
        const char *contenttype =
          array_state ? array_value : va_arg(params, char *);
        if(current_form->contenttype) {
          if(current_form->flags & HTTPPOST_FILENAME) {
            if(contenttype) {
              char *type = strdup(contenttype);
              if(!type)
                return_value = CURL_FORMADD_MEMORY;
              else {
                form = AddFormInfo(NULL, type, current_form);
                if(!form) {
                  free(type);
                  return_value = CURL_FORMADD_MEMORY;
                }
                else {
                  form->contenttype_alloc = TRUE;
                  current_form = form;
                  form = NULL;
                }
              }
            }
            else
              return_value = CURL_FORMADD_NULL;
          }
          else
            return_value = CURL_FORMADD_OPTION_TWICE;
        }
        else {
          if(contenttype) {
            current_form->contenttype = strdup(contenttype);
            if(!current_form->contenttype)
              return_value = CURL_FORMADD_MEMORY;
            else
              current_form->contenttype_alloc = TRUE;
          }
          else
            return_value = CURL_FORMADD_NULL;
        }
        break;
      }
    case CURLFORM_CONTENTHEADER:
      {
        /* this "cast increases required alignment of target type" but
           we consider it OK anyway */
        struct curl_slist *list = array_state ?
          (struct curl_slist *)(void *)array_value :
          va_arg(params, struct curl_slist *);

        if(current_form->contentheader)
          return_value = CURL_FORMADD_OPTION_TWICE;
        else
          current_form->contentheader = list;

        break;
      }
    case CURLFORM_FILENAME:
    case CURLFORM_BUFFER:
      {
        const char *filename = array_state ? array_value :
          va_arg(params, char *);
        if(current_form->showfilename)
          return_value = CURL_FORMADD_OPTION_TWICE;
        else {
          current_form->showfilename = strdup(filename);
          if(!current_form->showfilename)
            return_value = CURL_FORMADD_MEMORY;
          else
            current_form->showfilename_alloc = TRUE;
        }
        break;
      }
    default:
      return_value = CURL_FORMADD_UNKNOWN_OPTION;
      break;
    }
  }

  if(CURL_FORMADD_OK != return_value) {
    /* On error, free allocated fields for all nodes of the FormInfo linked
       list without deallocating nodes. List nodes are deallocated later on */
    struct FormInfo *ptr;
    for(ptr = first_form; ptr != NULL; ptr = ptr->more) {
      if(ptr->name_alloc) {
        Curl_safefree(ptr->name);
        ptr->name_alloc = FALSE;
      }
      if(ptr->value_alloc) {
        Curl_safefree(ptr->value);
        ptr->value_alloc = FALSE;
      }
      if(ptr->contenttype_alloc) {
        Curl_safefree(ptr->contenttype);
        ptr->contenttype_alloc = FALSE;
      }
      if(ptr->showfilename_alloc) {
        Curl_safefree(ptr->showfilename);
        ptr->showfilename_alloc = FALSE;
      }
    }
  }

  if(CURL_FORMADD_OK == return_value) {
    /* go through the list, check for completeness and if everything is
     * alright add the HttpPost item otherwise set return_value accordingly */

    post = NULL;
    for(form = first_form;
        form != NULL;
        form = form->more) {
      if(((!form->name || !form->value) && !post) ||
         ( (form->contentslength) &&
           (form->flags & HTTPPOST_FILENAME) ) ||
         ( (form->flags & HTTPPOST_FILENAME) &&
           (form->flags & HTTPPOST_PTRCONTENTS) ) ||

         ( (!form->buffer) &&
           (form->flags & HTTPPOST_BUFFER) &&
           (form->flags & HTTPPOST_PTRBUFFER) ) ||

         ( (form->flags & HTTPPOST_READFILE) &&
           (form->flags & HTTPPOST_PTRCONTENTS) )
        ) {
        return_value = CURL_FORMADD_INCOMPLETE;
        break;
      }
      if(((form->flags & HTTPPOST_FILENAME) ||
          (form->flags & HTTPPOST_BUFFER)) &&
         !form->contenttype) {
        char *f = (form->flags & HTTPPOST_BUFFER) ?
          form->showfilename : form->value;
        char const *type;
        type = Curl_mime_contenttype(f);
        if(!type)
          type = prevtype;
        if(!type)
          type = FILE_CONTENTTYPE_DEFAULT;

        /* our contenttype is missing */
        form->contenttype = strdup(type);
        if(!form->contenttype) {
          return_value = CURL_FORMADD_MEMORY;
          break;
        }
        form->contenttype_alloc = TRUE;
      }
      if(form->name && form->namelength) {
        /* Name should not contain nul bytes. */
        size_t i;
        for(i = 0; i < form->namelength; i++)
          if(!form->name[i]) {
            return_value = CURL_FORMADD_NULL;
            break;
          }
        if(return_value != CURL_FORMADD_OK)
          break;
      }
      if(!(form->flags & HTTPPOST_PTRNAME) &&
         (form == first_form) ) {
        /* Note that there is small risk that form->name is NULL here if the
           app passed in a bad combo, so we better check for that first. */
        if(form->name) {
          /* copy name (without strdup; possibly not null-terminated) */
          form->name = Curl_memdup0(form->name, form->namelength ?
                                    form->namelength :
                                    strlen(form->name));
        }
        if(!form->name) {
          return_value = CURL_FORMADD_MEMORY;
          break;
        }
        form->name_alloc = TRUE;
      }
      if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE |
                          HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER |
                          HTTPPOST_CALLBACK)) && form->value) {
        /* copy value (without strdup; possibly contains null characters) */
        size_t clen  = (size_t) form->contentslength;
        if(!clen)
          clen = strlen(form->value) + 1;

        form->value = Curl_memdup(form->value, clen);

        if(!form->value) {
          return_value = CURL_FORMADD_MEMORY;
          break;
        }
        form->value_alloc = TRUE;
      }
      post = AddHttpPost(form->name, form->namelength,
                         form->value, form->contentslength,
                         form->buffer, form->bufferlength,
                         form->contenttype, form->flags,
                         form->contentheader, form->showfilename,
                         form->userp,
                         post, httppost,
                         last_post);

      if(!post) {
        return_value = CURL_FORMADD_MEMORY;
        break;
      }

      if(form->contenttype)
        prevtype = form->contenttype;
    }
    if(CURL_FORMADD_OK != return_value) {
      /* On error, free allocated fields for nodes of the FormInfo linked
         list which are not already owned by the httppost linked list
         without deallocating nodes. List nodes are deallocated later on */
      struct FormInfo *ptr;
      for(ptr = form; ptr != NULL; ptr = ptr->more) {
        if(ptr->name_alloc) {
          Curl_safefree(ptr->name);
          ptr->name_alloc = FALSE;
        }
        if(ptr->value_alloc) {
          Curl_safefree(ptr->value);
          ptr->value_alloc = FALSE;
        }
        if(ptr->contenttype_alloc) {
          Curl_safefree(ptr->contenttype);
          ptr->contenttype_alloc = FALSE;
        }
        if(ptr->showfilename_alloc) {
          Curl_safefree(ptr->showfilename);
          ptr->showfilename_alloc = FALSE;
        }
      }
    }
  }

  /* Always deallocate FormInfo linked list nodes without touching node
     fields given that these have either been deallocated or are owned
     now by the httppost linked list */
  while(first_form) {
    struct FormInfo *ptr = first_form->more;
    free(first_form);
    first_form = ptr;
  }

  return return_value;
}