#include "ext/standard/type.h"
-#define NEW_BOUNDARY_CHECK 1
-#define SAFE_RETURN { if (namebuf) efree(namebuf); if (filenamebuf) efree(filenamebuf); if (lbuf) efree(lbuf); if (abuf) efree(abuf); if(arr_index) efree(arr_index); zend_hash_destroy(&PG(rfc1867_protected_variables)); return; }
+#define SAFE_RETURN { \
+ if (lbuf) efree(lbuf); \
+ if (abuf) efree(abuf); \
+ if(array_index) efree(array_index); \
+ zend_hash_destroy(&PG(rfc1867_protected_variables)); \
+ zend_llist_destroy(&header); \
+ if (mbuff->boundary_next) efree(mbuff->boundary_next); \
+ if (mbuff->boundary) efree(mbuff->boundary); \
+ if (mbuff->buffer) efree(mbuff->buffer); \
+ if (mbuff) efree(mbuff); \
+ return; }
+
/* The longest property name we use in an uploaded file array */
#define MAX_SIZE_OF_INDEX sizeof("[tmp_name]")
+
static void add_protected_variable(char *varname TSRMLS_DC)
{
int dummy=1;
}
-static void safe_php_register_variable_ex(char *var, zval *val, pval *track_vars_array, zend_bool override_protection TSRMLS_DC)
+static void safe_php_register_variable_ex(char *var, zval *val, zval *track_vars_array, zend_bool override_protection TSRMLS_DC)
{
if (override_protection || !is_protected_variable(var TSRMLS_CC)) {
php_register_variable_ex(var, val, track_vars_array TSRMLS_CC);
FREE_HASHTABLE(SG(rfc1867_uploaded_files));
}
-/*
- * Split raw mime stream up into appropriate components
+
+/*
+ * Following code is borrowed from Apache
+ *
*/
-static void php_mime_split(char *buf, int cnt, char *boundary, int len, zval *array_ptr TSRMLS_DC)
-{
- char *ptr, *loc, *loc2, *loc3, *s, *name, *filename, *u, *temp_filename, c;
- int state = 0, Done = 0, rem, urem;
- int eolsize;
- long bytes, max_file_size = 0;
- char *namebuf=NULL, *filenamebuf=NULL, *lbuf=NULL,
- *abuf=NULL, *start_arr=NULL, *end_arr=NULL, *arr_index=NULL;
- FILE *fp;
- int itype, is_arr_upload=0, arr_len=0;
- zval *http_post_files=NULL;
- zend_bool upload_successful;
- zend_bool magic_quotes_gpc;
- zend_hash_init(&PG(rfc1867_protected_variables), 5, NULL, NULL, 0);
+#define FILLUNIT (1024 * 5)
- ALLOC_HASHTABLE(SG(rfc1867_uploaded_files));
- zend_hash_init(SG(rfc1867_uploaded_files), 5, NULL, (dtor_func_t) free_estring, 0);
+typedef struct {
- ALLOC_ZVAL(http_post_files);
- array_init(http_post_files);
- INIT_PZVAL(http_post_files);
- PG(http_globals)[TRACK_VARS_FILES] = http_post_files;
+ /* read buffer */
+ char *buffer;
+ char *buf_begin;
+ int bufsize;
+ int bytes_in_buffer;
- ptr = buf;
- rem = cnt;
+ /* boundary info */
+ char *boundary;
+ char *boundary_next;
+ int boundary_next_len;
- while ((ptr - buf < cnt) && !Done) {
- switch (state) {
- case 0: /* Looking for mime boundary */
- loc = memchr(ptr, *boundary, rem); /* fixed */
- if (loc) {
- if (!strncmp(loc, boundary, len)) {
+} multipart_buffer;
- state = 1;
- eolsize = 2;
- if(*(loc+len)=='\n') {
- eolsize = 1;
- }
+typedef struct {
+ char *key;
+ char *value;
+} mime_header_entry;
- rem -= (loc - ptr) + len + eolsize;
- ptr = loc + len + eolsize;
- } else {
- rem -= (loc - ptr) + 1;
- ptr = loc + 1;
- }
- } else {
- Done = 1;
- }
- break;
- case 1: /* Check content-disposition */
- while (strncasecmp(ptr, "Content-Disposition: form-data;", 31)) {
- if (rem < 31) {
- SAFE_RETURN;
- }
- if (ptr[1] == '\n') {
- /* empty line as end of header found */
- php_error(E_WARNING, "File Upload Mime headers garbled ptr: [%c%c%c%c%c]", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4));
- SAFE_RETURN;
- }
- /* some other headerfield found, skip it */
- loc = (char *) memchr(ptr, '\n', rem)+1;
- if (!loc) {
- /* broken */
- php_error(E_WARNING, "File Upload Mime headers garbled ptr: [%c%c%c%c%c]", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4));
- SAFE_RETURN;
- }
- while (*loc == ' ' || *loc == '\t') {
- /* other field is folded, skip it */
- loc = (char *) memchr(loc, '\n', rem-(loc-ptr))+1;
- if (!loc) {
- /* broken */
- php_error(E_WARNING, "File Upload Mime headers garbled ptr: [%c%c%c%c%c]", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4));
- SAFE_RETURN;
- }
- }
- rem -= (loc - ptr);
- ptr = loc;
- }
- loc = memchr(ptr, '\n', rem);
- if (!loc) {
- /* broken */
- php_error(E_WARNING, "File Upload Mime headers garbled ptr: [%c%c%c%c%c]", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4));
- SAFE_RETURN;
- }
- while (loc[1] == ' ' || loc[1] == '\t') {
- /* field is folded, look for end */
- loc = memchr(loc+1, '\n', rem-(loc-ptr)-1);
- if (!loc) {
- /* broken */
- php_error(E_WARNING, "File Upload Mime headers garbled ptr: [%c%c%c%c%c]", *ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3), *(ptr + 4));
- SAFE_RETURN;
- }
- }
- name = strstr(ptr, " name=");
- if (name && name < loc) {
- name += 6;
- if ( *name == '\"' ) {
- name++;
- s = memchr(name, '\"', loc - name);
- } else {
- s = strpbrk(name, " \t()<>@,;:\\\"/[]?=\r\n");
- }
- if (!s) {
- php_error(E_WARNING, "File Upload Mime headers garbled name: [%c%c%c%c%c]", *name, *(name + 1), *(name + 2), *(name + 3), *(name + 4));
- SAFE_RETURN;
- }
-
- if (namebuf) {
- efree(namebuf);
- }
- namebuf = estrndup(name, s-name);
- if (lbuf) {
- efree(lbuf);
- }
- lbuf = emalloc(s-name + MAX_SIZE_OF_INDEX + 1);
- state = 2;
-
- /* the fix at this position was wrong
- * the end of headers search was broken
- * below. fix moved there and restored
- * pre 4.0.6 code here
- */
- loc2 = memchr(loc + 1, '\n', rem);
- rem -= (loc2 - ptr) + 1;
- ptr = loc2 + 1;
- /* is_arr_upload is true when name of file upload field
- * ends in [.*]
- * start_arr is set to point to 1st [
- * end_arr points to last ]
- */
- is_arr_upload = (start_arr = strchr(namebuf,'[')) &&
- (end_arr = strrchr(namebuf,']')) &&
- (end_arr == namebuf+strlen(namebuf)-1);
- if(is_arr_upload) {
- arr_len = strlen(start_arr); /* is NOW >=2 */
- if(arr_index) efree(arr_index);
- arr_index = estrndup(start_arr+1, arr_len-2);
- }
- } else {
- php_error(E_WARNING, "File upload error - no name component in content disposition");
- SAFE_RETURN;
- }
- filename = strstr(s, "filename=\"");
- if (filename && filename < loc) {
- filename += 10;
- s = memchr(filename, '\"', loc - filename);
- if (!s) {
- php_error(E_WARNING, "File Upload Mime headers garbled filename: [%c%c%c%c%c]", *filename, *(filename + 1), *(filename + 2), *(filename + 3), *(filename + 4));
- SAFE_RETURN;
- }
- if (filenamebuf) {
- efree(filenamebuf);
- }
- filenamebuf = estrndup(filename, s-filename);
-
- /* Add $foo_name */
- if (is_arr_upload) {
- if (abuf) {
- efree(abuf);
- }
- abuf = estrndup(namebuf, strlen(namebuf)-arr_len);
- sprintf(lbuf, "%s_name[%s]", abuf, arr_index);
- } else {
- sprintf(lbuf, "%s_name", namebuf);
- }
- s = strrchr(filenamebuf, '\\');
- if (s && s > filenamebuf) {
- safe_php_register_variable(lbuf, s+1, NULL, 0 TSRMLS_CC);
- } else {
- safe_php_register_variable(lbuf, filenamebuf, NULL, 0 TSRMLS_CC);
- }
- /* Add $foo[name] */
- if (is_arr_upload) {
- sprintf(lbuf, "%s[name][%s]", abuf, arr_index);
- } else {
- sprintf(lbuf, "%s[name]", namebuf);
- }
- if (s && s > filenamebuf) {
- register_http_post_files_variable(lbuf, s+1, http_post_files, 0 TSRMLS_CC);
- } else {
- register_http_post_files_variable(lbuf, filenamebuf, http_post_files, 0 TSRMLS_CC);
- }
+/*
+ fill up the buffer with client data.
+ returns number of bytes added to buffer.
+*/
+static int fill_buffer(multipart_buffer *self TSRMLS_DC)
+{
+ int bytes_to_read, actual_read = 0;
- state = 3;
- s = "";
- if ((loc2 - loc) > 2) {
- if (!strncasecmp(loc + 1, "Content-Type:", 13)) {
- c = *(loc2 - 1);
- *(loc2 - 1) = '\0';
- s = loc+15;
- }
- /* end of header fix fixed and moved here
- * find the double newline that marks the
- * end of the headers
- */
- loc3 = loc2;
- while (loc3[2] != '\n') {
-
- /* empty line as end of headers not yet found */
- loc3 = memchr(loc3 + 1, '\n', rem-(loc3-ptr)-1);
- if (loc3==NULL) {
- php_error(E_WARNING, "File Upload Mime headers garbled header3: [%c%c%c%c%c]", *loc2, *(loc2 + 1), *(loc2 + 2), *(loc2 + 3), *(loc2 + 4));
- SAFE_RETURN;
- }
- }
- rem -= (loc3 - ptr) + 3;
- ptr = loc3 + 3;
- }
+ /* shift the existing data if necessary */
+ if(self->bytes_in_buffer > 0 && self->buf_begin != self->buffer) {
+ memmove(self->buffer, self->buf_begin, self->bytes_in_buffer);
+ }
- /* Add $foo_type */
- if (is_arr_upload) {
- sprintf(lbuf, "%s_type[%s]", abuf, arr_index);
- } else {
- sprintf(lbuf, "%s_type", namebuf);
- }
- safe_php_register_variable(lbuf, s, NULL, 0 TSRMLS_CC);
-
- /* Add $foo[type] */
- if (is_arr_upload) {
- sprintf(lbuf, "%s[type][%s]", abuf, arr_index);
- } else {
- sprintf(lbuf, "%s[type]", namebuf);
- }
- register_http_post_files_variable(lbuf, s, http_post_files, 0 TSRMLS_CC);
- if(*s != '\0') {
- /* write old char back
- * most probably it is '\r'
- * and not '\n'
- */
- *(loc2 - 1) = c;
- }
- }
- break;
-
- case 2: /* handle form-data fields */
- loc = memchr(ptr, *boundary, rem);
- u = ptr;
- while (loc) {
- if (!strncmp(loc, boundary, len))
- break;
- u = loc + 1;
- urem = rem - (loc - ptr) - 1;
- loc = memchr(u, *boundary, urem);
- }
- if (!loc) {
- php_error(E_WARNING, "File Upload Field Data garbled");
- SAFE_RETURN;
- }
- *(loc - 4) = '\0';
-
- /* Check to make sure we are not overwriting special file
- * upload variables */
- safe_php_register_variable(namebuf, ptr, array_ptr, 0 TSRMLS_CC);
-
- /* And a little kludge to pick out special MAX_FILE_SIZE */
- itype = php_check_ident_type(namebuf);
- if (itype) {
- u = strchr(namebuf, '[');
- if (u)
- *u = '\0';
- }
- if (!strcmp(namebuf, "MAX_FILE_SIZE")) {
- max_file_size = atol(ptr);
- }
- if (itype) {
- if (u)
- *u = '[';
- }
- rem -= (loc - ptr);
- ptr = loc;
- state = 0;
- break;
-
- case 3: /* Handle file */
- loc = memchr(ptr, *boundary, rem);
- u = ptr;
- while (loc) {
- if (!strncmp(loc, boundary, len)
-#if NEW_BOUNDARY_CHECK
- && (loc-2>buf && *(loc-2)=='-' && *(loc-1)=='-') /* ensure boundary is prefixed with -- */
- && (loc-2==buf || *(loc-3)=='\n') /* ensure beginning of line */
-#endif
- ) {
- break;
- }
- u = loc + 1;
- urem = rem - (loc - ptr) - 1;
- loc = memchr(u, *boundary, urem);
- }
- if (!loc) {
- php_error(E_WARNING, "File Upload Error - No Mime boundary found after start of file header");
- SAFE_RETURN;
- }
- bytes = 0;
+ self->buf_begin = self->buffer;
- fp = php_open_temporary_file(PG(upload_tmp_dir), "php", &temp_filename TSRMLS_CC);
- if (!fp) {
- php_error(E_WARNING, "File upload error - unable to create a temporary file");
- SAFE_RETURN;
- }
- if ((loc - ptr - 4) > PG(upload_max_filesize)) {
- php_error(E_WARNING, "Max file size of %ld bytes exceeded - file [%s] not saved", PG(upload_max_filesize), namebuf);
- upload_successful = 0;
- } else if (max_file_size && ((loc - ptr - 4) > max_file_size)) {
- php_error(E_WARNING, "Max file size exceeded - file [%s] not saved", namebuf);
- upload_successful = 0;
- } else if ((loc - ptr - 4) <= 0) {
- upload_successful = 0;
- } else {
- bytes = fwrite(ptr, 1, loc - ptr - 4, fp);
- if (bytes < (loc - ptr - 4)) {
- php_error(E_WARNING, "Only %d bytes were written, expected to write %ld", bytes, loc - ptr - 4);
- }
- upload_successful = 1;
- }
- fclose(fp);
- add_protected_variable(namebuf TSRMLS_CC);
- if (!upload_successful) {
- if(temp_filename) {
- unlink(temp_filename);
- efree(temp_filename);
- }
- temp_filename = "none";
- } else {
- zend_hash_add(SG(rfc1867_uploaded_files), temp_filename, strlen(temp_filename)+1, &temp_filename, sizeof(char *), NULL);
- }
+ /* calculate the free space in the buffer */
+ bytes_to_read = self->bufsize - self->bytes_in_buffer;
- magic_quotes_gpc = PG(magic_quotes_gpc);
- PG(magic_quotes_gpc) = 0;
- safe_php_register_variable(namebuf, temp_filename, NULL, 1 TSRMLS_CC);
- /* Add $foo[tmp_name] */
- if(is_arr_upload) {
- sprintf(lbuf, "%s[tmp_name][%s]", abuf, arr_index);
- } else {
- sprintf(lbuf, "%s[tmp_name]", namebuf);
- }
- add_protected_variable(lbuf TSRMLS_CC);
- register_http_post_files_variable(lbuf, temp_filename, http_post_files, 1 TSRMLS_CC);
- PG(magic_quotes_gpc) = magic_quotes_gpc;
+ /* read the required number of bytes */
+ if(bytes_to_read > 0) {
- {
- zval file_size;
+ char *buf = self->buffer + self->bytes_in_buffer;
- Z_LVAL(file_size) = bytes;
- Z_TYPE(file_size) = IS_LONG;
-
- /* Add $foo_size */
- if(is_arr_upload) {
- sprintf(lbuf, "%s_size[%s]", abuf, arr_index);
- } else {
- sprintf(lbuf, "%s_size", namebuf);
- }
- safe_php_register_variable_ex(lbuf, &file_size, NULL, 0 TSRMLS_CC);
+ actual_read = sapi_module.read_post(buf, bytes_to_read TSRMLS_CC);
- /* Add $foo[size] */
- if(is_arr_upload) {
- sprintf(lbuf, "%s[size][%s]", abuf, arr_index);
- } else {
- sprintf(lbuf, "%s[size]", namebuf);
- }
- register_http_post_files_variable_ex(lbuf, &file_size, http_post_files, 0 TSRMLS_CC);
- }
- state = 0;
- rem -= (loc - ptr);
- ptr = loc;
- break;
+ /* update the buffer length */
+ if(actual_read > 0) {
+ self->bytes_in_buffer += actual_read;
}
}
- SAFE_RETURN;
+
+ return actual_read;
}
+/* eof if we are out of bytes, or if we hit the final boundary */
+static int multipart_buffer_eof(multipart_buffer *self TSRMLS_DC)
+{
+ if( (self->bytes_in_buffer == 0 && fill_buffer(self TSRMLS_CC) < 1) ) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* create new multipart_buffer structure */
+static multipart_buffer *multipart_buffer_new(char *boundary, int boundary_len)
+{
+ multipart_buffer *self = (multipart_buffer *) ecalloc(1, sizeof(multipart_buffer));
+
+ int minsize = boundary_len + 6;
+ if(minsize < FILLUNIT) minsize = FILLUNIT;
+
+ self->buffer = (char *) ecalloc(1, minsize + 1);
+ self->bufsize = minsize;
+
+ self->boundary = (char *) ecalloc(1, boundary_len + 3);
+ sprintf(self->boundary, "--%s", boundary);
+
+ self->boundary_next = (char *) ecalloc(1, boundary_len + 4);
+ sprintf(self->boundary_next, "\n--%s", boundary);
+ self->boundary_next_len = boundary_len + 3;
+
+ self->buf_begin = self->buffer;
+ self->bytes_in_buffer = 0;
+
+ return self;
+}
/*
- * Reads post data chunk
- *
+ gets the next CRLF terminated line from the input buffer.
+ if it doesn't find a CRLF, and the buffer isn't completely full, returns
+ NULL; otherwise, returns the beginning of the null-terminated line,
+ minus the CRLF.
+
+ note that we really just look for LF terminated lines. this works
+ around a bug in internet explorer for the macintosh which sends mime
+ boundaries that are only LF terminated when you use an image submit
+ button in a multipart/form-data form.
*/
-static int read_post_data_chunk(char *buf TSRMLS_DC)
+static char *next_line(multipart_buffer *self)
{
- int read_bytes;
+ /* look for LF in the data */
+ char* line = self->buf_begin;
+ char* ptr = memchr(self->buf_begin, '\n', self->bytes_in_buffer);
+
+ if(ptr) { /* LF found */
+
+ /* terminate the string, remove CRLF */
+ if((ptr - line) > 0 && *(ptr-1) == '\r') {
+ *(ptr-1) = 0;
+ } else {
+ *ptr = 0;
+ }
+
+ /* bump the pointer */
+ self->buf_begin = ptr + 1;
+ self->bytes_in_buffer -= (self->buf_begin - line);
- read_bytes = sapi_module.read_post(buf, SAPI_POST_BLOCK_SIZE TSRMLS_CC);
-
- SG(read_post_bytes) += read_bytes;
+ } else { /* no LF found */
+
+ /* buffer isn't completely full, fail */
+ if(self->bytes_in_buffer < self->bufsize) {
+ return NULL;
+ }
+ /* return entire buffer as a partial line */
+ line[self->bufsize] = 0;
+ self->buf_begin = ptr;
+ self->bytes_in_buffer = 0;
+ }
+
+ return line;
+}
+
+/* returns the next CRLF terminated line from the client */
+static char *get_line(multipart_buffer *self TSRMLS_DC)
+{
+ char* ptr = next_line(self);
+
+ if(!ptr) {
+ fill_buffer(self TSRMLS_CC);
+ ptr = next_line(self);
+ }
+
+ return ptr;
+}
+
+/* Free header entry */
+static void php_free_hdr_entry(mime_header_entry *h)
+{
+ if(h->key) efree(h->key);
+ if(h->value) efree(h->value);
+}
+
+/* finds a boundary */
+static int find_boundary(multipart_buffer *self, char *boundary TSRMLS_DC)
+{
+ char *line;
+
+ /* loop thru lines */
+ while( (line = get_line(self TSRMLS_CC)) )
+ {
+ /* finished if we found the boundary */
+ if(!strcmp(line, boundary)) {
+ return 1;
+ }
+ }
+
+ /* didn't find the boundary */
+ return 0;
+}
+
+/* parse headers */
+static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)
+{
+ char *line;
+ mime_header_entry prev_entry, entry;
+ int prev_len, cur_len;
+
+ /* didn't find boundary, abort */
+ if(!find_boundary(self, self->boundary TSRMLS_CC)) {
+ return 0;
+ }
+
+ /* get lines of text, or CRLF_CRLF */
+
+ while( (line = get_line(self TSRMLS_CC)) && strlen(line) > 0 )
+ {
+ /* add header to table */
- return read_bytes;
+ char *key = line;
+ char *value = strchr(line, ':');
+
+ if(value) {
+ *value = 0;
+ do { value++; } while(isspace(*value));
+
+ entry.value = estrdup(value);
+ entry.key = estrdup(key);
+
+ } else if(zend_llist_remove_tail(header)) { /* If no ':' on the line, add to previous line */
+
+ prev_len = strlen(prev_entry.value);
+ cur_len = strlen(line);
+
+ entry.value = emalloc(prev_len + cur_len + 1);
+ memcpy(entry.value, prev_entry.value, prev_len);
+ memcpy(entry.value + prev_len, line, cur_len);
+ entry.value[cur_len + prev_len] = '\0';
+
+ entry.key = estrdup(prev_entry.key);
+ }
+
+ zend_llist_add_element(header, &entry);
+ prev_entry = entry;
+ }
+
+ return 1;
}
+static char *php_mime_get_hdr_value(zend_llist header, char *key)
+{
+ mime_header_entry *entry;
+
+ if (key == NULL) {
+ return NULL;
+ }
+
+ entry = zend_llist_get_first(&header);
+ do {
+ if(!strcasecmp(entry->key, key)) {
+ return entry->value;
+ }
+ } while ((entry = zend_llist_get_next(&header)));
+
+ return NULL;
+}
+
+
+static char *php_ap_getword(char **line, char stop)
+{
+ char *pos = strchr(*line, stop);
+ char *res;
+
+ if (!pos) {
+ res = estrdup(*line);
+ *line += strlen(*line);
+ return res;
+ }
+
+ res = estrndup(*line, pos - *line);
+
+ while (*pos == stop) {
+ ++pos;
+ }
+
+ *line = pos;
+ return res;
+}
+
+static char *substring_conf(char *start, int len, char quote)
+{
+ char *result = emalloc(len + 2);
+ char *resp = result;
+ int i;
+
+ for (i = 0; i < len; ++i) {
+ if (start[i] == '\\' && (start[i + 1] == '\\' || (quote && start[i + 1] == quote))) {
+ *resp++ = start[++i];
+ } else {
+ *resp++ = start[i];
+ }
+ }
+
+ *resp++ = '\0';
+ return result;
+}
+
+static char *php_ap_getword_conf(char **line)
+{
+ char *str = *line, *strend, *res, quote;
+
+ while (*str && isspace(*str)) {
+ ++str;
+ }
+
+ if (!*str) {
+ *line = str;
+ return "";
+ }
+
+ if ((quote = *str) == '"' || quote == '\'') {
+ strend = str + 1;
+ while (*strend && *strend != quote) {
+ if (*strend == '\\' && strend[1] && strend[1] == quote) {
+ strend += 2;
+ } else {
+ ++strend;
+ }
+ }
+ res = substring_conf(str + 1, strend - str - 1, quote);
+
+ if (*strend == quote) {
+ ++strend;
+ }
+
+ } else {
+
+ strend = str;
+ while (*strend && !isspace(*strend)) {
+ ++strend;
+ }
+ res = substring_conf(str, strend - str, 0);
+ }
+
+ while (*strend && isspace(*strend)) {
+ ++strend;
+ }
+
+ *line = strend;
+ return res;
+}
+
+/*
+ search for a string in a fixed-length byte string.
+ if partial is true, partial matches are allowed at the end of the buffer.
+ returns NULL if not found, or a pointer to the start of the first match.
+*/
+static void *php_ap_memstr(char *haystack, int haystacklen, char *needle, int needlen, int partial)
+{
+ int len = haystacklen;
+ char *ptr = haystack;
+
+ /* iterate through first character matches */
+ while( (ptr = memchr(ptr, needle[0], len)) ) {
+
+ /* calculate length after match */
+ len = haystacklen - (ptr - (char *)haystack);
+
+ /* done if matches up to capacity of buffer */
+ if(memcmp(needle, ptr, needlen < len ? needlen : len) == 0 && (partial || len >= needlen)) {
+ break;
+ }
+
+ /* next character */
+ ptr++; len--;
+ }
+
+ return ptr;
+}
+
+/* read until a boundary condition */
+static int multipart_buffer_read(multipart_buffer *self, char *buf, int bytes TSRMLS_DC)
+{
+ int len, max;
+ char *bound;
+
+ /* fill buffer if needed */
+ if(bytes > self->bytes_in_buffer) {
+ fill_buffer(self TSRMLS_CC);
+ }
+
+ /* look for a potential boundary match, only read data up to that point */
+ if((bound = php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 1))) {
+ max = bound - self->buf_begin;
+ } else {
+ max = self->bytes_in_buffer;
+ }
+
+ /* maximum number of bytes we are reading */
+ len = max < bytes-1 ? max : bytes-1;
+
+ /* if we read any data... */
+ if(len > 0) {
+
+ /* copy the data */
+ memcpy(buf, self->buf_begin, len);
+ buf[len] = 0;
+
+ if(bound && len > 0 && buf[len-1] == '\r') {
+ buf[--len] = 0;
+ }
+
+ /* update the buffer */
+ self->bytes_in_buffer -= len;
+ self->buf_begin += len;
+ }
+
+ return len;
+}
+
+/*
+ XXX: this is horrible memory-usage-wise, but we only expect
+ to do this on small pieces of form data.
+*/
+static char *multipart_buffer_read_body(multipart_buffer *self TSRMLS_DC)
+{
+ char buf[FILLUNIT], *out=NULL;
+ int total_bytes=0, read_bytes=0;
+
+ while((read_bytes = multipart_buffer_read(self, buf, sizeof(buf) TSRMLS_CC))) {
+ total_bytes += read_bytes;
+ out = erealloc(out, total_bytes);
+ memcpy(out, buf, read_bytes);
+ }
+
+ if(out) {
+ out[total_bytes] = '\0';
+ }
+
+ return out;
+}
+
+
+/*
+ * The combined READER/HANDLER
+ *
+ */
+
SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler)
{
- char *boundary;
- uint boundary_len;
+ char *boundary, *s=NULL, *start_arr=NULL, *end_arr=NULL, *array_index=NULL;
+ char *temp_filename=NULL, *lbuf=NULL, *abuf=NULL;
+ int boundary_len=0, total_bytes=0, cancel_upload=0, is_arr_upload=0, array_len=0, max_file_size=0;
+ zval *http_post_files=NULL;
+ zend_bool magic_quotes_gpc;
+ multipart_buffer *mbuff;
zval *array_ptr = (zval *) arg;
+ FILE *fp;
+ zend_llist header;
+
if (!PG(file_uploads)) {
- php_error(E_WARNING, "File uploads are disabled");
+ sapi_module.sapi_error(E_WARNING, "File uploads are disabled");
return;
}
if (SG(request_info).content_length > SG(post_max_size)) {
- sapi_module.sapi_error(E_COMPILE_ERROR, "POST Content-Length of %d bytes exceeds the limit of %d bytes", SG(request_info).content_length, SG(post_max_size));
+ sapi_module.sapi_error(E_WARNING, "POST Content-Length of %d bytes exceeds the limit of %d bytes", SG(request_info).content_length, SG(post_max_size));
return;
}
+ /* Get the boundary */
boundary = strstr(content_type_dup, "boundary");
if (!boundary || !(boundary=strchr(boundary, '='))) {
- sapi_module.sapi_error(E_COMPILE_ERROR, "Missing boundary in multipart/form-data POST data");
+ sapi_module.sapi_error(E_WARNING, "Missing boundary in multipart/form-data POST data");
return;
}
boundary++;
boundary[boundary_len] = '\0';
}
- /* <FIXME> Temporary. Should be done same time as parsing. Maybe that Apache stuff.. */
+ /* Initialize the buffer */
+ if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) {
+ sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer");
+ return;
+ }
+
+ /* Initialize $_FILES[] */
+ zend_hash_init(&PG(rfc1867_protected_variables), 5, NULL, NULL, 0);
+
+ ALLOC_HASHTABLE(SG(rfc1867_uploaded_files));
+ zend_hash_init(SG(rfc1867_uploaded_files), 5, NULL, (dtor_func_t) free_estring, 0);
+
+ ALLOC_ZVAL(http_post_files);
+ array_init(http_post_files);
+ INIT_PZVAL(http_post_files);
+ PG(http_globals)[TRACK_VARS_FILES] = http_post_files;
+
+ zend_llist_init(&header, sizeof(mime_header_entry), (llist_dtor_func_t) php_free_hdr_entry, 0);
+
+ while (!multipart_buffer_eof(mbuff TSRMLS_CC))
{
- int allocated_bytes=SAPI_POST_BLOCK_SIZE+1, read_bytes;
-
- SG(request_info).post_data = emalloc(allocated_bytes);
+ char buff[FILLUNIT];
+ char *cd=NULL,*param=NULL,*filename=NULL;
+ int blen=0, wlen=0;
+
+ zend_llist_clean(&header);
+
+ if (!multipart_buffer_headers(mbuff, &header TSRMLS_CC)) {
+ SAFE_RETURN;
+ }
+
+ if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) {
+ char *pair=NULL;
+
+ while (isspace(*cd)) {
+ ++cd;
+ }
+
+ while (*cd && (pair = php_ap_getword(&cd, ';')))
+ {
+ char *key=NULL;
+
+ while (isspace(*cd)) {
+ ++cd;
+ }
+
+ if (strchr(pair, '=')) {
+ key = php_ap_getword(&pair, '=');
+
+ if(!strcmp(key, "name")) {
+ param = php_ap_getword_conf(&pair);
+ } else if(!strcmp(key, "filename")) {
+ filename = php_ap_getword_conf(&pair);
+ }
+ }
+ if(key) efree(key);
+ }
- for (;;) {
- read_bytes = read_post_data_chunk(SG(request_info).post_data+SG(read_post_bytes) TSRMLS_CC);
+ /* Normal form variable, safe to read all data into memory */
+ if (!filename && param) {
- if(read_bytes <= 0 || read_bytes < SAPI_POST_BLOCK_SIZE) {
- break;
+ char *value = multipart_buffer_read_body(mbuff TSRMLS_CC);
+
+ if(!value) {
+ value = "";
+ }
+
+ safe_php_register_variable(param, value, array_ptr, 0 TSRMLS_CC);
+ if (!strcmp(param, "MAX_FILE_SIZE")) {
+ max_file_size = atol(value);
+ }
+
+ if (param) efree(param);
+ continue;
}
- if (SG(read_post_bytes) > SG(post_max_size)) {
- php_error(E_WARNING, "Actual POST length does not match Content-Length, and exceeds %d bytes", SG(post_max_size));
- return;
+ /* Handle file */
+ fp = php_open_temporary_file(PG(upload_tmp_dir), "php", &temp_filename TSRMLS_CC);
+ if (!fp) {
+ sapi_module.sapi_error(E_WARNING, "File upload error - unable to create a temporary file");
+ SAFE_RETURN;
}
- if (SG(read_post_bytes) + SAPI_POST_BLOCK_SIZE >= allocated_bytes) {
- allocated_bytes = SG(read_post_bytes)+SAPI_POST_BLOCK_SIZE+1;
- SG(request_info).post_data = erealloc(SG(request_info).post_data, allocated_bytes);
+ total_bytes = 0;
+ cancel_upload = 0;
+
+ while (!cancel_upload && (blen = multipart_buffer_read(mbuff, buff, sizeof(buff) TSRMLS_CC)))
+ {
+ if (total_bytes > PG(upload_max_filesize)) {
+ sapi_module.sapi_error(E_WARNING, "Max file size of %ld bytes exceeded - file [%s] not saved", PG(upload_max_filesize), param);
+ cancel_upload = 1;
+ } else if (max_file_size && (total_bytes > max_file_size)) {
+ sapi_module.sapi_error(E_WARNING, "Max file size of %ld bytes exceeded - file [%s] not saved", max_file_size, param);
+ cancel_upload = 1;
+ } else if (blen > 0) {
+ wlen = fwrite(buff, 1, blen, fp);
+
+ if (wlen < blen) {
+ sapi_module.sapi_error(E_WARNING, "Only %d bytes were written, expected to write %ld", wlen, blen);
+ cancel_upload = 1;
+ } else {
+ total_bytes += wlen;
+ }
+ }
+ }
+ fclose(fp);
+
+ if (cancel_upload) {
+ if(temp_filename) {
+ unlink(temp_filename);
+ }
+ if (temp_filename) efree(temp_filename);
+ if (filename) efree(filename);
+ SAFE_RETURN;
+ } else if(total_bytes > 0) {
+ zend_hash_add(SG(rfc1867_uploaded_files), temp_filename, strlen(temp_filename) + 1, &temp_filename, sizeof(char *), NULL);
+ } else {
+ if(temp_filename) {
+ unlink(temp_filename);
+ efree(temp_filename);
+ }
+ efree(filename);
+ temp_filename="";
+ filename="";
}
- }
- SG(request_info).post_data[SG(read_post_bytes)] = 0; /* terminating NULL */
- SG(request_info).post_data_length = SG(read_post_bytes);
- }
- /* </FIXME> */
+ /* Initialize variables */
+ add_protected_variable(param TSRMLS_CC);
+
+ magic_quotes_gpc = PG(magic_quotes_gpc);
+ PG(magic_quotes_gpc) = 0;
+ safe_php_register_variable(param, temp_filename, NULL, 1 TSRMLS_CC);
+
+ /* is_arr_upload is true when name of file upload field
+ * ends in [.*]
+ * start_arr is set to point to 1st [
+ * end_arr points to last ]
+ */
+ is_arr_upload = (start_arr = strchr(param,'[')) &&
+ (end_arr = strrchr(param,']')) &&
+ (end_arr = param+strlen(param)-1);
+
+ if(is_arr_upload) {
+ array_len = strlen(start_arr);
+ if(array_index) efree(array_index);
+ array_index = estrndup(start_arr+1, array_len-2);
+ }
+
+
+ /* Add $foo_name */
+ if(lbuf) efree(lbuf);
+ lbuf = (char *) emalloc(strlen(param) + array_len + MAX_SIZE_OF_INDEX + 1);
+
+ if (is_arr_upload) {
+ if (abuf) efree(abuf);
+ abuf = estrndup(param, strlen(param)-array_len);
+ sprintf(lbuf, "%s_name[%s]", abuf, array_index);
+ } else {
+ sprintf(lbuf, "%s_name", param);
+ }
- if (SG(request_info).post_data) {
- php_mime_split(SG(request_info).post_data, SG(request_info).post_data_length, boundary, boundary_len, array_ptr TSRMLS_CC);
+ s = strrchr(filename, '\\');
+ if (s && s > filename) {
+ safe_php_register_variable(lbuf, s+1, NULL, 0 TSRMLS_CC);
+ } else {
+ safe_php_register_variable(lbuf, filename, NULL, 0 TSRMLS_CC);
+ }
+
+ /* Add $foo[name] */
+ if (is_arr_upload) {
+ sprintf(lbuf, "%s[name][%s]", abuf, array_index);
+ } else {
+ sprintf(lbuf, "%s[name]", param);
+ }
+ if (s && s > filename) {
+ register_http_post_files_variable(lbuf, s+1, http_post_files, 0 TSRMLS_CC);
+ } else {
+ register_http_post_files_variable(lbuf, filename, http_post_files, 0 TSRMLS_CC);
+ }
+ s = "";
+
+
+ /* Possible Content-Type: */
+ if (!(cd = php_mime_get_hdr_value(header, "Content-Type")) || filename == "") {
+ cd = "";
+ }
+
+ /* Add $foo_type */
+ if (is_arr_upload) {
+ sprintf(lbuf, "%s_type[%s]", abuf, array_index);
+ } else {
+ sprintf(lbuf, "%s_type", param);
+ }
+ safe_php_register_variable(lbuf, cd, NULL, 0 TSRMLS_CC);
+
+ /* Add $foo[type] */
+ if (is_arr_upload) {
+ sprintf(lbuf, "%s[type][%s]", abuf, array_index);
+ } else {
+ sprintf(lbuf, "%s[type]", param);
+ }
+ register_http_post_files_variable(lbuf, cd, http_post_files, 0 TSRMLS_CC);
+
+
+ /* Add $foo[tmp_name] */
+ if(is_arr_upload) {
+ sprintf(lbuf, "%s[tmp_name][%s]", abuf, array_index);
+ } else {
+ sprintf(lbuf, "%s[tmp_name]", param);
+ }
+ add_protected_variable(lbuf TSRMLS_CC);
+ register_http_post_files_variable(lbuf, temp_filename, http_post_files, 1 TSRMLS_CC);
+ PG(magic_quotes_gpc) = magic_quotes_gpc;
+
+ {
+ zval file_size;
+
+ file_size.value.lval = total_bytes;
+ file_size.type = IS_LONG;
+
+ /* Add $foo_size */
+ if(is_arr_upload) {
+ sprintf(lbuf, "%s_size[%s]", abuf, array_index);
+ } else {
+ sprintf(lbuf, "%s_size", param);
+ }
+ safe_php_register_variable_ex(lbuf, &file_size, NULL, 0 TSRMLS_CC);
+
+ /* Add $foo[size] */
+ if (is_arr_upload) {
+ sprintf(lbuf, "%s[size][%s]", abuf, array_index);
+ } else {
+ sprintf(lbuf, "%s[size]", param);
+ }
+ register_http_post_files_variable_ex(lbuf, &file_size, http_post_files, 0 TSRMLS_CC);
+ }
+ }
}
+
+ SAFE_RETURN;
}
/*