--- /dev/null
+.\" You can view this file with:
+.\" nroff -man [file]
+.\" $Id$
+.\"
+.TH curl_formadd 3 "19 August 2001" "libcurl 7.9" "libcurl Manual"
+.SH NAME
+curl_formadd - add a section to a multipart/formdata HTTP POST
+.SH SYNOPSIS
+.B #include <curl/curl.h>
+.sp
+.BI "CURLcode curl_formadd(struct HttpPost ** " firstitem,
+.BI "struct HttpPost ** " lastitem, " ...);"
+.ad
+.SH DESCRIPTION
+curl_formadd() is used to append sections when building a multipart/formdata
+HTTP POST (sometimes refered to as rfc1867-style posts). Append one section at
+a time until you've added all the sections you want included and then you pass
+the \fIfirstitem\fP pointer as parameter to \fBCURLOPT_HTTPPOST\fP.
+\fIlastitem\fP is set after each call and on repeated invokes it should be
+left as set to allow repeated invokes to find the end of the list in a faster
+way.
+
+After \fIlastitem\fP follow the real arguments that constitute the
+new section (if the following description confuses you jump directly
+to the examples):
+
+The first is always CURLFORM_COPYNAME followed by a string used for
+the name of the section.
+
+Afterwards one may use one of three arguments: CURLFORM_COPYCONTENTS,
+CURLFORM_PTRCONTENTS, or CURLFORM_FILE. followed by a char or void
+pointer (allowed for PTRCONTENTS).
+
+The next argument may be CURLFORM_CONTENTTYPE if the
+user wishes to specify one (for FILE if no type is given the library
+tries to provide the correct one; for CONTENTS no Content-Type is sent
+in this case)
+
+For CURLFORM_PTRCONTENTS the user may also add CURLFORM_CONTENTSLENGTH
+followed by the length as a long (if not given the library will use
+strlen to determine the length; for COPYCONTENTS this is always done).
+
+For CURLFORM_FILE the user may send multiple files in one section by
+providing multiple CURLFORM_FILE arguments each followed by the filename
+(and each FILE is allowed to have a CONTENTTYPE).
+
+The last argument always is CURLFORM_END.
+
+The pointers \fI*firstitem\fP and \fI*lastitem\fP should both be pointing to
+NULL in the first call to this function. All list-data will be allocated by
+the function itself. You must call \fIcurl_formfree\fP after the form post has
+been done to free the resources again.
+
+This function will copy all input data except the data pointed to by
+the argument after CURLFORM_PTRCONTENTS and keep its own
+version of it allocated until you call \fIcurl_formfree\fP. When
+you've passed the pointer to \fIcurl_easy_setopt\fP, you must not free
+the list until after you've called \fIcurl_easy_cleanup\fP for the
+curl handle. If you provide a pointer as an argument after
+CURLFORM_PTRCONTENTS you must ensure that the pointer stays valid
+until you call \fIcurl_form_free\fP and \fIcurl_easy_cleanup\fP.
+
+See example below.
+.SH RETURN VALUE
+Returns non-zero if an error occurs.
+.SH EXAMPLE
+.nf
+
+ HttpPost* post = NULL;
+ HttpPost* last = NULL;
+ char buffer[] = "test buffer";
+ char htmlbuffer[] = "<HTML>test buffer</HTML>";
+ long htmlbufferlength = strlen(htmlbuffer);
+ /* add null character into htmlbuffer, to demonstrate that
+ transfers of buffers containing null characters actually work
+ */
+ htmlbuffer[8] = '\\0';
+
+ /* Add simple name/content section */
+ curl_formadd(&post, &last, CURLFORM_COPYNAME, "name",
+ CURLFORM_COPYCONTENTS, "content", CURLFORM_END);
+ /* Add simple name/content/contenttype section */
+ curl_formadd(&post, &last, CURLFORM_COPYNAME, "htmlcode",
+ CURLFORM_COPYCONTENTS, "<HTML></HTML>",
+ CURLFORM_CONTENTTYPE, "text/html", CURLFORM_END);
+ /* Add name/ptrcontent section */
+ curl_formadd(&post, &past, CURLFORM_COPYNAME, "name_for_ptrcontent",
+ CURLFORM_PTRCONTENTS, buffer, CURLFORM_END);
+ /* Add name/ptrcontent/contenttype section */
+ curl_formadd(&post, &last, CURLFORM_COPYNAME, "html_code_with_hole",
+ CURLFORM_PTRCONTENTS, htmlbuffer,
+ CURLFORM_CONTENTSLENGTH, htmlbufferlength,
+ CURLFORM_CONTENTTYPE, "text/html", CURLFORM_END);
+ /* Add simple file section */
+ curl_formadd(&post, &last, CURLFORM_COPYNAME, "picture",
+ CURLFORM_FILE, "my-face.jpg", CURLFORM_END);
+ /* Add file/contenttype section */
+ curl_formadd(&post, &last, CURLFORM_COPYNAME, "picture",
+ CURLFORM_FILE, "my-face.jpg",
+ CURLFORM_CONTENTTYPE, "image/jpeg", CURLFORM_END);
+ /* Add two file section */
+ curl_formadd(&post, &last, CURLFORM_COPYNAME, "pictures",
+ CURLFORM_FILE, "my-face.jpg",
+ CURLFORM_FILE, "your-face.jpg", CURLFORM_END);
+ /* Set the form info */
+ curl_easy_setopt(curl, CURLOPT_HTTPPOST, post);
+
+.SH "SEE ALSO"
+.BR curl_easy_setopt "(3), "
+.BR curl_formparse "(3) [deprecated], "
+.BR curl_formfree "(3)
+.SH BUGS
+Surely there are some, you tell me!
+
/*
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
+ 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="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
+
+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
+...
+
+ For the old FormParse used by curl_formparse use:
+
+ gcc -DHAVE_CONFIG_H -I../ -g -D_OLD_FORM_DEBUG -o formdata -I../include formdata.c strequal.c
run the 'formdata' executable and make sure the output is ok!
/* 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)
+ const char *value)
{
if(*string)
free(*string);
memset(post, 0, sizeof(struct HttpPost));
GetStr(&post->name, name); /* get the name */
GetStr(&post->contents, contp); /* get the contents */
+ post->contentslength = 0;
post->flags = flags;
if(type) {
GetStr(&post->contenttype, (char *)type); /* get type */
memset(subpost, 0, sizeof(struct HttpPost));
GetStr(&subpost->name, name); /* get the name */
GetStr(&subpost->contents, contp); /* get the contents */
+ subpost->contentslength = 0;
subpost->flags = flags;
if(type) {
GetStr(&subpost->contenttype, (char *)type); /* get type */
GetStr(&post->name, name); /* get the name */
if( contp[0]=='<' ) {
GetStr(&post->contents, contp+1); /* get the contents */
+ post->contentslength = 0;
post->flags = HTTPPOST_READFILE;
}
else {
GetStr(&post->contents, contp); /* get the contents */
+ post->contentslength = 0;
post->flags = 0;
}
return FormParse(input, httppost, last_post);
}
+/***************************************************************************
+ *
+ * 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 0 on success and 1 if malloc failed.
+ *
+ ***************************************************************************/
+static struct HttpPost * AddHttpPost (char * name,
+ char * value,
+ long contentslength,
+ long flags,
+ struct HttpPost *parent_post,
+ struct HttpPost **httppost,
+ struct HttpPost **last_post)
+{
+ struct HttpPost *post;
+ post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
+ if(post) {
+ memset(post, 0, sizeof(struct HttpPost));
+ post->name = name;
+ post->contents = value;
+ post->contentslength = contentslength;
+ 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;
+ }
+ return post;
+}
+
+/***************************************************************************
+ *
+ * FormAdd()
+ *
+ * Stores a 'name=value' 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");
+ *
+ * name/value pair where only the content pointer is remembered:
+ * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
+ * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10);
+ * (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);
+ *
+ * Returns 0 on success, 1 if the first option is not CURLFORM_COPYNAME,
+ * 2 if AddHttpPost failes, and 3 if an unknown option is encountered
+ *
+ ***************************************************************************/
+
+static
+int FormAdd(struct HttpPost **httppost,
+ struct HttpPost **last_post,
+ va_list params)
+{
+ int go_on = TRUE;
+ int read_argument = TRUE;
+ unsigned int i;
+ char *name;
+ char *value;
+ const char *prevtype = NULL;
+ struct HttpPost *post = NULL;
+ CURLformoption next_option;
+
+ /* We always expect CURLFORM_COPYNAME first for the moment. */
+ next_option = va_arg(params, CURLformoption);
+ if (next_option != CURLFORM_COPYNAME)
+ return 1;
+
+ name = va_arg(params, char *);
+ do
+ {
+ /* if not already read read next argument */
+ if (read_argument)
+ next_option = va_arg(params, CURLformoption);
+ else
+ read_argument = TRUE;
+
+ switch (next_option)
+ {
+ case CURLFORM_COPYCONTENTS:
+ { /* simple name/value storage of duplicated data */
+ const char * contenttype = NULL;
+ value = va_arg(params, char *);
+ next_option = va_arg(params, CURLformoption);
+ if (next_option == CURLFORM_CONTENTTYPE)
+ contenttype = va_arg(params, char *);
+ else
+ read_argument = FALSE;
+ if ((post = AddHttpPost(strdup(name), strdup(value), 0, 0, NULL,
+ httppost, last_post)) == NULL) {
+ return 2;
+ }
+ if (contenttype)
+ post->contenttype = strdup(contenttype);
+ /* at the moment no more options are allowd in this case */
+ go_on = FALSE;
+ break;
+ }
+ case CURLFORM_PTRCONTENTS:
+ { /* name/value storage with value stored as a pointer */
+ const char * contenttype = NULL;
+ void * ptr_contents = va_arg(params, void *);
+ long contentslength;
+ int got_contentslength = FALSE;
+ /* either use provided length or use strlen () to get it */
+ next_option = va_arg(params, CURLformoption);
+ while ( (next_option == CURLFORM_CONTENTSLENGTH) ||
+ (next_option == CURLFORM_CONTENTTYPE) ) {
+ if (next_option == CURLFORM_CONTENTSLENGTH) {
+ contentslength = va_arg(params, long);
+ got_contentslength = TRUE;
+ }
+ else { /* CURLFORM_CONTENTTYPE */
+ contenttype = va_arg(params, char *);
+ }
+ next_option = va_arg(params, CURLformoption);
+ };
+ /* we already read the next CURLformoption */
+ read_argument = FALSE;
+ if (!got_contentslength)
+ /* no length given, use strlen to find out */
+ contentslength = strlen (ptr_contents);
+ if ((post = AddHttpPost(strdup(name), ptr_contents, contentslength,
+ HTTPPOST_PTRCONTENTS, NULL, httppost,
+ last_post))
+ == NULL) {
+ return 2;
+ }
+ if (contenttype)
+ post->contenttype = strdup(contenttype);
+ /* at the moment no more options are allowd in this case */
+ go_on = FALSE;
+ break;
+ }
+ case CURLFORM_FILE:
+ {
+ const char * contenttype = NULL;
+ value = va_arg(params, char *);
+ next_option = va_arg(params, CURLformoption);
+ /* if contenttype was provided retrieve it */
+ if (next_option == CURLFORM_CONTENTTYPE) {
+ contenttype = va_arg(params, char *);
+ }
+ else {
+ /*
+ * 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 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! */
+ 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(value) >= strlen(ctts[i].extension)) {
+ if(strequal(value +
+ strlen(value) - strlen(ctts[i].extension),
+ ctts[i].extension)) {
+ contenttype = ctts[i].type;
+ break;
+ }
+ }
+ }
+ /* we have a contenttype by now */
+ /* do not try to read the next option we already did that */
+ read_argument = FALSE;
+ }
+ if ( (post = AddHttpPost (strdup(name), strdup(value), 0,
+ HTTPPOST_FILENAME, post, httppost,
+ last_post)) == NULL) {
+ return 2;
+ }
+ post->contenttype = strdup (contenttype);
+ prevtype = post->contenttype;
+ /* we do not set go_on to false as multiple files are allowed */
+ break;
+ }
+ case CURLFORM_END:
+ /* this ends our loop */
+ break;
+ default:
+ fprintf (stderr, "got: %d\n", next_option);
+ return 3;
+ };
+
+ } while (go_on && next_option != CURLFORM_END);
+
+ return 0;
+}
+
+int curl_formadd(struct HttpPost **httppost,
+ struct HttpPost **last_post,
+ ...)
+{
+ va_list arg;
+ int result;
+ va_start(arg, last_post);
+ result = FormAdd(httppost, last_post, arg);
+ va_end(arg);
+ return result;
+}
+
static int AddFormData(struct FormData **formp,
const void *line,
long length)
if(form->name)
free(form->name); /* free the name */
- if(form->contents)
+ if( !(form->flags & HTTPPOST_PTRCONTENTS) && form->contents)
free(form->contents); /* free the contents */
if(form->contenttype)
free(form->contenttype); /* free the content type */
}
} else {
/* include the contents we got */
- size += AddFormData(&form, post->contents, 0);
+ size += AddFormData(&form, post->contents, post->contentslength);
}
} while((file = file->more)); /* for each specified file for this field */
size_t size,
size_t nitems,
FILE *mydata)
+{
+ struct Form *form;
+ int wantedsize;
+ int gotsize = 0;
+
+ form=(struct Form *)mydata;
+
+ wantedsize = size * nitems;
+
+ if(!form->data)
+ return -1; /* nothing, error, empty */
+
+ do {
+
+ 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);
+ /* 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;
+}
+
+/* possible (old) fread() emulation that copies at most one line */
+int Curl_FormReadOneLine(char *buffer,
+ size_t size,
+ size_t nitems,
+ FILE *mydata)
{
struct Form *form;
int wantedsize;
#ifdef _FORM_DEBUG
+int FormAddTest(const char * errormsg,
+ struct HttpPost **httppost,
+ struct HttpPost **last_post,
+ ...)
+{
+ int result;
+ va_list arg;
+ CURLformoption next_option;
+ char * value;
+ 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[] = "simple_PTRCONTENTS";
+ char name4[] = "PTRCONTENTS_+_CONTENTSLENGTH";
+ char name5[] = "PTRCONTENTS_+_CONTENTSLENGTH_+_CONTENTTYPE";
+ char name6[] = "FILE1_+_CONTENTTYPE";
+ char name7[] = "FILE1_+_FILE2";
+ char value1[] = "value for simple COPYCONTENTS";
+ char value2[] = "value for COPYCONTENTS + CONTENTTYPE";
+ char value3[] = "value for simple PTRCONTENTS";
+ char value4[] = "value for PTRCONTENTS + CONTENTSLENGTH";
+ char value5[] = "value for PTRCOTNENTS + CONTENTSLENGTH + CONTENTTYPE";
+ char value6[] = "inet_ntoa_r.h";
+ char value7[] = "Makefile.b32.resp";
+ char type2[] = "image/gif";
+ char type5[] = "text/plain";
+ char type6[] = "text/html";
+ int value4length = strlen(value4);
+ int value5length = strlen(value5);
+ int errors = 0;
+ int size;
+ int nread;
+ char buffer[4096];
+ struct HttpPost *httppost=NULL;
+ struct HttpPost *last_post=NULL;
+ struct HttpPost *post;
+
+ 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;
+ if (FormAddTest("simple PTRCONTENTS test", &httppost, &last_post,
+ CURLFORM_COPYNAME, name3, CURLFORM_PTRCONTENTS, value3,
+ CURLFORM_END))
+ ++errors;
+ /* make null character at start to check that contentslength works
+ correctly */
+ value4[1] = '\0';
+ if (FormAddTest("PTRCONTENTS + CONTENTSLENGTH test", &httppost, &last_post,
+ CURLFORM_COPYNAME, name4, CURLFORM_PTRCONTENTS, value4,
+ CURLFORM_CONTENTSLENGTH, value4length, CURLFORM_END))
+ ++errors;
+ /* make null character at start to check that contentslength works
+ correctly */
+ value5[1] = '\0';
+ if (FormAddTest("PTRCONTENTS + CONTENTSLENGTH + CONTENTTYPE test",
+ &httppost, &last_post,
+ CURLFORM_COPYNAME, name5, CURLFORM_PTRCONTENTS, value5,
+ CURLFORM_CONTENTSLENGTH, value5length,
+ CURLFORM_CONTENTTYPE, type5, CURLFORM_END))
+ ++errors;
+ if (FormAddTest("FILE + CONTENTTYPE test", &httppost, &last_post,
+ CURLFORM_COPYNAME, name6, CURLFORM_FILE, value6,
+ CURLFORM_CONTENTTYPE, type6, CURLFORM_END))
+ ++errors;
+ if (FormAddTest("FILE1 + FILE2 test", &httppost, &last_post,
+ CURLFORM_COPYNAME, name7, CURLFORM_FILE, value6,
+ CURLFORM_FILE, 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, stderr);
+ } while(1);
+
+ fprintf(stderr, "size: %d\n", size);
+ if (errors)
+ fprintf(stderr, "\n==> %d Test(s) failed!\n", errors);
+ else
+ fprintf(stdout, "\nAll Tests seem to have worked (please check output)\n");
+
+ return 0;
+}
+
+#endif
+
+#ifdef _OLD_FORM_DEBUG
int main(int argc, char **argv)
{