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:
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"));