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 unsigned long 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
)) {
168 if (EXPECTED(ss
= (*s
)->ops
->init(*s
))) {
169 ss
->flags
&= ~PHP_HTTP_ENCODING_STREAM_DIRTY
;
176 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
)
178 ZEND_RESULT_CODE rc
= FAILURE
;
180 if (EXPECTED(s
->ops
->update
)) {
181 rc
= s
->ops
->update(s
, in_str
, in_len
, out_str
, out_len
);
184 s
->flags
|= PHP_HTTP_ENCODING_STREAM_DIRTY
;
189 ZEND_RESULT_CODE
php_http_encoding_stream_flush(php_http_encoding_stream_t
*s
, char **out_str
, size_t *out_len
)
191 if (!s
->ops
->flush
) {
196 return s
->ops
->flush(s
, out_str
, out_len
);
199 zend_bool
php_http_encoding_stream_done(php_http_encoding_stream_t
*s
)
202 return !(s
->flags
& PHP_HTTP_ENCODING_STREAM_DIRTY
);
204 return s
->ops
->done(s
);
207 ZEND_RESULT_CODE
php_http_encoding_stream_finish(php_http_encoding_stream_t
*s
, char **out_str
, size_t *out_len
)
209 if (!s
->ops
->finish
) {
213 s
->flags
&= ~PHP_HTTP_ENCODING_STREAM_DIRTY
;
217 return s
->ops
->finish(s
, out_str
, out_len
);
220 void php_http_encoding_stream_dtor(php_http_encoding_stream_t
*s
)
222 if (EXPECTED(s
->ops
->dtor
)) {
227 void php_http_encoding_stream_free(php_http_encoding_stream_t
**s
)
230 if (EXPECTED((*s
)->ops
->dtor
)) {
233 pefree(*s
, ((*s
)->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
239 php_http_buffer_t buffer
;
240 unsigned long hexlen
;
244 static php_http_encoding_stream_t
*dechunk_init(php_http_encoding_stream_t
*s
)
246 struct dechunk_ctx
*ctx
= pecalloc(1, sizeof(*ctx
), (s
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
248 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)) {
259 static php_http_encoding_stream_t
*dechunk_copy(php_http_encoding_stream_t
*from
, php_http_encoding_stream_t
*to
)
261 int p
= from
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
;
262 struct dechunk_ctx
*from_ctx
= from
->ctx
, *to_ctx
= pemalloc(sizeof(*to_ctx
), p
);
264 if (php_http_buffer_init_ex(&to_ctx
->buffer
, PHP_HTTP_BUFFER_DEFAULT_SIZE
, p
? PHP_HTTP_BUFFER_INIT_PERSISTENT
: 0)) {
265 to_ctx
->hexlen
= from_ctx
->hexlen
;
266 to_ctx
->zeroed
= from_ctx
->zeroed
;
267 php_http_buffer_append(&to_ctx
->buffer
, from_ctx
->buffer
.data
, from_ctx
->buffer
.used
);
272 php_error_docref(NULL
, E_WARNING
, "Failed to copy inflate encoding stream: out of memory");
276 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
)
278 php_http_buffer_t tmp
;
279 struct dechunk_ctx
*ctx
= s
->ctx
;
282 php_error_docref(NULL
, E_WARNING
, "Dechunk encoding stream has already reached the end of chunked input");
285 if ((PHP_HTTP_BUFFER_NOMEM
== php_http_buffer_append(&ctx
->buffer
, data
, data_len
)) || !php_http_buffer_fix(&ctx
->buffer
)) {
293 php_http_buffer_init(&tmp
);
295 /* we have data in our buffer */
296 while (ctx
->buffer
.used
) {
298 /* we already know the size of the chunk and are waiting for data */
301 /* not enough data buffered */
302 if (ctx
->buffer
.used
< ctx
->hexlen
) {
305 if (s
->flags
& PHP_HTTP_ENCODING_STREAM_FLUSH_FULL
) {
306 /* flush all data (should only be chunk data) */
307 php_http_buffer_append(&tmp
, ctx
->buffer
.data
, ctx
->buffer
.used
);
308 /* waiting for less data now */
309 ctx
->hexlen
-= ctx
->buffer
.used
;
310 /* no more buffered data */
311 php_http_buffer_reset(&ctx
->buffer
);
315 /* we have too less data and don't need to flush */
321 /* we seem to have all data of the chunk */
323 php_http_buffer_append(&tmp
, ctx
->buffer
.data
, ctx
->hexlen
);
324 /* remove outgoing data from the buffer */
325 php_http_buffer_cut(&ctx
->buffer
, 0, ctx
->hexlen
);
332 /* we don't know the length of the chunk yet */
336 /* ignore preceeding CRLFs (too loose?) */
337 while (off
< ctx
->buffer
.used
&& (
338 ctx
->buffer
.data
[off
] == '\n' ||
339 ctx
->buffer
.data
[off
] == '\r')) {
343 php_http_buffer_cut(&ctx
->buffer
, 0, off
);
346 /* still data there? */
347 if (ctx
->buffer
.used
) {
351 /* we need eol, so we can be sure we have all hex digits */
352 php_http_buffer_fix(&ctx
->buffer
);
353 if ((eolstr
= php_http_locate_bin_eol(ctx
->buffer
.data
, ctx
->buffer
.used
, &eollen
))) {
356 /* read in chunk size */
357 ctx
->hexlen
= strtoul(ctx
->buffer
.data
, &stop
, 16);
359 /* if strtoul() stops at the beginning of the buffered data
360 there's something oddly wrong, i.e. bad input */
361 if (stop
== ctx
->buffer
.data
) {
362 php_error_docref(NULL
, E_WARNING
, "Failed to parse chunk len from '%.*s'", (int) MIN(16, ctx
->buffer
.used
), ctx
->buffer
.data
);
363 php_http_buffer_dtor(&tmp
);
367 /* cut out <chunk size hex><chunk extension><eol> */
368 php_http_buffer_cut(&ctx
->buffer
, 0, eolstr
+ eollen
- ctx
->buffer
.data
);
369 /* buffer->hexlen is 0 now or contains the size of the next chunk */
373 /* ignore following CRLFs (too loose?) */
374 while (off
< ctx
->buffer
.used
&& (
375 ctx
->buffer
.data
[off
] == '\n' ||
376 ctx
->buffer
.data
[off
] == '\r')) {
380 php_http_buffer_cut(&ctx
->buffer
, 0, off
);
388 /* we have not enough data buffered to read in chunk size */
396 php_http_buffer_fix(&tmp
);
398 *decoded_len
= tmp
.used
;
403 static ZEND_RESULT_CODE
dechunk_flush(php_http_encoding_stream_t
*s
, char **decoded
, size_t *decoded_len
)
405 struct dechunk_ctx
*ctx
= s
->ctx
;
408 /* flush all data (should only be chunk data) */
409 php_http_buffer_fix(&ctx
->buffer
);
410 php_http_buffer_data(&ctx
->buffer
, decoded
, decoded_len
);
411 /* waiting for less data now */
412 ctx
->hexlen
-= ctx
->buffer
.used
;
413 /* no more buffered data */
414 php_http_buffer_reset(&ctx
->buffer
);
425 static zend_bool
dechunk_done(php_http_encoding_stream_t
*s
)
427 return ((struct dechunk_ctx
*) s
->ctx
)->zeroed
;
430 static void dechunk_dtor(php_http_encoding_stream_t
*s
)
433 struct dechunk_ctx
*ctx
= s
->ctx
;
435 php_http_buffer_dtor(&ctx
->buffer
);
436 pefree(ctx
, (s
->flags
& PHP_HTTP_ENCODING_STREAM_PERSISTENT
));
441 static php_http_encoding_stream_ops_t php_http_encoding_dechunk_ops
= {
451 php_http_encoding_stream_ops_t
*php_http_encoding_stream_get_dechunk_ops(void)
453 return &php_http_encoding_dechunk_ops
;
456 static zend_object_handlers php_http_encoding_stream_object_handlers
;
458 zend_object
*php_http_encoding_stream_object_new(zend_class_entry
*ce
)
460 return &php_http_encoding_stream_object_new_ex(ce
, NULL
)->zo
;
463 php_http_encoding_stream_object_t
*php_http_encoding_stream_object_new_ex(zend_class_entry
*ce
, php_http_encoding_stream_t
*s
)
465 php_http_encoding_stream_object_t
*o
;
467 o
= ecalloc(1, sizeof(*o
) + zend_object_properties_size(ce
));
468 zend_object_std_init(&o
->zo
, ce
);
469 object_properties_init(&o
->zo
, ce
);
475 o
->zo
.handlers
= &php_http_encoding_stream_object_handlers
;
480 zend_object
*php_http_encoding_stream_object_clone(zend_object
*object
)
482 php_http_encoding_stream_object_t
*new_obj
, *old_obj
= PHP_HTTP_OBJ(object
, NULL
);
483 php_http_encoding_stream_t
*cpy
= php_http_encoding_stream_copy(old_obj
->stream
, NULL
);
489 new_obj
= php_http_encoding_stream_object_new_ex(old_obj
->zo
.ce
, cpy
);
490 zend_objects_clone_members(&new_obj
->zo
, &old_obj
->zo
);
495 void php_http_encoding_stream_object_free(zend_object
*object
)
497 php_http_encoding_stream_object_t
*o
= PHP_HTTP_OBJ(object
, NULL
);
500 php_http_encoding_stream_free(&o
->stream
);
502 zend_object_std_dtor(object
);
505 static zend_class_entry
*php_http_encoding_stream_class_entry
;
506 zend_class_entry
*php_http_get_encoding_stream_class_entry(void)
508 return php_http_encoding_stream_class_entry
;
511 static zend_class_entry
*php_http_dechunk_stream_class_entry
;
512 zend_class_entry
*php_http_get_dechunk_stream_class_entry(void)
514 return php_http_dechunk_stream_class_entry
;
517 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream___construct
, 0, 0, 0)
518 ZEND_ARG_INFO(0, flags
)
520 static PHP_METHOD(HttpEncodingStream
, __construct
)
523 php_http_encoding_stream_object_t
*obj
;
524 php_http_encoding_stream_ops_t
*ops
;
526 php_http_expect(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &flags
), invalid_arg
, return);
528 obj
= PHP_HTTP_OBJ(NULL
, getThis());
530 if (UNEXPECTED(obj
->stream
)) {
531 php_http_throw(bad_method_call
, "http\\Encoding\\Stream cannot be initialized twice");
535 if (instanceof_function(obj
->zo
.ce
, php_http_get_deflate_stream_class_entry())) {
536 ops
= php_http_encoding_stream_get_deflate_ops();
537 } else if (instanceof_function(obj
->zo
.ce
, php_http_get_inflate_stream_class_entry())) {
538 ops
= php_http_encoding_stream_get_inflate_ops();
539 } else if (instanceof_function(obj
->zo
.ce
, php_http_dechunk_stream_class_entry
)) {
540 ops
= &php_http_encoding_dechunk_ops
;
541 #if PHP_HTTP_HAVE_LIBBROTLI
542 } else if (instanceof_function(obj
->zo
.ce
, php_http_get_enbrotli_stream_class_entry())) {
543 ops
= php_http_encoding_stream_get_enbrotli_ops();
544 } else if (instanceof_function(obj
->zo
.ce
, php_http_get_debrotli_stream_class_entry())) {
545 ops
= php_http_encoding_stream_get_debrotli_ops();
548 php_http_throw(runtime
, "Unknown http\\Encoding\\Stream class '%s'", obj
->zo
.ce
->name
->val
);
552 php_http_expect(obj
->stream
= php_http_encoding_stream_init(obj
->stream
, ops
, flags
), runtime
, return);
555 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_update
, 0, 0, 1)
556 ZEND_ARG_INFO(0, data
)
558 static PHP_METHOD(HttpEncodingStream
, update
)
563 if (EXPECTED(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data_str
, &data_len
))) {
564 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
566 if (EXPECTED(obj
->stream
)) {
567 char *encoded_str
= NULL
;
570 if (EXPECTED(SUCCESS
== php_http_encoding_stream_update(obj
->stream
, data_str
, data_len
, &encoded_str
, &encoded_len
))) {
571 if (EXPECTED(encoded_str
)) {
572 RETURN_STR(php_http_cs2zs(encoded_str
, encoded_len
));
574 RETURN_EMPTY_STRING();
581 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_flush
, 0, 0, 0)
583 static PHP_METHOD(HttpEncodingStream
, flush
)
585 if (EXPECTED(SUCCESS
== zend_parse_parameters_none())) {
586 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
588 if (EXPECTED(obj
->stream
)) {
589 char *encoded_str
= NULL
;
592 if (EXPECTED(SUCCESS
== php_http_encoding_stream_flush(obj
->stream
, &encoded_str
, &encoded_len
))) {
594 RETURN_STR(php_http_cs2zs(encoded_str
, encoded_len
));
596 RETURN_EMPTY_STRING();
603 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_done
, 0, 0, 0)
605 static PHP_METHOD(HttpEncodingStream
, done
)
607 if (EXPECTED(SUCCESS
== zend_parse_parameters_none())) {
608 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
610 if (EXPECTED(obj
->stream
)) {
611 RETURN_BOOL(php_http_encoding_stream_done(obj
->stream
));
616 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEncodingStream_finish
, 0, 0, 0)
618 static PHP_METHOD(HttpEncodingStream
, finish
)
620 if (EXPECTED(SUCCESS
== zend_parse_parameters_none())) {
621 php_http_encoding_stream_object_t
*obj
= PHP_HTTP_OBJ(NULL
, getThis());
623 if (EXPECTED(obj
->stream
)) {
624 char *encoded_str
= NULL
;
627 if (EXPECTED(SUCCESS
== php_http_encoding_stream_finish(obj
->stream
, &encoded_str
, &encoded_len
))) {
628 if (EXPECTED(SUCCESS
== php_http_encoding_stream_reset(&obj
->stream
))) {
630 RETURN_STR(php_http_cs2zs(encoded_str
, encoded_len
));
632 RETURN_EMPTY_STRING();
635 PTR_FREE(encoded_str
);
642 static zend_function_entry php_http_encoding_stream_methods
[] = {
643 PHP_ME(HttpEncodingStream
, __construct
, ai_HttpEncodingStream___construct
, ZEND_ACC_PUBLIC
)
644 PHP_ME(HttpEncodingStream
, update
, ai_HttpEncodingStream_update
, ZEND_ACC_PUBLIC
)
645 PHP_ME(HttpEncodingStream
, flush
, ai_HttpEncodingStream_flush
, ZEND_ACC_PUBLIC
)
646 PHP_ME(HttpEncodingStream
, done
, ai_HttpEncodingStream_done
, ZEND_ACC_PUBLIC
)
647 PHP_ME(HttpEncodingStream
, finish
, ai_HttpEncodingStream_finish
, ZEND_ACC_PUBLIC
)
651 ZEND_BEGIN_ARG_INFO_EX(ai_HttpDechunkStream_decode
, 0, 0, 1)
652 ZEND_ARG_INFO(0, data
)
653 ZEND_ARG_INFO(1, decoded_len
)
655 static PHP_METHOD(HttpDechunkStream
, decode
)
661 if (EXPECTED(SUCCESS
== zend_parse_parameters(ZEND_NUM_ARGS(), "s|z!", &str
, &len
, &zlen
))) {
663 char *enc_str
= NULL
;
666 if (EXPECTED(end_ptr
= php_http_encoding_dechunk(str
, len
, &enc_str
, &enc_len
))) {
670 ZVAL_LONG(zlen
, str
+ len
- end_ptr
);
673 RETURN_STR(php_http_cs2zs(enc_str
, enc_len
));
675 RETURN_EMPTY_STRING();
682 static zend_function_entry php_http_dechunk_stream_methods
[] = {
683 PHP_ME(HttpDechunkStream
, decode
, ai_HttpDechunkStream_decode
, ZEND_ACC_PUBLIC
|ZEND_ACC_STATIC
)
687 PHP_MINIT_FUNCTION(http_encoding
)
689 zend_class_entry ce
= {0};
691 INIT_NS_CLASS_ENTRY(ce
, "http\\Encoding", "Stream", php_http_encoding_stream_methods
);
692 php_http_encoding_stream_class_entry
= zend_register_internal_class(&ce
);
693 php_http_encoding_stream_class_entry
->ce_flags
|= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
;
694 php_http_encoding_stream_class_entry
->create_object
= php_http_encoding_stream_object_new
;
695 memcpy(&php_http_encoding_stream_object_handlers
, zend_get_std_object_handlers(), sizeof(zend_object_handlers
));
696 php_http_encoding_stream_object_handlers
.offset
= XtOffsetOf(php_http_encoding_stream_object_t
, zo
);
697 php_http_encoding_stream_object_handlers
.clone_obj
= php_http_encoding_stream_object_clone
;
698 php_http_encoding_stream_object_handlers
.free_obj
= php_http_encoding_stream_object_free
;
700 zend_declare_class_constant_long(php_http_encoding_stream_class_entry
, ZEND_STRL("FLUSH_NONE"), PHP_HTTP_ENCODING_STREAM_FLUSH_NONE
);
701 zend_declare_class_constant_long(php_http_encoding_stream_class_entry
, ZEND_STRL("FLUSH_SYNC"), PHP_HTTP_ENCODING_STREAM_FLUSH_SYNC
);
702 zend_declare_class_constant_long(php_http_encoding_stream_class_entry
, ZEND_STRL("FLUSH_FULL"), PHP_HTTP_ENCODING_STREAM_FLUSH_FULL
);
704 memset(&ce
, 0, sizeof(ce
));
705 INIT_NS_CLASS_ENTRY(ce
, "http\\Encoding\\Stream", "Dechunk", php_http_dechunk_stream_methods
);
706 php_http_dechunk_stream_class_entry
= zend_register_internal_class_ex(&ce
, php_http_encoding_stream_class_entry
);
707 php_http_dechunk_stream_class_entry
->create_object
= php_http_encoding_stream_object_new
;
717 * vim600: noet sw=4 ts=4 fdm=marker
718 * vim<600: noet sw=4 ts=4