gout

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

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:
Mgit/repo.c | 45++++++++++++++++++++++++++++-----------------
Mutils.c | 52+++++++++++++++++++++++++++++++++++++++-------------
Mutils.h | 7+++++--
Mutils_tests.c | 15++++++---------
Mwriter/gopher/commit.c | 4++--
Mwriter/gopher/fileblob.c | 32++++++++++++++++----------------
Mwriter/gopher/repo_writer.c | 8++++----
Mwriter/html/commit.c | 4++--
Mwriter/html/fileblob.c | 32++++++++++++++++----------------
Mwriter/html/repo_writer.c | 8++++----
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); } }