commit 82464a65c9f60359137991abf9964721dff22141
parent e0353bf7735fbe23288e9196d6a3a45cea4d051e
Author: Chris Bracken <chris@bracken.jp>
Date: Mon, 15 Dec 2025 10:25:53 +0900
Update path_concat to dynamic buffer allocation
Also extracts relative path calculation to utils since this is
duplicated between HTML and Gopher writers.
Diffstat:
10 files changed, 122 insertions(+), 85 deletions(-)
diff --git a/git/repo.c b/git/repo.c
@@ -239,21 +239,23 @@ GitRepo* gitrepo_create(const char* path) {
}
repo->repo = git_repo;
- char repo_path[PATH_MAX];
- if (!realpath(path, repo_path)) {
+ char* repo_path = realpath(path, NULL);
+ if (!repo_path) {
err(1, "realpath");
}
- char git_path[PATH_MAX];
- path_concat(git_path, sizeof(git_path), repo_path, ".git");
- if (access(git_path, F_OK) != 0) {
- estrlcpy(git_path, repo_path, sizeof(git_path));
+
+ char* git_path_maybe = path_concat(repo_path, ".git");
+ char* git_path = NULL;
+ if (access(git_path_maybe, F_OK) != 0) {
+ git_path = estrdup(repo_path);
+ } else {
+ git_path = estrdup(git_path_maybe);
}
- char owner_path[PATH_MAX];
- path_concat(owner_path, sizeof(owner_path), git_path, "owner");
- char desc_path[PATH_MAX];
- path_concat(desc_path, sizeof(desc_path), git_path, "description");
- char url_path[PATH_MAX];
- path_concat(url_path, sizeof(url_path), git_path, "url");
+ free(git_path_maybe);
+
+ char* owner_path = path_concat(git_path, "owner");
+ char* desc_path = path_concat(git_path, "description");
+ char* url_path = path_concat(git_path, "url");
repo->name = gitrepo_name_from_path(repo_path);
repo->short_name = gitrepo_shortname_from_name(repo->name);
@@ -265,6 +267,13 @@ GitRepo* gitrepo_create(const char* path) {
repo->readme = gitrepo_first_matching_file(repo->repo, kReadmes, kReadmesLen);
repo->license =
gitrepo_first_matching_file(repo->repo, kLicenses, kLicensesLen);
+
+ free(repo_path);
+ free(git_path);
+ free(owner_path);
+ free(desc_path);
+ free(url_path);
+
return repo;
}
@@ -414,13 +423,11 @@ bool gitrepo_walk_tree_files(git_repository* repo,
return false;
}
- char entrypath[PATH_MAX];
+ char* entrypath;
if (path[0] == '\0') {
- estrlcpy(entrypath, entryname, sizeof(entrypath));
+ entrypath = estrdup(entryname);
} else {
- char full_path[PATH_MAX];
- path_concat(full_path, sizeof(full_path), path, entryname);
- estrlcpy(entrypath, full_path, sizeof(entrypath));
+ entrypath = path_concat(path, entryname);
}
git_object* obj = NULL;
@@ -441,13 +448,16 @@ bool gitrepo_walk_tree_files(git_repository* repo,
if (!gitrepo_walk_tree_files(repo, (git_tree*)obj, entrypath, cb,
user_data)) {
git_object_free(obj);
+ free(entrypath);
return false;
}
git_object_free(obj);
+ free(entrypath);
continue;
}
default:
git_object_free(obj);
+ free(entrypath);
continue;
}
@@ -472,6 +482,7 @@ bool gitrepo_walk_tree_files(git_repository* repo,
git_object_free(obj);
free(filemode);
}
+ free(entrypath);
}
return true;
}
diff --git a/utils.c b/utils.c
@@ -11,22 +11,21 @@
#include "third_party/openbsd/strlcat.h"
#include "third_party/openbsd/strlcpy.h"
-char* path_concat(char* out, size_t out_len, const char* p1, const char* p2) {
+char* path_concat(const char* p1, const char* p2) {
size_t p1_len = strlen(p1);
size_t p2_len = strlen(p2);
+ // +1 for '/', +1 for null terminator
+ size_t total_len = p1_len + p2_len + 2;
+ char* out = ecalloc(total_len, sizeof(char));
+
if (p1_len == 0) {
- estrlcpy(out, p2, out_len);
- return out;
- }
- if (p2_len == 0) {
- estrlcpy(out, p1, out_len);
- return out;
- }
- if (p1_len + p2_len + 2 > out_len) {
- errx(1, "path truncated. p1: '%s' p2: '%s'", p1, p2);
- }
- if (snprintf(out, out_len, "%s/%s", p1, p2) < 0) {
- err(1, "snprintf");
+ estrlcpy(out, p2, total_len);
+ } else if (p2_len == 0) {
+ estrlcpy(out, p1, total_len);
+ } else {
+ if (snprintf(out, total_len, "%s/%s", p1, p2) < 0) {
+ err(1, "snprintf");
+ }
}
return out;
}
@@ -140,3 +139,30 @@ bool is_safe_mailto(const char* email) {
* in HTML or could be used to craft malicious URIs. */
return strpbrk(email, "<>\"'\\\r\n") == NULL;
}
+
+char* relpath_from_dir(const char* dir) {
+ size_t slashes = 0;
+ for (const char* p = dir; *p != '\0'; p++) {
+ if (*p == '/') {
+ slashes++;
+ }
+ }
+
+ size_t num_components = slashes + 1;
+ if (strcmp(dir, ".") == 0) {
+ num_components = 1;
+ }
+
+ size_t relpath_chars = num_components * (sizeof("../") - 1);
+ size_t relpath_size = relpath_chars + 1;
+
+ char* relpath = ecalloc(relpath_size, 1);
+ char* ptr = relpath;
+ for (size_t i = 0; i < num_components; i++) {
+ memcpy(ptr, "../", sizeof("../") - 1);
+ ptr += (sizeof("../") - 1);
+ }
+ *ptr = '\0';
+
+ return relpath;
+}
diff --git a/utils.h b/utils.h
@@ -4,8 +4,8 @@
#include <stdbool.h>
#include <stdio.h>
-/* Concatenates path parts to "p1/p2". Exits on failure or truncation. */
-char* path_concat(char* out, size_t out_len, const char* p1, const char* p2);
+/* Concatenates path parts to "p1/p2". Returns a dynamically allocated string. Exits on failure. */
+char* path_concat(const char* p1, const char* p2);
/* Recursively creates the directories specified by path, like mkdir -p. */
int mkdirp(const char* path);
@@ -37,4 +37,7 @@ bool is_safe_url(const char* url);
/* Returns true if the email address is safe to use in a mailto: link. */
bool is_safe_mailto(const char* email);
+/* Computes a relative path of `../` components for a given directory path. */
+char* relpath_from_dir(const char* dir);
+
#endif // GOUT_UTILS_H_
diff --git a/utils_tests.c b/utils_tests.c
@@ -6,24 +6,21 @@
#include "utest.h"
UTEST(path_concat, CanConcatenatePaths) {
- char out[PATH_MAX];
- const char* returned = path_concat(out, sizeof(out), "p1", "p2");
+ char* out = path_concat("p1", "p2");
EXPECT_STREQ("p1/p2", out);
- EXPECT_STREQ("p1/p2", returned);
+ free(out);
}
UTEST(path_concat, CanConcatenatePathsFirstEmpty) {
- char out[PATH_MAX];
- const char* returned = path_concat(out, sizeof(out), "", "p2");
+ char* out = path_concat("", "p2");
EXPECT_STREQ("p2", out);
- EXPECT_STREQ("p2", returned);
+ free(out);
}
UTEST(path_concat, CanConcatenatePathsSecondEmpty) {
- char out[PATH_MAX];
- const char* returned = path_concat(out, sizeof(out), "p1", "");
+ char* out = path_concat("p1", "");
EXPECT_STREQ("p1", out);
- EXPECT_STREQ("p1", returned);
+ free(out);
}
UTEST(is_safe_repo_path, AllowsValidRelativePaths) {
diff --git a/writer/gopher/commit.c b/writer/gopher/commit.c
@@ -39,9 +39,9 @@ GopherCommit* gopher_commit_create(const GitRepo* repo,
if (snprintf(filename, sizeof(filename), "%s.gph", oid) < 0) {
err(1, "snprintf");
}
- char path[PATH_MAX];
- path_concat(path, sizeof(path), "commit", filename);
+ char* path = path_concat("commit", filename);
commit->out = efopen(path, "w");
+ free(path);
commit->page = gopher_page_create(commit->out, repo, title, "");
return commit;
}
diff --git a/writer/gopher/fileblob.c b/writer/gopher/fileblob.c
@@ -26,15 +26,14 @@ GopherFileBlob* gopher_fileblob_create(const GitRepo* repo, const char* path) {
blob->repo = repo;
// Create directories.
- char filename[PATH_MAX];
- if (snprintf(filename, sizeof(filename), "%s.gph", path) < 0) {
+ char filename_buffer[PATH_MAX];
+ if (snprintf(filename_buffer, sizeof(filename_buffer), "%s.gph", path) < 0) {
err(1, "snprintf");
}
- char out_path[PATH_MAX];
- path_concat(out_path, sizeof(out_path), "file", filename);
- char dir[PATH_MAX];
- estrlcpy(dir, out_path, sizeof(dir));
- const char* d = dirname(dir);
+
+ char* out_path = path_concat("file", filename_buffer);
+ char* dir_copy = estrdup(out_path);
+ const char* d = dirname(dir_copy);
if (!d) {
err(1, "dirname");
}
@@ -42,19 +41,20 @@ GopherFileBlob* gopher_fileblob_create(const GitRepo* repo, const char* path) {
blob->out = efopen(out_path, "w");
// Compute the relative path.
- char relpath[PATH_MAX];
- estrlcpy(relpath, "../", sizeof(relpath));
- for (const char* p = d; *p != '\0'; p++) {
- if (*p == '/') {
- estrlcat(relpath, "../", sizeof(relpath));
- }
- }
- estrlcpy(filename, path, sizeof(filename));
- const char* title = basename(filename);
+ char* relpath = relpath_from_dir(d);
+ free(dir_copy);
+
+ char* path_copy = estrdup(path);
+ const char* title = basename(path_copy);
if (!title) {
err(1, "basename");
}
blob->page = gopher_page_create(blob->out, repo, title, relpath);
+
+ free(out_path);
+ free(path_copy);
+ free(relpath);
+
return blob;
}
diff --git a/writer/gopher/repo_writer.c b/writer/gopher/repo_writer.c
@@ -90,9 +90,9 @@ void gopher_repowriter_add_commit(void* writer, const GitCommit* git_commit) {
gitcommit_oid(git_commit)) < 0) {
err(1, "snprintf");
}
- char path[PATH_MAX];
- path_concat(path, sizeof(path), "commit", filename);
+ char* path = path_concat("commit", filename);
atom_add_commit(gopher_writer->atom, git_commit, path, "", "");
+ free(path);
if (gopher_log_can_add_commits(gopher_writer->log)) {
gopher_log_add_commit(gopher_writer->log, git_commit);
@@ -116,10 +116,10 @@ void gopher_repowriter_add_reference(void* writer, const GitReference* ref) {
0) {
err(1, "snprintf");
}
- char path[PATH_MAX];
- path_concat(path, sizeof(path), "commit", filename);
+ char* path = path_concat("commit", filename);
atom_add_commit(gopher_writer->tags, commit, path, "",
gitreference_shorthand(ref));
+ free(path);
}
}
diff --git a/writer/html/commit.c b/writer/html/commit.c
@@ -42,9 +42,9 @@ HtmlCommit* html_commit_create(const GitRepo* repo,
if (snprintf(filename, sizeof(filename), "%s.html", oid) < 0) {
err(1, "snprintf");
}
- char path[PATH_MAX];
- path_concat(path, sizeof(path), "commit", filename);
+ char* path = path_concat("commit", filename);
commit->out = efopen(path, "w");
+ free(path);
commit->page = html_page_create(commit->out, repo, title, "../");
return commit;
}
diff --git a/writer/html/fileblob.c b/writer/html/fileblob.c
@@ -26,15 +26,14 @@ HtmlFileBlob* html_fileblob_create(const GitRepo* repo, const char* path) {
blob->repo = repo;
// Create directories.
- char filename[PATH_MAX];
- if (snprintf(filename, sizeof(filename), "%s.html", path) < 0) {
+ char filename_buffer[PATH_MAX];
+ if (snprintf(filename_buffer, sizeof(filename_buffer), "%s.html", path) < 0) {
err(1, "snprintf");
}
- char out_path[PATH_MAX];
- path_concat(out_path, sizeof(out_path), "file", filename);
- char dir[PATH_MAX];
- estrlcpy(dir, out_path, sizeof(dir));
- const char* d = dirname(dir);
+
+ char* out_path = path_concat("file", filename_buffer);
+ char* dir_copy = estrdup(out_path);
+ const char* d = dirname(dir_copy);
if (!d) {
err(1, "dirname");
}
@@ -42,19 +41,20 @@ HtmlFileBlob* html_fileblob_create(const GitRepo* repo, const char* path) {
blob->out = efopen(out_path, "w");
// Compute the relative path.
- char relpath[PATH_MAX];
- estrlcpy(relpath, "../", sizeof(relpath));
- for (const char* p = d; *p != '\0'; p++) {
- if (*p == '/') {
- estrlcat(relpath, "../", sizeof(relpath));
- }
- }
- estrlcpy(filename, path, sizeof(filename));
- const char* title = basename(filename);
+ char* relpath = relpath_from_dir(d);
+ free(dir_copy);
+
+ char* path_copy = estrdup(path);
+ const char* title = basename(path_copy);
if (!title) {
err(1, "basename");
}
blob->page = html_page_create(blob->out, repo, title, relpath);
+
+ free(out_path);
+ free(path_copy);
+ free(relpath);
+
return blob;
}
diff --git a/writer/html/repo_writer.c b/writer/html/repo_writer.c
@@ -90,9 +90,9 @@ void html_repowriter_add_commit(void* writer, const GitCommit* git_commit) {
gitcommit_oid(git_commit)) < 0) {
err(1, "snprintf");
}
- char path[PATH_MAX];
- path_concat(path, sizeof(path), "commit", filename);
+ char* path = path_concat("commit", filename);
atom_add_commit(html_writer->atom, git_commit, path, "text/html", "");
+ free(path);
if (html_log_can_add_commits(html_writer->log)) {
html_log_add_commit(html_writer->log, git_commit);
@@ -116,10 +116,10 @@ void html_repowriter_add_reference(void* writer, const GitReference* ref) {
0) {
err(1, "snprintf");
}
- char path[PATH_MAX];
- path_concat(path, sizeof(path), "commit", filename);
+ char* path = path_concat("commit", filename);
atom_add_commit(html_writer->tags, commit, path, "text/html",
gitreference_shorthand(ref));
+ free(path);
}
}