Commit Diff


commit - 501e489c90eeddec3f29b014864f57e840ea1fa8
commit + c8b74339185123feebb6164b91f500f1930e45ff
blob - 499526a5665c84462c8ba072d688d92c99b2d8b5
blob + b7f05157b697387c34a2aa1d1ea441f66636fa41
--- ChangeLog
+++ ChangeLog
@@ -1,5 +1,7 @@
 2021-01-24  Omar Polo  <op@omarpolo.com>
 
+	* parse.y (vhost): added support for location blocks
+
 	* server.c (send_dir): make the directory index customizable
 
 2021-01-23  Omar Polo  <op@omarpolo.com>
blob - e51768c9d681249ae8c76b238fb5506a8c4e02db
blob + 70ece216eb65b5deaaf34bce45113e2314463af6
--- gmid.1
+++ gmid.1
@@ -181,6 +181,21 @@ parameter will be added in the response.
 Set the directory index file.
 If not specified, it defaults to
 .Pa index.gmi
+.It Ic location Pa path Brq ...
+Specify server configuration rules for a specific location.
+The
+.Pa path
+argument will be matched against the request path with shell globbing
+rules.
+In case of multiple location statements in the same context, the last
+matching location will be put into effect.
+Therefore is advisable to match for a generic paths first and for more
+specific ones later on.
+A
+.Ic location
+section may include most of the server configuration rules
+except
+.Ic cert , Ic key , Ic root , Ic location No and Ic CGI .
 .El
 .Sh CGI
 When CGI scripts are enabled for a directory, a request for an
blob - 3df7bfb4bb72151709bcaed4c2ef346d47d159d1
blob + 139338e0f1048ebcbffaa6b2200d1cf1cc2f7647
--- gmid.h
+++ gmid.h
@@ -49,6 +49,7 @@
 #define MAX_USERS	64
 
 #define HOSTSLEN	64
+#define LOCLEN		32
 
 #define LOGE(c, fmt, ...) logs(LOG_ERR,     c, fmt, __VA_ARGS__)
 #define LOGW(c, fmt, ...) logs(LOG_WARNING, c, fmt, __VA_ARGS__)
@@ -56,16 +57,21 @@
 #define LOGI(c, fmt, ...) logs(LOG_INFO,    c, fmt, __VA_ARGS__)
 #define LOGD(c, fmt, ...) logs(LOG_DEBUG,   c, fmt, __VA_ARGS__)
 
+struct location {
+	char		*match;
+	char		*lang;
+	char		*default_mime;
+	char		*index;
+};
+
 struct vhost {
 	const char	*domain;
 	const char	*cert;
 	const char	*key;
 	const char	*dir;
 	const char	*cgi;
-	char		*lang;
 	int		 dirfd;
-	char		*default_mime;
-	char		*index;
+	struct location	 locations[LOCLEN];
 };
 
 extern struct vhost hosts[HOSTSLEN];
@@ -160,6 +166,7 @@ void		 parse_conf(const char*);
 void		 load_vhosts(struct tls_config*);
 int		 make_socket(int, int);
 int		 listener_main(void);
+void		 init_config(void);
 void		 usage(const char*);
 
 /* provided by lex/yacc */
@@ -175,6 +182,9 @@ void		 load_default_mime(struct mime*);
 const char	*mime(struct vhost*, const char*);
 
 /* server.c */
+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		 check_path(struct client*, const char*, int*);
 void		 open_file(struct pollfd*, struct client*);
 void		 check_for_cgi(char *, char*, struct pollfd*, struct client*);
blob - 0932bc3ba0af1fe2db1b68a5721a3e72b1f918a1
blob + b785796f7bf9fb2390aa06179bed58b0cd242d52
--- lex.l
+++ lex.l
@@ -60,6 +60,7 @@ default		return TDEFAULT;
 type		return TTYPE;
 server		return TSERVER;
 
+location	return TLOCATION;
 cert		return TCERT;
 key		return TKEY;
 root		return TROOT;
blob - 81bc2efba34f8142a4f82c1ce9f273fa6692a15c
blob + f8fdd67e54afb7e57d9e0575b552a98258b264fd
--- mime.c
+++ mime.c
@@ -97,8 +97,7 @@ mime(struct vhost *host, const char *path)
 	const char *def, *ext;
 	struct etm *t;
 
-	if ((def = host->default_mime) == NULL)
-		def = "application/octet-stream";
+	def = vhost_default_mime(host, path);
 
 	if ((ext = path_ext(path)) == NULL)
 		return def;
blob - 2d8f0b855acc9ea0c12421c60a7ab408b4e6b807
blob + e7883a9d1705124c010c75d6c852d3090d06c8a2
--- parse.y
+++ parse.y
@@ -30,6 +30,9 @@
 struct vhost *host = &hosts[0];
 size_t ihost = 0;
 
+struct location *loc = &hosts[0].locations[0];
+size_t iloc = 0;
+
 extern void yyerror(const char*);
 
 %}
@@ -43,7 +46,7 @@ extern void yyerror(const char*);
 }
 
 %token TDAEMON TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TSERVER
-%token TCERT TKEY TROOT TCGI TLANG
+%token TLOCATION TCERT TKEY TROOT TCGI TLANG
 %token TERR
 
 %token <str>	TSTRING
@@ -72,15 +75,20 @@ vhosts		: /* empty */
 		| vhosts vhost
 		;
 
-vhost		: TSERVER TSTRING '{' servopts '}' {
+vhost		: TSERVER TSTRING '{' servopts locations '}' {
+			host->locations[0].match = (char*)"*";
 			host->domain = $2;
 
 			if (host->cert == NULL || host->key == NULL ||
 			    host->dir == NULL)
 				errx(1, "invalid vhost definition: %s", $2);
+
 			if (++ihost == HOSTSLEN)
 				errx(1, "too much vhosts defined");
-                        host++;
+
+			host++;
+			loc = &host->locations[0];
+			iloc = 0;
 		}
 		| error '}'		{ yyerror("error in server directive"); }
 		;
@@ -98,16 +106,36 @@ servopt		: TCERT TSTRING		{ host->cert = $2; }
 			if (*host->cgi == '/')
 				host->cgi++;
 		}
-		| TDEFAULT TTYPE TSTRING {
-			free(host->default_mime);
-			host->default_mime = $3;
+		| locopt
+		;
+
+locations	: /* empty */
+		| locations location
+		;
+
+location	: TLOCATION TSTRING '{' locopts '}' {
+			loc->match = $2;
+			if (++iloc == LOCLEN)
+				errx(1, "too much location rules defined");
+			loc++;
 		}
+		| error '}'
+		;
+
+locopts		: /* empty */
+		| locopts locopt
+		;
+
+locopt		: TDEFAULT TTYPE TSTRING {
+			free(loc->default_mime);
+			loc->default_mime = $3;
+		}
 		| TLANG TSTRING {
-			free(host->lang);
-			host->lang = $2;
+			free(loc->lang);
+			loc->lang = $2;
 		}
 		| TINDEX TSTRING {
-			free(host->index);
-			host->index = $2;
+			free(loc->index);
+			loc->index = $2;
 		}
 		;
blob - 195cb0d91ba2f7d1e27f9ebb15a030869aeaf490
blob + 3c9c572fb8c337ede611f0ea7a7818e5e8b73d03
--- regress/Makefile
+++ regress/Makefile
@@ -35,6 +35,7 @@ testdata: fill-file
 	./sha testdata/index.gmi testdata/index.gmi.sha
 	cp hello slow err invalid serve-bigfile testdata/
 	mkdir testdata/dir
+	cp hello testdata/dir
 	cp testdata/index.gmi testdata/dir/foo.gmi
 
 runtime: testdata cert.pem
blob - 6456746896c5007c4069412fc1389420f767482b
blob + 8cf2afb1ee11f9c4d717dd411e42e23e37d4c7fb
--- regress/runtime
+++ regress/runtime
@@ -191,3 +191,17 @@ echo OK GET /dir/ with custom index
 
 check "should be running"
 quit
+
+config '' 'location "/dir/" { default type "text/plain" index "hello" }'
+checkconf
+run
+
+eq "$(head /dir/hello)"	"20 text/plain"			"Unexpected head for /"
+echo OK GET /dir/hello with location and default type
+
+eq "$(head /dir/)"	"20 text/plain"			"Unexpected head for /dir"
+eq "$(get  /dir/|tail -1)" 'echo "# hello world"'	"Unexpected body for /dir/"
+echo OK GET /dir/ with location and custom index
+
+check "should be running"
+quit
blob - 59760b3804a3f834b19d459916cb04956b9b30f9
blob + 48a7701556fc2f134906f2d8c9ec6f7c73ca42b2
--- server.c
+++ server.c
@@ -29,6 +29,54 @@
 
 int connected_clients;
 
+const char *
+vhost_lang(struct vhost *v, const char *path)
+{
+	struct location *loc;
+	const char *lang = NULL;
+
+	for (loc = v->locations; loc->match != NULL; ++loc) {
+		if (!fnmatch(loc->match, path, 0)) {
+			if (loc->lang != NULL)
+				lang = loc->lang;
+		}
+	}
+
+	return lang;
+}
+
+const char *
+vhost_default_mime(struct vhost *v, const char *path)
+{
+	struct location *loc;
+	const char *default_mime = "application/octet-stream";
+
+	for (loc = v->locations; loc->match != NULL; ++loc) {
+		if (!fnmatch(loc->match, path, 0)) {
+			if (loc->default_mime != NULL)
+				default_mime = loc->default_mime;
+		}
+	}
+
+	return default_mime;
+}
+
+const char *
+vhost_index(struct vhost *v, const char *path)
+{
+	struct location *loc;
+	const char *index = "index.gmi";
+
+	for (loc = v->locations; loc->match != NULL; ++loc) {
+		if (!fnmatch(loc->match, path, 0)) {
+			if (loc->index != NULL)
+				index = loc->index;
+		}
+	}
+
+	return index;
+}
+
 int
 check_path(struct client *c, const char *path, int *fd)
 {
@@ -255,17 +303,20 @@ void
 start_reply(struct pollfd *pfd, struct client *c, int code, const char *meta)
 {
 	char buf[1030];		/* status + ' ' + max reply len + \r\n\0 */
+	const char *lang;
 	size_t len;
 
 	c->code = code;
 	c->meta = meta;
 	c->state = S_INITIALIZING;
 
+	lang = vhost_lang(c->host, c->iri.path);
+
 	snprintf(buf, sizeof(buf), "%d ", code);
 	strlcat(buf, meta, sizeof(buf));
-	if (!strcmp(meta, "text/gemini") && c->host->lang != NULL) {
+	if (!strcmp(meta, "text/gemini") && lang != NULL) {
 		strlcat(buf, "; lang=", sizeof(buf));
-		strlcat(buf, c->host->lang, sizeof(buf));
+		strlcat(buf, lang, sizeof(buf));
 	}
 
 	len = strlcat(buf, "\r\n", sizeof(buf));
@@ -386,7 +437,6 @@ void
 send_dir(struct pollfd *fds, struct client *c)
 {
 	size_t len;
-	const char *index = "index.gmi";
 
 	/* guard against a re-entrant call: open_file -> send_dir ->
 	 * open_file -> send_dir.  This can happen only if:
@@ -416,9 +466,8 @@ send_dir(struct pollfd *fds, struct client *c)
 	if (!ends_with(c->sbuf, "/"))
 		strlcat(c->sbuf, "/", sizeof(c->sbuf));
 
-	if (c->host->index != NULL)
-		index = c->host->index;
-	len = strlcat(c->sbuf, index, sizeof(c->sbuf));
+	len = strlcat(c->sbuf, vhost_index(c->host, c->iri.path),
+	    sizeof(c->sbuf));
 
 	if (len >= sizeof(c->sbuf)) {
 		start_reply(fds, c, TEMP_FAILURE, "internal server error");