[6.0] 8be0403af Add std.fnmatch()

Dridi Boukelmoune dridi.boukelmoune at gmail.com
Thu Aug 16 08:53:23 UTC 2018


commit 8be0403af3bd3786c832df2d26f84eb87f480b2c
Author: Geoff Simmons <geoff at uplex.de>
Date:   Fri Jul 27 17:26:16 2018 +0200

    Add std.fnmatch()
    
    Closes: #2737

diff --git a/bin/varnishtest/tests/m00050.vtc b/bin/varnishtest/tests/m00050.vtc
new file mode 100644
index 000000000..a6578a6bd
--- /dev/null
+++ b/bin/varnishtest/tests/m00050.vtc
@@ -0,0 +1,188 @@
+varnishtest "std.fnmatch()"
+
+varnish v1 -vcl {
+	import std;
+	backend b { .host = "${bad_ip}"; }
+
+	sub vcl_recv {
+		return (synth(200));
+	}
+
+	sub vcl_synth {
+		set resp.http.Match
+			= std.fnmatch(req.http.Pattern, req.http.Subject);
+		set resp.http.Match-Nopathname = std.fnmatch(req.http.Pattern,
+							     req.http.Subject,
+							     pathname=false);
+		set resp.http.Match-Noescape = std.fnmatch(req.http.Pattern,
+							   req.http.Subject,
+							   noescape=true);
+		set resp.http.Match-Period = std.fnmatch(req.http.Pattern,
+							 req.http.Subject,
+							 period=true);
+	}
+} -start
+
+client c1 {
+	txreq -hdr "Pattern: /foo/" -hdr "Subject: /foo/"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/" -hdr "Subject: /bar/"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "false"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/*" -hdr "Subject: /foo/bar"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/bar/*" -hdr "Subject: /foo/bar"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "false"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/?" -hdr "Subject: /foo/b"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/?" -hdr "Subject: /foo/bar"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "false"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/[a-z]" -hdr "Subject: /foo/b"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/[a-z]" -hdr "Subject: /foo/B"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "false"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/[!a-z]" -hdr "Subject: /foo/B"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/*/quux" -hdr "Subject: /foo/bar/baz/quux"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "false"
+	expect resp.http.Match-Nopathname == "true"
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/?/bar" -hdr "Subject: /foo///bar"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "false"
+	expect resp.http.Match-Nopathname == "true"
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: /foo/[a/b]/bar" -hdr "Subject: /foo///bar"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "false"
+	expect resp.http.Match-Nopathname == "true"
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr {Pattern: \\foo} -hdr {Subject: \foo}
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == "false"
+	expect resp.http.Match-Period == resp.http.Match
+
+	txreq -hdr "Pattern: *foo" -hdr "Subject: .foo"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == "false"
+
+	txreq -hdr "Pattern: /*foo" -hdr "Subject: /.foo"
+	rxresp
+	expect resp.status == 200
+	expect resp.http.Match == "true"
+	expect resp.http.Match-Nopathname == resp.http.Match
+	expect resp.http.Match-Noescape == resp.http.Match
+	expect resp.http.Match-Period == "false"
+} -run
+
+server s1 {
+	rxreq
+	txresp
+} -start
+
+varnish v1 -vcl+backend {
+	import std;
+
+	sub vcl_deliver {
+		set resp.http.Match
+			= std.fnmatch(req.http.Pattern, req.http.Subject);
+	}
+}
+
+client c1 {
+	txreq -hdr "Pattern: /foo/"
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+} -run
+
+logexpect l1 -v v1 -d 1 -g vxid -q "VCL_Error" {
+	expect 0 * Begin
+	expect * = VCL_Error {^std\.fnmatch\(\): subject is NULL$}
+	expect * = End
+} -run
+
+logexpect l1 -v v1 -d 0 -g vxid -q "VCL_Error" {
+	expect 0 * Begin
+	expect * = VCL_Error {^std\.fnmatch\(\): pattern is NULL$}
+	expect * = End
+} -start
+
+client c1 {
+	txreq -hdr "Subject: /foo/"
+	rxresp
+	expect resp.status == 503
+	expect resp.reason == "VCL failed"
+} -run
+
+logexpect l1 -wait
diff --git a/configure.ac b/configure.ac
index bda22723e..d56dadf62 100644
--- a/configure.ac
+++ b/configure.ac
@@ -208,6 +208,7 @@ AC_CHECK_HEADERS([sys/vfs.h])
 AC_CHECK_HEADERS([endian.h])
 AC_CHECK_HEADERS([pthread_np.h], [], [], [#include <pthread.h>])
 AC_CHECK_HEADERS([priv.h])
+AC_CHECK_HEADERS([fnmatch.h], [], [AC_MSG_ERROR([fnmatch.h is required])])
 
 # Checks for library functions.
 _VARNISH_CHECK_EXPLICIT_BZERO
@@ -218,6 +219,7 @@ AC_CHECK_FUNCS([closefrom])
 AC_CHECK_FUNCS([sigaltstack])
 AC_CHECK_FUNCS([getpeereid])
 AC_CHECK_FUNCS([getpeerucred])
+AC_CHECK_FUNCS([fnmatch], [], [AC_MSG_ERROR([fnmatch(3) is required])])
 
 save_LIBS="${LIBS}"
 LIBS="${PTHREAD_LIBS}"
diff --git a/lib/libvmod_std/vmod.vcc b/lib/libvmod_std/vmod.vcc
index c8305ae9d..c46d24156 100644
--- a/lib/libvmod_std/vmod.vcc
+++ b/lib/libvmod_std/vmod.vcc
@@ -361,8 +361,70 @@ $Function BOOL syntax(REAL)
 Description
 	Returns the true if VCL version is at least REAL.
 
+$Function BOOL fnmatch(STRING pattern, STRING subject, BOOL pathname=1,
+		       BOOL noescape=0, BOOL period=0)
+
+Description
+	Shell-style pattern matching; returns `true` if *subject*
+	matches *pattern*, where *pattern* may contain wildcard
+	characters such as \* or ?.
+
+	The match is executed by the implementation of `fnmatch(3)` on
+	your system. The rules for pattern matching on most systems
+	include the following:
+
+	* \* matches any sequence of characters
+
+	* ? matches a single character
+
+	* a bracket expresion such as [abc] or [!0-9] is interpreted
+	  as a character class according to the rules of basic regular
+	  expressions (*not* PCRE regexen), except that ! is used for
+	  character class negation instead of ^.
+
+	If *pathname* is `true`, then the forward slash character / is
+	only matched literally, and never matches \*, ? or a bracket
+	expression. Otherwise, / may match one of those patterns.  By
+	default, *pathname* is `true`.
+
+	If *noescape* is `true`, then the backslash character \\ is
+	matched as an ordinary character. Otherwise, \\ is an escape
+	character, and matches the character that follows it in the
+	`pattern`. For example, \\\\ matches \\ when *noescape* is
+	`true`, and \\\\ when `false`. By default, *noescape* is
+	`false`.
+
+	If *period* is `true`, then a leading period character . only
+	matches literally, and never matches \*, ? or a bracket
+	expression. A period is leading if it is the first character
+	in `subject`; if *pathname* is also `true`, then a period that
+	immediately follows a / is also leading (as in "/.").  By
+	default, *period* is `false`.
+
+	`fnmatch()` invokes VCL failure and returns `false` if either
+	of *pattern* or *subject* is NULL -- for example, if an unset
+	header is specified.
+
+Examples
+	| # Matches URLs such as /foo/bar and /foo/bar/baz
+	| if (std.fnmatch("/foo/\*", req.url)) { ... }
+	|
+	| # Matches /foo/bar/quux, but not /foo/bar/baz/quux
+	| if (std.fnmatch("/foo/\*/quux", req.url)) { ... }
+	|
+	| # Matches /foo/bar/quux and /foo/bar/baz/quux
+	| if (std.fnmatch("/foo/\*/quux", req.url, pathname=false)) { ... }
+	|
+	| # Matches /foo/bar, /foo/car and /foo/far
+	| if (std.fnmatch("/foo/?ar", req.url)) { ... }
+	|
+	| # Matches /foo/ followed by a non-digit
+	| if (std.fnmatch("/foo/[!0-9]", req.url)) { ... }
+
+
 SEE ALSO
 ========
 
 * :ref:`varnishd(1)`
 * :ref:`vsl(7)`
+* `fnmatch(3)`
diff --git a/lib/libvmod_std/vmod_std.c b/lib/libvmod_std/vmod_std.c
index 865199372..5d7aa3b8d 100644
--- a/lib/libvmod_std/vmod_std.c
+++ b/lib/libvmod_std/vmod_std.c
@@ -37,6 +37,7 @@
 #include <string.h>
 #include <syslog.h>
 #include <sys/socket.h>
+#include <fnmatch.h>
 
 #include "cache/cache.h"
 
@@ -304,3 +305,28 @@ vmod_syntax(VRT_CTX, VCL_REAL r)
 	 */
 	return (round(r * 10) <= ctx->syntax);
 }
+
+VCL_BOOL v_matchproto_(td_std_fnmatch)
+vmod_fnmatch(VRT_CTX, VCL_STRING pattern, VCL_STRING subject,
+	     VCL_BOOL pathname, VCL_BOOL noescape, VCL_BOOL period)
+{
+	int flags = 0;
+
+	CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
+	if (pattern == NULL) {
+		VRT_fail(ctx, "std.fnmatch(): pattern is NULL");
+		return (0);
+	}
+	if (subject == NULL) {
+		VRT_fail(ctx, "std.fnmatch(): subject is NULL");
+		return (0);
+	}
+
+	if (pathname)
+		flags |= FNM_PATHNAME;
+	if (noescape)
+		flags |= FNM_NOESCAPE;
+	if (period)
+		flags |= FNM_PERIOD;
+	return (fnmatch(pattern, subject, flags) != FNM_NOMATCH);
+}


More information about the varnish-commit mailing list