Skip to content

Commit

Permalink
Add option for set cache policy based on cookie name or pattern
Browse files Browse the repository at this point in the history
Fixes tempesta-tech#1544

Signed-off-by: Aleksey Mikhaylov <[email protected]>
  • Loading branch information
ttaym committed Feb 15, 2022
1 parent 25a420c commit c71c876
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 108 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ Module.symvers
.settings
.pydevproject

# vscode project settings
.vscode

# Python compiler cache files
*.pyc

Expand Down
2 changes: 2 additions & 0 deletions fw/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ enum {
TFW_HTTP_FLAGS_REQ,
/* Sticky cookie is found and verified. */
TFW_HTTP_B_HAS_STICKY = TFW_HTTP_FLAGS_REQ,
/* Request calculated as fitting some caching by cookie rule */
TFW_HTTP_B_CACHE_CALC,
/* Request is non-idempotent. */
TFW_HTTP_B_NON_IDEMP,
/* Request stated 'Accept: text/html' header */
Expand Down
227 changes: 204 additions & 23 deletions fw/http_match.c
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ match_hdr_raw(const TfwHttpReq *req, const TfwHttpMatchRule *rule)
static bool
match_hdr(const TfwHttpReq *req, const TfwHttpMatchRule *rule)
{
tfw_http_hdr_t id = rule->hid;
tfw_http_hdr_t id = rule->val.hid;

BUG_ON(id < 0);
if (id == TFW_HTTP_HDR_RAW)
Expand Down Expand Up @@ -408,6 +408,57 @@ match_mark(const TfwHttpReq *req, const TfwHttpMatchRule *rule)
return mark == rule->arg.num;
}

static bool
match_cookie(const TfwHttpReq *req, const TfwHttpMatchRule *rule)
{
TfwStr cookie_val;
TfwStr *hdr, *end, *dup;
tfw_http_match_op_t op = rule->val.ptn.op;
const char *cstr = rule->val.ptn.str;
unsigned int clen = rule->val.ptn.len;

if (unlikely(rule->val.type != TFW_HTTP_MATCH_V_COOKIE))
return false;

hdr = &req->h_tbl->tbl[TFW_HTTP_HDR_COOKIE];
if (TFW_STR_EMPTY(hdr))
return 0;
TFW_STR_FOR_EACH_DUP(dup, hdr, end) {
TfwStr value = { 0 };
int r;

tfw_http_msg_clnthdr_val(req, dup, TFW_HTTP_HDR_COOKIE, &value);
r = tfw_http_search_cookie(cstr, clen,
&value, &cookie_val,
op, false);
if (r)
return r;
}

return 0;

// tfw_http_search_cookie(, , , &cookie_val, rule->val.ptn.op, false);

// flags = map_op_to_str_eq_flags(rule->op);
// /*
// * RFC 7230:
// * 5.4: Host header must be ignored when URI is absolute.
// * 5.4, 2.7.3: the comparison is case-insensitive.
// *
// * TODO:
// * 5.4, 2.7.3: Port 80 is equal to a non-given/empty port (done by
// * normalizing the host).
// */
// flags |= TFW_STR_EQ_CASEI;
// arg = &rule->arg;
// if (rule->op == TFW_HTTP_MATCH_O_SUFFIX)
// return tfw_str_eq_cstr_off(host, host->len - arg->len,
// arg->str, arg->len, flags);

// return tfw_str_eq_cstr(host, arg->str, arg->len, flags);
// return true;
}

typedef bool (*match_fn)(const TfwHttpReq *, const TfwHttpMatchRule *);

static const match_fn match_fn_tbl[_TFW_HTTP_MATCH_F_COUNT] = {
Expand All @@ -417,6 +468,7 @@ static const match_fn match_fn_tbl[_TFW_HTTP_MATCH_F_COUNT] = {
[TFW_HTTP_MATCH_F_METHOD] = match_method,
[TFW_HTTP_MATCH_F_URI] = match_uri,
[TFW_HTTP_MATCH_F_MARK] = match_mark,
[TFW_HTTP_MATCH_F_COOKIE] = match_cookie,
};

/**
Expand All @@ -425,7 +477,7 @@ static const match_fn match_fn_tbl[_TFW_HTTP_MATCH_F_COUNT] = {
* has appropriate action type.
*/
static bool
do_eval(const TfwHttpReq *req, const TfwHttpMatchRule *rule)
do_eval(TfwHttpReq *req, const TfwHttpMatchRule *rule)
{
match_fn match_fn;
tfw_http_match_fld_t field;
Expand Down Expand Up @@ -458,6 +510,16 @@ do_eval(const TfwHttpReq *req, const TfwHttpMatchRule *rule)
req->msg.skb_head->mark = rule->act.mark;
return false;
}
/*
* Evaluate binary flag setting action.
*/
if (rule->act.type ==TFW_HTTP_MATCH_ACT_FLAG) {
if (likely(rule->act.flg.set))
set_bit(rule->act.flg.fid, req->flags);
else
clear_bit(rule->act.flg.fid, req->flags);
return false;
}
return true;
}

Expand All @@ -471,6 +533,7 @@ tfw_http_tbl_arg_type(tfw_http_match_fld_t field)
[TFW_HTTP_MATCH_F_METHOD] = TFW_HTTP_MATCH_A_METHOD,
[TFW_HTTP_MATCH_F_URI] = TFW_HTTP_MATCH_A_STR,
[TFW_HTTP_MATCH_F_MARK] = TFW_HTTP_MATCH_A_NUM,
[TFW_HTTP_MATCH_F_COOKIE] = TFW_HTTP_MATCH_A_STR,
};

BUG_ON(field <= 0 || field >= _TFW_HTTP_MATCH_F_COUNT);
Expand All @@ -483,7 +546,7 @@ tfw_http_tbl_arg_type(tfw_http_match_fld_t field)
* Return a first matching rule.
*/
TfwHttpMatchRule *
tfw_http_match_req(const TfwHttpReq *req, struct list_head *mlst)
tfw_http_match_req(TfwHttpReq *req, struct list_head *mlst)
{
TfwHttpMatchRule *rule;

Expand Down Expand Up @@ -599,7 +662,8 @@ tfw_http_rule_arg_init(TfwHttpMatchRule *rule, const char *arg, size_t arg_len)
rule->arg.len = arg_len;
memcpy(rule->arg.str, arg, arg_len);
if (rule->field == TFW_HTTP_MATCH_F_HDR
&& rule->hid == TFW_HTTP_HDR_RAW)
&& rule->val.type == TFW_HTTP_MATCH_V_HEADER
&& rule->val.hid == TFW_HTTP_HDR_RAW)
{
char *p = rule->arg.str;
while ((*p = tolower(*p)))
Expand All @@ -609,14 +673,37 @@ tfw_http_rule_arg_init(TfwHttpMatchRule *rule, const char *arg, size_t arg_len)
return 0;
}

static
size_t
tfw_http_escape_pre_post(char *out , const char *str)
{
int i;
size_t len = 0;
bool escaped = false;

for (i = 0; str[i]; ++i) {
if (str[i] == '*' && !escaped && (i == 0 || !str[i + 1]))
continue;
if (str[i] != '\\' || escaped) {
escaped = false;
*out = str[i];
++len;
++out;
}
else if (str[i] == '\\') {
escaped = true;
}
}

return len;
}

const char *
tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field,
const char *raw_hdr_name, size_t *size_out,
tfw_http_match_arg_t *type_out,
tfw_http_match_op_t *op_out)
{
int i;
bool escaped;
char *arg_out, *pos;
size_t name_len = 0, full_name_len = 0, len = strlen(arg);
bool wc_arg = (arg[0] == '*' && len == 1);
Expand All @@ -631,7 +718,7 @@ tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field,
if (wc_arg && !raw_hdr_name)
return NULL;

if (raw_hdr_name) {
if (raw_hdr_name && field != TFW_HTTP_MATCH_F_COOKIE) {
name_len = strlen(raw_hdr_name);
full_name_len = name_len + SLEN(S_DLM);
}
Expand All @@ -641,7 +728,7 @@ tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field,
return ERR_PTR(-ENOMEM);
}

if (raw_hdr_name) {
if (raw_hdr_name && field != TFW_HTTP_MATCH_F_COOKIE) {
memcpy(arg_out, raw_hdr_name, name_len);
memcpy(arg_out + name_len, S_DLM, SLEN(S_DLM));
}
Expand Down Expand Up @@ -676,27 +763,63 @@ tfw_http_arg_adjust(const char *arg, tfw_http_match_fld_t field,
*op_out = TFW_HTTP_MATCH_O_SUFFIX;
}

len = full_name_len;
escaped = false;
pos = arg_out + full_name_len;
for (i = 0; arg[i]; ++i) {
if (arg[i] == '*' && !escaped && (i == 0 || !arg[i + 1]))
continue;
if (arg[i] != '\\' || escaped) {
escaped = false;
*pos = arg[i];
++len;
++pos;
}
else if (arg[i] == '\\') {
escaped = true;
len = tfw_http_escape_pre_post(pos, arg);
*size_out += full_name_len + len + 1;

return arg_out;
}

const char *
tfw_http_val_adjust(const char *val, tfw_http_match_fld_t field,
unsigned int *len_out,
tfw_http_match_val_t *type_out,
tfw_http_match_op_t *op_out)
{
size_t len;
char *val_out;
bool wc_val;

if (field != TFW_HTTP_MATCH_F_COOKIE) {
*type_out = TFW_HTTP_MATCH_V_HEADER;
return NULL;
}
*type_out = TFW_HTTP_MATCH_V_COOKIE;

if (!val) {
T_ERR_NL("http_tbl: cookie pattern is empty, must be filled\n");
return ERR_PTR(-EINVAL);
}

len = strlen(val);
wc_val = (val[0] == '*' && len == 1);

if (!(val_out = kzalloc(len + 1, GFP_KERNEL))) {
T_ERR_NL("http_match: unable to allocate rule field value.\n");
return ERR_PTR(-ENOMEM);
}

*op_out = TFW_HTTP_MATCH_O_EQ;
if (wc_val)
*op_out = TFW_HTTP_MATCH_O_WILDCARD;
if (!wc_val || (len > 1 && val[len - 1] == '*' && val[len - 2] != '\\'))
*op_out = TFW_HTTP_MATCH_O_PREFIX;
if (!wc_val && val[0] == '*') {
if (*op_out == TFW_HTTP_MATCH_O_PREFIX) {
T_ERR_NL("http_match: unable to match"
" double-wildcard patterns '%s'\n", val);
return ERR_PTR(-EINVAL);
} else {
*op_out = TFW_HTTP_MATCH_O_SUFFIX;
}
}
*size_out = len + 1;

return arg_out;
*len_out = tfw_http_escape_pre_post(val_out, val);

return val_out;
}


int
tfw_http_verify_hdr_field(tfw_http_match_fld_t field, const char **hdr_name,
unsigned int *hid_out)
Expand Down Expand Up @@ -730,3 +853,61 @@ tfw_http_verify_hdr_field(tfw_http_match_fld_t field, const char **hdr_name,

return 0;
}

/*
* Search for cookie in `Set-Cookie`/`Cookie` header value @cookie
* and save the cookie value into @val. Prefix, suffix or wildacar compare
* @op is supported, pass TFW_HTTP_MATCH_O_EQ for default behaviour.
* Flag @is_resp_hdr identifies the header name: true for `Set-Cookie`,
* false for `Cookie`.
*/
int
tfw_http_search_cookie(const char *cstr, unsigned long clen,
const TfwStr *cookie, TfwStr *val,
tfw_http_match_op_t op, bool is_resp_hdr)
{
TfwStr *chunk, *end;
TfwStr tmp = { .flags = 0, };
unsigned int n = cookie->nchunks;
(void) op;
/* Search cookie name. */
end = cookie->chunks + cookie->nchunks;
for (chunk = cookie->chunks; chunk != end; ++chunk, --n) {
if (!(chunk->flags & TFW_STR_NAME))
continue;
if (unlikely(op == TFW_HTTP_MATCH_O_WILDCARD))
break;
/*
* Create a temporary compound string, starting with this
* chunk. The total string length is not used here, so it
* is not set.
*/
tmp.chunks = chunk;
tmp.nchunks = n;
if (tfw_str_eq_cstr(&tmp, cstr, clen, TFW_STR_EQ_PREFIX))
break;
/*
* 'Cookie' header has multiple name-value pairs while the
* 'Set-Cookie' has only one.
*/
if (unlikely(is_resp_hdr))
return 0;
}
if (chunk == end)
return 0;

/* Search cookie value, starting with next chunk. */
for (++chunk; chunk != end; ++chunk)
if (chunk->flags & TFW_STR_VALUE)
break;
/*
* The party can send us zero-value cookie,
* treat this as not found cookie.
*/
if (unlikely(chunk == end))
return 0;

tfw_str_collect_cmp(chunk, end, val, ";");

return 1;
}
Loading

0 comments on commit c71c876

Please sign in to comment.