1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 6 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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 <sniper@php.net> |
17 : +----------------------------------------------------------------------+
18 : */
19 :
20 : /* $Id: rfc1867.c 290885 2009-11-17 20:33:51Z rasmus $ */
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 :
36 : #define DEBUG_FILE_UPLOAD ZEND_DEBUG
37 :
38 : PHPAPI int (*php_rfc1867_callback)(unsigned int event, void *event_data, void **extra TSRMLS_DC) = NULL;
39 :
40 : #define SAFE_RETURN { \
41 : if (lbuf) efree(lbuf); \
42 : if (abuf) efree(abuf); \
43 : if (array_index) efree(array_index); \
44 : zend_llist_destroy(&header); \
45 : if (mbuff->boundary_next) efree(mbuff->boundary_next); \
46 : if (mbuff->boundary) efree(mbuff->boundary); \
47 : if (mbuff->buffer) efree(mbuff->buffer); \
48 : if (mbuff) efree(mbuff); \
49 : return; }
50 :
51 : /* The longest property name we use in an uploaded file array */
52 : #define MAX_SIZE_OF_INDEX sizeof("[tmp_name]")
53 :
54 : /* The longest anonymous name */
55 : #define MAX_SIZE_ANONNAME 33
56 :
57 : /* Errors */
58 : #define UPLOAD_ERROR_OK 0 /* File upload succesful */
59 : #define UPLOAD_ERROR_A 1 /* Uploaded file exceeded upload_max_filesize */
60 : #define UPLOAD_ERROR_B 2 /* Uploaded file exceeded MAX_FILE_SIZE */
61 : #define UPLOAD_ERROR_C 3 /* Partially uploaded */
62 : #define UPLOAD_ERROR_D 4 /* No file uploaded */
63 : #define UPLOAD_ERROR_E 6 /* Missing /tmp or similar directory */
64 : #define UPLOAD_ERROR_F 7 /* Failed to write file to disk */
65 : #define UPLOAD_ERROR_X 8 /* File upload stopped by extension */
66 :
67 : void php_rfc1867_register_constants(TSRMLS_D)
68 17007 : {
69 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_OK", UPLOAD_ERROR_OK, CONST_CS | CONST_PERSISTENT);
70 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_INI_SIZE", UPLOAD_ERROR_A, CONST_CS | CONST_PERSISTENT);
71 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_FORM_SIZE", UPLOAD_ERROR_B, CONST_CS | CONST_PERSISTENT);
72 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_PARTIAL", UPLOAD_ERROR_C, CONST_CS | CONST_PERSISTENT);
73 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_FILE", UPLOAD_ERROR_D, CONST_CS | CONST_PERSISTENT);
74 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_TMP_DIR", UPLOAD_ERROR_E, CONST_CS | CONST_PERSISTENT);
75 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_CANT_WRITE", UPLOAD_ERROR_F, CONST_CS | CONST_PERSISTENT);
76 17007 : REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_EXTENSION", UPLOAD_ERROR_X, CONST_CS | CONST_PERSISTENT);
77 17007 : }
78 :
79 : static int unlink_filename(char **filename TSRMLS_DC)
80 36 : {
81 36 : VCWD_UNLINK(*filename);
82 36 : return 0;
83 : }
84 :
85 : void destroy_uploaded_files_hash(TSRMLS_D)
86 24 : {
87 24 : zend_hash_apply(SG(rfc1867_uploaded_files), (apply_func_t) unlink_filename TSRMLS_CC);
88 24 : zend_hash_destroy(SG(rfc1867_uploaded_files));
89 24 : FREE_HASHTABLE(SG(rfc1867_uploaded_files));
90 24 : }
91 :
92 : /*
93 : * Following code is based on apache_multipart_buffer.c from libapreq-0.33 package.
94 : *
95 : */
96 :
97 : #define FILLUNIT (1024 * 5)
98 :
99 : typedef struct {
100 :
101 : /* read buffer */
102 : char *buffer;
103 : char *buf_begin;
104 : int bufsize;
105 : int bytes_in_buffer;
106 :
107 : /* boundary info */
108 : char *boundary;
109 : char *boundary_next;
110 : int boundary_next_len;
111 :
112 : } multipart_buffer;
113 :
114 : typedef struct {
115 : char *key;
116 : char *value;
117 : } mime_header_entry;
118 :
119 : /*
120 : fill up the buffer with client data.
121 : returns number of bytes added to buffer.
122 : */
123 : static int fill_buffer(multipart_buffer *self TSRMLS_DC)
124 174 : {
125 174 : int bytes_to_read, total_read = 0, actual_read = 0;
126 : static zend_bool done = 0;
127 :
128 : /* shift the existing data if necessary */
129 174 : if (self->bytes_in_buffer > 0 && self->buf_begin != self->buffer) {
130 148 : memmove(self->buffer, self->buf_begin, self->bytes_in_buffer);
131 : }
132 :
133 174 : self->buf_begin = self->buffer;
134 :
135 : /* calculate the free space in the buffer */
136 174 : bytes_to_read = self->bufsize - self->bytes_in_buffer;
137 :
138 : /* read the required number of bytes */
139 372 : while (bytes_to_read > 0) {
140 :
141 198 : char *buf = self->buffer + self->bytes_in_buffer;
142 :
143 198 : actual_read = sapi_module.read_post(buf, bytes_to_read TSRMLS_CC);
144 :
145 : /* update the buffer length */
146 198 : if (actual_read > 0) {
147 24 : self->bytes_in_buffer += actual_read;
148 24 : SG(read_post_bytes) += actual_read;
149 24 : total_read += actual_read;
150 24 : bytes_to_read -= actual_read;
151 : } else {
152 174 : if (!done) {
153 : #ifdef DEBUG_FILE_UPLOAD_INTENSIVE
154 : fprintf(stderr, "\n###################\n%s\n#################\n", self->buffer);
155 : #endif
156 24 : done = 1;
157 : }
158 174 : break;
159 : }
160 : }
161 :
162 174 : return total_read;
163 : }
164 :
165 : /* eof if we are out of bytes, or if we hit the final boundary */
166 : static int multipart_buffer_eof(multipart_buffer *self TSRMLS_DC)
167 92 : {
168 92 : if ( (self->bytes_in_buffer == 0 && fill_buffer(self TSRMLS_CC) < 1) ) {
169 1 : return 1;
170 : } else {
171 91 : return 0;
172 : }
173 : }
174 :
175 : /* create new multipart_buffer structure */
176 : static multipart_buffer *multipart_buffer_new(char *boundary, int boundary_len)
177 24 : {
178 24 : multipart_buffer *self = (multipart_buffer *) ecalloc(1, sizeof(multipart_buffer));
179 :
180 24 : int minsize = boundary_len + 6;
181 24 : if (minsize < FILLUNIT) minsize = FILLUNIT;
182 :
183 24 : self->buffer = (char *) ecalloc(1, minsize + 1);
184 24 : self->bufsize = minsize;
185 :
186 24 : spprintf(&self->boundary, 0, "--%s", boundary);
187 :
188 24 : self->boundary_next_len = spprintf(&self->boundary_next, 0, "\n--%s", boundary);
189 :
190 24 : self->buf_begin = self->buffer;
191 24 : self->bytes_in_buffer = 0;
192 :
193 24 : return self;
194 : }
195 :
196 : /*
197 : gets the next CRLF terminated line from the input buffer.
198 : if it doesn't find a CRLF, and the buffer isn't completely full, returns
199 : NULL; otherwise, returns the beginning of the null-terminated line,
200 : minus the CRLF.
201 :
202 : note that we really just look for LF terminated lines. this works
203 : around a bug in internet explorer for the macintosh which sends mime
204 : boundaries that are only LF terminated when you use an image submit
205 : button in a multipart/form-data form.
206 : */
207 : static char *next_line(multipart_buffer *self)
208 340 : {
209 : /* look for LF in the data */
210 340 : char* line = self->buf_begin;
211 340 : char* ptr = memchr(self->buf_begin, '\n', self->bytes_in_buffer);
212 :
213 340 : if (ptr) { /* LF found */
214 :
215 : /* terminate the string, remove CRLF */
216 296 : if ((ptr - line) > 0 && *(ptr-1) == '\r') {
217 0 : *(ptr-1) = 0;
218 : } else {
219 296 : *ptr = 0;
220 : }
221 :
222 : /* bump the pointer */
223 296 : self->buf_begin = ptr + 1;
224 296 : self->bytes_in_buffer -= (self->buf_begin - line);
225 :
226 : } else { /* no LF found */
227 :
228 : /* buffer isn't completely full, fail */
229 44 : if (self->bytes_in_buffer < self->bufsize) {
230 44 : return NULL;
231 : }
232 : /* return entire buffer as a partial line */
233 0 : line[self->bufsize] = 0;
234 0 : self->buf_begin = ptr;
235 0 : self->bytes_in_buffer = 0;
236 : }
237 :
238 296 : return line;
239 : }
240 :
241 : /* returns the next CRLF terminated line from the client */
242 : static char *get_line(multipart_buffer *self TSRMLS_DC)
243 318 : {
244 318 : char* ptr = next_line(self);
245 :
246 318 : if (!ptr) {
247 22 : fill_buffer(self TSRMLS_CC);
248 22 : ptr = next_line(self);
249 : }
250 :
251 318 : return ptr;
252 : }
253 :
254 : /* Free header entry */
255 : static void php_free_hdr_entry(mime_header_entry *h)
256 89 : {
257 89 : if (h->key) {
258 89 : efree(h->key);
259 : }
260 89 : if (h->value) {
261 89 : efree(h->value);
262 : }
263 89 : }
264 :
265 : /* finds a boundary */
266 : static int find_boundary(multipart_buffer *self, char *boundary TSRMLS_DC)
267 91 : {
268 : char *line;
269 :
270 : /* loop thru lines */
271 251 : while( (line = get_line(self TSRMLS_CC)) )
272 : {
273 : /* finished if we found the boundary */
274 138 : if (!strcmp(line, boundary)) {
275 69 : return 1;
276 : }
277 : }
278 :
279 : /* didn't find the boundary */
280 22 : return 0;
281 : }
282 :
283 : /* parse headers */
284 : static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)
285 91 : {
286 : char *line;
287 : mime_header_entry prev_entry, entry;
288 : int prev_len, cur_len;
289 :
290 : /* didn't find boundary, abort */
291 91 : if (!find_boundary(self, self->boundary TSRMLS_CC)) {
292 22 : return 0;
293 : }
294 :
295 : /* get lines of text, or CRLF_CRLF */
296 :
297 227 : while( (line = get_line(self TSRMLS_CC)) && strlen(line) > 0 )
298 : {
299 : /* add header to table */
300 89 : char *key = line;
301 89 : char *value = NULL;
302 :
303 : /* space in the beginning means same header */
304 89 : if (!isspace(line[0])) {
305 89 : value = strchr(line, ':');
306 : }
307 :
308 89 : if (value) {
309 89 : *value = 0;
310 178 : do { value++; } while(isspace(*value));
311 :
312 89 : entry.value = estrdup(value);
313 89 : entry.key = estrdup(key);
314 :
315 0 : } else if (zend_llist_count(header)) { /* If no ':' on the line, add to previous line */
316 :
317 0 : prev_len = strlen(prev_entry.value);
318 0 : cur_len = strlen(line);
319 :
320 0 : entry.value = emalloc(prev_len + cur_len + 1);
321 0 : memcpy(entry.value, prev_entry.value, prev_len);
322 0 : memcpy(entry.value + prev_len, line, cur_len);
323 0 : entry.value[cur_len + prev_len] = '\0';
324 :
325 0 : entry.key = estrdup(prev_entry.key);
326 :
327 0 : zend_llist_remove_tail(header);
328 : } else {
329 0 : continue;
330 : }
331 :
332 89 : zend_llist_add_element(header, &entry);
333 89 : prev_entry = entry;
334 : }
335 :
336 69 : return 1;
337 : }
338 :
339 : static char *php_mime_get_hdr_value(zend_llist header, char *key)
340 105 : {
341 : mime_header_entry *entry;
342 :
343 105 : if (key == NULL) {
344 0 : return NULL;
345 : }
346 :
347 105 : entry = zend_llist_get_first(&header);
348 246 : while (entry) {
349 119 : if (!strcasecmp(entry->key, key)) {
350 83 : return entry->value;
351 : }
352 36 : entry = zend_llist_get_next(&header);
353 : }
354 :
355 22 : return NULL;
356 : }
357 :
358 : static char *php_ap_getword(char **line, char stop)
359 285 : {
360 285 : char *pos = *line, quote;
361 : char *res;
362 :
363 2607 : while (*pos && *pos != stop) {
364 :
365 2145 : if ((quote = *pos) == '"' || quote == '\'') {
366 108 : ++pos;
367 1154 : while (*pos && *pos != quote) {
368 938 : if (*pos == '\\' && pos[1] && pos[1] == quote) {
369 0 : pos += 2;
370 : } else {
371 938 : ++pos;
372 : }
373 : }
374 108 : if (*pos) {
375 108 : ++pos;
376 : }
377 1929 : } else ++pos;
378 :
379 : }
380 285 : if (*pos == '\0') {
381 69 : res = estrdup(*line);
382 69 : *line += strlen(*line);
383 69 : return res;
384 : }
385 :
386 216 : res = estrndup(*line, pos - *line);
387 :
388 648 : while (*pos == stop) {
389 216 : ++pos;
390 : }
391 :
392 216 : *line = pos;
393 216 : return res;
394 : }
395 :
396 : static char *substring_conf(char *start, int len, char quote TSRMLS_DC)
397 108 : {
398 108 : char *result = emalloc(len + 2);
399 108 : char *resp = result;
400 : int i;
401 :
402 1046 : for (i = 0; i < len; ++i) {
403 938 : if (start[i] == '\\' && (start[i + 1] == '\\' || (quote && start[i + 1] == quote))) {
404 0 : *resp++ = start[++i];
405 : } else {
406 938 : *resp++ = start[i];
407 : }
408 : }
409 :
410 108 : *resp = '\0';
411 108 : return result;
412 : }
413 :
414 : static char *php_ap_getword_conf(char **line TSRMLS_DC)
415 108 : {
416 108 : char *str = *line, *strend, *res, quote;
417 :
418 216 : while (*str && isspace(*str)) {
419 0 : ++str;
420 : }
421 :
422 108 : if (!*str) {
423 0 : *line = str;
424 0 : return estrdup("");
425 : }
426 :
427 216 : if ((quote = *str) == '"' || quote == '\'') {
428 108 : strend = str + 1;
429 108 : look_for_quote:
430 1154 : while (*strend && *strend != quote) {
431 938 : if (*strend == '\\' && strend[1] && strend[1] == quote) {
432 0 : strend += 2;
433 : } else {
434 938 : ++strend;
435 : }
436 : }
437 108 : if (*strend && *strend == quote) {
438 108 : char p = *(strend + 1);
439 108 : if (p != '\r' && p != '\n' && p != '\0') {
440 0 : strend++;
441 0 : goto look_for_quote;
442 : }
443 : }
444 :
445 108 : res = substring_conf(str + 1, strend - str - 1, quote TSRMLS_CC);
446 :
447 108 : if (*strend == quote) {
448 108 : ++strend;
449 : }
450 :
451 : } else {
452 :
453 0 : strend = str;
454 0 : while (*strend && !isspace(*strend)) {
455 0 : ++strend;
456 : }
457 0 : res = substring_conf(str, strend - str, 0 TSRMLS_CC);
458 : }
459 :
460 216 : while (*strend && isspace(*strend)) {
461 0 : ++strend;
462 : }
463 :
464 108 : *line = strend;
465 108 : return res;
466 : }
467 :
468 : /*
469 : search for a string in a fixed-length byte string.
470 : if partial is true, partial matches are allowed at the end of the buffer.
471 : returns NULL if not found, or a pointer to the start of the first match.
472 : */
473 : static void *php_ap_memstr(char *haystack, int haystacklen, char *needle, int needlen, int partial)
474 201 : {
475 201 : int len = haystacklen;
476 201 : char *ptr = haystack;
477 :
478 : /* iterate through first character matches */
479 402 : while( (ptr = memchr(ptr, needle[0], len)) ) {
480 :
481 : /* calculate length after match */
482 199 : len = haystacklen - (ptr - (char *)haystack);
483 :
484 : /* done if matches up to capacity of buffer */
485 199 : if (memcmp(needle, ptr, needlen < len ? needlen : len) == 0 && (partial || len >= needlen)) {
486 : break;
487 : }
488 :
489 : /* next character */
490 0 : ptr++; len--;
491 : }
492 :
493 201 : return ptr;
494 : }
495 :
496 : /* read until a boundary condition */
497 : static int multipart_buffer_read(multipart_buffer *self, char *buf, int bytes, int *end TSRMLS_DC)
498 127 : {
499 : int len, max;
500 : char *bound;
501 :
502 : /* fill buffer if needed */
503 127 : if (bytes > self->bytes_in_buffer) {
504 127 : fill_buffer(self TSRMLS_CC);
505 : }
506 :
507 : /* look for a potential boundary match, only read data up to that point */
508 127 : if ((bound = php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 1))) {
509 125 : max = bound - self->buf_begin;
510 125 : if (end && php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 0)) {
511 74 : *end = 1;
512 : }
513 : } else {
514 2 : max = self->bytes_in_buffer;
515 : }
516 :
517 : /* maximum number of bytes we are reading */
518 127 : len = max < bytes-1 ? max : bytes-1;
519 :
520 : /* if we read any data... */
521 127 : if (len > 0) {
522 :
523 : /* copy the data */
524 64 : memcpy(buf, self->buf_begin, len);
525 64 : buf[len] = 0;
526 :
527 64 : if (bound && len > 0 && buf[len-1] == '\r') {
528 0 : buf[--len] = 0;
529 : }
530 :
531 : /* update the buffer */
532 64 : self->bytes_in_buffer -= len;
533 64 : self->buf_begin += len;
534 : }
535 :
536 127 : return len;
537 : }
538 :
539 : /*
540 : XXX: this is horrible memory-usage-wise, but we only expect
541 : to do this on small pieces of form data.
542 : */
543 : static char *multipart_buffer_read_body(multipart_buffer *self, unsigned int *len TSRMLS_DC)
544 26 : {
545 26 : char buf[FILLUNIT], *out=NULL;
546 26 : int total_bytes=0, read_bytes=0;
547 :
548 77 : while((read_bytes = multipart_buffer_read(self, buf, sizeof(buf), NULL TSRMLS_CC))) {
549 25 : out = erealloc(out, total_bytes + read_bytes + 1);
550 25 : memcpy(out + total_bytes, buf, read_bytes);
551 25 : total_bytes += read_bytes;
552 : }
553 :
554 26 : if (out) {
555 25 : out[total_bytes] = '\0';
556 : }
557 26 : *len = total_bytes;
558 :
559 26 : return out;
560 : }
561 :
562 : static void register_raw_var_ex(char *var, zval *value, HashTable *array)
563 226 : {
564 226 : zend_hash_update(array, var, strlen(var) + 1, &value, sizeof(zval *), NULL);
565 226 : }
566 :
567 : static void register_raw_var(char *var, char *str, int str_len, HashTable *array)
568 146 : {
569 : zval *new_entry;
570 : assert(str != NULL);
571 :
572 : /* Prepare value */
573 146 : MAKE_STD_ZVAL(new_entry);
574 146 : ZVAL_STRINGL(new_entry, str, str_len, 1);
575 :
576 146 : register_raw_var_ex(var, new_entry, array);
577 146 : }
578 :
579 : /*
580 : * The combined READER/HANDLER
581 : *
582 : */
583 :
584 : SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler)
585 30 : {
586 30 : char *boundary, *s=NULL, *boundary_end = NULL, *start_arr=NULL, *array_index=NULL;
587 30 : char *temp_filename=NULL, *lbuf=NULL, *abuf=NULL;
588 30 : int boundary_len=0, total_bytes=0, cancel_upload=0, is_arr_upload=0, array_len=0;
589 30 : int max_file_size=0, skip_upload=0, anonindex=0, is_anonymous;
590 30 : HashTable *uploaded_files=NULL;
591 : HashTable *post_vars, *files_vars;
592 : multipart_buffer *mbuff;
593 30 : int fd=-1;
594 : zend_llist header;
595 30 : void *event_extra_data = NULL;
596 30 : int llen = 0;
597 30 : int upload_cnt = INI_INT("max_file_uploads");
598 :
599 30 : if (SG(post_max_size) > 0 && SG(request_info).content_length > SG(post_max_size)) {
600 2 : 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));
601 2 : return;
602 : }
603 :
604 : /* Get the boundary */
605 28 : boundary = strstr(content_type_dup, "boundary");
606 28 : if (!boundary || !(boundary=strchr(boundary, '='))) {
607 2 : sapi_module.sapi_error(E_WARNING, "Missing boundary in multipart/form-data POST data");
608 2 : return;
609 : }
610 :
611 26 : boundary++;
612 26 : boundary_len = strlen(boundary);
613 :
614 26 : if (boundary[0] == '"') {
615 3 : boundary++;
616 3 : boundary_end = strchr(boundary, '"');
617 3 : if (!boundary_end) {
618 2 : sapi_module.sapi_error(E_WARNING, "Invalid boundary in multipart/form-data POST data");
619 2 : return;
620 : }
621 : } else {
622 : /* search for the end of the boundary */
623 23 : boundary_end = strchr(boundary, ',');
624 : }
625 24 : if (boundary_end) {
626 2 : boundary_end[0] = '\0';
627 2 : boundary_len = boundary_end-boundary;
628 : }
629 :
630 : /* Initialize the buffer */
631 24 : if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) {
632 0 : sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer");
633 0 : return;
634 : }
635 :
636 24 : ALLOC_HASHTABLE(uploaded_files);
637 24 : zend_hash_init(uploaded_files, 5, NULL, (dtor_func_t) free_estring, 0);
638 24 : SG(rfc1867_uploaded_files) = uploaded_files;
639 :
640 24 : ALLOC_HASHTABLE(post_vars);
641 24 : zend_hash_init(post_vars, 5, NULL, ZVAL_PTR_DTOR, 0);
642 24 : SG(rfc1867_vars) = post_vars;
643 :
644 24 : ALLOC_HASHTABLE(files_vars);
645 24 : zend_hash_init(files_vars, 5, NULL, ZVAL_PTR_DTOR, 0);
646 24 : SG(rfc1867_files_vars) = files_vars;
647 :
648 24 : zend_llist_init(&header, sizeof(mime_header_entry), (llist_dtor_func_t) php_free_hdr_entry, 0);
649 :
650 24 : if (php_rfc1867_callback != NULL) {
651 : multipart_event_start event_start;
652 :
653 24 : event_start.content_length = SG(request_info).content_length;
654 24 : if (php_rfc1867_callback(MULTIPART_EVENT_START, &event_start, &event_extra_data TSRMLS_CC) == FAILURE) {
655 0 : goto fileupload_done;
656 : }
657 : }
658 :
659 116 : while (!multipart_buffer_eof(mbuff TSRMLS_CC))
660 : {
661 : char buff[FILLUNIT];
662 91 : char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL;
663 91 : size_t blen = 0, wlen = 0;
664 : off_t offset;
665 :
666 91 : zend_llist_clean(&header);
667 :
668 91 : if (!multipart_buffer_headers(mbuff, &header TSRMLS_CC)) {
669 22 : goto fileupload_done;
670 : }
671 :
672 69 : if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) {
673 69 : char *pair = NULL;
674 69 : int end = 0;
675 :
676 138 : while (isspace(*cd)) {
677 0 : ++cd;
678 : }
679 :
680 315 : while (*cd && (pair = php_ap_getword(&cd, ';')))
681 : {
682 177 : char *key = NULL, *word = pair;
683 :
684 462 : while (isspace(*cd)) {
685 108 : ++cd;
686 : }
687 :
688 177 : if (strchr(pair, '=')) {
689 108 : key = php_ap_getword(&pair, '=');
690 :
691 108 : if (!strcasecmp(key, "name")) {
692 66 : if (param) {
693 0 : efree(param);
694 : }
695 66 : param = php_ap_getword_conf(&pair TSRMLS_CC);
696 42 : } else if (!strcasecmp(key, "filename")) {
697 42 : if (filename) {
698 0 : efree(filename);
699 : }
700 42 : filename = php_ap_getword_conf(&pair TSRMLS_CC);
701 : }
702 : }
703 177 : if (key) {
704 108 : efree(key);
705 : }
706 177 : efree(word);
707 : }
708 :
709 : /* Normal form variable, safe to read all data into memory */
710 69 : if (!filename && param) {
711 : unsigned int value_len;
712 26 : char *value = multipart_buffer_read_body(mbuff, &value_len TSRMLS_CC);
713 :
714 26 : if (!value) {
715 1 : value = estrdup("");
716 1 : value_len = 0;
717 : }
718 :
719 26 : register_raw_var(param, value, value_len, post_vars);
720 :
721 26 : if (php_rfc1867_callback != NULL) {
722 : multipart_event_formdata event_formdata;
723 :
724 26 : event_formdata.post_bytes_processed = SG(read_post_bytes);
725 26 : event_formdata.name = ZSTR(param);
726 26 : event_formdata.value = PZSTR(value);
727 26 : event_formdata.length = value_len;
728 26 : event_formdata.newlength = NULL;
729 26 : php_rfc1867_callback(MULTIPART_EVENT_FORMDATA, &event_formdata, &event_extra_data TSRMLS_CC);
730 : }
731 :
732 26 : if (!strcasecmp(param, "MAX_FILE_SIZE")) {
733 1 : max_file_size = atol(value);
734 : }
735 :
736 26 : efree(param);
737 26 : efree(value);
738 26 : continue;
739 : }
740 :
741 : /* If file_uploads=off, skip the file part */
742 43 : if (!PG(file_uploads)) {
743 1 : skip_upload = 1;
744 42 : } else if (upload_cnt <= 0) {
745 0 : skip_upload = 1;
746 0 : sapi_module.sapi_error(E_WARNING, "Maximum number of allowable file uploads has been exceeded");
747 : }
748 :
749 : /* Return with an error if the posted data is garbled */
750 43 : if (!param && !filename) {
751 1 : sapi_module.sapi_error(E_WARNING, "File Upload Mime headers garbled");
752 1 : goto fileupload_done;
753 : }
754 :
755 42 : if (!param) {
756 2 : is_anonymous = 1;
757 2 : param = emalloc(MAX_SIZE_ANONNAME);
758 2 : snprintf(param, MAX_SIZE_ANONNAME, "%u", anonindex++);
759 : } else {
760 40 : is_anonymous = 0;
761 : }
762 :
763 : /* New Rule: never repair potential malicious user input */
764 42 : if (!skip_upload) {
765 41 : char *tmp = param;
766 41 : long c = 0;
767 :
768 281 : while (*tmp) {
769 200 : if (*tmp == '[') {
770 4 : c++;
771 196 : } else if (*tmp == ']') {
772 4 : c--;
773 4 : if (tmp[1] && tmp[1] != '[') {
774 1 : skip_upload = 1;
775 1 : break;
776 : }
777 : }
778 199 : if (c < 0) {
779 0 : skip_upload = 1;
780 0 : break;
781 : }
782 199 : tmp++;
783 : }
784 : }
785 :
786 42 : total_bytes = cancel_upload = 0;
787 :
788 42 : if (!skip_upload) {
789 : /* Handle file */
790 40 : fd = php_open_temporary_fd(PG(upload_tmp_dir), "php", &temp_filename TSRMLS_CC);
791 40 : upload_cnt--;
792 40 : if (fd==-1) {
793 0 : sapi_module.sapi_error(E_WARNING, "File upload error - unable to create a temporary file");
794 0 : cancel_upload = UPLOAD_ERROR_E;
795 : }
796 : }
797 :
798 42 : if (!skip_upload && php_rfc1867_callback != NULL) {
799 : multipart_event_file_start event_file_start;
800 :
801 40 : event_file_start.post_bytes_processed = SG(read_post_bytes);
802 40 : event_file_start.name = ZSTR(param);
803 40 : event_file_start.filename = PZSTR(filename);
804 40 : if (php_rfc1867_callback(MULTIPART_EVENT_FILE_START, &event_file_start, &event_extra_data TSRMLS_CC) == FAILURE) {
805 0 : if (temp_filename) {
806 0 : if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
807 0 : close(fd);
808 0 : unlink(temp_filename);
809 : }
810 0 : efree(temp_filename);
811 : }
812 0 : temp_filename="";
813 0 : efree(param);
814 0 : efree(filename);
815 0 : continue;
816 : }
817 : }
818 :
819 42 : if (skip_upload) {
820 2 : efree(param);
821 2 : efree(filename);
822 2 : continue;
823 : }
824 :
825 40 : if (strlen(filename) == 0) {
826 : #if DEBUG_FILE_UPLOAD
827 : sapi_module.sapi_error(E_NOTICE, "No file uploaded");
828 : #endif
829 1 : cancel_upload = UPLOAD_ERROR_D;
830 : }
831 :
832 40 : offset = 0;
833 40 : end = 0;
834 119 : while (!cancel_upload && (blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end TSRMLS_CC)))
835 : {
836 39 : if (php_rfc1867_callback != NULL) {
837 : multipart_event_file_data event_file_data;
838 :
839 39 : event_file_data.post_bytes_processed = SG(read_post_bytes);
840 39 : event_file_data.offset = offset;
841 39 : event_file_data.data = buff;
842 39 : event_file_data.length = blen;
843 39 : event_file_data.newlength = &blen;
844 39 : if (php_rfc1867_callback(MULTIPART_EVENT_FILE_DATA, &event_file_data, &event_extra_data TSRMLS_CC) == FAILURE) {
845 0 : cancel_upload = UPLOAD_ERROR_X;
846 0 : continue;
847 : }
848 : }
849 :
850 40 : if (PG(upload_max_filesize) > 0 && (int)(total_bytes+blen) > PG(upload_max_filesize)) {
851 : #if DEBUG_FILE_UPLOAD
852 : sapi_module.sapi_error(E_NOTICE, "upload_max_filesize of %ld bytes exceeded - file [%s=%s] not saved", PG(upload_max_filesize), param, filename);
853 : #endif
854 1 : cancel_upload = UPLOAD_ERROR_A;
855 39 : } else if (max_file_size && ((int)(total_bytes+blen) > max_file_size)) {
856 : #if DEBUG_FILE_UPLOAD
857 : sapi_module.sapi_error(E_NOTICE, "MAX_FILE_SIZE of %ld bytes exceeded - file [%s=%s] not saved", max_file_size, param, filename);
858 : #endif
859 1 : cancel_upload = UPLOAD_ERROR_B;
860 37 : } else if (blen > 0) {
861 37 : wlen = write(fd, buff, blen);
862 :
863 37 : if (wlen == -1) {
864 : /* write failed */
865 : #if DEBUG_FILE_UPLOAD
866 : sapi_module.sapi_error(E_NOTICE, "write() failed - %s", strerror(errno));
867 : #endif
868 0 : cancel_upload = UPLOAD_ERROR_F;
869 37 : } else if (wlen < blen) {
870 : #if DEBUG_FILE_UPLOAD
871 : sapi_module.sapi_error(E_NOTICE, "Only %d bytes were written, expected to write %d", wlen, blen);
872 : #endif
873 0 : cancel_upload = UPLOAD_ERROR_F;
874 : } else {
875 37 : total_bytes += wlen;
876 : }
877 37 : offset += wlen;
878 : }
879 : }
880 40 : if (fd!=-1) { /* may not be initialized if file could not be created */
881 40 : close(fd);
882 : }
883 40 : if (!cancel_upload && !end) {
884 : #if DEBUG_FILE_UPLOAD
885 : sapi_module.sapi_error(E_NOTICE, "Missing mime boundary at the end of the data for file %s", strlen(filename) > 0 ? filename : "");
886 : #endif
887 1 : cancel_upload = UPLOAD_ERROR_C;
888 : }
889 : #if DEBUG_FILE_UPLOAD
890 : if (strlen(filename) > 0 && total_bytes == 0 && !cancel_upload) {
891 : sapi_module.sapi_error(E_WARNING, "Uploaded file size 0 - file [%s=%s] not saved", param, filename);
892 : cancel_upload = 5;
893 : }
894 : #endif
895 40 : if (php_rfc1867_callback != NULL) {
896 : multipart_event_file_end event_file_end;
897 :
898 40 : event_file_end.post_bytes_processed = SG(read_post_bytes);
899 40 : event_file_end.temp_filename = ZSTR(temp_filename);
900 40 : event_file_end.cancel_upload = cancel_upload;
901 40 : if (php_rfc1867_callback(MULTIPART_EVENT_FILE_END, &event_file_end, &event_extra_data TSRMLS_CC) == FAILURE) {
902 0 : cancel_upload = UPLOAD_ERROR_X;
903 : }
904 : }
905 :
906 40 : if (cancel_upload) {
907 4 : if (temp_filename) {
908 4 : if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */
909 4 : unlink(temp_filename);
910 : }
911 4 : efree(temp_filename);
912 : }
913 4 : temp_filename="";
914 : } else {
915 36 : zend_hash_add(SG(rfc1867_uploaded_files), temp_filename, strlen(temp_filename) + 1, &temp_filename, sizeof(char *), NULL);
916 : }
917 :
918 : /* is_arr_upload is true when name of file upload field
919 : * ends in [.*]
920 : * start_arr is set to point to 1st [
921 : */
922 40 : is_arr_upload = (start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ']');
923 :
924 40 : if (is_arr_upload) {
925 3 : array_len = strlen(start_arr);
926 3 : if (array_index) {
927 2 : efree(array_index);
928 : }
929 3 : array_index = estrndup(start_arr + 1, array_len - 2);
930 : }
931 :
932 : /* Add $foo_name */
933 40 : if (lbuf) {
934 21 : efree(lbuf);
935 : }
936 40 : llen = strlen(param) + MAX_SIZE_OF_INDEX + 1;
937 40 : lbuf = (char *) emalloc(llen);
938 :
939 : /* The \ check should technically be needed for win32 systems only where
940 : * it is a valid path separator. However, IE in all it's wisdom always sends
941 : * the full path of the file on the user's filesystem, which means that unless
942 : * the user does basename() they get a bogus file name. Until IE's user base drops
943 : * to nill or problem is fixed this code must remain enabled for all systems.
944 : */
945 40 : s = strrchr(filename, '\\');
946 40 : if ((tmp = strrchr(filename, '/')) > s) {
947 1 : s = tmp;
948 : }
949 :
950 : /* Add $foo[name] */
951 40 : if (is_arr_upload) {
952 3 : snprintf(lbuf, llen, "%s[name][%s]", abuf, array_index);
953 : } else {
954 37 : snprintf(lbuf, llen, "%s[name]", param);
955 : }
956 41 : if (s && s > filename) {
957 1 : register_raw_var(lbuf, s+1, strlen(s+1), files_vars);
958 : } else {
959 39 : register_raw_var(lbuf, filename, strlen(filename), files_vars);
960 : }
961 40 : efree(filename);
962 40 : s = NULL;
963 :
964 : /* Possible Content-Type: */
965 66 : if (cancel_upload || !(cd = php_mime_get_hdr_value(header, "Content-Type"))) {
966 26 : cd = "";
967 : } else {
968 : /* fix for Opera 6.01 */
969 14 : s = strchr(cd, ';');
970 14 : if (s != NULL) {
971 1 : *s = '\0';
972 : }
973 : }
974 :
975 : /* Add $foo[type] */
976 40 : if (is_arr_upload) {
977 3 : snprintf(lbuf, llen, "%s[type][%s]", abuf, array_index);
978 : } else {
979 37 : snprintf(lbuf, llen, "%s[type]", param);
980 : }
981 40 : register_raw_var(lbuf, cd, strlen(cd), files_vars);
982 :
983 : /* Restore Content-Type Header */
984 40 : if (s != NULL) {
985 1 : *s = ';';
986 : }
987 40 : s = "";
988 :
989 : /* Add $foo[tmp_name] */
990 40 : if (is_arr_upload) {
991 3 : snprintf(lbuf, llen, "%s[tmp_name][%s]", abuf, array_index);
992 : } else {
993 37 : snprintf(lbuf, llen, "%s[tmp_name]", param);
994 : }
995 40 : register_raw_var(lbuf, temp_filename, strlen(temp_filename), files_vars);
996 :
997 : {
998 : zval *file_size, *error_type;
999 :
1000 40 : MAKE_STD_ZVAL(error_type);
1001 40 : ZVAL_LONG(error_type, cancel_upload);
1002 :
1003 40 : MAKE_STD_ZVAL(file_size);
1004 :
1005 : /* Add $foo[error] */
1006 40 : if (cancel_upload) {
1007 4 : ZVAL_LONG(file_size, 0);
1008 : } else {
1009 36 : ZVAL_LONG(file_size, total_bytes);
1010 : }
1011 :
1012 40 : if (is_arr_upload) {
1013 3 : snprintf(lbuf, llen, "%s[error][%s]", abuf, array_index);
1014 : } else {
1015 37 : snprintf(lbuf, llen, "%s[error]", param);
1016 : }
1017 40 : register_raw_var_ex(lbuf, error_type, files_vars);
1018 :
1019 : /* Add $foo[size] */
1020 40 : if (is_arr_upload) {
1021 3 : snprintf(lbuf, llen, "%s[size][%s]", abuf, array_index);
1022 : } else {
1023 37 : snprintf(lbuf, llen, "%s[size]", param);
1024 : }
1025 40 : register_raw_var_ex(lbuf, file_size, files_vars);
1026 : }
1027 40 : efree(param);
1028 : }
1029 : }
1030 :
1031 24 : fileupload_done:
1032 24 : if (php_rfc1867_callback != NULL) {
1033 : multipart_event_end event_end;
1034 :
1035 24 : event_end.post_bytes_processed = SG(read_post_bytes);
1036 24 : php_rfc1867_callback(MULTIPART_EVENT_END, &event_end, &event_extra_data TSRMLS_CC);
1037 : }
1038 :
1039 24 : SAFE_RETURN;
1040 : }
1041 :
1042 : /*
1043 : * Local variables:
1044 : * tab-width: 4
1045 : * c-basic-offset: 4
1046 : * End:
1047 : * vim600: sw=4 ts=4 fdm=marker
1048 : * vim<600: sw=4 ts=4
1049 : */
|