ext-uv/uv_http_parser.c
2014-08-21 17:52:31 +08:00

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);
}