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-2014, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
13 #include "php_http_api.h"
15 static inline int eol_match(char **line
, int *eol_len
)
19 while (' ' == *ptr
) ++ptr
;
21 if (ptr
== php_http_locate_eol(*line
, eol_len
)) {
29 const char *php_http_encoding_dechunk(const char *encoded
, size_t encoded_len
, char **decoded
, size_t *decoded_len
)
33 const char *e_ptr
= encoded
;
36 *decoded
= ecalloc(1, encoded_len
+ 1);
38 while ((encoded
+ encoded_len
- e_ptr
) > 0) {
39 ulong chunk_len
= 0, rest
;
41 chunk_len
= strtoul(e_ptr
, &n_ptr
, 16);
43 /* we could not read in chunk size */
46 * if this is the first turn and there doesn't seem to be a chunk
47 * size at the begining of the body, do not fail on apparently
48 * not encoded data and return a copy
50 if (e_ptr
== encoded
) {
51 php_error_docref(NULL
, E_NOTICE
, "Data does not seem to be chunked encoded");
52 memcpy(*decoded
, encoded
, encoded_len
);
53 *decoded_len
= encoded_len
;
54 return encoded
+ encoded_len
;
57 php_error_docref(NULL
, E_WARNING
, "Expected chunk size at pos %tu of %zu but got trash", n_ptr
- encoded
, encoded_len
);
64 /* move over '0' chunked encoding terminator and any new lines */
78 /* there should be CRLF after the chunk size, but we'll ignore SP+ too */
79 if (*n_ptr
&& !eol_match(&n_ptr
, &eol_len
)) {
81 php_error_docref(NULL
, E_WARNING
, "Expected CRLF at pos %tu of %zu but got 0x%02X 0x%02X", n_ptr
- encoded
, encoded_len
, *n_ptr
, *(n_ptr
+ 1));
83 php_error_docref(NULL
, E_WARNING
, "Expected LF at pos %tu of %zu but got 0x%02X", n_ptr
- encoded
, encoded_len
, *n_ptr
);
88 /* chunk size pretends more data than we actually got, so it's probably a truncated message */
89 if (chunk_len
> (rest
= encoded
+ encoded_len
- n_ptr
)) {
90 php_error_docref(NULL
, E_WARNING
, "Truncated message: chunk size %lu exceeds remaining data size %lu at pos %tu of %zu", chunk_len
, rest
, n_ptr
- encoded
, encoded_len
);
95 memcpy(*decoded
+ *decoded_len
, n_ptr
, chunk_len
);
96 *decoded_len
+= chunk_len
;
98 if (chunk_len
== rest
) {
99 e_ptr
= n_ptr
+ chunk_len
;
102 /* advance to next chunk */
103 e_ptr
= n_ptr
+ chunk_len
+ eol_len
;
110 php_http_encoding_stream_t
*php_http_encoding_stream_init(php_http_encoding_stream_t
*s
, php_http_encoding_stream_ops_t
*ops
, unsigned flags
)
115 s
= pemalloc(sizeof(*s
), (flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
117 memset(s
, 0, sizeof(*s
));
121 if (EXPECTED(s
->ops
= ops
)) {
122 php_http_encoding_stream_t
*ss
= s
->ops
->init(s
);
132 pefree(s
, (flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
137 php_http_encoding_stream_t
*php_http_encoding_stream_copy(php_http_encoding_stream_t
*from
, php_http_encoding_stream_t
*to
)
139 if (from
->ops
->copy
) {
140 php_http_encoding_stream_t
*ns
;
143 to
= pemalloc(sizeof(*to
), (from
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
145 memset(to
, 0, sizeof(*to
));
147 to
->flags
= from
->flags
;
150 if ((ns
= to
->ops
->copy(from
, to
))) {
160 ZEND_RESULT_CODE
php_http_encoding_stream_reset(php_http_encoding_stream_t
**s
)
162 php_http_encoding_stream_t
*ss
;
164 if (EXPECTED((*s
)->ops
->dtor
)) {
167 if (EXPECTED(ss
= (*s
)->ops
->init(*s
))) {
174 ZEND_RESULT_CODE
php_http_encoding_stream_update(php_http_encoding_stream_t
*s
, const char *in_str
, size_t in_len
, char **out_str
, size_t *out_len
)
176 if (UNEXPECTED(!s
->ops
->update
)) {
179 return s
->ops
->update(s
, in_str
, in_len
, out_str
, out_len
);
182 ZEND_RESULT_CODE
php_http_encoding_stream_flush(php_http_encoding_stream_t
*s
, char **out_str
, size_t *out_len
)
184 if (!s
->ops
->flush
) {
189 return s
->ops
->flush(s
, out_str
, out_len
);
192 zend_bool
php_http_encoding_stream_done(php_http_encoding_stream_t
*s
)
197 return s
->ops
->done(s
);
200 ZEND_RESULT_CODE
php_http_encoding_stream_finish(php_http_encoding_stream_t
*s
, char **out_str
, size_t *out_len
)
202 if (!s
->ops
->finish
) {
207 return s
->ops
->finish(s
, out_str
, out_len
);
210 void php_http_encoding_stream_dtor(php_http_encoding_stream_t
*s
)
212 if (EXPECTED(s
->ops
->dtor
)) {
217 void php_http_encoding_stream_free(php_http_encoding_stream_t
**s
)
220 if (EXPECTED((*s
)->ops
->dtor
)) {
223 pefree(*s
, ((*s
)->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
229 php_http_buffer_t buffer
;
234 static php_http_encoding_stream_t
*dechunk_init(php_http_encoding_stream_t
*s
)
236 struct dechunk_ctx
*ctx
= pecalloc(1, sizeof(*ctx
), (s
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
238 if (!php_http_buffer_init_ex(&ctx
->buffer
, PHP_HTTP_BUFFER_DEFAULT_SIZE
, (s
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
) ? PHP_HTTP_BUFFER_INIT_PERSISTENT
: 0)) {
249 static php_http_encoding_stream_t
*dechunk_copy(php_http_encoding_stream_t
*from
, php_http_encoding_stream_t
*to
)
251 int p
= from
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
;
252 struct dechunk_ctx
*from_ctx
= from
->ctx
, *to_ctx
= pemalloc(sizeof(*to_ctx
), p
);
254 if (php_http_buffer_init_ex(&to_ctx
->buffer
, PHP_HTTP_BUFFER_DEFAULT_SIZE
, p
? PHP_HTTP_BUFFER_INIT_PERSISTENT
: 0)) {
255 to_ctx
->hexlen
= from_ctx
->hexlen
;
256 to_ctx
->zeroed
= from_ctx
->zeroed
;
257 php_http_buffer_append(&to_ctx
->buffer
, from_ctx
->buffer
.data
, from_ctx
->buffer
.used
);
262 php_error_docref(NULL
, E_WARNING
, "Failed to copy inflate encoding stream: out of memory");
266 static ZEND_RESULT_CODE
dechunk_update(php_http_encoding_stream_t
*s
, const char *data
, size_t data_len
, char **decoded
, size_t *decoded_len
)
268 php_http_buffer_t tmp
;
269 struct dechunk_ctx
*ctx
= s
->ctx
;
272 php_error_docref(NULL
, E_WARNING
, "Dechunk encoding stream has already reached the end of chunked input");
275 if ((PHP_HTTP_BUFFER_NOMEM
== php_http_buffer_append(&ctx
->buffer
, data
, data_len
)) || !php_http_buffer_fix(&ctx
->buffer
)) {
283 php_http_buffer_init(&tmp
);
285 /* we have data in our buffer */
286 while (ctx
->buffer
.used
) {
288 /* we already know the size of the chunk and are waiting for data */
291 /* not enough data buffered */
292 if (ctx
->buffer
.used
< ctx
->hexlen
) {
295 if (s
->flags
& PHP_HTTP_ENCODING_STREAM_FLUSH_FULL
) {
296 /* flush all data (should only be chunk data) */
297 php_http_buffer_append(&tmp
, ctx
->buffer
.data
, ctx
->buffer
.used
);
298 /* waiting for less data now */
299 ctx
->hexlen
-= ctx
->buffer
.used
;
300 /* no more buffered data */
301 php_http_buffer_reset(&ctx
->buffer
);
305 /* we have too less data and don't need to flush */
311 /* we seem to have all data of the chunk */
313 php_http_buffer_append(&tmp
, ctx
->buffer
.data
, ctx
->hexlen
);
314 /* remove outgoing data from the buffer */
315 php_http_buffer_cut(&ctx
->buffer
, 0, ctx
->hexlen
);
322 /* we don't know the length of the chunk yet */
326 /* ignore preceeding CRLFs (too loose?) */
327 while (off
< ctx
->buffer
.used
&& (
328 ctx
->buffer
.data
[off
] == '\n' ||
329 ctx
->buffer
.data
[off
] == '\r')) {
333 php_http_buffer_cut(&ctx
->buffer
, 0, off
);
336 /* still data there? */
337 if (ctx
->buffer
.used
) {
341 /* we need eol, so we can be sure we have all hex digits */
342 php_http_buffer_fix(&ctx
->buffer
);
343 if ((eolstr
= php_http_locate_bin_eol(ctx
->buffer
.data
, ctx
->buffer
.used
, &eollen
))) {
346 /* read in chunk size */
347 ctx
->hexlen
= strtoul(ctx
->buffer
.data
, &stop
, 16);
349 /* if strtoul() stops at the beginning of the buffered data
350 there's something oddly wrong, i.e. bad input */
351 if (stop
== ctx
->buffer
.data
) {
352 php_error_docref(NULL
, E_WARNING
, "Failed to parse chunk len from '%.*s'", (int) MIN(16, ctx
->buffer
.used
), ctx
->buffer
.data
);
353 php_http_buffer_dtor(&tmp
);
357 /* cut out <chunk size hex><chunk extension><eol> */
358 php_http_buffer_cut(&ctx
->buffer
, 0, eolstr
+ eollen
- ctx
->buffer
.data
);
359 /* buffer->hexlen is 0 now or contains the size of the next chunk */
363 /* ignore following CRLFs (too loose?) */
364 while (off
< ctx
->buffer
.used
&& (
365 ctx
->buffer
.data
[off
] == '\n' ||
366 ctx
->buffer
.data
[off
] == '\r')) {
370 php_http_buffer_cut(&ctx
->buffer
, 0, off
);
378 /* we have not enough data buffered to read in chunk size */
386 php_http_buffer_fix(&tmp
);
388 *decoded_len
= tmp
.used
;
393 static ZEND_RESULT_CODE
dechunk_flush(php_http_encoding_stream_t
*s
, char **decoded
, size_t *decoded_len
)
395 struct dechunk_ctx
*ctx
= s
->ctx
;
398 /* flush all data (should only be chunk data) */
399 php_http_buffer_fix(&ctx
->buffer
);
400 php_http_buffer_data(&ctx
->buffer
, decoded
, decoded_len
);
401 /* waiting for less data now */
402 ctx
->hexlen
-= ctx
->buffer
.used
;
403 /* no more buffered data */
404 php_http_buffer_reset(&ctx
->buffer
);
415 static zend_bool
dechunk_done(php_http_encoding_stream_t
*s
)
417 return ((struct dechunk_ctx
*) s
->ctx
)->zeroed
;
420 static void dechunk_dtor(php_http_encoding_stream_t
*s
)
423 struct dechunk_ctx
*ctx
= s
->ctx
;
425 php_http_buffer_dtor(&ctx
->buffer
);
426 pefree(ctx
, (s
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
431 static php_http_encoding_stream_ops_t php_http_encoding_dechunk_ops
= {
441 php_http_encoding_stream_ops_t
*php_http_encoding_stream_get_dechunk_ops(void)
443 return &php_http_encoding_dechunk_ops
;
446 static zend_object_handlers php_http_encoding_stream_object_handlers
;
448 zend_object
*php_http_encoding_stream_object_new(zend_class_entry
*ce
)
450 return &php_http_encoding_stream_object_new_ex(ce
, NULL
)->zo
;
453 php_http_encoding_stream_object_t
*php_http_encoding_stream_object_new_ex(zend_class_entry
*ce
, php_http_encoding_stream_t
*s
)
455 php_http_encoding_stream_object_t
*o
;
457 o
= ecalloc(1, sizeof(*o
) + zend_object_properties_size(ce
));
458 zend_object_std_init(&o
->zo
, ce
);
459 object_properties_init(&o
->zo
, ce
);
465 o
->zo
.handlers
= &php_http_encoding_stream_object_handlers
;
470 zend_object
*php_http_encoding_stream_object_clone(zval
*object
)
472 php_http_encoding_stream_object_t
*new_obj
, *old_obj
= PHP_HTTP_OBJ(NULL
, object
);
473 php_http_encoding_stream_t
*cpy
= php_http_encoding_stream_copy(old_obj
->stream
, NULL
);
475 new_obj
= php_http_encoding_stream_object_new_ex(old_obj
->zo
.ce
, cpy
);
476 zend_objects_clone_members(&new_obj
->zo
, &old_obj
->zo
);
481 void php_http_encoding_stream_object_free(zend_object
*object
)
483 php_http_encoding_stream_object_t
*o
= PHP_HTTP_OBJ(object
, NULL
);
486 php_http_encoding_stream_free(&o
->stream
);
488 zend_object_std_dtor(object
);
491 static zend_class_entry
*php_http_encoding_stream_class_entry
;
492 zend_class_entry
*php_http_get_encoding_stream_class_entry(void)
494 return php_http_encoding_stream_class_entry
;
497 static zend_class_entry
*php_http_dechunk_stream_class_entry
;
498 zend_class_entry
*php_http_get_dechunk_stream_class_entry(void)
500 return php_http_dechunk_stream_class_entry
;
503 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream___construct
, 0, 0, 0)
504 ZEND_ARG_INFO(0, flags
)
506 static PHP_METHOD(HttpEncodingStream
, __construct
)
509 php_http_encoding_stream_object_t
*obj
;
510 php_http_encoding_stream_ops_t
*ops
;
512 php_http_expect(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &flags
), invalid_arg
, return);
514 obj
= PHP_HTTP_OBJ(NULL
, getThis());
516 if (UNEXPECTED(obj
->stream
)) {
517 php_http_throw(bad_method_call
, "http\\Encoding\\Stream cannot be initialized twice", NULL
);
521 if (instanceof_function(obj
->zo
.ce
, php_http_get_deflate_stream_class_entry())) {
522 ops
= php_http_encoding_stream_get_deflate_ops();
523 } else if (instanceof_function(obj
->zo
.ce
, php_http_get_inflate_stream_class_entry())) {
524 ops
= php_http_encoding_stream_get_inflate_ops();
525 } else if (instanceof_function(obj
->zo
.ce
, php_http_dechunk_stream_class_entry
)) {
526 ops
= &php_http_encoding_dechunk_ops
;
527 } else if (instanceof_function(obj
->zo
.ce
, php_http_get_enbrotli_stream_class_entry())) {
528 ops
= php_http_encoding_stream_get_enbrotli_ops();
529 } else if (instanceof_function(obj
->zo
.ce
, php_http_get_debrotli_stream_class_entry())) {
530 ops
= php_http_encoding_stream_get_debrotli_ops();
532 php_http_throw(runtime
, "Unknown http\\Encoding\\Stream class '%s'", obj
->zo
.ce
->name
->val
);
536 php_http_expect(obj
->stream
= php_http_encoding_stream_init(obj
->stream
, ops
, flags
), runtime
, return);
539 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_update
, 0, 0, 1)
540 ZEND_ARG_INFO(0, data
)
542 static PHP_METHOD(HttpEncodingStream
, update
)
547 if (EXPECTED(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data_str
, &data_len
))) {
548 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
550 if (EXPECTED(obj
->stream
)) {
551 char *encoded_str
= NULL
;
554 if (EXPECTED(SUCCESS
== php_http_encoding_stream_update(obj
->stream
, data_str
, data_len
, &encoded_str
, &encoded_len
))) {
555 if (EXPECTED(encoded_str
)) {
556 RETURN_STR(php_http_cs2zs(encoded_str
, encoded_len
));
558 RETURN_EMPTY_STRING();
565 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_flush
, 0, 0, 0)
567 static PHP_METHOD(HttpEncodingStream
, flush
)
569 if (EXPECTED(SUCCESS
== zend_parse_parameters_none())) {
570 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
572 if (EXPECTED(obj
->stream
)) {
573 char *encoded_str
= NULL
;
576 if (EXPECTED(SUCCESS
== php_http_encoding_stream_flush(obj
->stream
, &encoded_str
, &encoded_len
))) {
578 RETURN_STR(php_http_cs2zs(encoded_str
, encoded_len
));
580 RETURN_EMPTY_STRING();
587 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_done
, 0, 0, 0)
589 static PHP_METHOD(HttpEncodingStream
, done
)
591 if (EXPECTED(SUCCESS
== zend_parse_parameters_none())) {
592 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
594 if (EXPECTED(obj
->stream
)) {
595 RETURN_BOOL(php_http_encoding_stream_done(obj
->stream
));
600 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_finish
, 0, 0, 0)
602 static PHP_METHOD(HttpEncodingStream
, finish
)
604 if (EXPECTED(SUCCESS
== zend_parse_parameters_none())) {
605 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
607 if (EXPECTED(obj
->stream
)) {
608 char *encoded_str
= NULL
;
611 if (EXPECTED(SUCCESS
== php_http_encoding_stream_finish(obj
->stream
, &encoded_str
, &encoded_len
))) {
612 if (EXPECTED(SUCCESS
== php_http_encoding_stream_reset(&obj
->stream
))) {
614 RETURN_STR(php_http_cs2zs(encoded_str
, encoded_len
));
616 RETURN_EMPTY_STRING();
619 PTR_FREE(encoded_str
);
626 static zend_function_entry php_http_encoding_stream_methods
[] = {
627 PHP_ME(HttpEncodingStream
, __construct
, ai_HttpEncodingStream___construct
, ZEND_ACC_PUBLIC
|ZEND_ACC_CTOR
)
628 PHP_ME(HttpEncodingStream
, update
, ai_HttpEncodingStream_update
, ZEND_ACC_PUBLIC
)
629 PHP_ME(HttpEncodingStream
, flush
, ai_HttpEncodingStream_flush
, ZEND_ACC_PUBLIC
)
630 PHP_ME(HttpEncodingStream
, done
, ai_HttpEncodingStream_done
, ZEND_ACC_PUBLIC
)
631 PHP_ME(HttpEncodingStream
, finish
, ai_HttpEncodingStream_finish
, ZEND_ACC_PUBLIC
)
635 ZEND_BEGIN_ARG_INFO_EX(ai_HttpDechunkStream_decode
, 0, 0, 1)
636 ZEND_ARG_INFO(0, data
)
637 ZEND_ARG_INFO(1, decoded_len
)
639 static PHP_METHOD(HttpDechunkStream
, decode
)
645 if (EXPECTED(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "s|z!", &str
, &len
, &zlen
))) {
647 char *enc_str
= NULL
;
650 if (EXPECTED(end_ptr
= php_http_encoding_dechunk(str
, len
, &enc_str
, &enc_len
))) {
654 ZVAL_LONG(zlen
, str
+ len
- end_ptr
);
657 RETURN_STR(php_http_cs2zs(enc_str
, enc_len
));
659 RETURN_EMPTY_STRING();
666 static zend_function_entry php_http_dechunk_stream_methods
[] = {
667 PHP_ME(HttpDechunkStream
, decode
, ai_HttpDechunkStream_decode
, ZEND_ACC_PUBLIC
|ZEND_ACC_STATIC
)
671 PHP_MINIT_FUNCTION(http_encoding
)
673 zend_class_entry ce
= {0};
675 INIT_NS_CLASS_ENTRY(ce
, "http\\Encoding", "Stream", php_http_encoding_stream_methods
);
676 php_http_encoding_stream_class_entry
= zend_register_internal_class(&ce
);
677 php_http_encoding_stream_class_entry
->ce_flags
|= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
;
678 php_http_encoding_stream_class_entry
->create_object
= php_http_encoding_stream_object_new
;
679 memcpy(&php_http_encoding_stream_object_handlers
, zend_get_std_object_handlers(), sizeof(zend_object_handlers
));
680 php_http_encoding_stream_object_handlers
.offset
= XtOffsetOf(php_http_encoding_stream_object_t
, zo
);
681 php_http_encoding_stream_object_handlers
.clone_obj
= php_http_encoding_stream_object_clone
;
682 php_http_encoding_stream_object_handlers
.free_obj
= php_http_encoding_stream_object_free
;
684 zend_declare_class_constant_long(php_http_encoding_stream_class_entry
, ZEND_STRL("FLUSH_NONE"), PHP_HTTP_ENCODING_STREAM_FLUSH_NONE
);
685 zend_declare_class_constant_long(php_http_encoding_stream_class_entry
, ZEND_STRL("FLUSH_SYNC"), PHP_HTTP_ENCODING_STREAM_FLUSH_SYNC
);
686 zend_declare_class_constant_long(php_http_encoding_stream_class_entry
, ZEND_STRL("FLUSH_FULL"), PHP_HTTP_ENCODING_STREAM_FLUSH_FULL
);
688 memset(&ce
, 0, sizeof(ce
));
689 INIT_NS_CLASS_ENTRY(ce
, "http\\Encoding\\Stream", "Dechunk", php_http_dechunk_stream_methods
);
690 php_http_dechunk_stream_class_entry
= zend_register_internal_class_ex(&ce
, php_http_encoding_stream_class_entry
);
691 php_http_dechunk_stream_class_entry
->create_object
= php_http_encoding_stream_object_new
;
701 * vim600: noet sw=4 ts=4 fdm=marker
702 * vim<600: noet sw=4 ts=4