mirror of
https://github.com/danog/ext-uv.git
synced 2024-11-26 20:14:47 +01:00
259 lines
6.4 KiB
C
259 lines
6.4 KiB
C
#include "uv_http_parser.h"
|
|
|
|
static int uv_httpparser_handle;
|
|
|
|
void destruct_httpparser(zend_rsrc_list_entry *rsrc TSRMLS_DC)
|
|
{
|
|
php_http_parser_context *obj = (php_http_parser_context *)rsrc->ptr;
|
|
|
|
if (obj->headers) {
|
|
zval_ptr_dtor(&obj->headers);
|
|
}
|
|
if (obj->data) {
|
|
zval_ptr_dtor(&obj->data);
|
|
}
|
|
|
|
efree(obj);
|
|
}
|
|
|
|
void register_httpparser(int module_number)
|
|
{
|
|
uv_httpparser_handle = zend_register_list_destructors_ex(destruct_httpparser, NULL, PHP_UV_HTTPPARSER_RESOURCE_NAME, module_number);
|
|
}
|
|
|
|
/* http parser callbacks */
|
|
static int on_message_begin(http_parser *p)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int on_headers_complete(http_parser *p)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int on_message_complete(http_parser *p)
|
|
{
|
|
php_http_parser_context *result = p->data;
|
|
result->finished = 1;
|
|
|
|
if (result->tmp != NULL) {
|
|
efree(result->tmp);
|
|
result->tmp = NULL;
|
|
result->tmp_len = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define PHP_HTTP_PARSER_PARSE_URL(flag, name) \
|
|
if (result->handle.field_set & (1 << flag)) { \
|
|
const char *tmp_name = at+result->handle.field_data[flag].off; \
|
|
int length = result->handle.field_data[flag].len; \
|
|
add_assoc_stringl(data, #name, (char*)tmp_name, length, 1); \
|
|
}
|
|
|
|
static int on_url_cb(http_parser *p, const char *at, size_t len)
|
|
{
|
|
php_http_parser_context *result = p->data;
|
|
zval *data = result->data;
|
|
|
|
http_parser_parse_url(at, len, 0, &result->handle);
|
|
add_assoc_stringl(data, "QUERY_STRING", (char*)at, len, 1);
|
|
|
|
PHP_HTTP_PARSER_PARSE_URL(UF_SCHEMA, SCHEME);
|
|
PHP_HTTP_PARSER_PARSE_URL(UF_HOST, HOST);
|
|
PHP_HTTP_PARSER_PARSE_URL(UF_PORT, PORT);
|
|
PHP_HTTP_PARSER_PARSE_URL(UF_PATH, PATH);
|
|
PHP_HTTP_PARSER_PARSE_URL(UF_QUERY, QUERY);
|
|
PHP_HTTP_PARSER_PARSE_URL(UF_FRAGMENT, FRAGMENT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_status_cb(http_parser *p, const char *at, size_t len)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
char *php_uv_strtoupper(char *s, size_t len)
|
|
{
|
|
unsigned char *c, *e;
|
|
|
|
c = (unsigned char *)s;
|
|
e = (unsigned char *)c+len;
|
|
|
|
while (c < e) {
|
|
*c = toupper(*c);
|
|
if (*c == '-') *c = '_';
|
|
c++;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
static int header_field_cb(http_parser *p, const char *at, size_t len)
|
|
{
|
|
php_http_parser_context *result = p->data;
|
|
|
|
if (result->was_header_value) {
|
|
if (result->tmp != NULL) {
|
|
efree(result->tmp);
|
|
result->tmp = NULL;
|
|
result->tmp_len = 0;
|
|
}
|
|
result->tmp = estrndup(at, len);
|
|
php_uv_strtoupper(result->tmp, len);
|
|
result->tmp_len = len;
|
|
} else {
|
|
result->tmp = erealloc(result->tmp, len + result->tmp_len + 1);
|
|
memcpy(result->tmp + result->tmp_len, at, len);
|
|
result->tmp[result->tmp_len + len] = '\0';
|
|
result->tmp_len = result->tmp_len + len;
|
|
}
|
|
|
|
result->was_header_value = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int header_value_cb(http_parser *p, const char *at, size_t len)
|
|
{
|
|
php_http_parser_context *result = p->data;
|
|
zval *data = result->headers;
|
|
|
|
if (result->was_header_value) {
|
|
zval **element;
|
|
|
|
if (zend_hash_find(Z_ARRVAL_P(data), result->tmp, result->tmp_len+1, (void **)&element) == SUCCESS) {
|
|
Z_STRVAL_PP(element) = erealloc(Z_STRVAL_PP(element), Z_STRLEN_PP(element) + len + 1);
|
|
memcpy(Z_STRVAL_PP(element) + Z_STRLEN_PP(element), at, len);
|
|
|
|
Z_STRVAL_PP(element)[Z_STRLEN_PP(element)+len] = '\0';
|
|
Z_STRLEN_PP(element) = Z_STRLEN_PP(element) + len;
|
|
}
|
|
} else {
|
|
add_assoc_stringl(data, result->tmp, (char*)at, len, 1);
|
|
}
|
|
|
|
result->was_header_value = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int on_body_cb(http_parser *p, const char *at, size_t len)
|
|
{
|
|
php_http_parser_context *result = p->data;
|
|
zval *data = result->headers;
|
|
|
|
add_assoc_stringl(data, "BODY", (char*)at, len, 1);
|
|
|
|
return 0;
|
|
}
|
|
/* end of callback */
|
|
|
|
/* {{{ proto resource uv_http_parser_init(long $target = UV::HTTP_REQUEST)
|
|
*/
|
|
PHP_FUNCTION(uv_http_parser_init)
|
|
{
|
|
long target = HTTP_REQUEST;
|
|
zval *header, *result;
|
|
php_http_parser_context *ctx = NULL;
|
|
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
|
|
"|l",&target) == FAILURE) {
|
|
return;
|
|
}
|
|
|
|
ctx = emalloc(sizeof(php_http_parser_context));
|
|
http_parser_init(&ctx->parser, target);
|
|
|
|
MAKE_STD_ZVAL(header);
|
|
array_init(header);
|
|
|
|
MAKE_STD_ZVAL(result);
|
|
array_init(result);
|
|
|
|
ctx->data = result;
|
|
ctx->headers = header;
|
|
ctx->finished = 0;
|
|
ctx->was_header_value = 1;
|
|
ctx->tmp = NULL;
|
|
ctx->tmp_len = 0;
|
|
|
|
if (target == HTTP_RESPONSE) {
|
|
ctx->is_response = 1;
|
|
} else {
|
|
ctx->is_response = 0;
|
|
}
|
|
|
|
memset(&ctx->handle, 0, sizeof(struct http_parser_url));
|
|
|
|
/* setup callback */
|
|
ctx->settings.on_message_begin = on_message_begin;
|
|
ctx->settings.on_header_field = header_field_cb;
|
|
ctx->settings.on_header_value = header_value_cb;
|
|
ctx->settings.on_url = on_url_cb;
|
|
ctx->settings.on_status = on_status_cb;
|
|
ctx->settings.on_body = on_body_cb;
|
|
ctx->settings.on_headers_complete = on_headers_complete;
|
|
ctx->settings.on_message_complete = on_message_complete;
|
|
|
|
ZEND_REGISTER_RESOURCE(return_value, ctx, uv_httpparser_handle);
|
|
}
|
|
|
|
/* {{{ proto bool uv_http_parser_execute(resource $parser, string $body, array &$result)
|
|
*/
|
|
PHP_FUNCTION(uv_http_parser_execute)
|
|
{
|
|
zval *z_parser = NULL, *result = NULL, *version = NULL, *headers = NULL;
|
|
php_http_parser_context *context;
|
|
char *body;
|
|
int body_len;
|
|
char version_buffer[4] = {0};
|
|
size_t nparsed = 0;
|
|
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
|
|
"rs/a", &z_parser, &body, &body_len, &result) == FAILURE) {
|
|
return;
|
|
}
|
|
|
|
ZEND_FETCH_RESOURCE(context, php_http_parser_context*, &z_parser, -1, PHP_UV_HTTPPARSER_RESOURCE_NAME, uv_httpparser_handle);
|
|
|
|
if (context->finished == 1) {
|
|
php_error_docref(NULL TSRMLS_CC, E_NOTICE, "passed uv_parser resource has already finished.");
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
context->parser.data = context;
|
|
nparsed = http_parser_execute(&context->parser, &context->settings, body, body_len);
|
|
|
|
if (result) {
|
|
zval_dtor(result);
|
|
}
|
|
|
|
if (nparsed != body_len) {
|
|
zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_CC, "parse failed");
|
|
return;
|
|
}
|
|
|
|
ZVAL_ZVAL(result, context->data, 1, 0);
|
|
if (context->is_response == 0) {
|
|
add_assoc_string(result, "REQUEST_METHOD", (char*)http_method_str(context->parser.method), 1);
|
|
} else {
|
|
add_assoc_long(result, "STATUS_CODE", (long)context->parser.status_code);
|
|
}
|
|
add_assoc_long(result, "UPGRADE", (long)context->parser.upgrade);
|
|
|
|
MAKE_STD_ZVAL(version);
|
|
snprintf(version_buffer, 4, "%d.%d", context->parser.http_major, context->parser.http_minor);
|
|
ZVAL_STRING(version, version_buffer, 1);
|
|
|
|
MAKE_STD_ZVAL(headers);
|
|
ZVAL_ZVAL(headers, context->headers, 1, 0);
|
|
|
|
add_assoc_zval(headers, "VERSION", version);
|
|
add_assoc_zval(result, "HEADERS", headers);
|
|
|
|
RETURN_BOOL(context->finished);
|
|
}
|
|
|