| /* |
| * Copyright 2001-2004 Brandon Long |
| * All Rights Reserved. |
| * |
| * ClearSilver Templating System |
| * |
| * This code is made available under the terms of the ClearSilver License. |
| * http://www.clearsilver.net/license.hdf |
| * |
| */ |
| |
| /* rfc2388 defines multipart/form-data which is primarily used for |
| * HTTP file upload |
| */ |
| |
| #include "cs_config.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/stat.h> |
| #include <limits.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include "util/neo_misc.h" |
| #include "util/neo_err.h" |
| #include "util/neo_str.h" |
| #include "cgi.h" |
| #include "cgiwrap.h" |
| |
| static NEOERR * _header_value (char *hdr, char **val) |
| { |
| char *p, *q; |
| int l; |
| |
| *val = NULL; |
| |
| p = hdr; |
| while (*p && isspace(*p)) p++; |
| q = p; |
| while (*q && !isspace(*q) && *q != ';') q++; |
| if (!*p || p == q) return STATUS_OK; |
| |
| l = q - p; |
| *val = (char *) malloc (l+1); |
| if (*val == NULL) |
| return nerr_raise (NERR_NOMEM, "Unable to allocate space for val"); |
| memcpy (*val, p, l); |
| (*val)[l] = '\0'; |
| |
| return STATUS_OK; |
| } |
| |
| static NEOERR * _header_attr (char *hdr, char *attr, char **val) |
| { |
| char *p, *k, *v; |
| int found = 0; |
| int l, al; |
| char *r; |
| |
| *val = NULL; |
| l = strlen(attr); |
| |
| /* skip value */ |
| p = hdr; |
| while (*p && *p != ';') p++; |
| if (!*p) return STATUS_OK; |
| |
| p++; |
| while(*p && !found) |
| { |
| while (*p && isspace(*p)) p++; |
| if (!*p) return STATUS_OK; |
| /* attr name */ |
| k = p; |
| while (*p && !isspace(*p) && *p != ';' && *p != '=') p++; |
| if (!*p) return STATUS_OK; |
| if (l == (p-k) && !strncasecmp(attr, k, l)) |
| found = 1; |
| |
| while (*p && isspace(*p)) p++; |
| if (*p != ';' && *p != '=') return STATUS_OK; |
| if (*p == ';') |
| { |
| if (found) |
| { |
| *val = strdup (""); |
| if (*val == NULL) |
| return nerr_raise (NERR_NOMEM, "Unable to allocate value"); |
| return STATUS_OK; |
| } |
| } |
| else |
| { |
| p++; |
| if (*p == '"') |
| { |
| v = ++p; |
| while (*p && *p != '"') p++; |
| al = p-v; |
| if (*p) p++; |
| } |
| else |
| { |
| v = p; |
| while (*p && !isspace(*p) && *p != ';') p++; |
| al = p-v; |
| } |
| if (found) |
| { |
| r = (char *) malloc (al+1); |
| if (r == NULL) |
| return nerr_raise (NERR_NOMEM, "Unable to allocate value"); |
| memcpy (r, v, al); |
| r[al] = '\0'; |
| *val = r; |
| return STATUS_OK; |
| } |
| } |
| if (*p) p++; |
| } |
| return STATUS_OK; |
| } |
| |
| static NEOERR * _read_line (CGI *cgi, char **s, int *l, int *done) |
| { |
| int ofs = 0; |
| char *p; |
| int to_read; |
| |
| if (cgi->buf == NULL) |
| { |
| cgi->buflen = 4096; |
| cgi->buf = (char *) malloc (sizeof(char) * cgi->buflen); |
| if (cgi->buf == NULL) |
| return nerr_raise (NERR_NOMEM, "Unable to allocate cgi buf"); |
| } |
| if (cgi->unget) |
| { |
| cgi->unget = FALSE; |
| *s = cgi->last_start; |
| *l = cgi->last_length; |
| return STATUS_OK; |
| } |
| if (cgi->found_nl) |
| { |
| p = memchr (cgi->buf + cgi->nl, '\n', cgi->readlen - cgi->nl); |
| if (p) { |
| cgi->last_start = *s = cgi->buf + cgi->nl; |
| cgi->last_length = *l = p - (cgi->buf + cgi->nl) + 1; |
| cgi->found_nl = TRUE; |
| cgi->nl = p - cgi->buf + 1; |
| return STATUS_OK; |
| } |
| ofs = cgi->readlen - cgi->nl; |
| memmove(cgi->buf, cgi->buf + cgi->nl, ofs); |
| } |
| // Read either as much buffer space as we have left, or up to |
| // the amount of data remaining according to Content-Length |
| // If there is no Content-Length, just use the buffer space, but recognize |
| // that it might not work on some servers or cgiwrap implementations. |
| // Some servers will close their end of the stdin pipe, so cgiwrap_read |
| // will return if we ask for too much. Techically, not including |
| // Content-Length is against the HTTP spec, so we should consider failing |
| // earlier if we don't have a length. |
| to_read = cgi->buflen - ofs; |
| if (cgi->data_expected && (to_read > cgi->data_expected - cgi->data_read)) |
| { |
| to_read = cgi->data_expected - cgi->data_read; |
| } |
| cgiwrap_read (cgi->buf + ofs, to_read, &(cgi->readlen)); |
| if (cgi->readlen < 0) |
| { |
| return nerr_raise_errno (NERR_IO, "POST Read Error"); |
| } |
| if (cgi->readlen == 0) |
| { |
| *done = 1; |
| return STATUS_OK; |
| } |
| cgi->data_read += cgi->readlen; |
| if (cgi->upload_cb) |
| { |
| if (cgi->upload_cb (cgi, cgi->data_read, cgi->data_expected)) |
| return nerr_raise (CGIUploadCancelled, "Upload Cancelled"); |
| } |
| cgi->readlen += ofs; |
| p = memchr (cgi->buf, '\n', cgi->readlen); |
| if (!p) |
| { |
| cgi->found_nl = FALSE; |
| cgi->last_start = *s = cgi->buf; |
| cgi->last_length = *l = cgi->readlen; |
| return STATUS_OK; |
| } |
| cgi->last_start = *s = cgi->buf; |
| cgi->last_length = *l = p - cgi->buf + 1; |
| cgi->found_nl = TRUE; |
| cgi->nl = *l; |
| return STATUS_OK; |
| } |
| |
| static NEOERR * _read_header_line (CGI *cgi, STRING *line, int *done) |
| { |
| NEOERR *err; |
| char *s, *p; |
| int l; |
| |
| err = _read_line (cgi, &s, &l, done); |
| if (err) return nerr_pass (err); |
| if (*done || (l == 0)) return STATUS_OK; |
| if (isspace (s[0])) return STATUS_OK; |
| while (l && isspace(s[l-1])) l--; |
| err = string_appendn (line, s, l); |
| if (err) return nerr_pass (err); |
| |
| while (1) |
| { |
| err = _read_line (cgi, &s, &l, done); |
| if (err) break; |
| if (l == 0) break; |
| if (*done) break; |
| if (!(s[0] == ' ' || s[0] == '\t')) |
| { |
| cgi->unget = TRUE; |
| break; |
| } |
| while (l && isspace(s[l-1])) l--; |
| p = s; |
| while (*p && isspace(*p) && (p-s < l)) p++; |
| err = string_append_char (line, ' '); |
| if (err) break; |
| err = string_appendn (line, p, l - (p-s)); |
| if (err) break; |
| if (line->len > 50*1024*1024) |
| { |
| string_clear(line); |
| return nerr_raise(NERR_ASSERT, "read_header_line exceeded 50MB"); |
| } |
| } |
| return nerr_pass (err); |
| } |
| |
| static BOOL _is_boundary (char *boundary, char *s, int l, int *done) |
| { |
| static char *old_boundary = NULL; |
| static int bl; |
| |
| /* cache the boundary strlen... more pointless optimization by blong */ |
| if (old_boundary != boundary) |
| { |
| old_boundary = boundary; |
| bl = strlen(boundary); |
| } |
| |
| if (s[l-1] != '\n') |
| return FALSE; |
| l--; |
| if (s[l-1] == '\r') |
| l--; |
| |
| if (bl+2 == l && s[0] == '-' && s[1] == '-' && !strncmp (s+2, boundary, bl)) |
| return TRUE; |
| if (bl+4 == l && s[0] == '-' && s[1] == '-' && |
| !strncmp (s+2, boundary, bl) && |
| s[l-1] == '-' && s[l-2] == '-') |
| { |
| *done = 1; |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static NEOERR * _find_boundary (CGI *cgi, char *boundary, int *done) |
| { |
| NEOERR *err; |
| char *s; |
| int l; |
| |
| *done = 0; |
| while (1) |
| { |
| err = _read_line (cgi, &s, &l, done); |
| if (err) return nerr_pass (err); |
| if ((l == 0) || (*done)) { |
| *done = 1; |
| return STATUS_OK; |
| } |
| if (_is_boundary(boundary, s, l, done)) |
| return STATUS_OK; |
| } |
| return STATUS_OK; |
| } |
| |
| NEOERR *open_upload(CGI *cgi, int unlink_files, FILE **fpw) |
| { |
| NEOERR *err = STATUS_OK; |
| FILE *fp; |
| char path[_POSIX_PATH_MAX]; |
| int fd; |
| |
| *fpw = NULL; |
| |
| snprintf (path, sizeof(path), "%s/cgi_upload.XXXXXX", |
| hdf_get_value(cgi->hdf, "Config.Upload.TmpDir", "/var/tmp")); |
| |
| fd = mkstemp(path); |
| if (fd == -1) |
| { |
| return nerr_raise_errno (NERR_SYSTEM, "Unable to open temp file %s", |
| path); |
| } |
| |
| fp = fdopen (fd, "w+"); |
| if (fp == NULL) |
| { |
| close(fd); |
| return nerr_raise_errno (NERR_SYSTEM, "Unable to fdopen file %s", path); |
| } |
| if (unlink_files) unlink(path); |
| if (cgi->files == NULL) |
| { |
| err = uListInit (&(cgi->files), 10, 0); |
| if (err) |
| { |
| fclose(fp); |
| return nerr_pass(err); |
| } |
| } |
| err = uListAppend (cgi->files, fp); |
| if (err) |
| { |
| fclose (fp); |
| return nerr_pass(err); |
| } |
| if (!unlink_files) { |
| if (cgi->filenames == NULL) |
| { |
| err = uListInit (&(cgi->filenames), 10, 0); |
| if (err) |
| { |
| fclose(fp); |
| return nerr_pass(err); |
| } |
| } |
| err = uListAppend (cgi->filenames, strdup(path)); |
| if (err) |
| { |
| fclose (fp); |
| return nerr_pass(err); |
| } |
| } |
| *fpw = fp; |
| return STATUS_OK; |
| } |
| |
| static NEOERR * _read_part (CGI *cgi, char *boundary, int *done) |
| { |
| NEOERR *err = STATUS_OK; |
| STRING str; |
| HDF *child, *obj = NULL; |
| FILE *fp = NULL; |
| char buf[256]; |
| char *p; |
| char *name = NULL, *filename = NULL; |
| char *type = NULL, *tmp = NULL; |
| char *last = NULL; |
| int unlink_files = hdf_get_int_value(cgi->hdf, "Config.Upload.Unlink", 1); |
| |
| string_init (&str); |
| |
| while (1) |
| { |
| err = _read_header_line (cgi, &str, done); |
| if (err) break; |
| if (*done) break; |
| if (str.buf == NULL || str.buf[0] == '\0') break; |
| p = strchr (str.buf, ':'); |
| if (p) |
| { |
| *p = '\0'; |
| if (!strcasecmp(str.buf, "content-disposition")) |
| { |
| err = _header_attr (p+1, "name", &name); |
| if (err) break; |
| err = _header_attr (p+1, "filename", &filename); |
| if (err) break; |
| } |
| else if (!strcasecmp(str.buf, "content-type")) |
| { |
| err = _header_value (p+1, &type); |
| if (err) break; |
| } |
| else if (!strcasecmp(str.buf, "content-encoding")) |
| { |
| err = _header_value (p+1, &tmp); |
| if (err) break; |
| if (tmp && strcmp(tmp, "7bit") && strcmp(tmp, "8bit") && |
| strcmp(tmp, "binary")) |
| { |
| free(tmp); |
| err = nerr_raise (NERR_ASSERT, "form-data encoding is not supported"); |
| break; |
| } |
| free(tmp); |
| } |
| } |
| string_set(&str, ""); |
| } |
| if (err) |
| { |
| string_clear(&str); |
| if (name) free(name); |
| if (filename) free(filename); |
| if (type) free(type); |
| return nerr_pass (err); |
| } |
| |
| do |
| { |
| if (filename) |
| { |
| err = open_upload(cgi, unlink_files, &fp); |
| if (err) break; |
| } |
| |
| string_set(&str, ""); |
| while (!(*done)) |
| { |
| char *s; |
| int l, w; |
| |
| err = _read_line (cgi, &s, &l, done); |
| if (err) break; |
| if (*done || (l == 0)) break; |
| if (_is_boundary(boundary, s, l, done)) break; |
| if (filename) |
| { |
| if (last) fwrite (last, sizeof(char), strlen(last), fp); |
| if (l > 1 && s[l-1] == '\n' && s[l-2] == '\r') |
| { |
| last = "\r\n"; |
| l-=2; |
| } |
| else if (l > 0 && s[l-1] == '\n') |
| { |
| last = "\n"; |
| l--; |
| } |
| else last = NULL; |
| w = fwrite (s, sizeof(char), l, fp); |
| if (w != l) |
| { |
| err = nerr_raise_errno (NERR_IO, |
| "Short write on file %s upload %d < %d", filename, w, l); |
| break; |
| } |
| } |
| else |
| { |
| err = string_appendn(&str, s, l); |
| if (err) break; |
| } |
| } |
| if (err) break; |
| } while (0); |
| |
| /* Set up the cgi data */ |
| if (!err) |
| { |
| do { |
| /* FIXME: Hmm, if we've seen the same name here before, what should we do? |
| */ |
| if (filename) |
| { |
| fseek(fp, 0, SEEK_SET); |
| snprintf (buf, sizeof(buf), "Query.%s", name); |
| err = hdf_set_value (cgi->hdf, buf, filename); |
| if (!err && type) |
| { |
| snprintf (buf, sizeof(buf), "Query.%s.Type", name); |
| err = hdf_set_value (cgi->hdf, buf, type); |
| } |
| if (!err) |
| { |
| snprintf (buf, sizeof(buf), "Query.%s.FileHandle", name); |
| err = hdf_set_int_value (cgi->hdf, buf, uListLength(cgi->files)); |
| } |
| if (!err && !unlink_files) |
| { |
| char *path; |
| snprintf (buf, sizeof(buf), "Query.%s.FileName", name); |
| err = uListGet(cgi->filenames, uListLength(cgi->filenames)-1, |
| (void *)&path); |
| if (!err) err = hdf_set_value (cgi->hdf, buf, path); |
| } |
| } |
| else |
| { |
| snprintf (buf, sizeof(buf), "Query.%s", name); |
| while (str.len && isspace(str.buf[str.len-1])) |
| { |
| str.buf[str.len-1] = '\0'; |
| str.len--; |
| } |
| if (!(cgi->ignore_empty_form_vars && str.len == 0)) |
| { |
| /* If we've seen it before... we force it into a list */ |
| obj = hdf_get_obj (cgi->hdf, buf); |
| if (obj != NULL) |
| { |
| int i = 0; |
| char buf2[10]; |
| char *t; |
| child = hdf_obj_child (obj); |
| if (child == NULL) |
| { |
| t = hdf_obj_value (obj); |
| err = hdf_set_value (obj, "0", t); |
| if (err != STATUS_OK) break; |
| i = 1; |
| } |
| else |
| { |
| while (child != NULL) |
| { |
| i++; |
| child = hdf_obj_next (child); |
| if (err != STATUS_OK) break; |
| } |
| if (err != STATUS_OK) break; |
| } |
| snprintf (buf2, sizeof(buf2), "%d", i); |
| err = hdf_set_value (obj, buf2, str.buf); |
| if (err != STATUS_OK) break; |
| } |
| err = hdf_set_value (cgi->hdf, buf, str.buf); |
| } |
| } |
| } while (0); |
| } |
| |
| string_clear(&str); |
| if (name) free(name); |
| if (filename) free(filename); |
| if (type) free(type); |
| |
| return nerr_pass (err); |
| } |
| |
| NEOERR * parse_rfc2388 (CGI *cgi) |
| { |
| NEOERR *err; |
| char *ct_hdr; |
| char *boundary = NULL; |
| int l; |
| int done = 0; |
| |
| l = hdf_get_int_value (cgi->hdf, "CGI.ContentLength", -1); |
| ct_hdr = hdf_get_value (cgi->hdf, "CGI.ContentType", NULL); |
| if (ct_hdr == NULL) |
| return nerr_raise (NERR_ASSERT, "No content type header?"); |
| |
| cgi->data_expected = l; |
| cgi->data_read = 0; |
| if (cgi->upload_cb) |
| { |
| if (cgi->upload_cb (cgi, cgi->data_read, cgi->data_expected)) |
| return nerr_raise (CGIUploadCancelled, "Upload Cancelled"); |
| } |
| |
| err = _header_attr (ct_hdr, "boundary", &boundary); |
| if (err) return nerr_pass (err); |
| err = _find_boundary(cgi, boundary, &done); |
| while (!err && !done) |
| { |
| err = _read_part (cgi, boundary, &done); |
| } |
| |
| if (boundary) free(boundary); |
| return nerr_pass(err); |
| } |
| |
| /* this is here because it gets populated in this file */ |
| FILE *cgi_filehandle (CGI *cgi, const char *form_name) |
| { |
| NEOERR *err; |
| FILE *fp; |
| char buf[256]; |
| int n; |
| |
| if ((form_name == NULL) || (form_name[0] == '\0')) |
| { |
| /* if NULL, then its the PUT data we're looking for... */ |
| n = hdf_get_int_value (cgi->hdf, "PUT.FileHandle", -1); |
| } |
| else |
| { |
| snprintf (buf, sizeof(buf), "Query.%s.FileHandle", form_name); |
| n = hdf_get_int_value (cgi->hdf, buf, -1); |
| } |
| if (n == -1) return NULL; |
| err = uListGet(cgi->files, n-1, (void *)&fp); |
| if (err) |
| { |
| nerr_ignore(&err); |
| return NULL; |
| } |
| return fp; |
| } |