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

From:
"Omar Polo" <op@omarpolo.com>
Subject:
got_path_strip_trailing_slashes: change to preserve the first "/"
To:
gameoftrees@openbsd.org
Date:
Sat, 18 Jul 2026 22:25:57 +0200

Download raw body.

Thread
  • Omar Polo:

    got_path_strip_trailing_slashes: change to preserve the first "/"

otherwise a path made of only "/" becomes an empty string, which is not
desiderable since we're managing paths.  Adjust the callers to cope with
the behaviour change.

ok?
 
diff 554d01aac8a3901d8a7f0ac67b88392d1fd2a71d c3f50630385142d9b8e1f024294037c21ea4ac5c
commit - 554d01aac8a3901d8a7f0ac67b88392d1fd2a71d
commit + c3f50630385142d9b8e1f024294037c21ea4ac5c
blob - f367e5a43d5c219b2524ae00cf0508b70c89693a
blob + 86375a5ba981252f7373c914687eb34e0fe9822e
--- gotd/parse.y
+++ gotd/parse.y
@@ -1370,11 +1370,17 @@ conf_protect_ref_namespace(char **new, struct got_path
 	*new = NULL;
 
 	got_path_strip_trailing_slashes(namespace);
 	if (!refname_is_valid(namespace))
 		return -1;
-	if (asprintf(&s, "%s/", namespace) == -1) {
+	if (got_path_is_root_dir(namespace)) {
+		s = strdup(namespace);
+		if (s == NULL) {
+			yyerror("strdup: %s", strerror(errno));
+			return -1;
+		}
+	} else if (asprintf(&s, "%s/", namespace) == -1) {
 		yyerror("asprintf: %s", strerror(errno));
 		return -1;
 	}
 
 	error = got_pathlist_insert(&pe, refs, s, NULL);
@@ -1522,11 +1528,17 @@ conf_notify_ref_namespace(struct gotd_repo *repo, char
 
 	got_path_strip_trailing_slashes(namespace);
 	if (!refname_is_valid(namespace))
 		return -1;
 
-	if (asprintf(&s, "%s/", namespace) == -1) {
+	if (got_path_is_root_dir(namespace)) {
+		s = strdup(namespace);
+		if (s == NULL) {
+			yyerror("strdup: %s", strerror(errno));
+			return -1;
+		}
+	} else if (asprintf(&s, "%s/", namespace) == -1) {
 		yyerror("asprintf: %s", strerror(errno));
 		return -1;
 	}
 
 	error = got_pathlist_insert(&pe, &repo->notification_ref_namespaces,
blob - a93897378b3c9c123c2da0f01702fb86d2481274
blob + eaed62bb5e478983164ee2a6b5cf893e4e258435
--- gotsys/parse.y
+++ gotsys/parse.y
@@ -1063,13 +1063,11 @@ webopts1	: DISABLE AUTHENTICATION {
 				    "an empty string");
 				free($4);
 				YYERROR;
 			}
 
-			if (!got_path_is_root_dir($4))
-				got_path_strip_trailing_slashes($4);
-
+			got_path_strip_trailing_slashes($4);
 			if (!got_path_is_absolute($4)) {
 				int ret;
 
 				ret = snprintf(srv->repos_url_path,
 				    sizeof(srv->repos_url_path),
@@ -1828,11 +1826,17 @@ conf_protect_ref_namespace(char **new, struct got_path
 	*new = NULL;
 
 	got_path_strip_trailing_slashes(namespace);
 	if (!refname_is_valid(namespace))
 		return -1;
-	if (asprintf(&s, "%s/", namespace) == -1) {
+	if (got_path_is_root_dir(namespace)) {
+		s = strdup(namespace);
+		if (s == NULL) {
+			yyerror("strdup: %s", strerror(errno));
+			return -1;
+		}
+	} else if (asprintf(&s, "%s/", namespace) == -1) {
 		yyerror("asprintf: %s", strerror(errno));
 		return -1;
 	}
 
 	error = got_pathlist_insert(&pe, refs, s, NULL);
@@ -1979,12 +1983,17 @@ conf_notify_ref_namespace(struct gotsys_repo *repo, ch
 	char *s;
 
 	got_path_strip_trailing_slashes(namespace);
 	if (!refname_is_valid(namespace))
 		return -1;
-
-	if (asprintf(&s, "%s/", namespace) == -1) {
+	if (got_path_is_root_dir(namespace)) {
+		s = strdup(namespace);
+		if (s == NULL) {
+			yyerror("strdup: %s", strerror(errno));
+			return -1;
+		}
+	} else if (asprintf(&s, "%s/", namespace) == -1) {
 		yyerror("asprintf: %s", strerror(errno));
 		return -1;
 	}
 
 	error = got_pathlist_insert(&pe, &repo->notification_ref_namespaces,
blob - 361d77c7427fc4a3d7cf21474edb09a17c84f5ed
blob + dc9d8ca2826bc77b85db2cff43d7a50c8dfdbc3f
--- gotwebd/gotweb.c
+++ gotwebd/gotweb.c
@@ -539,10 +539,11 @@ gotweb_serve_website(struct request *c, struct website
 		for (i = 0; i < nentries; i++) {
 			struct gotweb_url url = {
 				.index_page = -1,
 				.action = -1,
 			};
+			const char *index = "/index.html";
 
 			te = got_object_tree_get_entry(tree, i);
 			if (te == NULL) {
 				char *id_str;
 
@@ -562,11 +563,14 @@ gotweb_serve_website(struct request *c, struct website
 			    strcasecmp(name, "index.html") != 0)
 				continue;
 
 			/* XXX gotweb_reply uses request struct field */
 			got_path_strip_trailing_slashes(p->document_uri);
-			if (strlcat(p->document_uri, "/index.html",
+			if (got_path_is_root_dir(p->document_uri))
+				index++; /* skip initial "/" */
+
+			if (strlcat(p->document_uri, index,
 			    sizeof(p->document_uri)) >=
 			    sizeof(p->document_uri)) {
 				error = got_error(GOT_ERR_NO_SPACE);
 				goto done;
 			}
@@ -696,13 +700,11 @@ gotweb_route_request(int *is_repository_request, struc
 			error = got_error_from_errno("strdup");
 			goto done;
 		}
 	}
 
-	if (!got_path_is_root_dir(*request_path))
-		got_path_strip_trailing_slashes(*request_path);
-
+	got_path_strip_trailing_slashes(*request_path);
 	*site = gotweb_get_website(srv, *request_path);
 
 	/*
 	 * If the target of the request is ambiguous, the longer path wins.
 	 */
blob - ab1b6288020128df255679667f47261e6eed76a0
blob + b618203af4224f676c92168f6e40fd0e17977773
--- gotwebd/pages.tmpl
+++ gotwebd/pages.tmpl
@@ -186,11 +186,12 @@ nextsep(char *s, char **t)
 	while (url_path[0] == '/')
 		url_path++;
 	if (url_path[0] != '\0') {
 		if (url_root[0] != '\0') {
 			got_path_strip_trailing_slashes(prefix);
-			strlcat(prefix, "/", sizeof(prefix));
+			if (!got_path_is_root_dir(prefix))
+				strlcat(prefix, "/", sizeof(prefix));
 		}
 		strlcat(prefix, url_path, sizeof(prefix));
 	}
 
 	got_path_strip_trailing_slashes(prefix);
@@ -1490,11 +1491,12 @@ date: {{ datebuf }} {{ " UTC" }} {{ "\n" }}
 	while (url_path[0] == '/')
 		url_path++;
 	if (url_path[0] != '\0') {
 		if (url_root[0] != '\0') {
 			got_path_strip_trailing_slashes(prefix);
-			strlcat(prefix, "/", sizeof(prefix));
+			if (!got_path_is_root_dir(prefix))
+				strlcat(prefix, "/", sizeof(prefix));
 		}
 		strlcat(prefix, url_path, sizeof(prefix));
 	}
 
 	got_path_strip_trailing_slashes(prefix);
blob - e356cea29b105402010bc95aa09710adfd5c96ef
blob + 6e931ee077053256fdea2e1ba51a4d329d71f30c
--- gotwebd/parse.y
+++ gotwebd/parse.y
@@ -427,13 +427,11 @@ main		: PREFORK NUMBER {
 				    " string");
 				free($2);
 				YYERROR;
 			}
 
-			if (!got_path_is_root_dir($2))
-				got_path_strip_trailing_slashes($2);
-
+			got_path_strip_trailing_slashes($2);
 			n = strlcpy(gotwebd->gotweb_url_root, $2,
 			    sizeof(gotwebd->gotweb_url_root));
 			if (n >= sizeof(gotwebd->gotweb_url_root)) {
 				yyerror("gotweb_url_root too long, exceeds "
 				    "%zd bytes: %s",
@@ -458,13 +456,11 @@ main		: PREFORK NUMBER {
 				    " string");
 				free($2);
 				YYERROR;
 			}
 
-			if (!got_path_is_root_dir($2))
-				got_path_strip_trailing_slashes($2);
-
+			got_path_strip_trailing_slashes($2);
 			n = strlcpy(gotwebd->repos_url_path, $2,
 			    sizeof(gotwebd->repos_url_path));
 			if (n >= sizeof(gotwebd->repos_url_path)) {
 				yyerror("repos_url_path too long, exceeds "
 				    "%zd bytes: %s",
@@ -809,13 +805,11 @@ serveropts1	: REPOS_PATH STRING {
 				    " string");
 				free($2);
 				YYERROR;
 			}
 
-			if (!got_path_is_root_dir($2))
-				got_path_strip_trailing_slashes($2);
-
+			got_path_strip_trailing_slashes($2);
 			n = strlcpy(new_srv->gotweb_url_root, $2,
 			    sizeof(new_srv->gotweb_url_root));
 			if (n >= sizeof(new_srv->gotweb_url_root)) {
 				yyerror("gotweb_url_root too long, exceeds "
 				    "%zd bytes: %s",
@@ -839,13 +833,11 @@ serveropts1	: REPOS_PATH STRING {
 				    " string");
 				free($2);
 				YYERROR;
 			}
 
-			if (!got_path_is_root_dir($2))
-				got_path_strip_trailing_slashes($2);
-
+			got_path_strip_trailing_slashes($2);
 			n = strlcpy(new_srv->repos_url_path, $2,
 			    sizeof(new_srv->repos_url_path));
 			if (n >= sizeof(new_srv->repos_url_path)) {
 				yyerror("repos_url_path too long, exceeds "
 				    "%zd bytes: %s",
@@ -2010,15 +2002,11 @@ parse_config(const char *filename, struct gotwebd *env
 				    sizeof(srv->full_repos_url_path) - 1);
 				return -1;
 			}
 		}
 
-		if (!got_path_is_root_dir(srv->full_repos_url_path)) {
-			got_path_strip_trailing_slashes(
-			    srv->full_repos_url_path);
-		}
-
+		got_path_strip_trailing_slashes(srv->full_repos_url_path);
 		RB_FOREACH(pe, got_pathlist_head, &srv->websites) {
 			const char *url_path = pe->path;
 			struct website *site = pe->data;
 
 			if (site->auth_config == 0)
blob - c52de692cb3df4f92464143bd3f8bdd3a32ebc90
blob + ccd4ca57ebc3da3041d26be979d2339e3fb5cf9d
--- lib/dial.c
+++ lib/dial.c
@@ -188,12 +188,13 @@ got_dial_parse_uri(char **proto, char **host, char **p
 	*server_path = strdup(p);
 	if (*server_path == NULL) {
 		err = got_error_from_errno("strdup");
 		goto done;
 	}
+
 	got_path_strip_trailing_slashes(*server_path);
-	if ((*server_path)[0] == '\0') {
+	if (got_path_is_root_dir(*server_path)) {
 		err = got_error(GOT_ERR_PARSE_URI);
 		goto done;
 	}
 
 	err = got_path_basename(repo_name, *server_path);
blob - b43f45fcda4659959a1775f2a328234862001f60
blob + 1be262eb423fe458698d66819eb694c1d1eef476
--- lib/path.c
+++ lib/path.c
@@ -429,11 +429,13 @@ void
 got_path_strip_trailing_slashes(char *path)
 {
 	size_t x;
 
 	x = strlen(path);
-	while (x-- > 0 && path[x] == '/')
+
+	/* if the input is "/" don't trim it */
+	while (x-- > 1 && path[x] == '/')
 		path[x] = '\0';
 }
 
 /* based on findprog() from usr.bin/which/which.c */
 const struct got_error *
blob - fca04b3b62b7ecd7035e827c334fa35ad977b7a3
blob + 1a6c5d63489f0fc3240c2601bd4a04ce00ad43ad
--- lib/worktree.c
+++ lib/worktree.c
@@ -183,13 +183,13 @@ got_worktree_init(const char *path, struct got_referen
 	} else {
 		absprefix = strdup(prefix);
 		if (absprefix == NULL)
 			return got_error_from_errno("strdup");
 	}
-	if (!got_path_is_root_dir(absprefix))
-		got_path_strip_trailing_slashes(absprefix);
 
+	got_path_strip_trailing_slashes(absprefix);
+
 	/* Create top-level directory (may already exist). */
 	if (mkdir(path, GOT_DEFAULT_DIR_MODE) == -1 && errno != EEXIST) {
 		err = got_error_from_errno2("mkdir", path);
 		goto done;
 	}
blob - 9c87f1cb18b976ef796bcbe79abffd540c2e80a8
blob + 5406ccec12650b57a31461290e66973c8fda4b86
--- lib/worktree_open.c
+++ lib/worktree_open.c
@@ -193,13 +193,13 @@ open_worktree(struct got_worktree **worktree, const ch
 
 	err = read_meta_file(&(*worktree)->path_prefix, path_meta,
 	    GOT_WORKTREE_PATH_PREFIX);
 	if (err)
 		goto done;
-	if (!got_path_is_root_dir((*worktree)->path_prefix))
-		got_path_strip_trailing_slashes((*worktree)->path_prefix);
 
+	got_path_strip_trailing_slashes((*worktree)->path_prefix);
+
 	err = read_meta_file(&base_commit_id_str, path_meta,
 	    GOT_WORKTREE_BASE_COMMIT);
 	if (err)
 		goto done;
 
blob - c4da368ef9fe5fb3b4c6ff3a0879637e0a4a2611
blob + c3febf63fe7daf00c838081bf3ab693ce816f381
--- regress/path/path_test.c
+++ regress/path/path_test.c
@@ -21,10 +21,11 @@
 #include <stdlib.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <err.h>
+#include <limits.h>
 
 #include "got_error.h"
 #include "got_path.h"
 
 #ifndef nitems
@@ -97,10 +98,43 @@ path_cmp(void)
 	}
 
 	return 1;
 }
 
+static int
+path_strip_trailing_slashes(void)
+{
+	const struct path_cmp_test {
+		const char *input;
+		const char *expected;
+	} test_data[] = {
+		{ "", "" },
+		{ "/", "/" },
+		{ "///", "/" },
+		{ "/a", "/a" },
+		{ "/a/", "/a" },
+		{ "/a//", "/a" },
+	};
+	size_t i;
+
+	for (i = 0; i < nitems(test_data); i++) {
+		char p[PATH_MAX];
+		const char *input = test_data[i].input;
+		const char *expected = test_data[i].expected;
+
+		strlcpy(p, input, sizeof(p));
+		got_path_strip_trailing_slashes(p);
+		if (strcmp(p, expected) != 0) {
+			test_printf("%d: '%s' -> '%s'; expected '%s'\n",
+			    i, input, p, expected);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
 const char *path_list_input[] = {
 	"", "/", "a", "/b", "/bar", "bar/sub", "/bar/sub", "/bar/",
 	"/bar.c", "/bar/sub/sub2", "/bar.sub.sub2", "/foo",
 	"/foo/sub", "/foo/", "/foo/", "x/a",
 };
@@ -246,10 +280,11 @@ main(int argc, char *argv[])
 	}
 	argc -= optind;
 	argv += optind;
 
 	RUN_TEST(path_cmp(), "path_cmp");
+	RUN_TEST(path_strip_trailing_slashes(), "path_strip_trailing_slashes");
 	RUN_TEST(path_list(), "path_list");
 	RUN_TEST(path_list_reverse_input(), "path_list_reverse_input");
 
 	return failure ? 1 : 0;
 }