wrk icon indicating copy to clipboard operation
wrk copied to clipboard

wrk response headers doesn't handle duplicate headers

Open erickt opened this issue 10 years ago • 5 comments

wrk doesn't seem to handle duplicated response headers like Set-Cookie correctly. This makes it difficult to test services that require multiple cookies, like a session cookie and a CSRF cookie in order to login to a service.

erickt avatar Aug 05 '15 23:08 erickt

Thanks for the issue report @erickt! Currently wrk overwrites each entry in the headers table with the last header seen. Should be easy to fix, although there will be a performance cost.

wg avatar Aug 07 '15 03:08 wg

+1 Is there a plan to support multiple cookies?

xianjimli avatar Sep 02 '17 10:09 xianjimli

I have a hack to fix this (didn't have the time to fix the problem from the root): In script.c, function void script_response(), which processes response headers, in case it's a Set-Cookie header I changed the header name to Set-CookiX when X is a running number (Set-Cooki1, Set-Cooki2...), so now the cookie headers are all distinct in name. This is the modified loop at the beginning of the function:

    for (char *c = headers->buffer; c < headers->cursor; ) {
        if (strcmp(c, "Set-Cookie") == 0) { /* New line */
            cookie_count++; /* New line */
            c[9] = cookie_count + '0'; /* New line */
        } /* New line */
        c = buffer_pushlstring(L, c);
        c = buffer_pushlstring(L, c);
        lua_rawset(L, -3);
    }

You need to recreate the wrk executable by running make. Of course you need some code to handle these headers and make them real cookies again, so I handled it in a lua script in the response hook:

response = function(status, headers, body)
    wrk.headers["Cookie"] = ''
    for key, value in pairs(headers) do
        if string.starts(key, "Set-Cooki") then
            wrk.headers["Cookie"] = wrk.headers["Cookie"] .. string.sub(value, 0, string.find(value, ";") - 1) .. ';'
        end
    end
    cookie = true
end

This concatenates the custom Set-CookiX headers into a standard Cookie header. This solution works great for my use case, but it's a dirty hack, a very dirty hack. I just didn't have time to change the underlying data structures that hold the headers and make them support duplicate names... Hope this helps.

njenia avatar Oct 29 '17 08:10 njenia

Thanks for the issue report @erickt! Currently wrk overwrites each entry in the headers table with the last header seen. Should be easy to fix, although there will be a performance cost.

hi @wg , is this plan already implemented? It would be nice if we have feature for duplicated header responds.

Thank you

bayupermadi avatar Dec 10 '19 02:12 bayupermadi

Inspired by the workaround of @njenia , I further modified the function void script_response() in script.c so that the cookies are stored in a sub-table named Set-Cookie (side-bonus: there can be more than 9) :

void script_response(lua_State *L, int status, buffer *headers, buffer *body) {
    lua_getglobal(L, "response");
    lua_pushinteger(L, status);
    lua_newtable(L); // parent headers table

    int cookie_idx = 1;

    for (char *c = headers->buffer; c < headers->cursor; ) {
        if (strcmp(c, "Set-Cookie") == 0) {
            if (cookie_idx == 1) { // create the cookies table
                // create table
                buffer_pushlstring(L, c); // -2, the name of the new table "Set-Cookie"
                lua_newtable(L); // -1, the new table
                lua_settable(L, -3); // tell the parent table in -3 to save the new table in -1 under the name "Set-Cookie" in -2
            }
            
            // get the cookie table
            c = buffer_pushlstring(L, c);
            lua_gettable(L, -2);

            // save the cookie
            lua_pushnumber(L, cookie_idx++); // -2, push the cookie index
            c = buffer_pushlstring(L, c); // -1, push the cookie value
            lua_settable(L, -3); // save the cookie

            lua_pop(L, 1); // go back to the headers table
        }
        else {
            c = buffer_pushlstring(L, c);
            c = buffer_pushlstring(L, c);
            lua_rawset(L, -3);
        }
    }

    lua_pushlstring(L, body->buffer, body->cursor - body->buffer);
    lua_call(L, 3, 0);

    buffer_reset(headers);
    buffer_reset(body);
}

Hope it help someone !

rsegretain avatar Feb 17 '25 09:02 rsegretain