Commit Diff


commit - daac4a945284dd0f8baa3f35234981f7bc0426da
commit + 6abda252e960943a302f7a945b1d008e463ea316
blob - ad55d54f5df1052a74d68b89d92e5116af8eea50
blob + a9e08cc67cf25cecb7448cd23442cc0e47c60daa
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,7 @@
+2021-02-06  Omar Polo  <op@omarpolo.com>
+
+	* parse.y (locopt): added ``block return'' and ``strip'' options
+
 2021-02-05  Omar Polo  <op@omarpolo.com>
 
 	* iri.c (parse_query): don't %-decode the query part.  This affects the value of QUERY_STRING for CGI scripts too, since that must be %-encoded and we're currently shipping it decoded.
blob - e6249ee72fcecfa99359f878b4e3af0f9bdd5431
blob + 58fa9cb3cf8c9d5473a90fbdd42e5fe025ab5fcc
--- gmid.1
+++ gmid.1
@@ -231,7 +231,45 @@ A
 section may include most of the server configuration rules
 except
 .Ic cert , Ic key , Ic root , Ic location No and Ic cgi .
+.It Ic block Op Ic return Ar code Op Ar meta
+Send a reply and close the connection;
+.Ar code
+is 40
+and
+.Ar meta
+is
+.Dq temporary failure
+by default.
+If
+.Ar code
+is in the 3x range, then
+.Ar meta
+must be provided.
+Inside
+.Ar meta ,
+the following special sequences are replaced:
+.Bl -tag -compact
+.It %%
+is replaced with a single
+.Sq % .
+.It %p
+is replaced with the request path.
+.It %q
+is replaced with the query string of the request.
+.It %P
+is replaced with the server port.
+.It %N
+is replaced with the server name.
 .El
+.It Ic strip Ar number
+Strip
+.Ar number
+components from the beginning of the path.
+It's only considered for the
+.Ar meta
+parameter in the scope of a matching
+.Ic block return .
+.El
 .Sh CGI
 When a request for an executable file matches the
 .Ic cgi
blob - d3e6737dfb1577ed079eee3efa9e6f6cef3531df
blob + 1a1bd59dccdc46a5b479b6f829e38de5cbe506b3
--- gmid.c
+++ gmid.c
@@ -439,6 +439,7 @@ free_config(void)
 			free((char*)l->lang);
 			free((char*)l->default_mime);
 			free((char*)l->index);
+			free((char*)l->block_fmt);
 		}
 	}
 	memset(hosts, 0, sizeof(hosts));
blob - e1e4e87734ed4e4a51b346ed5885aefd62520146
blob + e4dd437546bf7bcc3b012601ce32c7d62a31fbfa
--- gmid.h
+++ gmid.h
@@ -69,6 +69,9 @@ struct location {
 	const char	*default_mime;
 	const char	*index;
 	int		 auto_index; /* 0 auto, -1 off, 1 on */
+	int		 block_code;
+	const char	*block_fmt;
+	int		 strip;
 };
 
 struct vhost {
@@ -208,7 +211,7 @@ extern int yylineno;
 extern int yyparse(void);
 extern int yylex(void);
 
-void		 yyerror(const char*);
+void		 yyerror(const char*, ...);
 int		 parse_portno(const char*);
 void		 parse_conf(const char*);
 
@@ -223,6 +226,8 @@ const char	*vhost_lang(struct vhost*, const char*);
 const char	*vhost_default_mime(struct vhost*, const char*);
 const char	*vhost_index(struct vhost*, const char*);
 int		 vhost_auto_index(struct vhost*, const char*);
+int		 vhost_block_return(struct vhost*, const char*, int*, const char**);
+int		 vhost_strip(struct vhost*, const char*);
 void		 mark_nonblock(int);
 void		 loop(struct tls*, int, int);
 
blob - a2e4bf9dc4c56d491d54fbf72ed83ca3c8c9b566
blob + 04d902a39bbc4498fe9dc0cc29665682640ff3c1
--- lex.l
+++ lex.l
@@ -69,6 +69,9 @@ cgi		return TCGI;
 lang		return TLANG;
 index		return TINDEX;
 auto		return TAUTO;
+strip		return TSTRIP;
+block		return TBLOCK;
+return		return TRETURN;
 
 [{}]		return *yytext;
 
blob - 4e1bc4b71aae42603160f9476f38c704719ff8f0
blob + 77a3558f73c42d12b25629e5b75f251a8426c231
--- parse.y
+++ parse.y
@@ -18,6 +18,7 @@
  */
 
 #include <err.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -36,10 +37,13 @@ size_t iloc;
 int goterror = 0;
 const char *config_path;
 
-void		 yyerror(const char*);
+void		 yyerror(const char*, ...);
 int		 parse_portno(const char*);
 void		 parse_conf(const char*);
 char		*ensure_absolute_path(char*);
+int		 check_block_code(int);
+char		*check_block_fmt(char*);
+int		 check_strip_no(int);
 
 %}
 
@@ -54,6 +58,7 @@ char		*ensure_absolute_path(char*);
 %token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE
 %token TCHROOT TUSER TSERVER
 %token TLOCATION TCERT TKEY TROOT TCGI TLANG TINDEX TAUTO
+%token TSTRIP TBLOCK TRETURN
 %token TERR
 
 %token <str>	TSTRING
@@ -156,15 +161,42 @@ locopt		: TDEFAULT TTYPE TSTRING {
 			loc->index = $2;
 		}
 		| TAUTO TINDEX TBOOL	{ loc->auto_index = $3 ? 1 : -1; }
+		| TBLOCK TRETURN TNUM TSTRING {
+			if (loc->block_fmt != NULL)
+				yyerror("`block' rule specified more than once");
+			loc->block_fmt = check_block_fmt($4);
+			loc->block_code = check_block_code($3);
+		}
+		| TBLOCK TRETURN TNUM {
+			if (loc->block_fmt != NULL)
+				yyerror("`block' rule specified more than once");
+			loc->block_fmt = xstrdup("temporary failure");
+			loc->block_code = check_block_code($3);
+			if ($3 >= 30 && $3 < 40)
+				yyerror("missing `meta' for block return %d", $3);
+		}
+		| TBLOCK {
+			if (loc->block_fmt != NULL)
+				yyerror("`block' rule specified more than once");
+			loc->block_fmt = xstrdup("temporary failure");
+			loc->block_code = 40;
+		}
+		| TSTRIP TNUM		{ loc->strip = check_strip_no($2); }
 		;
 
 %%
 
 void
-yyerror(const char *msg)
+yyerror(const char *msg, ...)
 {
+	va_list ap;
+
 	goterror = 1;
-	fprintf(stderr, "%s:%d: %s\n", config_path, yylineno, msg);
+
+	va_start(ap, msg);
+	fprintf(stderr, "%s:%d: ", config_path, yylineno);
+	vfprintf(stderr, msg, ap);
+	va_end(ap);
 }
 
 int
@@ -204,3 +236,42 @@ ensure_absolute_path(char *path)
 		yyerror("not an absolute path");
 	return path;
 }
+
+int
+check_block_code(int n)
+{
+	if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
+		yyerror("invalid block code %d", n);
+	return n;
+}
+
+char *
+check_block_fmt(char *fmt)
+{
+	char *s;
+
+	for (s = fmt; *s; ++s) {
+		if (*s != '%')
+			continue;
+		switch (*++s) {
+		case '%':
+		case 'p':
+		case 'q':
+		case 'P':
+		case 'N':
+			break;
+		default:
+			yyerror("invalid format specifier %%%c", *s);
+		}
+	}
+
+	return fmt;
+}
+
+int
+check_strip_no(int n)
+{
+	if (n <= 0)
+		yyerror("invalid strip number %d", n);
+	return n;
+}
blob - b114bebd4202f3f980cce8bc36c3499f5db783d9
blob + 96ac135f8e0e6ce4dcfe4e9df6f56c859e6f4447
--- regress/runtime
+++ regress/runtime
@@ -220,3 +220,41 @@ echo OK GET /dir/ with auto index on
 
 check "should be running"
 quit
+
+# test block return and strip
+
+config '' 'location "*" { block }'
+checkconf
+run
+
+eq "$(head /)"		"40 temporary failure"		"Unexpected head for /"
+eq "$(get /)"		""				"Unexpected body for /"
+echo OK GET / with block
+
+eq "$(head /nonexists)"	"40 temporary failure"		"Unexpected head for /nonexists"
+eq "$(get /nonexists)"	""				"Unexpected body for /nonexists"
+echo OK GET /nonexists with block
+
+check "should be running"
+quit
+
+config '' '
+location "/dir" {
+	strip 1
+	block return 40 "%% %p %q %P %N test"
+}
+location "*" {
+	strip 99
+	block return 40 "%% %p %q %P %N test"
+}'
+checkconf
+run
+
+eq "$(head /dir/foo.gmi)"	"40 % /foo.gmi  10965 localhost test"
+echo OK GET /dir/foo.gmi with strip and block
+
+eq "$(head /bigfile)"		"40 %   10965 localhost test"
+echo OK GET /bigfile with strip and block
+
+check "should be running"
+quit
blob - f5b62e2c4b7176b304e89ad5b3c6f74f41015609
blob + a25f0a7c69f8d9de6e633204678c9a840a9e81b2
--- server.c
+++ server.c
@@ -35,6 +35,7 @@ static void	 open_file(struct pollfd*, struct client*)
 static void	 load_file(struct pollfd*, struct client*);
 static void	 check_for_cgi(struct pollfd*, struct client*);
 static void	 handle_handshake(struct pollfd*, struct client*);
+static int	 apply_block_return(struct pollfd*, struct client*);
 static void	 handle_open_conn(struct pollfd*, struct client*);
 static void	 start_reply(struct pollfd*, struct client*, int, const char*);
 static void	 handle_start_reply(struct pollfd*, struct client*);
@@ -129,6 +130,47 @@ vhost_auto_index(struct vhost *v, const char *path)
 	}
 
 	return v->locations[0].auto_index == 1;
+}
+
+int
+vhost_block_return(struct vhost *v, const char *path, int *code, const char **fmt)
+{
+	struct location *loc;
+
+	if (v == NULL || path == NULL)
+		return 0;
+
+	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+		if (!fnmatch(loc->match, path, 0)) {
+			if (loc->block_code != 0) {
+				*code = loc->block_code;
+				*fmt = loc->block_fmt;
+				return 1;
+			}
+		}
+	}
+
+	*code = v->locations[0].block_code;
+	*fmt = v->locations[0].block_fmt;
+	return v->locations[0].block_code != 0;
+}
+
+int
+vhost_strip(struct vhost *v, const char *path)
+{
+	struct location *loc;
+
+	if (v == NULL || path == NULL)
+		return 0;
+
+	for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
+		if (!fnmatch(loc->match, path, 0)) {
+			if (loc->strip != 0)
+				return loc->strip;
+		}
+	}
+
+	return v->locations[0].strip;
 }
 
 static int
@@ -331,6 +373,73 @@ err:
 		strncpy(c->req, "null", sizeof(c->req));
 
 	start_reply(fds, c, BAD_REQUEST, "Wrong/malformed host or missing SNI");
+}
+
+/* 1 if a matching `block return' (and apply it), 0 otherwise */
+static int
+apply_block_return(struct pollfd *fds, struct client *c)
+{
+	char *t, *path, buf[32];
+	const char *fmt;
+	int strip, code;
+	size_t i;
+
+	if (!vhost_block_return(c->host, c->iri.path, &code, &fmt))
+		return 0;
+
+	strip = vhost_strip(c->host, c->iri.path);
+	path = c->iri.path;
+	while (strip > 0) {
+		if ((t = strchr(path, '/')) == NULL) {
+			path = strchr(path, '\0');
+			break;
+		}
+		path = t;
+		strip--;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	for (i = 0; *fmt; ++fmt) {
+		if (i == sizeof(buf)-1 || *fmt == '%') {
+			strlcat(c->sbuf, buf, sizeof(c->sbuf));
+			memset(buf, 0, sizeof(buf));
+			i = 0;
+		}
+
+		if (*fmt != '%') {
+			buf[i++] = *fmt;
+			continue;
+		}
+
+		switch (*++fmt) {
+		case '%':
+			strlcat(c->sbuf, "%", sizeof(c->sbuf));
+			break;
+		case 'p':
+			strlcat(c->sbuf, path, sizeof(c->sbuf));
+			break;
+		case 'q':
+			strlcat(c->sbuf, c->iri.query, sizeof(c->sbuf));
+			break;
+		case 'P':
+			snprintf(buf, sizeof(buf), "%d", conf.port);
+			strlcat(c->sbuf, buf, sizeof(c->sbuf));
+			memset(buf, 0, sizeof(buf));
+			break;
+		case 'N':
+			strlcat(c->sbuf, c->domain, sizeof(c->sbuf));
+			break;
+		default:
+			fatal("%s: unknown fmt specifier %c",
+			    __func__, *fmt);
+		}
+	}
+
+	if (i != 0)
+		strlcat(c->sbuf, buf, sizeof(c->sbuf));
+
+	start_reply(fds, c, code, c->sbuf);
+	return 1;
 }
 
 static void
@@ -372,7 +481,8 @@ handle_open_conn(struct pollfd *fds, struct client *c)
 		return;
 	}
 
-	open_file(fds, c);
+	if (!apply_block_return(fds, c))
+		open_file(fds, c);
 }
 
 static void