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>
31 * $Source: /cvsroot/curl/curl/lib/formdata.c,v $
33 * $Date: 1999-12-29 14:21:22 $
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>
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
67 #define BOUNDARY_LENGTH 32
69 /* What kind of Content-Type to use on un-specified files with unrecognized
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
,
80 *string
= strdup(value
);
83 /***************************************************************************
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 */
120 char contents
[1024]="";
126 char *prevtype
= NULL
;
129 struct HttpPost
*post
;
130 struct HttpPost
*subpost
; /* a sub-node */
133 if(1 <= sscanf(input
, "%255[^ =] = %1023[^\n]", name
, contents
)) {
134 /* the input was using the correct format */
137 if('@' == contp
[0]) {
138 /* we use the @-letter to indicate file name(s) */
140 flags
= HTTPPOST_FILENAME
;
146 /* since this was a file, it may have a content-type specifier
149 sep
=strchr(contp
, FORM_TYPE_SEPARATOR
);
150 sep2
=strchr(contp
, FORM_FILE_SEPARATOR
);
152 /* pick the closest */
153 if(sep2
&& (sep2
< sep
)) {
156 /* no type was specified! */
160 /* if we got here on a comma, don't do much */
161 if(FORM_FILE_SEPARATOR
!= *sep
)
162 type
= strstr(sep
+1, "type=");
166 *sep
=0; /* terminate file name at separator */
169 type
+= strlen("type=");
171 if(2 != sscanf(type
, "%127[^/]/%127[^,\n]",
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
);
185 sep
=strchr(contp
, FORM_FILE_SEPARATOR
);
188 /* the next file name starts here */
194 * No type was specified, we scan through a few well-known
195 * extensions and pick the first we match!
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"}
210 /* default to the previously set/used! */
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
)) {
220 strlen(contp
) - strlen(ctts
[i
].extension
),
221 ctts
[i
].extension
)) {
227 /* we have a type by now */
231 /* For the first file name, we allocate and initiate the main list
234 post
= (struct HttpPost
*)malloc(sizeof(struct HttpPost
));
236 memset(post
, 0, sizeof(struct HttpPost
));
237 GetStr(&post
->name
, name
); /* get the name */
238 GetStr(&post
->contents
, contp
); /* get the contents */
241 GetStr(&post
->contenttype
, type
); /* get type */
242 prevtype
=post
->contenttype
; /* point to the allocated string! */
244 /* make the previous point to this */
246 (*last_post
)->next
= post
;
255 /* we add a file name to the previously allocated node, known as
257 subpost
=(struct HttpPost
*)malloc(sizeof(struct HttpPost
));
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
;
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 */
278 post
= (struct HttpPost
*)malloc(sizeof(struct HttpPost
));
280 memset(post
, 0, sizeof(struct HttpPost
));
281 GetStr(&post
->name
, name
); /* get the name */
282 GetStr(&post
->contents
, contp
); /* get the contents */
285 /* make the previous point to this */
287 (*last_post
)->next
= post
;
298 fprintf(stderr
, "Illegally formatted input field!\n");
304 static int AddFormData(struct FormData
**formp
,
308 struct FormData
*newform
= (struct FormData
*)
309 malloc(sizeof(struct FormData
));
310 newform
->next
= NULL
;
312 /* we make it easier for plain strings: */
314 length
= strlen((char *)line
);
316 newform
->line
= (char *)malloc(length
+1);
317 memcpy(newform
->line
, line
, length
+1);
318 newform
->length
= length
;
321 (*formp
)->next
= newform
;
331 static int AddFormDataf(struct FormData
**formp
,
337 vsprintf(s
, fmt
, ap
);
340 return AddFormData(formp
, s
, 0);
344 char *MakeFormBoundary(void)
347 static int randomizer
=0; /* this is just so that two boundaries within
348 the same form won't be identical */
351 static char table64
[]=
352 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
354 retstring
= (char *)malloc(BOUNDARY_LENGTH
);
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 */
372 void FormFree(struct FormData
*form
)
374 struct FormData
*next
;
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
,
386 struct FormData
*form
= NULL
;
387 struct FormData
*firstform
;
389 struct HttpPost
*file
;
393 char *fileboundary
=NULL
;
396 return NULL
; /* no input => no output! */
398 boundary
= MakeFormBoundary();
400 /* Make the first line of the output */
402 "Content-Type: multipart/form-data;"
405 /* we DO NOT count that line since that'll be part of the header! */
412 size
+= AddFormDataf(&form
, "\r\n--%s\r\n", boundary
);
414 size
+= AddFormDataf(&form
,
415 "Content-Disposition: form-data; name=\"%s\"",
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,"
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
,
445 if(file
->contenttype
) {
446 /* we have a specified type */
447 size
+= AddFormDataf(&form
,
448 "\r\nContent-Type: %s",
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
,
462 if(post
->flags
& HTTPPOST_FILENAME
) {
463 /* we should include the contents from the specified file */
468 fileread
= strequal("-", file
->contents
)?stdin
:
469 /* binary read for win32 crap */
470 fopen(file
->contents
, "rb");
472 while((nread
= fread(buffer
, 1, 1024, fileread
))) {
473 size
+= AddFormData(&form
,
477 if(fileread
!= stdin
)
481 size
+= AddFormDataf(&form
, "[File wasn't found by client]");
485 /* include the contents we got */
486 size
+= AddFormDataf(&form
,
489 } while((file
= file
->more
)); /* for each specified file for this field */
492 /* this was a multiple-file inclusion, make a termination file
494 size
+= AddFormDataf(&form
,
500 } while((post
=post
->next
)); /* for each field */
502 /* end-boundary for everything */
503 size
+= AddFormDataf(&form
,
514 int FormInit(struct Form
*form
, struct FormData
*formdata
)
516 form
->data
= formdata
;
520 return 1; /* error */
525 /* fread() emulation */
526 int FormReader(char *buffer
,
535 form
=(struct Form
*)mydata
;
537 wantedsize
= size
* nitems
;
540 return 0; /* nothing, error, empty */
544 if( (form
->data
->length
- form
->sent
) > wantedsize
) {
546 memcpy(buffer
, form
->data
->line
+ form
->sent
, wantedsize
);
548 form
->sent
+= wantedsize
;
554 form
->data
->line
+ form
->sent
,
555 gotsize
= (form
->data
->length
- form
->sent
) );
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) */
572 int main(int argc
, char **argv
)
576 "name1 = data in number one",
577 "name2 = number two data",
583 struct HttpPost
*httppost
=NULL
;
584 struct HttpPost
*last_post
=NULL
;
585 struct HttpPost
*post
;
590 struct FormData
*form
;
591 struct Form formread
;
593 for(i
=1; i
<argc
; i
++) {
595 if( FormParse( argv
[i
],
598 fprintf(stderr
, "Illegally formatted input field: '%s'!\n",
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
);