Skip to content
Snippets Groups Projects
formdata.c 47.7 KiB
Newer Older
/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
Daniel Stenberg's avatar
Daniel Stenberg committed
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at http://curl.haxx.se/docs/copyright.html.
Daniel Stenberg's avatar
Daniel Stenberg committed
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
Daniel Stenberg's avatar
Daniel Stenberg committed
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
Daniel Stenberg's avatar
Daniel Stenberg committed
 * $Id$
 ***************************************************************************/
Daniel Stenberg's avatar
Daniel Stenberg committed

/*
  Debug the form generator stand-alone by compiling this source file with:

  gcc -DHAVE_CONFIG_H -I../ -g -D_FORM_DEBUG -o formdata -I../include formdata.c strequal.c

  run the 'formdata' executable the output should end with:
  All Tests seem to have worked ...
  and the following parts should be there:

Content-Disposition: form-data; name="simple_COPYCONTENTS"
value for simple COPYCONTENTS

Content-Disposition: form-data; name="COPYCONTENTS_+_CONTENTTYPE"
Content-Type: image/gif
value for COPYCONTENTS + CONTENTTYPE

Content-Disposition: form-data; name="PRNAME_+_NAMELENGTH_+_COPYNAME_+_CONTENTSLENGTH"
vlue for PTRNAME + NAMELENGTH + COPYNAME + CONTENTSLENGTH
(or you might see P^@RNAME and v^@lue at the start)

Content-Disposition: form-data; name="simple_PTRCONTENTS"
value for simple PTRCONTENTS

Content-Disposition: form-data; name="PTRCONTENTS_+_CONTENTSLENGTH"
vlue for PTRCONTENTS + CONTENTSLENGTH
(or you might see v^@lue at the start)

Content-Disposition: form-data; name="PTRCONTENTS_+_CONTENTSLENGTH_+_CONTENTTYPE"
Content-Type: text/plain
vlue for PTRCOTNENTS + CONTENTSLENGTH + CONTENTTYPE
(or you might see v^@lue at the start)

Content-Disposition: form-data; name="FILE1_+_CONTENTTYPE"; filename="inet_ntoa_r.h"
Content-Type: text/html
...

Content-Disposition: form-data; name="FILE1_+_FILE2"
Content-Type: multipart/mixed, boundary=curlz1s0dkticx49MV1KGcYP5cvfSsz
Content-Disposition: attachment; filename="inet_ntoa_r.h"
Content-Type: text/plain
...
Content-Disposition: attachment; filename="Makefile.b32.resp"
Content-Type: text/plain
...

Content-Disposition: form-data; name="FILE1_+_FILE2_+_FILE3"
Content-Type: multipart/mixed, boundary=curlirkYPmPwu6FrJ1vJ1u1BmtIufh1
...
Content-Disposition: attachment; filename="inet_ntoa_r.h"
Content-Type: text/plain
...
Content-Disposition: attachment; filename="Makefile.b32.resp"
Content-Type: text/plain
...
Content-Disposition: attachment; filename="inet_ntoa_r.h"
Content-Type: text/plain

Content-Disposition: form-data; name="ARRAY: FILE1_+_FILE2_+_FILE3"
Content-Type: multipart/mixed, boundary=curlirkYPmPwu6FrJ1vJ1u1BmtIufh1
...
Content-Disposition: attachment; filename="inet_ntoa_r.h"
Content-Type: text/plain
...
Content-Disposition: attachment; filename="Makefile.b32.resp"
Content-Type: text/plain
...
Content-Disposition: attachment; filename="inet_ntoa_r.h"
Content-Type: text/plain
...

Content-Disposition: form-data; name="FILECONTENT"
...

#include <curl/curl.h>
/* Length of the random boundary string. */
#define BOUNDARY_LENGTH 40

#if !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY)
Daniel Stenberg's avatar
Daniel Stenberg committed
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME)
Daniel Stenberg's avatar
Daniel Stenberg committed
#include "formdata.h"
#include "strequal.h"
#include "memory.h"

#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
/* The last #include file should be: */
#include "memdebug.h"

#endif  /* !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) */

#ifndef CURL_DISABLE_HTTP

#if defined(HAVE_BASENAME) && defined(NEED_BASENAME_PROTO)
/* This system has a basename() but no prototype for it! */
char *basename(char *path);
#endif

static size_t readfromfile(struct Form *form, char *buffer, size_t size);

Daniel Stenberg's avatar
Daniel Stenberg committed
/* What kind of Content-Type to use on un-specified files with unrecognized
   extensions. */
#define HTTPPOST_CONTENTTYPE_DEFAULT "application/octet-stream"
Daniel Stenberg's avatar
Daniel Stenberg committed

#define FORM_FILE_SEPARATOR ','
#define FORM_TYPE_SEPARATOR ';'

/***************************************************************************
 *
 * AddHttpPost()
 * Adds a HttpPost structure to the list, if parent_post is given becomes
 * a subpost of parent_post instead of a direct list element.
 *
 * Returns newly allocated HttpPost on success and NULL if malloc failed.
 *
 ***************************************************************************/
AddHttpPost(char * name, size_t namelength,
            char * value, size_t contentslength,
            char * buffer, size_t bufferlength,
            char *contenttype,
            long flags,
            struct curl_slist* contentHeader,
            char *showfilename,
            struct curl_httppost *parent_post,
            struct curl_httppost **httppost,
            struct curl_httppost **last_post)
  post = (struct curl_httppost *)calloc(sizeof(struct curl_httppost), 1);
    post->namelength = (long)(name?(namelength?namelength:strlen(name)):0);
    post->contentslength = (long)contentslength;
    post->bufferlength = (long)bufferlength;
    post->contenttype = contenttype;
    post->contentheader = contentHeader;
    post->flags = flags;
  }
  else
    return NULL;
  if (parent_post) {
    /* now, point our 'more' to the original 'more' */
    post->more = parent_post->more;
    /* then move the original 'more' to point to ourselves */
    parent_post->more = post;
  }
  else {
    /* make the previous point to this */
    if(*last_post)
      (*last_post)->next = post;
    else
      (*httppost) = post;

    (*last_post) = post;
/***************************************************************************
 *
 * AddFormInfo()
 * Adds a FormInfo structure to the list presented by parent_form_info.
 *
 * Returns newly allocated FormInfo on success and NULL if malloc failed/
 * parent_form_info is NULL.
 *
 ***************************************************************************/
static FormInfo * AddFormInfo(char *value,
                              char *contenttype,
                              FormInfo *parent_form_info)
{
  FormInfo *form_info;
  form_info = (FormInfo *)malloc(sizeof(FormInfo));
  if(form_info) {
    memset(form_info, 0, sizeof(FormInfo));
    if (value)
      form_info->value = value;
    if (contenttype)
      form_info->contenttype = contenttype;
    form_info->flags = HTTPPOST_FILENAME;
  }
  else
    return NULL;
  if (parent_form_info) {
    /* now, point our 'more' to the original 'more' */
    form_info->more = parent_form_info->more;
    /* then move the original 'more' to point to ourselves */
    parent_form_info->more = form_info;
  }
  else
    return NULL;

  return form_info;
}

/***************************************************************************
 *
 * ContentTypeForFilename()
 * Provides content type for filename if one of the known types (else
 * (either the prevtype or the default is returned).
 *
 * Returns some valid contenttype for filename.
 *
 ***************************************************************************/
static const char * ContentTypeForFilename (const char *filename,
                                            const char *prevtype)
{
  const char *contenttype = NULL;
  unsigned int i;
  /*
   * No type was specified, we scan through a few well-known
   * extensions and pick the first we match!
   */
  struct ContentType {
    const char *extension;
    const char *type;
  };
  static const struct ContentType ctts[]={
    {".gif",  "image/gif"},
    {".jpg",  "image/jpeg"},
    {".jpeg", "image/jpeg"},
    {".txt",  "text/plain"},
    {".html", "text/html"}
  if(prevtype)
    /* default to the previously set/used! */
    contenttype = prevtype;
  else
    /* It seems RFC1867 defines no Content-Type to default to
       text/plain so we don't actually need to set this: */
    contenttype = HTTPPOST_CONTENTTYPE_DEFAULT;
  for(i=0; i<sizeof(ctts)/sizeof(ctts[0]); i++) {
    if(strlen(filename) >= strlen(ctts[i].extension)) {
      if(strequal(filename +
                  strlen(filename) - strlen(ctts[i].extension),
                  ctts[i].extension)) {
        contenttype = ctts[i].type;
        break;
    }
  }
  /* we have a contenttype by now */
  return contenttype;
}

/***************************************************************************
 *
 * Copies the 'source' data to a newly allocated buffer buffer (that is
 * returned). Uses buffer_length if not null, else uses strlen to determine
 * the length of the buffer to be copied
 * Returns the new pointer or NULL on failure.
 *
 ***************************************************************************/
static char *memdup(const char *src, size_t buffer_length)
  size_t length;
  bool add = FALSE;
  if (buffer_length)
    length = buffer_length;
    length = strlen(src);
  buffer = (char*)malloc(length+add);
  if (!buffer)
    return NULL; /* fail */
  memcpy(buffer, src, length);

  /* if length unknown do null termination */
  if (add)
    buffer[length] = '\0';

  return buffer;
/***************************************************************************
 *
 * FormAdd()
 * Stores a formpost parameter and builds the appropriate linked list.
 *
 * Has two principal functionalities: using files and byte arrays as
 * post parts. Byte arrays are either copied or just the pointer is stored
 * (as the user requests) while for files only the filename and not the
 * content is stored.
 *
 * While you may have only one byte array for each name, multiple filenames
 * are allowed (and because of this feature CURLFORM_END is needed after
 * using CURLFORM_FILE).
 *
 * Examples:
 *
 * Simple name/value pair with copied contents:
 * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
 * CURLFORM_COPYCONTENTS, "value", CURLFORM_END);
 *
 * name/value pair where only the content pointer is remembered:
 * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
 * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10, CURLFORM_END);
 * (if CURLFORM_CONTENTSLENGTH is missing strlen () is used)
 *
 * storing a filename (CONTENTTYPE is optional!):
 * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
 * CURLFORM_FILE, "filename1", CURLFORM_CONTENTTYPE, "plain/text",
 * CURLFORM_END);
 *
 * storing multiple filenames:
 * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
 * CURLFORM_FILE, "filename1", CURLFORM_FILE, "filename2", CURLFORM_END);
 *
 * CURL_FORMADD_OK             on success
 * CURL_FORMADD_MEMORY         if the FormInfo allocation fails
 * CURL_FORMADD_OPTION_TWICE   if one option is given twice for one Form
 * CURL_FORMADD_NULL           if a null pointer was given for a char
 * CURL_FORMADD_MEMORY         if the allocation of a FormInfo struct failed
 * CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used
 * CURL_FORMADD_INCOMPLETE     if the some FormInfo is not complete (or an error)
 * CURL_FORMADD_MEMORY         if a HttpPost struct cannot be allocated
 * CURL_FORMADD_MEMORY         if some allocation for string copying failed.
 * CURL_FORMADD_ILLEGAL_ARRAY  if an illegal option is used in an array
 *
 ***************************************************************************/

static
CURLFORMcode FormAdd(struct curl_httppost **httppost,
                     struct curl_httppost **last_post,
                     va_list params)
  FormInfo *first_form, *current_form, *form = NULL;
  CURLFORMcode return_value = CURL_FORMADD_OK;
  const char *prevtype = 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're parsing an
     array that we got passed to us. If FALSE we're parsing the input
     va_list arguments. */
  bool array_state = FALSE;
  /*
   * We need to allocate the first struct to fill in.
   */
  first_form = (FormInfo *)calloc(sizeof(struct FormInfo), 1);
  if(!first_form)
  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 ) {
      /* get the upcoming option from the given array */
      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 */
      option = va_arg(params, CURLformoption);
      if (CURLFORM_END == option)
    }

    switch (option) {
    case CURLFORM_ARRAY:
      if(array_state)
        /* we don't 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
      break;

      /*
       * Set the Name property.
       */
    case CURLFORM_PTRNAME:
      current_form->flags |= HTTPPOST_PTRNAME; /* fall through */
    case CURLFORM_COPYNAME:
      if (current_form->name)
        return_value = CURL_FORMADD_OPTION_TWICE;
        char *name = array_state?
          array_value:va_arg(params, char *);
        if (name)
          current_form->name = name; /* store for the moment */
      }
      break;
    case CURLFORM_NAMELENGTH:
      if (current_form->namelength)
        return_value = CURL_FORMADD_OPTION_TWICE;
          array_state?(long)array_value:(long)va_arg(params, long);
      break;

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

      /* Get contents from a given file name */
    case CURLFORM_FILECONTENT:
      if (current_form->flags != 0)
        return_value = CURL_FORMADD_OPTION_TWICE;
        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;
            current_form->flags |= HTTPPOST_READFILE;
            current_form->value_alloc = TRUE;
          }
      }
      break;

      /* We upload a file */
    case CURLFORM_FILE:
      {
        char *filename = array_state?array_value:
          va_arg(params, char *);
        if (current_form->value) {
          if (current_form->flags & HTTPPOST_FILENAME) {
            if (filename) {
              if ((current_form = AddFormInfo(strdup(filename),
                                              NULL, current_form)) == NULL)
            return_value = CURL_FORMADD_OPTION_TWICE;
            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;
            }
          }

    case CURLFORM_BUFFER:
      {
        char *filename = array_state?array_value:
          va_arg(params, char *);

        if (current_form->value) {
          if (current_form->flags & HTTPPOST_BUFFER) {
            if (filename) {
              if ((current_form = AddFormInfo(strdup(filename),
                                              NULL, current_form)) == NULL)
                return_value = CURL_FORMADD_MEMORY;
            }
            else
              return_value = CURL_FORMADD_NULL;
          }
          else
            return_value = CURL_FORMADD_OPTION_TWICE;
        }
        else {
            current_form->value = strdup(filename);
            if(!current_form->value)
              return_value = CURL_FORMADD_MEMORY;
          }
          else
            return_value = CURL_FORMADD_NULL;
          current_form->flags |= HTTPPOST_BUFFER;
        }
        break;
      }
    case CURLFORM_BUFFERPTR:
        current_form->flags |= HTTPPOST_PTRBUFFER;
      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 */
        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?(long)array_value:va_arg(params, long);
      break;

    case CURLFORM_CONTENTTYPE:
      {
          array_state?array_value:va_arg(params, char *);
        if (current_form->contenttype) {
          if (current_form->flags & HTTPPOST_FILENAME) {
              if ((current_form = AddFormInfo(NULL,
                                              strdup(contenttype),
                                              current_form)) == NULL)
            else
              return_value = CURL_FORMADD_NULL;
            return_value = CURL_FORMADD_OPTION_TWICE;
          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;
        }
    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*)array_value:
          va_arg(params, struct curl_slist*);
        if( current_form->contentheader )
          return_value = CURL_FORMADD_OPTION_TWICE;
        else
          current_form->contentheader = list;
        char *filename = array_state?array_value:
          va_arg(params, char *);
        if( current_form->showfilename )
          return_value = CURL_FORMADD_OPTION_TWICE;
          current_form->showfilename = strdup(filename);
          if(!current_form->showfilename)
            return_value = CURL_FORMADD_MEMORY;
          else
            current_form->showfilename_alloc = TRUE;
        }
      return_value = CURL_FORMADD_UNKNOWN_OPTION;
  if(CURL_FORMADD_OK == return_value) {
    /* go through the list, check for copleteness 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;
        if ( ((form->flags & HTTPPOST_FILENAME) ||
              (form->flags & HTTPPOST_BUFFER)) &&
             !form->contenttype ) {
          /* our contenttype is missing */
          form->contenttype
            = strdup(ContentTypeForFilename(form->value, prevtype));
          if(!form->contenttype) {
            return_value = CURL_FORMADD_MEMORY;
            break;
          }
          form->contenttype_alloc = TRUE;
        }
        if ( !(form->flags & HTTPPOST_PTRNAME) &&
             (form == first_form) ) {
          /* copy name (without strdup; possibly contains null characters) */
          form->name = memdup(form->name, form->namelength);
          if (!form->name) {
          form->name_alloc = TRUE;
        }
        if ( !(form->flags & HTTPPOST_FILENAME) &&
             !(form->flags & HTTPPOST_READFILE) &&
             !(form->flags & HTTPPOST_PTRCONTENTS) &&
             !(form->flags & HTTPPOST_PTRBUFFER) ) {
          /* copy value (without strdup; possibly contains null characters) */
          form->value = memdup(form->value, form->contentslength);
          if (!form->value) {
          form->value_alloc = TRUE;
        post = AddHttpPost(form->name, form->namelength,
                           form->value, form->contentslength,
                           form->contenttype, form->flags,
                           form->contentheader, form->showfilename,
                           post, httppost,
                           last_post);
        if (form->contenttype)
          prevtype = form->contenttype;
    /* we return on error, free possibly allocated fields */
    if(!form)
      form = current_form;
    if(form) {
      if(form->name_alloc)
        free(form->name);
      if(form->value_alloc)
        free(form->value);
      if(form->contenttype_alloc)
        free(form->contenttype);
      if(form->showfilename_alloc)
        free(form->showfilename);
    }
  /* always delete the allocated memory before returning */
  form = first_form;
  while (form != NULL) {
    FormInfo *delete_form;
    delete_form = form;
    form = form->more;
    free (delete_form);
  }
/*
 * curl_formadd() is a public API to add a section to the multipart formpost.
 */

CURLFORMcode curl_formadd(struct curl_httppost **httppost,
  va_start(arg, last_post);
  result = FormAdd(httppost, last_post, arg);
  va_end(arg);
  return result;
}

/*
 * AddFormData() adds a chunk of data to the FormData linked list.
 *
 * size is incremented by the chunk length, unless it is NULL
static CURLcode AddFormData(struct FormData **formp,
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  struct FormData *newform = (struct FormData *)
    malloc(sizeof(struct FormData));
  if (!newform)
    return CURLE_OUT_OF_MEMORY;
Daniel Stenberg's avatar
Daniel Stenberg committed
  newform->next = NULL;

  /* we make it easier for plain strings: */
  if(!length)
    length = strlen((char *)line);

  newform->line = (char *)malloc(length+1);
  if (!newform->line) {
    free(newform);
    return CURLE_OUT_OF_MEMORY;
  }
  memcpy(newform->line, line, length);
Daniel Stenberg's avatar
Daniel Stenberg committed
  newform->length = length;
  newform->line[length]=0; /* zero terminate for easier debugging */
Daniel Stenberg's avatar
Daniel Stenberg committed
  if(*formp) {
    (*formp)->next = newform;
    *formp = newform;
  }
  else
    *formp = newform;

  if (size) {
    if(type == FORM_DATA)
      *size += length;
    else {
      /* Since this is a file to be uploaded here, add the size of the actual
         file */
      if(!strequal("-", newform->line)) {
        if(!stat(newform->line, &file)) {
          *size += file.st_size;
        }
      }
    }
  }
/*
 * AddFormDataf() adds printf()-style formatted data to the formdata chain.
 */
Daniel Stenberg's avatar
Daniel Stenberg committed

static CURLcode AddFormDataf(struct FormData **formp,
                             const char *fmt, ...)
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  char s[4096];
Daniel Stenberg's avatar
Daniel Stenberg committed
  va_list ap;
  va_start(ap, fmt);
Daniel Stenberg's avatar
Daniel Stenberg committed
  va_end(ap);

  return AddFormData(formp, FORM_DATA, s, 0, size);
/*
 * Curl_formclean() is used from http.c, this cleans a built FormData linked
 * list
void Curl_formclean(struct FormData *form)
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  struct FormData *next;
Daniel Stenberg's avatar
Daniel Stenberg committed
  do {
    next=form->next;  /* the following form line */
    free(form->line); /* free the line */
    free(form);       /* free the struct */
  } while ((form = next) != NULL); /* continue */
/*
 * curl_formget()
 * Serialize a curl_httppost struct.
 * Returns 0 on success.
 */
int curl_formget(struct curl_httppost *form, void *arg,
                 curl_formget_callback append)
{
Yang Tse's avatar
Yang Tse committed
  CURLcode rc;
  rc = Curl_getFormData(&data, form, NULL, &size);
  if (rc != CURLE_OK)
    return (int)rc;

  for (ptr = data; ptr; ptr = ptr->next) {
    if (ptr->type == FORM_FILE) {
      char buffer[8192];
      size_t read;
      struct Form temp;

      Curl_FormInit(&temp, ptr);

      do {
        read = readfromfile(&temp, buffer, sizeof(buffer));
        if ((read == (size_t) -1) || (read != append(arg, buffer, read))) {
          if (temp.fp) {
            fclose(temp.fp);
          }
          Curl_formclean(data);
          return -1;
        }
      } while (read == sizeof(buffer));
    } else {
      if (ptr->length != append(arg, ptr->line, ptr->length)) {
        Curl_formclean(data);
        return -1;
      }
    }
  }
  Curl_formclean(data);
  return 0;
}

/*
 * curl_formfree() is an external function to free up a whole form post
 * chain
 */
void curl_formfree(struct curl_httppost *form)

  if(!form)
    /* no form to free, just get out of this */
    return;

Daniel Stenberg's avatar
Daniel Stenberg committed
  do {
    next=form->next;  /* the following form line */

    /* recurse to sub-contents */
    if(form->more)
      curl_formfree(form->more);

    if( !(form->flags & HTTPPOST_PTRNAME) && form->name)
Daniel Stenberg's avatar
Daniel Stenberg committed
      free(form->name); /* free the name */
    if( !(form->flags & HTTPPOST_PTRCONTENTS) && form->contents)
Daniel Stenberg's avatar
Daniel Stenberg committed
      free(form->contents); /* free the contents */
    if(form->contenttype)
      free(form->contenttype); /* free the content type */
    if(form->showfilename)
      free(form->showfilename); /* free the faked file name */
Daniel Stenberg's avatar
Daniel Stenberg committed
    free(form);       /* free the struct */
Daniel Stenberg's avatar
Daniel Stenberg committed

  } while ((form = next) != NULL); /* continue */
#ifndef HAVE_BASENAME
/*
  (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004
  Edition)

  The basename() function shall take the pathname pointed to by path and
  return a pointer to the final component of the pathname, deleting any
  trailing '/' characters.

  If the string pointed to by path consists entirely of the '/' character,
  basename() shall return a pointer to the string "/". If the string pointed
  to by path is exactly "//", it is implementation-defined whether '/' or "//"
  is returned.

  If path is a null pointer or points to an empty string, basename() shall
  return a pointer to the string ".".

  The basename() function may modify the string pointed to by path, and may
  return a pointer to static storage that may then be overwritten by a
  subsequent call to basename().

  The basename() function need not be reentrant. A function that is not
  required to be reentrant is not required to be thread-safe.

*/
static char *basename(char *path)
{
  /* Ignore all the details above for now and make a quick and simple
     implementaion here */
  char *s1;
  char *s2;

  s1=strrchr(path, '/');
  s2=strrchr(path, '\\');

  if(s1 && s2) {
    path = (s1 > s2? s1 : s2)+1;
  }
  else if(s1)
    path = s1 + 1;
  else if(s2)
Daniel Stenberg's avatar
Daniel Stenberg committed
    path = s2 + 1;

  return path;
}
#endif

static char *strippath(char *fullfile)
{
  char *filename;
  char *base;
  filename = strdup(fullfile); /* duplicate since basename() may ruin the
                                  buffer it works on */
  if(!filename)
    return NULL;
  base = strdup(basename(filename));

  free(filename); /* free temporary buffer */

  return base; /* returns an allocated string! */
}

/*
 * Curl_getFormData() converts a linked list of "meta data" into a complete
 * (possibly huge) multipart formdata. The input list is in 'post', while the
 * output resulting linked lists gets stored in '*finalform'. *sizep will get
 * the total size of the whole POST.
 * A multipart/form_data content-type is built, unless a custom content-type
 * is passed in 'custom_content_type'.
CURLcode Curl_getFormData(struct FormData **finalform,
                          struct curl_httppost *post,
                          curl_off_t *sizep)
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  struct FormData *form = NULL;
  struct FormData *firstform;
Daniel Stenberg's avatar
Daniel Stenberg committed

  curl_off_t size=0; /* support potentially ENORMOUS formposts */
Daniel Stenberg's avatar
Daniel Stenberg committed
  char *boundary;
  char *fileboundary=NULL;
  *finalform=NULL; /* default form is empty */
Daniel Stenberg's avatar
Daniel Stenberg committed

  if(!post)
    return result; /* no input => no output! */
Daniel Stenberg's avatar
Daniel Stenberg committed

  if(!boundary)
    return CURLE_OUT_OF_MEMORY;
Daniel Stenberg's avatar
Daniel Stenberg committed
  /* Make the first line of the output */
  result = AddFormDataf(&form, NULL,
                        "%s; boundary=%s\r\n",
                        custom_content_type?custom_content_type:
                        "Content-Type: multipart/form-data",
  /* we DO NOT include that line in the total size of the POST, since it'll be
     part of the header! */
Daniel Stenberg's avatar
Daniel Stenberg committed

  firstform = form;
Daniel Stenberg's avatar
Daniel Stenberg committed
  do {

    if(size) {
      result = AddFormDataf(&form, &size, "\r\n");
Daniel Stenberg's avatar
Daniel Stenberg committed
    /* boundary */
    result = AddFormDataf(&form, &size, "--%s\r\n", boundary);
Daniel Stenberg's avatar
Daniel Stenberg committed

    /* Maybe later this should be disabled when a custom_content_type is
       passed, since Content-Disposition is not meaningful for all multipart
       types.
    */
    result = AddFormDataf(&form, &size,
                          "Content-Disposition: form-data; name=\"");
    result = AddFormData(&form, FORM_DATA, post->name, post->namelength,
                         &size);
    result = AddFormDataf(&form, &size, "\"");
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(post->more) {
      /* If used, this is a link to more file names, we must then do
         the magic to include several files with the same field name */

      fileboundary = Curl_FormBoundary();
Daniel Stenberg's avatar
Daniel Stenberg committed

      result = AddFormDataf(&form, &size,
                            "\r\nContent-Type: multipart/mixed,"
                            " boundary=%s\r\n",
                            fileboundary);
      if (result)
Daniel Stenberg's avatar
Daniel Stenberg committed
    }

    file = post;

    do {

      /* If 'showfilename' is set, that is a faked name passed on to us
         to use to in the formpost. If that is not set, the actually used
         local file name should be added. */

Daniel Stenberg's avatar
Daniel Stenberg committed
      if(post->more) {
        char *filebasename=
          (!file->showfilename)?strippath(file->contents):NULL;

        result = AddFormDataf(&form, &size,
                              "\r\n--%s\r\nContent-Disposition: "
                              "attachment; filename=\"%s\"",
                              fileboundary,
                              (file->showfilename?file->showfilename:
                               filebasename));
        if (filebasename)
          free(filebasename);
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
      else if((post->flags & HTTPPOST_FILENAME) ||
              (post->flags & HTTPPOST_BUFFER)) {

        char *filebasename=
          (!post->showfilename)?strippath(post->contents):NULL;

        result = AddFormDataf(&form, &size,
                              "; filename=\"%s\"",
                              (post->showfilename?post->showfilename:
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(file->contenttype) {
        result = AddFormDataf(&form, &size,
                              "\r\nContent-Type: %s",
                              file->contenttype);
        if (result)
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
      curList = file->contentheader;
      while( curList ) {
        /* Process the additional headers specified for this form */
        result = AddFormDataf( &form, &size, "\r\n%s", curList->data );
        Curl_formclean(firstform);
        free(boundary);
        return result;
      }
#if 0
      /* The header Content-Transfer-Encoding: seems to confuse some receivers
       * (like the built-in PHP engine). While I can't see any reason why it
       * should, I can just as well skip this to the benefit of the users who
       * are using such confused receivers.
       */
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(file->contenttype &&
         !checkprefix("text/", file->contenttype)) {
        /* this is not a text content, mention our binary encoding */
        size += AddFormData(&form, "\r\nContent-Transfer-Encoding: binary", 0);
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
Daniel Stenberg's avatar
Daniel Stenberg committed

      result = AddFormDataf(&form, &size, "\r\n\r\n");
Daniel Stenberg's avatar
Daniel Stenberg committed

      if((post->flags & HTTPPOST_FILENAME) ||
         (post->flags & HTTPPOST_READFILE)) {
        /* we should include the contents from the specified file */
        FILE *fileread;
Daniel Stenberg's avatar
Daniel Stenberg committed

        fileread = strequal("-", file->contents)?
          stdin:fopen(file->contents, "rb"); /* binary read for win32  */
        /*
         * VMS: This only allows for stream files on VMS.  Stream files are
         * OK, as are FIXED & VAR files WITHOUT implied CC For implied CC,
         * every record needs to have a \n appended & 1 added to SIZE
         */

          if(fileread != stdin) {
            /* close the file again */
            /* add the file name only - for later reading from this */
            result = AddFormData(&form, FORM_FILE, file->contents, 0, &size);
          }
          else {
            /* When uploading from stdin, we can't know the size of the file,
             * thus must read the full file as before. We *could* use chunked
             * transfer-encoding, but that only works for HTTP 1.1 and we
             * can't be sure we work with such a server.
             */
            size_t nread;
            char buffer[512];
            while ((nread = fread(buffer, 1, sizeof(buffer), fileread)) != 0) {
              result = AddFormData(&form, FORM_DATA, buffer, nread, &size);
              if (result)
                break;
            }
          }

            Curl_formclean(firstform);
            free(boundary);
            return result;
          }
          Curl_formclean(firstform);
          free(boundary);
          *finalform = NULL;
          return CURLE_READ_ERROR;
      else if (post->flags & HTTPPOST_BUFFER) {
        /* include contents of buffer */
        result = AddFormData(&form, FORM_DATA, post->buffer,
                             post->bufferlength, &size);
        result = AddFormData(&form, FORM_DATA, post->contents,
                             post->contentslength, &size);
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
    } while ((file = file->more) != NULL); /* for each specified file for this field */
      Curl_formclean(firstform);
      free(boundary);
      return result;
    }
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(post->more) {
      /* this was a multiple-file inclusion, make a termination file
         boundary: */
      result = AddFormDataf(&form, &size,
                           fileboundary);
Daniel Stenberg's avatar
Daniel Stenberg committed
      free(fileboundary);
  } while ((post = post->next) != NULL); /* for each field */
    Curl_formclean(firstform);
    free(boundary);
    return result;
  }
Daniel Stenberg's avatar
Daniel Stenberg committed

  /* end-boundary for everything */
  result = AddFormDataf(&form, &size,
    Curl_formclean(firstform);
    free(boundary);
    return result;
  }
Daniel Stenberg's avatar
Daniel Stenberg committed

  *sizep = size;

  free(boundary);

/*
 * Curl_FormInit() inits the struct 'form' points to with the 'formdata'
 * and resets the 'sent' counter.
 */
int Curl_FormInit(struct Form *form, struct FormData *formdata )
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  if(!formdata)
    return 1; /* error */
  form->data = formdata;
  form->sent = 0;
static size_t readfromfile(struct Form *form, char *buffer, size_t size)
{
  size_t nread;
  if(!form->fp) {
    /* this file hasn't yet been opened */
    form->fp = fopen(form->data->line, "rb"); /* b is for binary */
    if(!form->fp)
      return (size_t)-1; /* failure */
  }
  nread = fread(buffer, 1, size, form->fp);

  if(nread != size) {
    /* this is the last chunk from the file, move on */
    fclose(form->fp);
    form->fp = NULL;
    form->data = form->data->next;
  }

  return nread;
}

/*
 * Curl_FormReader() is the fread() emulation function that will be used to
 * deliver the formdata to the transfer loop and then sent away to the peer.
 */
size_t Curl_FormReader(char *buffer,
                       size_t size,
                       size_t nitems,
                       FILE *mydata)
  size_t wantedsize;
  size_t gotsize = 0;

  form=(struct Form *)mydata;

  wantedsize = size * nitems;

  if(!form->data)
  if(form->data->type == FORM_FILE) {
    gotsize = readfromfile(form, buffer, wantedsize);
    if(gotsize)
      /* If positive or -1, return. If zero, continue! */
      return gotsize;
  }
    if( (form->data->length - form->sent ) > wantedsize - gotsize) {

      memcpy(buffer + gotsize , form->data->line + form->sent,
             wantedsize - gotsize);

      form->sent += wantedsize-gotsize;

      return wantedsize;
    }

    memcpy(buffer+gotsize,
           form->data->line + form->sent,
           (form->data->length - form->sent) );
    gotsize += form->data->length - form->sent;
    form->sent = 0;

    form->data = form->data->next; /* advance */

  } while(form->data && (form->data->type == FORM_DATA));
  /* If we got an empty line and we have more data, we proceed to the next
     line immediately to avoid returning zero before we've reached the end.
     This is the bug reported November 22 1999 on curl 6.3. (Daniel) */

  return gotsize;
}

/*
 * Curl_formpostheader() returns the first line of the formpost, the
 * request-header part (which is not part of the request-body like the rest of
 * the post).
 */
char *Curl_formpostheader(void *formp, size_t *len)
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  char *header;
  struct Form *form=(struct Form *)formp;
Daniel Stenberg's avatar
Daniel Stenberg committed

  if(!form->data)
Daniel Stenberg's avatar
Daniel Stenberg committed

  header = form->data->line;
  *len = form->data->length;
Daniel Stenberg's avatar
Daniel Stenberg committed

  form->data = form->data->next; /* advance */
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed
}


#ifdef _FORM_DEBUG
int FormAddTest(const char * errormsg,
                 struct curl_httppost **httppost,
                 struct curl_httppost **last_post,
                 ...)
{
  int result;
  va_list arg;
  va_start(arg, last_post);
  if ((result = FormAdd(httppost, last_post, arg)))
    fprintf (stderr, "ERROR doing FormAdd ret: %d action: %s\n", result,
             errormsg);
  va_end(arg);
  return result;
}


int main()
{
  char name1[] = "simple_COPYCONTENTS";
  char name2[] = "COPYCONTENTS_+_CONTENTTYPE";
  char name3[] = "PTRNAME_+_NAMELENGTH_+_COPYNAME_+_CONTENTSLENGTH";
  char name4[] = "simple_PTRCONTENTS";
  char name5[] = "PTRCONTENTS_+_CONTENTSLENGTH";
  char name6[] = "PTRCONTENTS_+_CONTENTSLENGTH_+_CONTENTTYPE";
  char name7[] = "FILE1_+_CONTENTTYPE";
  char name8[] = "FILE1_+_FILE2";
  char name9[] = "FILE1_+_FILE2_+_FILE3";
  char name10[] = "ARRAY: FILE1_+_FILE2_+_FILE3";
  char name11[] = "FILECONTENT";
  char value1[] = "value for simple COPYCONTENTS";
  char value2[] = "value for COPYCONTENTS + CONTENTTYPE";
  char value3[] = "value for PTRNAME + NAMELENGTH + COPYNAME + CONTENTSLENGTH";
  char value4[] = "value for simple PTRCONTENTS";
  char value5[] = "value for PTRCONTENTS + CONTENTSLENGTH";
  char value6[] = "value for PTRCOTNENTS + CONTENTSLENGTH + CONTENTTYPE";
  char value7[] = "inet_ntoa_r.h";
  char value8[] = "Makefile.b32.resp";
  char type2[] = "image/gif";
  char type6[] = "text/plain";
  char type7[] = "text/html";
  int name3length = strlen(name3);
  int value3length = strlen(value3);
  int value5length = strlen(value4);
  int value6length = strlen(value5);
  struct curl_httppost *httppost=NULL;
  struct curl_httppost *last_post=NULL;
  struct curl_forms forms[4];

  struct FormData *form;
  struct Form formread;

  if (FormAddTest("simple COPYCONTENTS test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name1, CURLFORM_COPYCONTENTS, value1,
                  CURLFORM_END))
    ++errors;
  if (FormAddTest("COPYCONTENTS  + CONTENTTYPE test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name2, CURLFORM_COPYCONTENTS, value2,
                  CURLFORM_CONTENTTYPE, type2, CURLFORM_END))
    ++errors;
  /* make null character at start to check that contentslength works
     correctly */
  name3[1] = '\0';
  value3[1] = '\0';
  if (FormAddTest("PTRNAME + NAMELENGTH + COPYNAME + CONTENTSLENGTH test",
                  &httppost, &last_post,
                  CURLFORM_PTRNAME, name3, CURLFORM_COPYCONTENTS, value3,
                  CURLFORM_CONTENTSLENGTH, value3length,
                  CURLFORM_NAMELENGTH, name3length, CURLFORM_END))
  if (FormAddTest("simple PTRCONTENTS test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name4, CURLFORM_PTRCONTENTS, value4,
                  CURLFORM_END))
    ++errors;
  /* make null character at start to check that contentslength works
     correctly */
  if (FormAddTest("PTRCONTENTS + CONTENTSLENGTH test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name5, CURLFORM_PTRCONTENTS, value5,
                  CURLFORM_CONTENTSLENGTH, value5length, CURLFORM_END))
    ++errors;
  /* make null character at start to check that contentslength works
     correctly */
  if (FormAddTest("PTRCONTENTS + CONTENTSLENGTH + CONTENTTYPE test",
                  &httppost, &last_post,
                  CURLFORM_COPYNAME, name6, CURLFORM_PTRCONTENTS, value6,
                  CURLFORM_CONTENTSLENGTH, value6length,
                  CURLFORM_CONTENTTYPE, type6, CURLFORM_END))
    ++errors;
  if (FormAddTest("FILE + CONTENTTYPE test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name7, CURLFORM_FILE, value7,
                  CURLFORM_CONTENTTYPE, type7, CURLFORM_END))
    ++errors;
  if (FormAddTest("FILE1 + FILE2 test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name8, CURLFORM_FILE, value7,
                  CURLFORM_FILE, value8, CURLFORM_END))
    ++errors;
  if (FormAddTest("FILE1 + FILE2 + FILE3 test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name9, CURLFORM_FILE, value7,
                  CURLFORM_FILE, value8, CURLFORM_FILE, value7, CURLFORM_END))
  forms[0].option = CURLFORM_FILE;
  forms[0].value  = value7;
  forms[1].option = CURLFORM_FILE;
  forms[1].value  = value8;
  forms[2].option = CURLFORM_FILE;
  forms[2].value  = value7;
  forms[3].option  = CURLFORM_END;
  if (FormAddTest("FILE1 + FILE2 + FILE3 ARRAY test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name10, CURLFORM_ARRAY, forms,
                  CURLFORM_END))
    ++errors;
  if (FormAddTest("FILECONTENT test", &httppost, &last_post,
                  CURLFORM_COPYNAME, name11, CURLFORM_FILECONTENT, value7,
                  CURLFORM_END))
    ++errors;

  form=Curl_getFormData(httppost, &size);

  Curl_FormInit(&formread, form);

  do {
    nread = Curl_FormReader(buffer, 1, sizeof(buffer),
                            (FILE *)&formread);

    if(-1 == nread)
      break;
    fwrite(buffer, nread, 1, stdout);
  fprintf(stdout, "size: %d\n", size);
    fprintf(stdout, "\n==> %d Test(s) failed!\n", errors);
  else
    fprintf(stdout, "\nAll Tests seem to have worked (please check output)\n");

  return 0;
}

#else  /* CURL_DISABLE_HTTP */
CURLFORMcode curl_formadd(struct curl_httppost **httppost,
                          struct curl_httppost **last_post,
                          ...)
{
  (void)httppost;
  (void)last_post;
  return CURL_FORMADD_DISABLED;
}

int curl_formget(struct curl_httppost *form, void *arg,
                 curl_formget_callback append)
void curl_formfree(struct curl_httppost *form)
{
  (void)form;
  /* does nothing HTTP is disabled */
}

#if !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY)

/*
 * Curl_FormBoundary() creates a suitable boundary string and returns an
 * allocated one. This is also used by SSL-code so it must be present even
 * if HTTP is disabled!
 */
char *Curl_FormBoundary(void)
{
  char *retstring;
  static int randomizer;   /* this is just so that two boundaries within
                              the same form won't be identical */
  static const char table16[]="abcdef0123456789";

  retstring = (char *)malloc(BOUNDARY_LENGTH+1);

  if(!retstring)
    return NULL; /* failed */

  srand((unsigned int)time(NULL)+randomizer++); /* seed */

  strcpy(retstring, "----------------------------");

  for(i=strlen(retstring); i<BOUNDARY_LENGTH; i++)
    retstring[i] = table16[rand()%16];

  /* 28 dashes and 12 hexadecimal digits makes 12^16 (184884258895036416)
     combinations */
  retstring[BOUNDARY_LENGTH]=0; /* zero terminate */

  return retstring;
}

#endif  /* !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) */