1 /*****************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
15 * License for the specific language governing rights and limitations
18 * The Original Code is Curl.
20 * The Initial Developer of the Original Code is Daniel Stenberg.
22 * Portions created by the Initial Developer are Copyright (C) 1998.
23 * All Rights Reserved.
25 * ------------------------------------------------------------
27 * - Daniel Stenberg <Daniel.Stenberg@haxx.nu>
38 * ------------------------------------------------------------
39 ****************************************************************************/
42 Debug the form generator stand-alone by compiling this source file with:
44 gcc -DHAVE_CONFIG_H -I../ -g -D_FORM_DEBUG -o formdata -I../include formdata.c
46 run the 'formdata' executable and make sure the output is ok!
48 try './formdata "name=Daniel" "poo=noo" "foo=bar"' and similarly
60 #include <curl/curl.h>
65 /* Length of the random boundary string. The risk of this being used
66 in binary data is very close to zero, 64^32 makes
67 6277101735386680763835789423207666416102355444464034512896
69 #define BOUNDARY_LENGTH 32
71 /* What kind of Content-Type to use on un-specified files with unrecognized
73 #define HTTPPOST_CONTENTTYPE_DEFAULT "text/plain"
75 /* This is a silly duplicate of the function in main.c to enable this source
76 to compile stand-alone for better debugging */
77 static void GetStr(char **string,
82 *string = strdup(value);
85 /***************************************************************************
89 * Reads a 'name=value' paramter and builds the appropriate linked list.
91 * Specify files to upload with 'name=@filename'. Supports specified
92 * given Content-Type of the files. Such as ';type=<content-type>'.
94 * You may specify more than one file for a single name (field). Specify
95 * multiple files by writing it like:
97 * 'name=@filename,filename2,filename3'
99 * If you want content-types specified for each too, write them like:
101 * 'name=@filename;type=image/gif,filename2,filename3'
103 ***************************************************************************/
105 int curl_FormParse(char *input,
106 struct HttpPost **httppost,
107 struct HttpPost **last_post)
109 return FormParse(input, httppost, last_post);
112 #define FORM_FILE_SEPARATOR ','
113 #define FORM_TYPE_SEPARATOR ';'
115 int FormParse(char *input,
116 struct HttpPost **httppost,
117 struct HttpPost **last_post)
119 /* nextarg MUST be a string in the format 'name=contents' and we'll
120 build a linked list with the info */
122 char contents[1024]="";
128 char *prevtype = NULL;
131 struct HttpPost *post;
132 struct HttpPost *subpost; /* a sub-node */
135 if(1 <= sscanf(input, "%255[^ =] = %1023[^\n]", name, contents)) {
136 /* the input was using the correct format */
139 if('@' == contp[0]) {
140 /* we use the @-letter to indicate file name(s) */
142 flags = HTTPPOST_FILENAME;
148 /* since this was a file, it may have a content-type specifier
151 sep=strchr(contp, FORM_TYPE_SEPARATOR);
152 sep2=strchr(contp, FORM_FILE_SEPARATOR);
154 /* pick the closest */
155 if(sep2 && (sep2 < sep)) {
158 /* no type was specified! */
162 /* if we got here on a comma, don't do much */
163 if(FORM_FILE_SEPARATOR != *sep)
164 type = strstr(sep+1, "type=");
168 *sep=0; /* terminate file name at separator */
171 type += strlen("type=");
173 if(2 != sscanf(type, "%127[^/]/%127[^,\n]",
175 fprintf(stderr, "Illegally formatted content-type field!\n");
176 return 2; /* illegal content-type syntax! */
178 /* now point beyond the content-type specifier */
179 sep = type + strlen(major)+strlen(minor)+1;
181 /* find the following comma */
182 sep=strchr(sep, FORM_FILE_SEPARATOR);
187 sep=strchr(contp, FORM_FILE_SEPARATOR);
190 /* the next file name starts here */
196 * No type was specified, we scan through a few well-known
197 * extensions and pick the first we match!
203 static struct ContentType ctts[]={
204 {".gif", "image/gif"},
205 {".jpg", "image/jpeg"},
206 {".jpeg", "image/jpeg"},
207 {".txt", "text/plain"},
208 {".html", "text/plain"}
212 /* default to the previously set/used! */
215 /* It seems RFC1867 defines no Content-Type to default to
216 text/plain so we don't actually need to set this: */
217 type = HTTPPOST_CONTENTTYPE_DEFAULT;
219 for(i=0; i<sizeof(ctts)/sizeof(ctts[0]); i++) {
220 if(strlen(contp) >= strlen(ctts[i].extension)) {
222 strlen(contp) - strlen(ctts[i].extension),
223 ctts[i].extension)) {
229 /* we have a type by now */
233 /* For the first file name, we allocate and initiate the main list
236 post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
238 memset(post, 0, sizeof(struct HttpPost));
239 GetStr(&post->name, name); /* get the name */
240 GetStr(&post->contents, contp); /* get the contents */
243 GetStr(&post->contenttype, type); /* get type */
244 prevtype=post->contenttype; /* point to the allocated string! */
246 /* make the previous point to this */
248 (*last_post)->next = post;
257 /* we add a file name to the previously allocated node, known as
259 subpost =(struct HttpPost *)malloc(sizeof(struct HttpPost));
261 memset(subpost, 0, sizeof(struct HttpPost));
262 GetStr(&subpost->name, name); /* get the name */
263 GetStr(&subpost->contents, contp); /* get the contents */
264 subpost->flags = flags;
266 GetStr(&subpost->contenttype, type); /* get type */
267 prevtype=subpost->contenttype; /* point to the allocated string! */
269 /* now, point our 'more' to the original 'more' */
270 subpost->more = post->more;
272 /* then move the original 'more' to point to ourselves */
273 post->more = subpost;
276 contp = sep; /* move the contents pointer to after the separator */
277 } while(sep && *sep); /* loop if there's another file name */
280 post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
282 memset(post, 0, sizeof(struct HttpPost));
283 GetStr(&post->name, name); /* get the name */
284 GetStr(&post->contents, contp); /* get the contents */
287 /* make the previous point to this */
289 (*last_post)->next = post;
300 fprintf(stderr, "Illegally formatted input field!\n");
306 static int AddFormData(struct FormData **formp,
310 struct FormData *newform = (struct FormData *)
311 malloc(sizeof(struct FormData));
312 newform->next = NULL;
314 /* we make it easier for plain strings: */
316 length = strlen((char *)line);
318 newform->line = (char *)malloc(length+1);
319 memcpy(newform->line, line, length+1);
320 newform->length = length;
323 (*formp)->next = newform;
333 static int AddFormDataf(struct FormData **formp,
339 vsprintf(s, fmt, ap);
342 return AddFormData(formp, s, 0);
346 char *MakeFormBoundary(void)
349 static int randomizer=0; /* this is just so that two boundaries within
350 the same form won't be identical */
353 static char table64[]=
354 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
356 retstring = (char *)malloc(BOUNDARY_LENGTH);
359 return NULL; /* failed */
361 srand(time(NULL)+randomizer++); /* seed */
363 strcpy(retstring, "curl"); /* bonus commercials 8*) */
365 for(i=4; i<(BOUNDARY_LENGTH-1); i++) {
366 retstring[i] = table64[rand()%64];
368 retstring[BOUNDARY_LENGTH-1]=0; /* zero terminate */
374 void FormFree(struct FormData *form)
376 struct FormData *next;
378 next=form->next; /* the following form line */
379 free(form->line); /* free the line */
380 free(form); /* free the struct */
382 } while((form=next)); /* continue */
385 struct FormData *getFormData(struct HttpPost *post,
388 struct FormData *form = NULL;
389 struct FormData *firstform;
391 struct HttpPost *file;
395 char *fileboundary=NULL;
398 return NULL; /* no input => no output! */
400 boundary = MakeFormBoundary();
402 /* Make the first line of the output */
404 "Content-Type: multipart/form-data;"
407 /* we DO NOT count that line since that'll be part of the header! */
414 size += AddFormDataf(&form, "\r\n--%s\r\n", boundary);
416 size += AddFormDataf(&form,
417 "Content-Disposition: form-data; name=\"%s\"",
421 /* If used, this is a link to more file names, we must then do
422 the magic to include several files with the same field name */
424 fileboundary = MakeFormBoundary();
426 size += AddFormDataf(&form,
427 "\r\nContent-Type: multipart/mixed,"
436 /* if multiple-file */
437 size += AddFormDataf(&form,
438 "\r\n--%s\r\nContent-Disposition: attachment; filename=\"%s\"",
439 fileboundary, file->contents);
441 else if(post->flags & HTTPPOST_FILENAME) {
442 size += AddFormDataf(&form,
447 if(file->contenttype) {
448 /* we have a specified type */
449 size += AddFormDataf(&form,
450 "\r\nContent-Type: %s",
453 if(file->contenttype &&
454 !strnequal("text/", file->contenttype, 5)) {
455 /* this is not a text content, mention our binary encoding */
456 size += AddFormDataf(&form,
457 "\r\nContent-Transfer-Encoding: binary");
461 size += AddFormDataf(&form,
464 if(post->flags & HTTPPOST_FILENAME) {
465 /* we should include the contents from the specified file */
470 fileread = strequal("-", file->contents)?stdin:
471 /* binary read for win32 crap */
472 fopen(file->contents, "rb");
474 while((nread = fread(buffer, 1, 1024, fileread))) {
475 size += AddFormData(&form,
479 if(fileread != stdin)
483 size += AddFormDataf(&form, "[File wasn't found by client]");
487 /* include the contents we got */
488 size += AddFormDataf(&form,
491 } while((file = file->more)); /* for each specified file for this field */
494 /* this was a multiple-file inclusion, make a termination file
496 size += AddFormDataf(&form,
502 } while((post=post->next)); /* for each field */
504 /* end-boundary for everything */
505 size += AddFormDataf(&form,
516 int FormInit(struct Form *form, struct FormData *formdata )
519 return 1; /* error */
521 /* First, make sure that we'll send a nice terminating sequence at the end
522 * of the post. We *DONT* add this string to the size of the data since this
523 * is actually AFTER the data. */
524 AddFormDataf(&formdata, "\r\n\r\n");
526 form->data = formdata;
532 /* fread() emulation */
533 int FormReader(char *buffer,
542 form=(struct Form *)mydata;
544 wantedsize = size * nitems;
547 return 0; /* nothing, error, empty */
551 if( (form->data->length - form->sent ) > wantedsize ) {
553 memcpy(buffer, form->data->line + form->sent, wantedsize);
555 form->sent += wantedsize;
561 form->data->line + form->sent,
562 gotsize = (form->data->length - form->sent) );
566 form->data = form->data->next; /* advance */
568 } while(!gotsize && form->data);
569 /* If we got an empty line and we have more data, we proceed to the next
570 line immediately to avoid returning zero before we've reached the end.
571 This is the bug reported November 22 1999 on curl 6.3. (Daniel) */
579 int main(int argc, char **argv)
583 "name1 = data in number one",
584 "name2 = number two data",
590 struct HttpPost *httppost=NULL;
591 struct HttpPost *last_post=NULL;
592 struct HttpPost *post;
597 struct FormData *form;
598 struct Form formread;
600 for(i=1; i<argc; i++) {
602 if( FormParse( argv[i],
605 fprintf(stderr, "Illegally formatted input field: '%s'!\n",
611 form=getFormData(httppost, &size);
613 FormInit(&formread, form);
615 while(nread = FormReader(buffer, 1, sizeof(buffer), (FILE *)&formread)) {
616 fwrite(buffer, nread, 1, stderr);
619 fprintf(stderr, "size: %d\n", size);