From 32580f1d50d0481c9069b65ef30d0acd59ada3bb Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Wed, 7 Mar 2012 16:16:04 +0000 Subject: [PATCH] add bool http\Message::isMultipart([&$boundary]) and http\Message http\Message::splitMultipartBody() --- php_http_buffer.c | 10 ++-- php_http_buffer.h | 4 +- php_http_env.c | 2 +- php_http_message.c | 103 ++++++++++++++++++++++++++++++++++++- php_http_message.h | 3 ++ php_http_message_body.c | 104 ++++++++++++++++++++++++++++++++++++++ php_http_message_body.h | 18 +------ php_http_message_parser.c | 2 +- php_http_misc.h | 4 +- 9 files changed, 224 insertions(+), 26 deletions(-) diff --git a/php_http_buffer.c b/php_http_buffer.c index 5a3927e..bd99dc8 100644 --- a/php_http_buffer.c +++ b/php_http_buffer.c @@ -384,22 +384,22 @@ PHP_HTTP_BUFFER_API void php_http_buffer_chunked_output(php_http_buffer_t **s, c STR_FREE(chunk); } -PHP_HTTP_BUFFER_API ssize_t php_http_buffer_passthru(php_http_buffer_t *s, size_t chunk_size, php_http_buffer_pass_func_t passin, void *passin_arg, php_http_buffer_pass_func_t passon, void *passon_arg TSRMLS_DC) +PHP_HTTP_BUFFER_API ssize_t php_http_buffer_passthru(php_http_buffer_t **s, size_t chunk_size, php_http_buffer_pass_func_t passin, void *passin_arg, php_http_buffer_pass_func_t passon, void *passon_arg TSRMLS_DC) { - size_t passed_on = 0, passed_in = php_http_buffer_chunked_input(&s, chunk_size, passin, passin_arg TSRMLS_CC); + size_t passed_on = 0, passed_in = php_http_buffer_chunked_input(s, chunk_size, passin, passin_arg TSRMLS_CC); if (passed_in == PHP_HTTP_BUFFER_PASS0) { return passed_in; } - if (passed_in) { - passed_on = passon(passon_arg, s->data, passed_in TSRMLS_CC); + if (passed_in || (*s)->used) { + passed_on = passon(passon_arg, (*s)->data, (*s)->used TSRMLS_CC); if (passed_on == PHP_HTTP_BUFFER_PASS0) { return passed_on; } if (passed_on) { - php_http_buffer_cut(s, 0, passed_on); + php_http_buffer_cut(*s, 0, passed_on); } } diff --git a/php_http_buffer.h b/php_http_buffer.h index 384120e..2052fe8 100644 --- a/php_http_buffer.h +++ b/php_http_buffer.h @@ -217,7 +217,9 @@ PHP_HTTP_BUFFER_API void php_http_buffer_free(php_http_buffer_t **buf); /* stores data in a php_http_buffer_t until it reaches chunk_size */ PHP_HTTP_BUFFER_API size_t php_http_buffer_chunk_buffer(php_http_buffer_t **s, const char *data, size_t data_len, char **chunk, size_t chunk_size); -typedef size_t (*php_http_buffer_pass_func_t)(void *opaque, const char *, size_t TSRMLS_DC); +typedef size_t (*php_http_buffer_pass_func_t)(void *opaque, char *, size_t TSRMLS_DC); + +PHP_HTTP_BUFFER_API ssize_t php_http_buffer_passthru(php_http_buffer_t **s, size_t chunk_size, php_http_buffer_pass_func_t passin, void *passin_arg, php_http_buffer_pass_func_t passon, void *passon_arg TSRMLS_DC); /* wrapper around php_http_buffer_chunk_buffer, which passes available chunks to passthru() */ PHP_HTTP_BUFFER_API void php_http_buffer_chunked_output(php_http_buffer_t **s, const char *data, size_t data_len, size_t chunk_size, php_http_buffer_pass_func_t passout, void *opaque TSRMLS_DC); diff --git a/php_http_env.c b/php_http_env.c index 926e252..6a7f17d 100644 --- a/php_http_env.c +++ b/php_http_env.c @@ -660,7 +660,7 @@ PHP_METHOD(HttpEnv, getRequestBody) zend_object_value ov; php_http_message_body_t *body = php_http_env_get_request_body(TSRMLS_C); - if (SUCCESS == php_http_new(&ov, class_entry, (php_http_new_t) php_http_message_body_object_new_ex, php_http_message_body_class_entry, body, NULL TSRMLS_CC)) { + if (SUCCESS == php_http_new(&ov, class_entry, (php_http_new_t) php_http_message_body_object_new_ex, php_http_message_body_class_entry, php_http_message_body_copy(body, NULL, 0), NULL TSRMLS_CC)) { RETURN_OBJVAL(ov, 0); } } diff --git a/php_http_message.c b/php_http_message.c index 0322a4a..7dbf608 100644 --- a/php_http_message.c +++ b/php_http_message.c @@ -20,7 +20,9 @@ PHP_HTTP_API zend_bool php_http_message_info_callback(php_http_message_t **messa if (!old || old->type || zend_hash_num_elements(&old->hdrs) || PHP_HTTP_BUFFER_LEN(old)) { (*message) = php_http_message_init(NULL, 0 TSRMLS_CC); (*message)->parent = old; - (*headers) = &((*message)->hdrs); + if (headers) { + (*headers) = &((*message)->hdrs); + } } if (info) { @@ -189,6 +191,63 @@ PHP_HTTP_API zval *php_http_message_header(php_http_message_t *msg, char *key_st return ret; } +PHP_HTTP_API zend_bool php_http_message_is_multipart(php_http_message_t *msg, char **boundary) +{ + zval *ct = php_http_message_header(msg, ZEND_STRL("Content-Type"), 1); + zend_bool is_multipart = 0; + TSRMLS_FETCH_FROM_CTX(msg->ts); + + if (ct) { + php_http_params_opts_t popts; + HashTable params; + + ZEND_INIT_SYMTABLE(¶ms); + php_http_params_opts_default_get(&popts); + popts.input.str = Z_STRVAL_P(ct); + popts.input.len = Z_STRLEN_P(ct); + + if (php_http_params_parse(¶ms, &popts TSRMLS_CC)) { + zval **cur, **arg; + char *ct_str; + + zend_hash_internal_pointer_reset(¶ms); + + if (SUCCESS == zend_hash_get_current_data(¶ms, (void *) &cur) + && Z_TYPE_PP(cur) == IS_ARRAY + && HASH_KEY_IS_STRING == zend_hash_get_current_key(¶ms, &ct_str, NULL, 0) + ) { + if (php_http_match(ct_str, "multipart", PHP_HTTP_MATCH_WORD)) { + is_multipart = 1; + + /* get boundary */ + if (boundary + && SUCCESS == zend_hash_find(Z_ARRVAL_PP(cur), ZEND_STRS("arguments"), (void *) &arg) + && Z_TYPE_PP(arg) == IS_ARRAY + ) { + zval **val; + HashPosition pos; + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + + FOREACH_KEYVAL(pos, *arg, key, val) { + if (key.type == HASH_KEY_IS_STRING && !strcasecmp(key.str, "boundary")) { + zval *bnd = php_http_ztyp(IS_STRING, *val); + + if (Z_STRLEN_P(bnd)) { + *boundary = estrndup(Z_STRVAL_P(bnd), Z_STRLEN_P(bnd)); + } + zval_ptr_dtor(&bnd); + } + } + } + } + } + } + zend_hash_destroy(¶ms); + zval_ptr_dtor(&ct); + } + + return is_multipart; +} /* */ PHP_HTTP_API void php_http_message_set_type(php_http_message_t *message, php_http_message_type_t type) @@ -749,6 +808,11 @@ PHP_HTTP_BEGIN_ARGS(prepend, 1) PHP_HTTP_END_ARGS; PHP_HTTP_EMPTY_ARGS(reverse); +PHP_HTTP_BEGIN_ARGS(isMultipart, 0) + PHP_HTTP_ARG_VAL(boundary, 1) +PHP_HTTP_END_ARGS; +PHP_HTTP_EMPTY_ARGS(splitMultipartBody); + static zval *php_http_message_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC); static void php_http_message_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC); static zval **php_http_message_object_get_prop_ptr(zval *object, zval *member, const zend_literal *literal_key TSRMLS_DC); @@ -802,6 +866,9 @@ zend_function_entry php_http_message_method_entry[] = { PHP_HTTP_MESSAGE_ME(prepend, ZEND_ACC_PUBLIC) PHP_HTTP_MESSAGE_ME(reverse, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(isMultipart, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(splitMultipartBody, ZEND_ACC_PUBLIC) + EMPTY_FUNCTION_ENTRY }; static zend_object_handlers php_http_message_object_handlers; @@ -1900,6 +1967,40 @@ PHP_METHOD(HttpMessage, reverse) } } +PHP_METHOD(HttpMessage, isMultipart) +{ + zval *zboundary = NULL; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z", &zboundary)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + char *boundary = NULL; + + RETVAL_BOOL(php_http_message_is_multipart(obj->message, zboundary ? &boundary : NULL)); + + if (zboundary && boundary) { + zval_dtor(zboundary); + ZVAL_STRING(zboundary, boundary, 0); + } + } +} + +PHP_METHOD(HttpMessage, splitMultipartBody) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + char *boundary = NULL; + + if (php_http_message_is_multipart(obj->message, &boundary)) { + php_http_message_t *msg; + + if ((msg = php_http_message_body_split(&obj->message->body, boundary))) { + RETVAL_OBJVAL(php_http_message_object_new_ex(php_http_message_class_entry, msg, NULL TSRMLS_CC), 0); + } + } + STR_FREE(boundary); + } +} + PHP_METHOD(HttpMessage, count) { if (SUCCESS == zend_parse_parameters_none()) { diff --git a/php_http_message.h b/php_http_message.h index db47fc6..8c6c554 100644 --- a/php_http_message.h +++ b/php_http_message.h @@ -51,6 +51,7 @@ PHP_HTTP_API void php_http_message_set_type(php_http_message_t *m, php_http_mess PHP_HTTP_API void php_http_message_set_info(php_http_message_t *message, php_http_info_t *info); PHP_HTTP_API zval *php_http_message_header(php_http_message_t *msg, char *key_str, size_t key_len, int join); +PHP_HTTP_API zend_bool php_http_message_is_multipart(php_http_message_t *msg, char **boundary); PHP_HTTP_API void php_http_message_to_string(php_http_message_t *msg, char **string, size_t *length); PHP_HTTP_API void php_http_message_to_struct(php_http_message_t *msg, zval *strct); @@ -135,6 +136,8 @@ PHP_METHOD(HttpMessage, detach); PHP_METHOD(HttpMessage, prepend); PHP_METHOD(HttpMessage, reverse); +PHP_METHOD(HttpMessage, isMultipart); +PHP_METHOD(HttpMessage, splitMultipartBody); #endif /* diff --git a/php_http_message_body.c b/php_http_message_body.c index 7019de3..41f036c 100644 --- a/php_http_message_body.c +++ b/php_http_message_body.c @@ -404,6 +404,110 @@ static STATUS add_recursive_files(php_http_message_body_t *body, const char *nam } } +struct splitbody_arg { + php_http_buffer_t buf; + php_http_message_parser_t *parser; + char *boundary_str; + size_t boundary_len; + size_t consumed; +}; + +static size_t splitbody(void *opaque, char *buf, size_t len TSRMLS_DC) +{ + struct splitbody_arg *arg = opaque; + const char *boundary = NULL; + size_t consumed = 0; + int first_boundary; + + do { + first_boundary = !(consumed || arg->consumed); + + if ((boundary = php_http_locate_str(buf, len, arg->boundary_str + first_boundary, arg->boundary_len - first_boundary))) { + size_t real_boundary_len = arg->boundary_len - 1, cut; + const char *real_boundary = boundary + !first_boundary; + + if (buf + len <= real_boundary + real_boundary_len) { + /* if we just have enough data for the boundary, it's just a byte too less */ + arg->consumed += consumed; + return consumed; + } + + if (!first_boundary) { + /* this is not the first boundary, read rest of this message */ + php_http_buffer_append(&arg->buf, buf, real_boundary - buf); + php_http_message_parser_parse(arg->parser, &arg->buf, 0, &arg->parser->message); + } + + /* move after the boundary */ + cut = real_boundary - buf + real_boundary_len; + buf += cut; + len -= cut; + consumed += cut; + + if (buf == php_http_locate_bin_eol(buf, len, NULL)) { + if (!first_boundary) { + /* advance messages */ + php_http_message_t *msg; + + msg = php_http_message_init(NULL, 0 TSRMLS_CC); + msg->parent = arg->parser->message; + arg->parser->message = msg; + } + } else { + /* is this the last boundary? */ + if (*buf == '-') { + /* ignore the rest */ + consumed += len; + len = 0; + } else { + /* let this be garbage */ + php_http_error(HE_WARNING, PHP_HTTP_E_MESSAGE_BODY, "Malformed multipart boundary at pos %zu", consumed); + return -1; + } + } + } + } while (boundary && len); + + /* let there be room for the next boundary */ + if (len > arg->boundary_len) { + consumed += len - arg->boundary_len; + php_http_buffer_append(&arg->buf, buf, len - arg->boundary_len); + php_http_message_parser_parse(arg->parser, &arg->buf, 0, &arg->parser->message); + } + + arg->consumed += consumed; + return consumed; +} + +PHP_HTTP_API php_http_message_t *php_http_message_body_split(php_http_message_body_t *body, const char *boundary) +{ + php_stream *s = php_http_message_body_stream(body); + php_http_buffer_t *tmp = NULL; + php_http_message_t *msg = NULL; + struct splitbody_arg arg; + TSRMLS_FETCH_FROM_CTX(body->ts); + + php_http_buffer_init(&arg.buf); + arg.parser = php_http_message_parser_init(NULL TSRMLS_CC); + arg.boundary_len = spprintf(&arg.boundary_str, 0, "\n--%s", boundary); + arg.consumed = 0; + + php_stream_rewind(s); + while (!php_stream_eof(s)) { + php_http_buffer_passthru(&tmp, 0x1000, (php_http_buffer_pass_func_t) _php_stream_read, s, splitbody, &arg TSRMLS_CC); + } + + msg = arg.parser->message; + arg.parser->message = NULL; + + php_http_buffer_free(&tmp); + php_http_message_parser_free(&arg.parser); + php_http_buffer_dtor(&arg.buf); + STR_FREE(arg.boundary_str); + + return msg; +} + /* PHP */ #define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpMessageBody, method, 0, req_args) diff --git a/php_http_message_body.h b/php_http_message_body.h index 18670bd..22f492a 100644 --- a/php_http_message_body.h +++ b/php_http_message_body.h @@ -22,22 +22,7 @@ typedef struct php_http_message_body { #endif } php_http_message_body_t; -typedef enum php_http_message_body_file_is { - PHP_HTTP_MESSAGE_BODY_FILE_IS_EMPTY, - PHP_HTTP_MESSAGE_BODY_FILE_IS_DATA, - PHP_HTTP_MESSAGE_BODY_FILE_IS_PATH, - PHP_HTTP_MESSAGE_BODY_FILE_IS_STREAM -} php_http_message_body_file_is_t; - -typedef struct php_http_message_body_file { - php_http_message_body_file_is_t type; - size_t length; - union { - char *data; - char *path; - php_stream *stream; - } file; -} php_http_message_body_file_t; +typedef struct php_http_message php_http_message_t; PHP_HTTP_API php_http_message_body_t *php_http_message_body_init(php_http_message_body_t *body, php_stream *stream TSRMLS_DC); PHP_HTTP_API php_http_message_body_t *php_http_message_body_copy(php_http_message_body_t *from, php_http_message_body_t *to, zend_bool dup_internal_stream_and_contents); @@ -56,6 +41,7 @@ PHP_HTTP_API const php_stream_statbuf *php_http_message_body_stat(php_http_messa #define php_http_message_body_mtime(b) (php_http_message_body_stat((b))->sb.st_mtime) PHP_HTTP_API char *php_http_message_body_etag(php_http_message_body_t *body); PHP_HTTP_API const char *php_http_message_body_boundary(php_http_message_body_t *body); +PHP_HTTP_API php_http_message_t *php_http_message_body_split(php_http_message_body_t *body, const char *boundary); static inline php_stream *php_http_message_body_stream(php_http_message_body_t *body) { diff --git a/php_http_message_parser.c b/php_http_message_parser.c index 319f089..23cf4f8 100644 --- a/php_http_message_parser.c +++ b/php_http_message_parser.c @@ -399,7 +399,7 @@ PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_parse(php_h len = buffer->used; cut = len; - php_http_message_parser_state_push(parser, 2, !buffer->used?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); + php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); break; } diff --git a/php_http_misc.h b/php_http_misc.h index cebf78d..35c8951 100644 --- a/php_http_misc.h +++ b/php_http_misc.h @@ -139,7 +139,9 @@ static inline const char *php_http_locate_bin_eol(const char *bin, size_t len, i for (eol = bin; eol - bin < len; ++eol) { if (*eol == '\r' || *eol == '\n') { - *eol_len = eol ? ((eol[0] == '\r' && eol[1] == '\n') ? 2 : 1) : 0; + if (eol_len) { + *eol_len = eol ? ((eol[0] == '\r' && eol[1] == '\n') ? 2 : 1) : 0; + } return eol; } } -- 2.30.2