]> granicus.if.org Git - php/commitdiff
@- Fixed HTTP file upload support to handle big files better. (Jani)
authorfoobar <sniper@php.net>
Sat, 27 Oct 2001 05:26:24 +0000 (05:26 +0000)
committerfoobar <sniper@php.net>
Sat, 27 Oct 2001 05:26:24 +0000 (05:26 +0000)
# There are some minor memleaks still..I tried to eliminate them but
# without luck. I'd be glad if someone could check this code out.
# Also, this uses the Apache libapreq. So there might be need to add some
# license thingie there too?

main/rfc1867.c

index abba25c221af95ff77583160d90298ed9c9e8d61..0c66baf0b9e32e24cdf390623318dc5fab34f589 100644 (file)
 #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;
@@ -55,7 +66,7 @@ static void safe_php_register_variable(char *var, char *strval, zval *track_vars
 }
 
 
-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);
@@ -97,419 +108,462 @@ void destroy_uploaded_files_hash(TSRMLS_D)
        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++;
@@ -521,38 +575,246 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler)
                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;
 }
 
 /*