1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2013 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
16   |          Jani Taskinen <jani@php.net>                                |
17   +----------------------------------------------------------------------+
18 */
19
20/* $Id$ */
21
22/*
23 *  This product includes software developed by the Apache Group
24 *  for use in the Apache HTTP server project (http://www.apache.org/).
25 *
26 */
27
28#include <stdio.h>
29#include "php.h"
30#include "php_open_temporary_file.h"
31#include "zend_globals.h"
32#include "php_globals.h"
33#include "php_variables.h"
34#include "rfc1867.h"
35#include "ext/standard/php_string.h"
36
37#define DEBUG_FILE_UPLOAD ZEND_DEBUG
38
39PHPAPI int (*php_rfc1867_callback)(unsigned int event, void *event_data, void **extra TSRMLS_DC) = NULL;
40
41#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
42#include "ext/mbstring/mbstring.h"
43
44static void safe_php_register_variable(char *var, char *strval, int val_len, zval *track_vars_array, zend_bool override_protection TSRMLS_DC);
45
46#define SAFE_RETURN { \
47    php_mb_flush_gpc_variables(num_vars, val_list, len_list, array_ptr TSRMLS_CC); \
48    if (lbuf) efree(lbuf); \
49    if (abuf) efree(abuf); \
50    if (array_index) efree(array_index); \
51    zend_hash_destroy(&PG(rfc1867_protected_variables)); \
52    zend_llist_destroy(&header); \
53    if (mbuff->boundary_next) efree(mbuff->boundary_next); \
54    if (mbuff->boundary) efree(mbuff->boundary); \
55    if (mbuff->buffer) efree(mbuff->buffer); \
56    if (mbuff) efree(mbuff); \
57    return; }
58
59void php_mb_flush_gpc_variables(int num_vars, char **val_list, int *len_list, zval *array_ptr  TSRMLS_DC) /* {{{ */
60{
61    int i;
62    if (php_mb_encoding_translation(TSRMLS_C)) {
63        if (num_vars > 0 &&
64            php_mb_gpc_encoding_detector(val_list, len_list, num_vars, NULL TSRMLS_CC) == SUCCESS) {
65            php_mb_gpc_encoding_converter(val_list, len_list, num_vars, NULL, NULL TSRMLS_CC);
66        }
67        for (i = 0; i<num_vars; i += 2) {
68            safe_php_register_variable(val_list[i], val_list[i+1], len_list[i+1], array_ptr, 0 TSRMLS_CC);
69            efree(val_list[i]);
70            efree(val_list[i+1]);
71        }
72        efree(val_list);
73        efree(len_list);
74    }
75}
76/* }}} */
77
78void php_mb_gpc_realloc_buffer(char ***pval_list, int **plen_list, int *num_vars_max, int inc  TSRMLS_DC) /* {{{ */
79{
80    /* allow only even increments */
81    if (inc & 1) {
82        inc++;
83    }
84    (*num_vars_max) += inc;
85    *pval_list = (char **)erealloc(*pval_list, (*num_vars_max+2)*sizeof(char *));
86    *plen_list = (int *)erealloc(*plen_list, (*num_vars_max+2)*sizeof(int));
87}
88/* }}} */
89
90void php_mb_gpc_stack_variable(char *param, char *value, char ***pval_list, int **plen_list, int *num_vars, int *num_vars_max TSRMLS_DC) /* {{{ */
91{
92    char **val_list = *pval_list;
93    int *len_list = *plen_list;
94
95    if (*num_vars >= *num_vars_max) {
96        php_mb_gpc_realloc_buffer(pval_list, plen_list, num_vars_max, 16 TSRMLS_CC);
97        /* in case realloc relocated the buffer */
98        val_list = *pval_list;
99        len_list = *plen_list;
100    }
101
102    val_list[*num_vars] = (char *)estrdup(param);
103    len_list[*num_vars] = strlen(param);
104    (*num_vars)++;
105    val_list[*num_vars] = (char *)estrdup(value);
106    len_list[*num_vars] = strlen(value);
107    (*num_vars)++;
108}
109/* }}} */
110
111#else
112
113#define SAFE_RETURN { \
114    if (lbuf) efree(lbuf); \
115    if (abuf) efree(abuf); \
116    if (array_index) efree(array_index); \
117    zend_hash_destroy(&PG(rfc1867_protected_variables)); \
118    zend_llist_destroy(&header); \
119    if (mbuff->boundary_next) efree(mbuff->boundary_next); \
120    if (mbuff->boundary) efree(mbuff->boundary); \
121    if (mbuff->buffer) efree(mbuff->buffer); \
122    if (mbuff) efree(mbuff); \
123    return; }
124#endif
125
126/* The longest property name we use in an uploaded file array */
127#define MAX_SIZE_OF_INDEX sizeof("[tmp_name]")
128
129/* The longest anonymous name */
130#define MAX_SIZE_ANONNAME 33
131
132/* Errors */
133#define UPLOAD_ERROR_OK   0  /* File upload succesful */
134#define UPLOAD_ERROR_A    1  /* Uploaded file exceeded upload_max_filesize */
135#define UPLOAD_ERROR_B    2  /* Uploaded file exceeded MAX_FILE_SIZE */
136#define UPLOAD_ERROR_C    3  /* Partially uploaded */
137#define UPLOAD_ERROR_D    4  /* No file uploaded */
138#define UPLOAD_ERROR_E    6  /* Missing /tmp or similar directory */
139#define UPLOAD_ERROR_F    7  /* Failed to write file to disk */
140#define UPLOAD_ERROR_X    8  /* File upload stopped by extension */
141
142void php_rfc1867_register_constants(TSRMLS_D) /* {{{ */
143{
144    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_OK",         UPLOAD_ERROR_OK, CONST_CS | CONST_PERSISTENT);
145    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_INI_SIZE",   UPLOAD_ERROR_A,  CONST_CS | CONST_PERSISTENT);
146    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_FORM_SIZE",  UPLOAD_ERROR_B,  CONST_CS | CONST_PERSISTENT);
147    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_PARTIAL",    UPLOAD_ERROR_C,  CONST_CS | CONST_PERSISTENT);
148    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_FILE",    UPLOAD_ERROR_D,  CONST_CS | CONST_PERSISTENT);
149    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_TMP_DIR", UPLOAD_ERROR_E,  CONST_CS | CONST_PERSISTENT);
150    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_CANT_WRITE", UPLOAD_ERROR_F,  CONST_CS | CONST_PERSISTENT);
151    REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_EXTENSION",  UPLOAD_ERROR_X,  CONST_CS | CONST_PERSISTENT);
152}
153/* }}} */
154
155static void normalize_protected_variable(char *varname TSRMLS_DC) /* {{{ */
156{
157    char *s = varname, *index = NULL, *indexend = NULL, *p;
158
159    /* overjump leading space */
160    while (*s == ' ') {
161        s++;
162    }
163
164    /* and remove it */
165    if (s != varname) {
166        memmove(varname, s, strlen(s)+1);
167    }
168
169    for (p = varname; *p && *p != '['; p++) {
170        switch(*p) {
171            case ' ':
172            case '.':
173                *p = '_';
174                break;
175        }
176    }
177
178    /* find index */
179    index = strchr(varname, '[');
180    if (index) {
181        index++;
182        s = index;
183    } else {
184        return;
185    }
186
187    /* done? */
188    while (index) {
189        while (*index == ' ' || *index == '\r' || *index == '\n' || *index=='\t') {
190            index++;
191        }
192        indexend = strchr(index, ']');
193        indexend = indexend ? indexend + 1 : index + strlen(index);
194
195        if (s != index) {
196            memmove(s, index, strlen(index)+1);
197            s += indexend-index;
198        } else {
199            s = indexend;
200        }
201
202        if (*s == '[') {
203            s++;
204            index = s;
205        } else {
206            index = NULL;
207        }
208    }
209    *s = '\0';
210}
211/* }}} */
212
213static void add_protected_variable(char *varname TSRMLS_DC) /* {{{ */
214{
215    int dummy = 1;
216
217    normalize_protected_variable(varname TSRMLS_CC);
218    zend_hash_add(&PG(rfc1867_protected_variables), varname, strlen(varname)+1, &dummy, sizeof(int), NULL);
219}
220/* }}} */
221
222static zend_bool is_protected_variable(char *varname TSRMLS_DC) /* {{{ */
223{
224    normalize_protected_variable(varname TSRMLS_CC);
225    return zend_hash_exists(&PG(rfc1867_protected_variables), varname, strlen(varname)+1);
226}
227/* }}} */
228
229static void safe_php_register_variable(char *var, char *strval, int val_len, zval *track_vars_array, zend_bool override_protection TSRMLS_DC) /* {{{ */
230{
231    if (override_protection || !is_protected_variable(var TSRMLS_CC)) {
232        php_register_variable_safe(var, strval, val_len, track_vars_array TSRMLS_CC);
233    }
234}
235/* }}} */
236
237static void safe_php_register_variable_ex(char *var, zval *val, zval *track_vars_array, zend_bool override_protection TSRMLS_DC) /* {{{ */
238{
239    if (override_protection || !is_protected_variable(var TSRMLS_CC)) {
240        php_register_variable_ex(var, val, track_vars_array TSRMLS_CC);
241    }
242}
243/* }}} */
244
245static void register_http_post_files_variable(char *strvar, char *val, zval *http_post_files, zend_bool override_protection TSRMLS_DC) /* {{{ */
246{
247    int register_globals = PG(register_globals);
248
249    PG(register_globals) = 0;
250    safe_php_register_variable(strvar, val, strlen(val), http_post_files, override_protection TSRMLS_CC);
251    PG(register_globals) = register_globals;
252}
253/* }}} */
254
255static void register_http_post_files_variable_ex(char *var, zval *val, zval *http_post_files, zend_bool override_protection TSRMLS_DC) /* {{{ */
256{
257    int register_globals = PG(register_globals);
258
259    PG(register_globals) = 0;
260    safe_php_register_variable_ex(var, val, http_post_files, override_protection TSRMLS_CC);
261    PG(register_globals) = register_globals;
262}
263/* }}} */
264
265static int unlink_filename(char **filename TSRMLS_DC) /* {{{ */
266{
267    VCWD_UNLINK(*filename);
268    return 0;
269}
270/* }}} */
271
272void destroy_uploaded_files_hash(TSRMLS_D) /* {{{ */
273{
274    zend_hash_apply(SG(rfc1867_uploaded_files), (apply_func_t) unlink_filename TSRMLS_CC);
275    zend_hash_destroy(SG(rfc1867_uploaded_files));
276    FREE_HASHTABLE(SG(rfc1867_uploaded_files));
277}
278/* }}} */
279
280/* {{{ Following code is based on apache_multipart_buffer.c from libapreq-0.33 package. */
281
282#define FILLUNIT (1024 * 5)
283
284typedef struct {
285
286    /* read buffer */
287    char *buffer;
288    char *buf_begin;
289    int  bufsize;
290    int  bytes_in_buffer;
291
292    /* boundary info */
293    char *boundary;
294    char *boundary_next;
295    int  boundary_next_len;
296
297} multipart_buffer;
298
299typedef struct {
300    char *key;
301    char *value;
302} mime_header_entry;
303
304/*
305 * Fill up the buffer with client data.
306 * Returns number of bytes added to buffer.
307 */
308static int fill_buffer(multipart_buffer *self TSRMLS_DC)
309{
310    int bytes_to_read, total_read = 0, actual_read = 0;
311
312    /* shift the existing data if necessary */
313    if (self->bytes_in_buffer > 0 && self->buf_begin != self->buffer) {
314        memmove(self->buffer, self->buf_begin, self->bytes_in_buffer);
315    }
316
317    self->buf_begin = self->buffer;
318
319    /* calculate the free space in the buffer */
320    bytes_to_read = self->bufsize - self->bytes_in_buffer;
321
322    /* read the required number of bytes */
323    while (bytes_to_read > 0) {
324
325        char *buf = self->buffer + self->bytes_in_buffer;
326
327        actual_read = sapi_module.read_post(buf, bytes_to_read TSRMLS_CC);
328
329        /* update the buffer length */
330        if (actual_read > 0) {
331            self->bytes_in_buffer += actual_read;
332            SG(read_post_bytes) += actual_read;
333            total_read += actual_read;
334            bytes_to_read -= actual_read;
335        } else {
336            break;
337        }
338    }
339
340    return total_read;
341}
342
343/* eof if we are out of bytes, or if we hit the final boundary */
344static int multipart_buffer_eof(multipart_buffer *self TSRMLS_DC)
345{
346    if ( (self->bytes_in_buffer == 0 && fill_buffer(self TSRMLS_CC) < 1) ) {
347        return 1;
348    } else {
349        return 0;
350    }
351}
352
353/* create new multipart_buffer structure */
354static multipart_buffer *multipart_buffer_new(char *boundary, int boundary_len)
355{
356    multipart_buffer *self = (multipart_buffer *) ecalloc(1, sizeof(multipart_buffer));
357
358    int minsize = boundary_len + 6;
359    if (minsize < FILLUNIT) minsize = FILLUNIT;
360
361    self->buffer = (char *) ecalloc(1, minsize + 1);
362    self->bufsize = minsize;
363
364    spprintf(&self->boundary, 0, "--%s", boundary);
365
366    self->boundary_next_len = spprintf(&self->boundary_next, 0, "\n--%s", boundary);
367
368    self->buf_begin = self->buffer;
369    self->bytes_in_buffer = 0;
370
371    return self;
372}
373
374/*
375 * Gets the next CRLF terminated line from the input buffer.
376 * If it doesn't find a CRLF, and the buffer isn't completely full, returns
377 * NULL; otherwise, returns the beginning of the null-terminated line,
378 * minus the CRLF.
379 *
380 * Note that we really just look for LF terminated lines. This works
381 * around a bug in internet explorer for the macintosh which sends mime
382 * boundaries that are only LF terminated when you use an image submit
383 * button in a multipart/form-data form.
384 */
385static char *next_line(multipart_buffer *self)
386{
387    /* look for LF in the data */
388    char* line = self->buf_begin;
389    char* ptr = memchr(self->buf_begin, '\n', self->bytes_in_buffer);
390
391    if (ptr) {  /* LF found */
392
393        /* terminate the string, remove CRLF */
394        if ((ptr - line) > 0 && *(ptr-1) == '\r') {
395            *(ptr-1) = 0;
396        } else {
397            *ptr = 0;
398        }
399
400        /* bump the pointer */
401        self->buf_begin = ptr + 1;
402        self->bytes_in_buffer -= (self->buf_begin - line);
403
404    } else {    /* no LF found */
405
406        /* buffer isn't completely full, fail */
407        if (self->bytes_in_buffer < self->bufsize) {
408            return NULL;
409        }
410        /* return entire buffer as a partial line */
411        line[self->bufsize] = 0;
412        self->buf_begin = ptr;
413        self->bytes_in_buffer = 0;
414    }
415
416    return line;
417}
418
419/* Returns the next CRLF terminated line from the client */
420static char *get_line(multipart_buffer *self TSRMLS_DC)
421{
422    char* ptr = next_line(self);
423
424    if (!ptr) {
425        fill_buffer(self TSRMLS_CC);
426        ptr = next_line(self);
427    }
428
429    return ptr;
430}
431
432/* Free header entry */
433static void php_free_hdr_entry(mime_header_entry *h)
434{
435    if (h->key) {
436        efree(h->key);
437    }
438    if (h->value) {
439        efree(h->value);
440    }
441}
442
443/* finds a boundary */
444static int find_boundary(multipart_buffer *self, char *boundary TSRMLS_DC)
445{
446    char *line;
447
448    /* loop thru lines */
449    while( (line = get_line(self TSRMLS_CC)) )
450    {
451        /* finished if we found the boundary */
452        if (!strcmp(line, boundary)) {
453            return 1;
454        }
455    }
456
457    /* didn't find the boundary */
458    return 0;
459}
460
461/* parse headers */
462static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)
463{
464    char *line;
465    mime_header_entry prev_entry, entry;
466    int prev_len, cur_len;
467
468    /* didn't find boundary, abort */
469    if (!find_boundary(self, self->boundary TSRMLS_CC)) {
470        return 0;
471    }
472
473    /* get lines of text, or CRLF_CRLF */
474
475    while( (line = get_line(self TSRMLS_CC)) && strlen(line) > 0 )
476    {
477        /* add header to table */
478        char *key = line;
479        char *value = NULL;
480
481        /* space in the beginning means same header */
482        if (!isspace(line[0])) {
483            value = strchr(line, ':');
484        }
485
486        if (value) {
487            *value = 0;
488            do { value++; } while(isspace(*value));
489
490            entry.value = estrdup(value);
491            entry.key = estrdup(key);
492
493        } else if (zend_llist_count(header)) { /* If no ':' on the line, add to previous line */
494
495            prev_len = strlen(prev_entry.value);
496            cur_len = strlen(line);
497
498            entry.value = emalloc(prev_len + cur_len + 1);
499            memcpy(entry.value, prev_entry.value, prev_len);
500            memcpy(entry.value + prev_len, line, cur_len);
501            entry.value[cur_len + prev_len] = '\0';
502
503            entry.key = estrdup(prev_entry.key);
504
505            zend_llist_remove_tail(header);
506        } else {
507            continue;
508        }
509
510        zend_llist_add_element(header, &entry);
511        prev_entry = entry;
512    }
513
514    return 1;
515}
516
517static char *php_mime_get_hdr_value(zend_llist header, char *key)
518{
519    mime_header_entry *entry;
520
521    if (key == NULL) {
522        return NULL;
523    }
524
525    entry = zend_llist_get_first(&header);
526    while (entry) {
527        if (!strcasecmp(entry->key, key)) {
528            return entry->value;
529        }
530        entry = zend_llist_get_next(&header);
531    }
532
533    return NULL;
534}
535
536static char *php_ap_getword(char **line, char stop)
537{
538    char *pos = *line, quote;
539    char *res;
540
541    while (*pos && *pos != stop) {
542        if ((quote = *pos) == '"' || quote == '\'') {
543            ++pos;
544            while (*pos && *pos != quote) {
545                if (*pos == '\\' && pos[1] && pos[1] == quote) {
546                    pos += 2;
547                } else {
548                    ++pos;
549                }
550            }
551            if (*pos) {
552                ++pos;
553            }
554        } else ++pos;
555    }
556    if (*pos == '\0') {
557        res = estrdup(*line);
558        *line += strlen(*line);
559        return res;
560    }
561
562    res = estrndup(*line, pos - *line);
563
564    while (*pos == stop) {
565        ++pos;
566    }
567
568    *line = pos;
569    return res;
570}
571
572static char *substring_conf(char *start, int len, char quote TSRMLS_DC)
573{
574    char *result = emalloc(len + 2);
575    char *resp = result;
576    int i;
577
578    for (i = 0; i < len; ++i) {
579        if (start[i] == '\\' && (start[i + 1] == '\\' || (quote && start[i + 1] == quote))) {
580            *resp++ = start[++i];
581        } else {
582#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
583            if (php_mb_encoding_translation(TSRMLS_C)) {
584                size_t j = php_mb_gpc_mbchar_bytes(start+i TSRMLS_CC);
585                while (j-- > 0 && i < len) {
586                    *resp++ = start[i++];
587                }
588                --i;
589            } else {
590                *resp++ = start[i];
591            }
592#else
593            *resp++ = start[i];
594#endif
595        }
596    }
597
598    *resp = '\0';
599    return result;
600}
601
602static char *php_ap_getword_conf(char **line TSRMLS_DC)
603{
604    char *str = *line, *strend, *res, quote;
605
606#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
607    if (php_mb_encoding_translation(TSRMLS_C)) {
608        int len=strlen(str);
609        php_mb_gpc_encoding_detector(&str, &len, 1, NULL TSRMLS_CC);
610    }
611#endif
612
613    while (*str && isspace(*str)) {
614        ++str;
615    }
616
617    if (!*str) {
618        *line = str;
619        return estrdup("");
620    }
621
622    if ((quote = *str) == '"' || quote == '\'') {
623        strend = str + 1;
624look_for_quote:
625        while (*strend && *strend != quote) {
626            if (*strend == '\\' && strend[1] && strend[1] == quote) {
627                strend += 2;
628            } else {
629                ++strend;
630            }
631        }
632        if (*strend && *strend == quote) {
633            char p = *(strend + 1);
634            if (p != '\r' && p != '\n' && p != '\0') {
635                strend++;
636                goto look_for_quote;
637            }
638        }
639
640        res = substring_conf(str + 1, strend - str - 1, quote TSRMLS_CC);
641
642        if (*strend == quote) {
643            ++strend;
644        }
645
646    } else {
647
648        strend = str;
649        while (*strend && !isspace(*strend)) {
650            ++strend;
651        }
652        res = substring_conf(str, strend - str, 0 TSRMLS_CC);
653    }
654
655    while (*strend && isspace(*strend)) {
656        ++strend;
657    }
658
659    *line = strend;
660    return res;
661}
662
663/*
664 * Search for a string in a fixed-length byte string.
665 * If partial is true, partial matches are allowed at the end of the buffer.
666 * Returns NULL if not found, or a pointer to the start of the first match.
667 */
668static void *php_ap_memstr(char *haystack, int haystacklen, char *needle, int needlen, int partial)
669{
670    int len = haystacklen;
671    char *ptr = haystack;
672
673    /* iterate through first character matches */
674    while( (ptr = memchr(ptr, needle[0], len)) ) {
675
676        /* calculate length after match */
677        len = haystacklen - (ptr - (char *)haystack);
678
679        /* done if matches up to capacity of buffer */
680        if (memcmp(needle, ptr, needlen < len ? needlen : len) == 0 && (partial || len >= needlen)) {
681            break;
682        }
683
684        /* next character */
685        ptr++; len--;
686    }
687
688    return ptr;
689}
690
691/* read until a boundary condition */
692static int multipart_buffer_read(multipart_buffer *self, char *buf, int bytes, int *end TSRMLS_DC)
693{
694    int len, max;
695    char *bound;
696
697    /* fill buffer if needed */
698    if (bytes > self->bytes_in_buffer) {
699        fill_buffer(self TSRMLS_CC);
700    }
701
702    /* look for a potential boundary match, only read data up to that point */
703    if ((bound = php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 1))) {
704        max = bound - self->buf_begin;
705        if (end && php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 0)) {
706            *end = 1;
707        }
708    } else {
709        max = self->bytes_in_buffer;
710    }
711
712    /* maximum number of bytes we are reading */
713    len = max < bytes-1 ? max : bytes-1;
714
715    /* if we read any data... */
716    if (len > 0) {
717
718        /* copy the data */
719        memcpy(buf, self->buf_begin, len);
720        buf[len] = 0;
721
722        if (bound && len > 0 && buf[len-1] == '\r') {
723            buf[--len] = 0;
724        }
725
726        /* update the buffer */
727        self->bytes_in_buffer -= len;
728        self->buf_begin += len;
729    }
730
731    return len;
732}
733
734/*
735  XXX: this is horrible memory-usage-wise, but we only expect
736  to do this on small pieces of form data.
737*/
738static char *multipart_buffer_read_body(multipart_buffer *self, unsigned int *len TSRMLS_DC)
739{
740    char buf[FILLUNIT], *out=NULL;
741    int total_bytes=0, read_bytes=0;
742
743    while((read_bytes = multipart_buffer_read(self, buf, sizeof(buf), NULL TSRMLS_CC))) {
744        out = erealloc(out, total_bytes + read_bytes + 1);
745        memcpy(out + total_bytes, buf, read_bytes);
746        total_bytes += read_bytes;
747    }
748
749    if (out) {
750        out[total_bytes] = '\0';
751    }
752    *len = total_bytes;
753
754    return out;
755}
756/* }}} */
757
758/*
759 * The combined READER/HANDLER
760 *
761 */
762
763SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */
764{
765    char *boundary, *s = NULL, *boundary_end = NULL, *start_arr = NULL, *array_index = NULL;
766    char *temp_filename = NULL, *lbuf = NULL, *abuf = NULL;
767    int boundary_len = 0, total_bytes = 0, cancel_upload = 0, is_arr_upload = 0, array_len = 0;
768    int max_file_size = 0, skip_upload = 0, anonindex = 0, is_anonymous;
769    zval *http_post_files = NULL;
770    HashTable *uploaded_files = NULL;
771#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
772    int str_len = 0, num_vars = 0, num_vars_max = 2*10, *len_list = NULL;
773    char **val_list = NULL;
774#endif
775    multipart_buffer *mbuff;
776    zval *array_ptr = (zval *) arg;
777    int fd = -1;
778    zend_llist header;
779    void *event_extra_data = NULL;
780    int llen = 0;
781    int upload_cnt = INI_INT("max_file_uploads");
782    long count = 0;
783
784    if (SG(post_max_size) > 0 && SG(request_info).content_length > SG(post_max_size)) {
785        sapi_module.sapi_error(E_WARNING, "POST Content-Length of %ld bytes exceeds the limit of %ld bytes", SG(request_info).content_length, SG(post_max_size));
786        return;
787    }
788
789    /* Get the boundary */
790    boundary = strstr(content_type_dup, "boundary");
791    if (!boundary) {
792        int content_type_len = strlen(content_type_dup);
793        char *content_type_lcase = estrndup(content_type_dup, content_type_len);
794
795        php_strtolower(content_type_lcase, content_type_len);
796        boundary = strstr(content_type_lcase, "boundary");
797        if (boundary) {
798            boundary = content_type_dup + (boundary - content_type_lcase);
799        }
800        efree(content_type_lcase);
801    }
802
803    if (!boundary || !(boundary = strchr(boundary, '='))) {
804        sapi_module.sapi_error(E_WARNING, "Missing boundary in multipart/form-data POST data");
805        return;
806    }
807
808    boundary++;
809    boundary_len = strlen(boundary);
810
811    if (boundary[0] == '"') {
812        boundary++;
813        boundary_end = strchr(boundary, '"');
814        if (!boundary_end) {
815            sapi_module.sapi_error(E_WARNING, "Invalid boundary in multipart/form-data POST data");
816            return;
817        }
818    } else {
819        /* search for the end of the boundary */
820        boundary_end = strpbrk(boundary, ",;");
821    }
822    if (boundary_end) {
823        boundary_end[0] = '\0';
824        boundary_len = boundary_end-boundary;
825    }
826
827    /* Initialize the buffer */
828    if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) {
829        sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer");
830        return;
831    }
832
833    /* Initialize $_FILES[] */
834    zend_hash_init(&PG(rfc1867_protected_variables), 5, NULL, NULL, 0);
835
836    ALLOC_HASHTABLE(uploaded_files);
837    zend_hash_init(uploaded_files, 5, NULL, (dtor_func_t) free_estring, 0);
838    SG(rfc1867_uploaded_files) = uploaded_files;
839
840    ALLOC_ZVAL(http_post_files);
841    array_init(http_post_files);
842    INIT_PZVAL(http_post_files);
843    PG(http_globals)[TRACK_VARS_FILES] = http_post_files;
844
845#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
846    if (php_mb_encoding_translation(TSRMLS_C)) {
847        val_list = (char **)ecalloc(num_vars_max+2, sizeof(char *));
848        len_list = (int *)ecalloc(num_vars_max+2, sizeof(int));
849    }
850#endif
851    zend_llist_init(&header, sizeof(mime_header_entry), (llist_dtor_func_t) php_free_hdr_entry, 0);
852
853    if (php_rfc1867_callback != NULL) {
854        multipart_event_start event_start;
855
856        event_start.content_length = SG(request_info).content_length;
857        if (php_rfc1867_callback(MULTIPART_EVENT_START, &event_start, &event_extra_data TSRMLS_CC) == FAILURE) {
858            goto fileupload_done;
859        }
860    }
861
862    while (!multipart_buffer_eof(mbuff TSRMLS_CC))
863    {
864        char buff[FILLUNIT];
865        char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL;
866        size_t blen = 0, wlen = 0;
867        off_t offset;
868
869        zend_llist_clean(&header);
870
871        if (!multipart_buffer_headers(mbuff, &header TSRMLS_CC)) {
872            goto fileupload_done;
873        }
874
875        if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) {
876            char *pair = NULL;
877            int end = 0;
878
879            while (isspace(*cd)) {
880                ++cd;
881            }
882
883            while (*cd && (pair = php_ap_getword(&cd, ';')))
884            {
885                char *key = NULL, *word = pair;
886
887                while (isspace(*cd)) {
888                    ++cd;
889                }
890
891                if (strchr(pair, '=')) {
892                    key = php_ap_getword(&pair, '=');
893
894                    if (!strcasecmp(key, "name")) {
895                        if (param) {
896                            efree(param);
897                        }
898                        param = php_ap_getword_conf(&pair TSRMLS_CC);
899                    } else if (!strcasecmp(key, "filename")) {
900                        if (filename) {
901                            efree(filename);
902                        }
903                        filename = php_ap_getword_conf(&pair TSRMLS_CC);
904                    }
905                }
906                if (key) {
907                    efree(key);
908                }
909                efree(word);
910            }
911
912            /* Normal form variable, safe to read all data into memory */
913            if (!filename && param) {
914                unsigned int value_len;
915                char *value = multipart_buffer_read_body(mbuff, &value_len TSRMLS_CC);
916                unsigned int new_val_len; /* Dummy variable */
917
918                if (!value) {
919                    value = estrdup("");
920                }
921
922                if (++count <= PG(max_input_vars) && sapi_module.input_filter(PARSE_POST, param, &value, value_len, &new_val_len TSRMLS_CC)) {
923                    if (php_rfc1867_callback != NULL) {
924                        multipart_event_formdata event_formdata;
925                        size_t newlength = new_val_len;
926
927                        event_formdata.post_bytes_processed = SG(read_post_bytes);
928                        event_formdata.name = param;
929                        event_formdata.value = &value;
930                        event_formdata.length = new_val_len;
931                        event_formdata.newlength = &newlength;
932                        if (php_rfc1867_callback(MULTIPART_EVENT_FORMDATA, &event_formdata, &event_extra_data TSRMLS_CC) == FAILURE) {
933                            efree(param);
934                            efree(value);
935                            continue;
936                        }
937                        new_val_len = newlength;
938                    }
939
940#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
941                    if (php_mb_encoding_translation(TSRMLS_C)) {
942                        php_mb_gpc_stack_variable(param, value, &val_list, &len_list, &num_vars, &num_vars_max TSRMLS_CC);
943                    } else {
944                        safe_php_register_variable(param, value, new_val_len, array_ptr, 0 TSRMLS_CC);
945                    }
946#else
947                    safe_php_register_variable(param, value, new_val_len, array_ptr, 0 TSRMLS_CC);
948#endif
949                } else {
950                    if (count == PG(max_input_vars) + 1) {
951                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Input variables exceeded %ld. To increase the limit change max_input_vars in php.ini.", PG(max_input_vars));
952                    }
953
954                    if (php_rfc1867_callback != NULL) {
955                        multipart_event_formdata event_formdata;
956
957                        event_formdata.post_bytes_processed = SG(read_post_bytes);
958                        event_formdata.name = param;
959                        event_formdata.value = &value;
960                        event_formdata.length = value_len;
961                        event_formdata.newlength = NULL;
962                        php_rfc1867_callback(MULTIPART_EVENT_FORMDATA, &event_formdata, &event_extra_data TSRMLS_CC);
963                    }
964                }
965
966                if (!strcasecmp(param, "MAX_FILE_SIZE")) {
967                    max_file_size = atol(value);
968                }
969
970                efree(param);
971                efree(value);
972                continue;
973            }
974
975            /* If file_uploads=off, skip the file part */
976            if (!PG(file_uploads)) {
977                skip_upload = 1;
978            } else if (upload_cnt <= 0) {
979                skip_upload = 1;
980                sapi_module.sapi_error(E_WARNING, "Maximum number of allowable file uploads has been exceeded");
981            }
982
983            /* Return with an error if the posted data is garbled */
984            if (!param && !filename) {
985                sapi_module.sapi_error(E_WARNING, "File Upload Mime headers garbled");
986                goto fileupload_done;
987            }
988
989            if (!param) {
990                is_anonymous = 1;
991                param = emalloc(MAX_SIZE_ANONNAME);
992                snprintf(param, MAX_SIZE_ANONNAME, "%u", anonindex++);
993            } else {
994                is_anonymous = 0;
995            }
996
997            /* New Rule: never repair potential malicious user input */
998            if (!skip_upload) {
999                long c = 0;
1000                tmp = param;
1001
1002                while (*tmp) {
1003                    if (*tmp == '[') {
1004                        c++;
1005                    } else if (*tmp == ']') {
1006                        c--;
1007                        if (tmp[1] && tmp[1] != '[') {
1008                            skip_upload = 1;
1009                            break;
1010                        }
1011                    }
1012                    if (c < 0) {
1013                        skip_upload = 1;
1014                        break;
1015                    }
1016                    tmp++;
1017                }
1018                /* Brackets should always be closed */
1019                if(c != 0) {
1020                    skip_upload = 1;
1021                }
1022            }
1023
1024            total_bytes = cancel_upload = 0;
1025            temp_filename = NULL;
1026            fd = -1;
1027
1028            if (!skip_upload && php_rfc1867_callback != NULL) {
1029                multipart_event_file_start event_file_start;
1030
1031                event_file_start.post_bytes_processed = SG(read_post_bytes);
1032                event_file_start.name = param;
1033                event_file_start.filename = &filename;
1034                if (php_rfc1867_callback(MULTIPART_EVENT_FILE_START, &event_file_start, &event_extra_data TSRMLS_CC) == FAILURE) {
1035                    temp_filename = "";
1036                    efree(param);
1037                    efree(filename);
1038                    continue;
1039                }
1040            }
1041
1042            if (skip_upload) {
1043                efree(param);
1044                efree(filename);
1045                continue;
1046            }
1047
1048            if (strlen(filename) == 0) {
1049#if DEBUG_FILE_UPLOAD
1050                sapi_module.sapi_error(E_NOTICE, "No file uploaded");
1051#endif
1052                cancel_upload = UPLOAD_ERROR_D;
1053            }
1054
1055            offset = 0;
1056            end = 0;
1057
1058            if (!cancel_upload) {
1059                /* only bother to open temp file if we have data */
1060                blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end TSRMLS_CC);
1061#if DEBUG_FILE_UPLOAD
1062                if (blen > 0) {
1063#else
1064                /* in non-debug mode we have no problem with 0-length files */
1065                {
1066#endif
1067                    fd = php_open_temporary_fd_ex(PG(upload_tmp_dir), "php", &temp_filename, 1 TSRMLS_CC);
1068                    upload_cnt--;
1069                    if (fd == -1) {
1070                        sapi_module.sapi_error(E_WARNING, "File upload error - unable to create a temporary file");
1071                        cancel_upload = UPLOAD_ERROR_E;
1072                    }
1073                }
1074            }
1075
1076            while (!cancel_upload && (blen > 0))
1077            {
1078                if (php_rfc1867_callback != NULL) {
1079                    multipart_event_file_data event_file_data;
1080
1081                    event_file_data.post_bytes_processed = SG(read_post_bytes);
1082                    event_file_data.offset = offset;
1083                    event_file_data.data = buff;
1084                    event_file_data.length = blen;
1085                    event_file_data.newlength = &blen;
1086                    if (php_rfc1867_callback(MULTIPART_EVENT_FILE_DATA, &event_file_data, &event_extra_data TSRMLS_CC) == FAILURE) {
1087                        cancel_upload = UPLOAD_ERROR_X;
1088                        continue;
1089                    }
1090                }
1091
1092                if (PG(upload_max_filesize) > 0 && (total_bytes+blen) > PG(upload_max_filesize)) {
1093#if DEBUG_FILE_UPLOAD
1094                    sapi_module.sapi_error(E_NOTICE, "upload_max_filesize of %ld bytes exceeded - file [%s=%s] not saved", PG(upload_max_filesize), param, filename);
1095#endif
1096                    cancel_upload = UPLOAD_ERROR_A;
1097                } else if (max_file_size && ((total_bytes+blen) > max_file_size)) {
1098#if DEBUG_FILE_UPLOAD
1099                    sapi_module.sapi_error(E_NOTICE, "MAX_FILE_SIZE of %ld bytes exceeded - file [%s=%s] not saved", max_file_size, param, filename);
1100#endif
1101                    cancel_upload = UPLOAD_ERROR_B;
1102                } else if (blen > 0) {
1103                    wlen = write(fd, buff, blen);
1104
1105                    if (wlen == -1) {
1106                        /* write failed */
1107#if DEBUG_FILE_UPLOAD
1108                        sapi_module.sapi_error(E_NOTICE, "write() failed - %s", strerror(errno));
1109#endif
1110                        cancel_upload = UPLOAD_ERROR_F;
1111                    } else if (wlen < blen) {
1112#if DEBUG_FILE_UPLOAD
1113                        sapi_module.sapi_error(E_NOTICE, "Only %d bytes were written, expected to write %d", wlen, blen);
1114#endif
1115                        cancel_upload = UPLOAD_ERROR_F;
1116                    } else {
1117                        total_bytes += wlen;
1118                    }
1119                    offset += wlen;
1120                }
1121
1122                /* read data for next iteration */
1123                blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end TSRMLS_CC);
1124            }
1125
1126            if (fd != -1) { /* may not be initialized if file could not be created */
1127                close(fd);
1128            }
1129
1130            if (!cancel_upload && !end) {
1131#if DEBUG_FILE_UPLOAD
1132                sapi_module.sapi_error(E_NOTICE, "Missing mime boundary at the end of the data for file %s", strlen(filename) > 0 ? filename : "");
1133#endif
1134                cancel_upload = UPLOAD_ERROR_C;
1135            }
1136#if DEBUG_FILE_UPLOAD
1137            if (strlen(filename) > 0 && total_bytes == 0 && !cancel_upload) {
1138                sapi_module.sapi_error(E_WARNING, "Uploaded file size 0 - file [%s=%s] not saved", param, filename);
1139                cancel_upload = 5;
1140            }
1141#endif
1142            if (php_rfc1867_callback != NULL) {
1143                multipart_event_file_end event_file_end;
1144
1145                event_file_end.post_bytes_processed = SG(read_post_bytes);
1146                event_file_end.temp_filename = temp_filename;
1147                event_file_end.cancel_upload = cancel_upload;
1148                if (php_rfc1867_callback(MULTIPART_EVENT_FILE_END, &event_file_end, &event_extra_data TSRMLS_CC) == FAILURE) {
1149                    cancel_upload = UPLOAD_ERROR_X;
1150                }
1151            }
1152
1153            if (cancel_upload) {
1154                if (temp_filename) {
1155                    if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
1156                        unlink(temp_filename);
1157                    }
1158                    efree(temp_filename);
1159                }
1160                temp_filename = "";
1161            } else {
1162                zend_hash_add(SG(rfc1867_uploaded_files), temp_filename, strlen(temp_filename) + 1, &temp_filename, sizeof(char *), NULL);
1163            }
1164
1165            /* is_arr_upload is true when name of file upload field
1166             * ends in [.*]
1167             * start_arr is set to point to 1st [ */
1168            is_arr_upload = (start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ']');
1169
1170            if (is_arr_upload) {
1171                array_len = strlen(start_arr);
1172                if (array_index) {
1173                    efree(array_index);
1174                }
1175                array_index = estrndup(start_arr + 1, array_len - 2);
1176            }
1177
1178            /* Add $foo_name */
1179            if (llen < strlen(param) + MAX_SIZE_OF_INDEX + 1) {
1180                llen = strlen(param);
1181                lbuf = (char *) safe_erealloc(lbuf, llen, 1, MAX_SIZE_OF_INDEX + 1);
1182                llen += MAX_SIZE_OF_INDEX + 1;
1183            }
1184
1185            if (is_arr_upload) {
1186                if (abuf) efree(abuf);
1187                abuf = estrndup(param, strlen(param)-array_len);
1188                snprintf(lbuf, llen, "%s_name[%s]", abuf, array_index);
1189            } else {
1190                snprintf(lbuf, llen, "%s_name", param);
1191            }
1192
1193#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
1194            if (php_mb_encoding_translation(TSRMLS_C)) {
1195                if (num_vars >= num_vars_max) {
1196                    php_mb_gpc_realloc_buffer(&val_list, &len_list, &num_vars_max, 1 TSRMLS_CC);
1197                }
1198                val_list[num_vars] = filename;
1199                len_list[num_vars] = strlen(filename);
1200                num_vars++;
1201                if (php_mb_gpc_encoding_detector(val_list, len_list, num_vars, NULL TSRMLS_CC) == SUCCESS) {
1202                    str_len = strlen(filename);
1203                    php_mb_gpc_encoding_converter(&filename, &str_len, 1, NULL, NULL TSRMLS_CC);
1204                }
1205                s = php_mb_strrchr(filename, '\\' TSRMLS_CC);
1206                if ((tmp = php_mb_strrchr(filename, '/' TSRMLS_CC)) > s) {
1207                    s = tmp;
1208                }
1209                num_vars--;
1210                goto filedone;
1211            }
1212#endif
1213            /* The \ check should technically be needed for win32 systems only where
1214             * it is a valid path separator. However, IE in all it's wisdom always sends
1215             * the full path of the file on the user's filesystem, which means that unless
1216             * the user does basename() they get a bogus file name. Until IE's user base drops
1217             * to nill or problem is fixed this code must remain enabled for all systems. */
1218            s = strrchr(filename, '\\');
1219            if ((tmp = strrchr(filename, '/')) > s) {
1220                s = tmp;
1221            }
1222#ifdef PHP_WIN32
1223            if (PG(magic_quotes_gpc)) {
1224                if ((tmp = strrchr(s ? s : filename, '\'')) > s) {
1225                    s = tmp;
1226                }
1227                if ((tmp = strrchr(s ? s : filename, '"')) > s) {
1228                    s = tmp;
1229                }
1230            }
1231#endif
1232
1233#if HAVE_MBSTRING && !defined(COMPILE_DL_MBSTRING)
1234filedone:
1235#endif
1236
1237            if (!is_anonymous) {
1238                if (s && s >= filename) {
1239                    safe_php_register_variable(lbuf, s+1, strlen(s+1), NULL, 0 TSRMLS_CC);
1240                } else {
1241                    safe_php_register_variable(lbuf, filename, strlen(filename), NULL, 0 TSRMLS_CC);
1242                }
1243            }
1244
1245            /* Add $foo[name] */
1246            if (is_arr_upload) {
1247                snprintf(lbuf, llen, "%s[name][%s]", abuf, array_index);
1248            } else {
1249                snprintf(lbuf, llen, "%s[name]", param);
1250            }
1251            if (s && s >= filename) {
1252                register_http_post_files_variable(lbuf, s+1, http_post_files, 0 TSRMLS_CC);
1253            } else {
1254                register_http_post_files_variable(lbuf, filename, http_post_files, 0 TSRMLS_CC);
1255            }
1256            efree(filename);
1257            s = NULL;
1258
1259            /* Possible Content-Type: */
1260            if (cancel_upload || !(cd = php_mime_get_hdr_value(header, "Content-Type"))) {
1261                cd = "";
1262            } else {
1263                /* fix for Opera 6.01 */
1264                s = strchr(cd, ';');
1265                if (s != NULL) {
1266                    *s = '\0';
1267                }
1268            }
1269
1270            /* Add $foo_type */
1271            if (is_arr_upload) {
1272                snprintf(lbuf, llen, "%s_type[%s]", abuf, array_index);
1273            } else {
1274                snprintf(lbuf, llen, "%s_type", param);
1275            }
1276            if (!is_anonymous) {
1277                safe_php_register_variable(lbuf, cd, strlen(cd), NULL, 0 TSRMLS_CC);
1278            }
1279
1280            /* Add $foo[type] */
1281            if (is_arr_upload) {
1282                snprintf(lbuf, llen, "%s[type][%s]", abuf, array_index);
1283            } else {
1284                snprintf(lbuf, llen, "%s[type]", param);
1285            }
1286            register_http_post_files_variable(lbuf, cd, http_post_files, 0 TSRMLS_CC);
1287
1288            /* Restore Content-Type Header */
1289            if (s != NULL) {
1290                *s = ';';
1291            }
1292            s = "";
1293
1294            {
1295                /* store temp_filename as-is (without magic_quotes_gpc-ing it, in case upload_tmp_dir
1296                 * contains escapeable characters. escape only the variable name.) */
1297                zval zfilename;
1298
1299                /* Initialize variables */
1300                add_protected_variable(param TSRMLS_CC);
1301
1302                /* if param is of form xxx[.*] this will cut it to xxx */
1303                if (!is_anonymous) {
1304                    ZVAL_STRING(&zfilename, temp_filename, 1);
1305                    safe_php_register_variable_ex(param, &zfilename, NULL, 1 TSRMLS_CC);
1306                }
1307
1308                /* Add $foo[tmp_name] */
1309                if (is_arr_upload) {
1310                    snprintf(lbuf, llen, "%s[tmp_name][%s]", abuf, array_index);
1311                } else {
1312                    snprintf(lbuf, llen, "%s[tmp_name]", param);
1313                }
1314                add_protected_variable(lbuf TSRMLS_CC);
1315                ZVAL_STRING(&zfilename, temp_filename, 1);
1316                register_http_post_files_variable_ex(lbuf, &zfilename, http_post_files, 1 TSRMLS_CC);
1317            }
1318
1319            {
1320                zval file_size, error_type;
1321
1322                error_type.value.lval = cancel_upload;
1323                error_type.type = IS_LONG;
1324
1325                /* Add $foo[error] */
1326                if (cancel_upload) {
1327                    file_size.value.lval = 0;
1328                    file_size.type = IS_LONG;
1329                } else {
1330                    file_size.value.lval = total_bytes;
1331                    file_size.type = IS_LONG;
1332                }
1333
1334                if (is_arr_upload) {
1335                    snprintf(lbuf, llen, "%s[error][%s]", abuf, array_index);
1336                } else {
1337                    snprintf(lbuf, llen, "%s[error]", param);
1338                }
1339                register_http_post_files_variable_ex(lbuf, &error_type, http_post_files, 0 TSRMLS_CC);
1340
1341                /* Add $foo_size */
1342                if (is_arr_upload) {
1343                    snprintf(lbuf, llen, "%s_size[%s]", abuf, array_index);
1344                } else {
1345                    snprintf(lbuf, llen, "%s_size", param);
1346                }
1347                if (!is_anonymous) {
1348                    safe_php_register_variable_ex(lbuf, &file_size, NULL, 0 TSRMLS_CC);
1349                }
1350
1351                /* Add $foo[size] */
1352                if (is_arr_upload) {
1353                    snprintf(lbuf, llen, "%s[size][%s]", abuf, array_index);
1354                } else {
1355                    snprintf(lbuf, llen, "%s[size]", param);
1356                }
1357                register_http_post_files_variable_ex(lbuf, &file_size, http_post_files, 0 TSRMLS_CC);
1358            }
1359            efree(param);
1360        }
1361    }
1362
1363fileupload_done:
1364    if (php_rfc1867_callback != NULL) {
1365        multipart_event_end event_end;
1366
1367        event_end.post_bytes_processed = SG(read_post_bytes);
1368        php_rfc1867_callback(MULTIPART_EVENT_END, &event_end, &event_extra_data TSRMLS_CC);
1369    }
1370
1371    SAFE_RETURN;
1372}
1373/* }}} */
1374
1375/*
1376 * Local variables:
1377 * tab-width: 4
1378 * c-basic-offset: 4
1379 * End:
1380 * vim600: sw=4 ts=4 fdm=marker
1381 * vim<600: sw=4 ts=4
1382 */
1383