]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: hlua: Add function to change the body length of an HTTP Message
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 16 May 2025 12:10:31 +0000 (14:10 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 16 May 2025 12:34:12 +0000 (14:34 +0200)
There was no function for a lua filter to change the body length of an HTTP
Message. But it is mandatory to be able to alter the message payload. It is
not possible update to directly update the message headers because the
internal state of the message must also be updated accordingly.

It is the purpose of HTTPMessage.set_body_len() function. The new body
length myst be passed as argument. If it is an integer, the right
"Content-Length" header is set. If the "chunked" string is used, it forces
the message to be chunked-encoded and in that case the "Transfer-Encoding"
header.

This patch should fix the issue #2837. It could be backported as far as 2.6.

doc/lua-api/index.rst
src/hlua.c

index a1261f64841477199872ae1ea971dba1fbef6b06..b9edce62b9b89c592790739fad9914466c15236c 100644 (file)
@@ -4607,6 +4607,27 @@ HTTPMessage class
    data by default.
   :returns: an integer containing the amount of bytes copied or -1.
 
+.. js:function:: HTTPMessage.set_body_len(http_msg, length)
+
+  This function changes the expected payload length of the HTTP message
+  **http_msg**. **length** can be an integer value. In that case, a
+  "Content-Length" header is added with the given value. It is also possible to
+  pass the **"chunked"** string instead of an integer value to force the HTTP
+  message to be chunk-encoded. In that case, a "Transfer-Encoding" header is
+  added with the "chunked" value. In both cases, all existing "Content-Length"
+  and "Transfer-Encoding" headers are removed.
+
+  This fnuction should be used in the filter context to be able to alter the
+  payload of the HTTP message. The internal state fo the HTTP message is updated
+  accordingly. :js:func:`HTTPMessage.add_header()` or
+  :js:func:`HTTPMessage.set_header()` functions must to be used in that case.
+
+  :param class_httpmessage http_msg: The manipulated HTTP message.
+  :param type length: The new payload length to set. It can be an integer or
+                     the string "chunked".
+  :returns: true if the payload length was successfully updated, false
+           otherwise.
+
 .. js:function:: HTTPMessage.set_eom(http_msg)
 
   This function set the end of message for the HTTP message **http_msg**.
index f5ea3f3bc17aefcfd3f6cefd06caa72a3a039ec0..9bb1fe930613b7fe859e946f400cbddce426715a 100644 (file)
@@ -7183,6 +7183,93 @@ __LJMP static int hlua_http_msg_set_status(lua_State *L)
        return 1;
 }
 
+/* Change the body length. Accepts a positive integer or the string "chunked". */
+__LJMP static int hlua_http_msg_set_body_len(lua_State *L)
+{
+       struct http_msg *msg;
+       struct htx *htx;
+       struct htx_sl *sl;
+       int type;
+
+       MAY_LJMP(check_args(L, 2, "set_body_len"));
+       msg = MAY_LJMP(hlua_checkhttpmsg(L, 1));
+       if (msg->msg_state > HTTP_MSG_BODY)
+               WILL_LJMP(luaL_error(L, "The 'set_body_len' function cannot be called during the message forwarding"));
+       htx = htxbuf(&msg->chn->buf);
+       sl = ASSUME_NONNULL(http_get_stline(htx));
+       type = lua_type(L, 2);
+       if (type == LUA_TSTRING) {
+               const char *str = lua_tostring(L, 2);
+               struct http_hdr_ctx ctx;
+
+               if (strcmp(str, "chunked") != 0)
+                       goto error;
+
+               /* Already chunked, nothing to do */
+               if (msg->flags & HTTP_MSGF_TE_CHNK)
+                       goto success;
+
+               /* add "Transfer-Encoding: chunked" header */
+               if (!http_add_header(htx, ist("Transfer-Encoding"), ist("chunked")))
+                       goto failure;
+               msg->flags |= (HTTP_MSGF_VER_11|HTTP_MSGF_XFER_LEN|HTTP_MSGF_TE_CHNK);
+               sl->flags |= (HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN|HTX_SL_F_XFER_ENC|HTX_SL_F_CHNK);
+
+               /* remove any Content-Length header */
+               if (msg->flags & HTTP_MSGF_CNT_LEN) {
+                       ctx.blk = NULL;
+                       while (http_find_header(htx, ist("Content-Length"), &ctx, 1))
+                               http_remove_header(htx, &ctx);
+                       msg->flags &= ~HTTP_MSGF_CNT_LEN;
+                       sl->flags &= ~HTX_SL_F_CLEN;
+               }
+       }
+       else if (type == LUA_TNUMBER) {
+               const char *clen;
+               ssize_t len;
+               struct http_hdr_ctx ctx;
+
+               len = lua_tointeger(L, 2);
+               if (len < 0)
+                       goto error;
+               clen = ultoa(len);
+
+               /* remove any Content-Length header */
+               if (msg->flags & HTTP_MSGF_CNT_LEN) {
+                       ctx.blk = NULL;
+                       while (http_find_header(htx, ist("Content-Length"), &ctx, 1))
+                               http_remove_header(htx, &ctx);
+               }
+
+               /* Now add Content-Length header */
+               if (!http_add_header(htx, ist("Content-Length"), ist(clen)))
+                       goto failure;
+               msg->flags |= (HTTP_MSGF_VER_11|HTTP_MSGF_XFER_LEN|HTTP_MSGF_CNT_LEN);
+               sl->flags |= (HTX_SL_F_VER_11|HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
+
+               /* remove any Transfer-Encoding header */
+               if (msg->flags & HTTP_MSGF_TE_CHNK) {
+                       ctx.blk = NULL;
+                       while (http_find_header(htx, ist("Transfer-Encoding"), &ctx, 1))
+                               http_remove_header(htx, &ctx);
+                       msg->flags &= ~HTTP_MSGF_TE_CHNK;
+                       sl->flags &= ~(HTX_SL_F_XFER_ENC|HTX_SL_F_CHNK);
+               }
+       }
+       else {
+         error:
+               WILL_LJMP(luaL_error(L, "The 'set_body_len' function expects a positive integer or the string 'chunked' as argument."));
+       }
+
+  success:
+       lua_pushboolean(L, 1);
+       return 1;
+
+  failure:
+       lua_pushboolean(L, 0);
+       return 1;
+}
+
 /* Returns true if the HTTP message is full. */
 __LJMP static int hlua_http_msg_is_full(lua_State *L)
 {
@@ -14321,6 +14408,7 @@ lua_State *hlua_init_state(int thread_num)
        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, "set_body_len",hlua_http_msg_set_body_len);
        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);