[master] 60cc5f9 Add VMOD blob.

Geoff Simmons geoff at uplex.de
Mon Sep 4 14:59:11 CEST 2017


commit 60cc5f9b33a50732d4e44a996498d60bdb0843dc
Author: Geoff Simmons <geoff at uplex.de>
Date:   Wed Aug 30 18:06:05 2017 +0200

    Add VMOD blob.
    
    References #2402

diff --git a/bin/varnishtest/tests/m00032.vtc b/bin/varnishtest/tests/m00032.vtc
new file mode 100644
index 0000000..711df62
--- /dev/null
+++ b/bin/varnishtest/tests/m00032.vtc
@@ -0,0 +1,26 @@
+varnishtest "VMOD blob vcl.use and .discard"
+
+server s1 {
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+} -start
+
+varnish v1 -vcl+backend {
+}
+
+varnish v1 -cli "vcl.list"
+varnish v1 -cli "vcl.use vcl1"
+
+varnish v1 -cli "vcl.use vcl2"
+varnish v1 -cli "vcl.use vcl1"
+
+varnish v1 -expect vmods == 1
+
+varnish v1 -cli "vcl.show vcl1"
+varnish v1 -cli "vcl.use vcl2"
+varnish v1 -cli "vcl.discard vcl1"
+varnish v1 -cli "vcl.list"
+
+varnish v1 -expect vmods == 0
diff --git a/bin/varnishtest/tests/m00033.vtc b/bin/varnishtest/tests/m00033.vtc
new file mode 100644
index 0000000..11f36e6
--- /dev/null
+++ b/bin/varnishtest/tests/m00033.vtc
@@ -0,0 +1,70 @@
+varnishtest "VMOD blob IDENTITY encode and decode"
+
+varnish v1 -vcl {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set resp.http.id =
+	      blob.encode(IDENTITY, blob.decode(IDENTITY,
+			      "The quick brown fox jumps over the lazy dog"));
+
+	    set resp.http.hobbes =
+	      blob.encode(IDENTITY, blob.decode(IDENTITY,
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."}));
+
+	    set resp.http.list =
+	      blob.encode(IDENTITY, blob.decode(IDENTITY,
+			      "" + req.http.unset + req.url +
+			      "The quick brown fox jumps over " +
+			      req.http.unset + "" + req.http.unset + "" +
+			      "the lazy dog" + req.url + req.http.unset + ""));
+
+	    set resp.http.empty =
+	      blob.encode(IDENTITY, blob.decode(IDENTITY, ""));
+
+	    set resp.http.undef =
+	      blob.encode(IDENTITY, blob.decode(IDENTITY,
+						      req.http.unset));
+
+	    set resp.http.emptylist =
+	      blob.encode(IDENTITY, blob.decode(IDENTITY,
+			      req.http.unset + "" + req.http.unset + ""));
+
+	    set resp.http.param =
+	      blob.encode(blob=
+			      blob.decode(encoded=
+				 "The quick brown fox jumps over the lazy dog",
+					    decoding=IDENTITY),
+			      encoding=IDENTITY);
+
+	    set resp.http.paramlist =
+	      blob.encode(IDENTITY, blob.decode(encoded=
+			      "" + req.http.unset + req.url +
+			      "The quick brown fox jumps over " +
+			      req.http.unset + "" + req.http.unset + "" +
+			      "the lazy dog" + req.url + req.http.unset + "",
+			      decoding=IDENTITY));
+
+	    set resp.http.truncated =
+              blob.encode(IDENTITY, blob.decode(HEX, "666f6f00626172"));
+	}
+} -start
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.id == "The quick brown fox jumps over the lazy dog"
+	expect resp.http.hobbes == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.list == "/The quick brown fox jumps over the lazy dog/"
+	expect resp.http.empty == ""
+	expect resp.http.undef == ""
+	expect resp.http.emptylist == ""
+	expect resp.http.param == "The quick brown fox jumps over the lazy dog"
+	expect resp.http.paramlist == "/The quick brown fox jumps over the lazy dog/"
+	expect resp.http.truncated == "foo"
+} -run
diff --git a/bin/varnishtest/tests/m00034.vtc b/bin/varnishtest/tests/m00034.vtc
new file mode 100644
index 0000000..24da94c
--- /dev/null
+++ b/bin/varnishtest/tests/m00034.vtc
@@ -0,0 +1,66 @@
+varnishtest "VMOD blob IDENTITY decode_n()"
+
+varnish v1 -vcl {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set resp.http.id =
+	      blob.encode(IDENTITY, blob.decode_n(5, IDENTITY,
+			      "The quick brown fox jumps over the lazy dog"));
+
+	    set resp.http.hobbes =
+	      blob.encode(IDENTITY, blob.decode_n(5, IDENTITY,
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."}));
+
+	    set resp.http.list =
+	      blob.encode(IDENTITY, blob.decode_n(6, IDENTITY,
+			      "" + req.http.unset + req.url +
+			      "The quick brown fox jumps over " +
+			      req.http.unset + "" + req.http.unset + "" +
+			      "the lazy dog" + req.url + req.http.unset + ""));
+
+	    set resp.http.empty =
+	      blob.encode(IDENTITY, blob.decode_n(5, IDENTITY, ""));
+
+	    set resp.http.undef =
+	      blob.encode(IDENTITY, blob.decode_n(5, IDENTITY,
+						      req.http.unset));
+
+	    set resp.http.emptylist =
+	      blob.encode(IDENTITY, blob.decode_n(5, IDENTITY,
+			      req.http.unset + "" + req.http.unset + ""));
+
+	    set resp.http.param =
+	      blob.encode(blob=
+			      blob.decode_n(encoded=
+				 "The quick brown fox jumps over the lazy dog",
+					    n=7, decoding=IDENTITY),
+			      encoding=IDENTITY);
+
+	    set resp.http.paramlist =
+	      blob.encode(IDENTITY, blob.decode_n(encoded=
+			      "" + req.http.unset + req.url +
+			      "The quick brown fox jumps over " +
+			      req.http.unset + "" + req.http.unset + "" +
+			      "the lazy dog" + req.url + req.http.unset + "",
+			      decoding=IDENTITY, n=37));
+	}
+} -start
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.id == "The q"
+	expect resp.http.hobbes == "Man i"
+	expect resp.http.list == "/The q"
+	expect resp.http.empty == ""
+	expect resp.http.undef == ""
+	expect resp.http.emptylist == ""
+	expect resp.http.param == "The qui"
+	expect resp.http.paramlist == "/The quick brown fox jumps over the l"
+} -run
diff --git a/bin/varnishtest/tests/m00035.vtc b/bin/varnishtest/tests/m00035.vtc
new file mode 100644
index 0000000..c9210c7
--- /dev/null
+++ b/bin/varnishtest/tests/m00035.vtc
@@ -0,0 +1,196 @@
+varnishtest "VMOD blob hex encode and decode"
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.pangram = "The quick brown fox jumps over the lazy dog";
+	    set req.http.hobbes =
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."};
+	    # base64 encoding of all 256 byte values in ascending order
+	    set req.http.b64all =
+{"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="};
+
+	    set resp.http.hexlc =
+              blob.encode(HEXLC,
+			      blob.decode(IDENTITY, req.http.pangram));
+
+	    set resp.http.hexuc =
+              blob.encode(HEXUC,
+			      blob.decode(IDENTITY, req.http.pangram));
+
+            set resp.http.hobbeslc =
+	      blob.encode(HEXLC, blob.decode(IDENTITY, req.http.hobbes));
+
+	    set resp.http.hobbesuc =
+	      blob.encode(HEXUC, blob.decode(IDENTITY, req.http.hobbes));
+
+            set resp.http.all-lc =
+	      blob.encode(HEXLC, blob.decode(BASE64, req.http.b64all));
+
+	    set resp.http.all-uc =
+	      blob.encode(HEXUC, blob.decode(BASE64, req.http.b64all));
+
+            set resp.http.empty-lc =
+	      blob.encode(HEXLC, blob.decode(IDENTITY, ""));
+
+	    set resp.http.empty-uc =
+	      blob.encode(HEXUC, blob.decode(IDENTITY, ""));
+
+	    set resp.http.hexlcparam =
+	      blob.encode(blob=blob.decode(IDENTITY, req.http.pangram),
+			     encoding=HEXLC);
+
+	    set resp.http.hexucparam =
+	      blob.encode(blob=blob.decode(IDENTITY, req.http.pangram),
+			     encoding=HEXUC);
+
+	    set req.http.hexucfoobar = "666F6F206261722062617A2071757578";
+	    set req.http.hexlcfoobar = std.tolower(req.http.hexucfoobar);
+	    # Hobbes quotation in hex
+            set req.http.hexhobbeslc = "4d616e2069732064697374696e677569736865642c206e6f74206f6e6c792062792068697320726561736f6e2c2062757420627920746869732073696e67756c61722070617373696f6e2066726f6d206f7468657220616e696d616c732c2077686963682069732061206c757374206f6620746865206d696e642c20746861742062792061207065727365766572616e6365206f662064656c6967687420696e2074686520636f6e74696e75656420616e6420696e6465666174696761626c652067656e65726174696f6e206f66206b6e6f776c656467652c2065786365656473207468652073686f727420766568656d656e6365206f6620616e79206361726e616c20706c6561737572652e";
+	    set req.http.hexhobbesuc = std.toupper(req.http.hexhobbeslc);
+            set req.http.hexalluc = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF";
+	    set req.http.hexalllc = std.tolower(req.http.hexalluc);
+            set req.http.hexallucodd = regsub(req.http.hexalluc, "^0", "");
+            set req.http.hexalllcodd = regsub(req.http.hexalllc, "^0", "");
+
+	    set resp.http.decuc =
+              blob.encode(IDENTITY,
+			     blob.decode(HEX, req.http.hexucfoobar));
+
+	    set resp.http.declc =
+              blob.encode(IDENTITY,
+			     blob.decode(HEX, req.http.hexlcfoobar));
+
+	    set resp.http.dechobbesuc =
+              blob.encode(IDENTITY,
+			     blob.decode(HEX, req.http.hexhobbesuc));
+
+	    set resp.http.dechobbeslc =
+              blob.encode(IDENTITY,
+			     blob.decode(HEX, req.http.hexhobbeslc));
+
+	    set resp.http.decalluc =
+              blob.encode(BASE64, blob.decode(HEX, req.http.hexalluc));
+
+	    set resp.http.decalllc =
+              blob.encode(BASE64, blob.decode(HEX, req.http.hexalllc));
+
+	    set resp.http.decallucodd =
+              blob.encode(BASE64, blob.decode(HEX,
+						      req.http.hexallucodd));
+
+	    set resp.http.decalllcodd =
+              blob.encode(BASE64, blob.decode(HEX,
+						      req.http.hexalllcodd));
+
+	    set resp.http.decempty =
+	      blob.encode(IDENTITY, blob.decode(HEX, ""));
+
+	    set resp.http.decemptybyte =
+	      blob.encode(IDENTITY, blob.decode(HEX, "00"));
+
+	    set resp.http.decemptynibble =
+	      blob.encode(IDENTITY, blob.decode(HEX, "0"));
+
+	    set resp.http.decemptypieces =
+              blob.encode(IDENTITY,
+			     blob.decode(HEX, req.http.unset + ""
+					    + req.http.unset + ""));
+
+	    set req.http.part1 = "666";
+	    set resp.http.dec2pieces =
+	      blob.encode(IDENTITY, blob.decode(HEX, req.http.part1 +
+			     "F6F206261722062617A2071757578"));
+
+	    set req.http.part2 = "57578";
+	    set resp.http.dec3param =
+	      blob.encode(blob=blob.decode(encoded=req.http.part1 +
+			     "F6F206261722062617A20717" + req.http.part2,
+			     decoding=HEX),
+			     encoding=IDENTITY);
+
+	    set resp.http.dec3pieces =
+	      blob.encode(IDENTITY, blob.decode(HEX, req.http.part1 +
+			     "F6F206261722062617A20717" + req.http.part2));
+
+	    set resp.http.decmanypieces =
+              blob.encode(IDENTITY, blob.decode(HEX, "" + req.http.unset
+			     + req.http.part1 + req.http.unset + ""
+			     + req.http.unset + "" + "F6F206261722062617A20717"
+			     + "" + req.http.unset + req.http.part2
+			     + req.http.unset + "" + req.http.unset));
+	}
+} -start
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.hexlc == "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"
+	expect resp.http.hexuc == "54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67"
+	expect resp.http.hobbeslc == "4d616e2069732064697374696e677569736865642c206e6f74206f6e6c792062792068697320726561736f6e2c2062757420627920746869732073696e67756c61722070617373696f6e2066726f6d206f7468657220616e696d616c732c2077686963682069732061206c757374206f6620746865206d696e642c20746861742062792061207065727365766572616e6365206f662064656c6967687420696e2074686520636f6e74696e75656420616e6420696e6465666174696761626c652067656e65726174696f6e206f66206b6e6f776c656467652c2065786365656473207468652073686f727420766568656d656e6365206f6620616e79206361726e616c20706c6561737572652e"
+	expect resp.http.hobbesuc == "4D616E2069732064697374696E677569736865642C206E6F74206F6E6C792062792068697320726561736F6E2C2062757420627920746869732073696E67756C61722070617373696F6E2066726F6D206F7468657220616E696D616C732C2077686963682069732061206C757374206F6620746865206D696E642C20746861742062792061207065727365766572616E6365206F662064656C6967687420696E2074686520636F6E74696E75656420616E6420696E6465666174696761626C652067656E65726174696F6E206F66206B6E6F776C656467652C2065786365656473207468652073686F727420766568656D656E6365206F6620616E79206361726E616C20706C6561737572652E"
+	expect resp.http.all-uc == "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"
+	expect resp.http.all-lc == "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	expect resp.http.empty-uc == ""
+	expect resp.http.empty-lc == ""
+	expect resp.http.hexlcparam == "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"
+	expect resp.http.hexucparam == "54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67"
+	expect resp.http.decuc == "foo bar baz quux"
+	expect resp.http.declc == "foo bar baz quux"
+	expect resp.http.dechobbesuc == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.dechobbeslc == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.decalluc == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.decalllc == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.decallucodd == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.decalllcodd == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.decempty == ""
+	expect resp.http.decemptybyte == ""
+	expect resp.http.decemptynibble == ""
+	expect resp.http.decemptypieces == ""
+	expect resp.http.dec2pieces == "foo bar baz quux"
+	expect resp.http.dec3pieces == "foo bar baz quux"
+	expect resp.http.dec3param == "foo bar baz quux"
+	expect resp.http.decmanypieces == "foo bar baz quux"
+} -run
+
+# Decode failure
+
+server s1 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+	  set req.http.foo = "123";
+	  set resp.http.badhex = blob.encode(HEXUC,
+				 blob.decode(HEX, "g" + req.http.foo));
+	}
+}
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.badhex == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+        expect 0 * Begin req
+        expect * = VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"g\"$"
+#"
+	expect * = End
+} -start
+
+logexpect l1 -wait
diff --git a/bin/varnishtest/tests/m00036.vtc b/bin/varnishtest/tests/m00036.vtc
new file mode 100644
index 0000000..88f869a
--- /dev/null
+++ b/bin/varnishtest/tests/m00036.vtc
@@ -0,0 +1,196 @@
+varnishtest "VMOD blob hex decode_n()"
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.pangram = "The quick brown fox jumps over the lazy dog";
+	    set req.http.hobbes =
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."};
+	    # base64 encoding of all 256 byte values in ascending order
+	    set req.http.b64all =
+{"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="};
+
+	    set resp.http.hexlc =
+              blob.encode(HEXLC,
+			      blob.decode_n(5, IDENTITY, req.http.pangram));
+
+	    set resp.http.hexuc =
+              blob.encode(HEXUC,
+			      blob.decode_n(5, IDENTITY, req.http.pangram));
+
+            set resp.http.hobbeslc =
+	      blob.encode(HEXLC, blob.decode_n(5, IDENTITY, req.http.hobbes));
+
+	    set resp.http.hobbesuc =
+	      blob.encode(HEXUC, blob.decode_n(5, IDENTITY, req.http.hobbes));
+
+            set resp.http.all-lc =
+	      blob.encode(HEXLC, blob.decode_n(8, BASE64, req.http.b64all));
+
+	    set resp.http.all-uc =
+	      blob.encode(HEXUC, blob.decode_n(8, BASE64, req.http.b64all));
+
+            set resp.http.empty-lc =
+	      blob.encode(HEXLC, blob.decode_n(5, IDENTITY, ""));
+
+	    set resp.http.empty-uc =
+	      blob.encode(HEXUC, blob.decode_n(5, IDENTITY, ""));
+
+	    set resp.http.hexlcparam =
+	      blob.encode(blob=blob.decode_n(5, IDENTITY, req.http.pangram),
+			     encoding=HEXLC);
+
+	    set resp.http.hexucparam =
+	      blob.encode(blob=blob.decode_n(5, IDENTITY, req.http.pangram),
+			     encoding=HEXUC);
+
+	    set req.http.hexucfoobar = "666F6F206261722062617A2071757578";
+	    set req.http.hexlcfoobar = std.tolower(req.http.hexucfoobar);
+	    # Hobbes quotation in hex
+            set req.http.hexhobbeslc = "4d616e2069732064697374696e677569736865642c206e6f74206f6e6c792062792068697320726561736f6e2c2062757420627920746869732073696e67756c61722070617373696f6e2066726f6d206f7468657220616e696d616c732c2077686963682069732061206c757374206f6620746865206d696e642c20746861742062792061207065727365766572616e6365206f662064656c6967687420696e2074686520636f6e74696e75656420616e6420696e6465666174696761626c652067656e65726174696f6e206f66206b6e6f776c656467652c2065786365656473207468652073686f727420766568656d656e6365206f6620616e79206361726e616c20706c6561737572652e";
+	    set req.http.hexhobbesuc = std.toupper(req.http.hexhobbeslc);
+            set req.http.hexalluc = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF";
+	    set req.http.hexalllc = std.tolower(req.http.hexalluc);
+            set req.http.hexallucodd = regsub(req.http.hexalluc, "^0", "");
+            set req.http.hexalllcodd = regsub(req.http.hexalllc, "^0", "");
+
+	    set resp.http.decuc =
+              blob.encode(IDENTITY,
+			     blob.decode_n(10, HEX, req.http.hexucfoobar));
+
+	    set resp.http.declc =
+              blob.encode(IDENTITY,
+			     blob.decode_n(10, HEX, req.http.hexlcfoobar));
+
+	    set resp.http.dechobbesuc =
+              blob.encode(IDENTITY,
+			     blob.decode_n(12, HEX, req.http.hexhobbesuc));
+
+	    set resp.http.dechobbeslc =
+              blob.encode(IDENTITY,
+			     blob.decode_n(12, HEX, req.http.hexhobbeslc));
+
+	    set resp.http.decalluc =
+              blob.encode(BASE64, blob.decode_n(12, HEX, req.http.hexalluc));
+
+	    set resp.http.decalllc =
+              blob.encode(BASE64, blob.decode_n(12, HEX, req.http.hexalllc));
+
+	    set resp.http.decallucodd =
+              blob.encode(BASE64, blob.decode_n(11, HEX,
+						      req.http.hexallucodd));
+
+	    set resp.http.decalllcodd =
+              blob.encode(BASE64, blob.decode_n(11, HEX,
+						      req.http.hexalllcodd));
+
+	    set resp.http.decempty =
+	      blob.encode(IDENTITY, blob.decode_n(5, HEX, ""));
+
+	    set resp.http.decemptybyte =
+	      blob.encode(IDENTITY, blob.decode_n(1, HEX, "00"));
+
+	    set resp.http.decemptynibble =
+	      blob.encode(IDENTITY, blob.decode_n(2, HEX, "0"));
+
+	    set resp.http.decemptypieces =
+              blob.encode(IDENTITY,
+			     blob.decode_n(5, HEX, req.http.unset + ""
+					    + req.http.unset + ""));
+
+	    set req.http.part1 = "666";
+	    set resp.http.dec2pieces =
+	      blob.encode(IDENTITY, blob.decode_n(6, HEX, req.http.part1 +
+			     "F6F206261722062617A2071757578"));
+
+	    set req.http.part2 = "57578";
+	    set resp.http.dec3param =
+	      blob.encode(blob=blob.decode_n(encoded=req.http.part1 +
+			     "F6F206261722062617A20717" + req.http.part2,
+			     decoding=HEX,n=8),
+			     encoding=IDENTITY);
+
+	    set resp.http.dec3pieces =
+	      blob.encode(IDENTITY, blob.decode_n(30, HEX, req.http.part1 +
+			     "F6F206261722062617A20717" + req.http.part2));
+
+	    set resp.http.decmanypieces =
+              blob.encode(IDENTITY, blob.decode_n(20, HEX, "" + req.http.unset
+			     + req.http.part1 + req.http.unset + ""
+			     + req.http.unset + "" + "F6F206261722062617A20717"
+			     + "" + req.http.unset + req.http.part2
+			     + req.http.unset + "" + req.http.unset));
+	}
+} -start
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.hexlc == "5468652071"
+	expect resp.http.hexuc == "5468652071"
+	expect resp.http.hobbeslc == "4d616e2069"
+	expect resp.http.hobbesuc == "4D616E2069"
+	expect resp.http.all-uc == "000102030405"
+	expect resp.http.all-lc == "000102030405"
+	expect resp.http.empty-uc == ""
+	expect resp.http.empty-lc == ""
+	expect resp.http.hexlcparam == "5468652071"
+	expect resp.http.hexucparam == "5468652071"
+	expect resp.http.decuc == "foo b"
+	expect resp.http.declc == "foo b"
+	expect resp.http.dechobbesuc == "Man is"
+	expect resp.http.dechobbeslc == "Man is"
+	expect resp.http.decalluc == "AAECAwQF"
+	expect resp.http.decalllc == "AAECAwQF"
+	expect resp.http.decallucodd == "AAECAwQF"
+	expect resp.http.decalllcodd == "AAECAwQF"
+	expect resp.http.decempty == ""
+	expect resp.http.decemptybyte == ""
+	expect resp.http.decemptynibble == ""
+	expect resp.http.decemptypieces == ""
+	expect resp.http.dec2pieces == "foo"
+	expect resp.http.dec3pieces == "foo bar baz quu"
+	expect resp.http.dec3param == "foo "
+	expect resp.http.decmanypieces == "foo bar ba"
+} -run
+
+# Decode failure
+
+server s1 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+	  set req.http.foo = "123";
+	  set resp.http.badhex = blob.encode(HEXUC,
+				 blob.decode_n(2, HEX, "g" + req.http.foo));
+	}
+}
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.badhex == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+        expect 0 * Begin req
+        expect * = VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"g\"$"
+#"
+	expect * = End
+} -start
+
+logexpect l1 -wait
diff --git a/bin/varnishtest/tests/m00037.vtc b/bin/varnishtest/tests/m00037.vtc
new file mode 100644
index 0000000..29bde50
--- /dev/null
+++ b/bin/varnishtest/tests/m00037.vtc
@@ -0,0 +1,302 @@
+varnishtest "VMOD blob base64 encode and decode"
+
+varnish v1 -vcl {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.pangram
+	      = "The quick brown fox jumps over the lazy dog";
+	    set req.http.hobbes =
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."};
+	    # hex encoding of all 256 byte values in descending order
+	    set req.http.hexall =
+{"fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"};
+
+	    set resp.http.b64 =
+              blob.encode(BASE64, blob.decode(IDENTITY,
+						      req.http.pangram));
+
+	    set resp.http.b64hobbes =
+              blob.encode(BASE64, blob.decode(IDENTITY,
+						      req.http.hobbes));
+
+	    set resp.http.b64all =
+	      blob.encode(BASE64, blob.decode(HEX, req.http.hexall));
+
+	    set resp.http.b64url =
+              blob.encode(BASE64URL,
+			      blob.decode(IDENTITY, req.http.pangram));
+
+	    set resp.http.b64urlhobbes =
+              blob.encode(BASE64URL,
+			      blob.decode(IDENTITY, req.http.hobbes));
+
+	    set resp.http.b64urlall =
+	      blob.encode(BASE64URL, blob.decode(HEX, req.http.hexall));
+
+	    set resp.http.b64urlnopad =
+              blob.encode(BASE64URLNOPAD,
+			      blob.decode(IDENTITY, req.http.pangram));
+
+	    set resp.http.b64nopadhobbes =
+              blob.encode(BASE64URLNOPAD,
+			      blob.decode(IDENTITY, req.http.hobbes));
+
+	    set resp.http.b64nopadall =
+              blob.encode(BASE64URLNOPAD,
+			      blob.decode(HEX, req.http.hexall));
+
+	    set resp.http.b64empty =
+	      blob.encode(BASE64, blob.decode(IDENTITY, ""));
+	    set resp.http.urlempty =
+	      blob.encode(BASE64URL, blob.decode(IDENTITY, ""));
+	    set resp.http.nopadempty =
+	      blob.encode(BASE64URLNOPAD, blob.decode(IDENTITY, ""));
+
+	    set resp.http.b64param =
+	      blob.encode(blob=blob.decode(IDENTITY, req.http.pangram),
+			      encoding=BASE64);
+
+	    set resp.http.b64urlparam =
+	      blob.encode(blob=blob.decode(IDENTITY, req.http.pangram),
+			      encoding=BASE64URL);
+
+	    set resp.http.b64urlnopadparam =
+	      blob.encode(blob=blob.decode(IDENTITY, req.http.pangram),
+			      encoding=BASE64URLNOPAD);
+
+	    set resp.http.b64xcode =
+		blob.transcode(IDENTITY, BASE64, req.url + "Hello world" +
+				   req.url);
+
+	    set resp.http.b64urlxcode =
+		blob.transcode(IDENTITY, BASE64URL, req.url + "Hello world" +
+				   req.url);
+
+	    set resp.http.b64urlnopadxcode =
+	      blob.transcode(IDENTITY, BASE64URLNOPAD,
+				 req.url + "Hello world" + req.url);
+	}
+} -start
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.b64 == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
+	expect resp.http.b64hobbes == "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="
+	expect resp.http.b64all == "//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.b64url == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
+	expect resp.http.b64urlhobbes == "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="
+	expect resp.http.b64urlall == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.b64urlnopad == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw"
+	expect resp.http.b64nopadhobbes == "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4"
+	expect resp.http.b64nopadall == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA"
+	expect resp.http.b64empty == ""
+	expect resp.http.urlempty == ""
+	expect resp.http.nopadempty == ""
+	expect resp.http.b64urlparam == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
+	expect resp.http.b64urlnopadparam == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw"
+	expect resp.http.b64xcode == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b64urlxcode == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b64urlnopadxcode == "L0hlbGxvIHdvcmxkLw"
+} -run
+
+varnish v1 -vcl {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.foobarbazquux = "L0hlbGxvIHdvcmxkLw==";
+	    set req.http.pangram
+	      = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==";
+	    set req.http.hobbes =
+{"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="};
+	    set req.http.hobbesnopad = regsuball(req.http.hobbes, "=", "");
+	    set req.http.all =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999++++////"};
+	    set req.http.allurl =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999----____"};
+
+	    set resp.http.dec = blob.encode(IDENTITY,
+				blob.decode(BASE64, req.http.foobarbazquux));
+
+	    set resp.http.dec2
+	      = blob.encode(IDENTITY,
+				blob.decode(BASE64, req.http.pangram));
+
+	    set resp.http.b64dechobbes =
+              blob.encode(IDENTITY,
+			      blob.decode(BASE64, req.http.hobbes));
+
+	    set resp.http.b64decall =
+	      blob.encode(HEXLC, blob.decode(BASE64, req.http.all));
+
+	    set resp.http.urldechobbes =
+              blob.encode(IDENTITY, blob.decode(BASE64URL,
+						      req.http.hobbes));
+
+	    set resp.http.urldecall =
+              blob.encode(HEXLC,
+			      blob.decode(BASE64URL, req.http.allurl));
+
+	    set resp.http.nopaddechobbes =
+              blob.encode(IDENTITY, blob.decode(BASE64URLNOPAD,
+						      req.http.hobbesnopad));
+
+	    set resp.http.nopaddecall =
+              blob.encode(HEXLC,
+			      blob.decode(BASE64URLNOPAD, req.http.allurl));
+
+	    set resp.http.b64empty =
+	      blob.encode(IDENTITY, blob.decode(BASE64, ""));
+	    set resp.http.urlempty =
+	      blob.encode(IDENTITY, blob.decode(BASE64URL, ""));
+	    set resp.http.nopadempty =
+	      blob.encode(IDENTITY, blob.decode(BASE64URLNOPAD, ""));
+	    set resp.http.emptypieces =
+	      blob.encode(IDENTITY,
+	      blob.decode(BASE64, req.http.unset + "" + req.http.unset
+				      + "" + req.http.unset + ""));
+
+            set resp.http.decenc
+	      = blob.encode(BASE64,
+				blob.decode(BASE64, req.http.foobarbazquux));
+
+	    set resp.http.l = "L";
+	    set resp.http.dec2pieces
+	      = blob.encode(IDENTITY, blob.decode(BASE64,
+			        resp.http.l + "0hlbGxvIHdvcmxkLw=="));
+
+	    set resp.http.pad = "==";
+	    set resp.http.dec3pieces
+	      = blob.encode(IDENTITY, blob.decode(BASE64,
+			        resp.http.l + "0hlbGxvIHdvcmxkLw"
+			        + resp.http.pad));
+
+	    set resp.http.mid1 = "GxvI";
+	    set resp.http.mid2 = "dvcmx";
+	    set resp.http.dec7pieces
+	      = blob.encode(IDENTITY, blob.decode(BASE64,
+			        resp.http.l + "0hlb" + resp.http.mid1
+			        + "H" + resp.http.mid2 + "kLw"
+			        + resp.http.pad));
+
+	    set resp.http.dec7param
+	      = blob.encode(blob=blob.decode(encoded=resp.http.l + "0hlb"
+			        + resp.http.mid1 + "H" + resp.http.mid2 + "kLw"
+			        + resp.http.pad, decoding=BASE64),
+			        encoding=IDENTITY);
+
+	    set resp.http.decnopad = blob.encode(IDENTITY,
+				     blob.decode(BASE64URLNOPAD,
+						     "L0hlbGxvIHdvcmxkLw"));
+
+	    set resp.http.decnopad2pieces
+	      = blob.encode(IDENTITY, blob.decode(BASE64URLNOPAD,
+			        resp.http.l + "0hlbGxvIHdvcmxkLw"));
+
+	    set resp.http.decnopad6pieces
+	      = blob.encode(IDENTITY, blob.decode(BASE64URLNOPAD,
+			        resp.http.l + "0hlb" + resp.http.mid1
+			        + "H" + resp.http.mid2 + "kLw"));
+
+	    set resp.http.decnopadlong
+	      = blob.encode(IDENTITY,
+		blob.decode(BASE64URLNOPAD,
+	        "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw"
+			       ));
+
+	}
+}
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.dec == "/Hello world/"
+	expect resp.http.dec2 == "The quick brown fox jumps over the lazy dog"
+	expect resp.http.b64dechobbes == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.b64decall == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df8208208618618a28a28e38e39249249659659a69a69e79e7a28a28a69a69aaaaaaaebaebb2cb2cb6db6dbaebaebefbefc30c30c71c71cb2cb2cf3cf3d34d34d75d75db6db6df7df7e38e38e79e79ebaebaefbefbf3cf3cf7df7dfbefbeffffff"
+	expect resp.http.urldechobbes == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.urldecall == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df8208208618618a28a28e38e39249249659659a69a69e79e7a28a28a69a69aaaaaaaebaebb2cb2cb6db6dbaebaebefbefc30c30c71c71cb2cb2cf3cf3d34d34d75d75db6db6df7df7e38e38e79e79ebaebaefbefbf3cf3cf7df7dfbefbeffffff"
+	expect resp.http.nopaddechobbes == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.nopaddecall == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df8208208618618a28a28e38e39249249659659a69a69e79e7a28a28a69a69aaaaaaaebaebb2cb2cb6db6dbaebaebefbefc30c30c71c71cb2cb2cf3cf3d34d34d75d75db6db6df7df7e38e38e79e79ebaebaefbefbf3cf3cf7df7dfbefbeffffff"
+	expect resp.http.b64empty == ""
+	expect resp.http.urlempty == ""
+	expect resp.http.nopadempty == ""
+	expect resp.http.emptypieces == ""
+	expect resp.http.decenc == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.dec2pieces == "/Hello world/"
+	expect resp.http.dec3pieces == "/Hello world/"
+	expect resp.http.dec7pieces == "/Hello world/"
+	expect resp.http.dec7param == "/Hello world/"
+	expect resp.http.decnopad == "/Hello world/"
+	expect resp.http.decnopad2pieces == "/Hello world/"
+	expect resp.http.decnopad6pieces == "/Hello world/"
+	expect resp.http.decnopadlong == "The quick brown fox jumps over the lazy dog"
+} -run
+
+# Decode failures
+
+server s1 -repeat 3 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+          set req.http.foo = "AAA=";
+          if (req.url == "/base64") {
+	        set resp.http.bad64 = blob.encode(IDENTITY,
+                    blob.decode(BASE64, "-_-_" + req.http.foo));
+          }
+          elsif (req.url == "/base64url") {
+	        set resp.http.badurl = blob.encode(IDENTITY,
+                    blob.decode(BASE64URL, "+/+/" + req.http.foo));
+          }
+          elsif (req.url == "/base64urlnopad") {
+	        set resp.http.badpad = blob.encode(IDENTITY,
+                    blob.decode(BASE64URLNOPAD, "TWFu" + req.http.foo));
+          }
+	}
+}
+
+client c1 {
+	txreq -url /base64
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad64 == <undef>
+} -run
+
+client c1 {
+	txreq -url /base64url
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.badurl == <undef>
+} -run
+
+client c1 {
+	txreq -url /base64urlnopad
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.badpad == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "-_-_"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "././"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "TWFu"$}
+} -run
diff --git a/bin/varnishtest/tests/m00038.vtc b/bin/varnishtest/tests/m00038.vtc
new file mode 100644
index 0000000..99c7d69
--- /dev/null
+++ b/bin/varnishtest/tests/m00038.vtc
@@ -0,0 +1,229 @@
+varnishtest "VMOD blob base64 decode_n()"
+
+varnish v1 -vcl {
+	import blob;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.foobarbazquux = "L0hlbGxvIHdvcmxkLw==";
+	    set req.http.pangram
+	      = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==";
+	    set req.http.hobbes =
+{"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="};
+	    set req.http.hobbesnopad = regsuball(req.http.hobbes, "=", "");
+	    set req.http.all =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999++++////"};
+	    set req.http.allurl =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999----____"};
+
+	    set resp.http.dec
+              = blob.encode(IDENTITY,
+				blob.decode_n(8, BASE64,
+						  req.http.foobarbazquux));
+
+	    set resp.http.deceq
+              = blob.encode(IDENTITY,
+				blob.decode_n(20, BASE64,
+						  req.http.foobarbazquux));
+
+	    set resp.http.declong
+              = blob.encode(IDENTITY,
+				blob.decode_n(30, BASE64,
+						  req.http.foobarbazquux));
+
+	    set resp.http.dec2
+	      = blob.encode(IDENTITY,
+				blob.decode_n(12, BASE64, req.http.pangram));
+
+	    set resp.http.b64dechobbes =
+              blob.encode(IDENTITY,
+			      blob.decode_n(24, BASE64, req.http.hobbes));
+
+	    set resp.http.b64decall =
+               blob.encode(HEXLC, blob.decode_n(128, BASE64,
+					                req.http.all));
+
+	    set resp.http.urldechobbes =
+              blob.encode(IDENTITY, blob.decode_n(180, BASE64URL,
+						          req.http.hobbes));
+
+	    set resp.http.urldecall =
+              blob.encode(HEXLC,
+			      blob.decode_n(256, BASE64URL,
+						req.http.allurl));
+
+	    set resp.http.nopaddechobbes =
+	      blob.encode(IDENTITY,
+		              blob.decode_n(500, BASE64URLNOPAD,
+						req.http.hobbesnopad));
+
+	    set resp.http.nopaddecall =
+	      blob.encode(HEXLC,
+			      blob.decode_n(256, BASE64URLNOPAD,
+						req.http.allurl));
+
+	    set resp.http.b64empty =
+	      blob.encode(IDENTITY, blob.decode_n(0, BASE64, ""));
+	    set resp.http.urlempty =
+	      blob.encode(IDENTITY, blob.decode_n(1, BASE64URL, ""));
+	    set resp.http.nopadempty =
+	        blob.encode(IDENTITY,
+				blob.decode_n(0, BASE64URLNOPAD, ""));
+	    set resp.http.emptypieces =
+	      blob.encode(IDENTITY,
+	      blob.decode_n(0, BASE64, req.http.unset + "" + req.http.unset
+				           + "" + req.http.unset + ""));
+
+            set resp.http.decenc
+	      = blob.encode(BASE64,
+				blob.decode_n(20, BASE64,
+						  req.http.foobarbazquux));
+
+	    set resp.http.l = "L";
+	    set resp.http.dec2pieces
+	      = blob.encode(IDENTITY, blob.decode_n(8, BASE64,
+			        resp.http.l + "0hlbGxvIHdvcmxkLw=="));
+
+	    set resp.http.pad = "==";
+	    set resp.http.dec3pieces
+	      = blob.encode(IDENTITY, blob.decode_n(12, BASE64,
+			        resp.http.l + "0hlbGxvIHdvcmxkLw"
+			        + resp.http.pad));
+
+	    set resp.http.mid1 = "GxvI";
+	    set resp.http.mid2 = "dvcmx";
+	    set resp.http.dec7pieces
+	      = blob.encode(IDENTITY, blob.decode_n(16, BASE64,
+			        resp.http.l + "0hlb" + resp.http.mid1
+			        + "H" + resp.http.mid2 + "kLw"
+			        + resp.http.pad));
+
+	    set resp.http.dec7param
+	      = blob.encode(blob=blob.decode_n(encoded=resp.http.l
+				+ "0hlb" + resp.http.mid1 + "H" + resp.http.mid2
+				+ "kLw" + resp.http.pad, decoding=BASE64, n=20),
+			        encoding=IDENTITY);
+
+	    set resp.http.decnopad = blob.encode(IDENTITY,
+				     blob.decode_n(18, BASE64URLNOPAD,
+						       "L0hlbGxvIHdvcmxkLw"));
+
+	    set resp.http.decnopad2pieces
+	      = blob.encode(IDENTITY, blob.decode_n(19, BASE64URLNOPAD,
+			        resp.http.l + "0hlbGxvIHdvcmxkLw"));
+
+	    set resp.http.decnopad6pieces
+	      = blob.encode(IDENTITY, blob.decode_n(18, BASE64URLNOPAD,
+			        resp.http.l + "0hlb" + resp.http.mid1
+			        + "H" + resp.http.mid2 + "kLw"));
+
+	    set resp.http.decnopadlong
+	      = blob.encode(IDENTITY,
+		blob.decode_n(60, BASE64URLNOPAD,
+	        "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw"
+			       ));
+
+	}
+} -start
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.dec == "/Hello"
+	expect resp.http.deceq == "/Hello world/"
+	expect resp.http.declong == "/Hello world/"
+	expect resp.http.dec2 == "The quick"
+	expect resp.http.b64dechobbes == "Man is distinguish"
+	expect resp.http.b64decall == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df"
+	expect resp.http.urldechobbes == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a p"
+	expect resp.http.urldecall == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df8208208618618a28a28e38e39249249659659a69a69e79e7a28a28a69a69aaaaaaaebaebb2cb2cb6db6dbaebaebefbefc30c30c71c71cb2cb2cf3cf3d34d34d75d75db6db6df7df7e38e38e79e79ebaebaefbefbf3cf3cf7df7dfbefbeffffff"
+	expect resp.http.nopaddechobbes == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.nopaddecall == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df8208208618618a28a28e38e39249249659659a69a69e79e7a28a28a69a69aaaaaaaebaebb2cb2cb6db6dbaebaebefbefc30c30c71c71cb2cb2cf3cf3d34d34d75d75db6db6df7df7e38e38e79e79ebaebaefbefbf3cf3cf7df7dfbefbeffffff"
+	expect resp.http.b64empty == ""
+	expect resp.http.urlempty == ""
+	expect resp.http.nopadempty == ""
+	expect resp.http.emptypieces == ""
+	expect resp.http.decenc == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.dec2pieces == "/Hello"
+	expect resp.http.dec3pieces == "/Hello wo"
+	expect resp.http.dec7pieces == "/Hello world"
+	expect resp.http.dec7param == "/Hello world/"
+	expect resp.http.decnopad == "/Hello world/"
+	expect resp.http.decnopad2pieces == "/Hello world/"
+	expect resp.http.decnopad6pieces == "/Hello world/"
+	expect resp.http.decnopadlong == "The quick brown fox jumps over the lazy dog"
+} -run
+
+# Decode failures
+
+server s1 -repeat 3 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+          set req.http.foo = "AAA=";
+          if (req.url == "/base64") {
+	        set resp.http.bad64 = blob.encode(IDENTITY,
+                    blob.decode_n(8, BASE64, "-_-_" + req.http.foo));
+          }
+          elsif (req.url == "/base64url") {
+	        set resp.http.badurl = blob.encode(IDENTITY,
+                    blob.decode_n(8, BASE64URL, "+/+/" + req.http.foo));
+          }
+          elsif (req.url == "/base64urlnopad") {
+	        set resp.http.badpad = blob.encode(IDENTITY,
+	            blob.decode_n(8, BASE64URLNOPAD,
+				      "TWFu" + req.http.foo));
+          }
+	}
+}
+
+client c1 {
+	txreq -url /base64
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad64 == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "-_-_"$}
+} -start
+
+logexpect l1 -wait
+
+client c1 {
+	txreq -url /base64url
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.badurl == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "././"$}
+} -start
+
+logexpect l1 -wait
+
+client c1 {
+	txreq -url /base64urlnopad
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.badpad == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "TWFu"$}
+} -start
+
+logexpect l1 -wait
diff --git a/bin/varnishtest/tests/m00039.vtc b/bin/varnishtest/tests/m00039.vtc
new file mode 100644
index 0000000..f5d4809
--- /dev/null
+++ b/bin/varnishtest/tests/m00039.vtc
@@ -0,0 +1,288 @@
+varnishtest "VMOD blob url encode and decode"
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.pangram = "The quick brown fox jumps over the lazy dog";
+	    set req.http.hobbes =
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."};
+	    set req.http.umlauts = "Übergrößenträger";
+	    set req.http.phkspeak = "småbørnspædagog";
+	    set req.http.utf8 = "日扼語";
+
+	    # base64 encoding of all 256 byte values in ascending order
+	    set req.http.b64all =
+{"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="};
+
+	    set resp.http.urllc =
+              blob.encode(URLLC,
+			      blob.decode(IDENTITY, req.http.pangram));
+
+	    set resp.http.urluc =
+              blob.encode(URLUC,
+			      blob.decode(IDENTITY, req.http.pangram));
+
+            set resp.http.hobbeslc =
+	      blob.encode(URLLC, blob.decode(IDENTITY, req.http.hobbes));
+
+	    set resp.http.hobbesuc =
+	      blob.encode(URLUC, blob.decode(IDENTITY, req.http.hobbes));
+
+            set resp.http.umlautslc =
+	      blob.encode(URLLC,
+		              blob.decode(IDENTITY, req.http.umlauts));
+
+            set resp.http.umlautsuc =
+	      blob.encode(URLUC,
+		              blob.decode(IDENTITY, req.http.umlauts));
+
+            set resp.http.phklc =
+	      blob.encode(URLLC,
+	                     blob.decode(IDENTITY, req.http.phkspeak));
+
+            set resp.http.phkuc =
+	      blob.encode(URLUC,
+	                     blob.decode(IDENTITY, req.http.phkspeak));
+
+            set resp.http.utf8lc =
+	      blob.encode(URLLC, blob.decode(IDENTITY, req.http.utf8));
+
+            set resp.http.utf8uc =
+	      blob.encode(URLUC, blob.decode(IDENTITY, req.http.utf8));
+
+            set resp.http.all-lc =
+	      blob.encode(URLLC, blob.decode(BASE64, req.http.b64all));
+
+	    set resp.http.all-uc =
+	      blob.encode(URLUC, blob.decode(BASE64, req.http.b64all));
+
+            set resp.http.empty-lc =
+	      blob.encode(URLLC, blob.decode(IDENTITY, ""));
+
+	    set resp.http.empty-uc =
+	      blob.encode(URLUC, blob.decode(IDENTITY, ""));
+
+	    set resp.http.urllcparam =
+	      blob.encode(blob=blob.decode(IDENTITY, req.http.pangram),
+			      encoding=URLLC);
+
+	    set resp.http.urlucparam =
+	      blob.encode(blob=blob.decode(IDENTITY, req.http.pangram),
+			      encoding=URLUC);
+
+	    set req.http.urllcfoobar = "foo%3abar%3abaz%3aquux";
+	    set req.http.urlucfoobar = "foo%3Abar%3Abaz%3Aquux";
+	    set req.http.urlmixedfoobar = "foo%3Abar%3abaz%3Aquux";
+            set req.http.urlhobbeslc = "Man%20is%20distinguished%2c%20not%20only%20by%20his%20reason%2c%20but%20by%20this%20singular%20passion%20from%20other%20animals%2c%20which%20is%20a%20lust%20of%20the%20mind%2c%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2c%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+	    set req.http.urlhobbesuc = "Man%20is%20distinguished%2C%20not%20only%20by%20his%20reason%2C%20but%20by%20this%20singular%20passion%20from%20other%20animals%2C%20which%20is%20a%20lust%20of%20the%20mind%2C%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2C%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+	    set req.http.urllcumlauts = "%c3%9cbergr%c3%b6%c3%9fentr%c3%a4ger";
+	    set req.http.urlucumlauts = "%C3%9Cbergr%C3%B6%C3%9Fentr%C3%A4ger";
+	    set req.http.urllcphk = "sm%c3%a5b%c3%b8rnsp%c3%a6dagog";
+	    set req.http.urlucphk = "sm%C3%A5b%C3%B8rnsp%C3%A6dagog";
+	    set req.http.urllcutf8 = "%e6%97%a5%e6%89%bc%e8%aa%9e";
+	    set req.http.urlucutf8 = "%E6%97%A5%E6%89%BC%E8%AA%9E";
+            set req.http.urlalluc = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF";
+	    set req.http.urlalllc = "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff";
+
+	    set resp.http.decuc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urlucfoobar));
+
+	    set resp.http.declc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urllcfoobar));
+
+	    set resp.http.decmixed =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urlmixedfoobar));
+
+	    set resp.http.dechobbesuc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urlhobbesuc));
+
+	    set resp.http.dechobbeslc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urlhobbeslc));
+
+	    set resp.http.decumlautsuc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urlucumlauts));
+
+	    set resp.http.decumlautslc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urllcumlauts));
+
+	    set resp.http.decphkuc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urlucphk));
+
+	    set resp.http.decphklc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urllcphk));
+
+	    set resp.http.decutf8uc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urlucutf8));
+
+	    set resp.http.decutf8lc =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.urllcutf8));
+
+	    set resp.http.decalluc =
+              blob.encode(BASE64, blob.decode(URL, req.http.urlalluc));
+
+	    set resp.http.decalllc =
+              blob.encode(BASE64, blob.decode(URL, req.http.urlalllc));
+
+	    set resp.http.decempty =
+	      blob.encode(IDENTITY, blob.decode(URL, ""));
+
+	    set resp.http.decemptybyte =
+	      blob.encode(IDENTITY, blob.decode(URL, "%00"));
+
+	    set resp.http.decemptypieces =
+              blob.encode(IDENTITY,
+			      blob.decode(URL, req.http.unset + ""
+					           + req.http.unset + ""));
+
+	    set req.http.part1 = "foo%";
+	    set resp.http.dec2pieces =
+	      blob.encode(IDENTITY, blob.decode(URL, req.http.part1 +
+			     "20bar%20baz%20quux"));
+
+	    set req.http.part2 = "0quux";
+	    set resp.http.dec3param =
+	      blob.encode(blob=blob.decode(encoded=req.http.part1 +
+			      "20bar%20baz%2" + req.http.part2,
+			      decoding=URL),
+			      encoding=IDENTITY);
+
+	    set resp.http.dec3pieces =
+	      blob.encode(IDENTITY, blob.decode(URL, req.http.part1 +
+			      "20bar%20baz%2" + req.http.part2));
+
+	    set resp.http.decmanypieces =
+              blob.encode(IDENTITY, blob.decode(URL, "" + req.http.unset
+			     + req.http.part1 + req.http.unset + ""
+			     + req.http.unset + "" + "20bar%20baz%2"
+			     + "" + req.http.unset + req.http.part2
+			     + req.http.unset + "" + req.http.unset));
+	}
+} -start
+
+client c1 {
+	txreq -url "/"
+	rxresp
+	expect resp.http.urllc == "The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog"
+	expect resp.http.urluc == "The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog"
+	expect resp.http.hobbeslc == "Man%20is%20distinguished%2c%20not%20only%20by%20his%20reason%2c%20but%20by%20this%20singular%20passion%20from%20other%20animals%2c%20which%20is%20a%20lust%20of%20the%20mind%2c%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2c%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure."
+	expect resp.http.hobbesuc == "Man%20is%20distinguished%2C%20not%20only%20by%20his%20reason%2C%20but%20by%20this%20singular%20passion%20from%20other%20animals%2C%20which%20is%20a%20lust%20of%20the%20mind%2C%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2C%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure."
+	expect resp.http.umlautslc == "%c3%9cbergr%c3%b6%c3%9fentr%c3%a4ger"
+	expect resp.http.umlautsuc == "%C3%9Cbergr%C3%B6%C3%9Fentr%C3%A4ger"
+	expect resp.http.phklc == "sm%c3%a5b%c3%b8rnsp%c3%a6dagog"
+	expect resp.http.phkuc == "sm%C3%A5b%C3%B8rnsp%C3%A6dagog"
+	expect resp.http.utf8lc == "%e6%97%a5%e6%89%bc%e8%aa%9e"
+	expect resp.http.utf8uc == "%E6%97%A5%E6%89%BC%E8%AA%9E"
+	expect resp.http.all-lc == "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff"
+	expect resp.http.all-uc == "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
+	expect resp.http.empty-uc == ""
+	expect resp.http.empty-lc == ""
+	expect resp.http.urllcparam == "The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog"
+	expect resp.http.urlucparam == "The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog"
+	expect resp.http.decuc == "foo:bar:baz:quux"
+	expect resp.http.declc == "foo:bar:baz:quux"
+	expect resp.http.decmixed == "foo:bar:baz:quux"
+	expect resp.http.dechobbesuc == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.dechobbeslc == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.decumlautsuc == "Übergrößenträger"
+	expect resp.http.decumlautslc == "Übergrößenträger"
+	expect resp.http.decphkuc == "småbørnspædagog"
+	expect resp.http.decphklc == "småbørnspædagog"
+	expect resp.http.decutf8uc == "日扼語"
+	expect resp.http.decutf8lc == "日扼語"
+	expect resp.http.decalluc == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.decalllc == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.decempty == ""
+	expect resp.http.decemptybyte == ""
+	expect resp.http.decemptypieces == ""
+	expect resp.http.dec2pieces == "foo bar baz quux"
+	expect resp.http.dec3pieces == "foo bar baz quux"
+	expect resp.http.dec3param == "foo bar baz quux"
+	expect resp.http.decmanypieces == "foo bar baz quux"
+} -run
+
+# Decode failures
+
+server s1 -repeat 4 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+                if (req.url == "/percent") {
+	                set resp.http.bad
+                            = blob.encode(URLUC, blob.decode(URL, "%"));
+                }
+                elsif (req.url == "/percent-two") {
+	                set resp.http.bad = blob.encode(URLUC,
+                                            blob.decode(URL, "%2"));
+                }
+                elsif (req.url == "/percent-q") {
+	                set resp.http.bad = blob.encode(URLUC,
+			                    blob.decode(URL, "%q"));
+                }
+                elsif (req.url == "/percent-two-q") {
+	                set resp.http.bad = blob.encode(URLUC,
+					    blob.decode(URL, "%2q"));
+                }
+	}
+}
+
+client c1 {
+	txreq -url /percent
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /percent-two
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /percent-q
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /percent-two-q
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+        expect * * VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"%\"$"
+        expect * * VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"%2\"$"
+        expect * * VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"%q\"$"
+        expect * * VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"%2q\"$"
+} -run
diff --git a/bin/varnishtest/tests/m00040.vtc b/bin/varnishtest/tests/m00040.vtc
new file mode 100644
index 0000000..b93a3c5
--- /dev/null
+++ b/bin/varnishtest/tests/m00040.vtc
@@ -0,0 +1,197 @@
+varnishtest "VMOD blob decode_n() with URL"
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.urllcfoobar = "foo%3abar%3abaz%3aquux";
+	    set req.http.urlucfoobar = "foo%3Abar%3Abaz%3Aquux";
+            set req.http.urlhobbeslc = "Man%20is%20distinguished%2c%20not%20only%20by%20his%20reason%2c%20but%20by%20this%20singular%20passion%20from%20other%20animals%2c%20which%20is%20a%20lust%20of%20the%20mind%2c%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2c%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+	    set req.http.urlhobbesuc = "Man%20is%20distinguished%2C%20not%20only%20by%20his%20reason%2C%20but%20by%20this%20singular%20passion%20from%20other%20animals%2C%20which%20is%20a%20lust%20of%20the%20mind%2C%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2C%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+	    set req.http.urllcumlauts = "%c3%9cbergr%c3%b6%c3%9fentr%c3%a4ger";
+	    set req.http.urlucumlauts = "%C3%9Cbergr%C3%B6%C3%9Fentr%C3%A4ger";
+	    set req.http.urllcphk = "sm%c3%a5b%c3%b8rnsp%c3%a6dagog";
+	    set req.http.urlucphk = "sm%C3%A5b%C3%B8rnsp%C3%A6dagog";
+	    set req.http.urllcutf8 = "%e6%97%a5%e6%89%bc%e8%aa%9e";
+	    set req.http.urlucutf8 = "%E6%97%A5%E6%89%BC%E8%AA%9E";
+            set req.http.urlalluc = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF";
+	    set req.http.urlalllc = "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff";
+
+	    set resp.http.decuc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(9, URL, req.http.urlucfoobar));
+
+	    set resp.http.declc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(12, URL, req.http.urllcfoobar));
+
+	    set resp.http.dechobbesuc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(27, URL, req.http.urlhobbesuc));
+
+	    set resp.http.dechobbeslc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(27, URL, req.http.urlhobbeslc));
+
+	    set resp.http.decumlautsuc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(17, URL,
+						req.http.urlucumlauts));
+
+	    set resp.http.decumlautslc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(25, URL,
+						req.http.urllcumlauts));
+
+	    set resp.http.decphkuc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(18, URL, req.http.urlucphk));
+
+	    set resp.http.decphklc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(30, URL, req.http.urllcphk));
+
+	    set resp.http.decutf8uc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(9, URL, req.http.urlucutf8));
+
+	    set resp.http.decutf8lc =
+              blob.encode(IDENTITY,
+			      blob.decode_n(18, URL, req.http.urllcutf8));
+
+	    set resp.http.decalluc =
+              blob.encode(HEXLC,
+			      blob.decode_n(252, URL, req.http.urlalluc));
+
+	    set resp.http.decalllc =
+              blob.encode(HEXUC,
+			      blob.decode_n(252, URL, req.http.urlalllc));
+
+	    set resp.http.decempty =
+	      blob.encode(IDENTITY, blob.decode_n(10, URL, ""));
+
+	    set resp.http.decemptybyte =
+	      blob.encode(IDENTITY, blob.decode_n(3, URL, "%00"));
+
+	    set resp.http.decemptypieces =
+              blob.encode(IDENTITY,
+			      blob.decode_n(1, URL, req.http.unset + ""
+					                + req.http.unset + ""));
+
+	    set req.http.part1 = "foo%";
+	    set resp.http.dec2pieces =
+	      blob.encode(IDENTITY,
+			      blob.decode_n(6, URL, req.http.part1 +
+			                                "20bar%20baz%20quux"));
+
+	    set req.http.part2 = "0quux";
+	    set resp.http.dec3param =
+	      blob.encode(blob=blob.decode_n(encoded=req.http.part1 +
+			      "20bar%20baz%2" + req.http.part2,
+			      decoding=URL, n=12),
+			      encoding=IDENTITY);
+
+	    set resp.http.dec3pieces =
+	      blob.encode(IDENTITY,
+			      blob.decode_n(18, URL, req.http.part1
+						+ "20bar%20baz%2"
+						+ req.http.part2));
+
+	    set resp.http.decmanypieces =
+              blob.encode(IDENTITY,
+			      blob.decode_n(24, URL, "" + req.http.unset
+			      + req.http.part1 + req.http.unset + ""
+			      + req.http.unset + "" + "20bar%20baz%2"
+			      + "" + req.http.unset + req.http.part2
+			      + req.http.unset + "" + req.http.unset));
+	}
+} -start
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.http.decuc == "foo:bar"
+	expect resp.http.declc == "foo:bar:"
+	expect resp.http.dechobbesuc == "Man is distinguished,"
+	expect resp.http.dechobbeslc == "Man is distinguished,"
+	expect resp.http.decumlautsuc == "Übergrö"
+	expect resp.http.decumlautslc == "Übergrößen"
+	expect resp.http.decphkuc == "småbørns"
+	expect resp.http.decphklc == "småbørnspædagog"
+	expect resp.http.decutf8uc == "日"
+	expect resp.http.decutf8lc == "日扼"
+	expect resp.http.decalluc == "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+	expect resp.http.decalllc == "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F"
+	expect resp.http.decempty == ""
+	expect resp.http.decemptybyte == ""
+	expect resp.http.decemptypieces == ""
+	expect resp.http.dec2pieces == "foo "
+	expect resp.http.dec3pieces == "foo bar baz "
+	expect resp.http.dec3param == "foo bar "
+	expect resp.http.decmanypieces == "foo bar baz quux"
+} -run
+
+# Decode failures
+
+server s1 -repeat 4 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+                if (req.url == "/percent") {
+		        set resp.http.bad = blob.encode(URLUC,
+			                    blob.decode_n(1, URL, "%20"));
+                }
+                elsif (req.url == "/percent-two") {
+	                set resp.http.bad = blob.encode(URLUC,
+                                            blob.decode_n(2, URL, "%20"));
+                }
+                elsif (req.url == "/comma") {
+	                set resp.http.good = blob.encode(IDENTITY,
+                                            blob.decode_n(3, URL, "%2c%q"));
+                }
+                elsif (req.url == "/colon") {
+	                set resp.http.good = blob.encode(IDENTITY,
+                                           blob.decode_n(3, URL, "%3a%2q"));
+                }
+	}
+}
+
+client c1 {
+	txreq -url /comma
+	rxresp
+	expect resp.status == 200
+	expect resp.http.good == ","
+	txreq -url /colon
+	rxresp
+	expect resp.status == 200
+	expect resp.http.good == ":"
+	txreq -url /percent
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /percent-two
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+        expect * * VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"%20\"$"
+        expect * * VCL_Error "^vmod blob error: cannot decode, illegal encoding beginning with \"%20\"$"
+} -run
diff --git a/bin/varnishtest/tests/m00041.vtc b/bin/varnishtest/tests/m00041.vtc
new file mode 100644
index 0000000..8ddd7c0
--- /dev/null
+++ b/bin/varnishtest/tests/m00041.vtc
@@ -0,0 +1,474 @@
+varnishtest "VMOD blob test transcode()"
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.foobar = "foo:bar:baz:quux";
+	    set req.http.hobbes =
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."};
+	    set req.http.hobbesb64 =
+{"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="};
+            set req.http.hexhobbes = "4d616e2069732064697374696e677569736865642c206e6f74206f6e6c792062792068697320726561736f6e2c2062757420627920746869732073696e67756c61722070617373696f6e2066726f6d206f7468657220616e696d616c732c2077686963682069732061206c757374206f6620746865206d696e642c20746861742062792061207065727365766572616e6365206f662064656c6967687420696e2074686520636f6e74696e75656420616e6420696e6465666174696761626c652067656e65726174696f6e206f66206b6e6f776c656467652c2065786365656473207468652073686f727420766568656d656e6365206f6620616e79206361726e616c20706c6561737572652e";
+	    set req.http.hexalldown =
+{"fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"};
+	    set req.http.hexallup =
+{"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"};
+	    set req.http.b64all =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999++++////"};
+	    set req.http.b64allurl =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999----____"};
+
+	  set resp.http.id2id
+	    = blob.transcode(encoded="Hello world");
+
+	  set resp.http.id2b64
+	    = blob.transcode(encoded=req.http.hobbes, encoding=BASE64);
+
+	  set resp.http.id2b64nopad
+	    = blob.transcode(IDENTITY, BASE64URLNOPAD, req.http.hobbes);
+
+	  set resp.http.id2hexuc
+	    = blob.transcode(IDENTITY, HEXUC, req.http.hobbes);
+
+	  set resp.http.id2hexlc
+	    = blob.transcode(IDENTITY, HEXLC, req.http.hobbes);
+
+	  set resp.http.id2urluc
+	    = blob.transcode(IDENTITY, URLUC, req.http.foobar);
+
+	  set resp.http.id2urllc
+	    = blob.transcode(IDENTITY, URLLC, req.http.foobar);
+
+	  set resp.http.b642id
+	    = blob.transcode(BASE64, IDENTITY, req.http.hobbesb64);
+
+	  set resp.http.hex2id
+	    = blob.transcode(HEX, IDENTITY, req.http.hexhobbes);
+
+	  set resp.http.hexalldown2b64
+	    = blob.transcode(HEX, BASE64, req.http.hexalldown);
+
+	  set resp.http.hexalldown2b64url
+	    = blob.transcode(HEX, BASE64URL, req.http.hexalldown);
+
+	  set resp.http.hexalldown2b64nopad
+	    = blob.transcode(HEX, BASE64URLNOPAD, req.http.hexalldown);
+
+	  set resp.http.hexallup2b64
+	    = blob.transcode(HEX, BASE64, req.http.hexallup);
+
+	  set resp.http.hexallup2b64url
+	    = blob.transcode(HEX, BASE64URL, req.http.hexallup);
+
+	  set resp.http.hexallup2b64nopad
+	    = blob.transcode(HEX, BASE64URLNOPAD, req.http.hexallup);
+
+	  set resp.http.hexalldown2urluc
+	    = blob.transcode(HEX, URLUC, req.http.hexalldown);
+
+	  set resp.http.hexalldown2urllc
+	    = blob.transcode(HEX, URLLC, req.http.hexalldown);
+
+	  set resp.http.hexallup2urluc
+	    = blob.transcode(HEX, URLUC, req.http.hexallup);
+
+	  set resp.http.hexallup2urllc
+	    = blob.transcode(HEX, URLLC, req.http.hexallup);
+
+	  set resp.http.b64all2hexuc
+	    = blob.transcode(BASE64, HEXUC, req.http.b64all);
+
+	  set resp.http.b64all2hexlc
+	    = blob.transcode(BASE64, HEXLC, req.http.b64all);
+
+	  set resp.http.b64allurl2hexuc
+	    = blob.transcode(BASE64URL, HEXUC, req.http.b64allurl);
+
+	  set resp.http.b64allurl2hexlc
+	    = blob.transcode(BASE64URL, HEXLC, req.http.b64allurl);
+
+	  set resp.http.b64all2urluc
+	    = blob.transcode(BASE64, URLUC, req.http.b64all);
+
+	  set resp.http.b64all2urllc
+	    = blob.transcode(BASE64, URLLC, req.http.b64all);
+
+	  set resp.http.b64allurl2urluc
+	    = blob.transcode(BASE64URL, URLUC, req.http.b64allurl);
+
+	  set resp.http.b64allurl2urllc
+	    = blob.transcode(BASE64URL, URLLC, req.http.b64allurl);
+
+	  set resp.http.hexuc2hexuc
+	    = blob.transcode(HEX, HEXUC, "0123456789ABCDEF");
+
+	  set resp.http.hexlc2hexlc
+	    = blob.transcode(HEX, HEXLC, "0123456789abcdef");
+
+	  set resp.http.hexuc2hexlc
+	    = blob.transcode(HEX, HEXLC, "0123456789ABCDEF");
+
+	  set resp.http.hexlc2hexuc
+	    = blob.transcode(HEX, HEXUC, "0123456789abcdef");
+
+	  set resp.http.hexmix2hexuc
+	    = blob.transcode(HEX, HEXUC, "0123456789ABCdef");
+
+	  set resp.http.hexmix2hexlc
+	    = blob.transcode(HEX, HEXLC, "0123456789abcDEF");
+
+	  set req.http.hexpart1 = "01234567";
+	  set req.http.hexpart2 = "89abcdef";
+	  set resp.http.hexparam
+	    = blob.transcode(encoded="" + req.http.unset + ""
+				 + req.http.unset + "" + req.http.hexpart1
+				 + "" + req.http.unset + "" + req.http.unset
+				 + req.http.hexpart2, decoding=HEX,
+				 encoding=HEXUC);
+
+	  set resp.http.b642b64
+	    = blob.transcode(BASE64, BASE64,
+				 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+				 + "ghijklmnopqrstuvwxyz0123456789+/");
+
+	  set resp.http.b64url2b64url =
+	    blob.transcode(BASE64URL, BASE64URL,
+			       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+			       + "ghijklmnopqrstuvwxyz0123456789-_");
+
+	  set resp.http.b64urlnopad2b64urlnopad =
+	    blob.transcode(BASE64URLNOPAD, BASE64URLNOPAD,
+			       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+			       + "ghijklmnopqrstuvwxyz0123456789-_");
+
+	}
+} -start
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.http.id2id == "Hello world"
+	expect resp.http.id2b64 == "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="
+	expect resp.http.id2b64nopad == "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4"
+	expect resp.http.id2hexuc == "4D616E2069732064697374696E677569736865642C206E6F74206F6E6C792062792068697320726561736F6E2C2062757420627920746869732073696E67756C61722070617373696F6E2066726F6D206F7468657220616E696D616C732C2077686963682069732061206C757374206F6620746865206D696E642C20746861742062792061207065727365766572616E6365206F662064656C6967687420696E2074686520636F6E74696E75656420616E6420696E6465666174696761626C652067656E65726174696F6E206F66206B6E6F776C656467652C2065786365656473207468652073686F727420766568656D656E6365206F6620616E79206361726E616C20706C6561737572652E"
+	expect resp.http.id2hexlc == "4d616e2069732064697374696e677569736865642c206e6f74206f6e6c792062792068697320726561736f6e2c2062757420627920746869732073696e67756c61722070617373696f6e2066726f6d206f7468657220616e696d616c732c2077686963682069732061206c757374206f6620746865206d696e642c20746861742062792061207065727365766572616e6365206f662064656c6967687420696e2074686520636f6e74696e75656420616e6420696e6465666174696761626c652067656e65726174696f6e206f66206b6e6f776c656467652c2065786365656473207468652073686f727420766568656d656e6365206f6620616e79206361726e616c20706c6561737572652e"
+	expect resp.http.id2urluc == "foo%3Abar%3Abaz%3Aquux"
+	expect resp.http.id2urllc == "foo%3abar%3abaz%3aquux"
+	expect resp.http.b642id == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.hex2id == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.hexalldown2b64 == "//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.hexalldown2b64url == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.hexalldown2b64nopad == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA"
+	expect resp.http.hexallup2b64 == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.hexallup2b64url == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w=="
+	expect resp.http.hexallup2b64nopad == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w"
+	expect resp.http.hexalldown2urluc == "%FF%FE%FD%FC%FB%FA%F9%F8%F7%F6%F5%F4%F3%F2%F1%F0%EF%EE%ED%EC%EB%EA%E9%E8%E7%E6%E5%E4%E3%E2%E1%E0%DF%DE%DD%DC%DB%DA%D9%D8%D7%D6%D5%D4%D3%D2%D1%D0%CF%CE%CD%CC%CB%CA%C9%C8%C7%C6%C5%C4%C3%C2%C1%C0%BF%BE%BD%BC%BB%BA%B9%B8%B7%B6%B5%B4%B3%B2%B1%B0%AF%AE%AD%AC%AB%AA%A9%A8%A7%A6%A5%A4%A3%A2%A1%A0%9F%9E%9D%9C%9B%9A%99%98%97%96%95%94%93%92%91%90%8F%8E%8D%8C%8B%8A%89%88%87%86%85%84%83%82%81%80%7F~%7D%7C%7Bzyxwvutsrqponmlkjihgfedcba%60_%5E%5D%5C%5BZYXWVUTSRQPONMLKJIHGFEDCBA%40%3F%3E%3D%3C%3B%3A9876543210%2F.-%2C%2B%2A%29%28%27%26%25%24%23%22%21%20%1F%1E%1D%1C%1B%1A%19%18%17%16%15%14%13%12%11%10%0F%0E%0D%0C%0B%0A%09%08%07%06%05%04%03%02%01%00"
+	expect resp.http.hexalldown2urllc == "%ff%fe%fd%fc%fb%fa%f9%f8%f7%f6%f5%f4%f3%f2%f1%f0%ef%ee%ed%ec%eb%ea%e9%e8%e7%e6%e5%e4%e3%e2%e1%e0%df%de%dd%dc%db%da%d9%d8%d7%d6%d5%d4%d3%d2%d1%d0%cf%ce%cd%cc%cb%ca%c9%c8%c7%c6%c5%c4%c3%c2%c1%c0%bf%be%bd%bc%bb%ba%b9%b8%b7%b6%b5%b4%b3%b2%b1%b0%af%ae%ad%ac%ab%aa%a9%a8%a7%a6%a5%a4%a3%a2%a1%a0%9f%9e%9d%9c%9b%9a%99%98%97%96%95%94%93%92%91%90%8f%8e%8d%8c%8b%8a%89%88%87%86%85%84%83%82%81%80%7f~%7d%7c%7bzyxwvutsrqponmlkjihgfedcba%60_%5e%5d%5c%5bZYXWVUTSRQPONMLKJIHGFEDCBA%40%3f%3e%3d%3c%3b%3a9876543210%2f.-%2c%2b%2a%29%28%27%26%25%24%23%22%21%20%1f%1e%1d%1c%1b%1a%19%18%17%16%15%14%13%12%11%10%0f%0e%0d%0c%0b%0a%09%08%07%06%05%04%03%02%01%00"
+	expect resp.http.hexallup2urluc == "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
+	expect resp.http.hexallup2urllc == "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff"
+	expect resp.http.b64all2hexuc == "0000000410410820820C30C31041041451451861861C71C720820824924928A28A2CB2CB30C30C34D34D38E38E3CF3CF4104104514514924924D34D35145145555555965965D75D761861865965969A69A6DB6DB71C71C75D75D79E79E7DF7DF8208208618618A28A28E38E39249249659659A69A69E79E7A28A28A69A69AAAAAAAEBAEBB2CB2CB6DB6DBAEBAEBEFBEFC30C30C71C71CB2CB2CF3CF3D34D34D75D75DB6DB6DF7DF7E38E38E79E79EBAEBAEFBEFBF3CF3CF7DF7DFBEFBEFFFFFF"
+	expect resp.http.b64all2hexlc == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df8208208618618a28a28e38e39249249659659a69a69e79e7a28a28a69a69aaaaaaaebaebb2cb2cb6db6dbaebaebefbefc30c30c71c71cb2cb2cf3cf3d34d34d75d75db6db6df7df7e38e38e79e79ebaebaefbefbf3cf3cf7df7dfbefbeffffff"
+	expect resp.http.b64allurl2hexuc == "0000000410410820820C30C31041041451451861861C71C720820824924928A28A2CB2CB30C30C34D34D38E38E3CF3CF4104104514514924924D34D35145145555555965965D75D761861865965969A69A6DB6DB71C71C75D75D79E79E7DF7DF8208208618618A28A28E38E39249249659659A69A69E79E7A28A28A69A69AAAAAAAEBAEBB2CB2CB6DB6DBAEBAEBEFBEFC30C30C71C71CB2CB2CF3CF3D34D34D75D75DB6DB6DF7DF7E38E38E79E79EBAEBAEFBEFBF3CF3CF7DF7DFBEFBEFFFFFF"
+	expect resp.http.b64allurl2hexlc == "0000000410410820820c30c31041041451451861861c71c720820824924928a28a2cb2cb30c30c34d34d38e38e3cf3cf4104104514514924924d34d35145145555555965965d75d761861865965969a69a6db6db71c71c75d75d79e79e7df7df8208208618618a28a28e38e39249249659659a69a69e79e7a28a28a69a69aaaaaaaebaebb2cb2cb6db6dbaebaebefbefc30c30c71c71cb2cb2cf3cf3d34d34d75d75db6db6df7df7e38e38e79e79ebaebaefbefbf3cf3cf7df7dfbefbeffffff"
+	expect resp.http.b64all2urluc == "%00%00%00%04%10A%08%20%82%0C0%C3%10A%04%14QE%18a%86%1Cq%C7%20%82%08%24%92I%28%A2%8A%2C%B2%CB0%C3%0C4%D3M8%E3%8E%3C%F3%CFA%04%10E%14QI%24%92M4%D3QE%14UUUYe%96%5Du%D7a%86%18e%96Yi%A6%9Am%B6%DBq%C7%1Cu%D7%5Dy%E7%9E%7D%F7%DF%82%08%20%86%18a%8A%28%A2%8E8%E3%92I%24%96Ye%9Ai%A6%9Ey%E7%A2%8A%28%A6%9Ai%AA%AA%AA%AE%BA%EB%B2%CB%2C%B6%DBm%BA%EB%AE%BE%FB%EF%C3%0C0%C7%1Cq%CB%2C%B2%CF%3C%F3%D3M4%D7%5Du%DBm%B6%DF%7D%F7%E3%8E8%E7%9Ey%EB%AE%BA%EF%BE%FB%F3%CF%3C%F7%DF%7D%FB%EF%BE%FF%FF%FF"
+	expect resp.http.b64all2urllc == "%00%00%00%04%10A%08%20%82%0c0%c3%10A%04%14QE%18a%86%1cq%c7%20%82%08%24%92I%28%a2%8a%2c%b2%cb0%c3%0c4%d3M8%e3%8e%3c%f3%cfA%04%10E%14QI%24%92M4%d3QE%14UUUYe%96%5du%d7a%86%18e%96Yi%a6%9am%b6%dbq%c7%1cu%d7%5dy%e7%9e%7d%f7%df%82%08%20%86%18a%8a%28%a2%8e8%e3%92I%24%96Ye%9ai%a6%9ey%e7%a2%8a%28%a6%9ai%aa%aa%aa%ae%ba%eb%b2%cb%2c%b6%dbm%ba%eb%ae%be%fb%ef%c3%0c0%c7%1cq%cb%2c%b2%cf%3c%f3%d3M4%d7%5du%dbm%b6%df%7d%f7%e3%8e8%e7%9ey%eb%ae%ba%ef%be%fb%f3%cf%3c%f7%df%7d%fb%ef%be%ff%ff%ff"
+	expect resp.http.b64allurl2urluc == "%00%00%00%04%10A%08%20%82%0C0%C3%10A%04%14QE%18a%86%1Cq%C7%20%82%08%24%92I%28%A2%8A%2C%B2%CB0%C3%0C4%D3M8%E3%8E%3C%F3%CFA%04%10E%14QI%24%92M4%D3QE%14UUUYe%96%5Du%D7a%86%18e%96Yi%A6%9Am%B6%DBq%C7%1Cu%D7%5Dy%E7%9E%7D%F7%DF%82%08%20%86%18a%8A%28%A2%8E8%E3%92I%24%96Ye%9Ai%A6%9Ey%E7%A2%8A%28%A6%9Ai%AA%AA%AA%AE%BA%EB%B2%CB%2C%B6%DBm%BA%EB%AE%BE%FB%EF%C3%0C0%C7%1Cq%CB%2C%B2%CF%3C%F3%D3M4%D7%5Du%DBm%B6%DF%7D%F7%E3%8E8%E7%9Ey%EB%AE%BA%EF%BE%FB%F3%CF%3C%F7%DF%7D%FB%EF%BE%FF%FF%FF"
+	expect resp.http.b64allurl2urllc == "%00%00%00%04%10A%08%20%82%0c0%c3%10A%04%14QE%18a%86%1cq%c7%20%82%08%24%92I%28%a2%8a%2c%b2%cb0%c3%0c4%d3M8%e3%8e%3c%f3%cfA%04%10E%14QI%24%92M4%d3QE%14UUUYe%96%5du%d7a%86%18e%96Yi%a6%9am%b6%dbq%c7%1cu%d7%5dy%e7%9e%7d%f7%df%82%08%20%86%18a%8a%28%a2%8e8%e3%92I%24%96Ye%9ai%a6%9ey%e7%a2%8a%28%a6%9ai%aa%aa%aa%ae%ba%eb%b2%cb%2c%b6%dbm%ba%eb%ae%be%fb%ef%c3%0c0%c7%1cq%cb%2c%b2%cf%3c%f3%d3M4%d7%5du%dbm%b6%df%7d%f7%e3%8e8%e7%9ey%eb%ae%ba%ef%be%fb%f3%cf%3c%f7%df%7d%fb%ef%be%ff%ff%ff"
+	expect resp.http.hexuc2hexuc == "0123456789ABCDEF"
+	expect resp.http.hexlc2hexlc == "0123456789abcdef"
+	expect resp.http.hexuc2hexlc == "0123456789abcdef"
+	expect resp.http.hexlc2hexuc == "0123456789ABCDEF"
+	expect resp.http.hexmix2hexuc == "0123456789ABCDEF"
+	expect resp.http.hexmix2hexlc == "0123456789abcdef"
+	expect resp.http.hexparam == "0123456789ABCDEF"
+	expect resp.http.b642b64 == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+	expect resp.http.b64url2b64url == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+	expect resp.http.b64urlnopad2b64urlnopad == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+} -run
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+            set req.http.urlhobbeslc = "Man%20is%20distinguished%2c%20not%20only%20by%20his%20reason%2c%20but%20by%20this%20singular%20passion%20from%20other%20animals%2c%20which%20is%20a%20lust%20of%20the%20mind%2c%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2c%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+	    set req.http.urlhobbesuc = "Man%20is%20distinguished%2C%20not%20only%20by%20his%20reason%2C%20but%20by%20this%20singular%20passion%20from%20other%20animals%2C%20which%20is%20a%20lust%20of%20the%20mind%2C%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2C%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+            set req.http.urlallupuc = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF";
+	    set req.http.urlalluplc = "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff";
+            set req.http.urlalldownuc = "%FF%FE%FD%FC%FB%FA%F9%F8%F7%F6%F5%F4%F3%F2%F1%F0%EF%EE%ED%EC%EB%EA%E9%E8%E7%E6%E5%E4%E3%E2%E1%E0%DF%DE%DD%DC%DB%DA%D9%D8%D7%D6%D5%D4%D3%D2%D1%D0%CF%CE%CD%CC%CB%CA%C9%C8%C7%C6%C5%C4%C3%C2%C1%C0%BF%BE%BD%BC%BB%BA%B9%B8%B7%B6%B5%B4%B3%B2%B1%B0%AF%AE%AD%AC%AB%AA%A9%A8%A7%A6%A5%A4%A3%A2%A1%A0%9F%9E%9D%9C%9B%9A%99%98%97%96%95%94%93%92%91%90%8F%8E%8D%8C%8B%8A%89%88%87%86%85%84%83%82%81%80%7F~%7D%7C%7Bzyxwvutsrqponmlkjihgfedcba%60_%5E%5D%5C%5BZYXWVUTSRQPONMLKJIHGFEDCBA%40%3F%3E%3D%3C%3B%3A9876543210%2F.-%2C%2B%2A%29%28%27%26%25%24%23%22%21%20%1F%1E%1D%1C%1B%1A%19%18%17%16%15%14%13%12%11%10%0F%0E%0D%0C%0B%0A%09%08%07%06%05%04%03%02%01%00";
+	    set req.http.urlalldownlc = "%ff%fe%fd%fc%fb%fa%f9%f8%f7%f6%f5%f4%f3%f2%f1%f0%ef%ee%ed%ec%eb%ea%e9%e8%e7%e6%e5%e4%e3%e2%e1%e0%df%de%dd%dc%db%da%d9%d8%d7%d6%d5%d4%d3%d2%d1%d0%cf%ce%cd%cc%cb%ca%c9%c8%c7%c6%c5%c4%c3%c2%c1%c0%bf%be%bd%bc%bb%ba%b9%b8%b7%b6%b5%b4%b3%b2%b1%b0%af%ae%ad%ac%ab%aa%a9%a8%a7%a6%a5%a4%a3%a2%a1%a0%9f%9e%9d%9c%9b%9a%99%98%97%96%95%94%93%92%91%90%8f%8e%8d%8c%8b%8a%89%88%87%86%85%84%83%82%81%80%7f~%7d%7c%7bzyxwvutsrqponmlkjihgfedcba%60_%5e%5d%5c%5bZYXWVUTSRQPONMLKJIHGFEDCBA%40%3f%3e%3d%3c%3b%3a9876543210%2f.-%2c%2b%2a%29%28%27%26%25%24%23%22%21%20%1f%1e%1d%1c%1b%1a%19%18%17%16%15%14%13%12%11%10%0f%0e%0d%0c%0b%0a%09%08%07%06%05%04%03%02%01%00";
+
+	  set resp.http.urluc2id
+	    = blob.transcode(URL, IDENTITY, req.http.urlhobbesuc);
+
+	  set resp.http.urllc2id
+	    = blob.transcode(URL, IDENTITY, req.http.urlhobbeslc);
+
+	  set resp.http.urlalldownuc2b64
+	    = blob.transcode(URL, BASE64, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownuc2b64url
+	    = blob.transcode(URL, BASE64URL, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownuc2b64nopad
+	    = blob.transcode(URL, BASE64URLNOPAD, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownlc2b64
+	    = blob.transcode(URL, BASE64, req.http.urlalldownlc);
+
+	  set resp.http.urlalldownlc2b64url
+	    = blob.transcode(URL, BASE64URL, req.http.urlalldownlc);
+
+	  set resp.http.urlalldownlc2b64nopad
+	    = blob.transcode(URL, BASE64URLNOPAD, req.http.urlalldownlc);
+
+	  set resp.http.urlallupuc2b64
+	    = blob.transcode(URL, BASE64, req.http.urlallupuc);
+
+	  set resp.http.urlallupuc2b64url
+	    = blob.transcode(URL, BASE64URL, req.http.urlallupuc);
+
+	  set resp.http.urlallupuc2b64nopad
+	    = blob.transcode(URL, BASE64URLNOPAD, req.http.urlallupuc);
+
+	  set resp.http.urlalluplc2b64
+	    = blob.transcode(URL, BASE64, req.http.urlalluplc);
+
+	  set resp.http.urlalluplc2b64url
+	    = blob.transcode(URL, BASE64URL, req.http.urlalluplc);
+
+	  set resp.http.urlalluplc2b64nopad
+	    = blob.transcode(URL, BASE64URLNOPAD, req.http.urlalluplc);
+
+	  set resp.http.urlalldownuc2urluc
+	    = blob.transcode(URL, URLUC, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownuc2urllc
+	    = blob.transcode(URL, URLLC, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownlc2urluc
+	    = blob.transcode(URL, URLUC, req.http.urlalldownlc);
+
+	  set resp.http.urlalldownlc2urllc
+	    = blob.transcode(URL, URLLC, req.http.urlalldownlc);
+
+	  set resp.http.urlallupuc2urluc
+	    = blob.transcode(URL, URLUC, req.http.urlallupuc);
+
+	  set resp.http.urlallupuc2urllc
+	    = blob.transcode(URL, URLLC, req.http.urlallupuc);
+
+	  set resp.http.urlalluplc2urluc
+	    = blob.transcode(URL, URLUC, req.http.urlalluplc);
+
+	  set resp.http.urlalluplc2urllc
+	    = blob.transcode(URL, URLLC, req.http.urlalluplc);
+
+	  }
+}
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.http.urluc2id == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.urllc2id == "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
+	expect resp.http.urlalldownuc2b64 == "//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.urlalldownuc2b64url == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.urlalldownuc2b64nopad == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA"
+	expect resp.http.urlalldownlc2b64 == "//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.urlalldownlc2b64url == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA=="
+	expect resp.http.urlalldownlc2b64nopad == "__79_Pv6-fj39vX08_Lx8O_u7ezr6uno5-bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL--vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI-OjYyLiomIh4aFhIOCgYB_fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAA"
+	expect resp.http.urlallupuc2b64 == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.urlallupuc2b64url == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w=="
+	expect resp.http.urlallupuc2b64nopad == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w"
+	expect resp.http.urlalluplc2b64 == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.urlalluplc2b64url == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w=="
+	expect resp.http.urlalluplc2b64nopad == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w"
+	expect resp.http.urlalldownuc2urluc == "%FF%FE%FD%FC%FB%FA%F9%F8%F7%F6%F5%F4%F3%F2%F1%F0%EF%EE%ED%EC%EB%EA%E9%E8%E7%E6%E5%E4%E3%E2%E1%E0%DF%DE%DD%DC%DB%DA%D9%D8%D7%D6%D5%D4%D3%D2%D1%D0%CF%CE%CD%CC%CB%CA%C9%C8%C7%C6%C5%C4%C3%C2%C1%C0%BF%BE%BD%BC%BB%BA%B9%B8%B7%B6%B5%B4%B3%B2%B1%B0%AF%AE%AD%AC%AB%AA%A9%A8%A7%A6%A5%A4%A3%A2%A1%A0%9F%9E%9D%9C%9B%9A%99%98%97%96%95%94%93%92%91%90%8F%8E%8D%8C%8B%8A%89%88%87%86%85%84%83%82%81%80%7F~%7D%7C%7Bzyxwvutsrqponmlkjihgfedcba%60_%5E%5D%5C%5BZYXWVUTSRQPONMLKJIHGFEDCBA%40%3F%3E%3D%3C%3B%3A9876543210%2F.-%2C%2B%2A%29%28%27%26%25%24%23%22%21%20%1F%1E%1D%1C%1B%1A%19%18%17%16%15%14%13%12%11%10%0F%0E%0D%0C%0B%0A%09%08%07%06%05%04%03%02%01%00"
+	expect resp.http.urlalldownuc2urllc == "%ff%fe%fd%fc%fb%fa%f9%f8%f7%f6%f5%f4%f3%f2%f1%f0%ef%ee%ed%ec%eb%ea%e9%e8%e7%e6%e5%e4%e3%e2%e1%e0%df%de%dd%dc%db%da%d9%d8%d7%d6%d5%d4%d3%d2%d1%d0%cf%ce%cd%cc%cb%ca%c9%c8%c7%c6%c5%c4%c3%c2%c1%c0%bf%be%bd%bc%bb%ba%b9%b8%b7%b6%b5%b4%b3%b2%b1%b0%af%ae%ad%ac%ab%aa%a9%a8%a7%a6%a5%a4%a3%a2%a1%a0%9f%9e%9d%9c%9b%9a%99%98%97%96%95%94%93%92%91%90%8f%8e%8d%8c%8b%8a%89%88%87%86%85%84%83%82%81%80%7f~%7d%7c%7bzyxwvutsrqponmlkjihgfedcba%60_%5e%5d%5c%5bZYXWVUTSRQPONMLKJIHGFEDCBA%40%3f%3e%3d%3c%3b%3a9876543210%2f.-%2c%2b%2a%29%28%27%26%25%24%23%22%21%20%1f%1e%1d%1c%1b%1a%19%18%17%16%15%14%13%12%11%10%0f%0e%0d%0c%0b%0a%09%08%07%06%05%04%03%02%01%00"
+	expect resp.http.urlalldownlc2urluc == "%FF%FE%FD%FC%FB%FA%F9%F8%F7%F6%F5%F4%F3%F2%F1%F0%EF%EE%ED%EC%EB%EA%E9%E8%E7%E6%E5%E4%E3%E2%E1%E0%DF%DE%DD%DC%DB%DA%D9%D8%D7%D6%D5%D4%D3%D2%D1%D0%CF%CE%CD%CC%CB%CA%C9%C8%C7%C6%C5%C4%C3%C2%C1%C0%BF%BE%BD%BC%BB%BA%B9%B8%B7%B6%B5%B4%B3%B2%B1%B0%AF%AE%AD%AC%AB%AA%A9%A8%A7%A6%A5%A4%A3%A2%A1%A0%9F%9E%9D%9C%9B%9A%99%98%97%96%95%94%93%92%91%90%8F%8E%8D%8C%8B%8A%89%88%87%86%85%84%83%82%81%80%7F~%7D%7C%7Bzyxwvutsrqponmlkjihgfedcba%60_%5E%5D%5C%5BZYXWVUTSRQPONMLKJIHGFEDCBA%40%3F%3E%3D%3C%3B%3A9876543210%2F.-%2C%2B%2A%29%28%27%26%25%24%23%22%21%20%1F%1E%1D%1C%1B%1A%19%18%17%16%15%14%13%12%11%10%0F%0E%0D%0C%0B%0A%09%08%07%06%05%04%03%02%01%00"
+	expect resp.http.urlalldownlc2urllc == "%ff%fe%fd%fc%fb%fa%f9%f8%f7%f6%f5%f4%f3%f2%f1%f0%ef%ee%ed%ec%eb%ea%e9%e8%e7%e6%e5%e4%e3%e2%e1%e0%df%de%dd%dc%db%da%d9%d8%d7%d6%d5%d4%d3%d2%d1%d0%cf%ce%cd%cc%cb%ca%c9%c8%c7%c6%c5%c4%c3%c2%c1%c0%bf%be%bd%bc%bb%ba%b9%b8%b7%b6%b5%b4%b3%b2%b1%b0%af%ae%ad%ac%ab%aa%a9%a8%a7%a6%a5%a4%a3%a2%a1%a0%9f%9e%9d%9c%9b%9a%99%98%97%96%95%94%93%92%91%90%8f%8e%8d%8c%8b%8a%89%88%87%86%85%84%83%82%81%80%7f~%7d%7c%7bzyxwvutsrqponmlkjihgfedcba%60_%5e%5d%5c%5bZYXWVUTSRQPONMLKJIHGFEDCBA%40%3f%3e%3d%3c%3b%3a9876543210%2f.-%2c%2b%2a%29%28%27%26%25%24%23%22%21%20%1f%1e%1d%1c%1b%1a%19%18%17%16%15%14%13%12%11%10%0f%0e%0d%0c%0b%0a%09%08%07%06%05%04%03%02%01%00"
+	expect resp.http.urlallupuc2urluc == "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
+	expect resp.http.urlalluplc2urluc == "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
+	expect resp.http.urlallupuc2urllc == "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff"
+	expect resp.http.urlalluplc2urllc == "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff"
+} -run
+
+# Decode failures
+
+server s1 -repeat 11 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+	        set req.http.foo = "AAA=";
+                if (req.url == "/1") {
+	                set resp.http.bad
+                            = blob.transcode(HEX, IDENTITY, "0x123456");
+                }
+                elsif (req.url == "/2") {
+	                set resp.http.bad
+                            = blob.transcode(BASE64, IDENTITY,
+						 "-_-_" + req.http.foo);
+                }
+                elsif (req.url == "/3") {
+	                set resp.http.bad
+                            = blob.transcode(BASE64URL, IDENTITY,
+						 "+/+/" + req.http.foo);
+                }
+                elsif (req.url == "/4") {
+	                set resp.http.bad
+                            = blob.transcode(BASE64URLNOPAD, IDENTITY,
+						 "TWFu" + req.http.foo);
+                }
+                elsif (req.url == "/5") {
+	                set resp.http.bad
+                            = blob.transcode(BASE64, BASE64,
+						 "_-_-" + req.http.foo);
+                }
+                elsif (req.url == "/6") {
+	                set resp.http.bad
+	                    = blob.transcode(BASE64URL, BASE64URL,
+                                                 "/+/+" + req.http.foo);
+                }
+                elsif (req.url == "/7") {
+	                set resp.http.bad
+	                    = blob.transcode(BASE64URLNOPAD, BASE64URLNOPAD,
+			                         "Zm9v" + req.http.foo);
+                }
+                elsif (req.url == "/8") {
+	                set resp.http.bad
+                            = blob.transcode(URL, IDENTITY, "%");
+                }
+                elsif (req.url == "/9") {
+	                set resp.http.bad
+                            = blob.transcode(URL, IDENTITY, "%2");
+                }
+                elsif (req.url == "/10") {
+	                set resp.http.bad
+                            = blob.transcode(URL, IDENTITY, "%q");
+                }
+                elsif (req.url == "/11") {
+	                set resp.http.bad
+                            = blob.transcode(URL, IDENTITY, "%2q");
+                }
+	}
+}
+
+client c1 {
+	txreq -url /1
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /2
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /3
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /4
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /5
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /6
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /7
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /8
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /9
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /10
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /11
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "0x123456"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "-_-_"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "\+/\+/"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "TWFu"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "_-_-"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "/\+/\+"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "Zm9v"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "%"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "%2"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "%q"$}
+        expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "%2q"$}
+} -run
diff --git a/bin/varnishtest/tests/m00042.vtc b/bin/varnishtest/tests/m00042.vtc
new file mode 100644
index 0000000..a34f7d0
--- /dev/null
+++ b/bin/varnishtest/tests/m00042.vtc
@@ -0,0 +1,470 @@
+varnishtest "VMOD blob test transcode_n()"
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	    set req.http.foobar = "foo:bar:baz:quux";
+	    set req.http.hobbes =
+{"Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."};
+	    set req.http.hobbesb64 =
+{"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="};
+            set req.http.hexhobbes = "4d616e2069732064697374696e677569736865642c206e6f74206f6e6c792062792068697320726561736f6e2c2062757420627920746869732073696e67756c61722070617373696f6e2066726f6d206f7468657220616e696d616c732c2077686963682069732061206c757374206f6620746865206d696e642c20746861742062792061207065727365766572616e6365206f662064656c6967687420696e2074686520636f6e74696e75656420616e6420696e6465666174696761626c652067656e65726174696f6e206f66206b6e6f776c656467652c2065786365656473207468652073686f727420766568656d656e6365206f6620616e79206361726e616c20706c6561737572652e";
+	    set req.http.hexalldown =
+{"fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"};
+	    set req.http.hexallup =
+{"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"};
+	    set req.http.b64all =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999++++////"};
+	    set req.http.b64allurl =
+{"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999----____"};
+
+          set resp.http.id2id
+	    = blob.transcode_n(5, IDENTITY, IDENTITY, "Hello world");
+
+	  set resp.http.id2b64
+	    = blob.transcode_n(5, IDENTITY, BASE64, req.http.hobbes);
+
+	  set resp.http.id2b64nopad
+	    = blob.transcode_n(5, IDENTITY, BASE64URLNOPAD, req.http.hobbes);
+
+	  set resp.http.id2hexuc
+	    = blob.transcode_n(5, IDENTITY, HEXUC, req.http.hobbes);
+
+	  set resp.http.id2hexlc
+	    = blob.transcode_n(5, IDENTITY, HEXLC, req.http.hobbes);
+
+	  set resp.http.id2urluc
+	    = blob.transcode_n(5, IDENTITY, URLUC, req.http.foobar);
+
+	  set resp.http.id2urllc
+	    = blob.transcode_n(5, IDENTITY, URLLC, req.http.foobar);
+
+	  set resp.http.b642id
+	    = blob.transcode_n(8, BASE64, IDENTITY, req.http.hobbesb64);
+
+	  set resp.http.hex2id
+	    = blob.transcode_n(12, HEX, IDENTITY, req.http.hexhobbes);
+
+	  set resp.http.hexalldown2b64
+	    = blob.transcode_n(12, HEX, BASE64, req.http.hexalldown);
+
+	  set resp.http.hexalldown2b64url
+	    = blob.transcode_n(12, HEX, BASE64URL, req.http.hexalldown);
+
+	  set resp.http.hexalldown2b64nopad
+	    = blob.transcode_n(10, HEX, BASE64URLNOPAD, req.http.hexalldown);
+
+	  set resp.http.hexallup2b64
+	    = blob.transcode_n(12, HEX, BASE64, req.http.hexallup);
+
+	  set resp.http.hexallup2b64url
+	    = blob.transcode_n(10, HEX, BASE64URL, req.http.hexallup);
+
+	  set resp.http.hexallup2b64nopad
+	    = blob.transcode_n(10, HEX, BASE64URLNOPAD, req.http.hexallup);
+
+	  set resp.http.hexalldown2urluc
+	    = blob.transcode_n(12, HEX, URLUC, req.http.hexalldown);
+
+	  set resp.http.hexalldown2urllc
+	    = blob.transcode_n(12, HEX, URLLC, req.http.hexalldown);
+
+	  set resp.http.hexallup2urluc
+	    = blob.transcode_n(12, HEX, URLUC, req.http.hexallup);
+
+	  set resp.http.hexallup2urllc
+	    = blob.transcode_n(12, HEX, URLLC, req.http.hexallup);
+
+	  set resp.http.b64all2hexuc
+	    = blob.transcode_n(8, BASE64, HEXUC, req.http.b64all);
+
+	  set resp.http.b64all2hexlc
+	    = blob.transcode_n(8, BASE64, HEXLC, req.http.b64all);
+
+	  set resp.http.b64allurl2hexuc
+	    = blob.transcode_n(8, BASE64URL, HEXUC, req.http.b64allurl);
+
+	  set resp.http.b64allurl2hexlc
+	    = blob.transcode_n(8, BASE64URL, HEXLC, req.http.b64allurl);
+
+	  set resp.http.b64all2urluc
+	    = blob.transcode_n(8, BASE64, URLUC, req.http.b64all);
+
+	  set resp.http.b64all2urllc
+	    = blob.transcode_n(8, BASE64, URLLC, req.http.b64all);
+
+	  set resp.http.b64allurl2urluc
+	    = blob.transcode_n(8, BASE64URL, URLUC, req.http.b64allurl);
+
+	  set resp.http.b64allurl2urllc
+	    = blob.transcode_n(8, BASE64URL, URLLC, req.http.b64allurl);
+
+	  set resp.http.hexuc2hexuc
+	    = blob.transcode_n(10, HEX, HEXUC, "0123456789ABCDEF");
+
+	  set resp.http.hexlc2hexlc
+	    = blob.transcode_n(10, HEX, HEXLC, "0123456789abcdef");
+
+	  set resp.http.hexuc2hexlc
+	    = blob.transcode_n(9, HEX, HEXLC, "0123456789ABCDEF");
+
+	  set resp.http.hexlc2hexuc
+	    = blob.transcode_n(9, HEX, HEXUC, "0123456789abcdef");
+
+	  set resp.http.hexmix2hexuc
+	    = blob.transcode_n(15, HEX, HEXUC, "0123456789ABCdef");
+
+	  set resp.http.hexmix2hexlc
+	    = blob.transcode_n(13, HEX, HEXLC, "0123456789abcDEF");
+
+	  set req.http.hexpart1 = "01234567";
+	  set req.http.hexpart2 = "89abcdef";
+	  set resp.http.hexparam
+	    = blob.transcode_n(encoded="" + req.http.unset + ""
+				   + req.http.unset + "" + req.http.hexpart1
+				   + "" + req.http.unset + "" + req.http.unset
+				   + req.http.hexpart2, decoding=HEX,
+				   encoding=HEXUC, n=10);
+
+	  set resp.http.b642b64
+	    = blob.transcode_n(36, BASE64, BASE64,
+				   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+				   + "ghijklmnopqrstuvwxyz0123456789+/");
+
+	  set resp.http.b64url2b64url =
+	    blob.transcode_n(34, BASE64URL, BASE64URL,
+			         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+			         + "ghijklmnopqrstuvwxyz0123456789-_");
+
+	  set resp.http.b64urlnopad2b64urlnopad =
+	    blob.transcode_n(34, BASE64URLNOPAD, BASE64URLNOPAD,
+			         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+			         + "ghijklmnopqrstuvwxyz0123456789-_");
+
+	}
+} -start
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.http.id2id == "Hello"
+	expect resp.http.id2b64 == "TWFuIGk="
+	expect resp.http.id2b64nopad == "TWFuIGk"
+	expect resp.http.id2hexuc == "4D616E2069"
+	expect resp.http.id2hexlc == "4d616e2069"
+	expect resp.http.id2urluc == "foo%3Ab"
+	expect resp.http.id2urllc == "foo%3ab"
+	expect resp.http.b642id == "Man is"
+	expect resp.http.hex2id == "Man is"
+	expect resp.http.hexalldown2b64 == "//79/Pv6"
+	expect resp.http.hexalldown2b64url == "__79_Pv6"
+	expect resp.http.hexalldown2b64nopad == "__79_Ps"
+	expect resp.http.hexallup2b64 == "AAECAwQF"
+	expect resp.http.hexallup2b64url == "AAECAwQ="
+	expect resp.http.hexallup2b64nopad == "AAECAwQ"
+	expect resp.http.hexalldown2urluc == "%FF%FE%FD%FC%FB%FA"
+	expect resp.http.hexalldown2urllc == "%ff%fe%fd%fc%fb%fa"
+	expect resp.http.hexallup2urluc == "%00%01%02%03%04%05"
+	expect resp.http.hexallup2urllc == "%00%01%02%03%04%05"
+	expect resp.http.b64all2hexuc == "000000041041"
+	expect resp.http.b64all2hexlc == "000000041041"
+	expect resp.http.b64allurl2hexuc == "000000041041"
+	expect resp.http.b64allurl2hexlc == "000000041041"
+	expect resp.http.b64all2urluc == "%00%00%00%04%10A"
+	expect resp.http.b64all2urllc == "%00%00%00%04%10A"
+	expect resp.http.b64allurl2urluc == "%00%00%00%04%10A"
+	expect resp.http.b64allurl2urllc == "%00%00%00%04%10A"
+	expect resp.http.hexuc2hexuc == "0123456789"
+	expect resp.http.hexlc2hexlc == "0123456789"
+	expect resp.http.hexuc2hexlc == "0012345678"
+	expect resp.http.hexlc2hexuc == "0012345678"
+	expect resp.http.hexmix2hexuc == "00123456789ABCDE"
+	expect resp.http.hexmix2hexlc == "00123456789abc"
+	expect resp.http.hexparam == "0123456789"
+	expect resp.http.b642b64 == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij"
+	expect resp.http.b64url2b64url == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefeQ=="
+	expect resp.http.b64urlnopad2b64urlnopad == "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgg"
+} -run
+
+varnish v1 -vcl {
+	import blob;
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+            set req.http.urlhobbeslc = "Man%20is%20distinguished%2c%20not%20only%20by%20his%20reason%2c%20but%20by%20this%20singular%20passion%20from%20other%20animals%2c%20which%20is%20a%20lust%20of%20the%20mind%2c%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2c%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+	    set req.http.urlhobbesuc = "Man%20is%20distinguished%2C%20not%20only%20by%20his%20reason%2C%20but%20by%20this%20singular%20passion%20from%20other%20animals%2C%20which%20is%20a%20lust%20of%20the%20mind%2C%20that%20by%20a%20perseverance%20of%20delight%20in%20the%20continued%20and%20indefatigable%20generation%20of%20knowledge%2C%20exceeds%20the%20short%20vehemence%20of%20any%20carnal%20pleasure.";
+            set req.http.urlallupuc = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF";
+	    set req.http.urlalluplc = "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e_%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d~%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff";
+            set req.http.urlalldownuc = "%FF%FE%FD%FC%FB%FA%F9%F8%F7%F6%F5%F4%F3%F2%F1%F0%EF%EE%ED%EC%EB%EA%E9%E8%E7%E6%E5%E4%E3%E2%E1%E0%DF%DE%DD%DC%DB%DA%D9%D8%D7%D6%D5%D4%D3%D2%D1%D0%CF%CE%CD%CC%CB%CA%C9%C8%C7%C6%C5%C4%C3%C2%C1%C0%BF%BE%BD%BC%BB%BA%B9%B8%B7%B6%B5%B4%B3%B2%B1%B0%AF%AE%AD%AC%AB%AA%A9%A8%A7%A6%A5%A4%A3%A2%A1%A0%9F%9E%9D%9C%9B%9A%99%98%97%96%95%94%93%92%91%90%8F%8E%8D%8C%8B%8A%89%88%87%86%85%84%83%82%81%80%7F~%7D%7C%7Bzyxwvutsrqponmlkjihgfedcba%60_%5E%5D%5C%5BZYXWVUTSRQPONMLKJIHGFEDCBA%40%3F%3E%3D%3C%3B%3A9876543210%2F.-%2C%2B%2A%29%28%27%26%25%24%23%22%21%20%1F%1E%1D%1C%1B%1A%19%18%17%16%15%14%13%12%11%10%0F%0E%0D%0C%0B%0A%09%08%07%06%05%04%03%02%01%00";
+	    set req.http.urlalldownlc = "%ff%fe%fd%fc%fb%fa%f9%f8%f7%f6%f5%f4%f3%f2%f1%f0%ef%ee%ed%ec%eb%ea%e9%e8%e7%e6%e5%e4%e3%e2%e1%e0%df%de%dd%dc%db%da%d9%d8%d7%d6%d5%d4%d3%d2%d1%d0%cf%ce%cd%cc%cb%ca%c9%c8%c7%c6%c5%c4%c3%c2%c1%c0%bf%be%bd%bc%bb%ba%b9%b8%b7%b6%b5%b4%b3%b2%b1%b0%af%ae%ad%ac%ab%aa%a9%a8%a7%a6%a5%a4%a3%a2%a1%a0%9f%9e%9d%9c%9b%9a%99%98%97%96%95%94%93%92%91%90%8f%8e%8d%8c%8b%8a%89%88%87%86%85%84%83%82%81%80%7f~%7d%7c%7bzyxwvutsrqponmlkjihgfedcba%60_%5e%5d%5c%5bZYXWVUTSRQPONMLKJIHGFEDCBA%40%3f%3e%3d%3c%3b%3a9876543210%2f.-%2c%2b%2a%29%28%27%26%25%24%23%22%21%20%1f%1e%1d%1c%1b%1a%19%18%17%16%15%14%13%12%11%10%0f%0e%0d%0c%0b%0a%09%08%07%06%05%04%03%02%01%00";
+
+	  set resp.http.urluc2id
+	    = blob.transcode_n(27, URL, IDENTITY, req.http.urlhobbesuc);
+
+	  set resp.http.urllc2id
+	    = blob.transcode_n(63, URL, IDENTITY, req.http.urlhobbeslc);
+
+	  set resp.http.urlalldownuc2b64
+	    = blob.transcode_n(18, URL, BASE64, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownuc2b64url
+	    = blob.transcode_n(15, URL, BASE64URL, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownuc2b64nopad
+	    = blob.transcode_n(15, URL, BASE64URLNOPAD,
+				   req.http.urlalldownuc);
+
+	  set resp.http.urlalldownlc2b64
+	    = blob.transcode_n(18, URL, BASE64, req.http.urlalldownlc);
+
+	  set resp.http.urlalldownlc2b64url
+	    = blob.transcode_n(15, URL, BASE64URL, req.http.urlalldownlc);
+
+	  set resp.http.urlalldownlc2b64nopad
+	    = blob.transcode_n(15, URL, BASE64URLNOPAD,
+				   req.http.urlalldownlc);
+
+	  set resp.http.urlallupuc2b64
+	    = blob.transcode_n(36, URL, BASE64, req.http.urlallupuc);
+
+	  set resp.http.urlallupuc2b64url
+	    = blob.transcode_n(33, URL, BASE64URL, req.http.urlallupuc);
+
+	  set resp.http.urlallupuc2b64nopad
+	    = blob.transcode_n(33, URL, BASE64URLNOPAD,
+				   req.http.urlallupuc);
+
+	  set resp.http.urlalluplc2b64
+	    = blob.transcode_n(36, URL, BASE64, req.http.urlalluplc);
+
+	  set resp.http.urlalluplc2b64url
+	    = blob.transcode_n(33, URL, BASE64URL, req.http.urlalluplc);
+
+	  set resp.http.urlalluplc2b64nopad
+	    = blob.transcode_n(33, URL, BASE64URLNOPAD,
+				   req.http.urlalluplc);
+
+	  set resp.http.urlalldownuc2urluc
+	    = blob.transcode_n(423, URL, URLUC, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownuc2urllc
+	    = blob.transcode_n(423, URL, URLLC, req.http.urlalldownuc);
+
+	  set resp.http.urlalldownlc2urluc
+	    = blob.transcode_n(423, URL, URLUC, req.http.urlalldownlc);
+
+	  set resp.http.urlalldownlc2urllc
+	    = blob.transcode_n(423, URL, URLLC, req.http.urlalldownlc);
+
+	  set resp.http.urlallupuc2urluc
+	    = blob.transcode_n(197, URL, URLUC, req.http.urlallupuc);
+
+	  set resp.http.urlallupuc2urllc
+	    = blob.transcode_n(197, URL, URLLC, req.http.urlallupuc);
+
+	  set resp.http.urlalluplc2urluc
+	    = blob.transcode_n(197, URL, URLUC, req.http.urlalluplc);
+
+	  set resp.http.urlalluplc2urllc
+	    = blob.transcode_n(197, URL, URLLC, req.http.urlalluplc);
+
+	  }
+}
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.http.urluc2id == "Man is distinguished,"
+	expect resp.http.urllc2id == "Man is distinguished, not only by his reason,"
+	expect resp.http.urlalldownuc2b64 == "//79/Pv6"
+	expect resp.http.urlalldownuc2b64url == "__79_Ps="
+	expect resp.http.urlalldownuc2b64nopad == "__79_Ps"
+	expect resp.http.urlalldownlc2b64 == "//79/Pv6"
+	expect resp.http.urlalldownlc2b64url == "__79_Ps="
+	expect resp.http.urlalldownlc2b64nopad == "__79_Ps"
+	expect resp.http.urlallupuc2b64 == "AAECAwQFBgcICQoL"
+	expect resp.http.urlallupuc2b64url == "AAECAwQFBgcICQo="
+	expect resp.http.urlallupuc2b64nopad == "AAECAwQFBgcICQo"
+	expect resp.http.urlalluplc2b64 == "AAECAwQFBgcICQoL"
+	expect resp.http.urlalluplc2b64url == "AAECAwQFBgcICQo="
+	expect resp.http.urlalluplc2b64nopad == "AAECAwQFBgcICQo"
+	expect resp.http.urlalldownuc2urluc == "%FF%FE%FD%FC%FB%FA%F9%F8%F7%F6%F5%F4%F3%F2%F1%F0%EF%EE%ED%EC%EB%EA%E9%E8%E7%E6%E5%E4%E3%E2%E1%E0%DF%DE%DD%DC%DB%DA%D9%D8%D7%D6%D5%D4%D3%D2%D1%D0%CF%CE%CD%CC%CB%CA%C9%C8%C7%C6%C5%C4%C3%C2%C1%C0%BF%BE%BD%BC%BB%BA%B9%B8%B7%B6%B5%B4%B3%B2%B1%B0%AF%AE%AD%AC%AB%AA%A9%A8%A7%A6%A5%A4%A3%A2%A1%A0%9F%9E%9D%9C%9B%9A%99%98%97%96%95%94%93%92%91%90%8F%8E%8D%8C%8B%8A%89%88%87%86%85%84%83%82%81%80%7F~%7D%7C%7Bzyxwvutsrqponmlkjihgfedcba"
+	expect resp.http.urlalldownuc2urllc == "%ff%fe%fd%fc%fb%fa%f9%f8%f7%f6%f5%f4%f3%f2%f1%f0%ef%ee%ed%ec%eb%ea%e9%e8%e7%e6%e5%e4%e3%e2%e1%e0%df%de%dd%dc%db%da%d9%d8%d7%d6%d5%d4%d3%d2%d1%d0%cf%ce%cd%cc%cb%ca%c9%c8%c7%c6%c5%c4%c3%c2%c1%c0%bf%be%bd%bc%bb%ba%b9%b8%b7%b6%b5%b4%b3%b2%b1%b0%af%ae%ad%ac%ab%aa%a9%a8%a7%a6%a5%a4%a3%a2%a1%a0%9f%9e%9d%9c%9b%9a%99%98%97%96%95%94%93%92%91%90%8f%8e%8d%8c%8b%8a%89%88%87%86%85%84%83%82%81%80%7f~%7d%7c%7bzyxwvutsrqponmlkjihgfedcba"
+	expect resp.http.urlalldownlc2urluc == "%FF%FE%FD%FC%FB%FA%F9%F8%F7%F6%F5%F4%F3%F2%F1%F0%EF%EE%ED%EC%EB%EA%E9%E8%E7%E6%E5%E4%E3%E2%E1%E0%DF%DE%DD%DC%DB%DA%D9%D8%D7%D6%D5%D4%D3%D2%D1%D0%CF%CE%CD%CC%CB%CA%C9%C8%C7%C6%C5%C4%C3%C2%C1%C0%BF%BE%BD%BC%BB%BA%B9%B8%B7%B6%B5%B4%B3%B2%B1%B0%AF%AE%AD%AC%AB%AA%A9%A8%A7%A6%A5%A4%A3%A2%A1%A0%9F%9E%9D%9C%9B%9A%99%98%97%96%95%94%93%92%91%90%8F%8E%8D%8C%8B%8A%89%88%87%86%85%84%83%82%81%80%7F~%7D%7C%7Bzyxwvutsrqponmlkjihgfedcba"
+	expect resp.http.urlalldownlc2urllc == "%ff%fe%fd%fc%fb%fa%f9%f8%f7%f6%f5%f4%f3%f2%f1%f0%ef%ee%ed%ec%eb%ea%e9%e8%e7%e6%e5%e4%e3%e2%e1%e0%df%de%dd%dc%db%da%d9%d8%d7%d6%d5%d4%d3%d2%d1%d0%cf%ce%cd%cc%cb%ca%c9%c8%c7%c6%c5%c4%c3%c2%c1%c0%bf%be%bd%bc%bb%ba%b9%b8%b7%b6%b5%b4%b3%b2%b1%b0%af%ae%ad%ac%ab%aa%a9%a8%a7%a6%a5%a4%a3%a2%a1%a0%9f%9e%9d%9c%9b%9a%99%98%97%96%95%94%93%92%91%90%8f%8e%8d%8c%8b%8a%89%88%87%86%85%84%83%82%81%80%7f~%7d%7c%7bzyxwvutsrqponmlkjihgfedcba"
+	expect resp.http.urlallupuc2urluc == "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	expect resp.http.urlalluplc2urluc == "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	expect resp.http.urlallupuc2urllc == "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	expect resp.http.urlalluplc2urllc == "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+} -run
+
+# Decode failures
+
+server s1 -repeat 11 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_deliver {
+	        set req.http.foo = "AAA=";
+                if (req.url == "/1") {
+		        set resp.http.good
+		            = blob.transcode_n(3, URL, IDENTITY, "%2c%q");
+                }
+                elsif (req.url == "/2") {
+	                set resp.http.good
+                            = blob.transcode_n(3, URL, IDENTITY, "%3a%2q");
+                }
+                elsif (req.url == "/3") {
+	                set resp.http.bad
+	                    = blob.transcode_n(8, HEX, IDENTITY,
+                                                   "0x123456");
+                }
+                elsif (req.url == "/4") {
+	                set resp.http.bad
+                            = blob.transcode_n(4, BASE64, IDENTITY,
+                                                   "-_-_" + req.http.foo);
+                }
+                elsif (req.url == "/5") {
+	                set resp.http.bad
+                            = blob.transcode_n(4, BASE64URL, IDENTITY,
+			                           "+/+/" + req.http.foo);
+                }
+                elsif (req.url == "/6") {
+	                set resp.http.bad
+	                    = blob.transcode_n(8, BASE64URLNOPAD, IDENTITY,
+	                                           "TWFu" + req.http.foo);
+                }
+                elsif (req.url == "/7") {
+	                set resp.http.bad
+                            = blob.transcode_n(4, BASE64, BASE64,
+			                           "_-_-" + req.http.foo);
+                }
+                elsif (req.url == "/8") {
+	                set resp.http.bad
+	                    = blob.transcode_n(4, BASE64URL, BASE64URL,
+			                           "/+/+" + req.http.foo);
+                }
+                elsif (req.url == "/9") {
+	                set resp.http.bad
+	                    = blob.transcode_n(8, BASE64URLNOPAD,
+                                                   BASE64URLNOPAD,
+				                   "Zm9v" + req.http.foo);
+                }
+                elsif (req.url == "/10") {
+	                set resp.http.bad
+                            = blob.transcode_n(1, URL, IDENTITY, "%20");
+                }
+                elsif (req.url == "/11") {
+	                set resp.http.bad
+                            = blob.transcode_n(2, URL, IDENTITY, "%20");
+                }
+	}
+}
+
+client c1 {
+	txreq -url /1
+	rxresp
+	expect resp.status == 200
+	expect resp.http.good == ","
+	txreq -url /2
+	rxresp
+	expect resp.status == 200
+	expect resp.http.good == ":"
+	txreq -url /3
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /4
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /5
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /6
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /7
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /8
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /9
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /10
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+client c1 {
+	txreq -url /11
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+	expect resp.http.bad == <undef>
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "0x123456"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "-_-_"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "\+/\+/"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "TWFu"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "_-_-"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "/\+/\+"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "Zm9v"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "%20"$}
+	expect * * VCL_Error {^vmod blob error: cannot decode, illegal encoding beginning with "%20"$}
+} -run
diff --git a/bin/varnishtest/tests/m00043.vtc b/bin/varnishtest/tests/m00043.vtc
new file mode 100644
index 0000000..c12ac14
--- /dev/null
+++ b/bin/varnishtest/tests/m00043.vtc
@@ -0,0 +1,334 @@
+varnishtest "VMOD blob blob object interface"
+
+server s1 {} -start
+
+varnish v1 -arg "-i serverid" -vcl+backend {
+	import blob;
+
+	sub vcl_init {
+	  new id = blob.blob(IDENTITY,
+				 "The quick brown fox jumps over the lazy dog");
+	  new idpieces
+	    = blob.blob(IDENTITY, "" + server.identity + " "
+			              + server.identity + "");
+	  new idempty = blob.blob(IDENTITY, "");
+	  new idparam
+	    = blob.blob(encoded="" + server.identity + " " + server.identity
+			    + "", decoding=IDENTITY);
+
+	  new hexuc = blob.blob(HEX, "666F6F206261722062617A2071757578");
+	  new hexlc = blob.blob(HEX, "666f6f206261722062617a2071757578");
+	  new hexmix = blob.blob(HEX, "666F6F206261722062617a2071757578");
+
+	  new b64 = blob.blob(BASE64, "L0hlbGxvIHdvcmxkLw==");
+
+	  new b64nopad = blob.blob(BASE64URLNOPAD, "L0hlbGxvIHdvcmxkLw");
+
+	  new hexall = blob.blob(HEX, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF");
+	  new b64all = blob.blob(BASE64, "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==");
+	}
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	  set resp.http.id = blob.encode(IDENTITY, id.get());
+	  set resp.http.idpieces = blob.encode(IDENTITY, idpieces.get());
+	  set resp.http.idparam = blob.encode(IDENTITY, idparam.get());
+	  set resp.http.idempty = blob.encode(IDENTITY, idempty.get());
+	  set resp.http.hexuc = blob.encode(IDENTITY, hexuc.get());
+	  set resp.http.hexlc = blob.encode(IDENTITY, hexlc.get());
+	  set resp.http.hexmix = blob.encode(IDENTITY, hexmix.get());
+	  set resp.http.b64 = blob.encode(IDENTITY, b64.get());
+	  set resp.http.b64nopad = blob.encode(IDENTITY, b64nopad.get());
+
+	  set resp.http.allhex2b64func = blob.encode(BASE64,
+							 hexall.get());
+	  set resp.http.allb642hexlcfunc = blob.encode(HEXLC,
+							   b64all.get());
+	  set resp.http.allhex2b64meth = hexall.encode(BASE64);
+	  set resp.http.allb642hexlcmeth = b64all.encode(HEXLC);
+
+	  set resp.http.id2id = id.encode(IDENTITY);
+	  set resp.http.id2b64 = id.encode(BASE64);
+	  set resp.http.id2b64url = id.encode(BASE64URL);
+	  set resp.http.id2b64urlnopad = id.encode(BASE64URLNOPAD);
+	  set resp.http.id2hexuc = id.encode(HEXUC);
+	  set resp.http.id2hexlc = id.encode(HEXLC);
+
+	  set resp.http.emptyid = idempty.encode(IDENTITY);
+	  set resp.http.emptyb64 = idempty.encode(BASE64);
+	  set resp.http.emptyb64url = idempty.encode(BASE64URL);
+	  set resp.http.emptyb64urlnopad = idempty.encode(BASE64URLNOPAD);
+	  set resp.http.emptyhexuc = idempty.encode(HEXUC);
+	  set resp.http.emptyhexlc = idempty.encode(HEXLC);
+
+	  set resp.http.hexuc2id = hexuc.encode(IDENTITY);
+	  set resp.http.hexuc2b64 = hexuc.encode(BASE64);
+	  set resp.http.hexuc2b64url = hexuc.encode(BASE64URL);
+	  set resp.http.hexuc2b64nopad = hexuc.encode(BASE64URLNOPAD);
+	  set resp.http.hexuc2hexuc = hexuc.encode(HEXUC);
+	  set resp.http.hexuc2hexlc = hexuc.encode(HEXLC);
+
+	  set resp.http.hexlc2id = hexlc.encode(IDENTITY);
+	  set resp.http.hexlc2b64 = hexlc.encode(BASE64);
+	  set resp.http.hexlc2b64url = hexlc.encode(BASE64URL);
+	  set resp.http.hexlc2b64nopad = hexlc.encode(BASE64URLNOPAD);
+	  set resp.http.hexlc2hexuc = hexlc.encode(HEXUC);
+	  set resp.http.hexlc2hexlc = hexlc.encode(HEXLC);
+	}
+} -start
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.http.id == "The quick brown fox jumps over the lazy dog"
+	expect resp.http.idpieces == "serverid serverid"
+	expect resp.http.idparam == "serverid serverid"
+	expect resp.http.idempty == ""
+	expect resp.http.hexuc == "foo bar baz quux"
+	expect resp.http.hexlc == "foo bar baz quux"
+	expect resp.http.hexmix == "foo bar baz quux"
+	expect resp.http.b64 == "/Hello world/"
+	expect resp.http.b64nopad == "/Hello world/"
+
+	expect resp.http.allhex2b64func == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.allb642hexlcfunc == "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	expect resp.http.allhex2b64meth == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
+	expect resp.http.allb642hexlcmeth == "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+
+	expect resp.http.id2id == "The quick brown fox jumps over the lazy dog"
+	expect resp.http.id2b64 == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
+	expect resp.http.id2b64url == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="
+	expect resp.http.id2b64urlnopad == "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw"
+	expect resp.http.id2hexuc == "54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F67"
+	expect resp.http.id2hexlc == "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"
+
+	expect resp.http.emptyid == ""
+	expect resp.http.emptyb64 == ""
+	expect resp.http.emptyb64url == ""
+	expect resp.http.emptyb64urlnopad == ""
+	expect resp.http.emptyhexuc == ""
+	expect resp.http.emptyhexlc == ""
+
+	expect resp.http.hexuc2id == "foo bar baz quux"
+	expect resp.http.hexuc2b64 == "Zm9vIGJhciBiYXogcXV1eA=="
+	expect resp.http.hexuc2b64url == "Zm9vIGJhciBiYXogcXV1eA=="
+	expect resp.http.hexuc2b64nopad == "Zm9vIGJhciBiYXogcXV1eA"
+	expect resp.http.hexuc2hexuc == "666F6F206261722062617A2071757578"
+	expect resp.http.hexuc2hexlc == "666f6f206261722062617a2071757578"
+
+	expect resp.http.hexlc2id == "foo bar baz quux"
+	expect resp.http.hexlc2b64 == "Zm9vIGJhciBiYXogcXV1eA=="
+	expect resp.http.hexlc2b64url == "Zm9vIGJhciBiYXogcXV1eA=="
+	expect resp.http.hexlc2b64nopad == "Zm9vIGJhciBiYXogcXV1eA"
+	expect resp.http.hexlc2hexuc == "666F6F206261722062617A2071757578"
+	expect resp.http.hexlc2hexlc == "666f6f206261722062617a2071757578"
+} -run
+
+# run twice to test retrieving cached encodings
+client c1 -run
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_init {
+	  new b64 = blob.blob(BASE64, "L0hlbGxvIHdvcmxkLw==");
+	  new b64url = blob.blob(BASE64URL, "L0hlbGxvIHdvcmxkLw==");
+	  new b64nopad = blob.blob(BASE64URLNOPAD, "L0hlbGxvIHdvcmxkLw");
+	}
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	  set resp.http.b642id = b64.encode(IDENTITY);
+	  set resp.http.b642b64 = b64.encode(BASE64);
+	  set resp.http.b642b64url = b64.encode(BASE64URL);
+	  set resp.http.b642b64nopad = b64.encode(BASE64URLNOPAD);
+	  set resp.http.b642hexuc = b64.encode(HEXUC);
+	  set resp.http.b642hexlc = b64.encode(HEXLC);
+	  set resp.http.b642urluc = b64.encode(URLUC);
+	  set resp.http.b642urllc = b64.encode(URLLC);
+
+	  set resp.http.b64url2id = b64url.encode(IDENTITY);
+	  set resp.http.b64url2b64 = b64url.encode(BASE64);
+	  set resp.http.b64url2b64url = b64url.encode(BASE64URL);
+	  set resp.http.b64url2b64nopad = b64url.encode(BASE64URLNOPAD);
+	  set resp.http.b64url2hexuc = b64url.encode(HEXUC);
+	  set resp.http.b64url2hexlc = b64url.encode(HEXLC);
+	  set resp.http.b64url2urluc = b64url.encode(URLUC);
+	  set resp.http.b64url2urllc = b64url.encode(URLLC);
+
+	  set resp.http.b64nopad2id = b64nopad.encode(IDENTITY);
+	  set resp.http.b64nopad2b64 = b64nopad.encode(BASE64);
+	  set resp.http.b64nopad2b64url = b64nopad.encode(BASE64URL);
+	  set resp.http.b64nopad2b64nopad = b64nopad.encode(BASE64URLNOPAD);
+	  set resp.http.b64nopad2hexuc = b64nopad.encode(HEXUC);
+	  set resp.http.b64nopad2hexlc = b64nopad.encode(HEXLC);
+	  set resp.http.b64nopad2urluc = b64nopad.encode(URLUC);
+	  set resp.http.b64nopad2urllc = b64nopad.encode(URLLC);
+	}
+}
+
+client c2 {
+	txreq
+	rxresp
+	expect resp.http.b642id == "/Hello world/"
+	expect resp.http.b642b64 == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b642b64url == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b642b64nopad == "L0hlbGxvIHdvcmxkLw"
+	expect resp.http.b642hexuc == "2F48656C6C6F20776F726C642F"
+	expect resp.http.b642hexlc == "2f48656c6c6f20776f726c642f"
+	expect resp.http.b642urluc == "%2FHello%20world%2F"
+	expect resp.http.b642urllc == "%2fHello%20world%2f"
+
+	expect resp.http.b64url2id == "/Hello world/"
+	expect resp.http.b64url2b64 == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b64url2b64url == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b64url2b64nopad == "L0hlbGxvIHdvcmxkLw"
+	expect resp.http.b64url2hexuc == "2F48656C6C6F20776F726C642F"
+	expect resp.http.b64url2hexlc == "2f48656c6c6f20776f726c642f"
+	expect resp.http.b64url2urluc == "%2FHello%20world%2F"
+	expect resp.http.b64url2urllc == "%2fHello%20world%2f"
+
+	expect resp.http.b64nopad2id == "/Hello world/"
+	expect resp.http.b64nopad2b64 == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b64nopad2b64url == "L0hlbGxvIHdvcmxkLw=="
+	expect resp.http.b64nopad2b64nopad == "L0hlbGxvIHdvcmxkLw"
+	expect resp.http.b64nopad2hexuc == "2F48656C6C6F20776F726C642F"
+	expect resp.http.b64nopad2hexlc == "2f48656c6c6f20776f726c642f"
+	expect resp.http.b64nopad2urluc == "%2FHello%20world%2F"
+	expect resp.http.b64nopad2urllc == "%2fHello%20world%2f"
+} -run
+
+# run twice
+client c2 -run
+
+varnish v1 -vcl+backend {
+	import blob;
+
+	sub vcl_init {
+	  new id = blob.blob(IDENTITY,
+				 "The quick brown fox jumps over the lazy dog");
+	  new url = blob.blob(URL, "foo%3Abar%3abaz%3Aquux");
+	  new idempty = blob.blob(IDENTITY, "");
+	  new hexuc = blob.blob(HEX, "666F6F206261722062617A2071757578");
+	  new hexlc = blob.blob(HEX, "666f6f206261722062617a2071757578");
+	}
+
+	sub vcl_recv {
+	  return(synth(200));
+	}
+
+	sub vcl_synth {
+	  set resp.http.id2urluc = id.encode(URLUC);
+	  set resp.http.id2urllc = id.encode(URLLC);
+	  set resp.http.emptyurluc = idempty.encode(URLUC);
+	  set resp.http.emptyurllc = idempty.encode(URLLC);
+	  set resp.http.url = blob.encode(IDENTITY, url.get());
+	  set resp.http.hexuc2urluc = hexuc.encode(URLUC);
+	  set resp.http.hexuc2urllc = hexuc.encode(URLLC);
+	  set resp.http.hexlc2urluc = hexlc.encode(URLUC);
+	  set resp.http.hexlc2urllc = hexlc.encode(URLLC);
+	  set resp.http.url2id = url.encode(IDENTITY);
+	  set resp.http.url2b64 = url.encode(BASE64);
+	  set resp.http.url2b64url = url.encode(BASE64URL);
+	  set resp.http.url2b64nopad = url.encode(BASE64URLNOPAD);
+	  set resp.http.url2hexuc = url.encode(HEXUC);
+	  set resp.http.url2hexlc = url.encode(HEXLC);
+	  set resp.http.url2urluc = url.encode(URLUC);
+	  set resp.http.url2urllc = url.encode(URLLC);
+	}
+}
+
+client c3 {
+	txreq
+	rxresp
+	expect resp.http.id2urluc == "The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog"
+	expect resp.http.id2urllc == "The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog"
+	expect resp.http.emptyurluc == ""
+	expect resp.http.emptyurllc == ""
+	expect resp.http.hexuc2urluc == "foo%20bar%20baz%20quux"
+	expect resp.http.hexuc2urllc == "foo%20bar%20baz%20quux"
+	expect resp.http.hexlc2urluc == "foo%20bar%20baz%20quux"
+	expect resp.http.hexlc2urllc == "foo%20bar%20baz%20quux"
+	expect resp.http.url == "foo:bar:baz:quux"
+	expect resp.http.url2id == "foo:bar:baz:quux"
+	expect resp.http.url2b64 == "Zm9vOmJhcjpiYXo6cXV1eA=="
+	expect resp.http.url2b64url == "Zm9vOmJhcjpiYXo6cXV1eA=="
+	expect resp.http.url2b64nopad == "Zm9vOmJhcjpiYXo6cXV1eA"
+	expect resp.http.url2hexuc == "666F6F3A6261723A62617A3A71757578"
+	expect resp.http.url2hexlc == "666f6f3a6261723a62617a3a71757578"
+	expect resp.http.url2urluc == "foo%3Abar%3Abaz%3Aquux"
+	expect resp.http.url2urllc == "foo%3abar%3abaz%3aquux"
+} -run
+
+# run twice
+client c3 -run
+
+varnish v1 -errvcl {vmod blob error: cannot create blob err, illegal encoding beginning with "g"} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new err = blob.blob(HEX, "g");
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: cannot create blob bad64, illegal encoding beginning with "-_-_"} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new bad64 = blob.blob(BASE64, "-_-_");
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: cannot create blob badurl, illegal encoding beginning with "+/+/"} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new badurl = blob.blob(BASE64URL, "+/+/");
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: cannot create blob badpad, illegal encoding beginning with "YWI="} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new badpad = blob.blob(BASE64URLNOPAD, "YWI=");
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: cannot create blob badurl, illegal encoding beginning with "%"} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new badurl = blob.blob(URL, "%");
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: cannot create blob badurl, illegal encoding beginning with "%2"} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new badurl = blob.blob(URL, "%2");
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: cannot create blob badurl, illegal encoding beginning with "%q"} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new badurl = blob.blob(URL, "%q");
+	}
+}
+
+varnish v1 -errvcl {vmod blob error: cannot create blob badurl, illegal encoding beginning with "%2q"} {
+	import blob;
+	backend b { .host="${bad_ip}"; }
+	sub vcl_init {
+	  new badurl = blob.blob(URL, "%2q");
+	}
+}
diff --git a/bin/varnishtest/tests/m00044.vtc b/bin/varnishtest/tests/m00044.vtc
new file mode 100644
index 0000000..20b6b51
--- /dev/null
+++ b/bin/varnishtest/tests/m00044.vtc
@@ -0,0 +1,164 @@
+varnishtest "VMOD blob test all examples from the vcc/rst"
+
+server s1 {
+    rxreq
+    txresp
+} -start
+
+varnish v1 -arg "-p http_max_hdr=128" -vcl+backend {
+    import blob;
+    import std;
+
+    ############################################################
+    # top Usage section
+    sub vcl_init {
+	# Create blob objects from encodings such as base64 or hex.
+	new myblob   = blob.blob(BASE64, "Zm9vYmFy");
+	new yourblob = blob.blob(encoded="666F6F", decoding=HEX);
+    }
+
+    sub vcl_deliver {
+	# The .get() method retrieves the BLOB from an object.
+	set resp.http.MyBlob-As-Hex
+	    = blob.encode(blob=myblob.get(), encoding=HEXLC);
+
+	# The .encode() method efficiently retrieves an encoding.
+	set resp.http.YourBlob-As-Base64 = yourblob.encode(BASE64);
+
+	# decode() and encode() functions convert blobs to text and
+	# vice versa at runtime.
+	set resp.http.Base64-Encoded
+	    = blob.encode(BASE64,
+			      blob.decode(HEX, req.http.Hex-Encoded));
+    }
+
+    sub vcl_recv {
+	# transcode() converts from one encoding to another.
+	set req.http.Hex-Encoded
+	    = blob.transcode(decoding=BASE64, encoding=HEXUC, encoded="YmF6");
+
+	# transcode() can replace other specific encoding/deconding
+	# vmods - e.g. vmod_urlcode
+	set req.url = blob.transcode(encoded=req.url, decoding=URL);
+	set req.http.url_urlcoded
+	    = blob.transcode(encoded=req.url, encoding=URLLC);
+    }
+    # get output from recv
+    sub vcl_deliver {
+	set resp.http.url_urlcoded = req.http.url_urlcoded;
+    }
+
+    ############################################################
+    # IDENTITY
+    sub vcl_deliver {
+	set resp.http.Trunced-Foo1
+	    = blob.encode(IDENTITY, blob.decode(HEX, "666f6f00626172"));
+	set resp.http.Trunced-Foo2
+	    = blob.encode(blob=blob.decode(HEX, "666f6f00626172"));
+    }
+
+    ############################################################
+    # HEX
+    sub vcl_deliver {
+	set resp.http.First = "abc";
+	set resp.http.Second = "def0";
+	set resp.http.Hex-Decoded = blob.encode(
+	    HEXLC,
+	    blob.decode(HEX, resp.http.First + resp.http.Second));
+    }
+
+    ############################################################
+    # encode - also contains decode examples
+    sub vcl_deliver {
+	set resp.http.encode1
+	    = blob.encode(HEXLC, blob.decode(BASE64, "Zm9vYmFyYmF6"));
+
+	# same with named parameters
+	set resp.http.encode2
+	    = blob.encode(blob=blob.decode(encoded="Zm9vYmFyYmF6",
+						   decoding=BASE64),
+			      encoding=HEXLC);
+
+	# convert blob to string
+	set resp.http.encode3
+	    = blob.encode(blob=blob.decode(encoded="foo"));
+    }
+
+    ############################################################
+    # transcode
+    sub vcl_deliver {
+	set resp.http.Hex2Base64-1 = blob.transcode(HEX, BASE64, "666f6f");
+
+	# same with named parameters
+	set resp.http.Hex2Base64-2
+	    = blob.transcode(encoded="666f6f",
+				 encoding=BASE64, decoding=HEX);
+
+	# replacement for urlcode.decode("foo%20bar")
+	set resp.http.urldecoded
+	    = blob.transcode(encoded="foo%20bar", decoding=URL);
+
+	# replacement for urlcode.encode("foo bar")
+	set resp.http.urlencoded
+	    = blob.transcode(encoded="foo bar", encoding=URLLC);
+    }
+
+    ############################################################
+    # blob init + .get + .encode
+    sub vcl_init {
+	new theblob1 = blob.blob(BASE64, "YmxvYg==");
+
+	# same with named arguments
+	new theblob2 = blob.blob(encoded="YmxvYg==", decoding=BASE64);
+
+	# string as a blob
+	new stringblob = blob.blob(encoded="bazz");
+    }
+    sub vcl_deliver {
+	# .get
+	set resp.http.The-Blob1 =
+	    blob.encode(blob=theblob1.get());
+
+	set resp.http.The-Blob2 =
+	    blob.encode(blob=theblob2.get());
+
+	set resp.http.The-Stringblob =
+	    blob.encode(blob=stringblob.get());
+
+	# .encode
+
+	# blob as text
+	set resp.http.The-Blob = theblob1.encode();
+
+	# blob as base64
+	set resp.http.The-Blob-b64 = theblob1.encode(BASE64);
+    }
+
+} -start
+
+client c1 {
+	txreq -url "/foo%20bar"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.MyBlob-As-Hex == "666f6f626172"
+	expect resp.http.YourBlob-As-Base64 == "Zm9v"
+	expect resp.http.Base64-Encoded == "YmF6"
+	expect resp.http.url_urlcoded == "%2ffoo%20bar"
+	expect resp.http.Trunced-Foo1 == "foo"
+	expect resp.http.Trunced-Foo2 == "foo"
+	expect resp.http.First == "abc"
+	expect resp.http.Second == "def0"
+	expect resp.http.Hex-Decoded == "0abcdef0"
+	expect resp.http.encode1 == "666f6f62617262617a"
+	expect resp.http.encode2 == "666f6f62617262617a"
+	expect resp.http.encode3 == "foo"
+	expect resp.http.Hex2Base64-1 == "Zm9v"
+	expect resp.http.Hex2Base64-2 == "Zm9v"
+	expect resp.http.urldecoded == "foo bar"
+	expect resp.http.urlencoded == "foo%20bar"
+	expect resp.http.The-Blob1 == "blob"
+	expect resp.http.The-Blob2 == "blob"
+	expect resp.http.The-Stringblob == "bazz"
+	expect resp.http.The-Blob == "blob"
+	expect resp.http.The-Blob-b64 == "YmxvYg=="
+} -run
diff --git a/bin/varnishtest/vmods.h b/bin/varnishtest/vmods.h
index a994bef..e732468 100644
--- a/bin/varnishtest/vmods.h
+++ b/bin/varnishtest/vmods.h
@@ -32,3 +32,4 @@ VTC_VMOD(debug)
 VTC_VMOD(directors)
 VTC_VMOD(purge)
 VTC_VMOD(vtc)
+VTC_VMOD(blob)
diff --git a/configure.ac b/configure.ac
index 2743139..c3a0984 100644
--- a/configure.ac
+++ b/configure.ac
@@ -734,6 +734,7 @@ AC_CONFIG_FILES([
     lib/libvmod_directors/Makefile
     lib/libvmod_purge/Makefile
     lib/libvmod_vtc/Makefile
+    lib/libvmod_blob/Makefile
     man/Makefile
     varnishapi.pc
     varnishapi-uninstalled.pc
diff --git a/doc/sphinx/Makefile.am b/doc/sphinx/Makefile.am
index 0de2f28..2d68243 100644
--- a/doc/sphinx/Makefile.am
+++ b/doc/sphinx/Makefile.am
@@ -213,6 +213,10 @@ reference/vmod_vtc.generated.rst: reference $(top_builddir)/lib/libvmod_vtc/vmod
 	cp $(top_builddir)/lib/libvmod_vtc/vmod_vtc.rst $@ || true
 BUILT_SOURCES += reference/vmod_vtc.generated.rst
 
+reference/vmod_blob.generated.rst: reference $(top_builddir)/lib/libvmod_blob/vmod_blob.rst
+	cp $(top_builddir)/lib/libvmod_blob/vmod_blob.rst $@ || true
+BUILT_SOURCES += reference/vmod_blob.generated.rst
+
 EXTRA_DIST += $(BUILT_SOURCES)
 MAINTAINERCLEANFILES = $(EXTRA_DIST)
 
diff --git a/doc/sphinx/reference/index.rst b/doc/sphinx/reference/index.rst
index e1164f8..39ccb81 100644
--- a/doc/sphinx/reference/index.rst
+++ b/doc/sphinx/reference/index.rst
@@ -24,6 +24,7 @@ The Varnish Reference Manual
 	vmod_directors.generated.rst
 	vmod_vtc.generated.rst
 	vmod_purge.generated.rst
+	vmod_blob.generated.rst
 	directors.rst
 	varnish-counters.rst
 	vsl.rst
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 8981c65..4afd3d0 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -9,4 +9,5 @@ SUBDIRS = \
 	libvmod_std \
 	libvmod_directors \
 	libvmod_purge \
-	libvmod_vtc
+	libvmod_vtc \
+	libvmod_blob
diff --git a/lib/libvmod_blob/Makefile.am b/lib/libvmod_blob/Makefile.am
new file mode 100644
index 0000000..b0de15f
--- /dev/null
+++ b/lib/libvmod_blob/Makefile.am
@@ -0,0 +1,53 @@
+#
+AM_LDFLAGS  = $(AM_LT_LDFLAGS)
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir)/bin/varnishd \
+	-I$(top_builddir)/include
+
+vmoddir = $(pkglibdir)/vmods
+vmod_srcdir = $(top_srcdir)/lib/libvmod_blob
+vmodtool = $(top_srcdir)/lib/libvcc/vmodtool.py
+vmodtoolargs = --strict
+vmod_LTLIBRARIES = libvmod_blob.la
+
+libvmod_blob_la_CFLAGS = \
+	@SAN_CFLAGS@
+
+libvmod_blob_la_LDFLAGS = $(AM_LDFLAGS) -module -export-dynamic -avoid-version -shared \
+	@SAN_LDFLAGS@
+
+libvmod_blob_la_SOURCES = \
+	vmod_blob.c \
+	vmod_blob.h \
+	id.c \
+	base64.h \
+	base64.c \
+	hex.h \
+	hex.c \
+	url.c \
+	wb.h \
+	wb.c \
+	parse_encoding.h \
+	parse_encoding.c
+
+nodist_libvmod_blob_la_SOURCES = \
+	vcc_if.c \
+	vcc_if.h
+
+# BUILT_SOURCES is only a hack and dependency tracking does not help for the first build
+$(libvmod_blob_la_OBJECTS):vcc_if.h
+
+vcc_if.h vmod_blob.rst vmod_blob.man.rst: vcc_if.c
+
+vcc_if.c: $(vmodtool) $(vmod_srcdir)/vmod.vcc
+	@PYTHON@ $(vmodtool) $(vmodtoolargs) $(vmod_srcdir)/vmod.vcc
+
+base64.o: base64.c base64.h
+
+EXTRA_DIST = vmod.vcc
+
+CLEANFILES = $(builddir)/vcc_if.c $(builddir)/vcc_if.h \
+	$(builddir)/vmod_blob.rst \
+	$(builddir)/vmod_blob.man.rst
diff --git a/lib/libvmod_blob/base64.c b/lib/libvmod_blob/base64.c
new file mode 100644
index 0000000..d06e323
--- /dev/null
+++ b/lib/libvmod_blob/base64.c
@@ -0,0 +1,192 @@
+/*-
+ * Copyright 2015-2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include "config.h"
+#include <errno.h>
+#include <stdint.h>
+
+#include "base64.h"
+
+#include "vdef.h"
+#include "vrt.h"
+#include "vas.h"
+
+#define base64_l(l)		(((l) << 2) / 3)
+
+size_t
+base64nopad_encode_l(size_t l)
+{
+	return base64_l(l) + 4;
+}
+
+size_t
+base64_encode_l(size_t l)
+{
+	return (((base64_l(l)) + 3) & ~3) + 1;
+}
+
+size_t
+base64_decode_l(size_t l)
+{
+	return ((l) * 3) >> 2;
+}
+
+static inline int
+decode(char *restrict *restrict dest, char *restrict const buf,
+       const size_t buflen, unsigned u, const int n)
+{
+	char *d;
+
+	if (n <= 1) {
+		errno = EINVAL;
+		return -1;
+	}
+	d = *dest;
+	for (int i = 0; i < n - 1; i++) {
+		if (d == buf + buflen) {
+			errno = ENOMEM;
+			return -1;
+		}
+		*d++ = (u >> 16) & 0xff;
+		u <<= 8;
+	}
+	*dest += d - *dest;
+	return 1;
+}
+
+ssize_t
+base64_encode(const enum encoding enc, char *restrict const buf,
+	      const size_t buflen, const char *restrict const inbuf,
+	      const size_t inlength)
+{
+	const struct b64_alphabet *alpha = &b64_alphabet[enc];
+	char *p = buf;
+	const uint8_t *in = (const uint8_t *)inbuf;
+	const uint8_t * const end = in + inlength;
+
+	AN(buf);
+	AN(alpha);
+	if (in == NULL || inlength == 0)
+		return 0;
+
+	if ((enc == BASE64URLNOPAD &&
+	     buflen < base64nopad_encode_l(inlength)) ||
+	    (enc != BASE64URLNOPAD &&
+	     buflen < base64_encode_l(inlength))) {
+		errno = ENOMEM;
+		return -1;
+	}
+
+	while (end - in >= 3) {
+		*p++ = alpha->b64[(in[0] >> 2) & 0x3f];
+		*p++ = alpha->b64[((in[0] << 4) | (in[1] >> 4)) & 0x3f];
+		*p++ = alpha->b64[((in[1] << 2) | (in[2] >> 6)) & 0x3f];
+		*p++ = alpha->b64[in[2] & 0x3f];
+		in += 3;
+	}
+	if (end - in > 0) {
+		*p++ = alpha->b64[(in[0] >> 2) & 0x3f];
+		if (end - in == 1) {
+			*p++ = alpha->b64[(in[0] << 4) & 0x3f];
+			if (alpha->padding) {
+				*p++ = alpha->padding;
+				*p++ = alpha->padding;
+			}
+		}
+		else {
+			*p++ = alpha->b64[((in[0] << 4) | (in[1] >> 4)) & 0x3f];
+			*p++ = alpha->b64[(in[1] << 2) & 0x3f];
+			if (alpha->padding) {
+				*p++ = alpha->padding;
+			}
+		}
+	}
+	assert(p >= buf && p - buf <= buflen);
+	return p - buf;
+}
+
+ssize_t
+base64_decode(const enum encoding dec, char *restrict const buf,
+	      const size_t buflen, ssize_t inlen,
+	      const char *const p, va_list ap)
+{
+	const struct b64_alphabet *alpha = &b64_alphabet[dec];
+	char *dest = buf;
+	unsigned u = 0, term = 0;
+	int n = 0;
+	size_t len = SIZE_MAX;
+
+	AN(buf);
+	AN(alpha);
+
+	if (inlen >= 0)
+		len = inlen;
+
+	for (const char *s = p; len > 0 && s != vrt_magic_string_end;
+	     s = va_arg(ap, const char *)) {
+		if (s == NULL)
+			continue;
+		if (*s && term) {
+			errno = EINVAL;
+			return -1;
+		}
+		while (*s && len) {
+			while (n < 4) {
+				char b = alpha->i64[(unsigned) *s++];
+				u <<= 6;
+				if (b == ILL) {
+					errno = EINVAL;
+					return -1;
+				}
+				n++;
+				if (b == PAD) {
+					term++;
+					continue;
+				}
+				u |= (unsigned) b;
+				if (--len == 0)
+					break;
+				if (!*s)
+					break;
+			}
+			if (n == 4) {
+				if (decode(&dest, buf, buflen, u, n-term) < 0)
+					return -1;
+				n = 0;
+			}
+		}
+	}
+	if (n) {
+		if (!alpha->padding)
+			u <<= 6 * (4 - n);
+		if (decode(&dest, buf, buflen, u, n-term) < 0)
+			return -1;
+	}
+
+	return dest - buf;
+}
diff --git a/lib/libvmod_blob/base64.h b/lib/libvmod_blob/base64.h
new file mode 100644
index 0000000..e5318bd
--- /dev/null
+++ b/lib/libvmod_blob/base64.h
@@ -0,0 +1,156 @@
+/*-
+ * Copyright 2015-2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include "vmod_blob.h"
+
+#define ILL -1
+#define PAD -2
+
+static const struct b64_alphabet {
+	const char b64[64];
+	const int8_t i64[256];
+	const int padding;
+} b64_alphabet[] = {
+	[BASE64] = {
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+		"ghijklmnopqrstuvwxyz0123456789+/",
+		{
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL,  62, ILL, ILL, ILL,  63, /* +, -    */
+			 52,  53,  54,  55,  56,  57,  58,  59, /* 0 - 7   */
+			 60,  61, ILL, ILL, ILL, PAD, ILL, ILL, /* 8, 9, = */ 
+			ILL,   0,   1,   2,   3,   4,   5,   6, /* A - G   */
+			  7,   8,   9,  10,  11,  12,  13,  14, /* H - O   */
+			 15,  16,  17,  18,  19,  20,  21,  22, /* P - W   */
+			 23,  24,  25, ILL, ILL, ILL, ILL, ILL, /* X, Y, Z */
+			ILL,  26,  27,  28,  29,  30,  31,  32, /* a - g   */
+			 33,  34,  35,  36,  37,  38,  39,  40, /* h - o   */
+			 41,  42,  43,  44,  45,  46,  47,  48, /* p - w   */
+			 49,  50,  51, ILL, ILL, ILL, ILL, ILL, /* x, y, z */
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+		},
+		'='
+	},
+	[BASE64URL] = {
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+		"ghijklmnopqrstuvwxyz0123456789-_",
+		{
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL,  62, ILL, ILL, /* -       */
+			 52,  53,  54,  55,  56,  57,  58,  59, /* 0 - 7   */
+			 60,  61, ILL, ILL, ILL, PAD, ILL, ILL, /* 8, 9, = */ 
+			ILL,   0,   1,   2,   3,   4,   5,   6, /* A - G   */
+			  7,   8,   9,  10,  11,  12,  13,  14, /* H - O   */
+			 15,  16,  17,  18,  19,  20,  21,  22, /* P - W   */
+			 23,  24,  25, ILL, ILL, ILL, ILL,  63, /* X-Z, _  */
+			ILL,  26,  27,  28,  29,  30,  31,  32, /* a - g   */
+			 33,  34,  35,  36,  37,  38,  39,  40, /* h - o   */
+			 41,  42,  43,  44,  45,  46,  47,  48, /* p - w   */
+			 49,  50,  51, ILL, ILL, ILL, ILL, ILL, /* x, y, z */
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+		},
+		'='
+	},
+	[BASE64URLNOPAD] = {
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
+		"ghijklmnopqrstuvwxyz0123456789-_",
+		{
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL,  62, ILL, ILL, /* -       */
+			 52,  53,  54,  55,  56,  57,  58,  59, /* 0 - 7   */
+			 60,  61, ILL, ILL, ILL, ILL, ILL, ILL, /* 8, 9    */ 
+			ILL,   0,   1,   2,   3,   4,   5,   6, /* A - G   */
+			  7,   8,   9,  10,  11,  12,  13,  14, /* H - O   */
+			 15,  16,  17,  18,  19,  20,  21,  22, /* P - W   */
+			 23,  24,  25, ILL, ILL, ILL, ILL,  63, /* X-Z, _  */
+			ILL,  26,  27,  28,  29,  30,  31,  32, /* a - g   */
+			 33,  34,  35,  36,  37,  38,  39,  40, /* h - o   */
+			 41,  42,  43,  44,  45,  46,  47,  48, /* p - w   */
+			 49,  50,  51, ILL, ILL, ILL, ILL, ILL, /* x, y, z */
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+			ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+		},
+		0
+	},
+};
diff --git a/lib/libvmod_blob/hex.c b/lib/libvmod_blob/hex.c
new file mode 100644
index 0000000..0195123
--- /dev/null
+++ b/lib/libvmod_blob/hex.c
@@ -0,0 +1,167 @@
+/*-
+ * Copyright 2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include "config.h"
+#include <ctype.h>
+#include <errno.h>
+
+#include "vmod_blob.h"
+#include "hex.h"
+
+#include "vdef.h"
+#include "vrt.h"
+#include "vas.h"
+
+const char hex_alphabet[][16] = {
+	"0123456789abcdef",
+	"0123456789ABCDEF"
+};
+
+/*
+ * Shift the ASCII table over so that it begins at '0', and replace the
+ * hex digits with their binary values. This fits all of the hex digits
+ * into 55 bytes (cacheline friendly).
+ */
+const uint8_t nibble[] = {
+	   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
+	   ILL, ILL, ILL, ILL, ILL, ILL, ILL, 10,  11,  12,
+	   13,  14,  15,  ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+	   ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
+	   ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL, 10,
+	   11,  12,  13,  14,  15
+};
+
+size_t
+hex_encode_l(size_t l)
+{
+	return ((l) << 1) + 1;
+}
+
+size_t
+hex_decode_l(size_t l)
+{
+	return ((l) + 1) >> 1;
+}
+
+static inline char
+hex2byte(const unsigned char hi, const unsigned char lo)
+{
+	return (nibble[hi - '0'] << 4) | nibble[lo - '0'];
+}
+
+ssize_t
+hex_encode(const enum encoding enc, char *restrict const buf,
+	   const size_t buflen, const char *restrict const in,
+	   const size_t inlen)
+{
+	char *p = buf;
+	const char *alphabet = hex_alphabet[0];
+
+	AN(buf);
+	assert(enc == HEXUC || enc == HEXLC);
+	if (in == NULL || inlen == 0)
+		return 0;
+	if (buflen < hex_encode_l(inlen))
+		return -1;
+
+	if (enc != HEXLC)
+		alphabet = hex_alphabet[1];
+
+	for (int i = 0; i < inlen; i++) {
+		*p++ = alphabet[(in[i] & 0xf0) >> 4];
+		*p++ = alphabet[in[i] & 0x0f];
+	}
+
+	return p - buf;
+}
+
+ssize_t
+hex_decode(const enum encoding dec, char *restrict const buf,
+	   const size_t buflen, ssize_t n,
+	   const char *restrict const p, va_list ap)
+{
+	char *dest = buf;
+	unsigned char extranib = 0;
+	ssize_t len = 0;
+	va_list ap2;
+
+	AN(buf);
+	assert(dec == HEX);
+
+	va_copy(ap2, ap);
+	for (const char *s = p; s != vrt_magic_string_end;
+	     s = va_arg(ap2, const char *)) {
+		const char *b = s;
+		if (s != NULL)
+			while (*s)
+				if (!isxdigit(*s++)) {
+					len = -1;
+					break;
+				}
+		if (len == -1)
+			break;
+		len += s - b;
+	}
+	va_end(ap2);
+
+	if (len == 0)
+		return 0;
+	if (len == -1) {
+		errno = EINVAL;
+		return -1;
+	}
+	if (n != -1 && len > n)
+		len = n;
+
+	if ((len+1) >> 1 > buflen) {
+		errno = ENOMEM;
+		return -1;
+	}
+	if (len & 1) {
+		extranib = '0';
+		len++;
+	}
+
+	for (const char *s = p; len > 0 && s != vrt_magic_string_end;
+	     s = va_arg(ap, const char *)) {
+		if (s == NULL || *s == '\0')
+			continue;
+		if (extranib) {
+			*dest++ = hex2byte(extranib, *s++);
+			len -= 2;
+		}
+		while (len >= 2 && *s && *(s+1)) {
+			*dest++ = hex2byte(*s, *(s+1));
+			s += 2;
+			len -= 2;
+		}
+		extranib = *s;
+	}
+	assert(dest <= buf + buflen);
+	return dest - buf;
+}
diff --git a/lib/libvmod_blob/hex.h b/lib/libvmod_blob/hex.h
new file mode 100644
index 0000000..9c0e344
--- /dev/null
+++ b/lib/libvmod_blob/hex.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright 2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include <stdint.h>
+
+#define ILL (0xff)
+
+/* These are defined in hex.c */
+
+extern const char hex_alphabet[][16];
+
+extern const uint8_t nibble[];
diff --git a/lib/libvmod_blob/id.c b/lib/libvmod_blob/id.c
new file mode 100644
index 0000000..3a32061
--- /dev/null
+++ b/lib/libvmod_blob/id.c
@@ -0,0 +1,102 @@
+/*-
+ * Copyright 2015-2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include "config.h"
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include "vmod_blob.h"
+
+#include "vdef.h"
+#include "vrt.h"
+#include "vas.h"
+
+size_t
+id_encode_l(size_t l)
+{
+	return l + 1;
+}
+
+size_t
+id_decode_l(size_t l)
+{
+	return l;
+}
+
+ssize_t
+id_encode(const enum encoding enc, char *restrict const buf,
+	  const size_t buflen, const char *restrict const in,
+	  const size_t inlen)
+{
+	(void) enc;
+	AN(buf);
+
+	if (buflen < inlen + 1)
+		return -1;
+	if (in == NULL || inlen == 0)
+		return 0;
+
+	memcpy(buf, in, inlen);
+	return inlen;
+}
+
+ssize_t
+id_decode(const enum encoding enc,
+	  char *restrict const buf, const size_t buflen,
+	  ssize_t n, const char *restrict const p, va_list ap)
+{
+	char *dest = buf;
+	size_t outlen = 0, c = SIZE_MAX;
+
+	(void) enc;
+	AN(buf);
+
+	if (n >= 0)
+		c = n;
+
+	for (const char *s = p; c > 0 && s != vrt_magic_string_end;
+	     s = va_arg(ap, const char *)) {
+		size_t len;
+
+		if (s == NULL || *s == '\0')
+			continue;
+		len = strlen(s);
+		if (len > c)
+			len = c;
+		c -= len;
+		if ((outlen += len) > buflen) {
+			errno = ENOMEM;
+			return -1;
+		}
+		memcpy(dest, s, len);
+		dest += len;
+	}
+
+	return outlen;
+}
diff --git a/lib/libvmod_blob/parse_encoding.c b/lib/libvmod_blob/parse_encoding.c
new file mode 100644
index 0000000..ec64b28
--- /dev/null
+++ b/lib/libvmod_blob/parse_encoding.c
@@ -0,0 +1,179 @@
+/*
+ * for the time being, this code is auto-generated outside the varnishd source
+ * tree, see
+ * https://code.uplex.de/uplex-varnish/libvmod-blobcode/blob/master/src/gen_enum_parse.pl
+ *
+ * TODO: integrate in vmodtool.py or replace with something else
+ * cf. the same TODO for the shard director in libvmod_directors
+ */
+
+#include "parse_encoding.h"
+#define term(c) ((c) == '\0')
+
+enum encoding
+parse_encoding (const char *m) {
+	int p;
+	enum encoding r;
+
+	switch (m[0]) {
+	case 'B':	goto _0B;	// BASE64, BASE64URL, BASE64URLNOPAD
+	case 'H':	goto _0H;	// HEX, HEXLC, HEXUC
+	case 'I':	goto _0I;	// IDENTITY
+	case 'U':	goto _0U;	// URL, URLLC, URLUC
+	default:	goto invalid;
+	}
+	 _0B:
+	switch (m[1]) {
+	case 'A':	goto _1BA;	// BASE64, BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _1BA:
+	switch (m[2]) {
+	case 'S':	goto _2BAS;	// BASE64, BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _2BAS:
+	switch (m[3]) {
+	case 'E':	goto _3BASE;	// BASE64, BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _3BASE:
+	switch (m[4]) {
+	case '6':	goto _4BASE6;	// BASE64, BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _4BASE6:
+	switch (m[5]) {
+	case '4':	goto _5BASE64;	// BASE64, BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _5BASE64:
+	//BASE64
+	if (term(m[6])) {
+	    r = BASE64;
+	    p = 6;
+	    goto ok;
+	}
+	switch (m[6]) {
+	case 'U':	goto _6BASE64U;	// BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _6BASE64U:
+	switch (m[7]) {
+	case 'R':	goto _7BASE64UR;	// BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _7BASE64UR:
+	switch (m[8]) {
+	case 'L':	goto _8BASE64URL;	// BASE64URL, BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _8BASE64URL:
+	//BASE64URL
+	if (term(m[9])) {
+	    r = BASE64URL;
+	    p = 9;
+	    goto ok;
+	}
+	switch (m[9]) {
+	case 'N':	goto _9BASE64URLN;	// BASE64URLNOPAD
+	default:	goto invalid;
+	}
+	 _9BASE64URLN:
+	//BASE64URLNOPAD
+	if ((m[10] == 'O') && (m[11] == 'P') && (m[12] == 'A') && (m[13] == 'D') && (term(m[14]))) {
+	    r = BASE64URLNOPAD;
+	    p = 14;
+	    goto ok;
+	}
+	goto invalid;
+	 _0H:
+	switch (m[1]) {
+	case 'E':	goto _1HE;	// HEX, HEXLC, HEXUC
+	default:	goto invalid;
+	}
+	 _1HE:
+	switch (m[2]) {
+	case 'X':	goto _2HEX;	// HEX, HEXLC, HEXUC
+	default:	goto invalid;
+	}
+	 _2HEX:
+	//HEX
+	if (term(m[3])) {
+	    r = HEX;
+	    p = 3;
+	    goto ok;
+	}
+	switch (m[3]) {
+	case 'L':	goto _3HEXL;	// HEXLC
+	case 'U':	goto _3HEXU;	// HEXUC
+	default:	goto invalid;
+	}
+	 _3HEXL:
+	//HEXLC
+	if ((m[4] == 'C') && (term(m[5]))) {
+	    r = HEXLC;
+	    p = 5;
+	    goto ok;
+	}
+	goto invalid;
+	 _3HEXU:
+	//HEXUC
+	if ((m[4] == 'C') && (term(m[5]))) {
+	    r = HEXUC;
+	    p = 5;
+	    goto ok;
+	}
+	goto invalid;
+	 _0I:
+	//IDENTITY
+	if ((m[1] == 'D') && (m[2] == 'E') && (m[3] == 'N') && (m[4] == 'T') && (m[5] == 'I') && (m[6] == 'T') && (m[7] == 'Y') && (term(m[8]))) {
+	    r = IDENTITY;
+	    p = 8;
+	    goto ok;
+	}
+	goto invalid;
+	 _0U:
+	switch (m[1]) {
+	case 'R':	goto _1UR;	// URL, URLLC, URLUC
+	default:	goto invalid;
+	}
+	 _1UR:
+	switch (m[2]) {
+	case 'L':	goto _2URL;	// URL, URLLC, URLUC
+	default:	goto invalid;
+	}
+	 _2URL:
+	//URL
+	if (term(m[3])) {
+	    r = URL;
+	    p = 3;
+	    goto ok;
+	}
+	switch (m[3]) {
+	case 'L':	goto _3URLL;	// URLLC
+	case 'U':	goto _3URLU;	// URLUC
+	default:	goto invalid;
+	}
+	 _3URLL:
+	//URLLC
+	if ((m[4] == 'C') && (term(m[5]))) {
+	    r = URLLC;
+	    p = 5;
+	    goto ok;
+	}
+	goto invalid;
+	 _3URLU:
+	//URLUC
+	if ((m[4] == 'C') && (term(m[5]))) {
+	    r = URLUC;
+	    p = 5;
+	    goto ok;
+	}
+	goto invalid;
+  ok:
+	return r;
+  invalid:
+    return _INVALID;
+    (void)p;
+}
diff --git a/lib/libvmod_blob/parse_encoding.h b/lib/libvmod_blob/parse_encoding.h
new file mode 100644
index 0000000..6d6068b
--- /dev/null
+++ b/lib/libvmod_blob/parse_encoding.h
@@ -0,0 +1,26 @@
+/*
+ * for the time being, this code is auto-generated outside the varnishd source
+ * tree, see
+ * https://code.uplex.de/uplex-varnish/libvmod-blobcode/blob/master/src/gen_enum_parse.pl
+ *
+ * TODO: integrate in vmodtool.py or replace with something else
+ * cf. the same TODO for the shard director in libvmod_directors
+ */
+
+enum encoding {
+	_INVALID = 0,
+	IDENTITY,
+	BASE64,
+	BASE64URL,
+	BASE64URLNOPAD,
+	HEX,
+	HEXUC,
+	HEXLC,
+	URL,
+	URLLC,
+	URLUC,
+	__MAX_ENCODING
+};
+
+enum encoding parse_encoding (const char *);
+
diff --git a/lib/libvmod_blob/url.c b/lib/libvmod_blob/url.c
new file mode 100644
index 0000000..7368030
--- /dev/null
+++ b/lib/libvmod_blob/url.c
@@ -0,0 +1,187 @@
+/*-
+ * Copyright 2015-2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include "config.h"
+#include <errno.h>
+
+#include "vmod_blob.h"
+#include "hex.h"
+
+#include "vdef.h"
+#include "vrt.h"
+#include "vas.h"
+
+/* Decoder states */
+enum state_e {
+	NORMAL,
+	PERCENT,  /* just read '%' */
+	FIRSTNIB, /* just read the first nibble after '%' */
+};
+
+size_t
+url_encode_l(size_t l)
+{
+	return (l * 3) + 1;
+}
+
+size_t
+url_decode_l(size_t l)
+{
+	return l;
+}
+
+/*
+ * Bitmap of unreserved characters according to RFC 3986 section 2.3
+ * (locale-independent and cacheline friendly)
+ */
+static const uint8_t unreserved[] = {
+	0x0,  0x0,  0x0,  0x0,  0x0,  0x60, 0xff, 0x3,
+	0xfe, 0xff, 0xff, 0x87, 0xfe, 0xff, 0xff, 0x47,
+	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,
+	0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0
+};
+
+static inline int
+isunreserved(const uint8_t c)
+{
+	return (unreserved[c >> 3] & (1 << (c & 7)));
+}
+
+static inline int
+isoutofrange(const uint8_t c)
+{
+	return (c < '0' || c > 'f');
+}
+
+ssize_t
+url_encode(const enum encoding enc, char *restrict const buf,
+	   const size_t buflen, const char *restrict const in,
+	   const size_t inlen)
+{
+	char *p = buf;
+	const char * const end = buf + buflen;
+	const char *alphabet = hex_alphabet[0];
+
+	AN(buf);
+	assert(enc == URLUC || enc == URLLC);
+	if (in == NULL || inlen == 0)
+		return 0;
+
+	if (enc != URLLC)
+		alphabet = hex_alphabet[1];
+
+	for (int i = 0; i < inlen; i++) {
+		if (isunreserved(in[i])) {
+			if (p == end)
+				return -1;
+			*p++ = in[i];
+		}
+		else {
+			if (p + 3 > end)
+				return -1;
+			*p++ = '%';
+			*p++ = alphabet[(in[i] & 0xf0) >> 4];
+			*p++ = alphabet[in[i] & 0x0f];
+		}
+	}
+
+	return p - buf;
+}
+
+ssize_t
+url_decode(const enum encoding dec, char *restrict const buf,
+	   const size_t buflen, ssize_t n, const char *restrict const p,
+	   va_list ap)
+{
+	char *dest = buf;
+	const char * const end = buf + buflen;
+	size_t len = SIZE_MAX;
+	uint8_t nib = 0;
+	enum state_e state = NORMAL;
+
+	AN(buf);
+	assert(dec == URL);
+
+	if (n >= 0 && n < len)
+		len = n;
+
+	for (const char *s = p; len > 0 && s != vrt_magic_string_end;
+	     s = va_arg(ap, const char *)) {
+		if (s == NULL || *s == '\0')
+			continue;
+		while (*s && len) {
+			uint8_t nib2;
+
+			switch(state) {
+			case NORMAL:
+				if (*s == '%')
+					state = PERCENT;
+				else {
+					if (dest == end) {
+						errno = ENOMEM;
+						return -1;
+					}
+					*dest++ = *s;
+				}
+				break;
+			case PERCENT:
+				if (isoutofrange(*s)
+				    || (nib = nibble[*s - '0']) == ILL) {
+					errno = EINVAL;
+					return -1;
+				}
+				state = FIRSTNIB;
+				break;
+			case FIRSTNIB:
+				if (dest == end) {
+					errno = ENOMEM;
+					return -1;
+				}
+				if (isoutofrange(*s)
+				    || (nib2 = nibble[*s - '0']) == ILL) {
+					errno = EINVAL;
+					return -1;
+				}
+				*dest++ = (nib << 4) | nib2;
+				nib = 0;
+				state = NORMAL;
+				break;
+			default:
+				WRONG("illegal URL decode state");
+			}
+			s++;
+			len--;
+		}
+	}
+	if (state != NORMAL) {
+		errno = EINVAL;
+		return -1;
+	}
+	assert(dest <= end);
+	return dest - buf;
+}
diff --git a/lib/libvmod_blob/vmod.vcc b/lib/libvmod_blob/vmod.vcc
new file mode 100644
index 0000000..826a6c9
--- /dev/null
+++ b/lib/libvmod_blob/vmod.vcc
@@ -0,0 +1,376 @@
+#-
+# This document is licensed under the same conditions as Varnish itself.
+# See LICENSE for details.
+#
+# Authors: Nils Goroll <nils.goroll at uplex.de>
+#          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+#
+
+$Module blob 3 utilities for the VCL blob type
+
+::
+
+  STRING blob.encode(ENUM encoding, BLOB blob)
+  BLOB blob.decode(ENUM decoding, STRING_LIST encoded)
+  BLOB blob.decode_n(INT n, ENUM decoding, STRING_LIST encoded)
+  STRING blob.transcode(ENUM decoding, ENUM encoding,
+                        STRING_LIST encoded)
+  STRING blob.transcode_n(INT n, ENUM decoding, ENUM encoding,
+                          STRING_LIST encoded)
+
+  new OBJ = blob.blob(ENUM decoding, STRING_LIST encoded)
+  BLOB <obj>.get()
+  STRING <obj>.encode(ENUM encoding)
+
+DESCRIPTION
+===========
+
+This VMOD provides utility functions and an object for the VCL data
+type BLOB, which may contain arbitrary data of any length.
+
+Examples::
+
+  sub vcl_init {
+      # Create blob objects from encodings such as base64 or hex.
+      new myblob   = blob.blob(BASE64, "Zm9vYmFy");
+      new yourblob = blob.blob(encoded="666F6F", decoding=HEX);
+  }
+
+  sub vcl_deliver {
+      # The .get() method retrieves the BLOB from an object.
+      set resp.http.MyBlob-As-Hex
+          = blob.encode(blob=myblob.get(), encoding=HEXLC);
+
+      # The .encode() method efficiently retrieves an encoding.
+      set resp.http.YourBlob-As-Base64 = yourblob.encode(BASE64);
+
+      # decode() and encode() functions convert blobs to text and
+      # vice versa at runtime.
+      set resp.http.Base64-Encoded
+          = blob.encode(BASE64,
+                            blob.decode(HEX, req.http.Hex-Encoded));
+  }
+
+  sub vcl_recv {
+      # transcode() converts from one encoding to another.
+      set req.http.Hex-Encoded
+          = blob.transcode(decoding=BASE64, encoding=HEXUC,
+                             encoded="YmF6");
+
+      # transcode() from URL to IDENTITY effects a URL decode.
+      set req.url = blob.transcode(encoded=req.url, decoding=URL);
+
+      # transcode() from IDENTITY to URL effects a URL encode.
+      set req.http.url_urlcoded
+          = blob.transcode(encoded=req.url, encoding=URLLC);
+  }
+
+ENCODING SCHEMES
+================
+
+Binary-to-text encoding schemes are specified by ENUMs in the VMOD's
+constructor, methods and functions. Decodings convert a (possibly
+concatenated) string into a blob, while encodings convert a blob into
+a string.
+
+ENUM values for a decoding can be one of:
+
+* ``IDENTITY``
+* ``BASE64``
+* ``BASE64URL``
+* ``BASE64URLNOPAD``
+* ``HEX``
+* ``URL``
+
+An encoding can be one of:
+
+* ``IDENTITY``
+* ``BASE64``
+* ``BASE64URL``
+* ``BASE64URLNOPAD``
+* ``HEXUC``
+* ``HEXLC``
+* ``URLUC``
+* ``URLLC``
+
+Empty strings are decoded into a "null blob" (of length 0),
+and conversely a null blob is encoded as the empty string.
+
+IDENTITY
+--------
+
+The simplest encoding converts between the BLOB and STRING data types,
+leaving the contents byte-identical.
+
+Note that a BLOB may contain a null byte at any position before its
+end; if such a BLOB is decoded with IDENTITY, the resulting STRING
+will have a null byte at that position. Since VCL strings, like C
+strings, are represented with a terminating null byte, the string will
+be truncated, appearing to contain less data than the original
+blob. For example::
+
+  # Decode from the hex encoding for "foo\0bar".
+  # The header will be seen as "foo".
+  set resp.http.Trunced-Foo1
+    = blob.encode(IDENTITY, blob.decode(HEX, "666f6f00626172"));
+
+IDENTITY is the default encoding and decoding. So the above can also
+be written as::
+
+  # Decode from the hex encoding for "foo\0bar".
+  # The header will be seen as "foo".
+  set resp.http.Trunced-Foo2
+    = blob.encode(blob=blob.decode(HEX, "666f6f00626172"));
+
+BASE64*
+-------
+
+The base64 encoding schemes use 4 characters to encode 3 bytes. There
+are no newlines or maximal line lengths -- whitespace is not
+permitted.
+
+The ``BASE64`` encoding uses the alphanumeric characters, ``+`` and
+``/``; and encoded strings are padded with the ``=`` character so that
+their length is always a multiple of four.
+
+The ``BASE64URL`` encoding also uses the alphanumeric characters, but
+``-`` and ``_`` instead of ``+`` and ``/``, so that an encoded string
+can be used safely in a URL. This scheme also uses the padding
+character ``=``.
+
+The ``BASE64URLNOPAD`` encoding uses the same alphabet as
+``BASE6URL``, but leaves out the padding. Thus the length of an
+encoding with this scheme is not necessarily a mutltiple of four.
+
+HEX*
+----
+
+The ``HEX`` decoding converts a hex string, which may contain upper-
+or lowercase characters for hex digits ``A`` through ``f``, into a
+blob. The ``HEXUC`` or ``HEXLC`` encodings convert a blob into a hex
+string with upper- and lowercase digits, respectively. A prefix such
+as ``0x`` is not used for any of these schemes.
+
+If a hex string to be decoded has an odd number of digits, it is
+decoded as if a ``0`` is prepended to it; that is, the first digit is
+interpreted as representing the least significant nibble of the first
+byte. For example::
+
+  # The concatenated string is "abcdef0", and is decoded as "0abcdef0".
+  set resp.http.First = "abc";
+  set resp.http.Second = "def0";
+  set resp.http.Hex-Decoded
+      = blob.encode(HEXLC, blob.decode(HEX,
+                        resp.http.First + resp.http.Second));
+
+URL*
+----
+
+The ``URL`` decoding replaces any ``%<2-hex-digits>`` substrings with
+the binary value of the hexadecimal number after the % sign.
+
+The ``URLLC`` and ``URLUC`` encodings implement "percent encoding" as per
+RFC3986, the hexadecimal characters A-F being output in lower- and
+uppercase, respectively.
+
+$Function BLOB decode(ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD,
+                            HEX, URL} decoding="IDENTITY", STRING_LIST encoded)
+
+Returns the BLOB derived from the string ``encoded`` according to the
+scheme specified by ``decoding``.
+
+``decoding`` defaults to IDENTITY
+
+Example::
+
+	blob.decode(BASE64, "Zm9vYmFyYmF6");
+
+	# same with named parameters
+	blob.decode(encoded="Zm9vYmFyYmF6", decoding=BASE64);
+
+	# convert string to blob
+	blob.decode(encoded="foo");
+
+$Function BLOB decode_n(INT n,
+			ENUM {IDENTITY, BASE64, BASE64URL,
+			BASE64URLNOPAD, HEX, URL} decoding="IDENTITY",
+			STRING_LIST encoded)
+
+Same as ``decode()``, but only decode the first ``n`` characters of
+the encoded string. If ``n`` is greater than the length of the string,
+then return the same result as ``decode()``.
+
+$Function STRING encode(ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD,
+                              HEXUC, HEXLC, URLUC, URLLC} encoding="IDENTITY",
+			      BLOB blob)
+
+Returns a string representation of the BLOB ``blob`` as specifed by
+``encoding``.
+
+``encoding`` defaults to IDENTITY
+
+Example::
+
+	set resp.http.encode1
+	    = blob.encode(HEXLC, blob.decode(BASE64, "Zm9vYmFyYmF6"));
+
+	# same with named parameters
+	set resp.http.encode2
+	    = blob.encode(blob=blob.decode(encoded="Zm9vYmFyYmF6",
+						   decoding=BASE64),
+			      encoding=HEXLC);
+
+	# convert blob to string
+	set resp.http.encode3
+	    = blob.encode(blob=blob.decode(encoded="foo"));
+
+$Function STRING transcode(ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD,
+                                 HEX, URL} decoding="IDENTITY",
+                           ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD,
+                                 HEXUC, HEXLC, URLUC, URLLC} encoding="IDENTITY",
+                           STRING_LIST encoded)
+
+Translates from one encoding to another, by first decoding the string
+``encoded`` according to the scheme ``decoding``, and then returning
+the encoding of the resulting blob according to the scheme
+``encoding``.
+
+``decoding`` and ``encoding`` default to IDENTITY
+
+Example::
+
+	set resp.http.Hex2Base64-1 = blob.transcode(HEX, BASE64, "666f6f");
+
+	# same with named parameters
+	set resp.http.Hex2Base64-2
+	    = blob.transcode(encoded="666f6f",
+				 encoding=BASE64, decoding=HEX);
+
+	# URL decode -- recall that IDENTITY is the default encoding.
+	set resp.http.urldecoded
+	    = blob.transcode(encoded="foo%20bar", decoding=URLLC);
+
+	# URL encode
+	set resp.http.urlencoded
+	    = blob.transcode(encoded="foo bar", encoding=URL);
+
+$Function STRING transcode_n(INT n,
+			     ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD,
+                                   HEX, URL} decoding="IDENTITY",
+                             ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD,
+                                   HEXUC, HEXLC, URLUC, URLLC} encoding="IDENTITY",
+                             STRING_LIST encoded)
+
+Same as ``transcode()``, but only from the first ``n`` characters of
+the encoded string.
+
+$Object blob(ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD, HEX,
+                   URL} decoding="IDENTITY",
+             STRING_LIST encoded)
+
+Creates an object that contains the BLOB derived from the string
+``encoded`` according to the scheme ``decoding``.
+
+Example::
+
+	new theblob1 = blob.blob(BASE64, "YmxvYg==");
+
+	# same with named arguments
+	new theblob2 = blob.blob(encoded="YmxvYg==", decoding=BASE64);
+
+	# string as a blob
+	new stringblob = blob.blob(encoded="bazz");
+
+$Method BLOB .get()
+
+Returns the BLOB created by the constructor.
+
+Example::
+
+	set resp.http.The-Blob1 =
+	    blob.encode(blob=theblob1.get());
+
+	set resp.http.The-Blob2 =
+	    blob.encode(blob=theblob2.get());
+
+	set resp.http.The-Stringblob =
+	    blob.encode(blob=stringblob.get());
+
+$Method STRING .encode(ENUM {IDENTITY, BASE64, BASE64URL, BASE64URLNOPAD, HEXUC,
+                             HEXLC, URLUC, URLLC} encoding="IDENTITY")
+
+Returns an encoding of BLOB created by the constructor, according to
+the scheme ``encoding``.
+
+Example::
+
+	# blob as text
+	set resp.http.The-Blob = theblob1.encode();
+
+	# blob as base64
+	set resp.http.The-Blob-b64 = theblob1.encode(BASE64);
+
+For any ``blob`` object and encoding ``ENC``, encodings via the ``.encode()``
+method and the ``encode()`` function are equal::
+
+  # Always true:
+  blob.encode(ENC, blob.get()) == blob.encode(ENC)
+
+But the object method is more efficient -- the encoding is computed
+once and cached (with allocation in heap memory), and the cached
+encoding is retrieved on every subsequent call. The ``encode()``
+function computes the encoding on every call, allocating space for the
+string in Varnish workspaces.
+
+So if the data in a BLOB are fixed at VCL initialization time, so that
+its encodings will always be the same, it is better to create a
+``blob`` object. The VMOD's functions should be used for data that are
+not known until runtime.
+
+ERRORS
+======
+
+The encoders and decoders may fail if there is insufficient space to
+create the new blob or string. Decoders may also fail if the encoded
+string is an illegal format for the decoding scheme.
+
+If any of the VMOD's methods, functions or constructor fail, then VCL
+failure is invoked, just as if ``return(fail)`` had been called in the
+VCL source. This means that:
+
+* If the ``blob`` object constructor fails, or if any methods or
+  functions fail during ``vcl_init``, then the VCL program will fail
+  to load, and the VCC compiler will emit an error message.
+
+* If a method or function fails in any other VCL subroutine besides
+  ``vcl_synth``, then control is directed to ``vcl_synth``. The
+  response status is set to 503 with the reason string ``"VCL
+  failed"``, and an error message will be written to the Varnish log
+  using the tag ``VCL_Error``.
+
+* If the failure occurs during ``vcl_synth``, then ``vcl_synth`` is
+  aborted. The response line ``"503 VCL failed"`` is returned, and
+  the ``VCL_Error`` message is written to the log.
+
+LIMITATIONS
+===========
+
+The VMOD allocates memory in various ways for new blobs and
+strings. The ``blob`` object and its methods allocate memory from the
+heap, and hence they are only limited by available virtual memory.
+
+The ``encode()``, ``decode()`` and ``transcode()`` functions allocate
+Varnish workspace.  If these functions are failing, as indicated by
+"out of space" messages in the Varnish log (with the ``VCL_Error``
+tag), then you will need to increase the varnishd parameters
+``workspace_client`` and/or ``workspace_backend``.
+
+The ``transcode()`` function also allocates space on the stack for a
+temporary BLOB. If this function causes stack overflow, you may need
+to increase the varnishd parameter ``thread_pool_stack``.
+
+SEE ALSO
+========
+
+* :ref:`varnishd(1)`
+* :ref:`vcl(7)`
diff --git a/lib/libvmod_blob/vmod_blob.c b/lib/libvmod_blob/vmod_blob.c
new file mode 100644
index 0000000..7d0ad92
--- /dev/null
+++ b/lib/libvmod_blob/vmod_blob.c
@@ -0,0 +1,609 @@
+/*-
+ * Copyright 2015-2017 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include "config.h"
+#include <errno.h>
+#include <pthread.h>
+
+#include "vcl.h"
+#include "vdef.h"
+#include "vrt.h"
+
+#include "vcc_if.h"
+#include "vmod_blob.h"
+#include "wb.h"
+
+struct vmod_blob_blob {
+	unsigned magic;
+#define VMOD_BLOB_MAGIC 0xfade4fa9
+	struct vmod_priv blob;
+	const char *encoding[__MAX_ENCODING];
+	pthread_mutex_t lock;
+};
+
+#define B64_FUNCS				   \
+		.decode_l	= base64_decode_l, \
+		.decode		= base64_decode,   \
+		.encode		= base64_encode
+
+#define HEX_FUNCS				\
+		.decode_l	= hex_decode_l, \
+		.decode		= hex_decode,	\
+		.encode_l	= hex_encode_l, \
+		.encode		= hex_encode
+
+#define URL_FUNCS				\
+		.decode_l	= url_decode_l, \
+		.decode		= url_decode,	\
+		.encode_l	= url_encode_l, \
+		.encode		= url_encode
+
+static const struct vmod_blob_fptr {
+	len_f		*const decode_l;
+	decode_f	*const decode;
+	len_f		*const encode_l;
+	encode_f	*const encode;
+} func[__MAX_ENCODING] = {
+	[_INVALID] = {
+		/* make implicit null init explicit for clarity */
+		.decode_l	= NULL,
+		.decode		= NULL,
+		.encode_l	= NULL,
+		.encode		= NULL
+	},
+	[IDENTITY] = {
+		.decode_l	= id_decode_l,
+		.decode		= id_decode,
+		.encode_l	= id_encode_l,
+		.encode		= id_encode
+	},
+	[BASE64] = {
+		B64_FUNCS,
+		.encode_l	= base64_encode_l
+	},
+	[BASE64URL] = {
+		B64_FUNCS,
+		.encode_l	= base64_encode_l
+	},
+	[BASE64URLNOPAD] = {
+		B64_FUNCS,
+		.encode_l	= base64nopad_encode_l
+	},
+	[HEX] = {
+		HEX_FUNCS
+	},
+	[HEXUC] = {
+		HEX_FUNCS
+	},
+	[HEXLC] = {
+		HEX_FUNCS
+	},
+	[URL] = {
+		URL_FUNCS
+	},
+	[URLUC] = {
+		URL_FUNCS
+	},
+	[URLLC] = {
+		URL_FUNCS
+	}
+};
+
+#undef B64_FUNCS
+#undef HEX_FUNCS
+#undef URL_FUNCS
+
+#define ERR(ctx, msg) \
+	VRT_fail((ctx), "vmod blob error: " msg)
+
+#define VERR(ctx, fmt, ...) \
+	VRT_fail((ctx), "vmod blob error: " fmt, __VA_ARGS__)
+
+#define ERRINVAL(ctx, enc) \
+	VERR((ctx), "cannot decode, illegal encoding beginning with \"%s\"", \
+	     (enc))
+
+#define VERRNOMEM(ctx, fmt, ...) \
+	VERR((ctx), fmt ", out of space", __VA_ARGS__)
+
+#define ERRNOMEM(ctx, msg) \
+	ERR((ctx), msg ", out of space")
+
+static char empty[1] = { '\0' };
+
+static const struct vmod_priv null_blob[1] =
+{
+	{
+		.priv = empty,
+		.len = 0,
+		.free = NULL
+	}
+};
+
+static inline size_t
+decode_l_va(enum encoding dec, const char * const p, va_list ap)
+{
+	size_t len = 0;
+
+	AENC(dec);
+
+	for (const char *s = p; s != vrt_magic_string_end;
+	     s = va_arg(ap, const char *))
+		if (s != NULL && *s != '\0')
+			len += strlen(s);
+
+	return(func[dec].decode_l(len));
+}
+
+static void
+err_decode(VRT_CTX, const char *enc)
+{
+	switch(errno) {
+	case EINVAL:
+		ERRINVAL(ctx, enc);
+		break;
+	case ENOMEM:
+		ERRNOMEM(ctx, "cannot decode");
+		break;
+	default:
+		WRONG("invalid errno");
+	}
+}
+
+/* Objects */
+
+VCL_VOID __match_proto__(td_blob_blob__init)
+vmod_blob__init(VRT_CTX, struct vmod_blob_blob **blobp, const char *vcl_name,
+		VCL_ENUM decs, const char *p, ...)
+{
+	struct vmod_blob_blob *b;
+	enum encoding dec = parse_encoding(decs);
+	va_list ap;
+	ssize_t len;
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	AN(blobp);
+	AZ(*blobp);
+	AN(vcl_name);
+	AENC(dec);
+
+	ALLOC_OBJ(b, VMOD_BLOB_MAGIC);
+	AN(b);
+	*blobp = b;
+	b->blob.free = NULL;
+	AZ(pthread_mutex_init(&b->lock, NULL));
+
+	va_start(ap, p);
+	len = decode_l_va(dec, p, ap);
+	va_end(ap);
+	if (len == 0) {
+		b->blob.len = 0;
+		b->blob.priv = NULL;
+		return;
+	}
+	assert(len > 0);
+
+	b->blob.priv = malloc(len);
+	if (b->blob.priv == NULL) {
+		VERRNOMEM(ctx, "cannot create blob %s", vcl_name);
+		return;
+	}
+
+	va_start(ap, p);
+	errno = 0;
+	len = func[dec].decode(dec, b->blob.priv, len, -1, p, ap);
+	va_end(ap);
+
+	if (len == -1) {
+		assert(errno == EINVAL);
+		free(b->blob.priv);
+		b->blob.priv = NULL;
+		VERR(ctx, "cannot create blob %s, illegal encoding beginning "
+		    "with \"%s\"", vcl_name, p);
+		return;
+	}
+	if (len == 0) {
+		b->blob.len = 0;
+		free(b->blob.priv);
+		b->blob.priv = NULL;
+		return;
+	}
+	b->blob.len = len;
+}
+
+VCL_BLOB __match_proto__(td_blob_blob_get)
+vmod_blob_get(VRT_CTX, struct vmod_blob_blob *b)
+{
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	CHECK_OBJ_NOTNULL(b, VMOD_BLOB_MAGIC);
+	return &b->blob;
+}
+
+VCL_STRING
+vmod_blob_encode(VRT_CTX, struct vmod_blob_blob *b, VCL_ENUM encs)
+{
+	enum encoding enc = parse_encoding(encs);
+	AENC(enc);
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	CHECK_OBJ_NOTNULL(b, VMOD_BLOB_MAGIC);
+
+	if (b->blob.len == 0)
+		return "";
+
+	if (b->encoding[enc] == NULL) {
+		AZ(pthread_mutex_lock(&b->lock));
+		if (b->encoding[enc] == NULL) {
+			ssize_t len = func[enc].encode_l(b->blob.len);
+
+			assert(len >= 0);
+			if (len == 0)
+				b->encoding[enc] = "";
+			else {
+				b->encoding[enc] = malloc(len);
+				if (b->encoding[enc] == NULL)
+					ERRNOMEM(ctx, "cannot encode");
+				else {
+					const char *s = b->encoding[enc];
+					len =
+						func[enc].encode(
+							enc, (void *) s, len,
+							b->blob.priv,
+							b->blob.len);
+					assert(len >= 0);
+					if (len == 0) {
+						free((void *) s);
+						b->encoding[enc] = "";
+					}
+					else
+						*((char *)s + len) = '\0';
+				}
+			}
+		}
+		AZ(pthread_mutex_unlock(&b->lock));
+	}
+	return b->encoding[enc];
+}
+
+VCL_VOID __match_proto__(td_blob_blob__fini)
+vmod_blob__fini(struct vmod_blob_blob **blobp)
+{
+	struct vmod_blob_blob *b;
+
+	if (blobp == NULL || *blobp == NULL)
+		return;
+
+	b = *blobp;
+	*blobp = NULL;
+	CHECK_OBJ(b, VMOD_BLOB_MAGIC);
+	if (b->blob.priv != NULL) {
+		free(b->blob.priv);
+		b->blob.priv = NULL;
+	}
+	for (int i = 0; i < __MAX_ENCODING; i++)
+		if (b->encoding[i] != NULL && b->encoding[i][0] != '\0') {
+			free((void *) b->encoding[i]);
+			b->encoding[i] = NULL;
+		}
+	AZ(pthread_mutex_destroy(&b->lock));
+	FREE_OBJ(b);
+}
+
+/* Functions */
+
+static inline const char *
+find_nonempty_va(const char *restrict *p, va_list ap)
+{
+	const char *q;
+
+	/* find first non-empty vararg */
+	for (; *p == vrt_magic_string_end || *p == NULL || **p == '\0';
+	     *p = va_arg(ap, const char *))
+		if (*p == vrt_magic_string_end)
+			return (vrt_magic_string_end);
+
+	/* find next non-empty vararg */
+	for (q = va_arg(ap, const char *);
+	     q != vrt_magic_string_end && (q == NULL || *q == '\0');
+	     q = va_arg(ap, const char *))
+		;
+
+	return (q);
+}
+
+/*
+ * special case: we can avoid copying for identity decode if we need to
+ * deal with a single vararg only - in which case we just have the blob
+ * point to the input string
+ */
+static VCL_BLOB
+decode_id_inplace(struct vmod_priv *b, VCL_INT n, const char *restrict p,
+		  va_list ap) {
+	const char *q;
+	int l;
+
+	if (n == 0)
+		return null_blob;
+
+	q = find_nonempty_va(&p, ap);
+
+	if (p == vrt_magic_string_end)
+		return null_blob;
+
+	if (q == vrt_magic_string_end) {
+		/* can use in-place decode */
+		l = strlen(p);
+		if (n > 0 && n < l)
+			l = n;
+		b->priv = (char *)p;
+		b->len = l;
+		b->free = NULL;
+		return (b);
+	}
+
+	if (n == -1)
+		return NULL;
+	l = strlen(p);
+	if (n > l)
+		return NULL;
+	b->priv = (char *)p;
+	b->len = n;
+	b->free = NULL;
+
+	return (b);
+}
+
+static VCL_BLOB
+decode(VRT_CTX, VCL_INT n, VCL_ENUM decs,
+       const char *restrict const p, va_list ap) {
+	enum encoding dec = parse_encoding(decs);
+	struct wb_s wb;
+	struct vmod_priv *b;
+	char *buf;
+	uintptr_t snap;
+	ssize_t len;
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	AENC(dec);
+	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
+
+	snap = WS_Snapshot(ctx->ws);
+	if ((b = WS_Alloc(ctx->ws, sizeof(struct vmod_priv))) == NULL) {
+		ERRNOMEM(ctx, "cannot decode");
+		return NULL;
+	}
+
+	if (dec == IDENTITY) {
+		va_list aq;
+		va_copy(aq, ap);
+		const struct vmod_priv *bb = decode_id_inplace(b, n, p, aq);
+		va_end(aq);
+		if (bb == null_blob)
+			WS_Reset(ctx->ws, snap);
+		if (bb == b)
+			return bb;
+	}
+
+	if (wb_create(ctx->ws, &wb) == NULL) {
+		WS_Reset(ctx->ws, snap);
+		ERRNOMEM(ctx, "cannot decode");
+		return NULL;
+	}
+	buf = wb_buf(&wb);
+
+	errno = 0;
+	len = func[dec].decode(dec, buf, wb_space(&wb), n, p, ap);
+
+	if (len == -1) {
+		err_decode(ctx, p);
+		wb_reset(&wb);
+		WS_Reset(ctx->ws, snap);
+		return NULL;
+	}
+	if (len == 0) {
+		wb_reset(&wb);
+		WS_Reset(ctx->ws, snap);
+		return null_blob;
+	}
+	wb_advance(&wb, len);
+	WS_ReleaseP(ctx->ws, wb_buf(&wb));
+	b->priv = buf;
+	b->len = len;
+	b->free = NULL;
+	return b;
+}
+
+VCL_BLOB __match_proto__(td_blob_decode)
+vmod_decode(VRT_CTX, VCL_ENUM decs, const char *p, ...)
+{
+	va_list ap;
+	VCL_BLOB r;
+
+	va_start(ap, p);
+	r = decode(ctx, -1, decs, p, ap);
+	va_end(ap);
+
+	return (r);
+}
+
+VCL_BLOB __match_proto__(td_blob_decode_n)
+vmod_decode_n(VRT_CTX, VCL_INT n, VCL_ENUM decs, const char *p, ...)
+{
+	va_list ap;
+	VCL_BLOB r;
+
+	va_start(ap, p);
+	r = decode(ctx, n, decs, p, ap);
+	va_end(ap);
+
+	return (r);
+}
+
+static VCL_STRING
+encode(VRT_CTX, enum encoding enc, VCL_BLOB b)
+{
+	struct wb_s wb;
+	ssize_t len;
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	AENC(enc);
+
+	if (b == NULL)
+		return NULL;
+
+	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
+	if (wb_create(ctx->ws, &wb) == NULL) {
+		ERRNOMEM(ctx, "cannot encode");
+		return NULL;
+	}
+
+	len = func[enc].encode(enc,
+			       wb_buf(&wb), wb_space(&wb), b->priv, b->len);
+
+	if (len == -1) {
+		ERRNOMEM(ctx, "cannot encode");
+		wb_reset(&wb);
+		return NULL;
+	}
+	if (len == 0) {
+		wb_reset(&wb);
+		return "";
+	}
+	wb_advance(&wb, len);
+	return wb_finish(&wb, NULL);
+}
+
+VCL_STRING __match_proto__(td_blob_encode)
+vmod_encode(VRT_CTX, VCL_ENUM encs, VCL_BLOB b)
+{
+	enum encoding enc = parse_encoding(encs);
+	return encode(ctx, enc, b);
+}
+
+static VCL_STRING
+transcode(VRT_CTX, VCL_INT n, VCL_ENUM decs, VCL_ENUM encs,
+	  const char *restrict const p, va_list ap)
+{
+	enum encoding dec = parse_encoding(decs);
+	enum encoding enc = parse_encoding(encs);
+	va_list aq;
+	struct vmod_priv b;
+	VCL_STRING r;
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
+
+	AENC(dec);
+	AENC(enc);
+
+	if (dec == IDENTITY) {
+		va_copy(aq, ap);
+		const struct vmod_priv *bb = decode_id_inplace(&b, n, p, aq);
+		va_end(aq);
+		if (bb != NULL) {
+			r = encode(ctx, enc, bb);
+			return (r);
+		}
+	}
+
+	/*
+	 * Allocate space for the decoded blob on the stack
+	 * ignoring the limitation imposed by n
+	 */
+	va_copy(aq, ap);
+	size_t l = decode_l_va(dec, p, aq);
+	va_end(aq);
+	if (l == 0)
+		return "";
+	/* XXX: handle stack overflow? */
+	char buf[l];
+	b.free = NULL;
+	b.priv = buf;
+
+	errno = 0;
+	va_copy(aq, ap);
+	b.len = func[dec].decode(dec, buf, l, n, p, aq);
+	va_end(aq);
+
+	if (b.len == -1) {
+		err_decode(ctx, p);
+		return NULL;
+	}
+
+	/*
+	 * If the encoding and decoding are the same, and the decoding was
+	 * legal, just return the string, if there was only one in the
+	 * STRING_LIST, or else the concatenated string.
+	 */
+	if (n == -1 && enc == dec) {
+		const char *q, *pp = p;
+		va_copy(aq, ap);
+		q = find_nonempty_va(&pp, ap);
+		va_end(aq);
+
+		if (pp == vrt_magic_string_end)
+			return "";
+
+		if (q == vrt_magic_string_end)
+			return pp;
+
+		r = VRT_String(ctx->ws, NULL, p, ap);
+		return r;
+	}
+
+	r = encode(ctx, enc, &b);
+	return (r);
+}
+
+VCL_STRING __match_proto__(td_blob_transcode)
+vmod_transcode(VRT_CTX, VCL_ENUM decs, VCL_ENUM encs,
+	       const char *p, ...)
+{
+	va_list ap;
+	VCL_STRING r;
+
+	va_start(ap, p);
+	r = transcode(ctx, -1, decs, encs, p, ap);
+	va_end(ap);
+
+	return (r);
+}
+
+VCL_STRING __match_proto__(td_blob_transcode_n)
+vmod_transcode_n(VRT_CTX, VCL_INT n, VCL_ENUM decs, VCL_ENUM encs,
+		 const char *p, ...)
+{
+	va_list ap;
+	VCL_STRING r;
+
+	va_start(ap, p);
+	r = transcode(ctx, n, decs, encs, p, ap);
+	va_end(ap);
+
+	return (r);
+}
diff --git a/lib/libvmod_blob/vmod_blob.h b/lib/libvmod_blob/vmod_blob.h
new file mode 100644
index 0000000..1b809e1
--- /dev/null
+++ b/lib/libvmod_blob/vmod_blob.h
@@ -0,0 +1,127 @@
+/*-
+ * Copyright 2015-2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *          Geoffrey Simmons <geoffrey.simmons at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sys/types.h>
+
+#include "parse_encoding.h"
+
+#define AENC(enc) assert((enc) > _INVALID && (enc) < __MAX_ENCODING)
+
+/*
+ * Length estimate interface
+ */
+typedef
+size_t  len_f(size_t);
+
+/*
+ * General interface for an encoder: encode the data at in of length inlen
+ * into a null-terminated string at buf, and return the length of the
+ * encoding.
+ *
+ * enc: encoding enum (from parse_encoding.h)
+ * buf: destination of the encoded string
+ * buflen: maximum length available at buf
+ * in: source of data to be encoded
+ * inlen: length of data to be encoded
+ *
+ * The regions pointed to by buf and in MUST NOT overlap (this is the
+ * contract imposed by restrict).
+ * An encoder SHALL NOT append the terminating null byte (this must
+ * be done by the caller).
+ *
+ * Returns:
+ * -1, if there is insufficient space at buf, *including* space for the
+ *     terminating null byte
+ * 0, if the length of the encoding is 0 -- the caller should return
+ *    the static constant empty string (literal "")
+ * otherwise, the number of bytes written (note that this does not
+ *            include any terminating null byte)
+ */
+typedef 
+ssize_t encode_f(const enum encoding enc, char *restrict const buf,
+		 const size_t buflen, const char *restrict const in,
+		 const size_t inlen);
+
+/*
+ * General interface for a decoder: decode the concatenation of strings
+ * in p and ap (obtained from a STRING_LIST) into buf, and return the
+ * length of decoded data.
+ *
+ * dec: decoding enum (from parse_encoding.h)
+ * buf: destination of the decoded data
+ * buflen: maximum length available at buf
+ * inlen: maximum length to read or -1 to read up to \0
+ * p, ap: strings obtained from a VCL STRING_LIST
+ *
+ * The regions pointed to by buf and any of the strings in p or ap MUST
+ * NOT overlap (per restrict).
+ * Note that the p,ap list is terminated by vrt_magic_string_end, and
+ * any member of the list may be NULL or empty.
+ *
+ * Returns:
+ * -1, if there is insufficient space at buf, or if the decoding is
+ *     invalid; errno SHALL be set to ENOMEM or EINVALID, respectively
+ * 0, if the length of the decoding is 0 -- the caller should return
+ *    a static constant empty BLOB
+ * otherwise, the number of bytes written
+ */
+typedef
+ssize_t decode_f(const enum encoding dec, char *restrict const buf,
+		 const size_t buflen, const ssize_t inlen,
+		 const char *restrict const p, va_list ap);
+
+/* id.c */
+len_f	 id_encode_l;
+len_f	 id_decode_l;
+
+encode_f id_encode;
+decode_f id_decode;
+
+/* base64.c */
+len_f	 base64_decode_l;
+len_f	 base64nopad_encode_l;
+len_f	 base64_encode_l;
+
+encode_f base64_encode;
+decode_f base64_decode;
+
+ /* hex.c */
+len_f	 hex_encode_l;
+len_f	 hex_decode_l;
+
+encode_f hex_encode;
+decode_f hex_decode;
+
+/* url.c */
+len_f	 url_encode_l;
+len_f	 url_decode_l;
+
+encode_f url_encode;
+decode_f url_decode;
diff --git a/lib/libvmod_blob/wb.c b/lib/libvmod_blob/wb.c
new file mode 100644
index 0000000..343ca59
--- /dev/null
+++ b/lib/libvmod_blob/wb.c
@@ -0,0 +1,74 @@
+/*-
+ * Copyright 2015 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Authors: Nils Goroll <nils.goroll at uplex.de>
+ *
+ * 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.
+ *
+ * write buffer: utility functions to append-write on a varnish workspace
+ */
+
+#include <string.h>
+
+#include "wb.h"
+
+char *
+wb_create(struct ws *ws, struct wb_s *wb)
+{
+	if (WS_Reserve(ws, 0) == 0) {
+		wb->w = NULL;
+		wb->ws = NULL;
+		return NULL;
+	}
+	wb->w = ws->f;
+	wb->ws = ws;
+
+	return wb->w;
+}
+
+void
+wb_reset(struct wb_s *wb)
+{
+	WS_Release(wb->ws, 0);
+	memset(wb, 0, sizeof(*wb));
+}
+
+/*
+ * release varnish workspace
+ *
+ * return start of buffer
+ */
+char *
+wb_finish(struct wb_s *wb, ssize_t *l)
+{
+	char *r = wb->ws->f;
+	assert(wb->ws->r - wb->w > 0);
+	if (l)
+		*l = wb_len(wb);
+
+	*wb->w = '\0';
+	wb->w++;
+
+	/* amount of space used */
+	WS_ReleaseP(wb->ws, wb->w);
+
+	return r;
+}
diff --git a/lib/libvmod_blob/wb.h b/lib/libvmod_blob/wb.h
new file mode 100644
index 0000000..7bd1cbf
--- /dev/null
+++ b/lib/libvmod_blob/wb.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright 2015-2016 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Author: Nils Goroll <nils.goroll at uplex.de>
+ *
+ * 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.
+ *
+ */
+
+#include "cache/cache.h"
+
+struct wb_s {
+	struct ws	*ws; // varnish workspace
+	char		*w;  // current write position
+};
+
+/* return one byte less for the final zero byte */
+static inline const char*
+wb_end(struct wb_s *wb) {
+	return wb->ws->r - 1;
+}
+
+/* return the write position */
+static inline char*
+wb_buf(struct wb_s *wb) {
+	return wb->w;
+}
+
+/* return one byte less for the final zero byte */
+static inline ssize_t
+wb_space(struct wb_s *wb) {
+	ssize_t f = wb->ws->r - wb->w;
+	assert(f > 0);
+	return f - 1;
+}
+
+static inline ssize_t
+wb_len(struct wb_s *wb) {
+	ssize_t l = wb->w - wb->ws->f;
+	assert(l >= 0);
+	return l;
+}
+
+static inline void
+wb_advance(struct wb_s *wb, ssize_t l) {
+	wb->w += l;			// final byte
+	assert(wb->w < wb_end(wb));
+}
+
+char *wb_create(struct ws *ws, struct wb_s *wb);
+void wb_reset(struct wb_s *wb);
+char *wb_finish(struct wb_s *wb, ssize_t *l);
diff --git a/man/Makefile.am b/man/Makefile.am
index 73f6049..2187ff9 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -18,7 +18,8 @@ dist_man_MANS = \
 	vmod_directors.3 \
 	vmod_purge.3 \
 	vmod_std.3 \
-	vmod_vtc.3
+	vmod_vtc.3 \
+	vmod_blob.3
 
 CLEANFILES = $(dist_man_MANS)
 
@@ -97,4 +98,7 @@ vmod_std.3: $(top_builddir)/lib/libvmod_std/vmod_std.man.rst
 vmod_vtc.3: $(top_builddir)/lib/libvmod_vtc/vmod_vtc.man.rst
 	${RST2MAN} $(RST2ANY_FLAGS) $? $@
 
+vmod_blob.3: $(top_builddir)/lib/libvmod_blob/vmod_blob.man.rst
+	${RST2MAN} $(RST2ANY_FLAGS) $? $@
+
 .NOPATH: $(dist_man_MANS)



More information about the varnish-commit mailing list