diff --git a/include/haproxy/hlua-t.h b/include/haproxy/hlua-t.h index 2c6cbd811..44f20d804 100644 --- a/include/haproxy/hlua-t.h +++ b/include/haproxy/hlua-t.h @@ -42,6 +42,7 @@ #define CLASS_SOCKET "Socket" #define CLASS_CHANNEL "Channel" #define CLASS_HTTP "HTTP" +#define CLASS_HTTP_MSG "HTTPMessage" #define CLASS_MAP "Map" #define CLASS_APPLET_TCP "AppletTCP" #define CLASS_APPLET_HTTP "AppletHTTP" diff --git a/src/hlua.c b/src/hlua.c index 25a3e519a..c1a578ed4 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -259,6 +259,7 @@ static int class_channel_ref; static int class_fetches_ref; static int class_converters_ref; static int class_http_ref; +static int class_http_msg_ref; static int class_map_ref; static int class_applet_tcp_ref; static int class_applet_http_ref; @@ -5502,6 +5503,39 @@ static int hlua_http_new(lua_State *L, struct hlua_txn *txn) return 1; } +/* This function creates and returns an array containing the status-line + * elements. This function does not fails. + */ +__LJMP static int hlua_http_get_stline(lua_State *L, struct htx_sl *sl) +{ + /* Create the table. */ + lua_newtable(L); + + if (sl->flags & HTX_SL_F_IS_RESP) { + lua_pushstring(L, "version"); + lua_pushlstring(L, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); + lua_settable(L, -3); + lua_pushstring(L, "code"); + lua_pushlstring(L, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); + lua_settable(L, -3); + lua_pushstring(L, "reason"); + lua_pushlstring(L, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); + lua_settable(L, -3); + } + else { + lua_pushstring(L, "method"); + lua_pushlstring(L, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); + lua_settable(L, -3); + lua_pushstring(L, "uri"); + lua_pushlstring(L, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); + lua_settable(L, -3); + lua_pushstring(L, "version"); + lua_pushlstring(L, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); + lua_settable(L, -3); + } + return 1; +} + /* This function creates ans returns an array of HTTP headers. * This function does not fails. It is used as wrapper with the * 2 following functions. @@ -5863,6 +5897,981 @@ static int hlua_http_res_set_status(lua_State *L) return 0; } +/* + * + * + * Class HTTPMessage + * + * + */ + +/* Returns a struct http_msg if the stack entry "ud" is a class HTTPMessage, + * otherwise it throws an error. + */ +__LJMP static struct http_msg *hlua_checkhttpmsg(lua_State *L, int ud) +{ + return MAY_LJMP(hlua_checkudata(L, ud, class_http_msg_ref)); +} + +/* Creates and pushes on the stack a HTTP object according with a current TXN. + */ +static __maybe_unused int hlua_http_msg_new(lua_State *L, struct http_msg *msg) +{ + /* Check stack size. */ + if (!lua_checkstack(L, 3)) + return 0; + + lua_newtable(L); + lua_pushlightuserdata(L, msg); + lua_rawseti(L, -2, 0); + + /* Create the "channel" field that contains the request channel object. */ + lua_pushstring(L, "channel"); + if (!hlua_channel_new(L, msg->chn)) + return 0; + lua_rawset(L, -3); + + /* Pop a class stream metatable and affect it to the table. */ + lua_rawgeti(L, LUA_REGISTRYINDEX, class_http_msg_ref); + lua_setmetatable(L, -2); + + return 1; +} + +/* Helper function returning a filter attached to the HTTP message at the + * position in the stack, filling the current offset and length of the + * filter. If no filter is attached, NULL is returned and and are + * filled with output and input length respectively. + */ +static struct filter *hlua_http_msg_filter(lua_State *L, int ud, struct http_msg *msg, size_t *offset, size_t *len) +{ + struct channel *chn = msg->chn; + struct htx *htx = htxbuf(&chn->buf); + struct filter *filter = NULL; + + *offset = co_data(msg->chn); + *len = htx->data - co_data(msg->chn); + + if (lua_getfield(L, ud, "__filter") == LUA_TLIGHTUSERDATA) { + filter = lua_touserdata (L, -1); + if (msg->msg_state >= HTTP_MSG_DATA) { + struct hlua_flt_ctx *flt_ctx = filter->ctx; + + *offset = flt_ctx->cur_off[CHN_IDX(chn)]; + *len = flt_ctx->cur_len[CHN_IDX(chn)]; + } + } + + lua_pop(L, 1); + return filter; +} + +/* Returns true if the channel attached to the HTTP message is the response + * channel. + */ +__LJMP static int hlua_http_msg_is_resp(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 1, "is_resp")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + lua_pushboolean(L, !!(msg->chn->flags & CF_ISRESP)); + return 1; +} + +/* Returns an array containing the elements status-line of the HTTP message. It relies + * on hlua_http_get_stline(). + */ +__LJMP static int hlua_http_msg_get_stline(lua_State *L) +{ + struct http_msg *msg; + struct htx *htx; + struct htx_sl *sl; + + MAY_LJMP(check_args(L, 1, "get_stline")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + htx = htxbuf(&msg->chn->buf); + sl = http_get_stline(htx); + if (!sl) + return 0; + return hlua_http_get_stline(L, sl); +} + +/* Returns an array containing all headers of the HTTP message. it relies on + * hlua_http_get_headers(). + */ +__LJMP static int hlua_http_msg_get_headers(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 1, "get_headers")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + return hlua_http_get_headers(L, msg); +} + +/* Deletes all occurrences of an header in the HTTP message matching on its + * name. It relies on hlua_http_del_hdr(). + */ +__LJMP static int hlua_http_msg_del_hdr(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 2, "del_header")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + return hlua_http_del_hdr(L, msg); +} + +/* Matches the full value line of all occurences of an header in the HTTP + * message given its name against a regex and replaces it if it matches. It + * relies on hlua_http_rep_hdr(). + */ +__LJMP static int hlua_http_msg_rep_hdr(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 4, "rep_header")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + return hlua_http_rep_hdr(L, msg, 1); +} + +/* Matches all comma-separated values of all occurences of an header in the HTTP + * message given its name against a regex and replaces it if it matches. It + * relies on hlua_http_rep_hdr(). + */ +__LJMP static int hlua_http_msg_rep_val(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 4, "rep_value")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + return hlua_http_rep_hdr(L, msg, 0); +} + +/* Add an header in the HTTP message. It relies on hlua_http_add_hdr() */ +__LJMP static int hlua_http_msg_add_hdr(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 3, "add_header")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + return hlua_http_add_hdr(L, msg); +} + +/* Add an header in the HTTP message removing existing headers with the same + * name. It relies on hlua_http_del_hdr() and hlua_http_add_hdr(). + */ +__LJMP static int hlua_http_msg_set_hdr(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 3, "set_header")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + hlua_http_del_hdr(L, msg); + return hlua_http_add_hdr(L, msg); +} + +/* Rewrites the request method. It relies on http_req_replace_stline(). */ +__LJMP static int hlua_http_msg_set_meth(lua_State *L) +{ + struct stream *s; + struct http_msg *msg; + const char *name; + size_t name_len; + + MAY_LJMP(check_args(L, 2, "set_method")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); + + if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + s = chn_strm(msg->chn); + lua_pushboolean(L, http_req_replace_stline(0, name, name_len, s->be, s) != -1); + return 1; +} + +/* Rewrites the request path. It relies on http_req_replace_stline(). */ +__LJMP static int hlua_http_msg_set_path(lua_State *L) +{ + struct stream *s; + struct http_msg *msg; + const char *name; + size_t name_len; + + MAY_LJMP(check_args(L, 2, "set_path")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); + + if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + s = chn_strm(msg->chn); + lua_pushboolean(L, http_req_replace_stline(1, name, name_len, s->be, s) != -1); + return 1; +} + +/* Rewrites the request query-string. It relies on http_req_replace_stline(). */ +__LJMP static int hlua_http_msg_set_query(lua_State *L) +{ + struct stream *s; + struct http_msg *msg; + const char *name; + size_t name_len; + + MAY_LJMP(check_args(L, 2, "set_query")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); + + if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + /* Check length. */ + if (name_len > trash.size - 1) { + lua_pushboolean(L, 0); + return 1; + } + + /* Add the mark question as prefix. */ + chunk_reset(&trash); + trash.area[trash.data++] = '?'; + memcpy(trash.area + trash.data, name, name_len); + trash.data += name_len; + + s = chn_strm(msg->chn); + lua_pushboolean(L, http_req_replace_stline(2, trash.area, trash.data, s->be, s) != -1); + return 1; +} + +/* Rewrites the request URI. It relies on http_req_replace_stline(). */ +__LJMP static int hlua_http_msg_set_uri(lua_State *L) +{ + struct stream *s; + struct http_msg *msg; + const char *name; + size_t name_len; + + MAY_LJMP(check_args(L, 2, "set_uri")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + name = MAY_LJMP(luaL_checklstring(L, 2, &name_len)); + + if ((msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + s = chn_strm(msg->chn); + lua_pushboolean(L, http_req_replace_stline(3, name, name_len, s->be, s) != -1); + return 1; +} + +/* Rewrites the response status code. It relies on http_res_set_status(). */ +__LJMP static int hlua_http_msg_set_status(lua_State *L) +{ + struct http_msg *msg; + unsigned int code; + const char *reason; + size_t reason_len; + + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + code = MAY_LJMP(luaL_checkinteger(L, 2)); + reason = MAY_LJMP(luaL_optlstring(L, 3, NULL, &reason_len)); + + if (!(msg->chn->flags & CF_ISRESP) || msg->msg_state > HTTP_MSG_BODY) + WILL_LJMP(lua_error(L)); + + lua_pushboolean(L, http_res_set_status(code, ist2(reason, reason_len), chn_strm(msg->chn)) != -1); + return 1; +} + +/* Returns true if the HTTP message is full. */ +__LJMP static int hlua_http_msg_is_full(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 1, "is_full")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + lua_pushboolean(L, channel_full(msg->chn, 0)); + return 1; +} + +/* Returns true if the HTTP message may still receive data. */ +__LJMP static int hlua_http_msg_may_recv(lua_State *L) +{ + struct http_msg *msg; + struct htx *htx; + + MAY_LJMP(check_args(L, 1, "may_recv")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + htx = htxbuf(&msg->chn->buf); + lua_pushboolean(L, (htx_expect_more(htx) && !channel_input_closed(msg->chn) && channel_may_recv(msg->chn))); + return 1; +} + +/* Returns true if the HTTP message EOM was received */ +__LJMP static int hlua_http_msg_is_eom(lua_State *L) +{ + struct http_msg *msg; + struct htx *htx; + + MAY_LJMP(check_args(L, 1, "may_recv")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + htx = htxbuf(&msg->chn->buf); + lua_pushboolean(L, !htx_expect_more(htx)); + return 1; +} + +/* Returns the number of bytes available in the input side of the HTTP + * message. This function never fails. + */ +__LJMP static int hlua_http_msg_get_in_len(lua_State *L) +{ + struct http_msg *msg; + struct htx *htx; + + MAY_LJMP(check_args(L, 1, "input")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + htx = htxbuf(&msg->chn->buf); + lua_pushinteger(L, htx->data - co_data(msg->chn)); + return 1; +} + +/* Returns the number of bytes available in the output side of the HTTP + * message. This function never fails. + */ +__LJMP static int hlua_http_msg_get_out_len(lua_State *L) +{ + struct http_msg *msg; + + MAY_LJMP(check_args(L, 1, "output")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + lua_pushinteger(L, co_data(msg->chn)); + return 1; +} + +/* Copies at most bytes of DATA blocks from the HTTP message + * starting at the offset and put it in a string LUA variables. It + * returns the length of the builded string. It stops on the first non-DATA HTX + * block. This function is called during the payload filtering, so the headers + * are already scheduled for output (from the filter point of view). + */ +static int _hlua_http_msg_dup(struct http_msg *msg, lua_State *L, size_t offset, size_t len) +{ + struct htx *htx = htxbuf(&msg->chn->buf); + struct htx_blk *blk; + struct htx_ret htxret; + luaL_Buffer b; + int ret = 0; + + luaL_buffinit(L, &b); + htxret = htx_find_offset(htx, offset); + for (blk = htxret.blk, offset = htxret.ret; blk && len; blk = htx_get_next_blk(htx, blk)) { + enum htx_blk_type type = htx_get_blk_type(blk); + struct ist v; + + switch (type) { + case HTX_BLK_UNUSED: + break; + + case HTX_BLK_DATA: + v = htx_get_blk_value(htx, blk); + v.ptr += offset; + v.len -= offset; + if (v.len > len) + v.len = len; + + luaL_addlstring(&b, v.ptr, v.len); + ret += v.len; + break; + + default: + if (!ret) { + /* Remove the empty string and push nil on the stack */ + lua_pop(L, 1); + lua_pushnil(L); + } + goto end; + } + offset = 0; + } + + luaL_pushresult(&b); + +end: + return ret; +} + +/* Copies the string to the HTTP message at the offset + * . This function returns -1 if data cannot be copied. Otherwise, it + * returns the amount of data written. This function is responsibile to update + * the filter context. + */ +static int _hlua_http_msg_insert(struct http_msg *msg, struct filter *filter, struct ist str, size_t offset) +{ + struct htx *htx = htx_from_buf(&msg->chn->buf); + struct htx_ret htxret; + int /*max, */ret = 0; + + /* Nothing to do, just return */ + if (unlikely(istlen(str) == 0)) + goto end; + + if (istlen(str) > htx_free_data_space(htx)) { + ret = -1; + goto end; + } + + htxret = htx_find_offset(htx, offset); + if (!htxret.blk || htx_get_blk_type(htxret.blk) != HTX_BLK_DATA) { + if (!htx_add_last_data(htx, str)) + goto end; + } + else { + struct ist v = htx_get_blk_value(htx, htxret.blk); + v.ptr += htxret.ret; + v.len = 0; + if (!htx_replace_blk_value(htx, htxret.blk, v, str)) + goto end; + } + ret = str.len; + if (ret) { + struct hlua_flt_ctx *flt_ctx = filter->ctx; + flt_update_offsets(filter, msg->chn, ret); + flt_ctx->cur_len[CHN_IDX(msg->chn)] += ret; + } + + end: + htx_to_buf(htx, &msg->chn->buf); + return ret; +} + +/* Helper function removing at most bytes of DATA blocks at the absolute + * position . It stops on the first non-DATA HTX block. This function is + * called during the payload filtering, so the headers are already scheduled for + * output (from the filter point of view). This function is responsibile to + * update the filter context. + */ +static void _hlua_http_msg_delete(struct http_msg *msg, struct filter *filter, size_t offset, size_t len) +{ + struct hlua_flt_ctx *flt_ctx = filter->ctx; + struct htx *htx = htx_from_buf(&msg->chn->buf); + struct htx_blk *blk; + struct htx_ret htxret; + size_t ret = 0; + + /* Be sure is always the amount of DATA to remove */ + if (htx->data == offset+len && htx_get_tail_type(htx) == HTX_BLK_DATA) { + htx_truncate(htx, offset); + ret = len; + goto end; + } + + htxret = htx_find_offset(htx, offset); + blk = htxret.blk; + if (htxret.ret) { + struct ist v; + + if (htx_get_blk_type(blk) != HTX_BLK_DATA) + goto end; + v = htx_get_blk_value(htx, blk); + v.ptr += htxret.ret; + if (v.len > len) + v.len = len; + blk = htx_replace_blk_value(htx, blk, v, ist2(NULL, 0)); + len -= v.len; + ret += v.len; + } + + + while (blk && len) { + enum htx_blk_type type = htx_get_blk_type(blk); + uint32_t sz = htx_get_blksz(blk); + + switch (type) { + case HTX_BLK_UNUSED: + break; + + case HTX_BLK_DATA: + if (len < sz) { + htx_cut_data_blk(htx, blk, len); + ret += len; + goto end; + } + break; + + default: + goto end; + } + + /* Remove oll the data block */ + len -= sz; + ret += sz; + blk = htx_remove_blk(htx, blk); + } + +end: + flt_update_offsets(filter, msg->chn, -ret); + flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; + /* WARNING: we don't call htx_to_buf() on purpose, because we don't want + * to loose the EOM flag if the message is empty. + */ +} + +/* Copies input data found in an HTTP message. Unlike the channel function used + * to duplicate raw data, this one can only be called inside a filter, from + * http_payload callback. So it cannot yield. An exception is returned if it is + * called from another callback. If nothing was copied, a nil value is pushed on + * the stack. + */ +__LJMP static int hlua_http_msg_get_body(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + size_t output, input; + int offset, len; + + if (lua_gettop(L) < 1 || lua_gettop(L) > 3) + WILL_LJMP(luaL_error(L, "'data' expects at most 2 arguments")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + filter = hlua_http_msg_filter(L, 1, msg, &output, &input); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + if (!ci_data(msg->chn) && channel_input_closed(msg->chn)) { + lua_pushnil(L); + return 1; + } + + offset = output; + if (lua_gettop(L) > 1) { + offset = MAY_LJMP(luaL_checkinteger(L, 2)); + if (offset < 0) + offset = MAX(0, input + offset); + offset += output; + if (offset < output || offset > input + output) { + lua_pushfstring(L, "offset out of range."); + WILL_LJMP(lua_error(L)); + } + } + len = output + input - offset; + if (lua_gettop(L) == 3) { + len = MAY_LJMP(luaL_checkinteger(L, 3)); + if (!len) + goto dup; + if (len == -1) + len = global.tune.bufsize; + if (len < 0) { + lua_pushfstring(L, "length out of range."); + WILL_LJMP(lua_error(L)); + } + } + + dup: + _hlua_http_msg_dup(msg, L, offset, len); + return 1; +} + +/* Appends a string to the HTTP message, after all existing DATA blocks but + * before the trailers, if any. It returns the amount of data written or -1 if + * nothing was copied. Unlike the channel function used to append data, this one + * can only be called inside a filter, from http_payload callback. So it cannot + * yield. An exception is returned if it is called from another callback. + */ +__LJMP static int hlua_http_msg_append(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + const char *str; + size_t offset, len, sz; + int ret; + + MAY_LJMP(check_args(L, 2, "append")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); + filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset+len); + lua_pushinteger(L, ret); + return 1; +} + +/* Prepends a string to the HTTP message, before all existing DATA blocks. It + * returns the amount of data written or -1 if nothing was copied. Unlike the + * channel function used to prepend data, this one can only be called inside a + * filter, from http_payload callback. So it cannot yield. An exception is + * returned if it is called from another callback. + */ +__LJMP static int hlua_http_msg_prepend(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + const char *str; + size_t offset, len, sz; + int ret; + + MAY_LJMP(check_args(L, 2, "prepend")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); + filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); + lua_pushinteger(L, ret); + return 1; +} + +/* Inserts a string to the HTTP message at a given offset. By default the string + * is appended at the end of DATA blocks. It returns the amount of data written + * or -1 if nothing was copied. Unlike the channel function used to insert data, + * this one can only be called inside a filter, from http_payload callback. So + * it cannot yield. An exception is returned if it is called from another + * callback. + */ +__LJMP static int hlua_http_msg_insert_data(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + const char *str; + size_t input, output, sz; + int offset; + int ret; + + if (lua_gettop(L) < 2 || lua_gettop(L) > 3) + WILL_LJMP(luaL_error(L, "'insert' expects at least 1 argument and at most 2 arguments")); + MAY_LJMP(check_args(L, 2, "insert")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); + filter = hlua_http_msg_filter(L, 1, msg, &input, &output); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + offset = input + output; + if (lua_gettop(L) > 2) { + offset = MAY_LJMP(luaL_checkinteger(L, 3)); + if (offset < 0) + offset = MAX(0, input + offset); + offset += output; + + if (offset < output || offset > output + input) { + lua_pushfstring(L, "offset out of range."); + WILL_LJMP(lua_error(L)); + } + } + + ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); + lua_pushinteger(L, ret); + return 1; +} + +/* Removes a given amount of data from the HTTP message at a given offset. By + * default all DATA blocks are removed. It returns the amount of data + * removed. Unlike the channel function used to remove data, this one can only + * be called inside a filter, from http_payload callback. So it cannot yield. An + * exception is returned if it is called from another callback. + */ +__LJMP static int hlua_http_msg_del_data(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + size_t input, output; + int offset, len; + + if (lua_gettop(L) < 1 || lua_gettop(L) > 3) + WILL_LJMP(luaL_error(L, "'insert' expects at most 2 arguments")); + MAY_LJMP(check_args(L, 2, "insert")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + filter = hlua_http_msg_filter(L, 1, msg, &input, &output); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + offset = input + output; + if (lua_gettop(L) > 2) { + offset = MAY_LJMP(luaL_checkinteger(L, 3)); + if (offset < 0) + offset = MAX(0, input + offset); + offset += output; + + if (offset < output || offset > output + input) { + lua_pushfstring(L, "offset out of range."); + WILL_LJMP(lua_error(L)); + } + } + + len = output + input - offset; + if (lua_gettop(L) == 4) { + len = MAY_LJMP(luaL_checkinteger(L, 4)); + if (!len) + goto end; + if (len == -1) + len = output + input - offset; + if (len < 0 || offset + len > output + input) { + lua_pushfstring(L, "length out of range."); + WILL_LJMP(lua_error(L)); + } + } + + _hlua_http_msg_delete(msg, filter, offset, len); + + end: + lua_pushinteger(L, len); + return 1; +} + +/* Replaces a given amount of data at the given offet by a string. By default, + * all remaining data are removed, accordingly to the filter context. It returns + * the amount of data written or -1 if nothing was copied. Unlike the channel + * function used to replace data, this one can only be called inside a filter, + * from http_payload callback. So it cannot yield. An exception is returned if + * it is called from another callback. + */ +__LJMP static int hlua_http_msg_set_data(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + struct htx *htx; + const char *str; + size_t input, output, sz; + int offset, len; + int ret; + + if (lua_gettop(L) < 2 || lua_gettop(L) > 4) + WILL_LJMP(luaL_error(L, "'set' expects at least 1 argument and at most 3 arguments")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); + filter = hlua_http_msg_filter(L, 1, msg, &output, &input); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + offset = output; + if (lua_gettop(L) > 2) { + offset = MAY_LJMP(luaL_checkinteger(L, 3)); + if (offset < 0) + offset = MAX(0, input + offset); + offset += output; + if (offset < output || offset > input + output) { + lua_pushfstring(L, "offset out of range."); + WILL_LJMP(lua_error(L)); + } + } + + len = output + input - offset; + if (lua_gettop(L) == 4) { + len = MAY_LJMP(luaL_checkinteger(L, 4)); + if (!len) + goto set; + if (len == -1) + len = output + input - offset; + if (len < 0 || offset + len > output + input) { + lua_pushfstring(L, "length out of range."); + WILL_LJMP(lua_error(L)); + } + } + + set: + /* Be sure we can copied the string once input data will be removed. */ + htx = htx_from_buf(&msg->chn->buf); + if (sz > htx_free_data_space(htx) + len) + lua_pushinteger(L, -1); + else { + _hlua_http_msg_delete(msg, filter, offset, len); + ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); + lua_pushinteger(L, ret); + } + return 1; +} + +/* Prepends data into an HTTP message and forward it, from the filter point of + * view. It returns the amount of data written or -1 if nothing was sent. Unlike + * the channel function used to send data, this one can only be called inside a + * filter, from http_payload callback. So it cannot yield. An exception is + * returned if it is called from another callback. + */ +__LJMP static int hlua_http_msg_send(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + struct htx *htx; + const char *str; + size_t offset, len, sz; + int ret; + + MAY_LJMP(check_args(L, 2, "send")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + str = MAY_LJMP(luaL_checklstring(L, 2, &sz)); + filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + /* Return an error if the channel's output is closed */ + if (unlikely(channel_output_closed(msg->chn))) { + lua_pushinteger(L, -1); + return 1; + } + + htx = htx_from_buf(&msg->chn->buf); + if (sz > htx_free_data_space(htx)) { + lua_pushinteger(L, -1); + return 1; + } + + ret = _hlua_http_msg_insert(msg, filter, ist2(str, sz), offset); + if (ret > 0) { + struct hlua_flt_ctx *flt_ctx = filter->ctx; + + FLT_OFF(filter, msg->chn) += ret; + flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; + flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret; + } + + lua_pushinteger(L, ret); + return 1; +} + +/* Forwards a given amount of bytes. It return -1 if the channel's output is + * closed. Otherwise, it returns the number of bytes forwarded. Unlike the + * channel function used to forward data, this one can only be called inside a + * filter, from http_payload callback. So it cannot yield. An exception is + * returned if it is called from another callback. All other functions deal with + * DATA block, this one not. +*/ +__LJMP static int hlua_http_msg_forward(lua_State *L) +{ + struct http_msg *msg; + struct filter *filter; + size_t offset, len; + int fwd, ret = 0; + + MAY_LJMP(check_args(L, 2, "forward")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + + if (msg->msg_state < HTTP_MSG_DATA) + WILL_LJMP(lua_error(L)); + + fwd = MAY_LJMP(luaL_checkinteger(L, 2)); + filter = hlua_http_msg_filter(L, 1, msg, &offset, &len); + if (!filter || !hlua_filter_from_payload(filter)) + WILL_LJMP(lua_error(L)); + + /* Nothing to do, just return */ + if (!fwd) + goto end; + + /* Return an error if the channel's output is closed */ + if (unlikely(channel_output_closed(msg->chn))) { + ret = -1; + goto end; + } + + ret = fwd; + if (ret > len) + ret = len; + + if (ret) { + struct hlua_flt_ctx *flt_ctx = filter->ctx; + + FLT_OFF(filter, msg->chn) += ret; + flt_ctx->cur_off[CHN_IDX(msg->chn)] += ret; + flt_ctx->cur_len[CHN_IDX(msg->chn)] -= ret; + } + + end: + lua_pushinteger(L, ret); + return 1; +} + +/* Set EOM flag on the HTX message. + * + * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't + * really know how to do without this feature. + */ +__LJMP static int hlua_http_msg_set_eom(lua_State *L) +{ + struct http_msg *msg; + struct htx *htx; + + MAY_LJMP(check_args(L, 1, "set_eom")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + htx = htxbuf(&msg->chn->buf); + htx->flags |= HTX_FL_EOM; + return 0; +} + +/* Unset EOM flag on the HTX message. + * + * NOTE: Not sure it is a good idea to manipulate this flag but for now I don't + * really know how to do without this feature. + */ +__LJMP static int hlua_http_msg_unset_eom(lua_State *L) +{ + struct http_msg *msg; + struct htx *htx; + + MAY_LJMP(check_args(L, 1, "set_eom")); + msg = MAY_LJMP(hlua_checkhttpmsg(L, 1)); + htx = htxbuf(&msg->chn->buf); + htx->flags &= ~HTX_FL_EOM; + return 0; +} + /* * * @@ -10343,6 +11352,55 @@ lua_State *hlua_init_state(int thread_num) /* Register previous table in the registry with reference and named entry. */ class_http_ref = hlua_register_metatable(L, CLASS_HTTP); + /* + * + * Register class HTTPMessage + * + */ + + /* Create and fill the metatable. */ + lua_newtable(L); + + /* Create and fille the __index entry. */ + lua_pushstring(L, "__index"); + lua_newtable(L); + + /* Register Lua functions. */ + hlua_class_function(L, "is_resp", hlua_http_msg_is_resp); + hlua_class_function(L, "get_stline", hlua_http_msg_get_stline); + hlua_class_function(L, "get_headers", hlua_http_msg_get_headers); + hlua_class_function(L, "del_header", hlua_http_msg_del_hdr); + hlua_class_function(L, "rep_header", hlua_http_msg_rep_hdr); + hlua_class_function(L, "rep_value", hlua_http_msg_rep_val); + hlua_class_function(L, "add_header", hlua_http_msg_add_hdr); + hlua_class_function(L, "set_header", hlua_http_msg_set_hdr); + hlua_class_function(L, "set_method", hlua_http_msg_set_meth); + hlua_class_function(L, "set_path", hlua_http_msg_set_path); + hlua_class_function(L, "set_query", hlua_http_msg_set_query); + hlua_class_function(L, "set_uri", hlua_http_msg_set_uri); + hlua_class_function(L, "set_status", hlua_http_msg_set_status); + hlua_class_function(L, "is_full", hlua_http_msg_is_full); + hlua_class_function(L, "may_recv", hlua_http_msg_may_recv); + hlua_class_function(L, "eom", hlua_http_msg_is_eom); + hlua_class_function(L, "input", hlua_http_msg_get_in_len); + hlua_class_function(L, "output", hlua_http_msg_get_out_len); + + hlua_class_function(L, "body", hlua_http_msg_get_body); + hlua_class_function(L, "set", hlua_http_msg_set_data); + hlua_class_function(L, "remove", hlua_http_msg_del_data); + hlua_class_function(L, "append", hlua_http_msg_append); + hlua_class_function(L, "prepend", hlua_http_msg_prepend); + hlua_class_function(L, "insert", hlua_http_msg_insert_data); + hlua_class_function(L, "set_eom", hlua_http_msg_set_eom); + hlua_class_function(L, "unset_eom", hlua_http_msg_unset_eom); + + hlua_class_function(L, "send", hlua_http_msg_send); + hlua_class_function(L, "forward", hlua_http_msg_forward); + + lua_rawset(L, -3); + + /* Register previous table in the registry with reference and named entry. */ + class_http_msg_ref = hlua_register_metatable(L, CLASS_HTTP_MSG); /* * * Register class AppletTCP