Initial revision
[libcurl.git] / lib / formdata.c
blob993e54a7cd99d54bc320d3ed5c04ceb7872d95c3
1 /*****************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
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
16 * under the License.
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 * ------------------------------------------------------------
26 * Main author:
27 * - Daniel Stenberg <Daniel.Stenberg@haxx.nu>
29 * http://curl.haxx.nu
31 * $Source: /cvsroot/curl/curl/lib/formdata.c,v $
32 * $Revision: 1.1 $
33 * $Date: 1999-12-29 14:21:22 $
34 * $Author: bagder $
35 * $State: Exp $
36 * $Locker: $
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
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <stdarg.h>
57 #include <time.h>
59 #include "setup.h"
60 #include <curl/curl.h>
61 #include "formdata.h"
63 /* Length of the random boundary string. The risk of this being used
64 in binary data is very close to zero, 64^32 makes
65 6277101735386680763835789423207666416102355444464034512896
66 combinations... */
67 #define BOUNDARY_LENGTH 32
69 /* What kind of Content-Type to use on un-specified files with unrecognized
70 extensions. */
71 #define HTTPPOST_CONTENTTYPE_DEFAULT "text/plain"
73 /* This is a silly duplicate of the function in main.c to enable this source
74 to compile stand-alone for better debugging */
75 static void GetStr(char **string,
76 char *value)
78 if(*string)
79 free(*string);
80 *string = strdup(value);
83 /***************************************************************************
85 * FormParse()
87 * Reads a 'name=value' paramter and builds the appropriate linked list.
89 * Specify files to upload with 'name=@filename'. Supports specified
90 * given Content-Type of the files. Such as ';type=<content-type>'.
92 * You may specify more than one file for a single name (field). Specify
93 * multiple files by writing it like:
95 * 'name=@filename,filename2,filename3'
97 * If you want content-types specified for each too, write them like:
99 * 'name=@filename;type=image/gif,filename2,filename3'
101 ***************************************************************************/
103 int curl_FormParse(char *input,
104 struct HttpPost **httppost,
105 struct HttpPost **last_post)
107 return FormParse(input, httppost, last_post);
110 #define FORM_FILE_SEPARATOR ','
111 #define FORM_TYPE_SEPARATOR ';'
113 int FormParse(char *input,
114 struct HttpPost **httppost,
115 struct HttpPost **last_post)
117 /* nextarg MUST be a string in the format 'name=contents' and we'll
118 build a linked list with the info */
119 char name[256];
120 char contents[1024]="";
121 char major[128];
122 char minor[128];
123 long flags = 0;
124 char *contp;
125 char *type = NULL;
126 char *prevtype = NULL;
127 char *sep;
128 char *sep2;
129 struct HttpPost *post;
130 struct HttpPost *subpost; /* a sub-node */
131 unsigned int i;
133 if(1 <= sscanf(input, "%255[^ =] = %1023[^\n]", name, contents)) {
134 /* the input was using the correct format */
135 contp = contents;
137 if('@' == contp[0]) {
138 /* we use the @-letter to indicate file name(s) */
140 flags = HTTPPOST_FILENAME;
141 contp++;
143 post=NULL;
145 do {
146 /* since this was a file, it may have a content-type specifier
147 at the end too */
149 sep=strchr(contp, FORM_TYPE_SEPARATOR);
150 sep2=strchr(contp, FORM_FILE_SEPARATOR);
152 /* pick the closest */
153 if(sep2 && (sep2 < sep)) {
154 sep = sep2;
156 /* no type was specified! */
158 if(sep) {
160 /* if we got here on a comma, don't do much */
161 if(FORM_FILE_SEPARATOR != *sep)
162 type = strstr(sep+1, "type=");
163 else
164 type=NULL;
166 *sep=0; /* terminate file name at separator */
168 if(type) {
169 type += strlen("type=");
171 if(2 != sscanf(type, "%127[^/]/%127[^,\n]",
172 major, minor)) {
173 fprintf(stderr, "Illegally formatted content-type field!\n");
174 return 2; /* illegal content-type syntax! */
176 /* now point beyond the content-type specifier */
177 sep = type + strlen(major)+strlen(minor)+1;
179 /* find the following comma */
180 sep=strchr(sep, FORM_FILE_SEPARATOR);
183 else {
184 type=NULL;
185 sep=strchr(contp, FORM_FILE_SEPARATOR);
187 if(sep) {
188 /* the next file name starts here */
189 *sep =0;
190 sep++;
192 if(!type) {
194 * No type was specified, we scan through a few well-known
195 * extensions and pick the first we match!
197 struct ContentType {
198 char *extension;
199 char *type;
201 static struct ContentType ctts[]={
202 {".gif", "image/gif"},
203 {".jpg", "image/jpeg"},
204 {".jpeg", "image/jpeg"},
205 {".txt", "text/plain"},
206 {".html", "text/plain"}
209 if(prevtype)
210 /* default to the previously set/used! */
211 type = prevtype;
212 else
213 /* It seems RFC1867 defines no Content-Type to default to
214 text/plain so we don't actually need to set this: */
215 type = HTTPPOST_CONTENTTYPE_DEFAULT;
217 for(i=0; i<sizeof(ctts)/sizeof(ctts[0]); i++) {
218 if(strlen(contp) >= strlen(ctts[i].extension)) {
219 if(strequal(contp +
220 strlen(contp) - strlen(ctts[i].extension),
221 ctts[i].extension)) {
222 type = ctts[i].type;
223 break;
227 /* we have a type by now */
230 if(NULL == post) {
231 /* For the first file name, we allocate and initiate the main list
232 node */
234 post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
235 if(post) {
236 memset(post, 0, sizeof(struct HttpPost));
237 GetStr(&post->name, name); /* get the name */
238 GetStr(&post->contents, contp); /* get the contents */
239 post->flags = flags;
240 if(type) {
241 GetStr(&post->contenttype, type); /* get type */
242 prevtype=post->contenttype; /* point to the allocated string! */
244 /* make the previous point to this */
245 if(*last_post)
246 (*last_post)->next = post;
247 else
248 (*httppost) = post;
250 (*last_post) = post;
254 else {
255 /* we add a file name to the previously allocated node, known as
256 'post' now */
257 subpost =(struct HttpPost *)malloc(sizeof(struct HttpPost));
258 if(subpost) {
259 memset(subpost, 0, sizeof(struct HttpPost));
260 GetStr(&subpost->name, name); /* get the name */
261 GetStr(&subpost->contents, contp); /* get the contents */
262 subpost->flags = flags;
263 if(type) {
264 GetStr(&subpost->contenttype, type); /* get type */
265 prevtype=subpost->contenttype; /* point to the allocated string! */
267 /* now, point our 'more' to the original 'more' */
268 subpost->more = post->more;
270 /* then move the original 'more' to point to ourselves */
271 post->more = subpost;
274 contp = sep; /* move the contents pointer to after the separator */
275 } while(sep && *sep); /* loop if there's another file name */
277 else {
278 post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
279 if(post) {
280 memset(post, 0, sizeof(struct HttpPost));
281 GetStr(&post->name, name); /* get the name */
282 GetStr(&post->contents, contp); /* get the contents */
283 post->flags = 0;
285 /* make the previous point to this */
286 if(*last_post)
287 (*last_post)->next = post;
288 else
289 (*httppost) = post;
291 (*last_post) = post;
297 else {
298 fprintf(stderr, "Illegally formatted input field!\n");
299 return 1;
301 return 0;
304 static int AddFormData(struct FormData **formp,
305 void *line,
306 long length)
308 struct FormData *newform = (struct FormData *)
309 malloc(sizeof(struct FormData));
310 newform->next = NULL;
312 /* we make it easier for plain strings: */
313 if(!length)
314 length = strlen((char *)line);
316 newform->line = (char *)malloc(length+1);
317 memcpy(newform->line, line, length+1);
318 newform->length = length;
320 if(*formp) {
321 (*formp)->next = newform;
322 *formp = newform;
324 else
325 *formp = newform;
327 return length;
331 static int AddFormDataf(struct FormData **formp,
332 char *fmt, ...)
334 char s[1024];
335 va_list ap;
336 va_start(ap, fmt);
337 vsprintf(s, fmt, ap);
338 va_end(ap);
340 return AddFormData(formp, s, 0);
344 char *MakeFormBoundary(void)
346 char *retstring;
347 static int randomizer=0; /* this is just so that two boundaries within
348 the same form won't be identical */
349 int i;
351 static char table64[]=
352 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
354 retstring = (char *)malloc(BOUNDARY_LENGTH);
356 if(!retstring)
357 return NULL; /* failed */
359 srand(time(NULL)+randomizer++); /* seed */
361 strcpy(retstring, "curl"); /* bonus commercials 8*) */
363 for(i=4; i<(BOUNDARY_LENGTH-1); i++) {
364 retstring[i] = table64[rand()%64];
366 retstring[BOUNDARY_LENGTH-1]=0; /* zero terminate */
368 return retstring;
372 void FormFree(struct FormData *form)
374 struct FormData *next;
375 do {
376 next=form->next; /* the following form line */
377 free(form->line); /* free the line */
378 free(form); /* free the struct */
380 } while(form=next); /* continue */
383 struct FormData *getFormData(struct HttpPost *post,
384 int *sizep)
386 struct FormData *form = NULL;
387 struct FormData *firstform;
389 struct HttpPost *file;
391 int size =0;
392 char *boundary;
393 char *fileboundary=NULL;
395 if(!post)
396 return NULL; /* no input => no output! */
398 boundary = MakeFormBoundary();
400 /* Make the first line of the output */
401 AddFormDataf(&form,
402 "Content-Type: multipart/form-data;"
403 " boundary=%s\r\n",
404 boundary);
405 /* we DO NOT count that line since that'll be part of the header! */
407 firstform = form;
409 do {
411 /* boundary */
412 size += AddFormDataf(&form, "\r\n--%s\r\n", boundary);
414 size += AddFormDataf(&form,
415 "Content-Disposition: form-data; name=\"%s\"",
416 post->name);
418 if(post->more) {
419 /* If used, this is a link to more file names, we must then do
420 the magic to include several files with the same field name */
422 fileboundary = MakeFormBoundary();
424 size += AddFormDataf(&form,
425 "\r\nContent-Type: multipart/mixed,"
426 " boundary=%s\r\n",
427 fileboundary);
430 file = post;
432 do {
433 if(post->more) {
434 /* if multiple-file */
435 size += AddFormDataf(&form,
436 "\r\n--%s\r\nContent-Disposition: attachment; filename=\"%s\"",
437 fileboundary, file->contents);
439 else if(post->flags & HTTPPOST_FILENAME) {
440 size += AddFormDataf(&form,
441 "; filename=\"%s\"",
442 post->contents);
445 if(file->contenttype) {
446 /* we have a specified type */
447 size += AddFormDataf(&form,
448 "\r\nContent-Type: %s",
449 file->contenttype);
451 if(file->contenttype &&
452 !strnequal("text/", file->contenttype, 5)) {
453 /* this is not a text content, mention our binary encoding */
454 size += AddFormDataf(&form,
455 "\r\nContent-Transfer-Encoding: binary");
459 size += AddFormDataf(&form,
460 "\r\n\r\n");
462 if(post->flags & HTTPPOST_FILENAME) {
463 /* we should include the contents from the specified file */
464 FILE *fileread;
465 char buffer[1024];
466 int nread;
468 fileread = strequal("-", file->contents)?stdin:
469 /* binary read for win32 crap */
470 fopen(file->contents, "rb");
471 if(fileread) {
472 while((nread = fread(buffer, 1, 1024, fileread))) {
473 size += AddFormData(&form,
474 buffer,
475 nread);
477 if(fileread != stdin)
478 fclose(fileread);
480 else {
481 size += AddFormDataf(&form, "[File wasn't found by client]");
484 else {
485 /* include the contents we got */
486 size += AddFormDataf(&form,
487 post->contents);
489 } while((file = file->more)); /* for each specified file for this field */
491 if(post->more) {
492 /* this was a multiple-file inclusion, make a termination file
493 boundary: */
494 size += AddFormDataf(&form,
495 "\r\n--%s--",
496 fileboundary);
497 free(fileboundary);
500 } while((post=post->next)); /* for each field */
502 /* end-boundary for everything */
503 size += AddFormDataf(&form,
504 "\r\n--%s--\r\n",
505 boundary);
507 *sizep = size;
509 free(boundary);
511 return firstform;
514 int FormInit(struct Form *form, struct FormData *formdata )
516 form->data = formdata;
517 form->sent = 0;
519 if(!formdata)
520 return 1; /* error */
522 return 0;
525 /* fread() emulation */
526 int FormReader(char *buffer,
527 size_t size,
528 size_t nitems,
529 FILE *mydata)
531 struct Form *form;
532 int wantedsize;
533 int gotsize;
535 form=(struct Form *)mydata;
537 wantedsize = size * nitems;
539 if(!form->data)
540 return 0; /* nothing, error, empty */
542 do {
544 if( (form->data->length - form->sent ) > wantedsize ) {
546 memcpy(buffer, form->data->line + form->sent, wantedsize);
548 form->sent += wantedsize;
550 return wantedsize;
553 memcpy(buffer,
554 form->data->line + form->sent,
555 gotsize = (form->data->length - form->sent) );
557 form->sent = 0;
559 form->data = form->data->next; /* advance */
561 } while(!gotsize && form->data);
562 /* If we got an empty line and we have more data, we proceed to the next
563 line immediately to avoid returning zero before we've reached the end.
564 This is the bug reported November 22 1999 on curl 6.3. (Daniel) */
566 return gotsize;
570 #ifdef _FORM_DEBUG
572 int main(int argc, char **argv)
574 #if 0
575 char *testargs[]={
576 "name1 = data in number one",
577 "name2 = number two data",
578 "test = @upload"
580 #endif
581 int i;
582 char *nextarg;
583 struct HttpPost *httppost=NULL;
584 struct HttpPost *last_post=NULL;
585 struct HttpPost *post;
586 int size;
587 int nread;
588 char buffer[4096];
590 struct FormData *form;
591 struct Form formread;
593 for(i=1; i<argc; i++) {
595 if( FormParse( argv[i],
596 &httppost,
597 &last_post)) {
598 fprintf(stderr, "Illegally formatted input field: '%s'!\n",
599 argv[i]);
600 return 1;
604 form=getFormData(httppost, &size);
606 FormInit(&formread, form);
608 while(nread = FormReader(buffer, 1, sizeof(buffer), (FILE *)&formread)) {
609 fwrite(buffer, nread, 1, stderr);
612 fprintf(stderr, "size: %d\n", size);
614 return 0;
617 #endif