Skip to content
Snippets Groups Projects
formdata.c 15.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Daniel Stenberg's avatar
    Daniel Stenberg committed
    /*****************************************************************************
     *                                  _   _ ____  _     
     *  Project                     ___| | | |  _ \| |    
     *                             / __| | | | |_) | |    
     *                            | (__| |_| |  _ <| |___ 
     *                             \___|\___/|_| \_\_____|
     *
     *  The contents of this file are subject to the Mozilla Public License
     *  Version 1.0 (the "License"); you may not use this file except in
     *  compliance with the License. You may obtain a copy of the License at
     *  http://www.mozilla.org/MPL/
     *
     *  Software distributed under the License is distributed on an "AS IS"
     *  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
     *  License for the specific language governing rights and limitations
     *  under the License.
     *
     *  The Original Code is Curl.
     *
     *  The Initial Developer of the Original Code is Daniel Stenberg.
     *
     *  Portions created by the Initial Developer are Copyright (C) 1998.
     *  All Rights Reserved.
     *
     * ------------------------------------------------------------
     * Main author:
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     * - Daniel Stenberg <daniel@haxx.se>
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     *
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     * 	http://curl.haxx.se
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
     *
     * $Source$
     * $Revision$
     * $Date$
     * $Author$
     * $State$
     * $Locker$
     *
     * ------------------------------------------------------------
     ****************************************************************************/
    
    /*
      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
    
      run the 'formdata' executable and make sure the output is ok!
    
      try './formdata "name=Daniel" "poo=noo" "foo=bar"' and similarly
    
     */
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdarg.h>
    
    #include <time.h>
    
    #include <curl/curl.h>
    #include "formdata.h"
    
    
    #include "strequal.h"
    
    
    /* The last #include file should be: */
    #ifdef MALLOCDEBUG
    #include "memdebug.h"
    #endif
    
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    /* Length of the random boundary string. The risk of this being used
       in binary data is very close to zero, 64^32 makes
       6277101735386680763835789423207666416102355444464034512896
       combinations... */
    #define BOUNDARY_LENGTH 32
    
    /* What kind of Content-Type to use on un-specified files with unrecognized
       extensions. */
    #define HTTPPOST_CONTENTTYPE_DEFAULT "text/plain"
    
    /* This is a silly duplicate of the function in main.c to enable this source
       to compile stand-alone for better debugging */
    static void GetStr(char **string,
    		   char *value)
    {
      if(*string)
        free(*string);
      *string = strdup(value);
    }
    
    /***************************************************************************
     *
     * FormParse()
     *	
     * Reads a 'name=value' paramter and builds the appropriate linked list.
     *
     * Specify files to upload with 'name=@filename'. Supports specified
     * given Content-Type of the files. Such as ';type=<content-type>'.
     *
     * You may specify more than one file for a single name (field). Specify
     * multiple files by writing it like:
     *
     * 'name=@filename,filename2,filename3'
     *
     * If you want content-types specified for each too, write them like:
     *
     * 'name=@filename;type=image/gif,filename2,filename3'
     *
     ***************************************************************************/
    
    
    int curl_formparse(char *input,
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
                       struct HttpPost **httppost,
                       struct HttpPost **last_post)
    {
      return FormParse(input, httppost, last_post);
    }
    
    #define FORM_FILE_SEPARATOR ','
    #define FORM_TYPE_SEPARATOR ';'
    
    int FormParse(char *input,
    	      struct HttpPost **httppost,
    	      struct HttpPost **last_post)
    {
      /* nextarg MUST be a string in the format 'name=contents' and we'll
         build a linked list with the info */
      char name[256];
    
      char contents[4096]="";
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      char major[128];
      char minor[128];
      long flags = 0;
      char *contp;
      char *type = NULL;
      char *prevtype = NULL;
      char *sep;
      char *sep2;
      struct HttpPost *post;
      struct HttpPost *subpost; /* a sub-node */
      unsigned int i;
    
    
      if(1 <= sscanf(input, "%255[^ =] = %4095[^\n]", name, contents)) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
        /* the input was using the correct format */
        contp = contents;
    
        if('@' == contp[0]) {
          /* we use the @-letter to indicate file name(s) */
          
          flags = HTTPPOST_FILENAME;
          contp++;
    
          post=NULL;
    
          do {
    	/* since this was a file, it may have a content-type specifier
    	   at the end too */
    
    	sep=strchr(contp, FORM_TYPE_SEPARATOR);
    	sep2=strchr(contp, FORM_FILE_SEPARATOR);
    
    	/* pick the closest */
    	if(sep2 && (sep2 < sep)) {
    	  sep = sep2;
    
    	  /* no type was specified! */
    	}
    	if(sep) {
    
    	  /* if we got here on a comma, don't do much */
    	  if(FORM_FILE_SEPARATOR != *sep)
    	    type = strstr(sep+1, "type=");
    	  else
    	    type=NULL;
    
    	  *sep=0; /* terminate file name at separator */
    
    	  if(type) {
    	    type += strlen("type=");
    	    
    	    if(2 != sscanf(type, "%127[^/]/%127[^,\n]",
    			   major, minor)) {
    	      fprintf(stderr, "Illegally formatted content-type field!\n");
    	      return 2; /* illegal content-type syntax! */
    	    }
    	    /* now point beyond the content-type specifier */
    	    sep = type + strlen(major)+strlen(minor)+1;
    
    	    /* find the following comma */
    	    sep=strchr(sep, FORM_FILE_SEPARATOR);
    	  }
    	}
    	else {
    	  type=NULL;
    	  sep=strchr(contp, FORM_FILE_SEPARATOR);
    	}
    	if(sep) {
    	  /* the next file name starts here */
    	  *sep =0;
    	  sep++;
    	}
    	if(!type) {
    	  /*
    	   * No type was specified, we scan through a few well-known
    	   * extensions and pick the first we match!
    	   */
    	  struct ContentType {
    	    char *extension;
    	    char *type;
    	  };
    	  static struct ContentType ctts[]={
    	    {".gif",  "image/gif"},
    	    {".jpg",  "image/jpeg"},
    	    {".jpeg", "image/jpeg"},
    	    {".txt",  "text/plain"},
    	    {".html", "text/plain"}
    	  };
    
    	  if(prevtype)
    	    /* default to the previously set/used! */
    	    type = prevtype;
    	  else
    	    /* It seems RFC1867 defines no Content-Type to default to
    	       text/plain so we don't actually need to set this: */
    	    type = HTTPPOST_CONTENTTYPE_DEFAULT;
    
    	  for(i=0; i<sizeof(ctts)/sizeof(ctts[0]); i++) {
    	    if(strlen(contp) >= strlen(ctts[i].extension)) {
    	      if(strequal(contp +
    			  strlen(contp) - strlen(ctts[i].extension),
    			  ctts[i].extension)) {
    		type = ctts[i].type;
    		break;
    	      }	      
    	    }
    	  }
    	  /* we have a type by now */
    	}
    
    	if(NULL == post) {
    	  /* For the first file name, we allocate and initiate the main list
    	     node */
    
    	  post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
    	  if(post) {
    	    memset(post, 0, sizeof(struct HttpPost));
    	    GetStr(&post->name, name);      /* get the name */
    	    GetStr(&post->contents, contp); /* get the contents */
    	    post->flags = flags;
    	    if(type) {
    	      GetStr(&post->contenttype, type); /* get type */
    	      prevtype=post->contenttype; /* point to the allocated string! */
    	    }
    	    /* make the previous point to this */
    	    if(*last_post)
    	      (*last_post)->next = post;
    	    else
    	      (*httppost) = post;
    
    	    (*last_post) = post;	  
    	  }
    
    	}
    	else {
    	  /* we add a file name to the previously allocated node, known as
                 'post' now */
    	  subpost =(struct HttpPost *)malloc(sizeof(struct HttpPost));
    	  if(subpost) {
    	     memset(subpost, 0, sizeof(struct HttpPost));
    	     GetStr(&subpost->name, name);      /* get the name */
    	     GetStr(&subpost->contents, contp); /* get the contents */
    	     subpost->flags = flags;
    	     if(type) {
    	       GetStr(&subpost->contenttype, type); /* get type */
    	       prevtype=subpost->contenttype; /* point to the allocated string! */
    	     }
    	     /* now, point our 'more' to the original 'more' */
    	     subpost->more = post->more;
    
    	     /* then move the original 'more' to point to ourselves */
    	     post->more = subpost;	     
    	  }
    	}
    	contp = sep; /* move the contents pointer to after the separator */
          } while(sep && *sep); /* loop if there's another file name */
        }
        else {
          post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
          if(post) {
    	memset(post, 0, sizeof(struct HttpPost));
    	GetStr(&post->name, name);      /* get the name */
    
    	if( contp[0]=='<' ) {
    	  GetStr(&post->contents, contp+1); /* get the contents */
    	  post->flags = HTTPPOST_READFILE;
    	}
    	else {
    	  GetStr(&post->contents, contp); /* get the contents */
    	  post->flags = 0;
    	}
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    	/* make the previous point to this */
    	if(*last_post)
    	  (*last_post)->next = post;
    	else
    	  (*httppost) = post;
    
    	(*last_post) = post;	  
          }
    
        }
    
      }
      else {
        fprintf(stderr, "Illegally formatted input field!\n");
        return 1;
      }
      return 0;
    }
    
    static int AddFormData(struct FormData **formp,
    			void *line,
    			long length)
    {
      struct FormData *newform = (struct FormData *)
        malloc(sizeof(struct FormData));
      newform->next = NULL;
    
      /* we make it easier for plain strings: */
      if(!length)
        length = strlen((char *)line);
    
      newform->line = (char *)malloc(length+1);
      memcpy(newform->line, line, length+1);
      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;
    
      return length;
    }
    
    
    static int AddFormDataf(struct FormData **formp,
    			 char *fmt, ...)
    {
    
      char s[4096];
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
      va_list ap;
      va_start(ap, fmt);
      vsprintf(s, fmt, ap);
      va_end(ap);
    
      return AddFormData(formp, s, 0);
    }
    
    
    char *MakeFormBoundary(void)
    {
      char *retstring;
      static int randomizer=0; /* this is just so that two boundaries within
    			      the same form won't be identical */
      int i;
    
      static char table64[]=
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
      retstring = (char *)malloc(BOUNDARY_LENGTH);
    
      if(!retstring)
        return NULL; /* failed */
    
      srand(time(NULL)+randomizer++); /* seed */
    
      strcpy(retstring, "curl"); /* bonus commercials 8*) */
    
      for(i=4; i<(BOUNDARY_LENGTH-1); i++) {
        retstring[i] = table64[rand()%64];
      }
      retstring[BOUNDARY_LENGTH-1]=0; /* zero terminate */
    
      return retstring;
    }
     
    
    void FormFree(struct FormData *form)
    {
      struct FormData *next;
      do {
        next=form->next;  /* the following form line */
        free(form->line); /* free the line */
        free(form);       /* free the struct */
    
    
      } while((form=next)); /* continue */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    }
    
    struct FormData *getFormData(struct HttpPost *post,
    			     int *sizep)
    {
      struct FormData *form = NULL;
      struct FormData *firstform;
    
      struct HttpPost *file;
    
      int size =0;
      char *boundary;
      char *fileboundary=NULL;
    
      if(!post)
        return NULL; /* no input => no output! */
    
      boundary = MakeFormBoundary();
      
      /* Make the first line of the output */
      AddFormDataf(&form,
                   "Content-Type: multipart/form-data;"
                   " boundary=%s\r\n",
                   boundary);
      /* we DO NOT count that line since that'll be part of the header! */
    
      firstform = form;
      
      do {
    
        /* boundary */
        size += AddFormDataf(&form, "\r\n--%s\r\n", boundary);
    
        size += AddFormDataf(&form,
    			 "Content-Disposition: form-data; name=\"%s\"",
    			 post->name);
    
        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 = MakeFormBoundary();
    
          size += AddFormDataf(&form,
    			   "\r\nContent-Type: multipart/mixed,"
    			   " boundary=%s\r\n",
    			   fileboundary);
        }
    
        file = post;
    
        do {
          if(post->more) {
    	/* if multiple-file */
    	size += AddFormDataf(&form,
    			     "\r\n--%s\r\nContent-Disposition: attachment; filename=\"%s\"",
    			     fileboundary, file->contents);
          }
          else if(post->flags & HTTPPOST_FILENAME) {
    	size += AddFormDataf(&form,
    			     "; filename=\"%s\"",
    			     post->contents);
          }
          
          if(file->contenttype) {
    	/* we have a specified type */
    	size += AddFormDataf(&form,
    			     "\r\nContent-Type: %s",
    			     file->contenttype);
          }
    
    
    #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 &&
    	 !strnequal("text/", file->contenttype, 5)) {
    	/* 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
    
    
          size += AddFormData(&form, "\r\n\r\n", 0);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
    
          if((post->flags & HTTPPOST_FILENAME) ||
    	 (post->flags & HTTPPOST_READFILE)) {
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    	/* we should include the contents from the specified file */
    	FILE *fileread;
    	char buffer[1024];
    	int nread;
    
    	fileread = strequal("-", file->contents)?stdin:
              /* binary read for win32 crap */
              fopen(file->contents, "rb");
    	if(fileread) {
    	  while((nread = fread(buffer, 1, 1024, fileread))) {
    	    size += AddFormData(&form,
    				buffer,
    				nread);
    	  }
              if(fileread != stdin)
                fclose(fileread);
    
    	} else {
    	  size += AddFormData(&form, "[File wasn't found by client]", 0);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    	}
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    	/* include the contents we got */
    
    	size += AddFormData(&form, post->contents, 0);
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
          }
        } while((file = file->more)); /* for each specified file for this field */
    
        if(post->more) {
          /* this was a multiple-file inclusion, make a termination file
             boundary: */
          size += AddFormDataf(&form,
    			   "\r\n--%s--",
    			   fileboundary);     
          free(fileboundary);
        }
    
      } while((post=post->next)); /* for each field */
    
      /* end-boundary for everything */
      size += AddFormDataf(&form,
    		       "\r\n--%s--\r\n",
    		       boundary);
    
      *sizep = size;
    
      free(boundary);
    
      return firstform;
    }
    
    int FormInit(struct Form *form, struct FormData *formdata )
    {
      if(!formdata)
        return 1; /* error */
    
    
    #if 0  
      struct FormData *lastnode=formdata;
    
      /* find the last node in the list */
      while(lastnode->next) {
        lastnode = lastnode->next;
      }
    
      /* Now, make sure that we'll send a nice terminating sequence at the end
    
       * of the post. We *DONT* add this string to the size of the data since this
       * is actually AFTER the data. */
    
      AddFormDataf(&lastnode, "\r\n\r\n");
    #endif
    
      form->data = formdata;
      form->sent = 0;
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
      return 0;
    }
    
    /* fread() emulation */
    int FormReader(char *buffer,
    	       size_t size,
    	       size_t nitems,
    	       FILE *mydata)
    {
      struct Form *form;
      int wantedsize;
      int gotsize;
    
      form=(struct Form *)mydata;
    
      wantedsize = size * nitems;
    
      if(!form->data)
    
        return -1; /* nothing, error, empty */
    
    Daniel Stenberg's avatar
    Daniel Stenberg committed
    
      do {
      
        if( (form->data->length - form->sent ) > wantedsize ) {
    
          memcpy(buffer, form->data->line + form->sent, wantedsize);
    
          form->sent += wantedsize;
    
          return wantedsize;
        }
    
        memcpy(buffer,
               form->data->line + form->sent,
               gotsize = (form->data->length - form->sent) );
    
        form->sent = 0;
    
        form->data = form->data->next; /* advance */
    
      } while(!gotsize && 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;
    }
    
    
    #ifdef _FORM_DEBUG
    
    int main(int argc, char **argv)
    {
    #if 0
      char *testargs[]={
        "name1 = data in number one",
        "name2 = number two data",
        "test = @upload"
      };
    #endif
      int i;
      char *nextarg;
      struct HttpPost *httppost=NULL;
      struct HttpPost *last_post=NULL;
      struct HttpPost *post;
      int size;
      int nread;
      char buffer[4096];
    
      struct FormData *form;
      struct Form formread;
    
      for(i=1; i<argc; i++) {
    
        if( FormParse( argv[i],
    		   &httppost,
    		   &last_post)) {
          fprintf(stderr, "Illegally formatted input field: '%s'!\n",
    	      argv[i]);
          return 1;
        }
      }
    
      form=getFormData(httppost, &size);
    
      FormInit(&formread, form);
    
      while(nread = FormReader(buffer, 1, sizeof(buffer), (FILE *)&formread)) {
        fwrite(buffer, nread, 1, stderr);
      }
    
      fprintf(stderr, "size: %d\n", size);
    
      return 0;
    }
    
    #endif