2 +--------------------------------------------------------------------+
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2005, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
20 #include "php_http_encoding_api.h"
22 #include "php_http_api.h"
25 # include "php_http_send_api.h"
26 # include "php_http_headers_api.h"
30 ZEND_EXTERN_MODULE_GLOBALS(http
);
32 static inline int eol_match(char **line
, int *eol_len
)
36 while (0x20 == *ptr
) ++ptr
;
38 if (ptr
== http_locate_eol(*line
, eol_len
)) {
46 /* {{{ char *http_encoding_dechunk(char *, size_t, char **, size_t *) */
47 PHP_HTTP_API
const char *_http_encoding_dechunk(const char *encoded
, size_t encoded_len
, char **decoded
, size_t *decoded_len TSRMLS_DC
)
51 const char *e_ptr
= encoded
;
54 *decoded
= ecalloc(1, encoded_len
);
56 while ((encoded
+ encoded_len
- e_ptr
) > 0) {
57 ulong chunk_len
= 0, rest
;
59 chunk_len
= strtoul(e_ptr
, &n_ptr
, 16);
61 /* we could not read in chunk size */
64 * if this is the first turn and there doesn't seem to be a chunk
65 * size at the begining of the body, do not fail on apparently
66 * not encoded data and return a copy
68 if (e_ptr
== encoded
) {
69 http_error(HE_NOTICE
, HTTP_E_ENCODING
, "Data does not seem to be chunked encoded");
70 memcpy(*decoded
, encoded
, encoded_len
);
71 *decoded_len
= encoded_len
;
72 return encoded
+ encoded_len
;
75 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Expected chunk size at pos %lu of %lu but got trash", (ulong
) (n_ptr
- encoded
), (ulong
) encoded_len
);
85 /* there should be CRLF after the chunk size, but we'll ignore SP+ too */
86 if (*n_ptr
&& !eol_match(&n_ptr
, &eol_len
)) {
88 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Expected CRLF at pos %lu of %lu but got 0x%02X 0x%02X", (ulong
) (n_ptr
- encoded
), (ulong
) encoded_len
, *n_ptr
, *(n_ptr
+ 1));
90 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Expected LF at pos %lu of %lu but got 0x%02X", (ulong
) (n_ptr
- encoded
), (ulong
) encoded_len
, *n_ptr
);
95 /* chunk size pretends more data than we actually got, so it's probably a truncated message */
96 if (chunk_len
> (rest
= encoded
+ encoded_len
- n_ptr
)) {
97 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Truncated message: chunk size %lu exceeds remaining data size %lu at pos %lu of %lu", chunk_len
, rest
, (ulong
) (n_ptr
- encoded
), (ulong
) encoded_len
);
102 memcpy(*decoded
+ *decoded_len
, n_ptr
, chunk_len
);
103 *decoded_len
+= chunk_len
;
105 if (chunk_len
== rest
) {
106 e_ptr
= n_ptr
+ chunk_len
;
109 /* advance to next chunk */
110 e_ptr
= n_ptr
+ chunk_len
+ eol_len
;
118 #ifdef HTTP_HAVE_ZLIB
120 static const char http_encoding_gzip_header
[] = {
121 (const char) 0x1f, // fixed value
122 (const char) 0x8b, // fixed value
123 (const char) Z_DEFLATED
, // compression algorithm
124 (const char) 0, // none of the possible flags defined by the GZIP "RFC"
125 (const char) 0, // no MTIME available (4 bytes)
126 (const char) 0, // =*=
127 (const char) 0, // =*=
128 (const char) 0, // =*=
129 (const char) 0, // two possible flag values for 9 compression levels? o_O
130 (const char) 0x03 // assume *nix OS
133 inline void http_init_gzencode_buffer(z_stream
*Z
, const char *data
, size_t data_len
, char **buf_ptr
)
139 Z
->next_in
= (Bytef
*) data
;
140 Z
->avail_in
= data_len
;
141 Z
->avail_out
= HTTP_ENCODING_BUFLEN(data_len
) + HTTP_ENCODING_SAFPAD
- 1;
143 *buf_ptr
= emalloc(HTTP_ENCODING_BUFLEN(data_len
) + sizeof(http_encoding_gzip_header
) + HTTP_ENCODING_SAFPAD
);
144 memcpy(*buf_ptr
, http_encoding_gzip_header
, sizeof(http_encoding_gzip_header
));
146 Z
->next_out
= (Bytef
*) *buf_ptr
+ sizeof(http_encoding_gzip_header
);
149 inline void http_init_deflate_buffer(z_stream
*Z
, const char *data
, size_t data_len
, char **buf_ptr
)
155 Z
->data_type
= Z_UNKNOWN
;
156 Z
->next_in
= (Bytef
*) data
;
157 Z
->avail_in
= data_len
;
158 Z
->avail_out
= HTTP_ENCODING_BUFLEN(data_len
) - 1;
159 Z
->next_out
= emalloc(HTTP_ENCODING_BUFLEN(data_len
));
161 *buf_ptr
= (char *) Z
->next_out
;
164 inline void http_init_uncompress_buffer(size_t data_len
, char **buf_ptr
, size_t *buf_len
, int *iteration
)
167 *buf_len
= data_len
* 2;
168 *buf_ptr
= emalloc(*buf_len
+ 1);
170 size_t new_len
= *buf_len
<< 2;
171 char *new_ptr
= erealloc_recoverable(*buf_ptr
, new_len
+ 1);
177 *iteration
= INT_MAX
-1; /* avoid integer overflow on increment op */
182 inline void http_init_inflate_buffer(z_stream
*Z
, const char *data
, size_t data_len
, char **buf_ptr
, size_t *buf_len
, int *iteration
)
187 http_init_uncompress_buffer(data_len
, buf_ptr
, buf_len
, iteration
);
189 Z
->next_in
= (Bytef
*) data
;
190 Z
->avail_in
= data_len
;
191 Z
->avail_out
= *buf_len
;
192 Z
->next_out
= (Bytef
*) *buf_ptr
;
195 inline size_t http_finish_buffer(size_t buf_len
, char **buf_ptr
)
197 (*buf_ptr
)[buf_len
] = '\0';
201 inline size_t http_finish_gzencode_buffer(z_stream
*Z
, const char *data
, size_t data_len
, char **buf_ptr
)
206 crc
= crc32(0L, Z_NULL
, 0);
207 crc
= crc32(crc
, (const Bytef
*) data
, data_len
);
209 trailer
= *buf_ptr
+ sizeof(http_encoding_gzip_header
) + Z
->total_out
;
212 trailer
[0] = (char) (crc
& 0xFF);
213 trailer
[1] = (char) ((crc
>> 8) & 0xFF);
214 trailer
[2] = (char) ((crc
>> 16) & 0xFF);
215 trailer
[3] = (char) ((crc
>> 24) & 0xFF);
216 trailer
[4] = (char) ((Z
->total_in
) & 0xFF);
217 trailer
[5] = (char) ((Z
->total_in
>> 8) & 0xFF);
218 trailer
[6] = (char) ((Z
->total_in
>> 16) & 0xFF);
219 trailer
[7] = (char) ((Z
->total_in
>> 24) & 0xFF);
221 return http_finish_buffer(Z
->total_out
+ sizeof(http_encoding_gzip_header
) + 8, buf_ptr
);
224 inline STATUS
http_verify_gzencode_buffer(const char *data
, size_t data_len
, const char **encoded
, size_t *encoded_len
, int error_level TSRMLS_DC
)
226 size_t offset
= sizeof(http_encoding_gzip_header
);
228 if (data_len
< offset
) {
229 goto really_bad_gzip_header
;
232 if (data
[0] != (const char) 0x1F || data
[1] != (const char) 0x8B) {
233 http_error_ex(error_level TSRMLS_CC
, HTTP_E_ENCODING
, "Unrecognized GZIP header start: 0x%02X 0x%02X", (int) data
[0], (int) (data
[1] & 0xFF));
237 if (data
[2] != (const char) Z_DEFLATED
) {
238 http_error_ex(error_level TSRMLS_CC
, HTTP_E_ENCODING
, "Unrecognized compression format (%d)", (int) (data
[2] & 0xFF));
239 /* still try to decode */
241 if ((data
[3] & 0x4) == 0x4) {
242 if (data_len
< offset
+ 2) {
243 goto really_bad_gzip_header
;
245 /* there are extra fields, the length follows the common header as 2 bytes LSB */
246 offset
+= (unsigned) ((data
[offset
] & 0xFF));
248 offset
+= (unsigned) ((data
[offset
] & 0xFF) << 8);
251 if ((data
[3] & 0x8) == 0x8) {
252 if (data_len
<= offset
) {
253 goto really_bad_gzip_header
;
255 /* there's a file name */
256 offset
+= strlen(&data
[offset
]) + 1 /*NUL*/;
258 if ((data
[3] & 0x10) == 0x10) {
259 if (data_len
<= offset
) {
260 goto really_bad_gzip_header
;
262 /* there's a comment */
263 offset
+= strlen(&data
[offset
]) + 1 /* NUL */;
265 if ((data
[3] & 0x2) == 0x2) {
266 /* there's a CRC16 of the header */
268 if (data_len
<= offset
) {
269 goto really_bad_gzip_header
;
273 cmp
= (unsigned) ((data
[offset
-2] & 0xFF));
274 cmp
+= (unsigned) ((data
[offset
-1] & 0xFF) << 8);
276 crc
= crc32(0L, Z_NULL
, 0);
277 crc
= crc32(crc
, (const Bytef
*) data
, sizeof(http_encoding_gzip_header
));
279 if (cmp
!= (crc
& 0xFFFF)) {
280 http_error_ex(error_level TSRMLS_CC
, HTTP_E_ENCODING
, "GZIP headers CRC checksums so not match (%lu, %lu)", cmp
, crc
& 0xFFFF);
286 if (data_len
< offset
+ 8) {
287 http_error(error_level TSRMLS_CC
, HTTP_E_ENCODING
, "Missing or truncated GZIP footer");
292 *encoded
= data
+ offset
;
295 *encoded_len
= data_len
- offset
- 8 /* size of the assumed GZIP footer */;
300 really_bad_gzip_header
:
301 http_error(error_level TSRMLS_CC
, HTTP_E_ENCODING
, "Missing or truncated GZIP header");
305 inline STATUS
http_verify_gzdecode_buffer(const char *data
, size_t data_len
, const char *decoded
, size_t decoded_len
, int error_level TSRMLS_DC
)
307 STATUS status
= SUCCESS
;
310 crc
= crc32(0L, Z_NULL
, 0);
311 crc
= crc32(crc
, (const Bytef
*) decoded
, decoded_len
);
313 cmp
= (unsigned) ((data
[data_len
-8] & 0xFF));
314 cmp
+= (unsigned) ((data
[data_len
-7] & 0xFF) << 8);
315 cmp
+= (unsigned) ((data
[data_len
-6] & 0xFF) << 16);
316 cmp
+= (unsigned) ((data
[data_len
-5] & 0xFF) << 24);
317 len
= (unsigned) ((data
[data_len
-4] & 0xFF));
318 len
+= (unsigned) ((data
[data_len
-3] & 0xFF) << 8);
319 len
+= (unsigned) ((data
[data_len
-2] & 0xFF) << 16);
320 len
+= (unsigned) ((data
[data_len
-1] & 0xFF) << 24);
323 http_error_ex(error_level TSRMLS_CC
, HTTP_E_ENCODING
, "Could not verify data integrity: CRC checksums do not match (%lu, %lu)", cmp
, crc
);
326 if (len
!= decoded_len
) {
327 http_error_ex(error_level TSRMLS_CC
, HTTP_E_ENCODING
, "Could not verify data integrity: data sizes do not match (%lu, %lu)", len
, decoded_len
);
333 PHP_HTTP_API STATUS
_http_encode(http_encoding_type type
, int level
, const char *data
, size_t data_len
, char **encoded
, size_t *encoded_len TSRMLS_DC
)
335 STATUS status
= SUCCESS
;
339 case HTTP_ENCODING_ANY
:
340 case HTTP_ENCODING_GZIP
:
341 status
= http_encoding_gzencode(level
, data
, data_len
, encoded
, encoded_len
);
344 case HTTP_ENCODING_DEFLATE
:
345 status
= http_encoding_deflate(level
, data
, data_len
, encoded
, encoded_len
);
348 case HTTP_ENCODING_COMPRESS
:
349 status
= http_encoding_compress(level
, data
, data_len
, encoded
, encoded_len
);
352 case HTTP_ENCODING_NONE
:
354 *encoded
= estrndup(data
, data_len
);
355 *encoded_len
= data_len
;
362 PHP_HTTP_API STATUS
_http_decode(http_encoding_type type
, const char *data
, size_t data_len
, char **decoded
, size_t *decoded_len TSRMLS_DC
)
364 STATUS status
= SUCCESS
;
368 case HTTP_ENCODING_ANY
:
369 if ( SUCCESS
!= http_encoding_gzdecode(data
, data_len
, decoded
, decoded_len
) &&
370 SUCCESS
!= http_encoding_inflate(data
, data_len
, decoded
, decoded_len
) &&
371 SUCCESS
!= http_encoding_uncompress(data
, data_len
, decoded
, decoded_len
)) {
376 case HTTP_ENCODING_GZIP
:
377 status
= http_encoding_gzdecode(data
, data_len
, decoded
, decoded_len
);
380 case HTTP_ENCODING_DEFLATE
:
381 status
= http_encoding_inflate(data
, data_len
, decoded
, decoded_len
);
384 case HTTP_ENCODING_COMPRESS
:
385 status
= http_encoding_uncompress(data
, data_len
, decoded
, decoded_len
);
388 case HTTP_ENCODING_NONE
:
390 *decoded
= estrndup(data
, data_len
);
391 *decoded_len
= data_len
;
398 PHP_HTTP_API STATUS
_http_encoding_gzencode(int level
, const char *data
, size_t data_len
, char **encoded
, size_t *encoded_len TSRMLS_DC
)
401 STATUS status
= Z_OK
;
403 http_init_gzencode_buffer(&Z
, data
, data_len
, encoded
);
405 if ( (Z_OK
== (status
= deflateInit2(&Z
, level
, Z_DEFLATED
, -MAX_WBITS
, MAX_MEM_LEVEL
, Z_DEFAULT_STRATEGY
))) &&
406 (Z_STREAM_END
== (status
= deflate(&Z
, Z_FINISH
))) &&
407 (Z_OK
== (status
= deflateEnd(&Z
)))) {
408 *encoded_len
= http_finish_gzencode_buffer(&Z
, data
, data_len
, encoded
);
413 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Could not gzencode data: %s", zError(status
));
417 PHP_HTTP_API STATUS
_http_encoding_deflate(int level
, const char *data
, size_t data_len
, char **encoded
, size_t *encoded_len TSRMLS_DC
)
420 STATUS status
= Z_OK
;
422 http_init_deflate_buffer(&Z
, data
, data_len
, encoded
);
424 if ( (Z_OK
== (status
= deflateInit2(&Z
, level
, Z_DEFLATED
, -MAX_WBITS
, MAX_MEM_LEVEL
, Z_DEFAULT_STRATEGY
))) &&
425 (Z_STREAM_END
== (status
= deflate(&Z
, Z_FINISH
))) &&
426 (Z_OK
== (status
= deflateEnd(&Z
)))) {
427 *encoded_len
= http_finish_buffer(Z
.total_out
, encoded
);
432 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Could not deflate data: %s", zError(status
));
436 PHP_HTTP_API STATUS
_http_encoding_compress(int level
, const char *data
, size_t data_len
, char **encoded
, size_t *encoded_len TSRMLS_DC
)
440 *encoded
= emalloc(*encoded_len
= HTTP_ENCODING_BUFLEN(data_len
));
442 if (Z_OK
== (status
= compress2((Bytef
*) *encoded
, (uLongf
*) encoded_len
, (const Bytef
*) data
, data_len
, level
))) {
443 http_finish_buffer(*encoded_len
, encoded
);
448 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Could not compress data: %s", zError(status
));
452 PHP_HTTP_API STATUS
_http_encoding_gzdecode(const char *data
, size_t data_len
, char **decoded
, size_t *decoded_len TSRMLS_DC
)
457 if ( (SUCCESS
== http_verify_gzencode_buffer(data
, data_len
, &encoded
, &encoded_len
, HE_NOTICE
)) &&
458 (SUCCESS
== http_encoding_inflate(encoded
, encoded_len
, decoded
, decoded_len
))) {
459 http_verify_gzdecode_buffer(data
, data_len
, *decoded
, *decoded_len
, HE_NOTICE
);
466 PHP_HTTP_API STATUS
_http_encoding_inflate(const char *data
, size_t data_len
, char **decoded
, size_t *decoded_len TSRMLS_DC
)
473 http_init_inflate_buffer(&Z
, data
, data_len
, decoded
, decoded_len
, &max
);
474 if (Z_OK
== (status
= inflateInit2(&Z
, -MAX_WBITS
))) {
475 if (Z_STREAM_END
== (status
= inflate(&Z
, Z_FINISH
))) {
476 if (Z_OK
== (status
= inflateEnd(&Z
))) {
477 *decoded_len
= http_finish_buffer(Z
.total_out
, decoded
);
482 } while (++max
< HTTP_ENCODING_MAXTRY
&& status
== Z_BUF_ERROR
);
485 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Could not inflate data: %s", zError(status
));
489 PHP_HTTP_API STATUS
_http_encoding_uncompress(const char *data
, size_t data_len
, char **decoded
, size_t *decoded_len TSRMLS_DC
)
495 http_init_uncompress_buffer(data_len
, decoded
, decoded_len
, &max
);
496 if (Z_OK
== (status
= uncompress((Bytef
*) *decoded
, (uLongf
*) decoded_len
, (const Bytef
*) data
, data_len
))) {
497 http_finish_buffer(*decoded_len
, decoded
);
500 } while (++max
< HTTP_ENCODING_MAXTRY
&& status
== Z_BUF_ERROR
);
503 http_error_ex(HE_WARNING
, HTTP_E_ENCODING
, "Could not uncompress data: %s", zError(status
));
507 #define HTTP_ENCODING_STREAM_ERROR(status, tofree) \
509 if (tofree) efree(tofree); \
510 http_error_ex(HE_WARNING, HTTP_E_ENCODING, "GZIP stream error: %s", zError(status)); \
514 PHP_HTTP_API STATUS
_http_encoding_stream_init(http_encoding_stream
*s
, int gzip
, int level
, char **encoded
, size_t *encoded_len TSRMLS_DC
)
518 memset(s
, 0, sizeof(http_encoding_stream
));
519 if (Z_OK
!= (status
= deflateInit2(&s
->Z
, level
, Z_DEFLATED
, -MAX_WBITS
, MAX_MEM_LEVEL
, Z_DEFAULT_STRATEGY
))) {
520 HTTP_ENCODING_STREAM_ERROR(status
, NULL
);
523 if (s
->gzip
= gzip
) {
524 s
->crc
= crc32(0L, Z_NULL
, 0);
525 *encoded_len
= sizeof(http_encoding_gzip_header
);
526 *encoded
= emalloc(*encoded_len
);
527 memcpy(*encoded
, http_encoding_gzip_header
, *encoded_len
);
536 PHP_HTTP_API STATUS
_http_encoding_stream_update(http_encoding_stream
*s
, const char *data
, size_t data_len
, char **encoded
, size_t *encoded_len TSRMLS_DC
)
540 *encoded_len
= HTTP_ENCODING_BUFLEN(data_len
);
541 *encoded
= emalloc(*encoded_len
);
543 s
->Z
.next_in
= (Bytef
*) data
;
544 s
->Z
.avail_in
= data_len
;
545 s
->Z
.next_out
= (Bytef
*) *encoded
;
546 s
->Z
.avail_out
= *encoded_len
;
548 status
= deflate(&s
->Z
, Z_SYNC_FLUSH
);
550 if (Z_OK
!= status
&& Z_STREAM_END
!= status
) {
551 HTTP_ENCODING_STREAM_ERROR(status
, *encoded
);
553 *encoded_len
-= s
->Z
.avail_out
;
556 s
->crc
= crc32(s
->crc
, (const Bytef
*) data
, data_len
);
562 PHP_HTTP_API STATUS
_http_encoding_stream_finish(http_encoding_stream
*s
, char **encoded
, size_t *encoded_len TSRMLS_DC
)
567 *encoded
= emalloc(*encoded_len
);
569 s
->Z
.next_out
= (Bytef
*) *encoded
;
570 s
->Z
.avail_out
= *encoded_len
;
572 if (Z_STREAM_END
!= (status
= deflate(&s
->Z
, Z_FINISH
)) || Z_OK
!= (status
= deflateEnd(&s
->Z
))) {
573 HTTP_ENCODING_STREAM_ERROR(status
, *encoded
);
576 *encoded_len
-= s
->Z
.avail_out
;
578 if (s
->Z
.avail_out
< 8) {
579 *encoded
= erealloc(*encoded
, *encoded_len
+ 8);
581 (*encoded
)[(*encoded_len
)++] = (char) (s
->crc
& 0xFF);
582 (*encoded
)[(*encoded_len
)++] = (char) ((s
->crc
>> 8) & 0xFF);
583 (*encoded
)[(*encoded_len
)++] = (char) ((s
->crc
>> 16) & 0xFF);
584 (*encoded
)[(*encoded_len
)++] = (char) ((s
->crc
>> 24) & 0xFF);
585 (*encoded
)[(*encoded_len
)++] = (char) ((s
->Z
.total_in
) & 0xFF);
586 (*encoded
)[(*encoded_len
)++] = (char) ((s
->Z
.total_in
>> 8) & 0xFF);
587 (*encoded
)[(*encoded_len
)++] = (char) ((s
->Z
.total_in
>> 16) & 0xFF);
588 (*encoded
)[(*encoded_len
)++] = (char) ((s
->Z
.total_in
>> 24) & 0xFF);
594 #endif /* HTTP_HAVE_ZLIB */
596 PHP_HTTP_API zend_bool
_http_encoding_response_start(size_t content_length TSRMLS_DC
)
598 if ( php_ob_handler_used("ob_gzhandler" TSRMLS_CC
) ||
599 php_ob_handler_used("zlib output compression" TSRMLS_CC
)) {
600 HTTP_G(send
).gzip_encoding
= 0;
602 if (!HTTP_G(send
).gzip_encoding
) {
603 /* emit a content-length header */
604 if (content_length
) {
605 char cl_header_str
[128];
606 size_t cl_header_len
;
607 cl_header_len
= snprintf(cl_header_str
, lenof(cl_header_str
), "Content-Length: %lu", (ulong
) content_length
);
608 http_send_header_string_ex(cl_header_str
, cl_header_len
, 1);
611 #ifndef HTTP_HAVE_ZLIB
612 HTTP_G(send
).gzip_encoding
= 0;
613 php_start_ob_buffer_named("ob_gzhandler", 0, 0 TSRMLS_CC
);
618 INIT_PZVAL(&zsupported
);
619 array_init(&zsupported
);
620 add_next_index_stringl(&zsupported
, "gzip", lenof("gzip"), 1);
621 add_next_index_stringl(&zsupported
, "deflate", lenof("deflate"), 1);
623 HTTP_G(send
).gzip_encoding
= 0;
625 if (selected
= http_negotiate_encoding(&zsupported
)) {
627 char *encoding
= NULL
;
630 if (HASH_KEY_IS_STRING
== zend_hash_get_current_key(selected
, &encoding
, &idx
, 0) && encoding
) {
631 if (!strcmp(encoding
, "gzip")) {
632 if (SUCCESS
== (hs
= http_send_header_string("Content-Encoding: gzip"))) {
633 HTTP_G(send
).gzip_encoding
= HTTP_ENCODING_GZIP
;
635 } else if (!strcmp(encoding
, "deflate")) {
636 if (SUCCESS
== (hs
= http_send_header_string("Content-Encoding: deflate"))) {
637 HTTP_G(send
).gzip_encoding
= HTTP_ENCODING_DEFLATE
;
641 http_send_header_string("Vary: Accept-Encoding");
645 zend_hash_destroy(selected
);
646 FREE_HASHTABLE(selected
);
649 zval_dtor(&zsupported
);
650 return HTTP_G(send
).gzip_encoding
;
662 * vim600: noet sw=4 ts=4 fdm=marker
663 * vim<600: noet sw=4 ts=4