[master] f64a54e37 Import vmod_cookie from varnish/varnish-modules
Dridi Boukelmoune
dridi.boukelmoune at gmail.com
Mon Feb 17 17:59:07 UTC 2020
commit f64a54e375fb8692d23bb8f6c8c191623fbbb443
Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
Date: Mon Feb 17 18:50:40 2020 +0100
Import vmod_cookie from varnish/varnish-modules
This is in essence the same VMOD so migrating from one to the other
should result in no VCL change, except for the removal of DEPRECATED
cookie.filter_except().
The test suite was refreshed to take advantage of recent features and
mostly operates in vcl_synth as a result, not wasting cycles bringing
needless backends up.
The C code and VCC descriptor were also improved for better code style
compliance and documentation consistency, without changing the VMOD's
behavior. On the C side actual changes mostly consisted in adding missing
assertions but there were notable changes:
- usage of VRE_Free instead of free (with test case)
- WS_VSB_* facility usage for the one use case
There are other opportunities for improvement, like usage of more modern
facilities like VRT_fail, or a named Lck instead of a plain mutex.
Closes #3184
diff --git a/configure.ac b/configure.ac
index 61c19dbcc..87fe4b11f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -853,6 +853,7 @@ AC_CONFIG_FILES([
lib/libvarnishapi/Makefile
lib/libvcc/Makefile
lib/libvgz/Makefile
+ lib/libvmod_cookie/Makefile
lib/libvmod_debug/Makefile
lib/libvmod_std/Makefile
lib/libvmod_directors/Makefile
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 907ee4a5e..1cd630954 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -5,6 +5,7 @@ SUBDIRS = \
libvarnishapi \
libvcc \
libvgz \
+ libvmod_cookie \
libvmod_debug \
libvmod_std \
libvmod_directors \
diff --git a/lib/libvmod_cookie/Makefile.am b/lib/libvmod_cookie/Makefile.am
new file mode 100644
index 000000000..e9ed0cf2e
--- /dev/null
+++ b/lib/libvmod_cookie/Makefile.am
@@ -0,0 +1,5 @@
+libvmod_cookie_la_SOURCES = \
+ vmod_cookie.c
+
+# Use vmodtool.py generated automake boilerplate
+include $(srcdir)/automake_boilerplate.am
diff --git a/lib/libvmod_cookie/automake_boilerplate.am b/lib/libvmod_cookie/automake_boilerplate.am
new file mode 100644
index 000000000..ded993389
--- /dev/null
+++ b/lib/libvmod_cookie/automake_boilerplate.am
@@ -0,0 +1,64 @@
+# Generated by vmodtool.py --boilerplate.
+
+AM_LDFLAGS = $(AM_LT_LDFLAGS)
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/bin/varnishd \
+ -I$(top_builddir)/include
+
+vmoddir = $(pkglibdir)/vmods
+vmodtool = $(top_srcdir)/lib/libvcc/vmodtool.py
+vmodtoolargs ?= --strict --boilerplate
+
+vmod_LTLIBRARIES = libvmod_cookie.la
+
+libvmod_cookie_la_CFLAGS = \
+ @SAN_CFLAGS@
+
+libvmod_cookie_la_LDFLAGS = \
+ -export-symbols-regex 'Vmod_cookie_Data' \
+ $(AM_LDFLAGS) \
+ $(VMOD_LDFLAGS) \
+ @SAN_LDFLAGS@
+
+nodist_libvmod_cookie_la_SOURCES = vcc_if.c vcc_if.h
+
+$(libvmod_cookie_la_OBJECTS): vcc_if.h
+
+vcc_if.h vmod_cookie.rst vmod_cookie.man.rst: vcc_if.c
+
+vcc_if.c: $(vmodtool) $(srcdir)/vmod.vcc
+ @PYTHON@ $(vmodtool) $(vmodtoolargs) $(srcdir)/vmod.vcc
+
+EXTRA_DIST = vmod.vcc automake_boilerplate.am
+
+CLEANFILES = $(builddir)/vcc_if.c $(builddir)/vcc_if.h \
+ $(builddir)/vmod_cookie.rst \
+ $(builddir)/vmod_cookie.man.rst
+
+TESTS = \
+ tests/cookie_b00000.vtc \
+ tests/cookie_b00001.vtc \
+ tests/cookie_b00002.vtc \
+ tests/cookie_b00003.vtc \
+ tests/cookie_b00004.vtc \
+ tests/cookie_b00005.vtc \
+ tests/cookie_b00006.vtc \
+ tests/cookie_b00007.vtc \
+ tests/cookie_b00008.vtc \
+ tests/cookie_b00009.vtc \
+ tests/cookie_b00010.vtc \
+ tests/cookie_b00011.vtc \
+ tests/cookie_b00012.vtc \
+ tests/cookie_b00013.vtc \
+ tests/cookie_r00028.vtc \
+ tests/cookie_v00000.vtc
+
+EXTRA_DIST += $(TESTS)
+
+vtc-refresh-tests:
+ @PYTHON@ $(vmodtool) $(vmodtoolargs) $(srcdir)/vmod.vcc
+ @cd $(top_builddir) && ./config.status --file=$(subdir)/Makefile
+
+include $(top_srcdir)/vtc.am
diff --git a/lib/libvmod_cookie/tests/cookie_b00000.vtc b/lib/libvmod_cookie/tests/cookie_b00000.vtc
new file mode 100644
index 000000000..a83633cfd
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00000.vtc
@@ -0,0 +1,22 @@
+varnishtest "Test vmod_cookie"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.set("cookie1", "cookie1BAD");
+ cookie.set("cookie2", "cookie2value");
+ cookie.set("cookie3", "cookie3value");
+ cookie.set("cookie4", "cookie4value");
+ cookie.set("cookie1", "cookie1value"); # overrides cookie1
+ cookie.delete("cookie2");
+ set resp.http.X-foo = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.X-foo == "cookie1=cookie1value; cookie3=cookie3value; cookie4=cookie4value;"
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00001.vtc b/lib/libvmod_cookie/tests/cookie_b00001.vtc
new file mode 100644
index 000000000..9b45f740e
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00001.vtc
@@ -0,0 +1,21 @@
+varnishtest "Test cookie.clean()"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.clean();
+ cookie.set("cookie1", "cookie1BAD");
+ set resp.http.X-foo = cookie.get_string();
+ cookie.clean();
+ set resp.http.X-bar = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq
+ rxresp
+ expect resp.http.X-foo == "cookie1=cookie1BAD;"
+ expect resp.http.X-bar == ""
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00002.vtc b/lib/libvmod_cookie/tests/cookie_b00002.vtc
new file mode 100644
index 000000000..d440f841d
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00002.vtc
@@ -0,0 +1,37 @@
+varnishtest "NULL/empty value checks"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ # nothing in here at this point.
+ set req.http.x-foo = cookie.get_string();
+
+ # XXX: We might want to revisit these assumptions since "="
+ # (empty name and empty value) is as correct as "name=value"
+ # for a cookie. See rfc6265 section 5.2 for reference.
+
+ # empty name
+ cookie.set("", "foo");
+
+ # empty value
+ cookie.set("cookie1", "");
+
+ # proper NULL
+ cookie.set(req.http.null, "foo");
+
+ # double delete
+ cookie.delete("cookie2");
+ cookie.delete("cookie2");
+
+ cookie.delete(req.http.null);
+ set resp.http.x-foo = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.x-foo == ""
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00003.vtc b/lib/libvmod_cookie/tests/cookie_b00003.vtc
new file mode 100644
index 000000000..af9653035
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00003.vtc
@@ -0,0 +1,31 @@
+varnishtest "Test cookie.keep()"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.set("biscuit", "standard");
+ cookie.set("bredela", "eggwhites");
+ cookie.set("chocolatechip", "verychippy");
+ cookie.set("empire", "jellytots");
+ cookie.keep("bredela,empire,baz");
+ set resp.http.X-foo = cookie.get_string();
+
+ # Test exotic admin-supplied filter strings.
+ cookie.parse("bredela=eggwhites; empire=jellytots;");
+ cookie.keep(",,,,bredela, ,empire,baz,");
+ set resp.http.X-bar = cookie.get_string();
+
+ cookie.keep(req.http.none);
+ set resp.http.X-baz = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.X-foo == "bredela=eggwhites; empire=jellytots;"
+ expect resp.http.X-bar == "bredela=eggwhites; empire=jellytots;"
+ expect resp.http.X-baz == ""
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00004.vtc b/lib/libvmod_cookie/tests/cookie_b00004.vtc
new file mode 100644
index 000000000..d996fbee2
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00004.vtc
@@ -0,0 +1,32 @@
+varnishtest "Test cookie.filter()"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.set("biscuit", "standard");
+ cookie.set("bredela", "eggwhites");
+ cookie.set("chocolatechip", "verychippy");
+ cookie.set("empire", "jellytots");
+ cookie.filter("bredela,empire,baz");
+ set resp.http.X-foo = cookie.get_string();
+
+ # Test exotic admin-supplied filter strings.
+ cookie.parse("bredela=eggwhites; empire=jellytots;");
+ cookie.filter(",,,,bredela, ,baz,");
+ set resp.http.X-bar = cookie.get_string();
+
+ cookie.parse("foo=bar; bar=baz;");
+ cookie.filter(req.http.none);
+ set resp.http.X-baz = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.X-foo == "biscuit=standard; chocolatechip=verychippy;"
+ expect resp.http.X-bar == "empire=jellytots;"
+ expect resp.http.X-baz == "foo=bar; bar=baz;"
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00005.vtc b/lib/libvmod_cookie/tests/cookie_b00005.vtc
new file mode 100644
index 000000000..7d8c4b7cd
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00005.vtc
@@ -0,0 +1,21 @@
+varnishtest "Test missing cookie"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.set("cookie1", "cookie1value");
+ cookie.set("cookie2", "cookie2value");
+ set resp.http.X-foo = cookie.get("cookie2");
+ # Make sure we handle this gracefully.
+ set resp.http.X-bar = "" + cookie.get("non-existing");
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.X-foo == "cookie2value"
+ expect resp.http.X-bar == ""
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00006.vtc b/lib/libvmod_cookie/tests/cookie_b00006.vtc
new file mode 100644
index 000000000..f171b5e20
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00006.vtc
@@ -0,0 +1,50 @@
+varnishtest "Test vmod_cookie concurrency"
+
+barrier b1 cond 2
+
+server s1 {
+ rxreq
+ barrier b1 sync
+ expect req.url == "/s1"
+ txresp
+} -start
+
+server s2 {
+ rxreq
+ expect req.url == "/s2"
+ txresp
+} -start
+
+varnish v1 -vcl+backend {
+ import cookie;
+
+ sub vcl_recv {
+ cookie.parse(req.http.cookie);
+
+ if (req.url == "/s1") {
+ set req.backend_hint = s1;
+ }
+ else {
+ set req.backend_hint = s2;
+ }
+ }
+
+ sub vcl_deliver {
+ set resp.http.x-val = cookie.get("a");
+ }
+} -start
+
+client c1 {
+ txreq -url "/s1" -hdr "Cookie: a=bar"
+ rxresp
+ expect resp.http.x-val == "bar"
+} -start
+
+client c2 {
+ barrier b1 sync
+ txreq -url "/s2" -hdr "Cookie: a=foo"
+ rxresp
+ expect resp.http.x-val == "foo"
+} -run
+
+client c1 -wait
diff --git a/lib/libvmod_cookie/tests/cookie_b00007.vtc b/lib/libvmod_cookie/tests/cookie_b00007.vtc
new file mode 100644
index 000000000..418e91120
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00007.vtc
@@ -0,0 +1,23 @@
+varnishtest "Test cookie.isset()"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.set("cookie1", "cookie1value");
+ set resp.http.does = cookie.isset("cookie1");
+ set resp.http.does-not = cookie.isset("non-existent");
+ set resp.http.null = cookie.isset("");
+ set resp.http.null2 = cookie.isset(req.http.probably-null);
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.does == "true"
+ expect resp.http.does-not == "false"
+ expect resp.http.null == "false"
+ expect resp.http.null2 == "false"
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00008.vtc b/lib/libvmod_cookie/tests/cookie_b00008.vtc
new file mode 100644
index 000000000..af1bfcae9
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00008.vtc
@@ -0,0 +1,25 @@
+varnishtest "Test large cookies"
+
+varnish v1 -cliok "param.set workspace_client 64k" -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.parse(req.http.cookie);
+ set resp.http.cookiestring = cookie.get_string();
+ set resp.http.cookie1 = cookie.isset("cookie1");
+ }
+} -start
+
+client c1 {
+ # Insanely long cookie name.
+ txreq -url "/" -hdr "Cookie: phohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1j=cookievalue"
+ rxresp
+ expect resp.http.cookiestring == "phohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1j=cookievalue;"
+
+ # Insane 6KB cookie value.
+ txreq -url "/" -hdr "Cookie: cookie1=foobarbazfoobarbazphohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1jphohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1jphohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1jphohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1jphohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1jphohx8aingie6Ide7peephie5paip6ang4thooh4ooquai8ohvah7eiqueeki8ooth7viequ0Tha5thewiSheih5jaimaiTahr1wi8WooQuoe7loothieThahweeneichoo8cufeelu3tie5cei1iShiemiezoofox6ahcaevaihocheungai2aeghaichaingee0EiGie3Ees5ujaem5uquahpieFeelei7Ohngei1afaTooph4aiquum1aewaidatheshuh1fohhoor0hoo6aeTeiy9xougahf3jeapooshuhoob5deiwareingahth7ahf2fafeer8Oobiewai3rei8ofejohjeiye4die8Na7ze6eixajauCairoth0lek8vioyuom6eih0egho2aingoo7coh1at3niochu6osahthi0ue1Luchae1eifeupiuwaa0raidiewaijese4oozee4eihie5shaBaoreacooNg8uW9eru9kigh3Feesi8iex2pu7ohfaiBiezael6ifaujiek4nutae1aalohchoteiPuaM2chiefaicaopheKohsh6Ho1wiephieseef1daj3Pahfie2ooch8shaing5baXeiLiep9lahfe9uDaxeehielais2eix3iekiew8aiter9Foo8noo2hae7ohdie1iB7hoop3podeengooSothoojui4AhXu5Nain8ohqu8if1ue5iTheimei5oghie9sheiv4Hejah1veixahcaixie8ahyieT8Phay4daeTei1aRiemae6oicheef2miiNuoxeil1kae2nea1roh9Rei1keiwaT2eoJaiNgie0den6aideif3uechaishaec4cai2eozieb9aeN9sai9ahnielohdaeGh2kaeleiteitai0ietoo7eiCha0baiW7dai0im1jul5OWijaLo2ohh3kooxu2oFah3loob6feiw7pie9eighu8ik4chae0Athou2fah5ieQuuic0Mu1j;"
+ rxresp
+ # We support long cookie values, should be fine.
+ expect resp.http.cookie1 == "true"
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00009.vtc b/lib/libvmod_cookie/tests/cookie_b00009.vtc
new file mode 100644
index 000000000..de7a8d731
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00009.vtc
@@ -0,0 +1,51 @@
+varnishtest "Test cookie parser"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.parse(req.http.cookie);
+ set resp.http.X-foo = cookie.get_string();
+ }
+
+} -start
+
+client c1 {
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+} -run
+
+client c2 {
+ txreq -hdr "Cookie: __utmc=253898641; __utma=253898641.654622101.1372224466.1372224466.1372224466.1; __utmb=253898641.44.10.1372224466; __utmz=253898641.1372224466.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=index%2Bof%2Bccnp%2Bpdf"
+ rxresp
+ expect resp.http.X-foo == "__utmc=253898641; __utma=253898641.654622101.1372224466.1372224466.1372224466.1; __utmb=253898641.44.10.1372224466; __utmz=253898641.1372224466.1.1.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=index%2Bof%2Bccnp%2Bpdf;"
+} -run
+
+client c3 {
+ txreq -hdr "Cookie: "
+ rxresp
+ expect resp.http.X-foo == ""
+} -run
+
+# An empty cookie is a non-existing cookie for us.
+client c4 {
+ txreq -hdr "Cookie: emptycookie="
+ rxresp
+ expect resp.http.X-foo == ""
+} -run
+
+# A single cookie should also work.
+client c5 {
+ txreq -hdr "Cookie: cookie1=foobarbaz"
+ rxresp
+ expect resp.http.X-foo == "cookie1=foobarbaz;"
+} -run
+
+# Don't overflow the buffer with an edge case
+client c6 {
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128a;=" -hdr "X-Not-Cookie: sessionid=a707505310ddf259bb290d3ca63fc561"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128a;"
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00010.vtc b/lib/libvmod_cookie/tests/cookie_b00010.vtc
new file mode 100644
index 000000000..fc8cbdd35
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00010.vtc
@@ -0,0 +1,17 @@
+varnishtest "Test rfc1123 string formatting function"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ set resp.http.x-date = cookie.format_rfc1123(now, 1d);
+ }
+} -start
+
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.date != <undef>
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00011.vtc b/lib/libvmod_cookie/tests/cookie_b00011.vtc
new file mode 100644
index 000000000..f9d394b3f
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00011.vtc
@@ -0,0 +1,24 @@
+varnishtest "Test cookie.get_re()"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.set("biscuit", "standard");
+ cookie.set("chocolatechip", "verychippy");
+ cookie.set("empire", "jellytots");
+
+ set resp.http.X-first = cookie.get_re("DOES_NOT_EXIST");
+ set resp.http.X-second = cookie.get_re("biscuit");
+ set resp.http.X-third = cookie.get_re("DOES_NOT_EXIST_EITHER");
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.X-first == ""
+ expect resp.http.X-second == "standard"
+ expect resp.http.X-third == ""
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00012.vtc b/lib/libvmod_cookie/tests/cookie_b00012.vtc
new file mode 100644
index 000000000..e0584af98
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00012.vtc
@@ -0,0 +1,33 @@
+varnishtest "Test cookie.filter_re()"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.set("biscuit", "standard");
+ cookie.set("bredela", "eggwhites");
+ cookie.set("empire", "jellytots");
+
+ set resp.http.X-foo = cookie.get_string();
+ cookie.filter_re("^NOT-MATCHING-ANYTHING$");
+ set resp.http.X-bar = cookie.get_string();
+
+ cookie.filter_re("^bredela");
+ set resp.http.X-baz = cookie.get_string();
+
+ cookie.filter_re(".*");
+ set resp.http.X-qux = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.X-foo == resp.http.X-bar
+
+ expect resp.http.X-baz != resp.http.X-foo
+ expect resp.http.X-baz == "biscuit=standard; empire=jellytots;"
+
+ expect resp.http.X-qux == ""
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_b00013.vtc b/lib/libvmod_cookie/tests/cookie_b00013.vtc
new file mode 100644
index 000000000..31624563a
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_b00013.vtc
@@ -0,0 +1,23 @@
+varnishtest "Test cookie.keep_re()"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.parse("foo=bar; baz=qux;");
+ cookie.keep_re("NOTHING_MATCHES_SO_NOTHING_KEPT$");
+ set resp.http.X-empty = cookie.get_string();
+
+ cookie.parse("biscuit=standard; bredela=eggwhites; empire=jellytots;");
+ cookie.keep_re("^b");
+ set resp.http.X-bees = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq -url "/"
+ rxresp
+ expect resp.http.X-empty == ""
+ expect resp.http.X-bees == "biscuit=standard; bredela=eggwhites;"
+} -run
diff --git a/lib/libvmod_cookie/tests/cookie_r00028.vtc b/lib/libvmod_cookie/tests/cookie_r00028.vtc
new file mode 100644
index 000000000..4e606887b
--- /dev/null
+++ b/lib/libvmod_cookie/tests/cookie_r00028.vtc
@@ -0,0 +1,101 @@
+varnishtest "Test issue https://github.com/varnish/varnish-modules/issues/28"
+
+varnish v1 -vcl {
+ import cookie;
+ backend be none;
+ sub vcl_recv { return (synth(200)); }
+ sub vcl_synth {
+ cookie.parse(req.http.cookie);
+ set resp.http.X-foo = cookie.get_string();
+ }
+} -start
+
+client c1 {
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+
+}
+
+client c2 {
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+ txreq -hdr "Cookie: csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560"
+ rxresp
+ expect resp.http.X-foo == "csrftoken=0e0c3616e41a6bd561b72b7f5fc1128f; sessionid=a707505310ddf259bb290d3ca63fc560;"
+
+}
+
+client c1 -repeat 2 -run
+client c2 -repeat 2 -run
diff --git a/lib/libvmod_cookie/vmod.vcc b/lib/libvmod_cookie/vmod.vcc
new file mode 100644
index 000000000..3cbdfe261
--- /dev/null
+++ b/lib/libvmod_cookie/vmod.vcc
@@ -0,0 +1,219 @@
+$Module cookie 3 "Varnish Cookie Module"
+
+DESCRIPTION
+===========
+
+Handle HTTP cookies easier in Varnish VCL.
+
+Parses a cookie header into an internal data store, where per-cookie
+get/set/delete functions are available. A filter_except() method removes all
+but a set comma-separated list of cookies. A filter() method removes a comma-
+separated list of cookies.
+
+Regular expressions can be used for either selecting cookies, deleting matching
+cookies and deleting non-matching cookie names.
+
+A convenience function for formatting the Set-Cookie Expires date field
+is also included.
+
+The state loaded with cookie.parse() has a lifetime of the current request
+or backend request context. To pass variables to the backend request, store
+the contents as fake bereq headers.
+
+Filtering example::
+
+ import cookie;
+
+ sub vcl_recv {
+ if (req.http.cookie) {
+ cookie.parse(req.http.cookie);
+ # Either delete the ones you want to get rid of:
+ cookie.delete("cookie2");
+ # or delete all but a few:
+ cookie.keep("SESSIONID,PHPSESSID");
+
+ # Store it back into req so it will be passed to the backend.
+ set req.http.cookie = cookie.get_string();
+
+ # If empty, unset so the builtin VCL can consider it for caching.
+ if (req.http.cookie == "") {
+ unset req.http.cookie;
+ }
+ }
+ }
+
+$ABI strict
+$Function VOID clean(PRIV_TASK)
+
+Clean up previously parsed cookies. It is not necessary to run clean()
+in normal operations.
+
+Example::
+
+ sub vcl_recv {
+ cookie.clean();
+ }
+
+$Function VOID delete(PRIV_TASK, STRING cookiename)
+
+Delete ``cookiename`` from internal vmod storage if it exists.
+
+Example::
+
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2;");
+ cookie.delete("cookie2");
+ # get_string() will now yield "cookie1: value1";
+ }
+
+$Function VOID filter(PRIV_TASK, STRING filterstring)
+
+Delete all cookies from internal vmod storage that are in the
+comma-separated argument cookienames.
+
+Example::
+
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2; cookie3: value3");
+ cookie.filter("cookie1,cookie2");
+ # get_string() will now yield
+ # "cookie3: value3";
+ }
+
+
+$Function VOID filter_re(PRIV_TASK, PRIV_CALL, STRING expression)
+
+Delete all cookies from internal vmod storage that matches the
+regular expression ``expression``.
+
+Example::
+
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2; cookie3: value3");
+ cookie.filter_re("^cookie[12]$");
+ # get_string() will now yield
+ # "cookie3: value3";
+ }
+
+$Function VOID keep(PRIV_TASK, STRING filterstring)
+
+Delete all cookies from internal vmod storage that is not in the
+comma-separated argument cookienames.
+
+Example::
+
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2; cookie3: value3");
+ cookie.keep("cookie1,cookie2");
+ # get_string() will now yield
+ # "cookie1: value1; cookie2: value2;";
+ }
+
+$Function VOID keep_re(PRIV_TASK, PRIV_CALL, STRING expression)
+
+Delete all cookies from internal vmod storage that does not match
+expression ``expression``.
+
+Example::
+
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2; cookie3: value3");
+ cookie.keep_re("^cookie1,cookie2");
+ # get_string() will now yield
+ # "cookie1: value1; cookie2: value2;";
+ }
+
+
+
+
+$Function STRING format_rfc1123(TIME now, DURATION timedelta)
+
+Get a RFC1123 formatted date string suitable for inclusion in a
+Set-Cookie response header.
+
+Care should be taken if the response has multiple Set-Cookie headers.
+In that case the header vmod should be used.
+
+Example::
+
+ sub vcl_deliver {
+ # Set a userid cookie on the client that lives for 5 minutes.
+ set resp.http.Set-Cookie = "userid=" + req.http.userid +
+ "; Expires=" + cookie.format_rfc1123(now, 5m) + "; httpOnly";
+ }
+
+$Function STRING get(PRIV_TASK, STRING cookiename)
+
+Get the value of ``cookiename``, as stored in internal vmod storage. If
+``cookiename`` does not exist an empty string is returned.
+
+Example::
+
+ import std;
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2;");
+ std.log("cookie1 value is: " + cookie.get("cookie1"));
+ }
+
+$Function STRING get_re(PRIV_TASK, PRIV_CALL, STRING expression)
+
+Get the value of the first cookie in internal vmod storage that matches
+regular expression ``expression``. If nothing matches, an empty string
+is returned.
+
+Example::
+
+ import std;
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2;");
+ std.log("cookie1 value is: " + cookie.get_re("^cookie1$"));
+ }
+
+$Function STRING get_string(PRIV_TASK)
+
+Get a Cookie string value with all cookies in internal vmod storage. Does
+not modify internal storage.
+
+Example::
+
+ sub vcl_recv {
+ cookie.parse(req.http.cookie);
+ cookie.filter_except("SESSIONID,PHPSESSID");
+ set req.http.cookie = cookie.get_string();
+ }
+
+$Function BOOL isset(PRIV_TASK, STRING cookiename)
+
+Check if ``cookiename`` is set in the internal vmod storage.
+
+Example::
+
+ import std;
+ sub vcl_recv {
+ cookie.parse("cookie1: value1; cookie2: value2;");
+ if (cookie.isset("cookie2")) {
+ std.log("cookie2 is set.");
+ }
+ }
+
+$Function VOID parse(PRIV_TASK, STRING cookieheader)
+
+Parse the cookie string in ``cookieheader``. If state already exists,
+``clean()`` will be run first.
+
+Example::
+
+ sub vcl_recv {
+ cookie.parse(req.http.Cookie);
+ }
+
+$Function VOID set(PRIV_TASK, STRING cookiename, STRING value)
+
+Set the internal vmod storage for ``cookiename`` to ``value``.
+
+Example::
+
+ sub vcl_recv {
+ cookie.set("cookie1", "value1");
+ std.log("cookie1 value is: " + cookie.get("cookie1"));
+ }
diff --git a/lib/libvmod_cookie/vmod_cookie.c b/lib/libvmod_cookie/vmod_cookie.c
new file mode 100644
index 000000000..4187a1fb1
--- /dev/null
+++ b/lib/libvmod_cookie/vmod_cookie.c
@@ -0,0 +1,522 @@
+/*-
+ * Copyright (c) 2012-2020 Varnish Software
+ *
+ * Author: Lasse Karstensen <lasse.karstensen at gmail.com>
+ * Author: Lasse Karstensen <lkarsten at varnish-software.com>
+ * Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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 AUTHOR 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 AUTHOR 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.
+ *
+ * Cookie VMOD that simplifies handling of the Cookie request header.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <pthread.h>
+
+#include <cache/cache.h>
+
+#include <vsb.h>
+#include <vre.h>
+
+#include "vcc_if.h"
+
+#define VRE_MAX_GROUPS 8
+
+enum filter_action {
+ blacklist,
+ whitelist
+};
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+
+struct cookie {
+ unsigned magic;
+#define VMOD_COOKIE_ENTRY_MAGIC 0x3BB41543
+ char *name;
+ char *value;
+ VTAILQ_ENTRY(cookie) list;
+};
+
+/* A structure to represent both whitelists and blacklists */
+struct matchlist {
+ char *name;
+ VTAILQ_ENTRY(matchlist) list;
+};
+
+struct vmod_cookie {
+ unsigned magic;
+#define VMOD_COOKIE_MAGIC 0x4EE5FB2E
+ VTAILQ_HEAD(, cookie) cookielist;
+};
+
+static void
+cobj_free(void *p)
+{
+ struct vmod_cookie *vcp;
+
+ CAST_OBJ_NOTNULL(vcp, p, VMOD_COOKIE_MAGIC);
+ FREE_OBJ(vcp);
+}
+
+static struct vmod_cookie *
+cobj_get(struct vmod_priv *priv)
+{
+ struct vmod_cookie *vcp;
+
+ if (priv->priv == NULL) {
+ ALLOC_OBJ(vcp, VMOD_COOKIE_MAGIC);
+ AN(vcp);
+ VTAILQ_INIT(&vcp->cookielist);
+ priv->priv = vcp;
+ priv->free = cobj_free;
+ } else
+ CAST_OBJ_NOTNULL(vcp, priv->priv, VMOD_COOKIE_MAGIC);
+
+ return (vcp);
+}
+
+VCL_VOID
+vmod_parse(VRT_CTX, struct vmod_priv *priv, VCL_STRING cookieheader)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+ char *name, *value;
+ const char *p, *sep;
+ int i = 0;
+
+ if (cookieheader == NULL || *cookieheader == '\0') {
+ VSLb(ctx->vsl, SLT_Debug, "cookie: nothing to parse");
+ return;
+ }
+
+ /* If called twice during the same request, clean out old state. */
+ if (!VTAILQ_EMPTY(&vcp->cookielist))
+ vmod_clean(ctx, priv);
+
+ p = cookieheader;
+ while (*p != '\0') {
+ while (isspace(*p))
+ p++;
+ sep = strchr(p, '=');
+ if (sep == NULL)
+ break;
+ name = strndup(p, pdiff(p, sep));
+ p = sep + 1;
+
+ sep = p;
+ while (*sep != '\0' && *sep != ';')
+ sep++;
+ value = strndup(p, pdiff(p, sep));
+
+ vmod_set(ctx, priv, name, value);
+ free(name);
+ free(value);
+ i++;
+ if (*sep == '\0')
+ break;
+ p = sep + 1;
+ }
+
+ VSLb(ctx->vsl, SLT_Debug, "cookie: parsed %i cookies.", i);
+}
+
+static struct cookie *
+find_cookie(struct vmod_cookie *vcp, VCL_STRING name)
+{
+ struct cookie *cookie;
+
+ VTAILQ_FOREACH(cookie, &vcp->cookielist, list) {
+ CHECK_OBJ_NOTNULL(cookie, VMOD_COOKIE_ENTRY_MAGIC);
+ if (!strcmp(cookie->name, name))
+ break;
+ }
+ return (cookie);
+}
+
+VCL_VOID
+vmod_set(VRT_CTX, struct vmod_priv *priv, VCL_STRING name, VCL_STRING value)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+ struct cookie *cookie;
+ char *p;
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
+
+ /* Empty cookies should be ignored. */
+ if (name == NULL || *name == '\0')
+ return;
+ if (value == NULL || *value == '\0')
+ return;
+
+ cookie = find_cookie(vcp, name);
+ if (cookie != NULL) {
+ p = WS_Printf(ctx->ws, "%s", value);
+ if (p == NULL) {
+ VSLb(ctx->vsl, SLT_Error,
+ "cookie: Workspace overflow in set()");
+ } else
+ cookie->value = p;
+ return;
+ }
+
+ cookie = WS_Alloc(ctx->ws, sizeof *cookie);
+ if (cookie == NULL) {
+ VSLb(ctx->vsl, SLT_Error,
+ "cookie: unable to get storage for cookie");
+ return;
+ }
+ INIT_OBJ(cookie, VMOD_COOKIE_ENTRY_MAGIC);
+ cookie->name = WS_Printf(ctx->ws, "%s", name);
+ cookie->value = WS_Printf(ctx->ws, "%s", value);
+ if (cookie->name == NULL || cookie->value == NULL) {
+ VSLb(ctx->vsl, SLT_Error,
+ "cookie: unable to get storage for cookie");
+ return;
+ }
+ VTAILQ_INSERT_TAIL(&vcp->cookielist, cookie, list);
+}
+
+VCL_BOOL
+vmod_isset(VRT_CTX, struct vmod_priv *priv, const char *name)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+ struct cookie *cookie;
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ if (name == NULL || *name == '\0')
+ return (0);
+
+ cookie = find_cookie(vcp, name);
+ return (cookie ? 1 : 0);
+}
+
+VCL_STRING
+vmod_get(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+ struct cookie *cookie;
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ if (name == NULL || *name == '\0')
+ return (NULL);
+
+ cookie = find_cookie(vcp, name);
+ return (cookie ? cookie->value : NULL);
+}
+
+
+static vre_t *
+compile_re(VRT_CTX, VCL_STRING expression) {
+ vre_t *vre;
+ const char *error;
+ int erroroffset;
+
+ vre = VRE_compile(expression, 0, &error, &erroroffset);
+ if (vre == NULL) {
+ VSLb(ctx->vsl, SLT_Error,
+ "cookie: PCRE compile error at char %i: %s",
+ erroroffset, error);
+ }
+ return (vre);
+}
+
+static void
+free_re(void *priv)
+{
+ vre_t *vre;
+
+ AN(priv);
+ vre = priv;
+ VRE_free(&vre);
+ AZ(vre);
+}
+
+VCL_STRING
+vmod_get_re(VRT_CTX, struct vmod_priv *priv, struct vmod_priv *priv_call,
+ VCL_STRING expression)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+ int i, ovector[VRE_MAX_GROUPS];
+ struct cookie *cookie = NULL;
+ struct cookie *current;
+ vre_t *vre = NULL;
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ if (expression == NULL || *expression == '\0')
+ return (NULL);
+
+ if (priv_call->priv == NULL) {
+ AZ(pthread_mutex_lock(&mtx));
+ vre = compile_re(ctx, expression);
+ if (vre == NULL) {
+ AZ(pthread_mutex_unlock(&mtx));
+ return (NULL);
+ }
+
+ priv_call->priv = vre;
+ priv_call->free = free_re;
+ AZ(pthread_mutex_unlock(&mtx));
+ }
+
+ VTAILQ_FOREACH(current, &vcp->cookielist, list) {
+ CHECK_OBJ_NOTNULL(current, VMOD_COOKIE_ENTRY_MAGIC);
+ VSLb(ctx->vsl, SLT_Debug, "cookie: checking %s", current->name);
+ i = VRE_exec(vre, current->name, strlen(current->name), 0, 0,
+ ovector, VRE_MAX_GROUPS, NULL);
+ if (i < 0)
+ continue;
+
+ VSLb(ctx->vsl, SLT_Debug, "cookie: %s is a match for regex '%s'",
+ current->name, expression);
+ cookie = current;
+ break;
+ }
+
+ return (cookie ? cookie->value : NULL);
+}
+
+VCL_VOID
+vmod_delete(VRT_CTX, struct vmod_priv *priv, VCL_STRING name)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+ struct cookie *cookie;
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ if (name == NULL || *name == '\0')
+ return;
+
+ cookie = find_cookie(vcp, name);
+
+ if (cookie != NULL)
+ VTAILQ_REMOVE(&vcp->cookielist, cookie, list);
+}
+
+VCL_VOID
+vmod_clean(VRT_CTX, struct vmod_priv *priv)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ AN(vcp);
+ VTAILQ_INIT(&vcp->cookielist);
+}
+
+static void
+filter_cookies(struct vmod_priv *priv, VCL_STRING list_s,
+ enum filter_action mode)
+{
+ struct cookie *cookieptr, *safeptr;
+ struct vmod_cookie *vcp = cobj_get(priv);
+ struct matchlist *mlentry, *mlsafe;
+ char const *p = list_s, *q;
+ int matched = 0;
+ VTAILQ_HEAD(, matchlist) matchlist_head;
+
+ VTAILQ_INIT(&matchlist_head);
+
+ /* Parse the supplied list. */
+ while (p && *p != '\0') {
+ while (isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+
+ q = p;
+ while (*q != '\0' && *q != ',')
+ q++;
+
+ if (q == p) {
+ p++;
+ continue;
+ }
+
+ /* XXX: can we reserve/release lumps of txt instead of
+ * malloc/free?
+ */
+ mlentry = malloc(sizeof *mlentry);
+ AN(mlentry);
+ mlentry->name = strndup(p, q - p);
+ AN(mlentry->name);
+
+ VTAILQ_INSERT_TAIL(&matchlist_head, mlentry, list);
+
+ p = q;
+ if (*p != '\0')
+ p++;
+ }
+
+ /* Filter existing cookies that either aren't in the whitelist or
+ * are in the blacklist (depending on the filter_action) */
+ VTAILQ_FOREACH_SAFE(cookieptr, &vcp->cookielist, list, safeptr) {
+ CHECK_OBJ_NOTNULL(cookieptr, VMOD_COOKIE_ENTRY_MAGIC);
+ matched = 0;
+
+ VTAILQ_FOREACH(mlentry, &matchlist_head, list) {
+ if (strcmp(cookieptr->name, mlentry->name) == 0) {
+ matched = 1;
+ break;
+ }
+ }
+ if (matched != mode)
+ VTAILQ_REMOVE(&vcp->cookielist, cookieptr, list);
+ }
+
+ VTAILQ_FOREACH_SAFE(mlentry, &matchlist_head, list, mlsafe) {
+ VTAILQ_REMOVE(&matchlist_head, mlentry, list);
+ free(mlentry->name);
+ free(mlentry);
+ }
+}
+
+VCL_VOID
+vmod_keep(VRT_CTX, struct vmod_priv *priv, VCL_STRING whitelist_s)
+{
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ filter_cookies(priv, whitelist_s, whitelist);
+}
+
+
+VCL_VOID
+vmod_filter(VRT_CTX, struct vmod_priv *priv, VCL_STRING blacklist_s)
+{
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ filter_cookies(priv, blacklist_s, blacklist);
+}
+
+static VCL_VOID
+re_filter(VRT_CTX, struct vmod_priv *priv, struct vmod_priv *priv_call,
+ VCL_STRING expression, enum filter_action mode)
+{
+ struct vmod_cookie *vcp = cobj_get(priv);
+ struct cookie *current, *safeptr;
+ int i, ovector[VRE_MAX_GROUPS];
+ vre_t *vre;
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ if (priv_call->priv == NULL) {
+ AZ(pthread_mutex_lock(&mtx));
+ vre = compile_re(ctx, expression);
+ if (vre == NULL) {
+ AZ(pthread_mutex_unlock(&mtx));
+ return; // Not much else to do, error already logged.
+ }
+
+ priv_call->priv = vre;
+ priv_call->free = free_re;
+ AZ(pthread_mutex_unlock(&mtx));
+ }
+
+ VTAILQ_FOREACH_SAFE(current, &vcp->cookielist, list, safeptr) {
+ CHECK_OBJ_NOTNULL(current, VMOD_COOKIE_ENTRY_MAGIC);
+
+ i = VRE_exec(priv_call->priv, current->name,
+ strlen(current->name), 0, 0, ovector, VRE_MAX_GROUPS, NULL);
+
+ switch (mode) {
+ case blacklist:
+ if (i < 0)
+ continue;
+ VSLb(ctx->vsl, SLT_Debug,
+ "Removing matching cookie %s (value: %s)",
+ current->name, current->value);
+ VTAILQ_REMOVE(&vcp->cookielist, current, list);
+ break;
+ case whitelist:
+ if (i >= 0) {
+ VSLb(ctx->vsl, SLT_Debug,
+ "Cookie %s matches expression '%s'",
+ current->name, expression);
+ continue;
+ }
+
+ VSLb(ctx->vsl, SLT_Debug,
+ "Removing cookie %s (value: %s)",
+ current->name, current->value);
+ VTAILQ_REMOVE(&vcp->cookielist, current, list);
+ break;
+ default:
+ WRONG("invalid mode");
+ }
+ }
+}
+
+
+VCL_VOID
+vmod_keep_re(VRT_CTX, struct vmod_priv *priv, struct vmod_priv *priv_call,
+ VCL_STRING expression)
+{
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ re_filter(ctx, priv, priv_call, expression, whitelist);
+}
+
+
+VCL_VOID
+vmod_filter_re(VRT_CTX, struct vmod_priv *priv, struct vmod_priv *priv_call,
+ VCL_STRING expression)
+{
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ re_filter(ctx, priv, priv_call, expression, blacklist);
+}
+
+
+VCL_STRING
+vmod_get_string(VRT_CTX, struct vmod_priv *priv)
+{
+ struct cookie *curr;
+ struct vsb output[1];
+ char *res;
+ struct vmod_cookie *vcp = cobj_get(priv);
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
+ WS_VSB_new(output, ctx->ws);
+
+ VTAILQ_FOREACH(curr, &vcp->cookielist, list) {
+ CHECK_OBJ_NOTNULL(curr, VMOD_COOKIE_ENTRY_MAGIC);
+ AN(curr->name);
+ AN(curr->value);
+ VSB_printf(output, "%s%s=%s;",
+ (curr == VTAILQ_FIRST(&vcp->cookielist)) ? "" : " ",
+ curr->name, curr->value);
+ }
+ res = WS_VSB_finish(output, ctx->ws, NULL);
+ if (res == NULL)
+ VSLb(ctx->vsl, SLT_Error, "cookie: Workspace overflow");
+ return (res);
+}
+
+VCL_STRING
+vmod_format_rfc1123(VRT_CTX, VCL_TIME ts, VCL_DURATION duration)
+{
+
+ CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+ return (VRT_TIME_string(ctx, ts + duration));
+}
diff --git a/man/Makefile.am b/man/Makefile.am
index 20b17dce9..c341ddc87 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -15,6 +15,7 @@ dist_man_MANS = \
varnishtest.1 \
vtc.7 \
varnishtop.1 \
+ vmod_cookie.3 \
vmod_directors.3 \
vmod_purge.3 \
vmod_std.3 \
@@ -91,6 +92,9 @@ varnishhist.1: \
$(top_builddir)/doc/sphinx/include/varnishhist_synopsis.rst
$(BUILD_MAN) $(top_srcdir)/doc/sphinx/reference/varnishhist.rst $@
+vmod_cookie.3: $(top_builddir)/lib/libvmod_cookie/vmod_cookie.man.rst
+ $(BUILD_MAN) $? $@
+
vmod_directors.3: $(top_builddir)/lib/libvmod_directors/vmod_directors.man.rst
$(BUILD_MAN) $? $@
More information about the varnish-commit
mailing list