"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
fix gotsysd test failures with new gotwebd redirect
To:
gameoftrees@openbsd.org
Date:
Sun, 19 Jul 2026 16:46:41 +0200

Download raw body.

Thread
Restore gotwebd's ability to redirect with HTTP status codes.

This is needed to keep gotsysd tests passing because w3m does not work as
required in our test scripts when the HTML body refresh redirect method
is used.

The new page refresh redirect way remains the default.
The non-default behaviour is used by our tests, and might be useful
to someone, somewhere, some day.

ok?
 
M  gotsysd/gotsysd.conf.5                                 |  42+  0-
M  gotsysd/gotsysd.h                                      |   6+  0-
M  gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c  |  21+  0-
M  gotsysd/parse.y                                        |  12+  0-
M  gotwebd/auth.c                                         |  14+  3-
M  gotwebd/config.c                                       |   2+  0-
M  gotwebd/gotwebd.conf.5                                 |  42+  0-
M  gotwebd/gotwebd.h                                      |  11+  0-
M  gotwebd/parse.y                                        |  27+  0-
M  lib/gotsys_imsg.c                                      |   1+  0-
M  regress/gotsysd/Makefile                               |   1+  0-

11 files changed, 179 insertions(+), 3 deletions(-)

commit - 1c54524a32d31c930c0502e9bd03f5a87b0fb4a1
commit + 8bdd1ffe7e4f0a504efd7053a3b7cd2769e87681
blob - 4dfbc9ea75ad91862e229537a96391baaaf267f8
blob + 33ce245cd0a169dc5496def1510606103b22d598
--- gotsysd/gotsysd.conf.5
+++ gotsysd/gotsysd.conf.5
@@ -359,6 +359,48 @@ weblogin command via other means.
 .It Ic login hint port Ar number
 Sets the SSH port number displayed in login hints which are shown on the error
 page if authentication has failed.
+.It Ic redirect_method Ar method
+Set the
+.Ar method
+used by
+.Xr gotwebd 8
+when redirecting clients to another page, such as from the login page
+to the summary page.
+.Pp
+The available methods are:
+.Bl -tag -width Ds
+.It page_refresh
+Send HTTP response status code 200 (success) and serve a
+.Dv meta  
+tag in the HTML response body with the
+.Dv http-equiv
+attribute set to
+.Dq refresh
+and a new URL in the
+.Dv url
+attribute.
+.Pp
+This method is the default and provides good browser compatibility overall.
+.pp
+This method can get in the way of scripts trying to navigate through a
+.Xr gotsh 1
+.Cm weblogin
+link, which is usually desirable, but not always.
+.It status_code
+Send HTTP response status code 307 (redirect) with a target URL in HTTP
+headers and with an empty response body.
+.Pp
+This method generally works as expected.
+In some situations browsers fail to follow the redirect and display the
+redirecting page instead.
+For example,
+.Xr gotsh 1
+.Cm weblogin 
+links have been known to fail loading the summary page if the redirecting
+link is opened by an external application asking Mozilla Firefox to open
+the login link, even though the redirect is followed as expected when
+the login link is visited via the browser's address bar.
+.El
 .It Ic user Ar name
 Set the
 .Ar user
blob - 51e57ac6df00893e2bed7ba77f9e4329f3ee2a6b
blob + 5f4a154bb14d721d0a35bc1904fe07e2d7e1c374
--- gotsysd/gotsysd.h
+++ gotsysd/gotsysd.h
@@ -107,6 +107,11 @@ enum gotsysd_web_auth_config {
 	GOTSYSD_WEB_AUTH_INSECURE	= 0x0f7f7f00
 };
 
+enum gotsysd_web_redirect_method {
+	GOTSYSD_WEB_REDIRECT_METHOD_PAGE_REFRESH,
+	GOTSYSD_WEB_REDIRECT_METHOD_STATUS_CODE
+};
+
 enum gotsysd_web_address_family {
 	GOTSYSD_LISTEN_ADDR_UNIX = 0,
 	GOTSYSD_LISTEN_ADDR_INET,
@@ -168,6 +173,7 @@ struct gotsysd_web_config {
 	char login_hint_user[MAX_IDENTIFIER_SIZE];
 	char login_hint_port[8];
 
+	enum gotsysd_web_redirect_method redirect_method;
 	enum gotsysd_web_auth_config auth_config;
 	struct gotsysd_web_addresslist listen_addrs;
 	struct gotsysd_web_serverlist servers;
blob - 3d4d01cfd032b4501d15f17cf275da2d549424a9
blob + f47afeacb14be6b7b606b9436eb491291146c0e9
--- gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c
+++ gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c
@@ -1368,6 +1368,27 @@ write_gotwebd_conf(void)
 		
 	}
 
+	switch (webcfg.redirect_method) {
+	case GOTSYSD_WEB_REDIRECT_METHOD_PAGE_REFRESH:
+		ret = dprintf(fd, "redirect_method page_refresh\n");
+		if (ret == -1) 
+			return got_error_from_errno2("dprintf", path);
+		if (ret != 29) {
+			return got_error_fmt(GOT_ERR_IO, "short write to %s",
+			    path);
+		}
+		break;
+	case GOTSYSD_WEB_REDIRECT_METHOD_STATUS_CODE:
+		ret = dprintf(fd, "redirect_method status_code\n");
+		if (ret == -1) 
+			return got_error_from_errno2("dprintf", path);
+		if (ret != 28) {
+			return got_error_fmt(GOT_ERR_IO, "short write to %s",
+			    path);
+		}
+		break;
+	}
+
 	switch (webcfg.auth_config) {
 	case GOTSYSD_WEB_AUTH_UNSET:
 		break;
blob - 28118ed7d0e240e4cd0309cb3a56e0dddc4ed4da
blob + 6af13da98b7fd6f22af8d422cdd31059cd2a6956
--- gotsysd/parse.y
+++ gotsysd/parse.y
@@ -119,6 +119,7 @@ typedef struct {
 %token	HTDOCS INSECURE PORT PREFORK SERVER NAME SOCKET WWW HINT SHOW_REPO_AGE
 %token	ERROR LISTEN ON USER GOTD DIRECTORY REPOSITORY UID RANGE PERMIT
 %token	DENY RO RW WEB GOTSYSD_LOGIN ENABLE DISABLE HIDE REPOSITORIES
+%token	REDIRECT_METHOD PAGE_REFRESH STATUS_CODE 
 
 %token	<v.string>	STRING
 %token	<v.number>	NUMBER
@@ -604,6 +605,14 @@ webopts1	: GOTSYSD_CONTROL SOCKET STRING {
 			}
 			gotsysd->web.auth_config = GOTSYSD_WEB_AUTH_INSECURE;
 		}
+		| REDIRECT_METHOD PAGE_REFRESH {
+			gotsysd->web.redirect_method =
+			    GOTSYSD_WEB_REDIRECT_METHOD_PAGE_REFRESH;
+		}
+		| REDIRECT_METHOD STATUS_CODE {
+			gotsysd->web.redirect_method =
+			    GOTSYSD_WEB_REDIRECT_METHOD_STATUS_CODE;
+		}
 		;
 
 webopts2	: webopts2 webopts1 nl
@@ -781,10 +790,12 @@ lookup(char *s)
 		{ "login",			GOTSYSD_LOGIN },
 		{ "name",			NAME },
 		{ "on",				ON },
+		{ "page_refresh",		PAGE_REFRESH },
 		{ "permit",			PERMIT },
 		{ "port",			PORT },
 		{ "prefork",			PREFORK },
 		{ "range",			RANGE },
+		{ "redirect_method",		REDIRECT_METHOD },
 		{ "repositories",		REPOSITORIES },
 		{ "repository",			REPOSITORY },
 		{ "ro",				RO },
@@ -792,6 +803,7 @@ lookup(char *s)
 		{ "server",			SERVER },
 		{ "show_repo_age",		SHOW_REPO_AGE },
 		{ "socket",			SOCKET },
+		{ "status_code",		STATUS_CODE },
 		{ "uid",			UID },
 		{ "user",			USER },
 		{ "web",			WEB },
blob - 6d8ed750c38c6f48f4132aac0bb66f7617c8426a
blob + 668dd10528386642058c7f883943dce5080411e5
--- gotwebd/auth.c
+++ gotwebd/auth.c
@@ -291,6 +291,7 @@ do_login(struct request *c)
 	char *token = NULL;
 	const char *identifier = NULL;
 	const time_t validity = 24 * 60 * 60; /* 1 day */
+	struct gotweb_url url;
 	struct gotwebd_repo *repo;
 
 	int r;
@@ -397,9 +398,19 @@ logged_in:
 		goto err;
 	}
 
-	if (gotweb_reply(c, 200, "text/html", NULL) == -1)
-		return;
-	gotweb_render_refresh_redirect(c->tp);
+	switch (srv->redirect_method) {
+	case GOTWEBD_REDIRECT_METHOD_UNSET:
+	case GOTWEBD_REDIRECT_METHOD_PAGE_REFRESH:
+		if (gotweb_reply(c, 200, "text/html", NULL) == -1)
+			return;
+		gotweb_render_refresh_redirect(c->tp);
+		break;
+	case GOTWEBD_REDIRECT_METHOD_STATUS_CODE:
+		memset(&url, 0, sizeof(url));
+		url.action = INDEX;
+		gotweb_reply(c, 307, "text/html", &url);
+		break;
+	}
 
 	return;
 
blob - 81a6e7549ba5fb191877024e3c107b2323226571
blob + 23ca25720b75ab3dc833779610701ceb9a240e4b
--- gotwebd/config.c
+++ gotwebd/config.c
@@ -58,6 +58,8 @@ config_init(struct gotwebd *env)
 	strlcpy(env->htdocs_path, D_HTDOCS_PATH, sizeof(env->htdocs_path));
 	strlcpy(env->gotweb_url_root, "/", sizeof(env->gotweb_url_root));
 
+	env->redirect_method = D_REDIRECT_METHOD;
+
 	env->prefork = GOTWEBD_NUMPROC;
 	TAILQ_INIT(&env->servers);
 	TAILQ_INIT(&env->sockets);
blob - 52cd85988144dc08e5b6d18734c4b91fcb5a50b2
blob + 5c02b6a0c73f37f237650f5841b47fc560f788a3
--- gotwebd/gotwebd.conf.5
+++ gotwebd/gotwebd.conf.5
@@ -111,6 +111,48 @@ are served directly from
 .Xr httpd 8 .
 .Pp
 This setting can also be configured on a per-server basis.
+.It Ic redirect_method Ar method
+Set the
+.Ar method
+used by
+.Xr gotwebd 8
+when redirecting clients to another page, such as from the login page
+to the summary page.
+.Pp
+The available methods are:
+.Bl -tag -width Ds
+.It page_refresh
+Send HTTP response status code 200 (success) and serve a
+.Dv meta  
+tag in the HTML response body with the
+.Dv http-equiv
+attribute set to
+.Dq refresh
+and a new URL in the
+.Dv url
+attribute.
+.Pp
+This method is the default and provides good browser compatibility overall.
+.pp
+This method can get in the way of scripts trying to navigate through a
+.Xr gotsh 1
+.Cm weblogin
+link, which is usually desirable, but not always.
+.It status_code
+Send HTTP response status code 307 (redirect) with a target URL in HTTP
+headers and with an empty response body.
+.Pp
+This method generally works as expected.
+In some situations browsers fail to follow the redirect and display the
+redirecting page instead.
+For example,
+.Xr gotsh 1
+.Cm weblogin 
+links have been known to fail loading the summary page if the redirecting
+link is opened by an external application asking Mozilla Firefox to open
+the login link, even though the redirect is followed as expected when
+the login link is visited via the browser's address bar.
+.El
 .It Ic repos_url_path Ar path
 Sets the URL path under which Git repositories will be displayed by
 .Xr gotwebd 8 .
blob - 4a5d254d0ac1ab54df690f896f47f5aa5bab104b
blob + 09387b7b57332d3ea2caabba553601ca1f6c66b9
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -96,6 +96,7 @@
 #define D_MAXSLCOMMDISP		 10
 #define D_MAXCOMMITDISP		 25
 #define D_MAXSLTAGDISP		 3
+#define D_REDIRECT_METHOD	 GOTWEBD_REDIRECT_METHOD_PAGE_REFRESH
 
 #define BUF			 8192
 
@@ -137,6 +138,12 @@ struct got_blob_object;
 struct got_tree_entry;
 struct got_reflist_head;
 
+enum gotwebd_redirect_method {
+	GOTWEBD_REDIRECT_METHOD_UNSET,
+	GOTWEBD_REDIRECT_METHOD_PAGE_REFRESH,
+	GOTWEBD_REDIRECT_METHOD_STATUS_CODE,
+};
+
 enum gotwebd_proc_type {
 	GOTWEBD_PROC_PARENT,
 	GOTWEBD_PROC_SOCKETS,
@@ -463,6 +470,8 @@ struct server {
 	enum gotwebd_auth_config auth_config;
 	struct gotwebd_access_rule_list access_rules;
 
+	enum gotwebd_redirect_method redirect_method;
+
 	struct gotwebd_repolist	 repos;
 	struct got_pathlist_head websites;
 };
@@ -547,6 +556,8 @@ struct gotwebd {
 
 	char		 login_hint_user[MAX_IDENTIFIER_SIZE];
 	char		 login_hint_port[GOTWEBD_MAXPORT];
+
+	enum gotwebd_redirect_method redirect_method;
 };
 
 /*
blob - e356cea29b105402010bc95aa09710adfd5c96ef
blob + 9df1ea9cfbf6bd1c4d2f6b1962c0ef0183a31719
--- gotwebd/parse.y
+++ gotwebd/parse.y
@@ -158,6 +158,7 @@ mediatype_ok(const char *s)
 %token	WEBSITE PATH BRANCH REPOS_URL_PATH DESCRIPTION
 %token	TYPES INCLUDE GOTWEBD_CONTROL
 %token	SSH_HOSTKEY_ECDSA SSH_HOSTKEY_ED25519 SSH_HOSTKEY_RSA
+%token	REDIRECT_METHOD STATUS_CODE PAGE_REFRESH
 
 %token	<v.string>	STRING
 %token	<v.number>	NUMBER
@@ -562,6 +563,14 @@ main		: PREFORK NUMBER {
 
 			free($2);
 		}
+		| REDIRECT_METHOD STATUS_CODE {
+			gotwebd->redirect_method =
+			    GOTWEBD_REDIRECT_METHOD_STATUS_CODE;
+		}
+		| REDIRECT_METHOD PAGE_REFRESH {
+			gotwebd->redirect_method =
+			    GOTWEBD_REDIRECT_METHOD_PAGE_REFRESH;
+		}
 		;
 
 server		: SERVER STRING {
@@ -1309,10 +1318,12 @@ lookup(char *s)
 		{ "max_commits_display",	MAX_COMMITS_DISPLAY },
 		{ "max_repos_display",		MAX_REPOS_DISPLAY },
 		{ "on",				ON },
+		{ "page_refresh",		PAGE_REFRESH },
 		{ "path",			PATH },
 		{ "permit",			PERMIT },
 		{ "port",			PORT },
 		{ "prefork",			PREFORK },
+		{ "redirect_method",		REDIRECT_METHOD },
 		{ "repos_path",			REPOS_PATH },
 		{ "repos_url_path",		REPOS_URL_PATH },
 		{ "repositories",		REPOSITORIES },
@@ -1331,6 +1342,7 @@ lookup(char *s)
 		{ "ssh_hostkey_ecdsa",		SSH_HOSTKEY_ECDSA},
 		{ "ssh_hostkey_ed25519",	SSH_HOSTKEY_ED25519},
 		{ "ssh_hostkey_rsa",		SSH_HOSTKEY_RSA},
+		{ "status_code",		STATUS_CODE },
 		{ "summary_commits_display",	SUMMARY_COMMITS_DISPLAY },
 		{ "summary_tags_display",	SUMMARY_TAGS_DISPLAY },
 		{ "types",			TYPES },
@@ -1979,6 +1991,19 @@ parse_config(const char *filename, struct gotwebd *env
 				return -1;
 			}
 		}
+
+		/*
+		 * The redirect method is not configurable on a per-server
+		 * basis in gotwebd.conf so currently it will always be UNSET.
+		 *
+		 * And gotwebd child processes will not see fields that are
+		 * set in our global env, unless we copy them across manually.
+		 * For now, let this field ride along in per-server config.
+		 * Maybe it will become configurable on a per-server basis
+		 * eventually, in which case this code remains correct.
+		 */
+		if (srv->redirect_method == GOTWEBD_REDIRECT_METHOD_UNSET)
+			srv->redirect_method = env->redirect_method;
 	}
 
 	TAILQ_FOREACH(srv, &env->servers, entry) {
@@ -2100,6 +2125,8 @@ conf_new_server(const char *name)
 	TAILQ_INIT(&srv->repos);
 	RB_INIT(&srv->websites);
 
+	srv->redirect_method = GOTWEBD_REDIRECT_METHOD_UNSET;
+
 	TAILQ_INSERT_TAIL(&gotwebd->servers, srv, entry);
 
 	return srv;
blob - 257b7facebe6fb8c629f5a449eab8ef4e1733b5f
blob + 9d8fa6b858c11913a7fbdf5d0fdbc6528830a4df
--- lib/gotsys_imsg.c
+++ lib/gotsys_imsg.c
@@ -1018,6 +1018,7 @@ gotsysd_web_config_init(struct gotsysd_web_config *web
 	TAILQ_INIT(&webcfg->listen_addrs);
 	STAILQ_INIT(&webcfg->servers);
 	webcfg->auth_config = GOTSYSD_WEB_AUTH_UNSET;
+	webcfg->redirect_method = GOTSYSD_WEB_REDIRECT_METHOD_PAGE_REFRESH;
 }
 
 const struct got_error *
blob - cc342ed751bdfbf38ef84e67954114553b0ec930
blob + 05827d3f6fda0c0972495eccdd15e90055748fa8
--- regress/gotsysd/Makefile
+++ regress/gotsysd/Makefile
@@ -202,6 +202,7 @@ ${GOTSYSD_CONF}:
 	@${UNPRIV} "printf '\tprefork 1\n'  >> $@"
 	@${UNPRIV} "printf '\tenable authentication insecure\n' >> $@"
 	@${UNPRIV} "printf '\tlogin hint user \"${GOTSYSD_TEST_USER}\"\n' >> $@"
+	@${UNPRIV} "printf '\tredirect_method status_code\n' >> $@"
 	@${UNPRIV} "printf '\tlisten on socket \\\"/var/www/run/gotweb.sock\\\"\n' >> $@"
 	@${UNPRIV} "printf '\tlisten on 127.0.0.1 port 9000\n' >> $@"
 	@${UNPRIV} "printf '\tlisten on ::1 port 9000\n' >> $@"