[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