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