Add new cookie into headers_in

Hello,
I have a filter module in rewrite phase. The module tests if a cookie is
in the request. If not the module generates one (if cookie is found the
module modifies it) and then send it back to the client via
headers_out.
But I need one more functionality. I want the generated/modified cookie
to be sent to the proxy module. I have used the headers_more_module for
this purpose so far and it worked perfectly. I generate an
$ap_filter_cookie_sid variable in my module that contains the cookie. I
would like my module to be able to modify headers_in on its own.
I have managed to modify existing cookie in the headers_in.headers. But
when no cookie is in headers_in.headers my module is not able to store
newly generated cookie to headers_in.headers. It seems OK in my module
but before running proxy module the Nginx stops processing the request.
When the first request contains a cookie the Nginx server processes it
correctly and each next request (also withnou cookie) as well. This
behaviour is a bit confusing and I have no idea how to fix it.

Can anybody help me?
Thank you, Michal

Complete code:

typedef u_char *(*ngx_http_ap_filter_op_run_pt) (ngx_http_request_t *r,
u_char *buf,
ngx_http_ap_filter_op_t *op);

typedef size_t (*ngx_http_ap_filter_op_getlen_pt) (ngx_http_request_t
*r, uintptr_t data);
#define COOKIE_EXPIRES_TIME 2592000

typedef struct {
ngx_str_t cookie_sid;
ngx_str_t sessionid;
} ngx_http_ap_filter_ctx_t;

typedef struct {
ngx_flag_t enable;
ngx_str_t sessionid;
} ngx_http_ap_filter_loc_conf_t;

static ngx_int_t ngx_http_ap_filter_add_variables(ngx_conf_t *cf);
static ngx_int_t
ngx_http_ap_filter_cookie_sid_variable(ngx_http_request_t *r,
ngx_http_variable_value_t v, uintptr_t data);
static void
ngx_http_ap_filter_create_loc_conf(ngx_conf_t *cf);
static ngx_int_t ngx_http_ap_filter_init(ngx_conf_t cf);
static char
ngx_http_ap_filter_merge_loc_conf(ngx_conf_t *cf, void
*parent, void *child);
static ngx_int_t ngx_http_ap_filter(ngx_http_request_t *r,
ngx_http_ap_filter_ctx_t *ctx);
static ngx_int_t ngx_http_ap_filter_handler(ngx_http_request_t *r);

static ngx_int_t ngx_http_ap_filter_get_cookie(ngx_http_request_t *r,
ngx_str_t *cookie_value);
static ngx_int_t ngx_http_ap_filter_set_cookie(ngx_http_request_t r,
ngx_str_t
cookie_value, ngx_http_ap_filter_ctx_t *ctx);

static ngx_command_t ngx_http_ap_filter_commands[] = {
{ ngx_string(“ap_filter”),

NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_ap_filter_loc_conf_t, enable),
NULL },
{ ngx_string(“ap_filter_sessionid”),

NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_ap_filter_loc_conf_t, sessionid),
NULL },
ngx_null_command
};

static ngx_http_module_t ngx_http_ap_filter_module_ctx = {
ngx_http_ap_filter_add_variables, /* preconfiguration /
ngx_http_ap_filter_init, /
postconfiguration /
NULL, /
create main configuration /
NULL, /
init main configuration /
NULL, /
create server configuration /
NULL, /
merge server configuration /
ngx_http_ap_filter_create_loc_conf, /
create location
configuration /
ngx_http_ap_filter_merge_loc_conf /
merge location configuration
*/
};

ngx_module_t ngx_http_ap_filter_module = {
NGX_MODULE_V1,
&ngx_http_ap_filter_module_ctx,/* module context /
ngx_http_ap_filter_commands, /
module directives /
NGX_HTTP_MODULE, /
module type /
NULL, /
init master /
NULL, /
init module /
NULL, /
init process /
NULL, /
init thread /
NULL, /
exit thread /
NULL, /
exit process /
NULL, /
exit master */
NGX_MODULE_V1_PADDING
};

static ngx_http_variable_t ngx_http_ap_filter_vars[] = {
// $ap_mod_cookie_sid - modified/generated cookie
{ ngx_string(“ap_filter_cookie_sid”), NULL,
ngx_http_ap_filter_cookie_sid_variable, 0,

NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0
},

{ ngx_null_string, NULL, NULL, 0, 0, 0 }

};

static ngx_int_t ngx_http_ap_filter_add_variables(ngx_conf_t *cf) {
ngx_http_variable_t *var, *v;

for (v = ngx_http_ap_filter_vars; v->name.len; v++) {
    var = ngx_http_add_variable(cf, &v->name, v->flags);
    if (var == NULL) {
        return NGX_ERROR;
    }

    var->get_handler = v->get_handler;
    var->data = v->data;
}

return NGX_OK;

}

static ngx_int_t
ngx_http_ap_filter_cookie_sid_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data) {
ngx_http_ap_filter_ctx_t *ctx;

ctx = reinterpret_cast(ngx_http_get_module_ctx(r,

ngx_http_ap_filter_module));

if (ctx == NULL) {
    v->not_found = 1;
    return NGX_OK;
}

v->len = ctx->cookie_sid.len;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = ctx->cookie_sid.data;

return NGX_OK;

}

static void *ngx_http_ap_filter_create_loc_conf(ngx_conf_t *cf) {
ngx_http_ap_filter_loc_conf_t *conf;

conf = reinterpret_cast(ngx_pcalloc(cf->pool,

sizeof(ngx_http_ap_filter_loc_conf_t)));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
conf->enable = NGX_CONF_UNSET_UINT;
return conf;
}

static char *ngx_http_ap_filter_merge_loc_conf(ngx_conf_t *cf, void
*parent, void *child) {
ngx_http_ap_filter_loc_conf_t *prev = reinterpret_cast(parent);
ngx_http_ap_filter_loc_conf_t *conf = reinterpret_cast(child);

ngx_conf_merge_value(conf->enable, prev->enable, 0);
ngx_conf_merge_str_value(conf->sessionid, prev->sessionid, "url");
return NGX_CONF_OK;

}

static ngx_int_t ngx_http_ap_filter_init(ngx_conf_t *cf) {
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;

cmcf = reinterpret_cast(ngx_http_conf_get_module_main_conf(cf,

ngx_http_core_module));
h =
reinterpret_cast(ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers));
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_ap_filter_handler;

return NGX_OK;

}

static ngx_int_t ngx_http_ap_filter_handler(ngx_http_request_t *r) {
ngx_int_t rc;
ngx_http_ap_filter_ctx_t *ctx;
ctx = reinterpret_cast(ngx_http_get_module_ctx(r,
ngx_http_ap_filter_module));
if (ctx == NULL) {
ctx = reinterpret_cast(ngx_pcalloc(r->pool,
sizeof(ngx_http_ap_filter_module)));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

    ngx_http_set_ctx(r, ctx, ngx_http_ap_filter_module);
}
rc = ngx_http_ap_filter(r, ctx);
if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {
    return rc;
}
if (rc == NGX_AGAIN) {
    return NGX_DONE;
}
if (rc != NGX_OK) {
    return rc;
}
return NGX_DECLINED;

}

static ngx_int_t ngx_http_ap_filter_get_cookie(ngx_http_request_t *r,
ngx_str_t *cookie_value) {
ngx_int_t n;
ngx_str_t cookie_key;
cookie_key.len = sizeof(“sid”)-1;
ngx_str_set(&cookie_key, “sid”);
n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies,
&cookie_key, cookie_value);
if (n != NGX_DECLINED) {
// ziskal jsem cookie
return NGX_OK;
} else {
return NGX_DECLINED;
}
}

static ngx_int_t ngx_http_ap_filter_set_cookie(ngx_http_request_t *r,
ngx_str_t *cookie_value,
ngx_http_ap_filter_ctx_t *ctx) {
u_char *cookie, *p;
size_t len;
ngx_table_elt_t *set_cookie;

u_char               expires[] = "; expires=Thu, 31-Dec-37 23:55:55

GMT";
len = 250;
cookie = reinterpret_cast(ngx_pnalloc(r->pool, len));
// vlozime nazev cookie - sid
p = ngx_copy(cookie, “sid”, 3);
*p++ = ‘=’;
p = ngx_cpymem(p, cookie_value->data, cookie_value->len);
p = ngx_cpymem(p, expires, sizeof(“; expires=”) - 1);
p = ngx_http_cookie_time(p, ngx_time() + COOKIE_EXPIRES_TIME);

set_cookie =

reinterpret_cast(ngx_list_push(&r->headers_out.headers));

 if (set_cookie == NULL) {
    return NGX_ERROR;
}
set_cookie->hash = 1;
ngx_str_set(&set_cookie->key, "Set-Cookie");
set_cookie->value.len = p - cookie;
set_cookie->value.data = cookie;
ctx->cookie_sid = set_cookie->value;

// HEADERS_IN SECTION:
const u_char cookie_key[] = "COOKIE";
ngx_table_elt_t *set_cookie_in;
u_char           ch;
ngx_uint_t        i,j;

ngx_list_part_t *part = &(r->headers_in.headers.part);
ngx_table_elt_t *header = reinterpret_cast(part->elts);
set_cookie_in = NULL;
// find cookies in headers_in.headers
for (i = 0; ; ++i) {
    if (i >= part->nelts) {
        if (part->next == NULL) {
            break;
        }
        part = part->next;
        header = reinterpret_cast(part->elts);
        i = 0;
    }
    if (header[i].key.len != (sizeof(cookie_key) - 1)) {
        continue;
    }

    for (j = 0; j < (sizeof(cookie_key) - 1); ++j) {
        ch = header[i].key.data[j];
        if (ch >= 'a' && ch <= 'z') ch &= ~0x20;
        if (ch != cookie_key[j]) break;
    }
    if (j == (sizeof(cookie_key) - 1)) {
        set_cookie_in = &header[i];
    }
}
// cookie not found !!!!!!!!!!
if (set_cookie_in == NULL) {


    set_cookie_in =

reinterpret_cast(ngx_list_push(&(r->headers_in.headers)));
if (set_cookie_in == NULL) {
return 0;
}
set_cookie_in->hash = 1;
ngx_str_set(&set_cookie_in->key, “Cookie”);
set_cookie_in->value.len = p - cookie;
set_cookie_in->value.data = cookie;
// cookie found - everything OK
} else {
set_cookie_in->value.len = p - cookie;
set_cookie_in->value.data = cookie;
}

ngx_pfree(r->pool, cookie);

return NGX_OK;

}

static ngx_int_t ngx_http_ap_filter(ngx_http_request_t *r,
ngx_http_ap_filter_ctx_t *ctx) {
ngx_http_ap_filter_loc_conf_t *cglcf;
ngx_str_t cookie_value;

cglcf = reinterpret_cast(ngx_http_get_module_loc_conf(r,

ngx_http_ap_filter_module));

if (cglcf->enable != 0) {
    ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,

“ap_filter enable”);
} else {
ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
“ap_filter disable”);
return NGX_OK;
}

// a C++ object for processinf the cookie
ApCookie_t cookieSid;
if (ngx_http_ap_filter_get_cookie(r, &cookie_value) == NGX_DECLINED)

{
cookieSid.createCookie(reinterpret_cast(cglcf->salt.data),
cglcf->salt.len,
reinterpret_cast(r->args.data), r->args.len);
} else {
cookieSid = ApCookie_t(reinterpret_cast(cookie_value.data),
cookie_value.len,
reinterpret_cast(cglcf->salt.data), cglcf->salt.len,
reinterpret_cast(r->args.data), r->args.len);
} // else

ngx_str_t cookieString;
ngx_str_set(&cookieString,

cookieSid.returnCookie(&(cookieString.len)));

ngx_int_t n = ngx_http_ap_filter_set_cookie(r, &cookieString, ctx);

if (n != NGX_OK) {
    return NGX_ERROR;

}

return NGX_OK;

}

Posted at Nginx Forum:

Hello!

On Mon, Jan 24, 2011 at 10:07:04AM -0500, michalkraus wrote:

I have managed to modify existing cookie in the headers_in.headers. But
when no cookie is in headers_in.headers my module is not able to store
newly generated cookie to headers_in.headers. It seems OK in my module
but before running proxy module the Nginx stops processing the request.
When the first request contains a cookie the Nginx server processes it
correctly and each next request (also withnou cookie) as well. This
behaviour is a bit confusing and I have no idea how to fix it.

From architecture point of view, you should never modify
r->headers_in. Instead, you should provide module output as
variable and use proxy_set_header to pass it to backend.

More specifically, modification of r->headers_in will fail and
cause SIGSEGV at least for subrequests even if done perfectly
correctly. Don’t do that.

Maxim D.

Thanks,
I have already read this recommendation in another topic. But
headers_more_module can modify header_in.headers and I wanted to do it
as well. I will just modify headers_out and I will provide the cookie as
a variable and use proxy_set_header. So I will avoid SIGSEGV.
One more question about reading value of a variable in my module. I have
output as variable $ap_filter_gsid from my module and I need to get the
value in another handler module.

Code in my module seems like:

{ ngx_string(“ap_filter_sessionid”),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_ap_filter_loc_conf_t, sessionid),
NULL },

There is string “$ap_filter_gsid” in the variable sessionid in
ngx_http_ap_filter_loc_conf_t struct and not the read value of the
variable. I need the same for reading $arg_u from query string. I went
through some module codes and I found out that I should write my
function instead of ngx_conf_set_str_slot directive. I tried to write
it, but it has never worked. What should be in this function.

Thanks for your advice,
Michal

Posted at Nginx Forum:

Hello!

On Tue, Jan 25, 2011 at 03:34:21AM -0500, michalkraus wrote:

Thanks,
I have already read this recommendation in another topic. But
headers_more_module can modify header_in.headers and I wanted to do it

The fact that some third-party module does this doesn’t mean that
it’s correct.

ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_ap_filter_loc_conf_t, sessionid),
NULL },

There is string “$ap_filter_gsid” in the variable sessionid in
ngx_http_ap_filter_loc_conf_t struct and not the read value of the
variable. I need the same for reading $arg_u from query string. I went
through some module codes and I found out that I should write my
function instead of ngx_conf_set_str_slot directive. I tried to write
it, but it has never worked. What should be in this function.

Yes, to allow variable expansion you should take care of it. For
single variable you may use ngx_http_get_variable_index() during
configuration and ngx_http_get_indexed_variable() at runtime.

If you want to allow arbitrary mix of static strings and variables -
you may want to use complex values instead. It’s probably easiest
interface, too, as there is generic configuration handler
ngx_http_set_complex_value_slot available. At runtime use
ngx_http_complex_value() to obtain expanded value.

Maxim D.

On Wed, Jan 26, 2011 at 12:31 PM, agentzh [email protected] wrote:

a right way :slight_smile: I won’t say that my hack there is not flawless, but I’m
open to any concrete test cases that can make it fail :wink:

Re michalkraus: if you want to do that, just check ngx_headers_more’s
source code. If it fails for you, just send a mail to me or file a bug
for my module :slight_smile:

Sorry, it has nothing to do with ngx_headers_more, it’s only related
to the ngx_http_subrequest() function in the nginx core. My hack was
not in the ngx_headers_more module, but in ngx_lua and ngx_echo. These
modules adjust the subrequest struct when issuing a subrequest. My
current way is not perfectly correct:

/* XXX work-around a bug in ngx_http_subrequest */
if (r->headers_in.headers.last == &r->headers_in.headers.part) {
    sr->headers_in.headers.last = &sr->headers_in.headers.part;
}

I should have checked the implementation of ngx_list more carefully
and come up with a saner way to work around that bug. I haven’t met
any issues yet but my gut feeling is that it’s still not perfect. I’ll
look at this closer later.

In short, whether it is valid to modify the input headers depends only
on how subrequests inherit their parents’ input headers, no?

Maxim D., you may not agree that it is a bug in
ngx_http_subrequest, but I do. IMHO, saying flatly that something
cannot be done just because how it is implemented is boring and no
fun.

Cheers,
-agentzh

On Tue, Jan 25, 2011 at 8:54 PM, Maxim D. [email protected]
wrote:

I have already read this recommendation in another topic. But
headers_more_module can modify header_in.headers and I wanted to do it

The fact that some third-party module does this doesn’t mean that
it’s correct.

The ngx_headers_more module does use some tricks to get around the bug
that nginx does not copy the input headers struct into subrequests’ in
a right way :slight_smile: I won’t say that my hack there is not flawless, but I’m
open to any concrete test cases that can make it fail :wink:

Re michalkraus: if you want to do that, just check ngx_headers_more’s
source code. If it fails for you, just send a mail to me or file a bug
for my module :slight_smile:

Cheers,
-agentzh

Thanks a lot for your time.
Now, my module provides the data for cookie as a variable and
proxy_set_header just sets the cookie. Sending a response to client via
headers_out is OK.
I have managed to get the value of the cookie variable in my another
module. I used ngx_http_get_variable_index() for getting the index of
variable in configuration section and then at runtime
ngx_http_get_indexed_variable() for getting the value.

Michal

Posted at Nginx Forum:

}

Another pitfall with ngx_http_subrequest is that subrequests share
the whole variable value cache (i.e., r->variables) by default. That
means, if a subrequest modifies a variable, it will also affects the
parent, and vice versa. This often leads to very confusing
side-effects and bad results (which is very similar to how evil global
variables can be ;)). So I explicitly disable this behavior in the
subrequests issued by ngx_lua, ngx_echo, and ngx_srcache :slight_smile:

Cheers,
-agentzh