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@lerdorf.on.ca>                       |
16   |          Stefan R�hrich <sr@linux.de>                                |
17   |          Zeev Suraski <zeev@zend.com>                                |
18   |          Jade Nicoletti <nicoletti@nns.ch>                           |
19   +----------------------------------------------------------------------+
20 */
21
22/* $Id$ */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include "php.h"
29#include "SAPI.h"
30#include "php_ini.h"
31
32#include <stdlib.h>
33#include <errno.h>
34#include <sys/types.h>
35#include <sys/stat.h>
36#include <fcntl.h>
37
38#ifdef PHP_WIN32
39# define O_RDONLY _O_RDONLY
40# include "win32/param.h"
41#else
42# include <sys/param.h>
43/* #include <sys/uio.h> */
44#endif
45
46#include "ext/standard/head.h"
47#include "safe_mode.h"
48#include "ext/standard/php_standard.h"
49#include "ext/standard/info.h"
50#include "php_zlib.h"
51#include "fopen_wrappers.h"
52
53#if HAVE_PWD_H
54# ifdef PHP_WIN32
55#  include "win32/pwd.h"
56# else
57#  include <pwd.h>
58# endif
59#endif
60
61#if defined(HAVE_UNISTD_H) && defined(PHP_WIN32)
62# undef HAVE_UNISTD_H
63#endif
64
65#ifdef COMPILE_DL_ZLIB
66# ifndef PUTS
67#  define PUTS(a) php_printf("%s",a)
68# endif
69# ifndef PUTC
70#  define PUTC(a) PUTS(a)
71# endif
72# ifndef PHPWRITE
73#  define PHPWRITE(a,n) php_write((a),(n) TSRMLS_CC)
74# endif
75#endif
76
77/* Win32 needs some more memory */
78#ifdef PHP_WIN32
79# define PHP_ZLIB_MODIFIER 100
80#else
81# define PHP_ZLIB_MODIFIER 1000
82#endif
83
84#define OS_CODE         0x03 /* FIXME */
85#define GZIP_HEADER_LENGTH      10
86#define GZIP_FOOTER_LENGTH      8
87
88/* True globals, no need for thread safety */
89static const int gz_magic[2] = {0x1f, 0x8b};    /* gzip magic header */
90
91static int php_zlib_output_compression_start(TSRMLS_D);
92
93static PHP_MINIT_FUNCTION(zlib);
94static PHP_MSHUTDOWN_FUNCTION(zlib);
95static PHP_RINIT_FUNCTION(zlib);
96static PHP_MINFO_FUNCTION(zlib);
97static PHP_FUNCTION(gzopen);
98static PHP_FUNCTION(readgzfile);
99static PHP_FUNCTION(gzfile);
100static PHP_FUNCTION(gzcompress);
101static PHP_FUNCTION(gzuncompress);
102static PHP_FUNCTION(gzdeflate);
103static PHP_FUNCTION(gzinflate);
104static PHP_FUNCTION(gzencode);
105static PHP_FUNCTION(ob_gzhandler);
106static PHP_FUNCTION(zlib_get_coding_type);
107
108/* {{{ arginfo */
109ZEND_BEGIN_ARG_INFO_EX(arginfo_gzfile, 0, 0, 1)
110    ZEND_ARG_INFO(0, filename)
111    ZEND_ARG_INFO(0, use_include_path)
112ZEND_END_ARG_INFO()
113
114ZEND_BEGIN_ARG_INFO_EX(arginfo_gzopen, 0, 0, 2)
115    ZEND_ARG_INFO(0, filename)
116    ZEND_ARG_INFO(0, mode)
117    ZEND_ARG_INFO(0, use_include_path)
118ZEND_END_ARG_INFO()
119
120ZEND_BEGIN_ARG_INFO_EX(arginfo_readgzfile, 0, 0, 1)
121    ZEND_ARG_INFO(0, filename)
122    ZEND_ARG_INFO(0, use_include_path)
123ZEND_END_ARG_INFO()
124
125ZEND_BEGIN_ARG_INFO_EX(arginfo_gzcompress, 0, 0, 1)
126    ZEND_ARG_INFO(0, data)
127    ZEND_ARG_INFO(0, level)
128ZEND_END_ARG_INFO()
129
130ZEND_BEGIN_ARG_INFO_EX(arginfo_gzuncompress, 0, 0, 1)
131    ZEND_ARG_INFO(0, data)
132    ZEND_ARG_INFO(0, length)
133ZEND_END_ARG_INFO()
134
135ZEND_BEGIN_ARG_INFO_EX(arginfo_gzdeflate, 0, 0, 1)
136    ZEND_ARG_INFO(0, data)
137    ZEND_ARG_INFO(0, level)
138ZEND_END_ARG_INFO()
139
140ZEND_BEGIN_ARG_INFO_EX(arginfo_gzinflate, 0, 0, 1)
141    ZEND_ARG_INFO(0, data)
142    ZEND_ARG_INFO(0, length)
143ZEND_END_ARG_INFO()
144
145ZEND_BEGIN_ARG_INFO(arginfo_zlib_get_coding_type, 0)
146ZEND_END_ARG_INFO()
147
148ZEND_BEGIN_ARG_INFO_EX(arginfo_gzencode, 0, 0, 1)
149    ZEND_ARG_INFO(0, data)
150    ZEND_ARG_INFO(0, level)
151    ZEND_ARG_INFO(0, encoding_mode)
152ZEND_END_ARG_INFO()
153
154ZEND_BEGIN_ARG_INFO_EX(arginfo_ob_gzhandler, 0, 0, 2)
155    ZEND_ARG_INFO(0, str)
156    ZEND_ARG_INFO(0, mode)
157ZEND_END_ARG_INFO()
158
159ZEND_BEGIN_ARG_INFO_EX(arginfo_gzputs, 0, 0, 2)
160    ZEND_ARG_INFO(0, fp)
161    ZEND_ARG_INFO(0, str)
162    ZEND_ARG_INFO(0, length)
163ZEND_END_ARG_INFO()
164
165ZEND_BEGIN_ARG_INFO(arginfo_gzpassthru, 0)
166    ZEND_ARG_INFO(0, fp)
167ZEND_END_ARG_INFO()
168
169ZEND_BEGIN_ARG_INFO_EX(arginfo_gzseek, 0, 0, 2)
170    ZEND_ARG_INFO(0, fp)
171    ZEND_ARG_INFO(0, offset)
172    ZEND_ARG_INFO(0, whence)
173ZEND_END_ARG_INFO()
174
175ZEND_BEGIN_ARG_INFO(arginfo_gzread, 0)
176    ZEND_ARG_INFO(0, fp)
177    ZEND_ARG_INFO(0, length)
178ZEND_END_ARG_INFO()
179
180ZEND_BEGIN_ARG_INFO_EX(arginfo_gzgetss, 0, 0, 1)
181    ZEND_ARG_INFO(0, fp)
182    ZEND_ARG_INFO(0, length)
183    ZEND_ARG_INFO(0, allowable_tags)
184ZEND_END_ARG_INFO()
185
186ZEND_BEGIN_ARG_INFO_EX(arginfo_gzgets, 0, 0, 1)
187    ZEND_ARG_INFO(0, fp)
188    ZEND_ARG_INFO(0, length)
189ZEND_END_ARG_INFO()
190/* }}} */
191
192/* {{{ php_zlib_functions[]
193 */
194static const zend_function_entry php_zlib_functions[] = {
195    PHP_FE(readgzfile,                      arginfo_readgzfile)
196    PHP_FALIAS(gzrewind,    rewind,         arginfo_gzpassthru)
197    PHP_FALIAS(gzclose,     fclose,         arginfo_gzpassthru)
198    PHP_FALIAS(gzeof,       feof,           arginfo_gzpassthru)
199    PHP_FALIAS(gzgetc,      fgetc,          arginfo_gzpassthru)
200    PHP_FALIAS(gzgets,      fgets,          arginfo_gzgets)
201    PHP_FALIAS(gzgetss,     fgetss,         arginfo_gzgetss)
202    PHP_FALIAS(gzread,      fread,          arginfo_gzread)
203    PHP_FE(gzopen,                          arginfo_gzopen)
204    PHP_FALIAS(gzpassthru,  fpassthru,      arginfo_gzpassthru)
205    PHP_FALIAS(gzseek,      fseek,          arginfo_gzseek)
206    PHP_FALIAS(gztell,      ftell,          arginfo_gzpassthru)
207    PHP_FALIAS(gzwrite,     fwrite,         arginfo_gzputs)
208    PHP_FALIAS(gzputs,      fwrite,         arginfo_gzputs)
209    PHP_FE(gzfile,                          arginfo_gzfile)
210    PHP_FE(gzcompress,                      arginfo_gzcompress)
211    PHP_FE(gzuncompress,                    arginfo_gzuncompress)
212    PHP_FE(gzdeflate,                       arginfo_gzdeflate)
213    PHP_FE(gzinflate,                       arginfo_gzinflate)
214    PHP_FE(gzencode,                        arginfo_gzencode)
215    PHP_FE(ob_gzhandler,                    arginfo_ob_gzhandler)
216    PHP_FE(zlib_get_coding_type,            arginfo_zlib_get_coding_type)
217    PHP_FE_END
218};
219/* }}} */
220
221ZEND_DECLARE_MODULE_GLOBALS(zlib)
222
223/* {{{ php_zlib_module_entry
224 */
225zend_module_entry php_zlib_module_entry = {
226    STANDARD_MODULE_HEADER,
227    "zlib",
228    php_zlib_functions,
229    PHP_MINIT(zlib),
230    PHP_MSHUTDOWN(zlib),
231    PHP_RINIT(zlib),
232    NULL,
233    PHP_MINFO(zlib),
234    "1.1",
235    PHP_MODULE_GLOBALS(zlib),
236    NULL,
237    NULL,
238    NULL,
239    STANDARD_MODULE_PROPERTIES_EX
240};
241/* }}} */
242
243#ifdef COMPILE_DL_ZLIB
244ZEND_GET_MODULE(php_zlib)
245#endif
246
247/* {{{ Memory management wrappers */
248
249static voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size)
250{
251    return (voidpf)safe_emalloc(items, size, 0);
252}
253
254static void php_zlib_free(voidpf opaque, voidpf address)
255{
256    efree((void*)address);
257}
258/* }}} */
259
260/* {{{ OnUpdate_zlib_output_compression */
261static PHP_INI_MH(OnUpdate_zlib_output_compression)
262{
263    int status, int_value;
264    char *ini_value;
265
266    if (new_value == NULL) {
267        return FAILURE;
268    }
269
270    if (!strncasecmp(new_value, "off", sizeof("off"))) {
271        new_value = "0";
272        new_value_length = sizeof("0");
273    } else if (!strncasecmp(new_value, "on", sizeof("on"))) {
274        new_value = "1";
275        new_value_length = sizeof("1");
276    }
277
278    int_value = zend_atoi(new_value, new_value_length);
279    ini_value = zend_ini_string("output_handler", sizeof("output_handler"), 0);
280
281    if (ini_value && *ini_value && int_value) {
282        php_error_docref("ref.outcontrol" TSRMLS_CC, E_CORE_ERROR, "Cannot use both zlib.output_compression and output_handler together!!");
283        return FAILURE;
284    }
285
286    if (stage == PHP_INI_STAGE_RUNTIME && SG(headers_sent) && !SG(request_info).no_headers) {
287        php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "Cannot change zlib.output_compression - headers already sent");
288        return FAILURE;
289    }
290
291    status = OnUpdateLong(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
292
293    if (stage == PHP_INI_STAGE_RUNTIME && int_value) {
294        status = php_zlib_output_compression_start(TSRMLS_C);
295    }
296
297    return status;
298}
299/* }}} */
300
301/* {{{ OnUpdate_zlib_output_compression_level */
302static PHP_INI_MH(OnUpdate_zlib_output_compression_level)
303{
304    OnUpdateLong(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
305
306    return SUCCESS;
307}
308/* }}} */
309
310/* {{{ OnUpdate_zlib_output_handler */
311static PHP_INI_MH(OnUpdate_zlib_output_handler)
312{
313    if (stage == PHP_INI_STAGE_RUNTIME && SG(headers_sent) && !SG(request_info).no_headers) {
314        php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "Cannot change zlib.output_handler - headers already sent");
315        return FAILURE;
316    }
317
318    OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
319
320    return SUCCESS;
321}
322/* }}} */
323
324
325PHP_INI_BEGIN()
326    STD_PHP_INI_BOOLEAN("zlib.output_compression",      "0", PHP_INI_ALL, OnUpdate_zlib_output_compression,       output_compression,       zend_zlib_globals, zlib_globals)
327    STD_PHP_INI_ENTRY("zlib.output_compression_level", "-1", PHP_INI_ALL, OnUpdate_zlib_output_compression_level, output_compression_level, zend_zlib_globals, zlib_globals)
328    STD_PHP_INI_ENTRY("zlib.output_handler",             "", PHP_INI_ALL, OnUpdate_zlib_output_handler,           output_handler,           zend_zlib_globals, zlib_globals)
329PHP_INI_END()
330
331/* {{{ PHP_MINIT_FUNCTION
332 */
333static PHP_MINIT_FUNCTION(zlib)
334{
335    php_register_url_stream_wrapper("compress.zlib", &php_stream_gzip_wrapper TSRMLS_CC);
336    php_stream_filter_register_factory("zlib.*", &php_zlib_filter_factory TSRMLS_CC);
337
338    REGISTER_LONG_CONSTANT("FORCE_GZIP", CODING_GZIP, CONST_CS | CONST_PERSISTENT);
339    REGISTER_LONG_CONSTANT("FORCE_DEFLATE", CODING_DEFLATE, CONST_CS | CONST_PERSISTENT);
340
341    REGISTER_INI_ENTRIES();
342
343    return SUCCESS;
344}
345/* }}} */
346
347/* {{{ PHP_RINIT_FUNCTION
348 */
349static PHP_RINIT_FUNCTION(zlib)
350{
351    ZLIBG(ob_gzhandler_status) = 0;
352    ZLIBG(compression_coding) = 0;
353
354    php_zlib_output_compression_start(TSRMLS_C);
355
356    return SUCCESS;
357}
358/* }}} */
359
360/* {{{ PHP_MSHUTDOWN_FUNCTION
361 */
362static PHP_MSHUTDOWN_FUNCTION(zlib)
363{
364    php_unregister_url_stream_wrapper("zlib" TSRMLS_CC);
365    php_stream_filter_unregister_factory("zlib.*" TSRMLS_CC);
366
367    UNREGISTER_INI_ENTRIES();
368
369    return SUCCESS;
370}
371/* }}} */
372
373/* {{{ PHP_MINFO_FUNCTION
374 */
375static PHP_MINFO_FUNCTION(zlib)
376{
377    php_info_print_table_start();
378    php_info_print_table_row(2, "ZLib Support", "enabled");
379    php_info_print_table_row(2, "Stream Wrapper support", "compress.zlib://");
380    php_info_print_table_row(2, "Stream Filter support", "zlib.inflate, zlib.deflate");
381    php_info_print_table_row(2, "Compiled Version", ZLIB_VERSION);
382    php_info_print_table_row(2, "Linked Version", (char *) zlibVersion());
383    php_info_print_table_end();
384
385    DISPLAY_INI_ENTRIES();
386}
387/* }}} */
388
389/* {{{ proto array gzfile(string filename [, int use_include_path])
390   Read und uncompress entire .gz-file into an array */
391static PHP_FUNCTION(gzfile)
392{
393    char *filename;
394    int filename_len;
395    long flags = 0;
396    char *slashed, buf[8192];
397    register int i = 0;
398    int use_include_path = 0;
399    php_stream *stream;
400
401    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &filename, &filename_len, &flags) == FAILURE) {
402        return;
403    }
404
405    use_include_path = flags ? USE_PATH : 0;
406
407    /* using a stream here is a bit more efficient (resource wise) than php_gzopen_wrapper */
408    stream = php_stream_gzopen(NULL, filename, "rb", use_include_path | ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL, NULL STREAMS_CC TSRMLS_CC);
409    if (stream == NULL) {
410        /* Error reporting is already done by stream code */
411        RETURN_FALSE;
412    }
413
414    /* Initialize return array */
415    array_init(return_value);
416
417    /* Now loop through the file and do the magic quotes thing if needed */
418    memset(buf,0,sizeof(buf));
419
420    while (php_stream_gets(stream, buf, sizeof(buf) - 1) != NULL) {
421        if (PG(magic_quotes_runtime)) {
422            int len;
423
424            slashed = php_addslashes(buf, 0, &len, 0 TSRMLS_CC); /* 0 = don't free source string */
425            add_index_stringl(return_value, i++, slashed, len, 0);
426        } else {
427            add_index_string(return_value, i++, buf, 1);
428        }
429    }
430    php_stream_close(stream);
431}
432/* }}} */
433
434/* {{{ proto resource gzopen(string filename, string mode [, int use_include_path])
435   Open a .gz-file and return a .gz-file pointer */
436static PHP_FUNCTION(gzopen)
437{
438    char *filename, *mode;
439    int filename_len, mode_len;
440    long flags = 0;
441    php_stream *stream;
442    int use_include_path = 0;
443
444    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &filename, &filename_len, &mode, &mode_len, &flags) == FAILURE) {
445        return;
446    }
447
448    use_include_path = flags ? USE_PATH : 0;
449
450    stream = php_stream_gzopen(NULL, filename, mode, use_include_path | ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL, NULL STREAMS_CC TSRMLS_CC);
451
452    if (!stream) {
453        RETURN_FALSE;
454    }
455    php_stream_to_zval(stream, return_value);
456}
457/* }}} */
458
459/*
460 * Read a file and write the ouput to stdout
461 */
462/* {{{ proto int readgzfile(string filename [, int use_include_path])
463   Output a .gz-file */
464static PHP_FUNCTION(readgzfile)
465{
466    char *filename;
467    int filename_len;
468    long flags = 0;
469    php_stream *stream;
470    int size;
471    int use_include_path = 0;
472
473    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &filename, &filename_len, &flags) == FAILURE) {
474        return;
475    }
476
477    use_include_path = flags ? USE_PATH : 0;
478
479    stream = php_stream_gzopen(NULL, filename, "rb", use_include_path | ENFORCE_SAFE_MODE, NULL, NULL STREAMS_CC TSRMLS_CC);
480    if (!stream) {
481        RETURN_FALSE;
482    }
483    size = php_stream_passthru(stream);
484    php_stream_close(stream);
485    RETURN_LONG(size);
486}
487/* }}} */
488
489/* {{{ proto string gzcompress(string data [, int level])
490   Gzip-compress a string */
491static PHP_FUNCTION(gzcompress)
492{
493    int data_len, status;
494    long level = Z_DEFAULT_COMPRESSION;
495    unsigned long l2;
496    char *data, *s2;
497
498    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &data, &data_len, &level) == FAILURE) {
499        return;
500    }
501
502    if ((level < -1) || (level > 9)) {
503        php_error_docref(NULL TSRMLS_CC, E_WARNING, "compression level (%ld) must be within -1..9", level);
504        RETURN_FALSE;
505    }
506
507    l2 = data_len + (data_len / PHP_ZLIB_MODIFIER) + 15 + 1; /* room for \0 */
508    s2 = (char *) emalloc(l2);
509    if (!s2) {
510        RETURN_FALSE;
511    }
512
513    if (level >= 0) {
514        status = compress2(s2, &l2, data, data_len, level);
515    } else {
516        status = compress(s2, &l2, data, data_len);
517    }
518
519    if (status == Z_OK) {
520        s2 = erealloc(s2, l2 + 1);
521        s2[l2] = '\0';
522        RETURN_STRINGL(s2, l2, 0);
523    } else {
524        efree(s2);
525        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
526        RETURN_FALSE;
527    }
528}
529/* }}} */
530
531/* {{{ proto string gzuncompress(string data [, int length])
532   Unzip a gzip-compressed string */
533static PHP_FUNCTION(gzuncompress)
534{
535    int data_len, status;
536    unsigned int factor=1, maxfactor=16;
537    long limit = 0;
538    unsigned long plength=0, length;
539    char *data, *s1=NULL, *s2=NULL;
540
541    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &data, &data_len, &limit) == FAILURE) {
542        return;
543    }
544
545    if (limit < 0) {
546        php_error_docref(NULL TSRMLS_CC, E_WARNING, "length (%ld) must be greater or equal zero", limit);
547        RETURN_FALSE;
548    }
549    plength = limit;
550
551    /*
552     zlib::uncompress() wants to know the output data length
553     if none was given as a parameter
554     we try from input length * 2 up to input length * 2^15
555     doubling it whenever it wasn't big enough
556     that should be eneugh for all real life cases
557    */
558    do {
559        length = plength ? plength : (unsigned long)data_len * (1 << factor++);
560        s2 = (char *) erealloc(s1, length);
561        status = uncompress(s2, &length, data, data_len);
562        s1 = s2;
563    } while ((status == Z_BUF_ERROR) && (!plength) && (factor < maxfactor));
564
565    if (status == Z_OK) {
566        s2 = erealloc(s2, length + 1); /* space for \0 */
567        s2[ length ] = '\0';
568        RETURN_STRINGL(s2, length, 0);
569    } else {
570        efree(s2);
571        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
572        RETURN_FALSE;
573    }
574}
575/* }}} */
576
577/* {{{ proto string gzdeflate(string data [, int level])
578   Gzip-compress a string */
579static PHP_FUNCTION(gzdeflate)
580{
581    int data_len,status;
582    long level = Z_DEFAULT_COMPRESSION;
583    z_stream stream;
584    char *data, *s2;
585
586    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &data, &data_len, &level) == FAILURE) {
587        return;
588    }
589
590    if ((level < -1) || (level > 9)) {
591        php_error_docref(NULL TSRMLS_CC, E_WARNING, "compression level (%ld) must be within -1..9", level);
592        RETURN_FALSE;
593    }
594
595    stream.data_type = Z_ASCII;
596    stream.zalloc = php_zlib_alloc;
597    stream.zfree  = php_zlib_free;
598    stream.opaque = (voidpf) Z_NULL;
599
600    stream.next_in = (Bytef *) data;
601    stream.avail_in = data_len;
602
603    stream.avail_out = stream.avail_in + (stream.avail_in / PHP_ZLIB_MODIFIER) + 15 + 1; /* room for \0 */
604
605    s2 = (char *) emalloc(stream.avail_out);
606    if (!s2) {
607        RETURN_FALSE;
608    }
609
610    stream.next_out = s2;
611
612    /* init with -MAX_WBITS disables the zlib internal headers */
613    status = deflateInit2(&stream, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, 0);
614    if (status == Z_OK) {
615        status = deflate(&stream, Z_FINISH);
616        if (status != Z_STREAM_END) {
617            deflateEnd(&stream);
618            if (status == Z_OK) {
619                status = Z_BUF_ERROR;
620            }
621        } else {
622            status = deflateEnd(&stream);
623        }
624    }
625
626    if (status == Z_OK) {
627        s2 = erealloc(s2,stream.total_out + 1); /* resize to buffer to the "right" size */
628        s2[ stream.total_out ] = '\0';
629        RETURN_STRINGL(s2, stream.total_out, 0);
630    } else {
631        efree(s2);
632        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
633        RETURN_FALSE;
634    }
635}
636/* }}} */
637
638/* {{{ proto string gzinflate(string data [, int length])
639   Unzip a gzip-compressed string */
640static PHP_FUNCTION(gzinflate)
641{
642    int data_len, status;
643    unsigned int factor=1, maxfactor=16;
644    long limit = 0;
645    unsigned long plength=0, length;
646    char *data, *s1=NULL, *s2=NULL;
647    z_stream stream;
648
649    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &data, &data_len, &limit) == FAILURE) {
650        return;
651    }
652
653    if (!data_len) {
654        RETURN_FALSE;
655    }
656
657    if (limit < 0) {
658        php_error_docref(NULL TSRMLS_CC, E_WARNING, "length (%ld) must be greater or equal zero", limit);
659        RETURN_FALSE;
660    }
661    plength = limit;
662
663    stream.zalloc = php_zlib_alloc;
664    stream.zfree = php_zlib_free;
665    stream.opaque = Z_NULL;
666    stream.avail_in = data_len + 1; /* there is room for \0 */
667    stream.next_in = (Bytef *) data;
668    stream.total_out = 0;
669
670    /* init with -MAX_WBITS disables the zlib internal headers */
671    status = inflateInit2(&stream, -MAX_WBITS);
672    if (status != Z_OK) {
673        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
674        RETURN_FALSE;
675    }
676
677    /*
678      stream.avail_out wants to know the output data length
679      if none was given as a parameter
680      we try from input length * 2 up to input length * 2^15
681      doubling it whenever it wasn't big enough
682      that should be enaugh for all real life cases
683    */
684    do {
685        length = plength ? plength : (unsigned long)data_len * (1 << factor++);
686        s2 = (char *) erealloc(s1, length);
687
688        if (!s2) {
689            if (s1) {
690                efree(s1);
691            }
692            inflateEnd(&stream);
693            RETURN_FALSE;
694        }
695        s1 = s2;
696
697        stream.next_out = (Bytef *) &s2[stream.total_out];
698        stream.avail_out = length - stream.total_out;
699        status = inflate(&stream, Z_NO_FLUSH);
700
701    } while ((Z_BUF_ERROR == status || (Z_OK == status && stream.avail_in)) && !plength && factor < maxfactor);
702
703    inflateEnd(&stream);
704
705    if ((plength && Z_OK == status) || factor >= maxfactor) {
706        status = Z_MEM_ERROR;
707    }
708
709    if (Z_STREAM_END == status || Z_OK == status) {
710        s2 = erealloc(s2, stream.total_out + 1); /* room for \0 */
711        s2[ stream.total_out ] = '\0';
712        RETURN_STRINGL(s2, stream.total_out, 0);
713    } else {
714        efree(s2);
715        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
716        RETURN_FALSE;
717    }
718}
719/* }}} */
720
721/* {{{ proto string zlib_get_coding_type(void)
722   Returns the coding type used for output compression */
723static PHP_FUNCTION(zlib_get_coding_type)
724{
725    switch (ZLIBG(compression_coding)) {
726        case CODING_GZIP:
727            RETURN_STRINGL("gzip", sizeof("gzip") - 1, 1);
728
729        case CODING_DEFLATE:
730            RETURN_STRINGL("deflate", sizeof("deflate") - 1, 1);
731    }
732
733    RETURN_FALSE;
734}
735
736/* {{{ php_do_deflate
737 */
738static int php_do_deflate(uint str_length, Bytef **p_buffer, uint *p_buffer_len, zend_bool do_start, zend_bool do_end TSRMLS_DC)
739{
740    Bytef *buffer;
741    uInt prev_outlen, outlen;
742    int err;
743    int start_offset = ((do_start && ZLIBG(compression_coding) == CODING_GZIP) ? 10 : 0);
744    int end_offset = (do_end ? 8 : 0);
745
746    outlen = (uint) (str_length + (str_length / PHP_ZLIB_MODIFIER) + 12 + 1); /* leave some room for a trailing \0 */
747    if ((outlen + start_offset + end_offset) > *p_buffer_len) {
748        buffer = (Bytef *) emalloc(outlen + start_offset + end_offset);
749    } else {
750        buffer = *p_buffer;
751    }
752
753    ZLIBG(stream).next_out = buffer + start_offset;
754    ZLIBG(stream).avail_out = outlen;
755
756    err = deflate(&ZLIBG(stream), Z_SYNC_FLUSH);
757    while (err == Z_OK && !ZLIBG(stream).avail_out) {
758        prev_outlen = outlen;
759        outlen *= 3;
760        if ((outlen + start_offset + end_offset) > *p_buffer_len) {
761            buffer = erealloc(buffer, outlen + start_offset + end_offset);
762        }
763
764        ZLIBG(stream).next_out = buffer + start_offset + prev_outlen;
765        ZLIBG(stream).avail_out = prev_outlen * 2;
766
767        err = deflate(&ZLIBG(stream), Z_SYNC_FLUSH);
768    }
769
770    if (do_end) {
771        err = deflate(&ZLIBG(stream), Z_FINISH);
772        buffer[outlen + start_offset - ZLIBG(stream).avail_out] = '\0';
773    }
774
775    *p_buffer = buffer;
776    *p_buffer_len = outlen - ZLIBG(stream).avail_out;
777
778    return err;
779}
780/* }}} */
781
782/* {{{ php_deflate_string
783 */
784static int php_deflate_string(const char *str, uint str_length, char **newstr, uint *new_length, zend_bool do_start, zend_bool do_end TSRMLS_DC)
785{
786    int err;
787
788    if (do_start) {
789        ZLIBG(stream).zalloc = php_zlib_alloc;
790        ZLIBG(stream).zfree = php_zlib_free;
791        ZLIBG(stream).opaque = Z_NULL;
792
793        switch (ZLIBG(compression_coding)) {
794            case CODING_GZIP:
795                /* windowBits is passed < 0 to suppress zlib header & trailer */
796                if (deflateInit2(&ZLIBG(stream), ZLIBG(output_compression_level), Z_DEFLATED,   -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
797                    /* TODO: print out error */
798                    return FAILURE;
799                }
800
801                ZLIBG(crc) = crc32(0L, Z_NULL, 0);
802                break;
803
804            case CODING_DEFLATE:
805                if (deflateInit(&ZLIBG(stream), ZLIBG(output_compression_level)) != Z_OK) {
806                    /* TODO: print out error */
807                    return FAILURE;
808                }
809                break;
810        }
811    }
812
813    ZLIBG(stream).next_in = (Bytef *) str;
814    ZLIBG(stream).avail_in = (uInt) str_length;
815
816    if (ZLIBG(compression_coding) == CODING_GZIP) {
817        ZLIBG(crc) = crc32(ZLIBG(crc), (const Bytef *) str, str_length);
818    }
819
820    err = php_do_deflate(str_length, (Bytef **) newstr, new_length, do_start, do_end TSRMLS_CC);
821    /* TODO: error handling (err may be Z_STREAM_ERROR, Z_BUF_ERROR, ?) */
822
823    if (do_start && ZLIBG(compression_coding) == CODING_GZIP) {
824        /* Write a very simple .gz header: */
825        (*newstr)[0] = gz_magic[0];
826        (*newstr)[1] = gz_magic[1];
827        (*newstr)[2] = Z_DEFLATED;
828        (*newstr)[3] = (*newstr)[4] = (*newstr)[5] = (*newstr)[6] = (*newstr)[7] = (*newstr)[8] = 0;
829        (*newstr)[9] = OS_CODE;
830        *new_length += 10;
831    }
832    if (do_end) {
833        if (ZLIBG(compression_coding) == CODING_GZIP) {
834            char *trailer = (*newstr) + (*new_length);
835
836            /* write crc & stream.total_in in LSB order */
837            trailer[0] = (char) ZLIBG(crc) & 0xFF;
838            trailer[1] = (char) (ZLIBG(crc) >> 8) & 0xFF;
839            trailer[2] = (char) (ZLIBG(crc) >> 16) & 0xFF;
840            trailer[3] = (char) (ZLIBG(crc) >> 24) & 0xFF;
841            trailer[4] = (char) ZLIBG(stream).total_in & 0xFF;
842            trailer[5] = (char) (ZLIBG(stream).total_in >> 8) & 0xFF;
843            trailer[6] = (char) (ZLIBG(stream).total_in >> 16) & 0xFF;
844            trailer[7] = (char) (ZLIBG(stream).total_in >> 24) & 0xFF;
845            trailer[8] = '\0';
846            *new_length += 8;
847        }
848        deflateEnd(&ZLIBG(stream));
849    }
850
851    return SUCCESS;
852}
853/* }}} */
854
855/* {{{ proto string gzencode(string data [, int level [, int encoding_mode]])
856   GZ encode a string */
857static PHP_FUNCTION(gzencode)
858{
859    char *data, *s2;
860    int data_len;
861    long level = Z_DEFAULT_COMPRESSION, coding = CODING_GZIP;
862    int status;
863    z_stream stream;
864
865    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ll", &data, &data_len, &level, &coding) == FAILURE) {
866        return;
867    }
868
869    if ((level < -1) || (level > 9)) {
870        php_error_docref(NULL TSRMLS_CC, E_WARNING, "compression level(%ld) must be within -1..9", level);
871        RETURN_FALSE;
872    }
873
874    if ((coding != CODING_GZIP) && (coding != CODING_DEFLATE)) {
875        php_error_docref(NULL TSRMLS_CC, E_WARNING, "encoding mode must be FORCE_GZIP or FORCE_DEFLATE");
876        RETURN_FALSE;
877    }
878
879    stream.zalloc = php_zlib_alloc;
880    stream.zfree = php_zlib_free;
881    stream.opaque = Z_NULL;
882
883    stream.next_in = (Bytef *) data;
884    stream.avail_in = data_len;
885
886    stream.avail_out = stream.avail_in + (stream.avail_in / PHP_ZLIB_MODIFIER) + 15 + 1; /* room for \0 */
887    s2 = (char *) emalloc(stream.avail_out + GZIP_HEADER_LENGTH + (coding == CODING_GZIP ? GZIP_FOOTER_LENGTH : 0));
888
889    /* add gzip file header */
890    s2[0] = gz_magic[0];
891    s2[1] = gz_magic[1];
892    s2[2] = Z_DEFLATED;
893    s2[3] = s2[4] = s2[5] = s2[6] = s2[7] = s2[8] = 0; /* time set to 0 */
894    s2[9] = OS_CODE;
895
896    stream.next_out = &(s2[GZIP_HEADER_LENGTH]);
897
898    switch (coding) {
899        case CODING_GZIP:
900            /* windowBits is passed < 0 to suppress zlib header & trailer */
901            if ((status = deflateInit2(&stream, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY)) != Z_OK) {
902                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
903                RETURN_FALSE;
904            }
905
906            break;
907        case CODING_DEFLATE:
908            if ((status = deflateInit(&stream, level)) != Z_OK) {
909                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
910                RETURN_FALSE;
911            }
912            break;
913    }
914
915    status = deflate(&stream, Z_FINISH);
916    if (status != Z_STREAM_END) {
917        deflateEnd(&stream);
918        if (status == Z_OK) {
919            status = Z_BUF_ERROR;
920        }
921    } else {
922        status = deflateEnd(&stream);
923    }
924
925    if (status == Z_OK) {
926        /* resize to buffer to the "right" size */
927        s2 = erealloc(s2, stream.total_out + GZIP_HEADER_LENGTH + (coding == CODING_GZIP ? GZIP_FOOTER_LENGTH : 0) + 1);
928
929        if (coding == CODING_GZIP) {
930            char *trailer = s2 + (stream.total_out + GZIP_HEADER_LENGTH);
931            uLong crc = crc32(0L, Z_NULL, 0);
932
933            crc = crc32(crc, (const Bytef *) data, data_len);
934
935            /* write crc & stream.total_in in LSB order */
936            trailer[0] = (char) crc & 0xFF;
937            trailer[1] = (char) (crc >> 8) & 0xFF;
938            trailer[2] = (char) (crc >> 16) & 0xFF;
939            trailer[3] = (char) (crc >> 24) & 0xFF;
940            trailer[4] = (char) stream.total_in & 0xFF;
941            trailer[5] = (char) (stream.total_in >> 8) & 0xFF;
942            trailer[6] = (char) (stream.total_in >> 16) & 0xFF;
943            trailer[7] = (char) (stream.total_in >> 24) & 0xFF;
944            trailer[8] = '\0';
945        } else {
946            s2[stream.total_out + GZIP_HEADER_LENGTH + (coding == CODING_GZIP ? GZIP_FOOTER_LENGTH : 0)] = '\0';
947        }
948        RETURN_STRINGL(s2, stream.total_out + GZIP_HEADER_LENGTH + (coding == CODING_GZIP ? GZIP_FOOTER_LENGTH : 0), 0);
949    } else {
950        efree(s2);
951        php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", zError(status));
952        RETURN_FALSE;
953    }
954}
955/* }}} */
956
957/* {{{ php_ob_gzhandler_check
958 */
959int php_ob_gzhandler_check(TSRMLS_D)
960{
961    /* check for wrong usages */
962    if (OG(ob_nesting_level > 0)) {
963        if (php_ob_handler_used("ob_gzhandler" TSRMLS_CC)) {
964            php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler 'ob_gzhandler' cannot be used twice");
965            return FAILURE;
966        }
967        if (php_ob_handler_used("mb_output_handler" TSRMLS_CC)) {
968            php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler 'ob_gzhandler' cannot be used after 'mb_output_handler'");
969            return FAILURE;
970        }
971        if (php_ob_handler_used("URL-Rewriter" TSRMLS_CC)) {
972            php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler 'ob_gzhandler' cannot be used after 'URL-Rewriter'");
973            return FAILURE;
974        }
975        if (php_ob_init_conflict("ob_gzhandler", "zlib output compression" TSRMLS_CC)) {
976            return FAILURE;
977        }
978    }
979
980    return SUCCESS;
981}
982
983/* }}} */
984
985/* {{{ proto string ob_gzhandler(string str, int mode)
986   Encode str based on accept-encoding setting - designed to be called from ob_start() */
987static PHP_FUNCTION(ob_gzhandler)
988{
989    char *string;
990    int string_len;
991    long mode;
992    zval **a_encoding;
993    zend_bool return_original = 0;
994    zend_bool do_start, do_end;
995
996    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &string, &string_len, &mode) == FAILURE) {
997        return;
998    }
999
1000    if (ZLIBG(ob_gzhandler_status) == -1) {
1001        RETURN_FALSE;
1002    }
1003
1004    zend_is_auto_global("_SERVER", sizeof("_SERVER")-1 TSRMLS_CC);
1005
1006    if (!PG(http_globals)[TRACK_VARS_SERVER]
1007        || zend_hash_find(PG(http_globals)[TRACK_VARS_SERVER]->value.ht, "HTTP_ACCEPT_ENCODING", sizeof("HTTP_ACCEPT_ENCODING"), (void **) &a_encoding) == FAILURE
1008    ) {
1009        ZLIBG(ob_gzhandler_status) = -1;
1010        RETURN_FALSE;
1011    }
1012
1013    convert_to_string_ex(a_encoding);
1014    if (php_memnstr(Z_STRVAL_PP(a_encoding), "gzip", 4, Z_STRVAL_PP(a_encoding) + Z_STRLEN_PP(a_encoding))) {
1015        ZLIBG(compression_coding) = CODING_GZIP;
1016    } else if (php_memnstr(Z_STRVAL_PP(a_encoding), "deflate", 7, Z_STRVAL_PP(a_encoding) + Z_STRLEN_PP(a_encoding))) {
1017        ZLIBG(compression_coding) = CODING_DEFLATE;
1018    } else {
1019        ZLIBG(ob_gzhandler_status) = -1;
1020        RETURN_FALSE;
1021    }
1022
1023    do_start = ((mode & PHP_OUTPUT_HANDLER_START) ? 1 : 0);
1024    do_end = ((mode & PHP_OUTPUT_HANDLER_END) ? 1 : 0);
1025    Z_STRVAL_P(return_value) = NULL;
1026    Z_STRLEN_P(return_value) = 0;
1027
1028    if (php_deflate_string(string, string_len, &Z_STRVAL_P(return_value), &Z_STRLEN_P(return_value), do_start, do_end TSRMLS_CC) == SUCCESS) {
1029        Z_TYPE_P(return_value) = IS_STRING;
1030        if (do_start) {
1031            switch (ZLIBG(compression_coding)) {
1032                case CODING_GZIP:
1033                    if (sapi_add_header("Content-Encoding: gzip", sizeof("Content-Encoding: gzip") - 1, 1) == FAILURE) {
1034                        return_original = 1;
1035                    }
1036                    if (sapi_add_header_ex("Vary: Accept-Encoding", sizeof("Vary: Accept-Encoding") - 1, 1, 0 TSRMLS_CC)==FAILURE) {
1037                        return_original = 1;
1038                    }
1039                    break;
1040                case CODING_DEFLATE:
1041                    if (sapi_add_header("Content-Encoding: deflate", sizeof("Content-Encoding: deflate") - 1, 1) == FAILURE) {
1042                        return_original = 1;
1043                    }
1044                    if (sapi_add_header_ex("Vary: Accept-Encoding", sizeof("Vary: Accept-Encoding") - 1, 1, 0 TSRMLS_CC)==FAILURE) {
1045                        return_original = 1;
1046                    }
1047                    break;
1048                default:
1049                    return_original = 1;
1050                    break;
1051            }
1052        }
1053
1054        if (return_original) {
1055            zval_dtor(return_value);
1056        }
1057
1058    } else {
1059        return_original = 1;
1060    }
1061
1062    if (return_original) {
1063        /* return the original string */
1064        RETURN_STRINGL(string, string_len, 1);
1065    }
1066}
1067/* }}} */
1068
1069/* {{{ php_gzip_output_handler
1070 */
1071static void php_gzip_output_handler(char *output, uint output_len, char **handled_output, uint *handled_output_len, int mode TSRMLS_DC)
1072{
1073    zend_bool do_start, do_end;
1074
1075    if (!ZLIBG(output_compression) || SG(sapi_headers).http_response_code == 204 || SG(sapi_headers).http_response_code == 304) {
1076        *handled_output = NULL;
1077    } else {
1078        do_start = (mode & PHP_OUTPUT_HANDLER_START ? 1 : 0);
1079        do_end = (mode & PHP_OUTPUT_HANDLER_END ? 1 : 0);
1080
1081        if (do_start) {
1082            if (!SG(headers_sent) && !SG(request_info).no_headers) {
1083                switch (ZLIBG(compression_coding)) {
1084                    case CODING_GZIP:
1085                        sapi_add_header_ex(ZEND_STRL("Content-Encoding: gzip"), 1, 1 TSRMLS_CC);
1086                        break;
1087                    case CODING_DEFLATE:
1088                        sapi_add_header_ex(ZEND_STRL("Content-Encoding: deflate"), 1, 1 TSRMLS_CC);
1089                        break;
1090                }
1091                sapi_add_header_ex(ZEND_STRL("Vary: Accept-Encoding"), 1, 0 TSRMLS_CC);
1092            } else {
1093                /* Disable compression if headers can not be set (Fix for bug #49816) */
1094                ZLIBG(output_compression) = 0;
1095                *handled_output = NULL;
1096                return;
1097            }
1098        }
1099
1100        if (php_deflate_string(output, output_len, handled_output, handled_output_len, do_start, do_end TSRMLS_CC) != SUCCESS) {
1101            zend_error(E_ERROR, "Compression failed");
1102        }
1103    }
1104}
1105/* }}} */
1106
1107/* {{{ php_enable_output_compression
1108 */
1109static int php_enable_output_compression(int buffer_size TSRMLS_DC)
1110{
1111    zval **a_encoding;
1112
1113    zend_is_auto_global("_SERVER", sizeof("_SERVER")-1 TSRMLS_CC);
1114
1115    if (!PG(http_globals)[TRACK_VARS_SERVER]
1116        || zend_hash_find(PG(http_globals)[TRACK_VARS_SERVER]->value.ht, "HTTP_ACCEPT_ENCODING", sizeof("HTTP_ACCEPT_ENCODING"), (void **) &a_encoding) == FAILURE
1117    ) {
1118        return FAILURE;
1119    }
1120
1121    convert_to_string_ex(a_encoding);
1122
1123    if (php_memnstr(Z_STRVAL_PP(a_encoding), "gzip", 4, Z_STRVAL_PP(a_encoding) + Z_STRLEN_PP(a_encoding))) {
1124        ZLIBG(compression_coding) = CODING_GZIP;
1125    } else if (php_memnstr(Z_STRVAL_PP(a_encoding), "deflate", 7, Z_STRVAL_PP(a_encoding) + Z_STRLEN_PP(a_encoding))) {
1126        ZLIBG(compression_coding) = CODING_DEFLATE;
1127    } else {
1128        return FAILURE;
1129    }
1130
1131    php_ob_set_internal_handler(php_gzip_output_handler, (uint)buffer_size, "zlib output compression", 0 TSRMLS_CC);
1132
1133    if (ZLIBG(output_handler) && strlen(ZLIBG(output_handler))) {
1134        php_start_ob_buffer_named(ZLIBG(output_handler), 0, 1 TSRMLS_CC);
1135    }
1136    return SUCCESS;
1137}
1138/* }}} */
1139
1140/* {{{ php_zlib_output_compression_start() */
1141static int php_zlib_output_compression_start(TSRMLS_D)
1142{
1143    switch (ZLIBG(output_compression)) {
1144        case 0:
1145            break;
1146        case 1:
1147            ZLIBG(output_compression) = 4096;
1148            /* break omitted intentionally */
1149        default:
1150            /* ZLIBG(compression_coding) should be 0 when zlib compression hasn't been started yet.. */
1151            if (ZLIBG(compression_coding) == 0) {
1152                return php_enable_output_compression(ZLIBG(output_compression) TSRMLS_CC);
1153            }
1154    }
1155    return SUCCESS;
1156}
1157/* }}} */
1158
1159/*
1160 * Local variables:
1161 * tab-width: 4
1162 * c-basic-offset: 4
1163 * End:
1164 * vim600: sw=4 ts=4 fdm=marker
1165 * vim<600: sw=4 ts=4
1166 */
1167