lua/http: read trailer fields from Lua

Header functions in the Lua http library read the header tables and the raw header
buffer. Once trailer fields moved out of those, a script could no longer see them at
all, and nothing replaced the access: the only way to act on a trailer was a rule.

Add the trailer twins over the tables and blocks the http.trailer buffers use:
tx:request_trailer(), tx:request_trailers(), tx:request_trailers_raw() and the response
versions. Name lookup is case insensitive and reports a miss as an error, like the
header functions. Repeated fields need no merging here: the parser folds them while
storing, so a name has one entry.

HTTP/2 is out of reach of these functions: the Lua http library is driven by htp, so it
has no HTTP/2 transaction to read.

Ticket: #8256.
pull/16485/head
Victor Julien 7 days ago
parent 4241dae1ad
commit 82177e0eec

@ -41,12 +41,8 @@ HTTP is transaction based, and the current transaction must be obtained before u
print(err)
end
All other functions are methods on the transaction table.
.. note::
The header functions below cover the header section only. HTTP trailer fields are not
reachable from Lua; match them with the ``http.trailer`` buffers in a rule instead.
All other functions are methods on the transaction table. Header functions cover the
header section; trailer fields have the functions at the end of this page.
Transaction Methods
~~~~~~~~~~~~~~~~~~~
@ -177,6 +173,49 @@ Example::
http_response_headers = tx:response_headers()
print(http_response_headers)
``request_trailer()``
^^^^^^^^^^^^^^^^^^^^^
Get the value of an HTTP request trailer field by name, or nil and an error if the
transaction has no field of that name. Names are matched case insensitively.
Example::
local tx = http.get_tx()
local value, err = tx:request_trailer("X-Trailed")
``response_trailer()``
^^^^^^^^^^^^^^^^^^^^^^
Get the value of an HTTP response trailer field by name.
``request_trailers()``
^^^^^^^^^^^^^^^^^^^^^^
Get all HTTP request trailer fields as a table.
Example::
local tx = http.get_tx()
for name, value in pairs(tx:request_trailers()) do
print(name, value)
end
``response_trailers()``
^^^^^^^^^^^^^^^^^^^^^^^
Get all HTTP response trailer fields as a table.
``request_trailers_raw()``
^^^^^^^^^^^^^^^^^^^^^^^^^^
Get the raw HTTP request trailer block, as it appeared on the wire.
``response_trailers_raw()``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Get the raw HTTP response trailer block, as it appeared on the wire.
``request_body()``
^^^^^^^^^^^^^^^^^^

@ -65,11 +65,14 @@ Major Changes
``http.header_names`` or a field keyword such as ``http.user_agent`` stops matching
and needs one of the trailer buffers. This also closes a hole where a trailer could
extend a header value that detection and logging had already accepted.
For HTTP/1 they also drop out of the ``http.request_headers`` and
``http.response_headers`` EVE arrays; no EVE key for trailer fields exists yet.
The Lua http library loses them the same way: ``tx:request_header()``,
``tx:request_headers()``, ``tx:request_headers_raw()`` and the response twins read the
header tables and the raw header buffer, and that library has no trailer accessor.
EVE output still covers them: with ``dump-all-headers`` set they appear in the
``http.request_headers`` and ``http.response_headers`` arrays as entries of their own,
instead of being appended to the value of a header field that shares their name. So a name
used in both blocks is listed twice, once per block. No EVE key for trailer fields exists
yet.
The Lua http library has accessors of its own for them now: ``tx:request_trailer()``,
``tx:request_trailers()``, ``tx:request_trailers_raw()`` and the response twins, next to
the header functions that keep reading the header section only.
- A HTTP/1 header block spanning several packets is no longer re-inspected while it is still
arriving: a rule on ``http.header`` or ``http.header.raw`` now alerts once per transaction

@ -251,6 +251,116 @@ static int LuaHttpGetResponseHeaders(lua_State *luastate)
return LuaHttpGetHeaders(luastate, 1);
}
/* Trailers live in their own table now that they are kept out of the headers. Repeated
* fields are merged while parsing, so a name has a single entry. */
static int LuaHttpGetTrailer(lua_State *luastate, int dir)
{
struct LuaTx *tx = luaL_testudata(luastate, 1, htp_tx);
if (tx == NULL) {
lua_pushnil(luastate);
return 1;
}
/* since arg was added at last, it must be on top of the stack */
const char *name = LuaGetStringArgument(luastate, lua_gettop(luastate));
if (name == NULL) {
return LuaCallbackError(luastate, "argument missing, empty or wrong type");
}
const htp_headers_t *table =
dir ? htp_tx_response_trailers(tx->tx) : htp_tx_request_trailers(tx->tx);
const size_t nlen = strlen(name);
const size_t count = table != NULL ? htp_headers_size(table) : 0;
for (size_t i = 0; i < count; i++) {
const htp_header_t *h = htp_headers_get_index(table, i);
if ((size_t)htp_header_name_len(h) == nlen &&
strncasecmp((const char *)htp_header_name_ptr(h), name, nlen) == 0) {
return LuaPushStringBuffer(luastate, htp_header_value_ptr(h), htp_header_value_len(h));
}
}
return LuaCallbackError(luastate, "trailer not found");
}
static int LuaHttpGetRequestTrailer(lua_State *luastate)
{
return LuaHttpGetTrailer(luastate, 0 /* request */);
}
static int LuaHttpGetResponseTrailer(lua_State *luastate)
{
return LuaHttpGetTrailer(luastate, 1 /* response */);
}
static int LuaHttpGetRawTrailers(lua_State *luastate, int dir)
{
struct LuaTx *tx = luaL_testudata(luastate, 1, htp_tx);
if (tx == NULL) {
lua_pushnil(luastate);
return 1;
}
HtpTxUserData *htud = (HtpTxUserData *)htp_tx_get_user_data(tx->tx);
uint8_t *raw = htud->request_trailers_raw;
uint32_t raw_len = htud->request_trailers_raw_len;
if (dir == 1) {
raw = htud->response_trailers_raw;
raw_len = htud->response_trailers_raw_len;
}
if (raw == NULL || raw_len == 0)
return LuaCallbackError(luastate, "no raw trailers");
return LuaPushStringBuffer(luastate, raw, raw_len);
}
static int LuaHttpGetRawRequestTrailers(lua_State *luastate)
{
return LuaHttpGetRawTrailers(luastate, 0);
}
static int LuaHttpGetRawResponseTrailers(lua_State *luastate)
{
return LuaHttpGetRawTrailers(luastate, 1);
}
static int LuaHttpGetTrailers(lua_State *luastate, int dir)
{
struct LuaTx *tx = luaL_testudata(luastate, 1, htp_tx);
if (tx == NULL) {
lua_pushnil(luastate);
return 1;
}
const htp_headers_t *table =
dir ? htp_tx_response_trailers(tx->tx) : htp_tx_request_trailers(tx->tx);
if (table == NULL) {
lua_pushnil(luastate);
return 1;
}
lua_newtable(luastate);
const size_t count = htp_headers_size(table);
for (size_t i = 0; i < count; i++) {
const htp_header_t *h = htp_headers_get_index(table, i);
LuaPushStringBuffer(luastate, htp_header_name_ptr(h), htp_header_name_len(h));
LuaPushStringBuffer(luastate, htp_header_value_ptr(h), htp_header_value_len(h));
lua_settable(luastate, -3);
}
return 1;
}
/** \brief return request trailers as lua table */
static int LuaHttpGetRequestTrailers(lua_State *luastate)
{
return LuaHttpGetTrailers(luastate, 0);
}
/** \brief return response trailers as lua table */
static int LuaHttpGetResponseTrailers(lua_State *luastate)
{
return LuaHttpGetTrailers(luastate, 1);
}
static int LuaHttpGetBody(lua_State *luastate, int dir)
{
struct LuaTx *tx = luaL_testudata(luastate, 1, htp_tx);
@ -319,6 +429,12 @@ static const struct luaL_Reg txlib[] = {
{"request_uri_normalized", LuaHttpGetRequestUriNormalized},
{"request_headers", LuaHttpGetRequestHeaders},
{"response_headers", LuaHttpGetResponseHeaders},
{"request_trailer", LuaHttpGetRequestTrailer},
{"response_trailer", LuaHttpGetResponseTrailer},
{"request_trailers", LuaHttpGetRequestTrailers},
{"response_trailers", LuaHttpGetResponseTrailers},
{"request_trailers_raw", LuaHttpGetRawRequestTrailers},
{"response_trailers_raw", LuaHttpGetRawResponseTrailers},
{"request_host", LuaHttpGetRequestHost},
{"request_body", LuaHttpGetRequestBody},
{"response_body", LuaHttpGetResponseBody},

Loading…
Cancel
Save