1 /*****************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 2000, Daniel Stenberg, <daniel@haxx.se>, et al.
10 * In order to be useful for every potential user, curl and libcurl are
11 * dual-licensed under the MPL and the MIT/X-derivate licenses.
13 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
14 * copies of the Software, and permit persons to whom the Software is
15 * furnished to do so, under the terms of the MPL or the MIT/X-derivate
16 * licenses. You may pick one of these licenses.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
22 *****************************************************************************/
25 Debug the form generator stand-alone by compiling this source file with:
27 gcc -DHAVE_CONFIG_H -I../ -g -D_FORM_DEBUG -o formdata -I../include formdata.c
29 run the 'formdata' executable and make sure the output is ok!
31 try './formdata "name=Daniel" "poo=noo" "foo=bar"' and similarly
44 #include <curl/curl.h>
49 /* The last #include file should be: */
54 /* Length of the random boundary string. The risk of this being used
55 in binary data is very close to zero, 64^32 makes
56 6277101735386680763835789423207666416102355444464034512896
58 #define BOUNDARY_LENGTH 32
60 /* What kind of Content-Type to use on un-specified files with unrecognized
62 #define HTTPPOST_CONTENTTYPE_DEFAULT "text/plain"
64 /* This is a silly duplicate of the function in main.c to enable this source
65 to compile stand-alone for better debugging */
66 static void GetStr(char **string,
71 *string = strdup(value);
74 /***************************************************************************
78 * Reads a 'name=value' paramter and builds the appropriate linked list.
80 * Specify files to upload with 'name=@filename'. Supports specified
81 * given Content-Type of the files. Such as ';type=<content-type>'.
83 * You may specify more than one file for a single name (field). Specify
84 * multiple files by writing it like:
86 * 'name=@filename,filename2,filename3'
88 * If you want content-types specified for each too, write them like:
90 * 'name=@filename;type=image/gif,filename2,filename3'
92 ***************************************************************************/
94 int curl_formparse(char *input,
95 struct HttpPost **httppost,
96 struct HttpPost **last_post)
98 return FormParse(input, httppost, last_post);
101 #define FORM_FILE_SEPARATOR ','
102 #define FORM_TYPE_SEPARATOR ';'
104 int FormParse(char *input,
105 struct HttpPost **httppost,
106 struct HttpPost **last_post)
108 /* nextarg MUST be a string in the format 'name=contents' and we'll
109 build a linked list with the info */
111 char contents[4096]="";
117 char *prevtype = NULL;
120 struct HttpPost *post;
121 struct HttpPost *subpost; /* a sub-node */
124 if(1 <= sscanf(input, "%255[^ =] = %4095[^\n]", name, contents)) {
125 /* the input was using the correct format */
128 if('@' == contp[0]) {
129 /* we use the @-letter to indicate file name(s) */
131 flags = HTTPPOST_FILENAME;
137 /* since this was a file, it may have a content-type specifier
140 sep=strchr(contp, FORM_TYPE_SEPARATOR);
141 sep2=strchr(contp, FORM_FILE_SEPARATOR);
143 /* pick the closest */
144 if(sep2 && (sep2 < sep)) {
147 /* no type was specified! */
151 /* if we got here on a comma, don't do much */
152 if(FORM_FILE_SEPARATOR != *sep)
153 type = strstr(sep+1, "type=");
157 *sep=0; /* terminate file name at separator */
160 type += strlen("type=");
162 if(2 != sscanf(type, "%127[^/]/%127[^,\n]",
164 fprintf(stderr, "Illegally formatted content-type field!\n");
165 return 2; /* illegal content-type syntax! */
167 /* now point beyond the content-type specifier */
168 sep = type + strlen(major)+strlen(minor)+1;
170 /* find the following comma */
171 sep=strchr(sep, FORM_FILE_SEPARATOR);
176 sep=strchr(contp, FORM_FILE_SEPARATOR);
179 /* the next file name starts here */
185 * No type was specified, we scan through a few well-known
186 * extensions and pick the first we match!
192 static struct ContentType ctts[]={
193 {".gif", "image/gif"},
194 {".jpg", "image/jpeg"},
195 {".jpeg", "image/jpeg"},
196 {".txt", "text/plain"},
197 {".html", "text/plain"}
201 /* default to the previously set/used! */
204 /* It seems RFC1867 defines no Content-Type to default to
205 text/plain so we don't actually need to set this: */
206 type = HTTPPOST_CONTENTTYPE_DEFAULT;
208 for(i=0; i<sizeof(ctts)/sizeof(ctts[0]); i++) {
209 if(strlen(contp) >= strlen(ctts[i].extension)) {
211 strlen(contp) - strlen(ctts[i].extension),
212 ctts[i].extension)) {
218 /* we have a type by now */
222 /* For the first file name, we allocate and initiate the main list
225 post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
227 memset(post, 0, sizeof(struct HttpPost));
228 GetStr(&post->name, name); /* get the name */
229 GetStr(&post->contents, contp); /* get the contents */
232 GetStr(&post->contenttype, type); /* get type */
233 prevtype=post->contenttype; /* point to the allocated string! */
235 /* make the previous point to this */
237 (*last_post)->next = post;
246 /* we add a file name to the previously allocated node, known as
248 subpost =(struct HttpPost *)malloc(sizeof(struct HttpPost));
250 memset(subpost, 0, sizeof(struct HttpPost));
251 GetStr(&subpost->name, name); /* get the name */
252 GetStr(&subpost->contents, contp); /* get the contents */
253 subpost->flags = flags;
255 GetStr(&subpost->contenttype, type); /* get type */
256 prevtype=subpost->contenttype; /* point to the allocated string! */
258 /* now, point our 'more' to the original 'more' */
259 subpost->more = post->more;
261 /* then move the original 'more' to point to ourselves */
262 post->more = subpost;
265 contp = sep; /* move the contents pointer to after the separator */
266 } while(sep && *sep); /* loop if there's another file name */
269 post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
271 memset(post, 0, sizeof(struct HttpPost));
272 GetStr(&post->name, name); /* get the name */
273 if( contp[0]=='<' ) {
274 GetStr(&post->contents, contp+1); /* get the contents */
275 post->flags = HTTPPOST_READFILE;
278 GetStr(&post->contents, contp); /* get the contents */
282 /* make the previous point to this */
284 (*last_post)->next = post;
295 fprintf(stderr, "Illegally formatted input field!\n");
301 static int AddFormData(struct FormData **formp,
305 struct FormData *newform = (struct FormData *)
306 malloc(sizeof(struct FormData));
307 newform->next = NULL;
309 /* we make it easier for plain strings: */
311 length = strlen((char *)line);
313 newform->line = (char *)malloc(length+1);
314 memcpy(newform->line, line, length+1);
315 newform->length = length;
316 newform->line[length]=0; /* zero terminate for easier debugging */
319 (*formp)->next = newform;
329 static int AddFormDataf(struct FormData **formp,
335 vsprintf(s, fmt, ap);
338 return AddFormData(formp, s, 0);
342 char *MakeFormBoundary(void)
345 static int randomizer=0; /* this is just so that two boundaries within
346 the same form won't be identical */
349 static char table64[]=
350 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
352 retstring = (char *)malloc(BOUNDARY_LENGTH);
355 return NULL; /* failed */
357 srand(time(NULL)+randomizer++); /* seed */
359 strcpy(retstring, "curl"); /* bonus commercials 8*) */
361 for(i=4; i<(BOUNDARY_LENGTH-1); i++) {
362 retstring[i] = table64[rand()%64];
364 retstring[BOUNDARY_LENGTH-1]=0; /* zero terminate */
369 /* Used from http.c */
370 void FormFree(struct FormData *form)
372 struct FormData *next;
374 next=form->next; /* the following form line */
375 free(form->line); /* free the line */
376 free(form); /* free the struct */
378 } while((form=next)); /* continue */
381 /* external function to free up a whole form post chain */
382 void curl_formfree(struct HttpPost *form)
384 struct HttpPost *next;
386 next=form->next; /* the following form line */
388 /* recurse to sub-contents */
390 curl_formfree(form->more);
393 free(form->name); /* free the name */
395 free(form->contents); /* free the contents */
396 if(form->contenttype)
397 free(form->contenttype); /* free the content type */
398 free(form); /* free the struct */
400 } while((form=next)); /* continue */
403 struct FormData *getFormData(struct HttpPost *post,
406 struct FormData *form = NULL;
407 struct FormData *firstform;
409 struct HttpPost *file;
413 char *fileboundary=NULL;
416 return NULL; /* no input => no output! */
418 boundary = MakeFormBoundary();
420 /* Make the first line of the output */
422 "Content-Type: multipart/form-data;"
425 /* we DO NOT count that line since that'll be part of the header! */
432 size += AddFormDataf(&form, "\r\n--%s\r\n", boundary);
434 size += AddFormDataf(&form,
435 "Content-Disposition: form-data; name=\"%s\"",
439 /* If used, this is a link to more file names, we must then do
440 the magic to include several files with the same field name */
442 fileboundary = MakeFormBoundary();
444 size += AddFormDataf(&form,
445 "\r\nContent-Type: multipart/mixed,"
454 /* if multiple-file */
455 size += AddFormDataf(&form,
456 "\r\n--%s\r\nContent-Disposition: attachment; filename=\"%s\"",
457 fileboundary, file->contents);
459 else if(post->flags & HTTPPOST_FILENAME) {
460 size += AddFormDataf(&form,
465 if(file->contenttype) {
466 /* we have a specified type */
467 size += AddFormDataf(&form,
468 "\r\nContent-Type: %s",
473 /* The header Content-Transfer-Encoding: seems to confuse some receivers
474 * (like the built-in PHP engine). While I can't see any reason why it
475 * should, I can just as well skip this to the benefit of the users who
476 * are using such confused receivers.
479 if(file->contenttype &&
480 !strnequal("text/", file->contenttype, 5)) {
481 /* this is not a text content, mention our binary encoding */
482 size += AddFormData(&form, "\r\nContent-Transfer-Encoding: binary", 0);
486 size += AddFormData(&form, "\r\n\r\n", 0);
488 if((post->flags & HTTPPOST_FILENAME) ||
489 (post->flags & HTTPPOST_READFILE)) {
490 /* we should include the contents from the specified file */
495 fileread = strequal("-", file->contents)?stdin:
496 /* binary read for win32 crap */
497 fopen(file->contents, "rb");
499 while((nread = fread(buffer, 1, 1024, fileread))) {
500 size += AddFormData(&form,
504 if(fileread != stdin)
507 size += AddFormData(&form, "[File wasn't found by client]", 0);
510 /* include the contents we got */
511 size += AddFormData(&form, post->contents, 0);
513 } while((file = file->more)); /* for each specified file for this field */
516 /* this was a multiple-file inclusion, make a termination file
518 size += AddFormDataf(&form,
524 } while((post=post->next)); /* for each field */
526 /* end-boundary for everything */
527 size += AddFormDataf(&form,
538 int FormInit(struct Form *form, struct FormData *formdata )
541 return 1; /* error */
544 struct FormData *lastnode=formdata;
546 /* find the last node in the list */
547 while(lastnode->next) {
548 lastnode = lastnode->next;
551 /* Now, make sure that we'll send a nice terminating sequence at the end
552 * of the post. We *DONT* add this string to the size of the data since this
553 * is actually AFTER the data. */
554 AddFormDataf(&lastnode, "\r\n\r\n");
556 form->data = formdata;
562 /* fread() emulation */
563 int FormReader(char *buffer,
572 form=(struct Form *)mydata;
574 wantedsize = size * nitems;
577 return -1; /* nothing, error, empty */
581 if( (form->data->length - form->sent ) > wantedsize ) {
583 memcpy(buffer, form->data->line + form->sent, wantedsize);
585 form->sent += wantedsize;
591 form->data->line + form->sent,
592 gotsize = (form->data->length - form->sent) );
596 form->data = form->data->next; /* advance */
598 } while(!gotsize && form->data);
599 /* If we got an empty line and we have more data, we proceed to the next
600 line immediately to avoid returning zero before we've reached the end.
601 This is the bug reported November 22 1999 on curl 6.3. (Daniel) */
609 int main(int argc, char **argv)
613 "name1 = data in number one",
614 "name2 = number two data",
620 struct HttpPost *httppost=NULL;
621 struct HttpPost *last_post=NULL;
622 struct HttpPost *post;
627 struct FormData *form;
628 struct Form formread;
630 for(i=1; i<argc; i++) {
632 if( FormParse( argv[i],
635 fprintf(stderr, "Illegally formatted input field: '%s'!\n",
641 form=getFormData(httppost, &size);
643 FormInit(&formread, form);
645 while(nread = FormReader(buffer, 1, sizeof(buffer), (FILE *)&formread)) {
646 fwrite(buffer, nread, 1, stderr);
649 fprintf(stderr, "size: %d\n", size);