moved here from the newlib branch
[platform/upstream/curl.git] / lib / formdata.c
1 /*****************************************************************************
2  *                                  _   _ ____  _     
3  *  Project                     ___| | | |  _ \| |    
4  *                             / __| | | | |_) | |    
5  *                            | (__| |_| |  _ <| |___ 
6  *                             \___|\___/|_| \_\_____|
7  *
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/
12  *
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.
17  *
18  *  The Original Code is Curl.
19  *
20  *  The Initial Developer of the Original Code is Daniel Stenberg.
21  *
22  *  Portions created by the Initial Developer are Copyright (C) 1998.
23  *  All Rights Reserved.
24  *
25  * ------------------------------------------------------------
26  * Main author:
27  * - Daniel Stenberg <Daniel.Stenberg@haxx.nu>
28  *
29  *      http://curl.haxx.nu
30  *
31  * $Source$
32  * $Revision$
33  * $Date$
34  * $Author$
35  * $State$
36  * $Locker$
37  *
38  * ------------------------------------------------------------
39  ****************************************************************************/
40
41 /*
42   Debug the form generator stand-alone by compiling this source file with:
43
44   gcc -DHAVE_CONFIG_H -I../ -g -D_FORM_DEBUG -o formdata -I../include formdata.c
45
46   run the 'formdata' executable and make sure the output is ok!
47
48   try './formdata "name=Daniel" "poo=noo" "foo=bar"' and similarly
49
50  */
51
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <stdarg.h>
56
57 #include <time.h>
58
59 #include "setup.h"
60 #include <curl/curl.h>
61 #include "formdata.h"
62
63 #include "strequal.h"
64
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
68    combinations... */
69 #define BOUNDARY_LENGTH 32
70
71 /* What kind of Content-Type to use on un-specified files with unrecognized
72    extensions. */
73 #define HTTPPOST_CONTENTTYPE_DEFAULT "text/plain"
74
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,
78                    char *value)
79 {
80   if(*string)
81     free(*string);
82   *string = strdup(value);
83 }
84
85 /***************************************************************************
86  *
87  * FormParse()
88  *      
89  * Reads a 'name=value' paramter and builds the appropriate linked list.
90  *
91  * Specify files to upload with 'name=@filename'. Supports specified
92  * given Content-Type of the files. Such as ';type=<content-type>'.
93  *
94  * You may specify more than one file for a single name (field). Specify
95  * multiple files by writing it like:
96  *
97  * 'name=@filename,filename2,filename3'
98  *
99  * If you want content-types specified for each too, write them like:
100  *
101  * 'name=@filename;type=image/gif,filename2,filename3'
102  *
103  ***************************************************************************/
104
105 int curl_FormParse(char *input,
106                    struct HttpPost **httppost,
107                    struct HttpPost **last_post)
108 {
109   return FormParse(input, httppost, last_post);
110 }
111
112 #define FORM_FILE_SEPARATOR ','
113 #define FORM_TYPE_SEPARATOR ';'
114
115 int FormParse(char *input,
116               struct HttpPost **httppost,
117               struct HttpPost **last_post)
118 {
119   /* nextarg MUST be a string in the format 'name=contents' and we'll
120      build a linked list with the info */
121   char name[256];
122   char contents[1024]="";
123   char major[128];
124   char minor[128];
125   long flags = 0;
126   char *contp;
127   char *type = NULL;
128   char *prevtype = NULL;
129   char *sep;
130   char *sep2;
131   struct HttpPost *post;
132   struct HttpPost *subpost; /* a sub-node */
133   unsigned int i;
134
135   if(1 <= sscanf(input, "%255[^ =] = %1023[^\n]", name, contents)) {
136     /* the input was using the correct format */
137     contp = contents;
138
139     if('@' == contp[0]) {
140       /* we use the @-letter to indicate file name(s) */
141       
142       flags = HTTPPOST_FILENAME;
143       contp++;
144
145       post=NULL;
146
147       do {
148         /* since this was a file, it may have a content-type specifier
149            at the end too */
150
151         sep=strchr(contp, FORM_TYPE_SEPARATOR);
152         sep2=strchr(contp, FORM_FILE_SEPARATOR);
153
154         /* pick the closest */
155         if(sep2 && (sep2 < sep)) {
156           sep = sep2;
157
158           /* no type was specified! */
159         }
160         if(sep) {
161
162           /* if we got here on a comma, don't do much */
163           if(FORM_FILE_SEPARATOR != *sep)
164             type = strstr(sep+1, "type=");
165           else
166             type=NULL;
167
168           *sep=0; /* terminate file name at separator */
169
170           if(type) {
171             type += strlen("type=");
172             
173             if(2 != sscanf(type, "%127[^/]/%127[^,\n]",
174                            major, minor)) {
175               fprintf(stderr, "Illegally formatted content-type field!\n");
176               return 2; /* illegal content-type syntax! */
177             }
178             /* now point beyond the content-type specifier */
179             sep = type + strlen(major)+strlen(minor)+1;
180
181             /* find the following comma */
182             sep=strchr(sep, FORM_FILE_SEPARATOR);
183           }
184         }
185         else {
186           type=NULL;
187           sep=strchr(contp, FORM_FILE_SEPARATOR);
188         }
189         if(sep) {
190           /* the next file name starts here */
191           *sep =0;
192           sep++;
193         }
194         if(!type) {
195           /*
196            * No type was specified, we scan through a few well-known
197            * extensions and pick the first we match!
198            */
199           struct ContentType {
200             char *extension;
201             char *type;
202           };
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"}
209           };
210
211           if(prevtype)
212             /* default to the previously set/used! */
213             type = prevtype;
214           else
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;
218
219           for(i=0; i<sizeof(ctts)/sizeof(ctts[0]); i++) {
220             if(strlen(contp) >= strlen(ctts[i].extension)) {
221               if(strequal(contp +
222                           strlen(contp) - strlen(ctts[i].extension),
223                           ctts[i].extension)) {
224                 type = ctts[i].type;
225                 break;
226               }       
227             }
228           }
229           /* we have a type by now */
230         }
231
232         if(NULL == post) {
233           /* For the first file name, we allocate and initiate the main list
234              node */
235
236           post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
237           if(post) {
238             memset(post, 0, sizeof(struct HttpPost));
239             GetStr(&post->name, name);      /* get the name */
240             GetStr(&post->contents, contp); /* get the contents */
241             post->flags = flags;
242             if(type) {
243               GetStr(&post->contenttype, type); /* get type */
244               prevtype=post->contenttype; /* point to the allocated string! */
245             }
246             /* make the previous point to this */
247             if(*last_post)
248               (*last_post)->next = post;
249             else
250               (*httppost) = post;
251
252             (*last_post) = post;          
253           }
254
255         }
256         else {
257           /* we add a file name to the previously allocated node, known as
258              'post' now */
259           subpost =(struct HttpPost *)malloc(sizeof(struct HttpPost));
260           if(subpost) {
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;
265              if(type) {
266                GetStr(&subpost->contenttype, type); /* get type */
267                prevtype=subpost->contenttype; /* point to the allocated string! */
268              }
269              /* now, point our 'more' to the original 'more' */
270              subpost->more = post->more;
271
272              /* then move the original 'more' to point to ourselves */
273              post->more = subpost;           
274           }
275         }
276         contp = sep; /* move the contents pointer to after the separator */
277       } while(sep && *sep); /* loop if there's another file name */
278     }
279     else {
280       post = (struct HttpPost *)malloc(sizeof(struct HttpPost));
281       if(post) {
282         memset(post, 0, sizeof(struct HttpPost));
283         GetStr(&post->name, name);      /* get the name */
284         GetStr(&post->contents, contp); /* get the contents */
285         post->flags = 0;
286
287         /* make the previous point to this */
288         if(*last_post)
289           (*last_post)->next = post;
290         else
291           (*httppost) = post;
292
293         (*last_post) = post;      
294       }
295
296     }
297
298   }
299   else {
300     fprintf(stderr, "Illegally formatted input field!\n");
301     return 1;
302   }
303   return 0;
304 }
305
306 static int AddFormData(struct FormData **formp,
307                         void *line,
308                         long length)
309 {
310   struct FormData *newform = (struct FormData *)
311     malloc(sizeof(struct FormData));
312   newform->next = NULL;
313
314   /* we make it easier for plain strings: */
315   if(!length)
316     length = strlen((char *)line);
317
318   newform->line = (char *)malloc(length+1);
319   memcpy(newform->line, line, length+1);
320   newform->length = length;
321   
322   if(*formp) {
323     (*formp)->next = newform;
324     *formp = newform;
325   }
326   else
327     *formp = newform;
328
329   return length;
330 }
331
332
333 static int AddFormDataf(struct FormData **formp,
334                          char *fmt, ...)
335 {
336   char s[1024];
337   va_list ap;
338   va_start(ap, fmt);
339   vsprintf(s, fmt, ap);
340   va_end(ap);
341
342   return AddFormData(formp, s, 0);
343 }
344
345
346 char *MakeFormBoundary(void)
347 {
348   char *retstring;
349   static int randomizer=0; /* this is just so that two boundaries within
350                               the same form won't be identical */
351   int i;
352
353   static char table64[]=
354     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
355
356   retstring = (char *)malloc(BOUNDARY_LENGTH);
357
358   if(!retstring)
359     return NULL; /* failed */
360
361   srand(time(NULL)+randomizer++); /* seed */
362
363   strcpy(retstring, "curl"); /* bonus commercials 8*) */
364
365   for(i=4; i<(BOUNDARY_LENGTH-1); i++) {
366     retstring[i] = table64[rand()%64];
367   }
368   retstring[BOUNDARY_LENGTH-1]=0; /* zero terminate */
369
370   return retstring;
371 }
372  
373
374 void FormFree(struct FormData *form)
375 {
376   struct FormData *next;
377   do {
378     next=form->next;  /* the following form line */
379     free(form->line); /* free the line */
380     free(form);       /* free the struct */
381
382   } while((form=next)); /* continue */
383 }
384
385 struct FormData *getFormData(struct HttpPost *post,
386                              int *sizep)
387 {
388   struct FormData *form = NULL;
389   struct FormData *firstform;
390
391   struct HttpPost *file;
392
393   int size =0;
394   char *boundary;
395   char *fileboundary=NULL;
396
397   if(!post)
398     return NULL; /* no input => no output! */
399
400   boundary = MakeFormBoundary();
401   
402   /* Make the first line of the output */
403   AddFormDataf(&form,
404                "Content-Type: multipart/form-data;"
405                " boundary=%s\r\n",
406                boundary);
407   /* we DO NOT count that line since that'll be part of the header! */
408
409   firstform = form;
410   
411   do {
412
413     /* boundary */
414     size += AddFormDataf(&form, "\r\n--%s\r\n", boundary);
415
416     size += AddFormDataf(&form,
417                          "Content-Disposition: form-data; name=\"%s\"",
418                          post->name);
419
420     if(post->more) {
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 */
423
424       fileboundary = MakeFormBoundary();
425
426       size += AddFormDataf(&form,
427                            "\r\nContent-Type: multipart/mixed,"
428                            " boundary=%s\r\n",
429                            fileboundary);
430     }
431
432     file = post;
433
434     do {
435       if(post->more) {
436         /* if multiple-file */
437         size += AddFormDataf(&form,
438                              "\r\n--%s\r\nContent-Disposition: attachment; filename=\"%s\"",
439                              fileboundary, file->contents);
440       }
441       else if(post->flags & HTTPPOST_FILENAME) {
442         size += AddFormDataf(&form,
443                              "; filename=\"%s\"",
444                              post->contents);
445       }
446       
447       if(file->contenttype) {
448         /* we have a specified type */
449         size += AddFormDataf(&form,
450                              "\r\nContent-Type: %s",
451                              file->contenttype);
452       }
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");
458       }
459
460
461       size += AddFormDataf(&form,
462                            "\r\n\r\n");
463
464       if(post->flags & HTTPPOST_FILENAME) {
465         /* we should include the contents from the specified file */
466         FILE *fileread;
467         char buffer[1024];
468         int nread;
469
470         fileread = strequal("-", file->contents)?stdin:
471           /* binary read for win32 crap */
472           fopen(file->contents, "rb");
473         if(fileread) {
474           while((nread = fread(buffer, 1, 1024, fileread))) {
475             size += AddFormData(&form,
476                                 buffer,
477                                 nread);
478           }
479           if(fileread != stdin)
480             fclose(fileread);
481         }
482         else {
483           size += AddFormDataf(&form, "[File wasn't found by client]");
484         }
485       }
486       else {
487         /* include the contents we got */
488         size += AddFormDataf(&form,
489                              post->contents);
490       }
491     } while((file = file->more)); /* for each specified file for this field */
492
493     if(post->more) {
494       /* this was a multiple-file inclusion, make a termination file
495          boundary: */
496       size += AddFormDataf(&form,
497                            "\r\n--%s--",
498                            fileboundary);     
499       free(fileboundary);
500     }
501
502   } while((post=post->next)); /* for each field */
503
504   /* end-boundary for everything */
505   size += AddFormDataf(&form,
506                        "\r\n--%s--\r\n",
507                        boundary);
508
509   *sizep = size;
510
511   free(boundary);
512
513   return firstform;
514 }
515
516 int FormInit(struct Form *form, struct FormData *formdata )
517 {
518   if(!formdata)
519     return 1; /* error */
520   
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");
525
526   form->data = formdata;
527   form->sent = 0;
528
529   return 0;
530 }
531
532 /* fread() emulation */
533 int FormReader(char *buffer,
534                size_t size,
535                size_t nitems,
536                FILE *mydata)
537 {
538   struct Form *form;
539   int wantedsize;
540   int gotsize;
541
542   form=(struct Form *)mydata;
543
544   wantedsize = size * nitems;
545
546   if(!form->data)
547     return 0; /* nothing, error, empty */
548
549   do {
550   
551     if( (form->data->length - form->sent ) > wantedsize ) {
552
553       memcpy(buffer, form->data->line + form->sent, wantedsize);
554
555       form->sent += wantedsize;
556
557       return wantedsize;
558     }
559
560     memcpy(buffer,
561            form->data->line + form->sent,
562            gotsize = (form->data->length - form->sent) );
563
564     form->sent = 0;
565
566     form->data = form->data->next; /* advance */
567
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) */
572
573   return gotsize;
574 }
575
576
577 #ifdef _FORM_DEBUG
578
579 int main(int argc, char **argv)
580 {
581 #if 0
582   char *testargs[]={
583     "name1 = data in number one",
584     "name2 = number two data",
585     "test = @upload"
586   };
587 #endif
588   int i;
589   char *nextarg;
590   struct HttpPost *httppost=NULL;
591   struct HttpPost *last_post=NULL;
592   struct HttpPost *post;
593   int size;
594   int nread;
595   char buffer[4096];
596
597   struct FormData *form;
598   struct Form formread;
599
600   for(i=1; i<argc; i++) {
601
602     if( FormParse( argv[i],
603                    &httppost,
604                    &last_post)) {
605       fprintf(stderr, "Illegally formatted input field: '%s'!\n",
606               argv[i]);
607       return 1;
608     }
609   }
610
611   form=getFormData(httppost, &size);
612
613   FormInit(&formread, form);
614
615   while(nread = FormReader(buffer, 1, sizeof(buffer), (FILE *)&formread)) {
616     fwrite(buffer, nread, 1, stderr);
617   }
618
619   fprintf(stderr, "size: %d\n", size);
620
621   return 0;
622 }
623
624 #endif