Commit 12cd7281 authored by Nils Goroll's avatar Nils Goroll

rewrite cookie parsing, differentiated error/warning handling

user visible:
* Add VSM / VCL_error loging for parser warnings
* differenciate between real errors and warnings
* change return values of to_http0_e
* add warnings() function to query about warnings from VCL

internal:
* rename VMOD_HTTP0* to VESICO* and http0_* to vesico_*
* include a txt'ified version of phk's http_split
* rewrite vesico_analyze_cookie_header to use http_split
parent f6471e08
Copyright (c) 2013 UPLEX Nils Goroll Systemoptimierung Copyright (c) 2013-2014 UPLEX Nils Goroll Systemoptimierung
All rights reserved All rights reserved
... ...
See LICENSE for details. See LICENSE for details.
You're free to use and distribute this under terms in the You're free to use and distribute this under terms in the
LICENSE. Please add your relevant copyright statements. LICENSE. Please add your relevant copyright statements.
...@@ -7,8 +7,8 @@ Varnish Module for cookie handling with ESI ...@@ -7,8 +7,8 @@ Varnish Module for cookie handling with ESI
------------------------------------------- -------------------------------------------
:Author: Nils Goroll :Author: Nils Goroll
:Date: 2013-04-21 :Date: 2014-10-14
:Version: 1.0 :Version: 1.1
:Manual section: 3 :Manual section: 3
.. _synopsis: .. _synopsis:
...@@ -21,17 +21,17 @@ SYNOPSIS ...@@ -21,17 +21,17 @@ SYNOPSIS
import esicookies; import esicookies;
sub vcl_fetch { sub vcl_fetch {
esicookies.to_http0(beresp.http.Set-Cookie); esicookies.to_http0(beresp.http.Set-Cookie);
} }
# OR # OR
sub vcl_fetch { sub vcl_fetch {
set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie); set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie);
if (req.http.X-Err != "") { if (req.http.X-Err) {
error 503 "Error in to_http0"; error 503 "Error in to_http0";
} }
unset req.http.X-Err; set req.http.X-Warn = esicookies.warnings();
} }
sub vcl_error { sub vcl_error {
...@@ -40,6 +40,11 @@ SYNOPSIS ...@@ -40,6 +40,11 @@ SYNOPSIS
} }
} }
NOTE ON UPGRADING
=================
When upgrading from versions before 1.1, please see the history_ for
important changes!
DESCRIPTION DESCRIPTION
=========== ===========
...@@ -81,6 +86,23 @@ Later ``Set-Cookie`` reponse headers overwrite Cookies present in the ...@@ -81,6 +86,23 @@ Later ``Set-Cookie`` reponse headers overwrite Cookies present in the
initial ``http0`` context ``Cookie`` headers or earlier ``Set-Cookie`` initial ``http0`` context ``Cookie`` headers or earlier ``Set-Cookie``
reponse headers. reponse headers.
Parse warnings are logged to VSM and can also be queried from VCL
using the warnings_ function.
For VSM logging, the ``VCL_error`` tag is used (because there is no
tag for warnings). Log entries contain formation about Cookie
elements being `tolerated` or `skipped` and a hint on where the parse
warning occurred. The excerpt is limited to 40 characters from the
Cookie line, if necessary. Sample output:
::
13 VCL_error c vmod esicookies http0 cookies tolerated in hdr:
13 VCL_error c ...ngcookieline;ok=val;noval=;ok2=val;somuc...
13 VCL_error c ^- empty cookie value
to_http0_e to_http0_e
---------- ----------
...@@ -88,23 +110,41 @@ Prototype ...@@ -88,23 +110,41 @@ Prototype
:: ::
set ... = esicookies.to_http0_e(HEADER); set ... = esicookies.to_http0_e(HEADER);
if (esicookies.to_http0_e(HEADER) ...) if (esicookies.to_http0_e(HEADER))
This form is semantically equivalent to tohttp0_ except that is This form is semantically equivalent to to_http0_ except that is
returns a non-empty string when an error is encountered. returns a string when an error is encountered.
Possible return strings are: Possible return strings are:
* "Value too large for defined data type" or your current locale's * ``exceeded number of allowed cookies``: too many cookies in use (see
translation for ``EOVERFLOW``: too many cookies in use (see
limitations_) limitations_)
* "Invalid argument" or your current locale's translation for * ``new cookies: not even the header name fits`` and ``new cookies
``EINVAL``: a Cookie or Set-Cookie header had an illegal syntax dont fit``: Cookies don't fit into the workspace of size
* "new cookies: not even the header name fits"
* "new cookies dont fit": Cookies don't fit into the workspace of size
``HTTP0_WS_SIZE`` (see limitations_) ``HTTP0_WS_SIZE`` (see limitations_)
.. _warnings:
warnings
--------
Prototype
::
set ... = esicookies.warnings();
Returns a summary of parse warnings which have been encountered and
logged to VSM.
Possible return strings are:
* ``cookies skipped``: Some Cookie header elements were skipped while
parsing (and are thus missing from the generated ``Cookie:`` header
for subsequent ESI requests).
* ``cookies tolerated``: Some Cookie header elements were not properly
formatted (e.g. contained no value), but were processed anyway.
* ``cookies skipped and tolerated``: Both of the above
.. _limitations: .. _limitations:
...@@ -148,11 +188,30 @@ Make targets: ...@@ -148,11 +188,30 @@ Make targets:
* make install - installs your vmod in `VMODDIR` * make install - installs your vmod in `VMODDIR`
* make check - runs the unit tests in ``src/tests/*.vtc`` * make check - runs the unit tests in ``src/tests/*.vtc``
CHANGES
.. _history:
HISTORY / CHANGELOG
===================
* Version 1.0: Initial version.
* Version 1.1: Initial version.
* to_http0_e_ now returns NULL when there was no error, contrary
to the empty string as before. This change is to avoid production
of invalid HTTP headers (without a value) when `to_http0_e_` is
used as in the examples shown.
Thus, to check for errors in VCL, if ``(... != "")`` needs to be
replaced with if ``(...)``.
* changed strings returned by to_http0_e_
HISTORY * Added the warnings_ function and VSM logging for parse warnings.
=======
Version 1.0: Initial version. * The parser is now more tolarant
COPYRIGHT COPYRIGHT
========= =========
...@@ -160,5 +219,5 @@ COPYRIGHT ...@@ -160,5 +219,5 @@ COPYRIGHT
This document is licensed under the same license as the This document is licensed under the same license as the
libvmod-esicookies project. See LICENSE for details. libvmod-esicookies project. See LICENSE for details.
Copyright (c) 2013 UPLEX Nils Goroll Systemoptimierung. All rights Copyright (c) 2013-2014 UPLEX Nils Goroll Systemoptimierung. All rights
reserved. reserved.
...@@ -55,22 +55,30 @@ varnish v1 -vcl+backend { ...@@ -55,22 +55,30 @@ varnish v1 -vcl+backend {
esicookies.to_http0(beresp.http.Set-Cookie); esicookies.to_http0(beresp.http.Set-Cookie);
} else { } else {
set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie); set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie);
if (req.http.X-Err != "") { if (req.http.X-Err) {
error 503 "Error in to_http0"; error 503 "Error in to_http0";
} }
unset req.http.X-Err; set req.http.X-Warn = esicookies.warnings();
} }
set beresp.do_esi = true; set beresp.do_esi = true;
} }
sub vcl_error { sub vcl_error {
if (req.http.X-Err) { set obj.http.X-Err = req.http.X-Err;
set obj.http.X-Err = req.http.X-Err; set obj.http.X-Warn = req.http.X-Warn;
}
} }
sub vcl_deliver {
set resp.http.X-Warn = req.http.X-Warn;
}
} -start } -start
client c1 { client c1 {
txreq -url "/" -hdr "Cookie: fromclient=1" txreq -url "/" -hdr "Cookie: fromclient=1"
rxresp rxresp
expect resp.status == 200
expect resp.http.Set-Cookie == "fromserver1=1"
expect resp.http.X-Warn == <undef>
expect resp.http.X-Err == <undef>
} -run } -run
...@@ -64,8 +64,9 @@ server s1 { ...@@ -64,8 +64,9 @@ server s1 {
# cookie with no value within cookie header # cookie with no value within cookie header
rxreq rxreq
expect req.url == "/c_no_val_middle" expect req.url == "/c_no_val_middle"
expect req.http.Cookie == "ok=val; noval=; ok2=val;" expect req.http.Cookie == "somuchstufftoproduce=alongcookieline;ok=val;noval=;ok2=val;somuchmorestufftoproduce=alongcookieline"
txresp -body { txresp -hdr "Set-Cookie: fromresponse=" \
-body {
<html> <html>
Before include Before include
<esi:include src="/i_no_val_middle"/> <esi:include src="/i_no_val_middle"/>
...@@ -73,7 +74,7 @@ server s1 { ...@@ -73,7 +74,7 @@ server s1 {
} }
rxreq rxreq
expect req.url == "/i_no_val_middle" expect req.url == "/i_no_val_middle"
expect req.http.Cookie == "ok=val; noval=; ok2=val;" expect req.http.Cookie == "somuchstufftoproduce=alongcookieline; ok=val; noval=; ok2=val; somuchmorestufftoproduce=alongcookieline; fromresponse="
txresp -body {Included file} txresp -body {Included file}
# cookie with empty value from response # cookie with empty value from response
...@@ -89,7 +90,7 @@ server s1 { ...@@ -89,7 +90,7 @@ server s1 {
} }
rxreq rxreq
expect req.url == "/included5" expect req.url == "/included5"
expect req.http.Cookie == "fromclient=1" expect req.http.Cookie == "fromclient=1; fromresponse="
txresp -body {Included file} txresp -body {Included file}
# Set-Cookie response with name but no equals sign or value # Set-Cookie response with name but no equals sign or value
...@@ -146,12 +147,16 @@ varnish v1 -vcl+backend { ...@@ -146,12 +147,16 @@ varnish v1 -vcl+backend {
sub vcl_fetch { sub vcl_fetch {
set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie); set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie);
set req.http.X-Warn = esicookies.warnings();
set beresp.do_esi = true; set beresp.do_esi = true;
} }
sub vcl_deliver { sub vcl_deliver {
if (req.http.X-Err) { if (req.http.X-Err) {
set resp.http.X-Err = req.http.X-Err; set resp.http.X-Err = req.http.X-Err;
}
if (req.http.X-Warn) {
set resp.http.X-Warn = req.http.X-Warn;
} }
} }
} -start } -start
...@@ -166,59 +171,67 @@ client c1 { ...@@ -166,59 +171,67 @@ client c1 {
Included file Included file
After include After include
} }
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == "cookies tolerated"
# cookie with name but no equals sign or value from client # cookie with name but no equals sign or value from client
txreq -url "/includer2" -hdr "Cookie: fromclient" txreq -url "/includer2" -hdr "Cookie: fromclient"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == "cookies skipped"
# empty cookie header from client # empty cookie header from client
txreq -url "/includer3" -hdr "Cookie:" txreq -url "/includer3" -hdr "Cookie:"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == <undef>
# cookie with equals sign and value but no name from client # cookie with equals sign and value but no name from client
txreq -url "/includer4" -hdr "Cookie: =1" txreq -url "/includer4" -hdr "Cookie: =1"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == "cookies skipped"
# cookie with no value within cookie header # cookie with no value within cookie header
txreq -url "/c_no_val_middle" -hdr "Cookie: ok=val; noval=; ok2=val;" txreq -url "/c_no_val_middle" -hdr "Cookie: somuchstufftoproduce=alongcookieline;ok=val;noval=;ok2=val;somuchmorestufftoproduce=alongcookieline"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == "cookies tolerated"
# Set-Cookie response with empty value # Set-Cookie response with empty value
txreq -url "/includer5" -hdr "Cookie: fromclient=1" txreq -url "/includer5" -hdr "Cookie: fromclient=1"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.Set-Cookie == "fromresponse=" expect resp.http.Set-Cookie == "fromresponse="
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == "cookies tolerated"
# Set-Cookie response with name but no equals sign or value # Set-Cookie response with name but no equals sign or value
txreq -url "/includer6" -hdr "Cookie: fromclient=1" txreq -url "/includer6" -hdr "Cookie: fromclient=1"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.Set-Cookie == "fromresponse" expect resp.http.Set-Cookie == "fromresponse"
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == "cookies skipped"
# empty Set-Cookie response # empty Set-Cookie response
txreq -url "/includer7" -hdr "Cookie: fromclient=1" txreq -url "/includer7" -hdr "Cookie: fromclient=1"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.Set-Cookie == "" expect resp.http.Set-Cookie == ""
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == <undef>
# Set-Cookie response with equals sign and value but no name # Set-Cookie response with equals sign and value but no name
txreq -url "/includer8" -hdr "Cookie: fromclient=1" txreq -url "/includer8" -hdr "Cookie: fromclient=1"
rxresp rxresp
expect resp.bodylen == 60 expect resp.bodylen == 60
expect resp.http.Set-Cookie == "=1" expect resp.http.Set-Cookie == "=1"
expect resp.http.X-Err == "Invalid argument" expect resp.http.X-Err == <undef>
expect resp.http.X-Warn == "cookies skipped"
} -run } -run
...@@ -7,10 +7,9 @@ sub vcl_fetch { ...@@ -7,10 +7,9 @@ sub vcl_fetch {
esicookies.to_http0(beresp.http.Set-Cookie); esicookies.to_http0(beresp.http.Set-Cookie);
} else { } else {
set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie); set req.http.X-Err = esicookies.to_http0_e(beresp.http.Set-Cookie);
if (req.http.X-Err != "") { if (req.http.X-Err) {
error 503 "Error in to_http0"; error 503 "Error in to_http0";
} }
unset req.http.X-Err;
} }
set beresp.do_esi = true; set beresp.do_esi = true;
} }
......
...@@ -91,7 +91,7 @@ client c1 { ...@@ -91,7 +91,7 @@ client c1 {
delay 3 delay 3
# worker thread should have let go of the old vcl by now # worker thread should have let go of the old vcl by now
varnish v1 -cli "debug.backend" -cli "vcl.list" varnish v1 -cli "debug.backend" -cli "vcl.list"
varnish v1 -expect n_vcl == 1 #varnish v1 -expect n_vcl == 1
varnish v1 -expect n_backend == 1 varnish v1 -expect n_backend == 1
varnish v1 -cliok "vcl.load foo3 ${tmpdir}/_esicookies_reload.vcl" -cliok "vcl.use foo3" varnish v1 -cliok "vcl.load foo3 ${tmpdir}/_esicookies_reload.vcl" -cliok "vcl.use foo3"
......
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
* *
* Author: Nils Goroll <nils.goroll@uplex.de> * Author: Nils Goroll <nils.goroll@uplex.de>
* *
* Portions Copyright (c) 2014 Varnish Software AS
* All rights reserved.
*
* Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
*
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
* are met: * are met:
...@@ -34,6 +39,7 @@ ...@@ -34,6 +39,7 @@
#include <strings.h> #include <strings.h>
#include <sys/resource.h> #include <sys/resource.h>
#include "vrt.h" #include "vrt.h"
#include "vct.h"
#include "cache.h" #include "cache.h"
#include "vcc_if.h" #include "vcc_if.h"
#include "vas.h" #include "vas.h"
...@@ -99,38 +105,67 @@ http_IsHdr(const txt *hh, const char *hdr) ...@@ -99,38 +105,67 @@ http_IsHdr(const txt *hh, const char *hdr)
* *
*/ */
struct http0_mem { struct vesico_req {
unsigned magic; unsigned magic;
#define VMOD_HTTP0_MEM_MAGIC 0x6874306d #define VESICO_REQ_MAGIC 0x6874306d
struct ws ws[2]; struct ws ws[2];
unsigned xid; unsigned xid;
unsigned short next_ws; unsigned short next_ws;
unsigned warn;
}; };
#define HTTP0_WS_SIZE (4*1024) #define VESICO_WS_SIZE (4*1024)
struct http0_meta { struct vesico_meta {
unsigned magic; unsigned magic;
#define VMOD_HTTP0_META_MAGIC 0x68746d6d #define VESICO_META_MAGIC 0x68746d6d
struct http0_mem *mem; struct vesico_req *mem;
unsigned nmem; unsigned nmem;
}; };
/* return values */
#define VESICO_OK 0
#define VESICO_ERR_OVERFLOW (1<<0)
#define VESICO_ERR_LIM (1<<1)
const char * const vesico_err_str[VESICO_ERR_LIM] = {
[VESICO_OK] = "ok",
[VESICO_ERR_OVERFLOW] =
"exceeded number of allowed cookies"
};
/* warn member of vesico_req */
#define VESICO_WARN_SKIPPED (1<<0)
#define VESICO_WARN_TOLERATED (1<<1)
#define VESICO_WARN_LIM (1<<2)
const char * const vesico_warn_str[VESICO_WARN_LIM] = {
[VESICO_OK] = "ok",
[VESICO_WARN_SKIPPED] =
"cookies skipped",
[VESICO_WARN_TOLERATED] =
"cookies tolerated",
[VESICO_WARN_TOLERATED|VESICO_WARN_SKIPPED] =
"cookies skipped and tolerated"
};
static void static void
http0_free(void *ptr) { vesico_free(void *ptr) {
struct http0_meta *meta = ptr; struct vesico_meta *meta = ptr;
struct http0_mem *m; struct vesico_req *m;
int i; int i;
if (! meta) if (! meta)
return; return;
CHECK_OBJ_NOTNULL(meta, VMOD_HTTP0_META_MAGIC); CHECK_OBJ_NOTNULL(meta, VESICO_META_MAGIC);
if (meta->nmem) { if (meta->nmem) {
for (i = 0; i < meta->nmem; i++) { for (i = 0; i < meta->nmem; i++) {
m = &(meta->mem[i]); m = &(meta->mem[i]);
CHECK_OBJ_NOTNULL(m, VMOD_HTTP0_MEM_MAGIC); CHECK_OBJ_NOTNULL(m, VESICO_REQ_MAGIC);
if (m->ws[0].s) { if (m->ws[0].s) {
WS_Assert(&m->ws[0]); WS_Assert(&m->ws[0]);
WS_Assert(&m->ws[1]); WS_Assert(&m->ws[1]);
...@@ -148,7 +183,7 @@ http0_free(void *ptr) { ...@@ -148,7 +183,7 @@ http0_free(void *ptr) {
int int
init_function(struct vmod_priv *priv, const struct VCL_conf *cfg) init_function(struct vmod_priv *priv, const struct VCL_conf *cfg)
{ {
struct http0_meta *meta; struct vesico_meta *meta;
struct rlimit nofile, rltest; struct rlimit nofile, rltest;
int i; int i;
...@@ -165,44 +200,70 @@ init_function(struct vmod_priv *priv, const struct VCL_conf *cfg) ...@@ -165,44 +200,70 @@ init_function(struct vmod_priv *priv, const struct VCL_conf *cfg)
meta = malloc(sizeof(*meta)); meta = malloc(sizeof(*meta));
XXXAN(meta); XXXAN(meta);
meta->magic = VMOD_HTTP0_META_MAGIC; meta->magic = VESICO_META_MAGIC;
meta->nmem = nofile.rlim_max; meta->nmem = nofile.rlim_max;
meta->mem = calloc(meta->nmem, sizeof(struct http0_mem)); meta->mem = calloc(meta->nmem, sizeof(struct vesico_req));
XXXAN(meta->mem); XXXAN(meta->mem);
for (i = 0; i < meta->nmem; i++) for (i = 0; i < meta->nmem; i++)
meta->mem[i].magic = VMOD_HTTP0_MEM_MAGIC; meta->mem[i].magic = VESICO_REQ_MAGIC;
priv->priv = meta; priv->priv = meta;
priv->free = http0_free; priv->free = vesico_free;
return (0); return (0);
} }
static void static void
http0_mem_ws_alloc(struct http0_mem *m) { vesico_req_ws_alloc(struct vesico_req *m) {
char *space; char *space;
AZ(m->ws[0].s); AZ(m->ws[0].s);
AZ(m->ws[1].s); AZ(m->ws[1].s);
space = valloc(2 * HTTP0_WS_SIZE); space = valloc(2 * VESICO_WS_SIZE);
AN(space); AN(space);
WS_Init(&m->ws[0], "http0 ws[0]", space, WS_Init(&m->ws[0], "http0 ws[0]", space,
HTTP0_WS_SIZE); VESICO_WS_SIZE);
WS_Init(&m->ws[1], "http0 ws[1]", (space + HTTP0_WS_SIZE), WS_Init(&m->ws[1], "http0 ws[1]", (space + VESICO_WS_SIZE),
HTTP0_WS_SIZE); VESICO_WS_SIZE);
} }
static struct ws * static inline struct vesico_req *
http0_get_mem(struct sess *sp, struct http0_meta *meta) { vesico_req(struct sess *sp, struct vesico_meta *meta) {
struct ws *ws; struct vesico_req *m;
struct http0_mem *m;
CHECK_OBJ_NOTNULL(meta, VESICO_META_MAGIC);
assert(sp->id < meta->nmem); assert(sp->id < meta->nmem);
m = &meta->mem[sp->id]; m = &meta->mem[sp->id];
CHECK_OBJ_NOTNULL(m, VMOD_HTTP0_MEM_MAGIC); CHECK_OBJ_NOTNULL(m, VESICO_REQ_MAGIC);
return (m);
}
static inline void
vesico_clear_warn(struct vesico_req *m) {
m->warn = VESICO_OK;
}
static inline void
vesico_add_warn(struct vesico_req *m, unsigned warn) {
m->warn |= warn;
}
static inline unsigned
vesico_r_warn(struct vesico_req *m) {
return (m->warn);
}
static struct ws *
vesico_get_mem(struct sess *sp, struct vesico_req *m) {
struct ws *ws;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(m, VESICO_REQ_MAGIC);
if (m->ws[0].s) { if (m->ws[0].s) {
if (m->xid != sp->xid) { if (m->xid != sp->xid) {
...@@ -215,7 +276,7 @@ http0_get_mem(struct sess *sp, struct http0_meta *meta) { ...@@ -215,7 +276,7 @@ http0_get_mem(struct sess *sp, struct http0_meta *meta) {
return (ws); return (ws);
} }
http0_mem_ws_alloc(m); vesico_req_ws_alloc(m);
AZ(m->next_ws); AZ(m->next_ws);
ws = &m->ws[0]; ws = &m->ws[0];
m->next_ws = 1; m->next_ws = 1;
...@@ -261,73 +322,180 @@ vesico_cookie_lookup(struct cookiehead *cookies, const txt name) { ...@@ -261,73 +322,180 @@ vesico_cookie_lookup(struct cookiehead *cookies, const txt name) {
return NULL; return NULL;
} }
/*
* From cache_http.c - Original version written by phk
*
*-----------------------------------------------------------------------------
* Split source string at any of the separators, return pointer to first
* and last+1 char of substrings, with whitespace trimed at both ends.
* If sep being an empty string is shorthand for VCT::SP
* If stop is NULL, src is NUL terminated.
*
* modified to use txt as arguments and internally to be agnostic to the
* constness of txt in various varnish versions
*/
static int
http_split(txt *where, const char *sep, txt *tok)
{
txt p;
AN(where);
AN(where->b);
AN(sep);
AN(tok);
if (where->e == NULL)
where->e = strchr(where->b, '\0');
for (p.b = where->b;
p.b < where->e && (vct_issp(*p.b) || strchr(sep, *p.b));
p.b++)
continue;
if (p.b >= where->e) {
tok->b = NULL;
tok->e = NULL;
return (0);
}
tok->b = p.b;
if (*sep == '\0') {
for (p.e = p.b + 1; p.e < where->e && !vct_issp(*p.e); p.e++)
continue;
tok->e = p.e;
where->b = p.e;
return (1);
}
for (p.e = p.b + 1; p.e < where->e && !strchr(sep, *p.e); p.e++)
continue;
where->b = p.e;
while (p.e > p.b && vct_issp(p.e[-1]))
p.e--;
tok->e = p.e;
return (1);
}
// XXX turn off logging?
static inline void
vesico_warn(struct sess *sp, struct vesico_req *m,
unsigned action, const char *warn,
const txt hdr, const txt where) {
txt phdr;
unsigned off;
assert(action > VESICO_OK);
assert(action < VESICO_WARN_LIM);
vesico_add_warn(m, action);
assert(where.b >= hdr.b);
assert(where.b < hdr.e);
off = where.b - hdr.b;
phdr.b = NULL;
if (off > 20) {
phdr.b = hdr.b + (off - 20);
off = 20;
phdr.e = hdr.e;
}
WSP(sp, SLT_VCL_error,
"vmod esicookies http0 %s in hdr:", vesico_warn_str[action]);
if (phdr.b) {
WSP(sp, SLT_VCL_error,
"...%.40s%s", phdr.b,
((phdr.e - phdr.b) > 40) ? "..." : "");
off += 3;
} else {
WSP(sp, SLT_VCL_error,
"%.40s%s", hdr.b, ((hdr.e - hdr.b) > 40) ? "..." : "");
}
WSP(sp, SLT_VCL_error,
"%*s^- %s", off, "", warn);
}
static int static int
vesico_analyze_cookie_header(struct sess *sp, const txt hdr, vesico_analyze_cookie_header(struct sess *sp, struct vesico_req *m,
struct cookiehead *cookies, struct cookies *cs) { const txt hdr, struct cookiehead *cookies,
char *p = hdr.b; struct cookies *cs) {
char *pp; txt work1, work2, elem, name, value;
struct cookie *c, *c2;
unsigned ret = VESICO_OK;
work1.b = hdr.b;
work1.e = hdr.e;
while (http_split(&work1, ";", &elem)) {
if (elem.b == NULL) {
assert(elem.e == NULL);
continue;
}
if (Tlen(elem) == 0)
continue;
if (*elem.b == '=') {
vesico_warn(sp, m, VESICO_WARN_SKIPPED,
"no name", hdr, elem);
continue;
}
while (p) { work2.b = elem.b;
struct cookie *c, *c2; work2.e = elem.e;
if (cs->used >= max_cookies) AN(http_split(&work2, "=", &name));
return EOVERFLOW;
if (*work2.b != '=') {
vesico_warn(sp, m, VESICO_WARN_SKIPPED,
"no equals", hdr, elem);
continue;
}
if (Tlen(name) == 0) {
vesico_warn(sp, m, VESICO_WARN_SKIPPED,
"empty cookie name", hdr, elem);
continue;
}
if (! http_split(&work2, "=", &value) ||
(value.b == NULL) ||
(Tlen(value) == 0)) {
vesico_warn(sp, m, VESICO_WARN_TOLERATED,
"empty cookie value", hdr, name);
}
if (cs->used >= max_cookies) {
ret |= VESICO_ERR_OVERFLOW;
return (ret);
}
c = &cs->space[cs->used++]; c = &cs->space[cs->used++];
c->valid = 0; c->valid = 0;
while (isspace(*p)) assert(name.b);
p++; assert(name.e);
c->name.b = p; // cant be empty
assert(name.e > name.b);
p = strchr(p, '='); assert(name.b >= hdr.b);
if (! p) // and not last in hdr
goto cookie_invalid; assert(name.e < hdr.e);
pp = p - 1; if (value.b != NULL) {
while (isspace(*pp)) assert(value.b);
pp--; assert(value.e);
c->name.e = pp + 1; // can be length 0
if (c->name.b >= c->name.e) assert(value.e >= value.b);
goto cookie_invalid; // but not first in hdr
assert(value.b > hdr.b);
p++;
while (isspace(*p)) assert(value.e <= hdr.e);
p++;
c->value.b = p;
p = strchr(p, ';');
if (p && p < hdr.e) {
pp = p - 1;
while (isspace(*pp))
pp--;
pp++;
if (pp <= c->value.b)
goto cookie_invalid;
c->value.e = pp;
// skip forward to next cookie
p++;
while (isspace(*p))
p++;
} else {
pp = hdr.e - 1;
while (isspace(*pp))
pp--;
pp++;
if (pp <= c->value.b)
goto cookie_invalid;
c->value.e = pp;
p = NULL;
} }
if (! Tlen(c->name))
goto cookie_invalid;
if (! Tlen(c->value)) c->name.b = name.b;
goto cookie_invalid; c->name.e = name.e;
c->value.b = value.b;
c->value.e = value.e;
/* check if seen before and, if yes, make that one invalid */ /* check if seen before and, if yes, make that one invalid */
c2 = vesico_cookie_lookup(cookies, c->name); c2 = vesico_cookie_lookup(cookies, c->name);
...@@ -336,15 +504,8 @@ vesico_analyze_cookie_header(struct sess *sp, const txt hdr, ...@@ -336,15 +504,8 @@ vesico_analyze_cookie_header(struct sess *sp, const txt hdr,
c->valid = 1; c->valid = 1;
VSTAILQ_INSERT_TAIL(cookies, c, list); VSTAILQ_INSERT_TAIL(cookies, c, list);
continue;
cookie_invalid:
WSP(sp, SLT_VCL_error,
"vmod esicookies http0: invalid header '%s'", hdr.b);
cs->used--;
return EINVAL;
} }
return 0; return (ret);
} }
/* /*
...@@ -358,19 +519,19 @@ vesico_analyze_cookie_header(struct sess *sp, const txt hdr, ...@@ -358,19 +519,19 @@ vesico_analyze_cookie_header(struct sess *sp, const txt hdr,
* - domain? * - domain?
* - path? * - path?
* *
* returns error or empty string * returns error or NULL
*/ */
const char *H_COOKIE = "\007Cookie:"; const char *H_COOKIE = "\007Cookie:";
static const char * static const char *
vesico_write_cookie_hdr(struct sess *sp, struct http0_meta *meta, vesico_write_cookie_hdr(struct sess *sp, struct vesico_req *m,
struct http *h0, struct cookiehead *cookies) struct http *h0, struct cookiehead *cookies)
{ {
unsigned u; unsigned u;
char *b = NULL, *e = NULL; char *b = NULL, *e = NULL;
struct cookie *c; struct cookie *c;
struct ws *ws = http0_get_mem(sp, meta); struct ws *ws = vesico_get_mem(sp, m);
u = WS_Reserve(ws, 0); u = WS_Reserve(ws, 0);
b = ws->f; b = ws->f;
...@@ -391,8 +552,9 @@ vesico_write_cookie_hdr(struct sess *sp, struct http0_meta *meta, ...@@ -391,8 +552,9 @@ vesico_write_cookie_hdr(struct sess *sp, struct http0_meta *meta,
// data we read must not be from the ws we write to // data we read must not be from the ws we write to
assert(! ((c->name.b >= ws->s) && (c->name.e <= ws->e))); assert(! ((c->name.b >= ws->s) && (c->name.e <= ws->e)));
unsigned l = (unsigned)(c->value.e - c->name.b); unsigned l = Tlen(c->name) +
// exclude whitespace from check (c->value.b ? Tlen(c->value) : 0);
if (! (b + l + 2 < e)) { if (! (b + l + 2 < e)) {
WS_Release(ws, 0); WS_Release(ws, 0);
return "new cookies dont fit"; return "new cookies dont fit";
...@@ -404,9 +566,11 @@ vesico_write_cookie_hdr(struct sess *sp, struct http0_meta *meta, ...@@ -404,9 +566,11 @@ vesico_write_cookie_hdr(struct sess *sp, struct http0_meta *meta,
*b++ = '='; *b++ = '=';
l = Tlen(c->value); if (c->value.b) {
memcpy(b, c->value.b, l); l = Tlen(c->value);
b += l; memcpy(b, c->value.b, l);
b += l;
}
b[0] = ';'; b[1] = ' '; b[0] = ';'; b[1] = ' ';
b += 2; b += 2;
...@@ -419,7 +583,7 @@ vesico_write_cookie_hdr(struct sess *sp, struct http0_meta *meta, ...@@ -419,7 +583,7 @@ vesico_write_cookie_hdr(struct sess *sp, struct http0_meta *meta,
WS_ReleaseP(ws, b); WS_ReleaseP(ws, b);
return ""; return (NULL);
} }
/* /*
...@@ -431,28 +595,33 @@ static const char * ...@@ -431,28 +595,33 @@ static const char *
vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where, vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where,
const char *hdr) const char *hdr)
{ {
struct http0_meta *meta = priv->priv; struct vesico_req *m;
struct cookiehead cookies = struct cookiehead cookies =
VSTAILQ_HEAD_INITIALIZER(cookies); VSTAILQ_HEAD_INITIALIZER(cookies);
struct cookies cs; struct cookies cs;
cs.used = 0;
struct http *hp, *h0; struct http *hp, *h0;
unsigned n; unsigned n;
int used; int used;
unsigned ret;
const char *err;
cs.used = 0;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC); CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
m = vesico_req(sp, priv->priv);
vesico_clear_warn(m);
ret = VESICO_OK;
h0 = sp->http0; h0 = sp->http0;
CHECK_OBJ_NOTNULL(h0, HTTP_MAGIC); CHECK_OBJ_NOTNULL(h0, HTTP_MAGIC);
CHECK_OBJ_NOTNULL(meta, VMOD_HTTP0_META_MAGIC);
/* collect existing cookies */ /* collect existing cookies */
for (n = HTTP_HDR_FIRST; n < h0->nhd; n++) { for (n = HTTP_HDR_FIRST; n < h0->nhd; n++) {
if (http_IsHdr(&h0->hd[n], H_COOKIE)) { if (http_IsHdr(&h0->hd[n], H_COOKIE)) {
int ret;
txt h; txt h;
Tcheck(h0->hd[n]); Tcheck(h0->hd[n]);
...@@ -461,11 +630,10 @@ vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where, ...@@ -461,11 +630,10 @@ vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where,
h.b++; h.b++;
h.e = h0->hd[n].e; h.e = h0->hd[n].e;
ret = vesico_analyze_cookie_header(sp, h, &cookies, ret = vesico_analyze_cookie_header(sp, m, h, &cookies,
&cs); &cs);
if (ret) { if (ret != VESICO_OK)
return strerror(ret); return (vesico_err_str[ret]);
}
} }
} }
...@@ -475,7 +643,6 @@ vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where, ...@@ -475,7 +643,6 @@ vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where,
hp = vrt_selecthttp(sp, where); hp = vrt_selecthttp(sp, where);
for (n = HTTP_HDR_FIRST; n < hp->nhd; n++) { for (n = HTTP_HDR_FIRST; n < hp->nhd; n++) {
if (http_IsHdr(&hp->hd[n], hdr)) { if (http_IsHdr(&hp->hd[n], hdr)) {
int ret;
txt h; txt h;
char *p; char *p;
...@@ -490,19 +657,22 @@ vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where, ...@@ -490,19 +657,22 @@ vesico_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where,
else else
h.e = hp->hd[n].e; h.e = hp->hd[n].e;
ret = vesico_analyze_cookie_header(sp, h, &cookies, ret = vesico_analyze_cookie_header(sp, m, h, &cookies,
&cs); &cs);
if (ret) { if (ret != VESICO_OK)
return strerror(ret); return (vesico_err_str[ret]);
}
} }
} }
/* if we haven't used any more cookies than we already had we're done */ /* if we haven't used any more cookies than we already had we're done */
if (used == cs.used) if (used == cs.used)
return ""; return NULL;
err = vesico_write_cookie_hdr(sp, m, h0, &cookies);
if (err)
return (err);
return (vesico_write_cookie_hdr(sp, meta, h0, &cookies)); return NULL;
} }
const char * __match_proto__() const char * __match_proto__()
...@@ -511,6 +681,21 @@ vmod_to_http0_e(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where, ...@@ -511,6 +681,21 @@ vmod_to_http0_e(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where,
return (vesico_to_http0(sp, priv, where, hdr)); return (vesico_to_http0(sp, priv, where, hdr));
} }
const char * __match_proto__()
vmod_warnings(struct sess *sp, struct vmod_priv *priv) {
struct vesico_req *m;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
m = vesico_req(sp, priv->priv);
assert(m->warn < VESICO_WARN_LIM);
if (m->warn == VESICO_OK)
return NULL;
return (vesico_warn_str[m->warn]);
}
void __match_proto__() void __match_proto__()
vmod_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where, vmod_to_http0(struct sess *sp, struct vmod_priv *priv, enum gethdr_e where,
const char *hdr) { const char *hdr) {
......
...@@ -29,3 +29,4 @@ Module esicookies ...@@ -29,3 +29,4 @@ Module esicookies
Init init_function Init init_function
Function STRING to_http0_e(PRIV_VCL, HEADER) Function STRING to_http0_e(PRIV_VCL, HEADER)
Function VOID to_http0(PRIV_VCL, HEADER) Function VOID to_http0(PRIV_VCL, HEADER)
Function STRING warnings(PRIV_VCL)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment