@@ -15,19 +15,19 @@
#define ST_LEN ST_URL_IDX

/* Callback identifiers are indices into the fenv table where the
* callback is saved.
* callback is saved. If you add/remove/change anything about these,
* be sure to update lhp_callback_names and FLAG_GET_BUF_CB_ID.
*/
#define CB_ON_MESSAGE_BEGIN 1
#define CB_ON_URL 2
#define CB_ON_PATH 3
#define CB_ON_QUERY_STRING 4
#define CB_ON_FRAGMENT 5
#define CB_ON_HEADER_FIELD 6
#define CB_ON_HEADER_VALUE 7
#define CB_ON_HEADERS_COMPLETE 8
#define CB_ON_BODY 9
#define CB_ON_MESSAGE_COMPLETE 10
#define CB_LEN sizeof(lhp_callback_names)/sizeof(*lhp_callback_names)
#define CB_ON_HEADER 6
#define CB_ON_HEADERS_COMPLETE 7
#define CB_ON_BODY 8
#define CB_ON_MESSAGE_COMPLETE 9
#define CB_LEN (sizeof(lhp_callback_names)/sizeof(*lhp_callback_names))

static const char *lhp_callback_names[] = {
/* The MUST be in the same order as the above callbacks */
@@ -36,230 +36,347 @@ static const char *lhp_callback_names[] = {
"on_path",
"on_query_string",
"on_fragment",
"on_header_field",
"on_header_value",
"on_header",
"on_headers_complete",
"on_body",
"on_message_complete",
};

/* Non-callback FENV indices. */
#define FENV_BUFFER_IDX 11
#define FENV_URL_IDX 12
#define FENV_BUFFER_IDX CB_LEN + 1
#define FENV_URL_IDX CB_LEN + 2
#define FENV_LEN FENV_URL_IDX

#define CB_ID_TO_CB_BIT(cb_id) (1<<((cb_id)-1))
#define CB_ID_TO_BUF_BIT(cb_id) (1<<((cb_id)+CB_LEN-1))
#define CB_ID_TO_CB_BIT(cb_id) (1<<(cb_id))
#define CB_ID_TO_BUF_BIT(cb_id) (1<<((cb_id)+CB_LEN))

/* Git the cb_id that has information stored in buff */
#define FLAG_GET_BUF_CB_ID(flags) lhp_buf_bit_to_cb_id \
( (flags) & \
(CB_ID_TO_BUF_BIT(CB_ON_PATH) | \
CB_ID_TO_BUF_BIT(CB_ON_QUERY_STRING) | \
CB_ID_TO_BUF_BIT(CB_ON_FRAGMENT) | \
CB_ID_TO_BUF_BIT(CB_ON_HEADER) | \
0) )

/* Test/set/remove a bit from the flags field of lhttp_parser. The
* FLAG_*_CB() macros test/set/remove the bit that signifies that a
* callback with that id has been registered in the FENV. The
* FLAG_*_BUF() macros test/set/remove the bit that signifies that
* callback with that id has been registered in the FENV.
*
* The FLAG_*_BUF() macros test/set/remove the bit that signifies that
* data is buffered for that callback.
*
* The FLAG_*_HFIELD() macros test/set/remove the bit that signifies
* that the first element of the buffer is the header field key.
*/
#define FLAG_HAS_CB(flags, cb_id) ( (flags) & CB_ID_TO_CB_BIT(cb_id) )
#define FLAG_SET_CB(flags, cb_id) ( (flags) |= CB_ID_TO_CB_BIT(cb_id) )
#define FLAG_RM_CB(flags, cb_id) ( (flags) &= ~CB_ID_TO_CB_BIT(cb_id) )

#define FLAG_HAS_BUF(flags, cb_id) ( (flags) & CB_ID_TO_BUF_BIT(cb_id) )
#define FLAG_SET_BUF(flags, cb_id) ( (flags) |= CB_ID_TO_BUF_BIT(cb_id) )
#define FLAG_RM_BUF(flags, cb_id) ( (flags) &= ~CB_ID_TO_BUF_BIT(cb_id) )

#define FLAG_HAS_HFIELD(flags) ( (flags) & 1 )
#define FLAG_SET_HFIELD(flags) ( (flags) |= 1 )
#define FLAG_RM_HFIELD(flags) ( (flags) &= ~1 )

typedef struct lhttp_parser {
http_parser parser; /* embedded http_parser. */
int flags; /* See above flag test/set/remove macros. */
int cb_id; /* current callback id. */
int buf_len; /* number of buffered chunks for current callback. */
int url_len; /* number of buffered chunks for 'on_url' callback. */
http_parser parser; /* embedded http_parser. */
int flags; /* See above flag test/set/remove macros. */
int buf_len; /* number of buffered chunks for current callback. */
int url_len; /* number of buffered chunks for 'on_url' callback. */
} lhttp_parser;

static int lhp_table_concat_and_clear(lua_State *L, int idx, int len) {
/* Translate a "buf bit" into the cb_id.
*/
static inline int lhp_buf_bit_to_cb_id(int flags) {
switch ( flags ) {
case CB_ID_TO_BUF_BIT(CB_ON_PATH): return CB_ON_PATH;
case CB_ID_TO_BUF_BIT(CB_ON_QUERY_STRING): return CB_ON_QUERY_STRING;
case CB_ID_TO_BUF_BIT(CB_ON_FRAGMENT): return CB_ON_FRAGMENT;
case CB_ID_TO_BUF_BIT(CB_ON_HEADER): return CB_ON_HEADER;
}
return 0;
}

/* Concatinate and remove elements from the table at idx starting at
* element begin and going to length len. The final concatinated string
* is on the top of the stack.
*/
static int lhp_table_concat_and_clear(lua_State *L, int idx, int begin, int len) {
luaL_Buffer buff;
int i;
int real_len = len-begin+1;

assert(NULL != L);
/* Empty table? */
if ( !real_len ) {
lua_pushliteral(L, "");
return 0;
}

/* fast path one chunk case. */
if (1 == len) {
lua_rawgeti(L, idx, 1);
/* One element? */
if ( 1 == real_len ) {
lua_rawgeti(L, idx, begin);
/* remove values from buffer. */
lua_pushnil(L);
lua_rawseti(L, idx, 1);
lua_rawseti(L, idx, begin);
return 0;
}
/* do a table concat. */
luaL_buffinit(L, &buff);
for(i = 1; i <= len; i++) {
lua_rawgeti(L, idx, i);
for(; begin <= len; begin++) {
lua_rawgeti(L, idx, begin);
luaL_addvalue(&buff);
/* remove values from buffer. */
lua_pushnil(L);
lua_rawseti(L, idx, i);
lua_rawseti(L, idx, begin);
}
luaL_pushresult(&buff);
return 0;
}

static int lhp_flush_event(http_parser* parser, int cb_id, int buf_idx, int *buf_len) {
lhttp_parser* lparser = (lhttp_parser*)parser;
lua_State* L;
int len;
/* "Flush" the buffer at buf_idx of length buf_len for the callback
* identified by cb_id. The CB_ON_HEADER cb_id is flushed by
* inspecting FLAG_HAS_HFIELD(). If that bit is not set, then the
* buffer at buf_idx is concatinated into a single string element in
* the buffer and nothing is pushed on the Lua stack. Otherwise the
* buffer table is cleared after pushing the following onto the Lua
* stack:
*
* CB_ON_HEADER function,
* first element of the buffer,
* second - length element of the buffer concatinated
*
* If cb_id is not CB_ON_HEADER then the buffer table is cleared after
* pushing the following onto the Lua stack:
*
* cb_id function,
* first - length elements of the buffer concatinated
*
*/
static int lhp_flush(lhttp_parser* lparser, int cb_id, int buf_idx, int* buf_len) {
lua_State* L = (lua_State*)lparser->parser.data;
int begin, len, result, top, save;

assert(NULL != parser);
L = (lua_State*)parser->data;
assert(NULL != L);
assert(cb_id);
assert(FLAG_HAS_BUF(lparser->flags, cb_id));
assert(CB_ON_URL==cb_id ?
ST_URL_IDX == buf_idx :
ST_BUFFER_IDX == buf_idx);
assert(CB_ON_URL==cb_id ?
&lparser->url_len == buf_len :
&lparser->buf_len == buf_len);

/* no event to flush. */
if ( ! cb_id ||
! FLAG_HAS_BUF(lparser->flags, cb_id) )
{
return 0;
}
if ( ! lua_checkstack(L, 7) ) return -1;

if ( ! lua_checkstack(L, 5) ) return -1;
len = *buf_len;
begin = 1;
top = lua_gettop(L);

FLAG_RM_BUF(lparser->flags, cb_id);
if ( CB_ON_HEADER == cb_id ) {
if ( FLAG_HAS_HFIELD(lparser->flags) ) {
/* Push <cnt>, <func>, <arg1>[, <arg2>] */
lua_pushinteger(L, 3);
lua_rawgeti(L, ST_FENV_IDX, cb_id);
lua_rawgeti(L, buf_idx, 1);
lua_pushnil(L);
lua_rawseti(L, buf_idx, 1);

begin = 2;
save = 0;
*buf_len = 0;
FLAG_RM_HFIELD(lparser->flags);
} else {
/* Save */
begin = 1;
save = 1;
*buf_len = 1;
}
} else {
/* Push <cnt>, <func>[, <arg1> */
lua_pushinteger(L, 2);
lua_rawgeti(L, ST_FENV_IDX, cb_id);

/* push event callback function. */
lua_rawgeti(L, ST_FENV_IDX, cb_id);
begin = 1;
save = 0;
*buf_len = 0;
}

/* get buffer length. */
len = *buf_len;
*buf_len = 0; /* reset buffer length. */
return lhp_table_concat_and_clear(L, buf_idx, len);
}
result = lhp_table_concat_and_clear(L, buf_idx, begin, len);
if ( 0 != result ) {
lua_settop(L, top);
return result;
}

static int lhp_http_cb(http_parser* parser, int cb_id) {
lhttp_parser* lparser = (lhttp_parser*)parser;
lua_State* L;
assert(NULL != parser);
L = (lua_State*)parser->data;
assert(NULL != L);
if ( save ) lua_rawseti(L, buf_idx, 1);

/* check if event has a callback function. */
if ( FLAG_HAS_CB(lparser->flags, cb_id) ) {
if ( ! lua_checkstack(L, 5) ) return -1;
return 0;
}

/* push event callback function. */
lua_rawgeti(L, ST_FENV_IDX, cb_id);
lua_pushnil(L);
/* Puts the str of length len into the buffer table at buf_idx and
* updates buf_len. It also sets the buf flag for cb_id.
*/
static int lhp_buffer(lhttp_parser* lparser, int cb_id, int buf_idx, int *buf_len, const char* str, size_t len, int hfield) {
lua_State* L = (lua_State*)lparser->parser.data;

assert(cb_id);
assert(FLAG_HAS_CB(lparser->flags, cb_id));
assert(CB_ON_URL==cb_id ?
ST_URL_IDX == buf_idx :
ST_BUFFER_IDX == buf_idx);
assert(CB_ON_URL==cb_id ?
&lparser->url_len == buf_len :
&lparser->buf_len == buf_len);

/* insert event chunk into buffer. */
FLAG_SET_BUF(lparser->flags, cb_id);
if ( hfield ) {
FLAG_SET_HFIELD(lparser->flags);
}

lua_pushlstring(L, str, len);
lua_rawseti(L, buf_idx, ++(*buf_len));

return 0;
}

static int lhp_buffer_data(http_parser* parser, int cb_id, int buf_idx, int *buf_len, const char* str, size_t len) {
lhttp_parser* lparser = (lhttp_parser*)parser;
lua_State* L;
/* Push the zero argument event for cb_id. Post condition:
* Lua stack contains 1, <func>
*/
static int lhp_push_nil_event(lhttp_parser* lparser, int cb_id) {
lua_State* L = (lua_State*)lparser->parser.data;

assert(NULL != parser);
L = (lua_State*)parser->data;
assert(NULL != L);
assert(FLAG_HAS_CB(lparser->flags, cb_id));

/* only buffer chunk if event has a callback function. */
if ( FLAG_HAS_CB(lparser->flags, cb_id) ) {
if ( ! lua_checkstack(L, 5) ) return -1;
if ( ! lua_checkstack(L, 5) ) return -1;

lua_pushinteger(L, 1);
lua_rawgeti(L, ST_FENV_IDX, cb_id);

/* insert event chunk into buffer. */
FLAG_SET_BUF(lparser->flags, cb_id);
lua_pushlstring(L, str, len);
lua_rawseti(L, buf_idx, ++(*buf_len));
}
return 0;
}

static int lhp_http_data_cb(http_parser* parser, int cb_id, const char* str, size_t len) {
lhttp_parser* lparser = (lhttp_parser*)parser;
/* flush previous event. */
if (cb_id != lparser->cb_id) {
int result = lhp_flush_event(parser, lparser->cb_id, ST_BUFFER_IDX, &(lparser->buf_len));
/* Flush the buffer and/or url as long as it is not the except_cb_id
* being buffered.
*/
static int lhp_flush_except(lhttp_parser* lparser, int except_cb_id, int flush_url, int hfield) {
int flush = 0;
int cb_id = FLAG_GET_BUF_CB_ID(lparser->flags);

/* flush previous event and/or url */
if ( cb_id ) {
if ( cb_id == CB_ON_HEADER ) {
flush = hfield ^ FLAG_HAS_HFIELD(lparser->flags);
} else if ( cb_id != except_cb_id ) {
flush = 1;
}
}

if ( flush ) {
int result = lhp_flush(lparser, cb_id, ST_BUFFER_IDX, &lparser->buf_len);
if ( 0 != result ) return result;
}

if ( flush_url && FLAG_HAS_BUF(lparser->flags, CB_ON_URL) ) {
int result = lhp_flush(lparser, CB_ON_URL, ST_URL_IDX, &lparser->url_len);
if ( 0 != result ) return result;
}
lparser->cb_id = cb_id;
return lhp_buffer_data(parser, cb_id, ST_BUFFER_IDX, &(lparser->buf_len), str, len);
return 0;
}

static int lhp_flush_url(http_parser* parser) {
/* The event for cb_id where cb_id takes a string argument. If
* flush_url is true, then any buffered information in the URL_IDX
* will be flushed.
*/
static int lhp_http_data_cb(http_parser* parser, int cb_id, const char* str, size_t len, int flush_url, int hfield) {
lhttp_parser* lparser = (lhttp_parser*)parser;
assert(NULL != lparser);
int buf_idx;
int* buf_len;

if ( cb_id != CB_ON_URL ) {
int result = lhp_flush_except(lparser, cb_id, flush_url, hfield);
if ( 0 != result ) return result;
}

/* flush on_url event. */
return lhp_flush_event(parser, CB_ON_URL, ST_URL_IDX, &(lparser->url_len));
if ( ! FLAG_HAS_CB(lparser->flags, cb_id) ) return 0;

if ( CB_ON_URL == cb_id ) {
buf_idx = ST_URL_IDX;
buf_len = &lparser->url_len;
} else {
buf_idx = ST_BUFFER_IDX;
buf_len = &lparser->buf_len;
}

return lhp_buffer(lparser, cb_id, buf_idx, buf_len, str, len, hfield);
}

static int lhp_http_no_buffer_cb(http_parser* parser, int cb_id, const char* str, size_t len) {
static int lhp_http_cb(http_parser* parser, int cb_id, int flush_url) {
lhttp_parser* lparser = (lhttp_parser*)parser;
lua_State* L;

assert(NULL != parser);
L = (lua_State*)parser->data;
assert(NULL != L);

/* push event callback function. */
if (FLAG_HAS_CB(lparser->flags, cb_id)) {
int result = lhp_flush_except(lparser, cb_id, flush_url, 0);
if ( 0 != result ) return result;

if ( ! lua_checkstack(L, 5) ) return -1;
if ( ! FLAG_HAS_CB(lparser->flags, cb_id) ) return 0;

lua_rawgeti(L, ST_FENV_IDX, cb_id);
lua_pushlstring(L, str, len);
}
return 0;
return lhp_push_nil_event(lparser, cb_id);
}

static int lhp_message_begin_cb(http_parser* parser) {
return lhp_http_cb(parser, CB_ON_MESSAGE_BEGIN);
return lhp_http_cb(parser, CB_ON_MESSAGE_BEGIN, 0);
}

static int lhp_url_cb(http_parser* parser, const char* str, size_t len) {
lhttp_parser* lparser = (lhttp_parser*)parser;
return lhp_buffer_data(parser, CB_ON_URL, ST_URL_IDX, &(lparser->url_len), str, len);
return lhp_http_data_cb(parser, CB_ON_URL, str, len, 0, 0);
}

static int lhp_path_cb(http_parser* parser, const char* str, size_t len) {
return lhp_http_data_cb(parser, CB_ON_PATH, str, len);
return lhp_http_data_cb(parser, CB_ON_PATH, str, len, 0, 0);
}

static int lhp_query_string_cb(http_parser* parser, const char* str, size_t len) {
return lhp_http_data_cb(parser, CB_ON_QUERY_STRING, str, len);
return lhp_http_data_cb(parser, CB_ON_QUERY_STRING, str, len, 0, 0);
}

static int lhp_fragment_cb(http_parser* parser, const char* str, size_t len) {
return lhp_http_data_cb(parser, CB_ON_FRAGMENT, str, len);
return lhp_http_data_cb(parser, CB_ON_FRAGMENT, str, len, 0, 0);
}

static int lhp_header_field_cb(http_parser* parser, const char* str, size_t len) {
/* make sure on_url event was flushed. */
int result = lhp_flush_url(parser);
if ( 0 != result ) return result;

return lhp_http_data_cb(parser, CB_ON_HEADER_FIELD, str, len);
return lhp_http_data_cb(parser, CB_ON_HEADER, str, len, 1, 0);
}

static int lhp_header_value_cb(http_parser* parser, const char* str, size_t len) {
return lhp_http_data_cb(parser, CB_ON_HEADER_VALUE, str, len);
return lhp_http_data_cb(parser, CB_ON_HEADER, str, len, 0, 1);
}

static int lhp_headers_complete_cb(http_parser* parser) {
return lhp_http_cb(parser, CB_ON_HEADERS_COMPLETE, 1);
}

static int lhp_body_cb(http_parser* parser, const char* str, size_t len) {
/* on_headers_complete did any flushing, so just push the cb */
lhttp_parser* lparser = (lhttp_parser*)parser;
/* make sure on_url event was flushed. */
int result = lhp_flush_url(parser);
if ( 0 != result ) return result;
lua_State* L = (lua_State*)lparser->parser.data;

/* flush last header event. */
result = lhp_flush_event(parser, lparser->cb_id, ST_BUFFER_IDX, &(lparser->buf_len));
if ( 0 != result ) return result;
if ( ! FLAG_HAS_CB(lparser->flags, CB_ON_BODY) ) return 0;

return lhp_http_cb(parser, CB_ON_HEADERS_COMPLETE);
}
if ( ! lua_checkstack(L, 5) ) return -1;

static int lhp_body_cb(http_parser* parser, const char* str, size_t len) {
return lhp_http_no_buffer_cb(parser, CB_ON_BODY, str, len);
lua_pushinteger(L, 2);
lua_rawgeti(L, ST_FENV_IDX, CB_ON_BODY);
lua_pushlstring(L, str, len);

return 0;
}

static int lhp_message_complete_cb(http_parser* parser) {
/* Send on_body(nil) message to comply with LTN12 */
int result = lhp_http_cb(parser, CB_ON_BODY);
int result = lhp_push_nil_event((lhttp_parser*)parser, CB_ON_BODY);
if ( 0 != result ) return result;

return lhp_http_cb(parser, CB_ON_MESSAGE_COMPLETE);
return lhp_http_cb(parser, CB_ON_MESSAGE_COMPLETE, 0);
}

static int lhp_init(lua_State* L, enum http_parser_type type) {
@@ -273,7 +390,6 @@ static int lhp_init(lua_State* L, enum http_parser_type type) {
/* Stack: callbacks, userdata */

lparser->flags = 0;
lparser->cb_id = 0;
lparser->buf_len = 0;
lparser->url_len = 0;

@@ -371,7 +487,8 @@ static int lhp_execute(lua_State* L) {
lua_pushnumber(L, result);
lua_replace(L, ST_LEN+1);
/* Transform the stack into a table: */
len = lua_gettop(L) - ST_URL_IDX;
len = lua_gettop(L) - ST_LEN;

return len;
}

@@ -420,19 +537,25 @@ static int lhp_status_code(lua_State* L) {
/* The execute method has a "lua based stub" so that callbacks
* can yield without having to apply the CoCo patch to Lua. */
static const char* lhp_execute_lua =
"local execute = ...\n"
"local function execute_lua(result, event_cb, chunk, ...)\n"
" if event_cb then\n"
/* call current event callback. */
" event_cb(chunk)\n"
/* handle next event from stack. */
" return execute_lua(result, ...)\n"
" end\n"
/* done no more events on stack. */
" return result\n"
"local c_execute = ...\n"
"local function execute(result, cnt, cb, arg1, arg2, ...)\n"
" if ( not cnt ) then\n"
" return result\n"
" end\n"
" if ( cnt == 3 ) then\n"
" cb(arg1, arg2)\n"
" return execute(result, ...)\n"
" end\n"
" if ( cnt == 2 ) then\n"
" cb(arg1)\n"
" return execute(result, arg2, ...)"
" end\n"
/* if ( cnt == 1 ) then */
" cb()\n"
" return execute(result, arg1, arg2, ...)\n"
"end\n"
"return function(...)\n"
" return execute_lua(execute(...))\n"
" return execute(c_execute(...))\n"
"end";
static void lhp_push_execute_fn(lua_State* L) {
#ifndef NDEBUG
@@ -63,13 +63,9 @@ function max_events_test()

local cbs = {}
local field_cnt = 0
local value_cnt = 0
function cbs.on_header_field(field)
function cbs.on_header(field, value)
field_cnt = field_cnt + 1
end
function cbs.on_header_value(value)
value_cnt = value_cnt + 1
end

local parser = lhp.request(cbs)
local input = table.concat(input_tbl)
@@ -82,8 +78,6 @@ function max_events_test()
-- handled gracefully... note that
ok(field_cnt < header_cnt and field_cnt > 1000,
"Expect " .. header_cnt .. " field events, got " .. field_cnt)
ok(value_cnt < header_cnt-1 and field_cnt > 1000,
"Expect " .. (header_cnt-1) .. " field events, got " .. value_cnt)

result = parser:execute(input)

@@ -220,7 +214,6 @@ function init_parser()
local reqs = {}
local cur = nil
local cb = {}
local header_field = nil

function cb.on_message_begin()
ok(cur == nil)
@@ -243,16 +236,9 @@ function init_parser()
table.insert(cur.body, value)
end

function cb.on_header_field(value)
ok(nil == header_field)
header_field = value
end

function cb.on_header_value(value)
ok(header_field ~= nil)
ok(cur.headers[header_field] == nil)
cur.headers[header_field] = value
header_field = nil
function cb.on_header(field, value)
ok(cur.headers[field] == nil)
cur.headers[field] = value
end

function cb.on_message_complete()