commit - daac4a945284dd0f8baa3f35234981f7bc0426da
commit + 6abda252e960943a302f7a945b1d008e463ea316
blob - ad55d54f5df1052a74d68b89d92e5116af8eea50
blob + a9e08cc67cf25cecb7448cd23442cc0e47c60daa
--- ChangeLog
+++ ChangeLog
+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
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
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
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 {
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*);
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
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
*/
#include <err.h>
+#include <stdarg.h>
#include <stdio.h>
#include <string.h>
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);
%}
%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
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
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
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
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*);
}
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
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
return;
}
- open_file(fds, c);
+ if (!apply_block_return(fds, c))
+ open_file(fds, c);
}
static void