gout

A static git page generator
git clone https://git.bracken.jp/gout.git
Log | Files | Refs | README | LICENSE

commit 7a782b595fb5b90b881c833cd66e106a9da22342
parent b5939e792834bd521922ec7fd218b15ba590eb57
Author: Chris Bracken <chris@bracken.jp>
Date:   Fri, 20 Feb 2026 21:14:48 +0900

utils: simplify path_concat and add more tests

Diffstat:
Msrc/utils.c | 32++++++++------------------------
Msrc/utils_tests.c | 42++++++++++++++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+), 24 deletions(-)

diff --git a/src/utils.c b/src/utils.c @@ -25,30 +25,14 @@ char* path_concat(const char* p1, const char* p2) { bool p1_slash = p1[p1_len - 1] == '/'; bool p2_slash = p2[0] == '/'; - - char* out; - size_t total_len; - if (p1_slash && p2_slash) { - total_len = p1_len + p2_len; - out = ecalloc(total_len, sizeof(char)); - int r = snprintf(out, total_len, "%s%s", p1, p2 + 1); - if (r < 0 || (size_t)r >= total_len) { - errx(1, "snprintf: path truncated or error"); - } - } else if (!p1_slash && !p2_slash) { - total_len = p1_len + p2_len + 2; - out = ecalloc(total_len, sizeof(char)); - int r = snprintf(out, total_len, "%s/%s", p1, p2); - if (r < 0 || (size_t)r >= total_len) { - errx(1, "snprintf: path truncated or error"); - } - } else { - total_len = p1_len + p2_len + 1; - out = ecalloc(total_len, sizeof(char)); - int r = snprintf(out, total_len, "%s%s", p1, p2); - if (r < 0 || (size_t)r >= total_len) { - errx(1, "snprintf: path truncated or error"); - } + bool sep = !p1_slash && !p2_slash; + bool skip = p1_slash && p2_slash; + + size_t total_len = p1_len + p2_len + sep - skip + 1; + char* out = ecalloc(total_len, sizeof(char)); + int r = snprintf(out, total_len, "%s%s%s", p1, sep ? "/" : "", p2 + skip); + if (r < 0 || (size_t)r >= total_len) { + errx(1, "snprintf: path truncated or error"); } return out; } diff --git a/src/utils_tests.c b/src/utils_tests.c @@ -23,6 +23,48 @@ UTEST(path_concat, CanConcatenatePathsSecondEmpty) { free(out); } +UTEST(path_concat, BothEmpty) { + char* out = path_concat("", ""); + EXPECT_STREQ("", out); + free(out); +} + +UTEST(path_concat, TrailingSlashP1) { + char* out = path_concat("p1/", "p2"); + EXPECT_STREQ("p1/p2", out); + free(out); +} + +UTEST(path_concat, LeadingSlashP2) { + char* out = path_concat("p1", "/p2"); + EXPECT_STREQ("p1/p2", out); + free(out); +} + +UTEST(path_concat, BothSlashes) { + char* out = path_concat("p1/", "/p2"); + EXPECT_STREQ("p1/p2", out); + free(out); +} + +UTEST(path_concat, AbsoluteP1) { + char* out = path_concat("/p1", "p2"); + EXPECT_STREQ("/p1/p2", out); + free(out); +} + +UTEST(path_concat, NestedPaths) { + char* out = path_concat("a/b", "c/d"); + EXPECT_STREQ("a/b/c/d", out); + free(out); +} + +UTEST(path_concat, MultipleTrailingSlashesP1) { + char* out = path_concat("p1//", "p2"); + EXPECT_STREQ("p1//p2", out); + free(out); +} + UTEST(is_safe_repo_path, AllowsValidRelativePaths) { EXPECT_TRUE(is_safe_repo_path("foo")); EXPECT_TRUE(is_safe_repo_path("foo/bar"));