summaryrefslogtreecommitdiff
path: root/lib/formdata.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/formdata.c')
-rw-r--r--lib/formdata.c617
1 files changed, 617 insertions, 0 deletions
diff --git a/lib/formdata.c b/lib/formdata.c
new file mode 100644
index 000000000..eff0212e4
--- /dev/null
+++ b/lib/formdata.c
@@ -0,0 +1,617 @@
+/*****************************************************************************
+ * _ _ ____ _
+ * 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 <Daniel.Stenberg@haxx.nu>
+ *
+ * http://curl.haxx.nu
+ *
+ * $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
+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <time.h>
+
+#include "setup.h"
+#include <curl/curl.h>
+#include "formdata.h"
+
+/* 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,
+ 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[1024]="";
+ 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[^ =] = %1023[^\n]", name, contents)) {
+ /* 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 */
+ GetStr(&post->contents, contp); /* get the contents */
+ post->flags = 0;
+
+ /* 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;
+
+ if(*formp) {
+ (*formp)->next = newform;
+ *formp = newform;
+ }
+ else
+ *formp = newform;
+
+ return length;
+}
+
+
+static int AddFormDataf(struct FormData **formp,
+ char *fmt, ...)
+{
+ char s[1024];
+ 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 */
+}
+
+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(file->contenttype &&
+ !strnequal("text/", file->contenttype, 5)) {
+ /* this is not a text content, mention our binary encoding */
+ size += AddFormDataf(&form,
+ "\r\nContent-Transfer-Encoding: binary");
+ }
+
+
+ size += AddFormDataf(&form,
+ "\r\n\r\n");
+
+ if(post->flags & HTTPPOST_FILENAME) {
+ /* 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 += AddFormDataf(&form, "[File wasn't found by client]");
+ }
+ }
+ else {
+ /* include the contents we got */
+ size += AddFormDataf(&form,
+ post->contents);
+ }
+ } 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 )
+{
+ form->data = formdata;
+ form->sent = 0;
+
+ if(!formdata)
+ return 1; /* error */
+
+ 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 0; /* nothing, error, empty */
+
+ 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