Merge commit '26773844e7bd57df1216bd74360a62ec2dc976e3' as 'src/deps/src/nginx_cookie_flag_module'

This commit is contained in:
Théophile Diot 2023-06-30 15:37:39 -04:00
commit 8973eb0290
4 changed files with 466 additions and 0 deletions

View File

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2017, Airis777
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,62 @@
The Nginx module for adding cookie flag
==========
[![License](http://img.shields.io/badge/license-BSD-brightgreen.svg)](https://github.com/Airis777/nginx_cookie_flag_module/blob/master/LICENSE)
The Nginx module for adding cookie flag
## Dependencies
* [nginx](http://nginx.org)
## Compatibility
* 1.11.x (last tested: 1.11.2)
Earlier versions is not tested.
## Installation
1. Clone the git repository.
```
git clone git://github.com:AirisX/nginx_cookie_flag_module.git
```
2. Add the module to the build configuration by adding
`--add-module=/path/to/nginx_cookie_flag_module`
or
`--add-dynamic-module=/path/to/nginx_cookie_flag_module`
3. Build the nginx binary.
4. Install the nginx binary.
## Synopsis
```Nginx
location / {
set_cookie_flag Secret HttpOnly secure SameSite;
set_cookie_flag * HttpOnly;
set_cookie_flag SessionID SameSite=Lax secure;
set_cookie_flag SiteToken SameSite=Strict;
}
```
## Description
This module for Nginx allows to set the flags "**HttpOnly**", "**secure**" and "**SameSite**" for cookies in the "*Set-Cookie*" response headers.
The register of letters for the flags doesn't matter as it will be converted to the correct value. The order of cookie declaration among multiple directives doesn't matter too.
It is possible to set a default value using symbol "*". In this case flags will be added to the all cookies if no other value for them is overriden.
## Directives
### set_cookie_flag
-| -
--- | ---
**Syntax** | **set_cookie_flag** \<cookie_name\|*\> [HttpOnly] [secure] [SameSite\|SameSite=[Lax\|Strict]];
**Default** | -
**Context** | server, location
Description: Add flag to desired cookie.
## Author
Anton Saraykin [<Airisenator@gmail.com>]

View File

@ -0,0 +1,12 @@
ngx_addon_name=ngx_http_cookie_flag_filter_module
if test -n "$ngx_module_link"; then
ngx_module_type=HTTP_FILTER
ngx_module_name=$ngx_addon_name
ngx_module_srcs="$ngx_addon_dir/$ngx_addon_name.c"
. auto/module
else
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $ngx_addon_name"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/$ngx_addon_name.c"
fi

View File

@ -0,0 +1,367 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#define NUM_FLAGS 3
#define MIN_ARGS 3
typedef struct {
ngx_str_t cookie_name;
ngx_flag_t httponly;
ngx_flag_t secure;
ngx_flag_t samesite;
ngx_flag_t samesite_lax;
ngx_flag_t samesite_strict;
} ngx_http_cookie_t;
typedef struct {
ngx_array_t *cookies;
} ngx_http_cookie_flag_filter_loc_conf_t;
static char *ngx_http_cookie_flag_filter_cmd(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_cookie_flag_filter_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_cookie_flag_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
static ngx_int_t ngx_http_cookie_flag_filter_init(ngx_conf_t *cf);
static ngx_int_t ngx_http_cookie_flag_filter_append(ngx_http_request_t *r, ngx_http_cookie_t *flag, ngx_table_elt_t *header);
static ngx_int_t ngx_http_cookie_flag_filter_handler(ngx_http_request_t *r);
static size_t ngx_char_pos(ngx_str_t str, u_char ch);
static u_char *ngx_get_arg_name(ngx_pool_t *pool, ngx_str_t src);
static size_t ngx_char_pos(ngx_str_t str, u_char ch) {
char *pos = NULL;
size_t ind = 0;
pos = strchr((char *) str.data, (int) ch);
if (pos != NULL) {
ind = (size_t) (pos - (char *) str.data);
}
return ind;
}
static u_char *ngx_get_arg_name(ngx_pool_t *pool, ngx_str_t src) {
u_char *dst;
size_t pos;
pos = ngx_char_pos(src, '=');
if(pos) {
dst = ngx_pnalloc(pool, pos + 1);
if (dst == NULL) {
return NULL;
}
ngx_memcpy(dst, src.data, pos);
return dst;
} else {
return src.data;
}
}
static ngx_command_t ngx_http_cookie_flag_filter_commands[] = {
/* set cookie flag directive */
{ ngx_string("set_cookie_flag"),
NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
ngx_http_cookie_flag_filter_cmd,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_cookie_flag_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_cookie_flag_filter_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_cookie_flag_filter_create_loc_conf, /* create location configuration */
ngx_http_cookie_flag_filter_merge_loc_conf /* merge location configuration */
};
ngx_module_t ngx_http_cookie_flag_filter_module = {
NGX_MODULE_V1,
&ngx_http_cookie_flag_filter_module_ctx, /* module context */
ngx_http_cookie_flag_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_output_header_filter_pt ngx_http_next_header_filter;
static char *
ngx_http_cookie_flag_filter_cmd(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_cookie_flag_filter_loc_conf_t *flcf = conf;
ngx_http_cookie_t *cookie, tmp;
ngx_str_t *value;
ngx_uint_t i, j;
value = cf->args->elts;
if (cf->args->nelts > (NUM_FLAGS + 2) || cf->args->nelts < MIN_ARGS) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "The number of arguments is incorrect");
return NGX_CONF_ERROR;
}
// check on duplication
if (cf->args->nelts > MIN_ARGS) {
for (i = MIN_ARGS; i < cf->args->nelts; i++) {
for (j = MIN_ARGS - 1; j < i; j++) {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0, "filter http_cookie_flag - comparasion \"%V\" and \"%V\"", &value[i], &value[j]);
u_char *first = ngx_get_arg_name(cf->pool, value[j]);
u_char *second = ngx_get_arg_name(cf->pool, value[i]);
if (ngx_strcasecmp(first, second) == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Duplicate flag \"%s\" (%V) detected", second, &value[i]);
return NGX_CONF_ERROR;
}
}
}
}
if (flcf->cookies == NULL) {
flcf->cookies = ngx_array_create(cf->pool, 1, sizeof(ngx_http_cookie_t));
if (flcf->cookies == NULL) {
return NGX_CONF_ERROR;
}
} else {
// check whether cookie name has already set
cookie = flcf->cookies->elts;
for (i = 0; i < flcf->cookies->nelts; i++) {
if (ngx_strncasecmp(cookie[i].cookie_name.data, value[1].data, value[1].len) == 0) {
return "The cookie value has already set in previous directives";
}
}
}
cookie = ngx_array_push(flcf->cookies);
if (cookie == NULL) {
return NGX_CONF_ERROR;
}
// set cookie name
cookie->cookie_name.data = value[1].data;
cookie->cookie_name.len = value[1].len;
cookie->httponly = 0;
cookie->secure = 0;
cookie->samesite = 0;
cookie->samesite_lax = 0;
cookie->samesite_strict = 0;
// normalize and check 2nd and 3rd parameters
for (i = 2; i < cf->args->nelts; i++) {
if (ngx_strncasecmp(value[i].data, (u_char *) "httponly", 8) == 0 && value[i].len == 8) {
cookie->httponly = 1;
} else if (ngx_strncasecmp(value[i].data, (u_char *) "secure", 6) == 0 && value[i].len == 6) {
cookie->secure = 1;
} else if (ngx_strncasecmp(value[i].data, (u_char *) "samesite", 8) == 0 && value[i].len == 8) {
cookie->samesite = 1;
} else if (ngx_strncasecmp(value[i].data, (u_char *) "samesite=lax", 12) == 0 && value[i].len == 12) {
cookie->samesite_lax = 1;
} else if (ngx_strncasecmp(value[i].data, (u_char *) "samesite=strict", 15) == 0 && value[i].len == 15) {
cookie->samesite_strict = 1;
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "The parameter value \"%V\" is incorrect", &value[i]);
return NGX_CONF_ERROR;
}
}
// move default settings to the end of array
cookie = flcf->cookies->elts;
for (i = 0; i < flcf->cookies->nelts; i++) {
if (ngx_strncasecmp(cookie[i].cookie_name.data, (u_char *) "*", 1) == 0 && i < flcf->cookies->nelts - 1) {
tmp = cookie[flcf->cookies->nelts - 1];
cookie[flcf->cookies->nelts - 1] = cookie[i];
cookie[i] = tmp;
}
}
return NGX_CONF_OK;
}
static void *
ngx_http_cookie_flag_filter_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_cookie_flag_filter_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_cookie_flag_filter_loc_conf_t));
if (conf == NULL) {
return NULL;
}
return conf;
}
static char *
ngx_http_cookie_flag_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_cookie_flag_filter_loc_conf_t *prev = parent;
ngx_http_cookie_flag_filter_loc_conf_t *conf = child;
if (conf->cookies == NULL) {
conf->cookies = prev->cookies;
}
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_cookie_flag_filter_init(ngx_conf_t *cf)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_cookie_flag_filter_handler;
return NGX_OK;
}
static ngx_int_t
ngx_http_cookie_flag_filter_append(ngx_http_request_t *r, ngx_http_cookie_t *cookie, ngx_table_elt_t *header)
{
ngx_str_t tmp;
if (cookie->httponly == 1 && ngx_strcasestrn(header->value.data, "; HttpOnly", 10 - 1) == NULL) {
tmp.data = ngx_pnalloc(r->pool, header->value.len + sizeof("; HttpOnly") - 1);
if (tmp.data == NULL) {
return NGX_ERROR;
}
tmp.len = ngx_sprintf(tmp.data, "%V; HttpOnly", &header->value) - tmp.data;
header->value.data = tmp.data;
header->value.len = tmp.len;
}
if (cookie->secure == 1 && ngx_strcasestrn(header->value.data, "; secure", 8 - 1) == NULL) {
tmp.data = ngx_pnalloc(r->pool, header->value.len + sizeof("; secure") - 1);
if (tmp.data == NULL) {
return NGX_ERROR;
}
tmp.len = ngx_sprintf(tmp.data, "%V; secure", &header->value) - tmp.data;
header->value.data = tmp.data;
header->value.len = tmp.len;
}
if (cookie->samesite == 1 && ngx_strcasestrn(header->value.data, "; SameSite", 10 - 1) == NULL) {
tmp.data = ngx_pnalloc(r->pool, header->value.len + sizeof("; SameSite") - 1);
if (tmp.data == NULL) {
return NGX_ERROR;
}
tmp.len = ngx_sprintf(tmp.data, "%V; SameSite", &header->value) - tmp.data;
header->value.data = tmp.data;
header->value.len = tmp.len;
}
if (cookie->samesite_lax == 1 && ngx_strcasestrn(header->value.data, "; SameSite=Lax", 14 - 1) == NULL) {
tmp.data = ngx_pnalloc(r->pool, header->value.len + sizeof("; SameSite=Lax") - 1);
if (tmp.data == NULL) {
return NGX_ERROR;
}
tmp.len = ngx_sprintf(tmp.data, "%V; SameSite=Lax", &header->value) - tmp.data;
header->value.data = tmp.data;
header->value.len = tmp.len;
}
if (cookie->samesite_strict == 1 && ngx_strcasestrn(header->value.data, "; SameSite=Strict", 17 - 1) == NULL) {
tmp.data = ngx_pnalloc(r->pool, header->value.len + sizeof("; SameSite=Strict") - 1);
if (tmp.data == NULL) {
return NGX_ERROR;
}
tmp.len = ngx_sprintf(tmp.data, "%V; SameSite=Strict", &header->value) - tmp.data;
header->value.data = tmp.data;
header->value.len = tmp.len;
}
return NGX_OK;
}
static ngx_int_t
ngx_http_cookie_flag_filter_handler(ngx_http_request_t *r)
{
ngx_http_cookie_flag_filter_loc_conf_t *flcf;
ngx_http_cookie_t *cookie;
ngx_uint_t i, j;
ngx_list_part_t *part;
ngx_table_elt_t *header;
flcf = ngx_http_get_module_loc_conf(r, ngx_http_cookie_flag_filter_module);
if (flcf->cookies == NULL) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "filter http_cookie_flag is disabled");
return ngx_http_next_header_filter(r);
}
cookie = flcf->cookies->elts;
if (flcf->cookies->nelts != 0) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "filter http_cookie_flag is enabled");
}
// Checking whether Set-Cookie header is present
part = &r->headers_out.headers.part;
header = part->elts;
for (i = 0; /* void */; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (ngx_strncasecmp(header[i].key.data, (u_char *) "set-cookie", 10) == 0) {
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "filter http_cookie_flag - before: \"%V: %V\"", &header[i].key, &header[i].value);
// for each security cookie we check whether preset it within Set-Cookie value. If not then we append.
for (j = 0; j < flcf->cookies->nelts; j++) {
if (ngx_strncasecmp(cookie[j].cookie_name.data, (u_char *) "*", 1) != 0) {
// append "=" to the security cookie name. The result will be something like "cookie_name="
char *cookie_name = ngx_pnalloc(r->pool, sizeof("=") - 1 + cookie[j].cookie_name.len);
if (cookie_name == NULL) {
return NGX_ERROR;
}
strcpy(cookie_name, (char *) cookie[j].cookie_name.data);
strcat(cookie_name, "=");
// if Set-Cookie contains a cookie from settings
if (ngx_strcasestrn(header[i].value.data, cookie_name, strlen(cookie_name) - 1) != NULL) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "filter http_cookie_flag - add flags for cookie \"%V\"", &cookie[j].cookie_name);
ngx_int_t res = ngx_http_cookie_flag_filter_append(r, &cookie[j], &header[i]);
if (res != NGX_OK) {
return NGX_ERROR;
}
break; // otherwise default value will be added
}
} else if (ngx_strncasecmp(cookie[j].cookie_name.data, (u_char *) "*", 1) == 0) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "filter http_cookie_flag - add default cookie flags");
ngx_int_t res = ngx_http_cookie_flag_filter_append(r, &cookie[j], &header[i]);
if (res != NGX_OK) {
return NGX_ERROR;
}
}
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "filter http_cookie_flag - after: \"%V: %V\"", &header[i].key, &header[i].value);
}
}
return ngx_http_next_header_filter(r);
}