├── .gitignore ├── COPYING ├── LICENSE ├── Makefile.am ├── README.rst ├── autogen.sh ├── configure.ac ├── m4 └── PLACEHOLDER └── src ├── Makefile.am ├── tests └── urlcode01.vtc ├── vmod_urlcode.c └── vmod_urlcode.vcc /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.in 3 | .deps/ 4 | .libs/ 5 | *.o 6 | *.lo 7 | *.la 8 | *~ 9 | 10 | /aclocal.m4 11 | /autom4te.cache/ 12 | /compile 13 | /config.guess 14 | /config.h 15 | /config.h.in 16 | /config.log 17 | /config.status 18 | /config.sub 19 | /configure 20 | /depcomp 21 | /install-sh 22 | /libtool 23 | /ltmain.sh 24 | /missing 25 | /stamp-h1 26 | /m4/ 27 | 28 | /src/vcc_if.c 29 | /src/vcc_if.h 30 | /vmod_urlcode.3 31 | /nbproject/private/ 32 | 33 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Varnish Software AS 2 | ... 3 | See LICENSE for details. 4 | 5 | You're free to use and distribute this under terms in the 6 | LICENSE. Please add your relevant copyright statements. 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions 3 | are met: 4 | 1. Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright 7 | notice, this list of conditions and the following disclaimer in the 8 | documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 11 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 12 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 13 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 14 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 15 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 16 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 18 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 19 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 20 | SUCH DAMAGE. 21 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | 3 | SUBDIRS = src 4 | 5 | EXTRA_DIST = README.rst 6 | 7 | dist_man_MANS = vmod_urlcode.3 8 | MAINTAINERCLEANFILES = $(dist_man_MANS) 9 | 10 | vmod_urlcode.3: README.rst 11 | if HAVE_RST2MAN 12 | ${RST2MAN} README.rst $@ 13 | else 14 | @echo "========================================" 15 | @echo "You need rst2man installed to make dist" 16 | @echo "========================================" 17 | @false 18 | endif 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | vmod_urlcode 3 | ============ 4 | 5 | ---------------------- 6 | Varnish URLcode Module 7 | ---------------------- 8 | 9 | :Author: Rogier "DocWilco" Mulhuijzen 10 | :Date: 2012-02-15 11 | :Version: 1.0 12 | :Manual section: 3 13 | 14 | SYNOPSIS 15 | ======== 16 | 17 | :: 18 | 19 | import urlcode; 20 | urlcode.encode(); 21 | urlcode.decode(); 22 | 23 | DESCRIPTION 24 | =========== 25 | 26 | Varnish Module (vmod) for encoding or decoding to/from "percent encoding" as 27 | per RFC3986. 28 | 29 | For backward compatibility, a + will be decoded to a space. 30 | 31 | FUNCTIONS 32 | ========= 33 | 34 | Example VCL:: 35 | 36 | backend foo { ... }; 37 | 38 | import urlcode; 39 | 40 | sub vcl_recv { 41 | set req.url = "/example?url=" + urlcode.encode("http://" + 42 | req.http.host + req.url); 43 | } 44 | 45 | encode 46 | ------ 47 | 48 | Prototype 49 | :: 50 | 51 | urlcode.encode(STRING_LIST input) 52 | 53 | Return value 54 | STRING 55 | Description 56 | Returns a percent encoded version of input. Or NULL if sess_workspace 57 | does not have enough space for the operation. 58 | Example 59 | :: 60 | 61 | set resp.http.foo = urlcode.encode("hello world!"); 62 | 63 | decode 64 | ------ 65 | 66 | Prototype 67 | :: 68 | 69 | urlcode.decode(STRING_LIST input) 70 | 71 | Return value 72 | STRING 73 | Description 74 | Returns a percent decoded version of input. Or NULL if either the 75 | encoding is invalid or sess_workspace does not have enough space for 76 | the operation. 77 | Example 78 | :: 79 | 80 | set resp.http.foo = urlcode.decode("hello%20world%21"); 81 | 82 | 83 | INSTALLATION 84 | ============ 85 | 86 | The source tree is based on autotools to configure the building, and 87 | does also have the necessary bits in place to do functional unit tests 88 | using the varnishtest tool. 89 | 90 | Usage:: 91 | 92 | ./configure VARNISHSRC=DIR [VMODDIR=DIR] 93 | 94 | `VARNISHSRC` is the directory of the Varnish source tree for which to 95 | compile your vmod. Both the `VARNISHSRC` and `VARNISHSRC/include` 96 | will be added to the include search paths for your module. 97 | 98 | Optionally you can also set the vmod install directory by adding 99 | `VMODDIR=DIR` (defaults to the pkg-config discovered directory from your 100 | Varnish installation). 101 | 102 | Make targets: 103 | 104 | * make - builds the vmod 105 | * make install - installs your vmod in `VMODDIR` 106 | * make check - runs the unit tests in ``src/tests/*.vtc`` 107 | 108 | 109 | HISTORY 110 | ======= 111 | 112 | Version 1.0: Initial version. 113 | 114 | COPYRIGHT 115 | ========= 116 | 117 | This document is licensed under the same license as the 118 | libvmod-urlcode project. See LICENSE for details. 119 | 120 | * Copyright (c) 2012 Fastly Inc. 121 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | warn() { 4 | echo "WARNING: $@" 1>&2 5 | } 6 | 7 | case `uname -s` in 8 | Darwin) 9 | LIBTOOLIZE=glibtoolize 10 | ;; 11 | FreeBSD) 12 | LIBTOOLIZE=libtoolize 13 | ;; 14 | Linux) 15 | LIBTOOLIZE=libtoolize 16 | ;; 17 | SunOS) 18 | LIBTOOLIZE=libtoolize 19 | ;; 20 | *) 21 | warn "unrecognized platform:" `uname -s` 22 | LIBTOOLIZE=libtoolize 23 | esac 24 | 25 | automake_version=`automake --version | tr ' ' '\n' | egrep '^[0-9]\.[0-9a-z.-]+'` 26 | if [ -z "$automake_version" ] ; then 27 | warn "unable to determine automake version" 28 | else 29 | case $automake_version in 30 | 0.*|1.[0-8]|1.[0-8][.-]*) 31 | warn "automake ($automake_version) detected; 1.9 or newer recommended" 32 | ;; 33 | *) 34 | ;; 35 | esac 36 | fi 37 | 38 | set -ex 39 | 40 | aclocal -I m4 41 | $LIBTOOLIZE --copy --force 42 | autoheader 43 | automake --add-missing --copy --foreign 44 | autoconf 45 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ(2.59) 2 | AC_COPYRIGHT([Copyright (c) 2011 Varnish Software AS]) 3 | AC_INIT([libvmod-urlcode], [master]) 4 | AC_CONFIG_MACRO_DIR([m4]) 5 | AC_CONFIG_SRCDIR(src/vmod_urlcode.vcc) 6 | AM_CONFIG_HEADER(config.h) 7 | 8 | AC_CANONICAL_SYSTEM 9 | AC_LANG(C) 10 | 11 | AM_INIT_AUTOMAKE([foreign]) 12 | 13 | AC_GNU_SOURCE 14 | AC_PROG_CC 15 | AC_PROG_CC_STDC 16 | if test "x$ac_cv_prog_cc_c99" = xno; then 17 | AC_MSG_ERROR([Could not find a C99 compatible compiler]) 18 | fi 19 | AC_PROG_CPP 20 | 21 | AC_PROG_INSTALL 22 | AC_PROG_LIBTOOL 23 | AC_PROG_MAKE_SET 24 | 25 | # Check for rst utilities 26 | AC_CHECK_PROGS(RST2MAN, [rst2man rst2man.py], "no") 27 | if test "x$RST2MAN" = "xno"; then 28 | AC_MSG_WARN([rst2man not found - not building man pages]) 29 | fi 30 | AM_CONDITIONAL(HAVE_RST2MAN, [test "x$RST2MAN" != "xno"]) 31 | 32 | # Check for pkg-config 33 | PKG_PROG_PKG_CONFIG 34 | 35 | # Checks for header files. 36 | AC_HEADER_STDC 37 | AC_CHECK_HEADERS([sys/stdlib.h]) 38 | 39 | # Check for python 40 | AC_CHECK_PROGS(PYTHON, [python3 python3.1 python3.2 python2.7 python2.6 python2.5 python2 python], [AC_MSG_ERROR([Python is needed to build this vmod, please install python.])]) 41 | 42 | # Varnish include files tree 43 | VARNISH_VMOD_INCLUDES 44 | VARNISH_VMOD_DIR 45 | VARNISH_VMODTOOL 46 | 47 | AC_PATH_PROG([VARNISHTEST], [varnishtest]) 48 | AC_PATH_PROG([VARNISHD], [varnishd]) 49 | 50 | AC_CONFIG_FILES([ 51 | Makefile 52 | src/Makefile 53 | ]) 54 | AC_OUTPUT 55 | -------------------------------------------------------------------------------- /m4/PLACEHOLDER: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CPPFLAGS = @VMOD_INCLUDES@ 2 | 3 | vmoddir = @VMOD_DIR@ 4 | vmod_LTLIBRARIES = libvmod_urlcode.la 5 | 6 | libvmod_urlcode_la_LDFLAGS = -module -export-dynamic -avoid-version 7 | 8 | libvmod_urlcode_la_SOURCES = \ 9 | vcc_if.c \ 10 | vcc_if.h \ 11 | vmod_urlcode.c 12 | 13 | vcc_if.c vcc_if.h: @VMODTOOL@ $(top_srcdir)/src/vmod_urlcode.vcc 14 | @VMODTOOL@ $(top_srcdir)/src/vmod_urlcode.vcc 15 | 16 | VMOD_TESTS = tests/*.vtc 17 | .PHONY: $(VMOD_TESTS) 18 | 19 | tests/*.vtc: 20 | @VARNISHTEST@ -Dvarnishd=@VARNISHD@ -Dvmod_topbuild=$(abs_top_builddir) $@ 21 | 22 | check: $(VMOD_TESTS) 23 | 24 | EXTRA_DIST = \ 25 | vmod_urlcode.vcc \ 26 | $(VMOD_TESTS) 27 | 28 | CLEANFILES = $(builddir)/vcc_if.c $(builddir)/vcc_if.h 29 | -------------------------------------------------------------------------------- /src/tests/urlcode01.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test urlcode vmod" 2 | 3 | server s1 { 4 | rxreq 5 | txresp -hdr "Foo1: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_~" \ 6 | -hdr "Foo2: `!@#$%^&*()+={}[]:;'\\|<>,?/ \"" \ 7 | -hdr "Foo3: hello" \ 8 | -hdr "Foo4: world" \ 9 | -hdr "Foo5: %2" \ 10 | -hdr "Foo6: %" \ 11 | -hdr "Foo7: +" \ 12 | -hdr "Foo8: \xf6\xff" \ 13 | -hdr "Connection: close" \ 14 | -body "Hi!\n" 15 | } -start 16 | 17 | varnish v1 -vcl+backend { 18 | import urlcode from "${vmod_topbuild}/src/.libs/libvmod_urlcode.so"; 19 | 20 | sub vcl_backend_response { 21 | set beresp.http.Bar1 = urlcode.encode(beresp.http.Foo1); 22 | set beresp.http.Baz1 = urlcode.decode(beresp.http.Bar1); 23 | set beresp.http.Bar2 = urlcode.encode(beresp.http.Foo2); 24 | set beresp.http.Baz2 = urlcode.decode(beresp.http.Bar2); 25 | set beresp.http.Bar3 = urlcode.encode(beresp.http.Foo3 + " " 26 | + beresp.http.Foo4) + "!"; 27 | set beresp.http.Baz3 = urlcode.decode("hello" + beresp.http.Foo5 28 | + beresp.http.nonexistant + "0world") + "!"; 29 | set beresp.http.Baz4 = urlcode.decode("hello" + beresp.http.Foo6 30 | + beresp.http.nonexistant + "20world") + "!"; 31 | set beresp.http.Baz5 = urlcode.decode("hello" + beresp.http.Foo7 32 | + beresp.http.nonexistant + "world") + "!"; 33 | set beresp.http.Bar6 = urlcode.encode("hello " 34 | + beresp.http.nonexistant + "world") + "!"; 35 | set beresp.http.Baz6 = urlcode.decode({"hello%20"} 36 | + beresp.http.nonexistant + "world") + "!"; 37 | set beresp.http.Baz7 = urlcode.decode(beresp.http.nonexistant) + "!"; 38 | set beresp.http.Bar8 = urlcode.encode(beresp.http.Foo8); 39 | } 40 | } -start 41 | 42 | client c1 { 43 | txreq -url "/" 44 | rxresp 45 | expect resp.status == 200 46 | expect resp.http.X-Varnish == "1001" 47 | expect resp.http.foo1 == "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_~" 48 | expect resp.http.bar1 == "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_~" 49 | expect resp.http.baz1 == "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_~" 50 | expect resp.http.foo2 == "`!@#$%^&*()+={}[]:;'\\|<>,?/ \"" 51 | expect resp.http.bar2 == "%60%21%40%23%24%25%5E%26%2A%28%29%2B%3D%7B%7D%5B%5D%3A%3B%27%5C%7C%3C%3E%2C%3F%2F%20%22" 52 | expect resp.http.baz2 == "`!@#$%^&*()+={}[]:;'\\|<>,?/ \"" 53 | expect resp.http.bar3 == "hello%20world!" 54 | expect resp.http.baz3 == "hello world!" 55 | expect resp.http.baz4 == "hello world!" 56 | expect resp.http.baz5 == "hello world!" 57 | expect resp.http.bar6 == "hello%20world!" 58 | expect resp.http.baz6 == "hello world!" 59 | expect resp.http.baz7 == "!" 60 | expect resp.http.bar8 == "%F6%FF" 61 | } -run 62 | -------------------------------------------------------------------------------- /src/vmod_urlcode.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "vrt.h" 4 | #include "cache/cache.h" 5 | 6 | #include "vcc_if.h" 7 | 8 | static char hexchars[] = "0123456789ABCDEF"; 9 | #define visalpha(c) \ 10 | ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) 11 | #define visalnum(c) \ 12 | ((c >= '0' && c <= '9') || visalpha(c)) 13 | 14 | const char * 15 | vmod_encode(const struct vrt_ctx *ctx, const char *str, ...) 16 | { 17 | char *b, *e; 18 | unsigned u; 19 | va_list ap; 20 | 21 | CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); 22 | CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC); 23 | u = WS_Reserve(ctx->ws, 0); 24 | e = b = ctx->ws->f; 25 | e += u; 26 | va_start(ap, str); 27 | while (b < e && str != vrt_magic_string_end) { 28 | while (b < e && str && *str) { 29 | if (visalnum((int) *str) || *str == '-' || *str == '.' 30 | || *str == '_' || *str == '~') { /* RFC3986 2.3 */ 31 | *b++ = *str++; 32 | } else if (b + 4 >= e) { /* % hex hex NULL */ 33 | b = e; /* not enough space */ 34 | } else { 35 | *b++ = '%'; 36 | unsigned char foo = *str; 37 | *b++ = hexchars[foo >> 4]; 38 | *b++ = hexchars[*str & 15]; 39 | str++; 40 | } 41 | } 42 | str = va_arg(ap, const char *); 43 | } 44 | if (b < e) 45 | *b = '\0'; 46 | b++; 47 | if (b > e) { 48 | WS_Release(ctx->ws, 0); 49 | return (NULL); 50 | } else { 51 | e = b; 52 | b = ctx->ws->f; 53 | WS_Release(ctx->ws, e - b); 54 | return (b); 55 | } 56 | } 57 | 58 | static inline int 59 | vmod_hex_to_int(char c) 60 | { 61 | if (c >= '0' && c <= '9') 62 | return (c - '0'); 63 | else if (c >= 'A' && c <= 'F') 64 | return (c - 'A' + 10); 65 | else if (c >= 'a' && c <= 'f') 66 | return (c - 'a' + 10); 67 | return (-1); 68 | } 69 | 70 | const char * 71 | vmod_decode(const struct vrt_ctx *ctx, const char *str, ...) 72 | { 73 | char *b, *e; 74 | int h, l; 75 | unsigned u; 76 | va_list ap; 77 | int percent = 0; 78 | 79 | CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); 80 | CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC); 81 | u = WS_Reserve(ctx->ws, 0); 82 | e = b = ctx->ws->f; 83 | e += u; 84 | va_start(ap, str); 85 | while (b < e && str != vrt_magic_string_end) { 86 | if (str == NULL || *str == '\0') { 87 | str = va_arg(ap, const char *); 88 | } else if (percent == 0) { 89 | switch(*str) { 90 | case '%': 91 | percent = 1; 92 | str++; 93 | break; 94 | case '+': 95 | *b++ = ' '; 96 | str++; 97 | break; 98 | default: 99 | *b++ = *str++; 100 | break; 101 | } 102 | } else if (percent == 1) { 103 | h = vmod_hex_to_int(*str++); 104 | if (h < 0) 105 | b = e; 106 | percent = 2; 107 | } else if (percent == 2) { 108 | l = vmod_hex_to_int(*str++); 109 | if (l < 0) 110 | b = e; 111 | *b++ = (char) ((h << 4) | l); 112 | percent = 0; 113 | } 114 | } 115 | if (b < e) 116 | *b = '\0'; 117 | b++; 118 | if (b > e) { 119 | WS_Release(ctx->ws, 0); 120 | return (NULL); 121 | } else { 122 | e = b; 123 | b = ctx->ws->f; 124 | WS_Release(ctx->ws, e - b); 125 | return (b); 126 | } 127 | } 128 | 129 | -------------------------------------------------------------------------------- /src/vmod_urlcode.vcc: -------------------------------------------------------------------------------- 1 | $Module urlcode 3 urlencode/urldecode functions vmod 2 | $Function STRING encode(STRING_LIST) 3 | $Function STRING decode(STRING_LIST) 4 | --------------------------------------------------------------------------------