• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2001-2004 Brandon Long
3  * All Rights Reserved.
4  *
5  * ClearSilver Templating System
6  *
7  * This code is made available under the terms of the ClearSilver License.
8  * http://www.clearsilver.net/license.hdf
9  *
10  */
11 
12 /* rfc2388 defines multipart/form-data which is primarily used for
13  * HTTP file upload
14  */
15 
16 #include "cs_config.h"
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <sys/stat.h>
22 #include <limits.h>
23 #include <ctype.h>
24 #include <string.h>
25 #include "util/neo_misc.h"
26 #include "util/neo_err.h"
27 #include "util/neo_str.h"
28 #include "cgi.h"
29 #include "cgiwrap.h"
30 
_header_value(char * hdr,char ** val)31 static NEOERR * _header_value (char *hdr, char **val)
32 {
33   char *p, *q;
34   int l;
35 
36   *val = NULL;
37 
38   p = hdr;
39   while (*p && isspace(*p)) p++;
40   q = p;
41   while (*q && !isspace(*q) && *q != ';') q++;
42   if (!*p || p == q) return STATUS_OK;
43 
44   l = q - p;
45   *val = (char *) malloc (l+1);
46   if (*val == NULL)
47     return nerr_raise (NERR_NOMEM, "Unable to allocate space for val");
48   memcpy (*val, p, l);
49   (*val)[l] = '\0';
50 
51   return STATUS_OK;
52 }
53 
_header_attr(char * hdr,char * attr,char ** val)54 static NEOERR * _header_attr (char *hdr, char *attr, char **val)
55 {
56   char *p, *k, *v;
57   int found = 0;
58   int l, al;
59   char *r;
60 
61   *val = NULL;
62   l = strlen(attr);
63 
64   /* skip value */
65   p = hdr;
66   while (*p && *p != ';') p++;
67   if (!*p) return STATUS_OK;
68 
69   p++;
70   while(*p && !found)
71   {
72     while (*p && isspace(*p)) p++;
73     if (!*p) return STATUS_OK;
74     /* attr name */
75     k = p;
76     while (*p && !isspace(*p) && *p != ';' && *p != '=') p++;
77     if (!*p) return STATUS_OK;
78     if (l == (p-k) && !strncasecmp(attr, k, l))
79       found = 1;
80 
81     while (*p && isspace(*p)) p++;
82     if (*p != ';' && *p != '=') return STATUS_OK;
83     if (*p == ';')
84     {
85       if (found)
86       {
87 	*val = strdup ("");
88 	if (*val == NULL)
89 	  return nerr_raise (NERR_NOMEM, "Unable to allocate value");
90 	return STATUS_OK;
91       }
92     }
93     else
94     {
95       p++;
96       if (*p == '"')
97       {
98 	v = ++p;
99 	while (*p && *p != '"') p++;
100 	al = p-v;
101 	if (*p) p++;
102       }
103       else
104       {
105 	v = p;
106 	while (*p && !isspace(*p) && *p != ';') p++;
107 	al = p-v;
108       }
109       if (found)
110       {
111 	r = (char *) malloc (al+1);
112 	if (r == NULL)
113 	  return nerr_raise (NERR_NOMEM, "Unable to allocate value");
114 	memcpy (r, v, al);
115 	r[al] = '\0';
116 	*val = r;
117 	return STATUS_OK;
118       }
119     }
120     if (*p) p++;
121   }
122   return STATUS_OK;
123 }
124 
_read_line(CGI * cgi,char ** s,int * l,int * done)125 static NEOERR * _read_line (CGI *cgi, char **s, int *l, int *done)
126 {
127   int ofs = 0;
128   char *p;
129   int to_read;
130 
131   if (cgi->buf == NULL)
132   {
133     cgi->buflen = 4096;
134     cgi->buf = (char *) malloc (sizeof(char) * cgi->buflen);
135     if (cgi->buf == NULL)
136       return nerr_raise (NERR_NOMEM, "Unable to allocate cgi buf");
137   }
138   if (cgi->unget)
139   {
140     cgi->unget = FALSE;
141     *s = cgi->last_start;
142     *l = cgi->last_length;
143     return STATUS_OK;
144   }
145   if (cgi->found_nl)
146   {
147     p = memchr (cgi->buf + cgi->nl, '\n', cgi->readlen - cgi->nl);
148     if (p) {
149       cgi->last_start = *s = cgi->buf + cgi->nl;
150       cgi->last_length = *l = p - (cgi->buf + cgi->nl) + 1;
151       cgi->found_nl = TRUE;
152       cgi->nl = p - cgi->buf + 1;
153       return STATUS_OK;
154     }
155     ofs = cgi->readlen - cgi->nl;
156     memmove(cgi->buf, cgi->buf + cgi->nl, ofs);
157   }
158   // Read either as much buffer space as we have left, or up to
159   // the amount of data remaining according to Content-Length
160   // If there is no Content-Length, just use the buffer space, but recognize
161   // that it might not work on some servers or cgiwrap implementations.
162   // Some servers will close their end of the stdin pipe, so cgiwrap_read
163   // will return if we ask for too much.  Techically, not including
164   // Content-Length is against the HTTP spec, so we should consider failing
165   // earlier if we don't have a length.
166   to_read = cgi->buflen - ofs;
167   if (cgi->data_expected && (to_read > cgi->data_expected - cgi->data_read))
168   {
169     to_read = cgi->data_expected - cgi->data_read;
170   }
171   cgiwrap_read (cgi->buf + ofs, to_read, &(cgi->readlen));
172   if (cgi->readlen < 0)
173   {
174     return nerr_raise_errno (NERR_IO, "POST Read Error");
175   }
176   if (cgi->readlen == 0)
177   {
178     *done = 1;
179     return STATUS_OK;
180   }
181   cgi->data_read += cgi->readlen;
182   if (cgi->upload_cb)
183   {
184     if (cgi->upload_cb (cgi, cgi->data_read, cgi->data_expected))
185       return nerr_raise (CGIUploadCancelled, "Upload Cancelled");
186   }
187   cgi->readlen += ofs;
188   p = memchr (cgi->buf, '\n', cgi->readlen);
189   if (!p)
190   {
191     cgi->found_nl = FALSE;
192     cgi->last_start = *s = cgi->buf;
193     cgi->last_length = *l = cgi->readlen;
194     return STATUS_OK;
195   }
196   cgi->last_start = *s = cgi->buf;
197   cgi->last_length = *l = p - cgi->buf + 1;
198   cgi->found_nl = TRUE;
199   cgi->nl = *l;
200   return STATUS_OK;
201 }
202 
_read_header_line(CGI * cgi,STRING * line,int * done)203 static NEOERR * _read_header_line (CGI *cgi, STRING *line, int *done)
204 {
205   NEOERR *err;
206   char *s, *p;
207   int l;
208 
209   err = _read_line (cgi, &s, &l, done);
210   if (err) return nerr_pass (err);
211   if (*done || (l == 0)) return STATUS_OK;
212   if (isspace (s[0])) return STATUS_OK;
213   while (l && isspace(s[l-1])) l--;
214   err = string_appendn (line, s, l);
215   if (err) return nerr_pass (err);
216 
217   while (1)
218   {
219     err = _read_line (cgi, &s, &l, done);
220     if (err) break;
221     if (l == 0) break;
222     if (*done) break;
223     if (!(s[0] == ' ' || s[0] == '\t'))
224     {
225       cgi->unget = TRUE;
226       break;
227     }
228     while (l && isspace(s[l-1])) l--;
229     p = s;
230     while (*p && isspace(*p) && (p-s < l)) p++;
231     err = string_append_char (line, ' ');
232     if (err) break;
233     err = string_appendn (line, p, l - (p-s));
234     if (err) break;
235     if (line->len > 50*1024*1024)
236     {
237       string_clear(line);
238       return nerr_raise(NERR_ASSERT, "read_header_line exceeded 50MB");
239     }
240   }
241   return nerr_pass (err);
242 }
243 
_is_boundary(char * boundary,char * s,int l,int * done)244 static BOOL _is_boundary (char *boundary, char *s, int l, int *done)
245 {
246   static char *old_boundary = NULL;
247   static int bl;
248 
249   /* cache the boundary strlen... more pointless optimization by blong */
250   if (old_boundary != boundary)
251   {
252     old_boundary = boundary;
253     bl = strlen(boundary);
254   }
255 
256   if (s[l-1] != '\n')
257     return FALSE;
258   l--;
259   if (s[l-1] == '\r')
260     l--;
261 
262   if (bl+2 == l && s[0] == '-' && s[1] == '-' && !strncmp (s+2, boundary, bl))
263     return TRUE;
264   if (bl+4 == l && s[0] == '-' && s[1] == '-' &&
265       !strncmp (s+2, boundary, bl) &&
266       s[l-1] == '-' && s[l-2] == '-')
267   {
268     *done = 1;
269     return TRUE;
270   }
271   return FALSE;
272 }
273 
_find_boundary(CGI * cgi,char * boundary,int * done)274 static NEOERR * _find_boundary (CGI *cgi, char *boundary, int *done)
275 {
276   NEOERR *err;
277   char *s;
278   int l;
279 
280   *done = 0;
281   while (1)
282   {
283     err = _read_line (cgi, &s, &l, done);
284     if (err) return nerr_pass (err);
285     if ((l == 0) || (*done)) {
286       *done = 1;
287       return STATUS_OK;
288     }
289     if (_is_boundary(boundary, s, l, done))
290       return STATUS_OK;
291   }
292   return STATUS_OK;
293 }
294 
open_upload(CGI * cgi,int unlink_files,FILE ** fpw)295 NEOERR *open_upload(CGI *cgi, int unlink_files, FILE **fpw)
296 {
297   NEOERR *err = STATUS_OK;
298   FILE *fp;
299   char path[_POSIX_PATH_MAX];
300   int fd;
301 
302   *fpw = NULL;
303 
304   snprintf (path, sizeof(path), "%s/cgi_upload.XXXXXX",
305       hdf_get_value(cgi->hdf, "Config.Upload.TmpDir", "/var/tmp"));
306 
307   fd = mkstemp(path);
308   if (fd == -1)
309   {
310     return nerr_raise_errno (NERR_SYSTEM, "Unable to open temp file %s",
311 	path);
312   }
313 
314   fp = fdopen (fd, "w+");
315   if (fp == NULL)
316   {
317     close(fd);
318     return nerr_raise_errno (NERR_SYSTEM, "Unable to fdopen file %s", path);
319   }
320   if (unlink_files) unlink(path);
321   if (cgi->files == NULL)
322   {
323     err = uListInit (&(cgi->files), 10, 0);
324     if (err)
325     {
326       fclose(fp);
327       return nerr_pass(err);
328     }
329   }
330   err = uListAppend (cgi->files, fp);
331   if (err)
332   {
333     fclose (fp);
334     return nerr_pass(err);
335   }
336   if (!unlink_files) {
337     if (cgi->filenames == NULL)
338     {
339       err = uListInit (&(cgi->filenames), 10, 0);
340       if (err)
341       {
342 	fclose(fp);
343 	return nerr_pass(err);
344       }
345     }
346     err = uListAppend (cgi->filenames, strdup(path));
347     if (err)
348     {
349       fclose (fp);
350       return nerr_pass(err);
351     }
352   }
353   *fpw = fp;
354   return STATUS_OK;
355 }
356 
_read_part(CGI * cgi,char * boundary,int * done)357 static NEOERR * _read_part (CGI *cgi, char *boundary, int *done)
358 {
359   NEOERR *err = STATUS_OK;
360   STRING str;
361   HDF *child, *obj = NULL;
362   FILE *fp = NULL;
363   char buf[256];
364   char *p;
365   char *name = NULL, *filename = NULL;
366   char *type = NULL, *tmp = NULL;
367   char *last = NULL;
368   int unlink_files = hdf_get_int_value(cgi->hdf, "Config.Upload.Unlink", 1);
369 
370   string_init (&str);
371 
372   while (1)
373   {
374     err = _read_header_line (cgi, &str, done);
375     if (err) break;
376     if (*done) break;
377     if (str.buf == NULL || str.buf[0] == '\0') break;
378     p = strchr (str.buf, ':');
379     if (p)
380     {
381       *p = '\0';
382       if (!strcasecmp(str.buf, "content-disposition"))
383       {
384 	err = _header_attr (p+1, "name", &name);
385 	if (err) break;
386 	err = _header_attr (p+1, "filename", &filename);
387 	if (err) break;
388       }
389       else if (!strcasecmp(str.buf, "content-type"))
390       {
391 	err = _header_value (p+1, &type);
392 	if (err) break;
393       }
394       else if (!strcasecmp(str.buf, "content-encoding"))
395       {
396 	err = _header_value (p+1, &tmp);
397 	if (err) break;
398 	if (tmp && strcmp(tmp, "7bit") && strcmp(tmp, "8bit") &&
399 	    strcmp(tmp, "binary"))
400 	{
401 	  free(tmp);
402 	  err = nerr_raise (NERR_ASSERT, "form-data encoding is not supported");
403 	  break;
404 	}
405 	free(tmp);
406       }
407     }
408     string_set(&str, "");
409   }
410   if (err)
411   {
412     string_clear(&str);
413     if (name) free(name);
414     if (filename) free(filename);
415     if (type) free(type);
416     return nerr_pass (err);
417   }
418 
419   do
420   {
421     if (filename)
422     {
423       err = open_upload(cgi, unlink_files, &fp);
424       if (err) break;
425     }
426 
427     string_set(&str, "");
428     while (!(*done))
429     {
430       char *s;
431       int l, w;
432 
433       err = _read_line (cgi, &s, &l, done);
434       if (err) break;
435       if (*done || (l == 0)) break;
436       if (_is_boundary(boundary, s, l, done)) break;
437       if (filename)
438       {
439 	if (last) fwrite (last, sizeof(char), strlen(last), fp);
440 	if (l > 1 && s[l-1] == '\n' && s[l-2] == '\r')
441 	{
442 	  last = "\r\n";
443 	  l-=2;
444 	}
445 	else if (l > 0 && s[l-1] == '\n')
446 	{
447 	  last = "\n";
448 	  l--;
449 	}
450 	else last = NULL;
451 	w = fwrite (s, sizeof(char), l, fp);
452 	if (w != l)
453 	{
454 	  err = nerr_raise_errno (NERR_IO,
455 	      "Short write on file %s upload %d < %d", filename, w, l);
456 	  break;
457 	}
458       }
459       else
460       {
461 	err = string_appendn(&str, s, l);
462 	if (err) break;
463       }
464     }
465     if (err) break;
466   } while (0);
467 
468   /* Set up the cgi data */
469   if (!err)
470   {
471     do {
472       /* FIXME: Hmm, if we've seen the same name here before, what should we do?
473        */
474       if (filename)
475       {
476 	fseek(fp, 0, SEEK_SET);
477 	snprintf (buf, sizeof(buf), "Query.%s", name);
478 	err = hdf_set_value (cgi->hdf, buf, filename);
479 	if (!err && type)
480 	{
481 	  snprintf (buf, sizeof(buf), "Query.%s.Type", name);
482 	  err = hdf_set_value (cgi->hdf, buf, type);
483 	}
484 	if (!err)
485 	{
486 	  snprintf (buf, sizeof(buf), "Query.%s.FileHandle", name);
487 	  err = hdf_set_int_value (cgi->hdf, buf, uListLength(cgi->files));
488 	}
489 	if (!err && !unlink_files)
490 	{
491 	  char *path;
492 	  snprintf (buf, sizeof(buf), "Query.%s.FileName", name);
493 	  err = uListGet(cgi->filenames, uListLength(cgi->filenames)-1,
494 	      (void *)&path);
495 	  if (!err) err = hdf_set_value (cgi->hdf, buf, path);
496 	}
497       }
498       else
499       {
500 	snprintf (buf, sizeof(buf), "Query.%s", name);
501 	while (str.len && isspace(str.buf[str.len-1]))
502 	{
503 	  str.buf[str.len-1] = '\0';
504 	  str.len--;
505 	}
506 	if (!(cgi->ignore_empty_form_vars && str.len == 0))
507 	{
508 	  /* If we've seen it before... we force it into a list */
509 	  obj = hdf_get_obj (cgi->hdf, buf);
510 	  if (obj != NULL)
511 	  {
512 	    int i = 0;
513 	    char buf2[10];
514 	    char *t;
515 	    child = hdf_obj_child (obj);
516 	    if (child == NULL)
517 	    {
518 	      t = hdf_obj_value (obj);
519 	      err = hdf_set_value (obj, "0", t);
520 	      if (err != STATUS_OK) break;
521 	      i = 1;
522 	    }
523 	    else
524 	    {
525 	      while (child != NULL)
526 	      {
527 		i++;
528 		child = hdf_obj_next (child);
529 		if (err != STATUS_OK) break;
530 	      }
531 	      if (err != STATUS_OK) break;
532 	    }
533 	    snprintf (buf2, sizeof(buf2), "%d", i);
534 	    err = hdf_set_value (obj, buf2, str.buf);
535 	    if (err != STATUS_OK) break;
536 	  }
537 	  err = hdf_set_value (cgi->hdf, buf, str.buf);
538 	}
539       }
540     } while (0);
541   }
542 
543   string_clear(&str);
544   if (name) free(name);
545   if (filename) free(filename);
546   if (type) free(type);
547 
548   return nerr_pass (err);
549 }
550 
parse_rfc2388(CGI * cgi)551 NEOERR * parse_rfc2388 (CGI *cgi)
552 {
553   NEOERR *err;
554   char *ct_hdr;
555   char *boundary = NULL;
556   int l;
557   int done = 0;
558 
559   l = hdf_get_int_value (cgi->hdf, "CGI.ContentLength", -1);
560   ct_hdr = hdf_get_value (cgi->hdf, "CGI.ContentType", NULL);
561   if (ct_hdr == NULL)
562     return nerr_raise (NERR_ASSERT, "No content type header?");
563 
564   cgi->data_expected = l;
565   cgi->data_read = 0;
566   if (cgi->upload_cb)
567   {
568     if (cgi->upload_cb (cgi, cgi->data_read, cgi->data_expected))
569       return nerr_raise (CGIUploadCancelled, "Upload Cancelled");
570   }
571 
572   err = _header_attr (ct_hdr, "boundary", &boundary);
573   if (err) return nerr_pass (err);
574   err = _find_boundary(cgi, boundary, &done);
575   while (!err && !done)
576   {
577     err = _read_part (cgi, boundary, &done);
578   }
579 
580   if (boundary) free(boundary);
581   return nerr_pass(err);
582 }
583 
584 /* this is here because it gets populated in this file */
cgi_filehandle(CGI * cgi,const char * form_name)585 FILE *cgi_filehandle (CGI *cgi, const char *form_name)
586 {
587   NEOERR *err;
588   FILE *fp;
589   char buf[256];
590   int n;
591 
592   if ((form_name == NULL) || (form_name[0] == '\0'))
593   {
594     /* if NULL, then its the PUT data we're looking for... */
595     n = hdf_get_int_value (cgi->hdf, "PUT.FileHandle", -1);
596   }
597   else
598   {
599     snprintf (buf, sizeof(buf), "Query.%s.FileHandle", form_name);
600     n = hdf_get_int_value (cgi->hdf, buf, -1);
601   }
602   if (n == -1) return NULL;
603   err = uListGet(cgi->files, n-1, (void *)&fp);
604   if (err)
605   {
606     nerr_ignore(&err);
607     return NULL;
608   }
609   return fp;
610 }
611