diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Harden the page renderers
| -rw-r--r-- | source/ui-atom.c | 87 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-blame.c | 29 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-blob.c | 42 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-blob.h | 6 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-clone.c | 9 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-commit.c | 49 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-diff.c | 88 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-empty.c | 4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-log.c | 97 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-patch.c | 35 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-plain.c | 11 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-refs.c | 47 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-repolist.c | 11 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-shared.c | 187 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-shared.h | 24 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-snapshot.c | 71 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-snapshot.h | 6 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-ssdiff.c | 46 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-stats.c | 56 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-stats.h | 6 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-summary.c | 17 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-summary.h | 4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-tag.c | 15 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | source/ui-tree.c | 61 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
24 files changed, 658 insertions, 350 deletions
diff --git a/source/ui-atom.c b/source/ui-atom.c index 46039a0..b3265b7 100644 --- a/source/ui-atom.c +++ b/source/ui-atom.c @@ -14,6 +14,9 @@ #include "ui-atom.h" #include "ui-shared.h" +// Stands in for every byte the feed cannot carry. +#define XML_REPLACEMENT "?" + /* * Atom timestamps have to be RFC 3339, so a feed ignores the date-format and * local-time settings the browsable pages honour. The zero is the timezone @@ -24,9 +27,6 @@ static const char *feed_date(timestamp_t when) return show_date(when, 0, date_mode_from_type(DATE_ISO8601_STRICT)); } -// Stands in for every byte the feed cannot carry. -#define XML_REPLACEMENT "?" - /* * How many bytes the UTF-8 sequence at p holds, or 0 when invalid. The * lead-byte ranges fold in the overlong, surrogate and out-of-range cases, @@ -130,7 +130,8 @@ static void print_email(const char *email) static void print_entry(struct commit *commit, const char *host) { struct commitinfo *info; - char *hex; + char *hex, *pageurl; + char delim = '&'; info = cgit_parse_commit(commit); hex = oid_to_hex(&commit->object.oid); @@ -158,24 +159,18 @@ static void print_entry(struct commit *commit, const char *host) html("<published>"); xml_txt(feed_date(info->author_date)); html("</published>\n"); - { - char *pageurl; - char delim = '&'; - - html("<link rel='alternate' type='text/html' href='"); - html(cgit_httpscheme()); - html_attr(host); - pageurl = cgit_pageurl(ctx.repo->url, "commit", NULL); - html_attr(pageurl); - // Without a virtual root the page url is already a query - // string, so the commit id continues it instead of opening - // one. - if (ctx.cfg.virtual_root) - delim = '?'; - html_attrf("%cid=%s", delim, hex); - html("'/>\n"); - free(pageurl); - } + html("<link rel='alternate' type='text/html' href='"); + html(cgit_httpscheme()); + html_attr(host); + pageurl = cgit_pageurl(ctx.repo->url, "commit", NULL); + html_attr(pageurl); + // Without a virtual root the page url is already a query string, so + // the commit id continues it instead of opening one. + if (ctx.cfg.virtual_root) + delim = '?'; + html_attrf("%cid=%s", delim, hex); + html("'/>\n"); + free(pageurl); html("<id>"); html_txtf("urn:%s:%s", the_hash_algo->name, hex); html("</id>\n"); @@ -188,12 +183,13 @@ static void print_entry(struct commit *commit, const char *host) void cgit_print_atom(char *tip, const char *path, int max_count) { - char *host; + char *host, *fullurl, *repourl; // setup_revisions reads a command line, so the first slot is the // unused program name and parsing starts at the second. - const char *argv[] = {NULL, tip, NULL, NULL, NULL}; + const char *argv[] = { NULL, tip, NULL, NULL, NULL }; struct commit *commit; struct rev_info rev; + struct strbuf idbuf = STRBUF_INIT; int argc = 2; bool need_updated = true; @@ -214,7 +210,12 @@ void cgit_print_atom(char *tip, const char *path, int max_count) rev.show_root_diff = 0; rev.max_count = max_count; setup_revisions(argc, argv, &rev, NULL); - prepare_revision_walk(&rev); + // A failed setup leaves the walk holding freed commits, so it must + // not be read from. + if (prepare_revision_walk(&rev)) { + cgit_print_error_page(500, "Internal Server Error", "Unable to read the history"); + return; + } // CGI guarantees a server name, so only a bare test run reaches the // fallback, which keeps the mandatory feed id and links present. @@ -240,26 +241,22 @@ void cgit_print_atom(char *tip, const char *path, int max_count) html("<subtitle>"); xml_txt(ctx.repo->desc); html("</subtitle>\n"); - { - char *fullurl = cgit_currentfullurl(); - char *repourl = cgit_repourl(ctx.repo->url); - struct strbuf idbuf = STRBUF_INIT; - - strbuf_addf(&idbuf, "%s%s%s", cgit_httpscheme(), host, fullurl); - html("<id>"); - xml_txt(idbuf.buf); - html("</id>\n"); - strbuf_release(&idbuf); - html("<link rel='self' href='"); - html_attrf("%s%s%s", cgit_httpscheme(), host, fullurl); - html("'/>\n"); - html("<link rel='alternate' type='text/html' href='"); - html_attrf("%s%s%s", cgit_httpscheme(), host, repourl); - html("'/>\n"); - free(fullurl); - free(repourl); - } - while ((commit = get_revision(&rev)) != NULL) { + fullurl = cgit_currentfullurl(); + repourl = cgit_repourl(ctx.repo->url); + strbuf_addf(&idbuf, "%s%s%s", cgit_httpscheme(), host, fullurl); + html("<id>"); + xml_txt(idbuf.buf); + html("</id>\n"); + strbuf_release(&idbuf); + html("<link rel='self' href='"); + html_attrf("%s%s%s", cgit_httpscheme(), host, fullurl); + html("'/>\n"); + html("<link rel='alternate' type='text/html' href='"); + html_attrf("%s%s%s", cgit_httpscheme(), host, repourl); + html("'/>\n"); + free(fullurl); + free(repourl); + while ((commit = get_revision(&rev))) { if (need_updated) { html("<updated>"); xml_txt(feed_date(commit->date)); diff --git a/source/ui-blame.c b/source/ui-blame.c index 7248120..c020dbc 100644 --- a/source/ui-blame.c +++ b/source/ui-blame.c @@ -17,10 +17,6 @@ #include "ui-blame.h" #include "ui-shared.h" -// A tab in the rendered source runs on to the next multiple of this, -// matching what the browser does on its own since tab-size is left alone. -#define TAB_WIDTH 8 - enum blame_target { TARGET_MISSING, TARGET_FILE, @@ -116,14 +112,15 @@ static char *suspect_detail(struct blame_origin *suspect) info = cgit_parse_commit(suspect->commit); - strbuf_addf(&detail, "author %s", info->author); - if (ctx.cfg.enable_plain_email) + // A commit object may lack either ident line, leaving the fields NULL. + strbuf_addf(&detail, "author %s", info->author ? info->author : ""); + if (ctx.cfg.enable_plain_email && info->author_email) strbuf_addf(&detail, " %s", info->author_email); strbuf_addf(&detail, " %s\n", show_date(info->author_date, info->author_tz, cgit_date_mode(DATE_ISO8601))); - strbuf_addf(&detail, "committer %s", info->committer); - if (ctx.cfg.enable_plain_email) + strbuf_addf(&detail, "committer %s", info->committer ? info->committer : ""); + if (ctx.cfg.enable_plain_email && info->committer_email) strbuf_addf(&detail, " %s", info->committer_email); strbuf_addf(&detail, " %s\n\n", show_date(info->committer_date, info->committer_tz, cgit_date_mode(DATE_ISO8601))); @@ -179,12 +176,12 @@ static void emit_hashes(struct blame_scoreboard *sb) static void emit_entry_linenumbers(struct blame_entry *ent) { - const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n"; struct strbuf numbers = STRBUF_INIT; int lineno = ent->lno; while (lineno < ent->lno + ent->num_lines) { - strbuf_addf(&numbers, numberfmt, ++lineno); + lineno++; + strbuf_addf(&numbers, "<a id='n%d' href='#n%d'>%d</a>\n", lineno, lineno, lineno); if (numbers.len >= HTML_BATCH) { html_raw(numbers.buf, numbers.len); strbuf_reset(&numbers); @@ -274,7 +271,7 @@ static void print_blame_page(const struct object_id *oid, const char *path, type = odb_read_object_info(the_repository->objects, oid, &size); if (type == OBJ_BAD) { - cgit_print_error_page(404, "Not Found", "Bad object name: %s", oid_to_hex(oid)); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", oid_to_hex(oid)); return; } if (ctx.cfg.max_blob_size && size / 1024 > (unsigned long)ctx.cfg.max_blob_size) { @@ -287,7 +284,8 @@ static void print_blame_page(const struct object_id *oid, const char *path, buf = odb_read_object(the_repository->objects, oid, &type, &size); if (!buf) { - cgit_print_error_page(500, "Internal Server Error", "Error reading object %s", oid_to_hex(oid)); + cgit_print_error_page(500, "Internal Server Error", "Unable to read object %s", + oid_to_hex(oid)); return; } @@ -333,6 +331,7 @@ static void print_blame_page(const struct object_id *oid, const char *path, html("<pre><code>"); if (ctx.repo->source_filter) { char *filter_arg = xstrdup(filename); + cgit_open_filter(ctx.repo->source_filter, filter_arg); html_raw(buf, size); cgit_close_filter(ctx.repo->source_filter); @@ -412,12 +411,12 @@ void cgit_print_blame(void) rev = ctx.qry.head; if (repo_get_oid(the_repository, rev, &oid)) { - cgit_print_error_page(404, "Not Found", "Invalid revision name: %s", rev); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", rev); return; } commit = lookup_commit_reference(the_repository, &oid); if (!commit || repo_parse_commit(the_repository, commit)) { - cgit_print_error_page(404, "Not Found", "Invalid commit reference: %s", rev); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", rev); return; } @@ -428,7 +427,7 @@ void cgit_print_blame(void) if (walk.found == TARGET_MISSING) cgit_print_error_page(404, "Not Found", "Not found"); else if (walk.found == TARGET_FOLDER) - cgit_print_error_page(404, "Not Found", "Blame is not available for folders."); + cgit_print_error_page(404, "Not Found", "Blame is not available for a directory"); free(walk.rev); } diff --git a/source/ui-blob.c b/source/ui-blob.c index a298e54..8407342 100644 --- a/source/ui-blob.c +++ b/source/ui-blob.c @@ -22,6 +22,18 @@ struct walk_tree_context { }; /* + * An annotated tag names a commit through its tag object, and a path is looked + * up in the commit's tree, not in the tag. + */ +static void peel_to_commit(struct object_id *oid) +{ + struct commit *commit = lookup_commit_reference_gently(the_repository, oid, 1); + + if (commit) + oidcpy(oid, &commit->object.oid); +} + +/* * read_tree reads the return value as a direction rather than a status, so * READ_TREE_RECURSIVE means step into this entry and zero means step over it. */ @@ -30,9 +42,14 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base, { struct walk_tree_context *walk = context; + // Stepping into a submodule entry would have git look its commit up + // in this repository, which does not hold it, and die. if (walk->file_only && !S_ISREG(mode)) - return READ_TREE_RECURSIVE; - if (strncmp(base->buf, walk->match_path, base->len) || strcmp(walk->match_path + base->len, pathname)) + return S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0; + if ( + strncmp(base->buf, walk->match_path, base->len) || + strcmp(walk->match_path + base->len, pathname) + ) return READ_TREE_RECURSIVE; oidcpy(walk->matched_oid, oid); walk->found_path = 1; @@ -47,6 +64,7 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base, static int find_path_oid(struct object_id *oid, char *path, int file_only) { struct commit *commit = lookup_commit_reference(the_repository, oid); + struct tree *tree; // nowildcard_len matching len makes git treat the path as literal // rather than as a glob. struct pathspec_item item = { @@ -65,7 +83,14 @@ static int find_path_oid(struct object_id *oid, char *path, int file_only) .file_only = file_only }; - read_tree(the_repository, repo_get_commit_tree(the_repository, commit), &paths, walk_tree, &walk); + // A commit git cannot parse, one with a broken tree line for example, + // comes back null and has no tree to search. + if (!commit) + return 0; + tree = repo_get_commit_tree(the_repository, commit); + if (!tree) + return 0; + read_tree(the_repository, tree, &paths, walk_tree, &walk); return walk.found_path; } @@ -105,6 +130,7 @@ int cgit_print_file(char *path, const char *head, int file_only, int html_escape if (repo_get_oid(the_repository, head, &oid)) return -1; + peel_to_commit(&oid); type = odb_read_object_info(the_repository->objects, &oid, &size); if (type == OBJ_COMMIT) { if (!find_path_oid(&oid, path, file_only)) @@ -152,14 +178,15 @@ void cgit_print_blob(const char *hex, char *path, const char *head, int file_onl if (hex) { if (get_oid_hex(hex, &oid)) { - cgit_print_error_page(400, "Bad Request", "Bad hex value: %s", hex); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", hex); return; } } else { if (repo_get_oid(the_repository, head, &oid)) { - cgit_print_error_page(404, "Not Found", "Bad ref: %s", head); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", head); return; } + peel_to_commit(&oid); } type = odb_read_object_info(the_repository->objects, &oid, &size); @@ -173,7 +200,7 @@ void cgit_print_blob(const char *hex, char *path, const char *head, int file_onl } if (type == OBJ_BAD) { - cgit_print_error_page(404, "Not Found", "Bad object name: %s", hex ? hex : path); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", hex ? hex : path); return; } @@ -185,7 +212,8 @@ void cgit_print_blob(const char *hex, char *path, const char *head, int file_onl buf = odb_read_object(the_repository->objects, &oid, &type, &size); if (!buf) { - cgit_print_error_page(500, "Internal Server Error", "Error reading object %s", hex ? hex : path); + cgit_print_error_page(500, "Internal Server Error", "Unable to read object %s", + hex ? hex : path); return; } diff --git a/source/ui-blob.h b/source/ui-blob.h index 2eab0db..58e0abb 100644 --- a/source/ui-blob.h +++ b/source/ui-blob.h @@ -1,7 +1,7 @@ /* - * Declarations for reading a blob out of the object database and writing it to - * the client. A caller either hands the whole response over to the blob's own - * bytes and headers, or drops a file's contents into a page cgit is already + * The blob page, which reads a blob out of the object database and writes it + * to the client. A caller either hands the whole response over to the blob's + * own bytes and headers, or drops a file's contents into a page cgit is already * building, as the summary page does for a readme. */ diff --git a/source/ui-clone.c b/source/ui-clone.c index 4329e9d..2544e9f 100644 --- a/source/ui-clone.c +++ b/source/ui-clone.c @@ -72,6 +72,7 @@ static void print_pack_info(void) // directly leaves those packs unfindable. for (entry = packfile_store_get_packs(files->packed); entry; entry = entry->next) { struct packed_git *pack = entry->pack; + if (pack->pack_local) htmlf("P %s\n", last_path_component(pack->pack_name)); } @@ -79,8 +80,7 @@ static void print_pack_info(void) } /* - * Beyond the obvious directory traversal, the strict character set heads off - * other funny business, for example the file name quirks of the Cygwin port. + * Only the characters an object path can hold pass, and no dotdot component. */ static int path_is_safe(const char *path) { @@ -112,6 +112,11 @@ static void send_file(const char *path) } return; } + // fopen opens a directory on most systems and reads nothing from it. + if (!S_ISREG(st.st_mode)) { + cgit_print_error_page(404, "Not Found", "Not found"); + return; + } ctx.page.mimetype = "application/octet-stream"; // Offer the file under its path inside the repository, so the layout // of the server's disk stays out of the download name. diff --git a/source/ui-commit.c b/source/ui-commit.c index 80c7faf..1fdc5fa 100644 --- a/source/ui-commit.c +++ b/source/ui-commit.c @@ -32,7 +32,7 @@ static void print_ident_row(const char *role, const char *name, const char *emai timestamp_t date, int tz) { htmlf("<tr><th>%s</th><td>", role); - cgit_open_filter(ctx.repo->email_filter, email, "commit"); + cgit_open_filter(ctx.repo->email_filter, email ? email : "", "commit"); html_txt(name); if (ctx.cfg.enable_plain_email) { html(" "); @@ -46,10 +46,16 @@ static void print_ident_row(const char *role, const char *name, const char *emai html("</time></td></tr>\n"); } -static int print_parent_rows(struct commit *commit, const char *rev, const char *prefix) +/* + * Counts the parents that could be read and leaves the first of them in first, + * which the diff below the message is taken against. + */ +static int print_parent_rows(struct commit *commit, const char *rev, const char *prefix, + struct object_id *first) { struct commit_list *p; struct commit *parent; + struct commitinfo *info; const char *parent_hex, *label; int parents = 0; @@ -61,13 +67,19 @@ static int print_parent_rows(struct commit *commit, const char *rev, const char html("</td></tr>\n"); continue; } + if (!parents) + oidcpy(first, &p->item->object.oid); html("<tr><th>parent</th><td colspan='2' class='oid'><div class='spanning'>"); parent_hex = label = oid_to_hex(&p->item->object.oid); - if (ctx.repo->enable_subject_links) - label = cgit_parse_commit(parent)->subject; + info = ctx.repo->enable_subject_links ? cgit_parse_commit(parent) : NULL; + if (info) + label = info->subject; cgit_commit_link(label, NULL, NULL, ctx.qry.head, parent_hex, prefix); + if (info) + cgit_free_commitinfo(info); html(" ("); - cgit_diff_link("diff", NULL, NULL, ctx.qry.head, rev, oid_to_hex(&p->item->object.oid), prefix); + cgit_diff_link("diff", NULL, NULL, ctx.qry.head, rev, oid_to_hex(&p->item->object.oid), + prefix); html(")</div></td></tr>\n"); parents++; } @@ -127,8 +139,9 @@ static void print_trailer_value(const char *key, const char *value) cgit_open_filter(ctx.repo->trailer_filter, key, "commit"); html_txt(value); cgit_close_filter(ctx.repo->trailer_filter); - } else + } else { print_filtered_text(value); + } } /* @@ -204,7 +217,10 @@ static void print_message(struct commitinfo *info) end = trailer_block_end(block); free_trailers(&items); trailer_block_release(block); - if (start >= prefix && start < end) { + // git leaves trailing comment lines and an old style Conflicts + // section after the block, which the split would lose, so such + // a message is shown whole. + if (start >= prefix && start < end && !full.buf[end + strspn(full.buf + end, "\n")]) { size_t len = start - prefix; while (len > 0 && info->msg[len - 1] == '\n') @@ -228,8 +244,9 @@ void cgit_print_commit(char *hex, const char *prefix) struct commit *commit; struct commitinfo *info; struct strbuf notes = STRBUF_INIT; - struct object_id oid; - const char *commit_hex, *first_parent; + struct object_id oid, first_oid; + const char *commit_hex; + char *first_parent; char *tree_rev; int parents; @@ -237,17 +254,17 @@ void cgit_print_commit(char *hex, const char *prefix) hex = ctx.qry.head; if (repo_get_oid(the_repository, hex, &oid)) { - cgit_print_error_page(400, "Bad Request", "Bad object id: %s", hex); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", hex); return; } commit = lookup_commit_reference(the_repository, &oid); if (!commit) { - cgit_print_error_page(404, "Not Found", "Bad commit reference: %s", hex); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", hex); return; } info = cgit_parse_commit(commit); - format_display_notes(&oid, ¬es, PAGE_ENCODING, 1); + format_display_notes(&commit->object.oid, ¬es, PAGE_ENCODING, 1); load_ref_decorations(NULL, DECORATE_FULL_REFS); @@ -271,7 +288,7 @@ void cgit_print_commit(char *hex, const char *prefix) // The parent rows follow the commit row so the commit hashes sit // together, leaving the tree beside the download links. - parents = print_parent_rows(commit, hex, prefix); + parents = print_parent_rows(commit, hex, prefix, &first_oid); html("<tr><th>tree</th><td colspan='2' class='oid'><div class='spanning'>"); tree_rev = xstrdup(hex); @@ -304,11 +321,9 @@ void cgit_print_commit(char *hex, const char *prefix) } if (parents < OCTOPUS_PARENTS) { - if (parents) - first_parent = oid_to_hex(&commit->parents->item->object.oid); - else - first_parent = NULL; + first_parent = parents ? xstrdup(oid_to_hex(&first_oid)) : NULL; cgit_print_diff(ctx.qry.oid, first_parent, prefix, 0, 0); + free(first_parent); } strbuf_release(¬es); diff --git a/source/ui-diff.c b/source/ui-diff.c index fb86a96..ee8dc76 100644 --- a/source/ui-diff.c +++ b/source/ui-diff.c @@ -23,10 +23,6 @@ // and a second walk renders the page instead. #define BODY_BUDGET (8 * 1024 * 1024) -// What a context of zero means once the diff runs, mirroring the fallback in -// cgit_diff_files, so the control offers the value the diff will really use. -#define DEFAULT_CONTEXT_LINES 3 - struct fileinfo { char status; struct object_id old_oid[1]; @@ -120,7 +116,7 @@ static void print_fileinfo(struct fileinfo *info) class = "stg"; break; default: - die("bug: unhandled diff status %c", info->status); + BUG("unhandled diff status %c", info->status); } html("<tr>"); @@ -131,13 +127,18 @@ static void print_fileinfo(struct fileinfo *info) cgit_print_filemode(info->new_mode); } - if (info->old_mode != info->new_mode && !is_null_oid(info->old_oid) && !is_null_oid(info->new_oid)) { + if ( + info->old_mode != info->new_mode && + !is_null_oid(info->old_oid) && + !is_null_oid(info->new_oid) + ) { html("<span class='modechange'>["); cgit_print_filemode(info->old_mode); html("]</span>"); } htmlf("</td><td class='%s'>", class); - cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.oid, ctx.qry.oid2, info->new_path); + cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.oid, ctx.qry.oid2, + info->new_path); if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) { htmlf(" (%s from ", info->status == DIFF_STATUS_COPIED ? "copied" : "renamed"); html_txt(info->old_path); @@ -145,10 +146,11 @@ static void print_fileinfo(struct fileinfo *info) } html("</td><td class='right'>"); if (info->binary) { - htmlf("bin</td><td class='graph'>%lu -> %lu bytes</td></tr>\n", info->old_size, info->new_size); + htmlf("bin</td><td class='graph'>%lu -> %lu bytes</td></tr>\n", info->old_size, + info->new_size); return; } - htmlf("%d", info->added + info->removed); + htmlf("%u", info->added + info->removed); html("</td><td class='graph'>"); html("<table><tr>"); add_span = (int)(info->added * 1000.0 / scale + 0.5); @@ -173,7 +175,11 @@ static void count_diff_lines(char *line, int len) } if (!render_line_fn || render_suppressed) return; - if (cap_diffs && ctx.cfg.max_diff_lines > 0 && lines_added + lines_removed > ctx.cfg.max_diff_lines) { + if ( + cap_diffs && + ctx.cfg.max_diff_lines > 0 && + lines_added + lines_removed > ctx.cfg.max_diff_lines + ) { render_suppressed = 1; return; } @@ -184,7 +190,8 @@ static int show_filepair(struct diff_filepair *pair) { if (!current_prefix) return 1; - return starts_with(pair->one->path, current_prefix) || starts_with(pair->two->path, current_prefix); + return starts_with(pair->one->path, current_prefix) || + starts_with(pair->two->path, current_prefix); } /* @@ -220,8 +227,8 @@ static char *abbrev_oid(const struct object_id *oid) return xstrdup(repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV)); } -static void print_file_header(const struct object_id *old_oid, char *old_path, int old_mode, - const struct object_id *new_oid, char *new_path, int new_mode) +static void print_file_header(const struct object_id *old_oid, char *old_path, unsigned old_mode, + const struct object_id *new_oid, char *new_path, unsigned new_mode) { char *old_abbrev, *new_abbrev; int subproject; @@ -253,8 +260,9 @@ static void print_file_header(const struct object_id *old_oid, char *old_path, i if (is_null_oid(old_oid)) { old_path = "dev/null"; html("<br>--- /"); - } else + } else { html("<br>--- a/"); + } if (old_mode != 0) cgit_tree_link(old_path, NULL, NULL, ctx.qry.head, oid_to_hex(old_rev_oid), old_path); else @@ -262,8 +270,9 @@ static void print_file_header(const struct object_id *old_oid, char *old_path, i if (is_null_oid(new_oid)) { new_path = "dev/null"; html("<br>+++ /"); - } else + } else { html("<br>+++ b/"); + } if (new_mode != 0) cgit_tree_link(new_path, NULL, NULL, ctx.qry.head, oid_to_hex(new_rev_oid), new_path); else @@ -303,13 +312,15 @@ static void print_truncated(const char *path) else html("<div class='truncated'>"); html("This diff is too large to be rendered inline. "); - cgit_diff_link("View it on its own page", NULL, NULL, ctx.qry.head, ctx.qry.oid, ctx.qry.oid2, path); + cgit_diff_link("View it on its own page", NULL, NULL, ctx.qry.head, ctx.qry.oid, ctx.qry.oid2, + path); html("."); if (use_ssdiff) { html("</td></tr>"); cgit_ssdiff_footer(); - } else + } else { html("</div>"); + } } static struct fileinfo *reserve_item(void) @@ -552,7 +563,7 @@ void cgit_print_diff_ctrls(void) html("<select name='context'>"); selected = ctx.qry.context; if (!selected) - selected = DEFAULT_CONTEXT_LINES; + selected = DEFAULT_DIFF_CONTEXT; for (i = 1; i <= 10; i++) html_intoption(i, cgit_fmt("%d", i), selected); for (i = 15; i <= MAX_DIFF_CONTEXT_LINES; i += 5) @@ -583,17 +594,37 @@ void cgit_print_diff_ctrls(void) html("</div>\n"); } +/* + * Whether the path names a directory in either tree. The diff below it can + * then still span many files, so the caps stay on. + */ +static int prefix_is_dir(const struct object_id *old_tree, const struct object_id *new_tree, + const char *prefix) +{ + struct object_id oid; + unsigned short mode; + char *path = xstrdup(prefix); + size_t len = strlen(path); + int is_dir = 0; + + if (len && path[len - 1] == '/') + path[len - 1] = '\0'; + if (new_tree && !get_tree_entry(the_repository, new_tree, path, &oid, &mode)) + is_dir = S_ISDIR(mode); + else if (old_tree && !get_tree_entry(the_repository, old_tree, path, &oid, &mode)) + is_dir = S_ISDIR(mode); + free(path); + return is_dir; +} + void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix, int show_ctrls, int raw) { struct commit *new_commit, *old_commit; const struct object_id *old_tree_oid, *new_tree_oid; + const char *path = prefix; diff_type difftype; - // Decided from the caller's prefix before the follow logic below - // rewrites it to "", otherwise follow=1 silently disables the caps. - cap_diffs = !prefix; - // Detecting renames needs the diff machinery to examine the whole // commit, so with follow set the prefix is applied in show_filepair // instead of being passed down. @@ -607,19 +638,19 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi if (!new_rev) new_rev = ctx.qry.head; if (repo_get_oid(the_repository, new_rev, new_rev_oid)) { - cgit_print_error_page(404, "Not Found", "Bad object name: %s", new_rev); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", new_rev); return; } new_commit = lookup_commit_reference(the_repository, new_rev_oid); if (!new_commit || repo_parse_commit(the_repository, new_commit)) { - cgit_print_error_page(404, "Not Found", "Bad commit: %s", oid_to_hex(new_rev_oid)); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", oid_to_hex(new_rev_oid)); return; } new_tree_oid = get_commit_tree_oid(new_commit); if (old_rev) { if (repo_get_oid(the_repository, old_rev, old_rev_oid)) { - cgit_print_error_page(404, "Not Found", "Bad object name: %s", old_rev); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", old_rev); return; } } else if (new_commit->parents && new_commit->parents->item) { @@ -631,7 +662,7 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi if (!is_null_oid(old_rev_oid)) { old_commit = lookup_commit_reference(the_repository, old_rev_oid); if (!old_commit || repo_parse_commit(the_repository, old_commit)) { - cgit_print_error_page(404, "Not Found", "Bad commit: %s", oid_to_hex(old_rev_oid)); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", oid_to_hex(old_rev_oid)); return; } old_tree_oid = get_commit_tree_oid(old_commit); @@ -639,6 +670,11 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi old_tree_oid = NULL; } + // A path narrows the diff to one file, whose own page always renders + // in full. Decided from the caller's path, since the follow logic + // above has emptied the prefix. + cap_diffs = !path || prefix_is_dir(old_tree_oid, new_tree_oid, path); + if (raw) { print_raw_patch(old_tree_oid, new_tree_oid); return; diff --git a/source/ui-empty.c b/source/ui-empty.c index dd8dc64..30887c5 100644 --- a/source/ui-empty.c +++ b/source/ui-empty.c @@ -24,8 +24,8 @@ void cgit_print_empty_repo(void) { cgit_print_error("Repository seems to be empty"); - // cgit_add_clone_urls draws on just these two, so with neither set the - // table below would be a Clone heading with no rows. + // cgit_add_clone_urls draws on these two alone, so with neither set + // the table below would be a Clone heading with no rows. if (!ctx.repo->clone_url && !ctx.cfg.clone_prefix) return; diff --git a/source/ui-log.c b/source/ui-log.c index 316df75..86cb756 100644 --- a/source/ui-log.c +++ b/source/ui-log.c @@ -176,6 +176,9 @@ static void wrap_subject(struct commitinfo *info, struct strbuf *msg) --cut; if (!cut) cut = ctx.cfg.max_msg_len - strlen(wrap_symbol); + // A cut inside a multibyte character would leave both halves invalid. + while (cut > 0 && (info->subject[cut] & 0xC0) == 0x80) + --cut; strbuf_add(msg, info->subject + cut, subject_len - cut); strbuf_trim(msg); @@ -244,7 +247,7 @@ static void print_commit(struct commit *commit, struct rev_info *revs) oid_to_hex(&commit->object.oid), ctx.qry.vpath); cgit_print_commit_decorations(commit); html("</td><td class='col-author'>"); - cgit_open_filter(ctx.repo->email_filter, info->author_email, "log"); + cgit_open_filter(ctx.repo->email_filter, info->author_email ? info->author_email : "", "log"); html_txt(info->author); cgit_close_filter(ctx.repo->email_filter); @@ -286,8 +289,9 @@ static void print_commit(struct commit *commit, struct rev_info *revs) int msg_lines = ctx.qry.showmsg ? line_count(msgbuf.buf) : 0; print_graph_padding(revs, &graphbuf, msg_lines); - } else + } else { html("<td></td>"); + } // Either way one cell is already on the row, so the message // spans the remaining columns. @@ -317,6 +321,27 @@ static const char *disambiguate_ref(const char *ref, int *must_free_result) return ref; } +/* + * A range token is one revision, or two joined by two or three dots, and each + * side has to pass cgit_valid_rev. An empty side means HEAD to git. + */ +static int valid_range_token(const char *arg) +{ + const char *dots = strstr(arg, ".."); + char *left; + int ok; + + if (!dots) + return cgit_valid_rev(arg); + left = xstrndup(arg, dots - arg); + dots += 2; + if (*dots == '.') + dots++; + ok = (!*left || cgit_valid_rev(left)) && (!*dots || cgit_valid_rev(dots)); + free(left); + return ok; +} + static char *next_token(char **src) { char *token; @@ -348,7 +373,7 @@ static void print_pager(struct rev_info *revs, int ofs, int cnt) struct commit *more = get_revision(revs); html("</table>\n"); - // A single page needs no pager, and an empty list is just noise. + // A single page needs no pager, and an empty list has nothing to page. if (ofs <= 0 && !more) return; html("<ul class='pager'>"); @@ -378,7 +403,7 @@ static void print_pager(struct rev_info *revs, int ofs, int cnt) void cgit_print_commit_decorations(struct commit *commit) { const struct name_decoration *deco; - static char buf[1024]; + const char *buf; deco = get_name_decoration(&commit->object); if (!deco) @@ -388,12 +413,13 @@ void cgit_print_commit_decorations(struct commit *commit) struct object_id oid_tag, peeled; int is_annotated = 0; - strlcpy(buf, prettify_refname(deco->name), sizeof(buf)); + buf = prettify_refname(deco->name); switch (deco->type) { case DECORATION_NONE: break; case DECORATION_REF_LOCAL: - cgit_log_link(buf, NULL, "branch-deco", buf, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, 0); + cgit_log_link(buf, NULL, "branch-deco", buf, NULL, ctx.qry.vpath, 0, NULL, NULL, + ctx.qry.showmsg, 0); break; case DECORATION_REF_TAG: if (!refs_read_ref(get_main_ref_store(the_repository), deco->name, &oid_tag) && @@ -411,7 +437,8 @@ void cgit_print_commit_decorations(struct commit *commit) ); break; default: - cgit_commit_link(buf, NULL, "deco", ctx.qry.head, oid_to_hex(&commit->object.oid), ctx.qry.vpath); + cgit_commit_link(buf, NULL, "deco", ctx.qry.head, oid_to_hex(&commit->object.oid), + ctx.qry.vpath); break; } deco = deco->next; @@ -435,11 +462,10 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern if (!tip) tip = ctx.qry.head; tip = disambiguate_ref(tip, &must_free_tip); - if (tip && tip[0] == '-') { - // setup_revisions() reads a leading-dash argument as an - // option, so a tip like "--output=<path>" would become a - // request to write an arbitrary file. No valid ref or object - // name begins with a dash, so refuse it. + // Checked here as well as in prepare_repo_cmd, since a caller can pass + // a tip of its own and setup_revisions reads a leading dash as an + // option. + if (tip && !cgit_valid_rev(tip)) { cgit_print_error_page(400, "Bad Request", "Invalid revision"); if (must_free_tip) free((char *)tip); @@ -448,6 +474,16 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern } strvec_push(&rev_argv, tip); + // A leading colon makes git read the path as a pathspec with magic, + // which can end the request inside git. + if (path && path[0] == ':') { + cgit_print_error_page(400, "Bad Request", "Invalid path"); + if (must_free_tip) + free((char *)tip); + strvec_clear(&rev_argv); + return; + } + if (grep && pattern && *pattern) { pattern = xstrdup(pattern); if (!strcmp(grep, "grep") || !strcmp(grep, "author") || !strcmp(grep, "committer")) { @@ -455,16 +491,17 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern } else if (!strcmp(grep, "range")) { char *arg; - // Each whitespace separated token is taken as a - // revision expression only, since a leading dash - // would reach setup_revisions as a rev-list option. - // The tip pushed above goes away, since the range - // supersedes it. + // Each whitespace separated token has to pass the same + // check as any other revision. The tip pushed above goes + // away, since the range supersedes it. strvec_pop(&rev_argv); while ((arg = next_token(&pattern))) { - if (*arg == '-') { - fprintf(stderr, "[cgit] Bad range expression: %s\n", arg); - break; + if (!valid_range_token(arg)) { + cgit_print_error_page(400, "Bad Request", "Invalid revision"); + if (must_free_tip) + free((char *)tip); + strvec_clear(&rev_argv); + return; } strvec_push(&rev_argv, arg); } @@ -504,6 +541,10 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern load_ref_decorations(NULL, DECORATE_FULL_REFS); rev.show_decorations = 1; rev.grep_filter.ignore_case = 1; + // The search is literal. A pattern would run as a regular expression + // over every message in the history, and one with a backreference + // takes exponential time to match. + rev.grep_filter.pattern_type_option = GREP_PATTERN_TYPE_FIXED; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = ctx.cfg.renamelimit; @@ -511,7 +552,13 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE); compile_grep_patterns(&rev.grep_filter); - prepare_revision_walk(&rev); + // A failed setup leaves the walk holding freed commits, so it must + // not be read from. + if (prepare_revision_walk(&rev)) { + cgit_print_error_page(500, "Internal Server Error", "Unable to read the history"); + strvec_clear(&rev_argv); + return; + } if (pager) { cgit_print_layout_start(); @@ -549,14 +596,14 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern if (ofs < 0) ofs = 0; - for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; ) { + for (i = 0; i < ofs && (commit = get_revision(&rev)); ) { if (should_show(commit, &rev)) i++; release_commit_memory(the_repository->parsed_objects, commit); commit->parents = NULL; } - for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; ) { + for (i = 0; i < cnt && (commit = get_revision(&rev)); ) { // Clearing the flag per commit keeps a commit from being // diffed twice when the file or line columns are on. counts_ready = 0; @@ -570,7 +617,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern if (pager) { print_pager(&rev, ofs, cnt); cgit_print_layout_end(); - } else if ((commit = get_revision(&rev)) != NULL) { + } else if ((commit = get_revision(&rev))) { htmlf("<tr class='nohover'><td colspan='%d'>", columns); cgit_log_link( "[...]", NULL, NULL, ctx.qry.head, NULL, ctx.qry.vpath, 0, @@ -579,8 +626,6 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern html("</td></tr>\n"); } - // The cast is safe because must_free_tip is only set for a string this - // function allocated. if (must_free_tip) free((char *)tip); } diff --git a/source/ui-patch.c b/source/ui-patch.c index baa87c9..1dcdfbe 100644 --- a/source/ui-patch.c +++ b/source/ui-patch.c @@ -35,7 +35,7 @@ static int resolve_range(const char *new_rev, const char *old_rev, } commit = lookup_commit_reference(the_repository, new_oid); if (!commit) { - cgit_print_error_page(404, "Not Found", "Bad commit reference: %s", new_rev); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", new_rev); return -1; } @@ -45,7 +45,7 @@ static int resolve_range(const char *new_rev, const char *old_rev, return -1; } if (!lookup_commit_reference(the_repository, old_oid)) { - cgit_print_error_page(404, "Not Found", "Bad commit reference: %s", old_rev); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", old_rev); return -1; } } else if (commit->parents && commit->parents->item) { @@ -75,6 +75,13 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref if (!new_rev) new_rev = ctx.qry.head; + // A leading colon makes git read the path as a pathspec with magic, + // which can end the request inside git after the headers are out. + if (prefix && prefix[0] == ':') { + cgit_print_error_page(400, "Bad Request", "Invalid path"); + return; + } + if (resolve_range(new_rev, old_rev, &new_oid, &old_oid)) return; @@ -84,10 +91,6 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref xsnprintf(rev_range, REV_RANGE_LEN, "%s..%s", oid_to_hex(&old_oid), oid_to_hex(&new_oid)); } - ctx.page.mimetype = "text/plain"; - ctx.page.filename = cgit_fmt("%s.patch", rev_range); - cgit_print_http_headers(); - if (!ctx.cfg.enable_plain_email) { rev_argv[FORMAT_ARG] = "--format=format:From %H Mon Sep 17 00:00:00 2001%n" @@ -101,11 +104,10 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref rev.show_root_diff = 1; rev.max_parents = 1; rev.diffopt.output_format |= DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH | DIFF_FORMAT_SUMMARY; + // Allocated rather than formatted into cgit_fmt's fixed buffer, because + // the path comes from the request and a long one would abort the + // process. if (prefix) - // Allocated rather than formatted into cgit_fmt's fixed - // buffer, because the path comes from the request and a long - // one would abort the process here, with the headers for a - // successful response already on the wire. rev.diffopt.stat_sep = cgit_fmtalloc("(limited to '%s')\n\n", prefix); setup_revisions(rev_argc, rev_argv, &rev, NULL); // A single commit resolves to a range starting at its parent, so this @@ -113,9 +115,18 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref // emitting a patch for the whole history. if (ctx.cfg.max_patch_count > 0) rev.max_count = ctx.cfg.max_patch_count; - prepare_revision_walk(&rev); + // A failed setup leaves the walk holding freed commits, so it must + // not be read from, and the headers wait until it has succeeded. + if (prepare_revision_walk(&rev)) { + cgit_print_error_page(500, "Internal Server Error", "Unable to read the history"); + return; + } + + ctx.page.mimetype = "text/plain"; + ctx.page.filename = cgit_fmt("%s.patch", rev_range); + cgit_print_http_headers(); - while ((commit = get_revision(&rev)) != NULL) { + while ((commit = get_revision(&rev))) { log_tree_commit(&rev, commit); // Two dashes and a space is the mail signature separator, so // git am and mail readers drop the version note below rather diff --git a/source/ui-plain.c b/source/ui-plain.c index 6b45637..959b1bf 100644 --- a/source/ui-plain.c +++ b/source/ui-plain.c @@ -15,10 +15,8 @@ #include "ui-plain.h" #include "ui-shared.h" -/* - * A listing is opened by the entry that matched and closed only once the walk - * is over, so the end of the page has to tell the three cases apart. - */ +// A listing is opened by the entry that matched and closed only once the walk +// is over, so the end of the page has to tell the three cases apart. enum response { RESPONSE_NONE, RESPONSE_BLOB, @@ -133,9 +131,9 @@ static void print_dir(const char *base, int baselen, const char *path) // cgit_plain_link is asked for with a null path. fullpath[len - 1] = 0; slash = strrchr(fullpath, '/'); - if (slash) + if (slash) { *(slash + 1) = 0; - else { + } else { free(fullpath); fullpath = NULL; } @@ -199,6 +197,7 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base, static int dir_prefix_len(const char *path) { const char *slash = strrchr(path, '/'); + if (slash) return slash - path + 1; return 0; diff --git a/source/ui-refs.c b/source/ui-refs.c index c375137..d89e3ea 100644 --- a/source/ui-refs.c +++ b/source/ui-refs.c @@ -16,11 +16,9 @@ #include "ui-refs.h" #include "ui-shared.h" -/* - * The slice of a sorted ref list that one page shows, with end one past the - * last row. size is what a full page holds, so it also decides whether the - * list needs a pager. - */ +// The slice of a sorted ref list that one page shows, with end one past the +// last row. size is what a full page holds, so it also decides whether the +// list needs a pager. struct ref_page { int size; int start; @@ -80,10 +78,12 @@ static void collect_branches(struct reflist *list) static void print_branch_header(void) { - html("<tr class='nohover'><th class='left'>Branch</th>" - "<th class='left'>Commit message</th>" - "<th class='left col-author'>Author</th>" - "<th colspan='2' class='left'>Age</th></tr>\n"); + html( + "<tr class='nohover'><th class='left'>Branch</th>" + "<th class='left'>Commit message</th>" + "<th class='left col-author'>Author</th>" + "<th colspan='2' class='left'>Age</th></tr>\n" + ); } static int print_branch(struct refinfo *ref) @@ -100,7 +100,10 @@ static int print_branch(struct refinfo *ref) if (ref->object->type == OBJ_COMMIT) { cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL); html("</td><td class='col-author'>"); - cgit_open_filter(ctx.repo->email_filter, info->author_email, "refs"); + // A filter must not be handed a NULL argument. + cgit_open_filter( + ctx.repo->email_filter, info->author_email ? info->author_email : "", "refs" + ); html_txt(info->author); cgit_close_filter(ctx.repo->email_filter); html("</td><td colspan='2'>"); @@ -124,10 +127,12 @@ static void collect_tags(struct reflist *list) static void print_tag_header(void) { - html("<tr class='nohover'><th class='left'>Tag</th>" - "<th class='left'>Download</th>" - "<th class='left col-author'>Author</th>" - "<th colspan='2' class='left'>Age</th></tr>\n"); + html( + "<tr class='nohover'><th class='left'>Tag</th>" + "<th class='left'>Download</th>" + "<th class='left col-author'>Author</th>" + "<th colspan='2' class='left'>Age</th></tr>\n" + ); } static int print_tag(struct refinfo *ref) @@ -162,7 +167,10 @@ static int print_tag(struct refinfo *ref) cgit_close_filter(ctx.repo->email_filter); } } else if (ref->object->type == OBJ_COMMIT) { - cgit_open_filter(ctx.repo->email_filter, ref->commit->author_email, "refs"); + cgit_open_filter( + ctx.repo->email_filter, ref->commit->author_email ? ref->commit->author_email : "", + "refs" + ); html_txt(ref->commit->author); cgit_close_filter(ctx.repo->email_filter); } @@ -201,7 +209,8 @@ static void print_ref_pager(int ofs, int pagesize, int count, const char *path) } htmlf("%d - %d of %d", ofs + 1, ofs + pagesize < count ? ofs + pagesize : count, count); if (ofs + pagesize < count) { - url = cgit_pageurl(ctx.qry.repo, cgit_fmt("refs/%s", path), cgit_fmt("ofs=%d", ofs + pagesize)); + url = cgit_pageurl(ctx.qry.repo, cgit_fmt("refs/%s", path), + cgit_fmt("ofs=%d", ofs + pagesize)); html(" <a href='"); html_attr(url); html("'>[next]</a>"); @@ -328,11 +337,11 @@ void cgit_print_refs(void) cgit_print_layout_start(); html("<table class='list'>\n"); - if (ctx.qry.path && starts_with(ctx.qry.path, "heads")) + if (ctx.qry.path && starts_with(ctx.qry.path, "heads")) { print_branches_page(ctx.cfg.max_ref_count); - else if (ctx.qry.path && starts_with(ctx.qry.path, "tags")) + } else if (ctx.qry.path && starts_with(ctx.qry.path, "tags")) { print_tags_page(ctx.cfg.max_ref_count); - else { + } else { cgit_print_branches(ctx.cfg.max_ref_count); html("<tr class='nohover'><td colspan='5'></td></tr>\n"); cgit_print_tags(ctx.cfg.max_ref_count); diff --git a/source/ui-repolist.c b/source/ui-repolist.c index cd22afd..031cb0d 100644 --- a/source/ui-repolist.c +++ b/source/ui-repolist.c @@ -175,8 +175,10 @@ static int any_repos_visible(void) return 0; } -// currenturl is passed in because it is the same for every heading and every -// row, and working it out here would mean an allocation and a free per cell. +/* + * currenturl is passed in because it is the same for every heading and every + * row, and working it out here would mean an allocation and a free per cell. + */ static void print_column_header(const char *title, const char *column, const char *currenturl) { htmlf("<th class='left col-%s'><a href='", column); @@ -208,7 +210,7 @@ static int section_changed(const char *section, const char *last) return 0; if (!section || !last) return 1; - return strcmp(section, last) != 0; + return strcmp(section, last); } static void print_section_row(const char *section, int columns) @@ -269,7 +271,8 @@ static void print_pager(int total, int pagelen, char *search, char *sort) for (i = 0, ofs = 0; ofs < total; i++, ofs = i * pagelen) { class = (ctx.qry.ofs == ofs) ? "current" : NULL; html("<li>"); - cgit_index_link(cgit_fmt("[%d]", i + 1), cgit_fmt("Page %d", i + 1), class, search, sort, ofs, 0); + cgit_index_link(cgit_fmt("[%d]", i + 1), cgit_fmt("Page %d", i + 1), class, search, sort, + ofs, 0); html("</li>"); } html("</ul>\n"); diff --git a/source/ui-shared.c b/source/ui-shared.c index bb786a6..25a84a6 100644 --- a/source/ui-shared.c +++ b/source/ui-shared.c @@ -27,9 +27,6 @@ #define MIN_TRUNCATE_LEN 15 #define ELLIPSIS_LEN 3 -// Lines of context git itself defaults to, which a link has no reason to name. -#define DEFAULT_DIFF_CONTEXT 3 - // Bounds the breadcrumbs, so that one request cannot turn into an unbounded // row of links. #define MAX_CRUMB_LEVELS 15 @@ -49,7 +46,7 @@ static const char *repo_basename(const char *reponame) len = strlcpy(buf, reponame, sizeof(buf)); if (len >= sizeof(buf)) - die("repo_basename: truncated repository name '%s'", reponame); + die("Repository name too long: %s", reponame); last = len - 1; while (last && buf[last] == '/') buf[last--] = '\0'; @@ -212,7 +209,7 @@ static void emit_diff_args(const char *delim) { if (ctx.qry.difftype) { html(delim); - htmlf("dt=%d", ctx.qry.difftype); + htmlf("dt=%d", (int)ctx.qry.difftype); delim = "&"; } if (ctx.qry.context > 0 && ctx.qry.context != DEFAULT_DIFF_CONTEXT) { @@ -395,9 +392,16 @@ static void add_clone_urls(void (*fn)(const char *), char *urls, char *suffix) static const struct object_id *pinned_oid(void) { static struct object_id oid; + static int resolved, pinned; struct object_id head_oid; struct commit *commit; + // The chrome asks several times per page and the answer cannot change + // within a request. + if (resolved) + return pinned ? &oid : NULL; + resolved = 1; + if (!ctx.repo || !ctx.qry.has_oid || !ctx.qry.oid || !ctx.qry.head) return NULL; if (repo_get_oid(the_repository, ctx.qry.oid, &oid) || @@ -412,6 +416,7 @@ static const struct object_id *pinned_oid(void) oidcpy(&oid, &commit->object.oid); if (oideq(&oid, &head_oid)) return NULL; + pinned = 1; return &oid; } @@ -463,6 +468,7 @@ static int print_branch_option(const struct reference *ref, void *data) { struct branch_option_data *opt = data; const char *name = ref->name; + // The switcher runs on every page, so it is bounded like the refs list. if (ctx.cfg.max_ref_count && opt->count >= ctx.cfg.max_ref_count) return -1; @@ -501,13 +507,15 @@ static void print_header(void) cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1); html(" : "); cgit_summary_link(ctx.repo->name, NULL, NULL, NULL); - } else + } else { html_txt(ctx.cfg.root_title); + } html("</h1>\n"); // A repository with no commits has no branches to list, so the - // switcher would be an empty select next to a switch button. - if (ctx.repo && ctx.env.authenticated && !ctx.empty_repo) { + // switcher would be an empty select next to a switch button, and an + // error raised before the head was resolved has nothing to select. + if (ctx.repo && ctx.env.authenticated && !ctx.empty_repo && ctx.qry.head) { const struct object_id *pinned = pinned_oid(); // Only one option may carry selected, and a pinned commit // outranks the branch it was reached from. @@ -577,7 +585,8 @@ static void print_repo_tabs(void) html("<ul>\n"); if (ctx.repo->readme.nr) { html("<li>"); - reporevlink("about", "about", "About this repository", tab_class("about"), ctx.qry.head, NULL, NULL); + reporevlink("about", "about", "About this repository", tab_class("about"), ctx.qry.head, + NULL, NULL); html("</li>\n"); } html("<li>"); @@ -677,10 +686,14 @@ static void snapshot_link(const char *name, const char *title, const char *class reporevlink("snapshot", name, title, class, head, rev, archivename); } -// The link is built out of the request in ctx.qry, so a caller that alters a -// field of ctx.qry first gets a link differing in exactly that. +/* + * The link is built out of the request in ctx.qry, so a caller that alters a + * field of ctx.qry first gets a link differing in exactly that. + */ static void self_link(const char *name, const char *title, const char *class) { + const char *rev = ctx.qry.has_oid ? ctx.qry.oid : NULL; + if (!strcmp(ctx.qry.page, "repolist")) cgit_index_link(name, title, class, ctx.qry.search, ctx.qry.sort, ctx.qry.ofs, 1); else if (!strcmp(ctx.qry.page, "summary")) @@ -688,32 +701,31 @@ static void self_link(const char *name, const char *title, const char *class) else if (!strcmp(ctx.qry.page, "tag")) cgit_tag_link(name, title, class, ctx.qry.has_oid ? ctx.qry.oid : ctx.qry.head); else if (!strcmp(ctx.qry.page, "tree")) - cgit_tree_link(name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); + cgit_tree_link(name, title, class, ctx.qry.head, rev, ctx.qry.path); else if (!strcmp(ctx.qry.page, "plain")) - cgit_plain_link(name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); + cgit_plain_link(name, title, class, ctx.qry.head, rev, ctx.qry.path); else if (!strcmp(ctx.qry.page, "blame")) - cgit_blame_link(name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); + cgit_blame_link(name, title, class, ctx.qry.head, rev, ctx.qry.path); else if (!strcmp(ctx.qry.page, "log")) cgit_log_link( - name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, - ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, ctx.qry.search, - ctx.qry.showmsg, ctx.qry.follow + name, title, class, ctx.qry.head, rev, ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, + ctx.qry.search, ctx.qry.showmsg, ctx.qry.follow ); else if (!strcmp(ctx.qry.page, "commit")) - cgit_commit_link(name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); + cgit_commit_link(name, title, class, ctx.qry.head, rev, ctx.qry.path); else if (!strcmp(ctx.qry.page, "patch")) - cgit_patch_link(name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); + cgit_patch_link(name, title, class, ctx.qry.head, rev, ctx.qry.path); else if (!strcmp(ctx.qry.page, "refs")) - cgit_refs_link(name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); + cgit_refs_link(name, title, class, ctx.qry.head, rev, ctx.qry.path); else if (!strcmp(ctx.qry.page, "snapshot")) - snapshot_link(name, title, class, ctx.qry.head, ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); + snapshot_link(name, title, class, ctx.qry.head, rev, ctx.qry.path); else if (!strcmp(ctx.qry.page, "diff")) cgit_diff_link(name, title, class, ctx.qry.head, ctx.qry.oid, ctx.qry.oid2, ctx.qry.path); else if (!strcmp(ctx.qry.page, "stats")) cgit_stats_link(name, title, class, ctx.qry.head, ctx.qry.path); else { // A page name this switch does not know still gets a plain - // repolink, which covers any simple page added later. + // repolink. repolink(title, class, ctx.qry.page, ctx.qry.head, ctx.qry.path); html("'>"); html_txt(name); @@ -746,7 +758,9 @@ static void print_path_crumbs(char *path) ctx.qry.path = old_path; } -// A reverse memchr, which glibc has as memrchr but macOS and the BSDs do not. +/* + * A reverse memchr, which glibc has as memrchr but macOS and the BSDs do not. + */ static const char *find_last_char(const char *start, const char *end, int c) { while (end > start) { @@ -759,6 +773,7 @@ static const char *find_last_char(const char *start, const char *end, int c) static void vprint_error(const char *fmt, va_list ap) { va_list cp; + html("<div class='error'>"); va_copy(cp, ap); html_vtxtf(fmt, cp); @@ -769,6 +784,7 @@ static void vprint_error(const char *fmt, va_list ap) void cgit_print_error(const char *fmt, ...) { va_list ap; + va_start(ap, fmt); vprint_error(fmt, ap); va_end(ap); @@ -819,15 +835,16 @@ char *cgit_currentfullurl(void) memcpy(query + 1, orig_query, len + 1); query[0] = '?'; match = query; - while ((match = strstr(match, "url=")) != NULL) { + while ((match = strstr(match, "url="))) { if (match[-1] == '?' || match[-1] == '&') { const char *next = strchr(match, '&'); if (next) memmove(match, next + 1, strlen(next)); else match[0] = '\0'; - } else - ++match; + } else { + match++; + } } if (!query[1]) query[0] = '\0'; @@ -845,6 +862,7 @@ char *cgit_currentfullurl(void) const char *cgit_loginurl(void) { static const char *login_url; + if (!login_url) login_url = cgit_fmtalloc("%s?p=login", root_url()); return login_url; @@ -868,7 +886,7 @@ char *cgit_fileurl(const char *reponame, const char *pagename, const char *filen // and each sink escapes the whole string for wherever it lands. if (ctx.cfg.virtual_root) { strbuf_addf(&sb, "%s%s/%s/%s", ctx.cfg.virtual_root, reponame, pagename, - (filename ? filename:"")); + filename ? filename : ""); delim = "?"; } else { strbuf_addf(&sb, "?url=%s/%s/%s", reponame, pagename, (filename ? filename : "")); @@ -974,12 +992,20 @@ void cgit_commit_link(const char *name, const char *title, const char *class, html("'>"); if (name && name[0] != '\0') { if (ctx.cfg.max_msg_len >= MIN_TRUNCATE_LEN && strlen(name) > (size_t)ctx.cfg.max_msg_len) { - html_ntxt(name, ctx.cfg.max_msg_len - ELLIPSIS_LEN); + size_t len = ctx.cfg.max_msg_len - ELLIPSIS_LEN; + + // A cut inside a multibyte character would leave an + // invalid sequence on the page. + while (len > 0 && (name[len] & 0xC0) == 0x80) + len--; + html_ntxt(name, len); html("..."); - } else + } else { html_txt(name); - } else + } + } else { html_txt("(no commit message)"); + } html("</a>"); } @@ -1151,11 +1177,9 @@ static struct cgit_repo *repo_serving_url(const char *url) return best; } -/* - * Hosts whose repository and commit pages follow a known form, so an ssh - * url can still be offered as a browser link. kernel.org runs cgit, which - * keeps the .git suffix in its own page urls. - */ +// Hosts whose repository and commit pages follow a known form, so an ssh url +// can still be offered as a browser link. kernel.org runs cgit, which keeps +// the .git suffix in its own page urls. static const struct forge { const char *host; const char *commit_seg; @@ -1336,6 +1360,7 @@ void cgit_submodule_link(const char *class, char *path, const char *rev) struct date_mode cgit_date_mode(enum date_mode_type type) { static struct date_mode mode; + mode.type = type; mode.local = ctx.cfg.local_time; return mode; @@ -1382,6 +1407,7 @@ void cgit_print_age(time_t t, int tz, time_t max_relative) void cgit_print_http_headers(void) { + ctx.page.headers_sent = 1; if (ctx.env.no_http && !strcmp(ctx.env.no_http, "1")) return; @@ -1396,7 +1422,7 @@ void cgit_print_http_headers(void) else if (ctx.page.mimetype) htmlf("Content-Type: %s\n", ctx.page.mimetype); if (ctx.page.size) - htmlf("Content-Length: %zd\n", ctx.page.size); + htmlf("Content-Length: %zu\n", ctx.page.size); if (ctx.page.filename) { html("Content-Disposition: inline; filename=\""); html_header_arg_in_quotes(ctx.page.filename); @@ -1539,6 +1565,7 @@ void cgit_print_docend(void) void cgit_print_error_page(int code, const char *msg, const char *fmt, ...) { va_list ap; + va_start(ap, fmt); cgit_vprint_error_page(code, msg, fmt, ap); va_end(ap); @@ -1550,9 +1577,21 @@ void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list // holding it. A request naming a commit not yet pushed would cache its // 404 under the never-expiring static ttl and keep serving it after // the push, so the error is rendered straight to the visitor instead. + html_flush(); cache_abandon_fill(); + // Once the status line is out the error can only join the body under + // way, and the page that hit it carries on to its own end. + if (ctx.page.headers_sent) { + vprint_error(fmt, ap); + return; + } ctx.page.status = code; ctx.page.statusmsg = msg; + // A page that had already chosen another type, such as an image + // served through the about page, still answers its error as html. + ctx.page.mimetype = "text/html"; + ctx.page.charset = PAGE_ENCODING; + ctx.page.filename = NULL; cgit_print_layout_start(); vprint_error(fmt, ap); cgit_print_layout_end(); @@ -1581,7 +1620,9 @@ void cgit_add_clone_urls(void (*fn)(const char *)) static char *http_clone_url; static int http_clone_url_scanned; -// Scheme names compare case-insensitively. +/* + * Scheme names compare case-insensitively. + */ static int is_http_url(const char *url) { return istarts_with(url, "http://") || istarts_with(url, "https://"); @@ -1745,47 +1786,60 @@ void cgit_print_filemode(unsigned short mode) html_fileperm(mode); } -/* - * One tag's claim on a snapshot stem, the stem being the tag name with any - * leading v or V removed. Claims are counted over the tag list itself rather - * than by looking refs up by name, because a loose ref lookup on a - * case-insensitive filesystem finds v1.2 when asked for V1.2 and would call - * every tag still stored loose ambiguous. - */ -struct snapshot_stem { - const char *tag; - const char *stem; - int claimants; - int is_tag; -}; +// The tag names and their stems, a stem being the name with any leading v or V +// removed, gathered once per request. Claims on a stem are counted over the +// tag list itself rather than by looking refs up by name, because a loose ref +// lookup on a case-insensitive filesystem finds v1.2 when asked for V1.2 and +// would call every tag still stored loose ambiguous. +static struct string_list tag_names = STRING_LIST_INIT_DUP; +static struct string_list tag_stems = STRING_LIST_INIT_DUP; +static int tags_collected; -static int count_stem_claimants(const struct reference *ref, void *data) +static int collect_tag(const struct reference *ref, void *data) { - struct snapshot_stem *probe = data; const char *name = ref->name; - if (!strcmp(name, probe->tag)) - probe->is_tag = 1; + string_list_append(&tag_names, name); if (name[0] == 'v' || name[0] == 'V') name++; - if (!strcmp(name, probe->stem)) - probe->claimants++; + string_list_append(&tag_stems, name); return 0; } +/* + * Whether ref is a tag whose stem no other tag shares. + */ +static int stem_is_unique(const char *ref) +{ + struct string_list_item *stem; + size_t i; + + if (!tags_collected) { + tags_collected = 1; + refs_for_each_tag_ref(get_main_ref_store(the_repository), collect_tag, NULL); + string_list_sort(&tag_names); + string_list_sort(&tag_stems); + } + if (!string_list_has_string(&tag_names, ref)) + return 0; + stem = string_list_lookup(&tag_stems, ref + 1); + if (!stem) + return 0; + // The lookup lands on any one of equal entries, so both neighbours + // are checked. + i = stem - tag_stems.items; + return (i == 0 || strcmp(tag_stems.items[i - 1].string, stem->string)) && + (i + 1 == tag_stems.nr || strcmp(tag_stems.items[i + 1].string, stem->string)); +} + static void compose_snapshot_prefix(struct strbuf *filename, const char *base, const char *ref) { // A tag named v1.2 or V1.2 gives its snapshot the prettier name 1.2, // but only where dropping the letter cannot land two different tags on // one name, so that a snapshot can still be traced back to the tag it // was made from. - if ((ref[0] == 'v' || ref[0] == 'V') && isdigit((unsigned char)ref[1])) { - struct snapshot_stem probe = { .tag = ref, .stem = ref + 1 }; - - refs_for_each_tag_ref(get_main_ref_store(the_repository), count_stem_claimants, &probe); - if (probe.is_tag && probe.claimants == 1) - ref++; - } + if ((ref[0] == 'v' || ref[0] == 'V') && isdigit((unsigned char)ref[1]) && stem_is_unique(ref)) + ref++; strbuf_addf(filename, "%s-%s", base, ref); } @@ -1815,7 +1869,10 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co html(" ("); snapshot_link("sig", NULL, NULL, NULL, NULL, filename.buf); html(")"); - } else if (starts_with(f->suffix, ".tar") && cgit_snapshot_get_sig(ref, &cgit_snapshot_formats[0])) { + } else if ( + starts_with(f->suffix, ".tar") && + cgit_snapshot_get_sig(ref, &cgit_snapshot_formats[0]) + ) { // A compressed tarball offers the signature made for // the plain tar it expands to, which is the first // format in the table. @@ -1841,7 +1898,7 @@ void cgit_set_title_from_path(const char *path) // The path is read from the end so that the title names the file first // and the directories it sits in after it. last_slash = path + strlen(path); - while ((slash = find_last_char(path, last_slash, '/')) != NULL) { + while ((slash = find_last_char(path, last_slash, '/'))) { strbuf_add(&sb, slash + 1, last_slash - slash - 1); strbuf_addstr(&sb, " < "); last_slash = slash; diff --git a/source/ui-shared.h b/source/ui-shared.h index e2d2db9..4809bc1 100644 --- a/source/ui-shared.h +++ b/source/ui-shared.h @@ -29,7 +29,9 @@ extern char *cgit_fileurl(const char *reponame, const char *pagename, const char const char *query); extern char *cgit_pageurl(const char *reponame, const char *pagename, const char *query); -// Call fn once for every URL this repository can be cloned from. +/* + * Call fn once for every URL this repository can be cloned from. + */ extern void cgit_add_clone_urls(void (*fn)(const char *)); /* @@ -39,7 +41,8 @@ extern void cgit_add_clone_urls(void (*fn)(const char *)); */ extern void cgit_index_link(const char *name, const char *title, const char *class, const char *pattern, const char *sort, int ofs, int always_root); -extern void cgit_summary_link(const char *name, const char *title, const char *class, const char *head); +extern void cgit_summary_link(const char *name, const char *title, const char *class, + const char *head); extern void cgit_tag_link(const char *name, const char *title, const char *class, const char *tag); extern void cgit_tree_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path); @@ -60,7 +63,9 @@ extern void cgit_diff_link(const char *name, const char *title, const char *clas const char *head, const char *new_rev, const char *old_rev, const char *path); extern void cgit_stats_link(const char *name, const char *title, const char *class, const char *head, const char *path); -// Names the object by type and id, abbreviated unless full is set. +/* + * Names the object by type and id, abbreviated unless full is set. + */ extern void cgit_object_link(struct object *obj, int full); extern void cgit_submodule_link(const char *class, char *path, const char *rev); @@ -88,10 +93,13 @@ extern void cgit_print_error_page(int code, const char *msg, const char *fmt, .. extern void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list ap); extern void cgit_print_pageheader(void); extern void cgit_print_filemode(unsigned short mode); -extern void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, const char *separator); +extern void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, + const char *separator); -// The name a snapshot of this repository is downloaded under, which is the -// configured prefix or the last component of the repository URL. +/* + * The name a snapshot of this repository is downloaded under, which is the + * configured prefix or the last component of the repository URL. + */ extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo); /* @@ -102,6 +110,8 @@ extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo); */ extern void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page); -// Put path in front of the page title, its last component first. +/* + * Put path in front of the page title, its last component first. + */ extern void cgit_set_title_from_path(const char *path); #endif // CGIT_UI_SHARED_H diff --git a/source/ui-snapshot.c b/source/ui-snapshot.c index a9bf858..5f2d69d 100644 --- a/source/ui-snapshot.c +++ b/source/ui-snapshot.c @@ -15,6 +15,7 @@ #include "cgit.h" #include "filter.h" #include "html.h" +#include "shared.h" #include "ui-shared.h" #include "ui-snapshot.h" @@ -61,6 +62,31 @@ static int write_zip_archive(const char *hex, const char *prefix) return write_archive_format("--format=zip", hex, prefix); } +static int program_on_path(const char *program) +{ + const char *path = getenv("PATH"); + struct strbuf full = STRBUF_INIT; + int found = 0; + + if (!path) + return 0; + while (!found) { + const char *end = strchrnul(path, ':'); + + strbuf_reset(&full); + strbuf_add(&full, path, end - path); + if (!full.len) + strbuf_addch(&full, '.'); + strbuf_addf(&full, "/%s", program); + found = !access(full.buf, X_OK); + if (!*end) + break; + path = end + 1; + } + strbuf_release(&full); + return found; +} + static int write_compressed_tar_archive(const char *hex, const char *prefix, char *argv[]) { struct cgit_exec_filter filter; @@ -76,24 +102,28 @@ static int write_compressed_tar_archive(const char *hex, const char *prefix, cha static int write_tar_gzip_archive(const char *hex, const char *prefix) { char *argv[] = { "gzip", "-n", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } static int write_tar_bzip2_archive(const char *hex, const char *prefix) { char *argv[] = { "bzip2", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } static int write_tar_lzip_archive(const char *hex, const char *prefix) { char *argv[] = { "lzip", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } static int write_tar_xz_archive(const char *hex, const char *prefix) { char *argv[] = { "xz", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } @@ -102,20 +132,21 @@ static int write_tar_zstd_archive(const char *hex, const char *prefix) // Single-threaded like the other compressors, since -T0 would let one // request pin every core. char *argv[] = { "zstd", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); } // ui-shared.c reads entry zero as the tar whose signature stands in for every // tar variant, so this order cannot change. const struct cgit_snapshot_format cgit_snapshot_formats[] = { - { ".tar", "application/x-tar", write_tar_archive }, - { ".tar.gz", "application/gzip", write_tar_gzip_archive }, - { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive }, - { ".tar.lz", "application/x-lzip", write_tar_lzip_archive }, - { ".tar.xz", "application/x-xz", write_tar_xz_archive }, - { ".tar.zst", "application/zstd", write_tar_zstd_archive }, - { ".zip", "application/zip", write_zip_archive }, - { NULL } + { ".tar", "application/x-tar", write_tar_archive, NULL }, + { ".tar.gz", "application/gzip", write_tar_gzip_archive, "gzip" }, + { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, "bzip2" }, + { ".tar.lz", "application/x-lzip", write_tar_lzip_archive, "lzip" }, + { ".tar.xz", "application/x-xz", write_tar_xz_archive, "xz" }, + { ".tar.zst", "application/zstd", write_tar_zstd_archive, "zstd" }, + { ".zip", "application/zip", write_zip_archive, NULL }, + { 0 } }; // Each tree is read the first time a signature for that format is asked for, @@ -142,7 +173,10 @@ static int resolves(const char *rev) { struct object_id oid; - return repo_get_oid(the_repository, rev, &oid) == 0; + // The name goes no further than this lookup, so a leading dash is + // fine here, while the wider syntax is refused as in cgit_valid_rev. + return !check_refname_format(rev, REFNAME_ALLOW_ONELEVEL) && + !repo_get_oid(the_repository, rev, &oid); } static const char *ref_from_filename(const struct cgit_repo *repo, const char *filename, @@ -197,14 +231,23 @@ static int send_snapshot(const struct cgit_snapshot_format *format, const char * return 1; } if (!lookup_commit_reference(the_repository, &oid)) { - cgit_print_error_page(400, "Bad Request", "Not a commit reference: %s", hex); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", hex); + return 1; + } + // A compressor that cannot run only shows up as a broken pipe once + // the tar is under way, so it is looked for while a status can still + // say so. + if (format->program && !program_on_path(format->program)) { + cgit_print_error_page(500, "Internal Server Error", "Unable to run %s", format->program); return 1; } ctx.page.mimetype = xstrdup(format->mimetype); ctx.page.filename = xstrdup(filename); cgit_print_http_headers(); init_archivers(); - format->write_func(hex, prefix); + // The archiver reads its arguments as a command line, so it gets the + // resolved id and never the name. + format->write_func(oid_to_hex(&oid), prefix); return 0; } @@ -321,6 +364,12 @@ void cgit_print_snapshot(const char *head, const char *hex, const char *filename return; } + // The name becomes the prefix of every member of the archive, and a + // revision expression such as :/pattern could carry a path in it. + if (!hex && dwim && strchr(filename, '/')) { + cgit_print_error_page(404, "Not Found", "Not found"); + return; + } if (!hex && dwim) { hex = ref_from_filename(ctx.repo, filename, f); if (!hex) { diff --git a/source/ui-snapshot.h b/source/ui-snapshot.h index c594168..412807e 100644 --- a/source/ui-snapshot.h +++ b/source/ui-snapshot.h @@ -17,6 +17,8 @@ struct cgit_snapshot_format { const char *suffix; const char *mimetype; write_archive_fn_t write_func; + // The compressor the writer runs, or NULL when it needs none. + const char *program; }; // Terminated by an entry with a NULL suffix. @@ -32,7 +34,9 @@ extern unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f); */ extern int cgit_parse_snapshots_mask(const char *str); -// The detached signature stored for this format under refs/notes, if any. +/* + * The detached signature stored for this format under refs/notes, if any. + */ extern const struct object_id *cgit_snapshot_get_sig( const char *ref, const struct cgit_snapshot_format *f); diff --git a/source/ui-ssdiff.c b/source/ui-ssdiff.c index 2ea4751..ad543f4 100644 --- a/source/ui-ssdiff.c +++ b/source/ui-ssdiff.c @@ -14,10 +14,6 @@ #include "ui-shared.h" #include "ui-ssdiff.h" -// The stylesheet sets no tab-size and tabs are expanded here rather than left -// to the browser, so this has to be the width a browser would pick on its own. -#define TAB_WIDTH 8 - // One line held back until its run ends, owning the copy taken of it. struct deferred_line { int line_no; @@ -90,13 +86,13 @@ static char *longest_common_subsequence(const char *old_line, const char *new_li while (i < old_len && j < new_len) { if (old_line[i] == new_line[j]) { lcs[pos] = old_line[i]; - pos += 1; - i += 1; - j += 1; + pos++; + i++; + j++; } else if (lcs_table[i + 1][j] >= lcs_table[i][j + 1]) { - i += 1; + i++; } else { - j += 1; + j++; } } @@ -143,9 +139,9 @@ static void print_line_with_lcs(const char *class, const char *line, const char for (i = 0; i < len; i++) { if (in_common) { - if (line[i] == lcs[matched]) - matched += 1; - else { + if (line[i] == lcs[matched]) { + matched++; + } else { in_common = 0; flush_run(&run); htmlf("<span class='%s'>", class); @@ -154,7 +150,7 @@ static void print_line_with_lcs(const char *class, const char *line, const char in_common = 1; flush_run(&run); html("</span>"); - matched += 1; + matched++; } strbuf_addch(&run, line[i]); } @@ -202,10 +198,11 @@ static void print_row(const char *class, int old_line_no, char *old_line, if (old_line_no > 0) { print_lineno_cell(cgit_get_current_old_file(), old_rev_oid, old_line_no); htmlf("<td class='%s'>", class); - } else if (old_line) + } else if (old_line) { htmlf("<td class='lineno'></td><td class='%s'>", class); - else + } else { htmlf("<td class='lineno'></td><td class='%s_dark'>", class); + } if (old_line) { if (lcs) print_line_with_lcs("del", old_line, lcs); @@ -217,10 +214,11 @@ static void print_row(const char *class, int old_line_no, char *old_line, if (new_line_no > 0) { print_lineno_cell(cgit_get_current_new_file(), new_rev_oid, new_line_no); htmlf("<td class='%s'>", class); - } else if (new_line) + } else if (new_line) { htmlf("<td class='lineno'></td><td class='%s'>", class); - else + } else { htmlf("<td class='lineno'></td><td class='%s_dark'>", class); + } if (new_line) { if (lcs) print_line_with_lcs("add", new_line, lcs); @@ -264,7 +262,7 @@ static int count_deferred(struct deferred_line *item) int count = 0; while (item) { - count += 1; + count++; item = item->next; } return count; @@ -342,9 +340,9 @@ static int hunk_start_line(const char *hunk, char marker) long line_no; p = strchr(hunk, marker); - if (p == NULL) + if (!p) return 0; - p += 1; + p++; line_no = strtol(p, NULL, 10); if (line_no < 0 || line_no > INT_MAX) return 0; @@ -383,14 +381,14 @@ void cgit_ssdiff_line_cb(char *line, int len) if (line[0] == ' ') { print_deferred_lines(); print_row("ctx", current_old_line, line, current_new_line, line, 0); - current_old_line += 1; - current_new_line += 1; + current_old_line++; + current_new_line++; } else if (line[0] == '+') { defer_line(&deferred_new, &deferred_new_last, line, current_new_line); - current_new_line += 1; + current_new_line++; } else if (line[0] == '-') { defer_line(&deferred_old, &deferred_old_last, line, current_old_line); - current_old_line += 1; + current_old_line++; } else if (line[0] == '@') { html("<tr><td colspan='4' class='hunk'>"); html_txt(line); diff --git a/source/ui-stats.c b/source/ui-stats.c index a04b189..3960b14 100644 --- a/source/ui-stats.c +++ b/source/ui-stats.c @@ -17,11 +17,9 @@ #define DEFAULT_AUTHOR_ROWS 10 -/* - * One author's share of the window. periods is keyed by the label of a - * period, with the commit count stored in the util field itself rather than - * behind another allocation. - */ +// One author's share of the window. periods is keyed by the label of a period, +// with the commit count stored in the util field itself rather than behind +// another allocation. struct authorstat { long total; struct string_list periods; @@ -144,10 +142,8 @@ static char *pretty_year(struct tm *tm) return cgit_fmt("%d", tm->tm_year + 1900); } -/* - * The order runs from the finest window to the coarsest, because a repository - * caps the page by storing an index into this table as its max-stats. - */ +// The order runs from the finest window to the coarsest, because a repository +// caps the page by storing an index into this table as its max-stats. static const struct cgit_period periods[] = { {'w', "week", 4, trunc_week, dec_week, inc_week, pretty_week}, {'m', "month", 4, trunc_month, dec_month, inc_month, pretty_month}, @@ -157,10 +153,13 @@ static const struct cgit_period periods[] = { static void window_start(const struct cgit_period *period, struct tm *tm) { - time_t now; + // Read once, so the buckets and the column labels agree on the window + // even when the clock crosses a period boundary between them. + static time_t now; int i; - time(&now); + if (!now) + time(&now); gmtime_r(&now, tm); period->trunc(tm); for (i = 1; i < period->count; i++) @@ -175,7 +174,6 @@ static void add_commit(struct string_list *authors, struct commitinfo *info, char *name, *label; struct tm date; time_t when; - uintptr_t *count; // A commit can lack an author header, so fall back rather than // handing xstrdup a NULL. @@ -194,11 +192,11 @@ static void add_commit(struct string_list *authors, struct commitinfo *info, return; period->trunc(&date); label = xstrdup(period->pretty(&date)); + // The pointer field holds the count itself. bucket = string_list_insert(&stats->periods, label); - count = (uintptr_t *)&bucket->util; - if (*count) + if (bucket->util) free(label); - (*count)++; + bucket->util = (void *)((uintptr_t)bucket->util + 1); stats->total++; } @@ -213,10 +211,11 @@ static struct string_list collect_stats(const struct cgit_period *period) struct string_list authors; struct rev_info rev; struct commit *commit; - // setup_revisions reads the entries after the double dash up to a - // NULL, past the count, so the sentinel has to stay even when the - // path fills the slot before it. - const char *argv[] = {NULL, ctx.qry.head, NULL, NULL, NULL}; + // setup_revisions reads these the way main does, skipping the first + // entry, and reads past the count up to a NULL after the double dash, + // so the sentinel has to stay even when the path fills the slot before + // it. + const char *argv[] = { NULL, ctx.qry.head, NULL, NULL, NULL }; int argc = 2; time_t since; struct tm tm; @@ -235,16 +234,19 @@ static struct string_list collect_stats(const struct cgit_period *period) rev.max_parents = 1; rev.verbose_header = 1; rev.show_root_diff = 0; - // setup_revisions reads argv the way main does and ignores the first - // entry, so the head to walk sits at argv[1]. setup_revisions(argc, argv, &rev, NULL); // Prune the walk to the displayed window instead of traversing the // whole history and discarding older commits. The check below still // bounds the period edge exactly. rev.max_age = since; - prepare_revision_walk(&rev); memset(&authors, 0, sizeof(authors)); - while ((commit = get_revision(&rev)) != NULL) { + // A failed setup leaves the walk holding freed commits, so it must + // not be read from. + if (prepare_revision_walk(&rev)) { + cgit_print_error_page(500, "Internal Server Error", "Unable to read the history"); + return authors; + } + while ((commit = get_revision(&rev))) { struct commitinfo *info = cgit_parse_commit(commit); if ((time_t)info->committer_date >= since) @@ -352,7 +354,7 @@ static void print_authors(struct string_list *authors, int max_rows, const struc if (!bucket) html("<td>0</td>"); else { - htmlf("<td>%lu</td>", (uintptr_t)bucket->util); + htmlf("<td>%ju</td>", (uintmax_t)(uintptr_t)bucket->util); total += (uintptr_t)bucket->util; } } @@ -360,7 +362,8 @@ static void print_authors(struct string_list *authors, int max_rows, const struc } if (rows < authors->nr) - print_summary_row(authors, rows, authors->nr - rows, "Others (%ld)", "left", "", "sum", labels); + print_summary_row(authors, rows, authors->nr - rows, "Others (%ld)", "left", "", "sum", + labels); print_summary_row(authors, 0, authors->nr, "Total", "total", "sum", "sum", labels); html("</table>\n"); @@ -380,7 +383,8 @@ static void print_options_form(const struct cgit_period *period, int top) html("<tr><td class='label'>Period:</td>"); html("<td class='ctrl'><select name='period'>"); for (i = 0; i < choices; i++) - html_option(cgit_fmt("%c", periods[i].code), periods[i].name, cgit_fmt("%c", period->code)); + html_option(cgit_fmt("%c", periods[i].code), periods[i].name, + cgit_fmt("%c", period->code)); html("</select></td></tr>\n"); } html("<tr><td class='label'>Authors:</td>"); diff --git a/source/ui-stats.h b/source/ui-stats.h index feee741..325e082 100644 --- a/source/ui-stats.h +++ b/source/ui-stats.h @@ -38,8 +38,10 @@ struct cgit_period { */ extern int cgit_find_stats_period(const char *expr, const struct cgit_period **period); -// The name of the period at a one based index, or an empty string when the -// index names no period. +/* + * The name of the period at a one based index, or an empty string when the + * index names no period. + */ extern const char *cgit_find_stats_periodname(int idx); extern void cgit_show_stats(void); diff --git a/source/ui-summary.c b/source/ui-summary.c index ea8f783..da530e5 100644 --- a/source/ui-summary.c +++ b/source/ui-summary.c @@ -71,10 +71,10 @@ static int path_within(const char *base, const char *path) static char *resolve_about_path(const char *filename, const char *ref, const char *path) { char *copy, *base_dir, *full_path; - char *resolved_base = NULL, *resolved_full = NULL; + char *resolved_base = NULL, *resolved_full = NULL, *resolved_repo = NULL; // dirname is allowed to write into its argument and to return a - // pointer into it, so base_dir borrows from a copy we keep alive. + // pointer into it, so base_dir borrows from a copy freed at the end. copy = xstrdup(filename); base_dir = dirname(copy); if (!strcmp(base_dir, ".") || !strcmp(base_dir, "..")) { @@ -83,13 +83,21 @@ static char *resolve_about_path(const char *filename, const char *ref, const cha return NULL; } full_path = xstrdup(path); - } else + } else { full_path = cgit_fmtalloc("%s/%s", base_dir, path); + } + // A readme in the repository directory, or above it, would open the + // repository's own files, its config and hooks among them. if (!ref) { resolved_base = realpath(base_dir, NULL); resolved_full = realpath(full_path, NULL); - if (!resolved_base || !resolved_full || !path_within(resolved_base, resolved_full)) { + resolved_repo = realpath(ctx.repo->path, NULL); + if ( + !resolved_base || !resolved_full || !resolved_repo || + path_within(resolved_base, resolved_repo) || + !path_within(resolved_base, resolved_full) + ) { free(full_path); full_path = NULL; } @@ -98,6 +106,7 @@ static char *resolve_about_path(const char *filename, const char *ref, const cha free(copy); free(resolved_base); free(resolved_full); + free(resolved_repo); return full_path; } diff --git a/source/ui-summary.h b/source/ui-summary.h index 676cc47..0f61da5 100644 --- a/source/ui-summary.h +++ b/source/ui-summary.h @@ -1,6 +1,6 @@ /* - * Declarations for the two pages a repository opens with, the summary of its - * refs and recent commits and the about page built from its readme. cmd.c + * The summary and about pages, the two a repository opens with, one listing + * its refs and recent commits and the other built from its readme. cmd.c * reaches both, and it hands the about page whatever path follows the page * name in the url, so a readme can link to a file beside it. */ diff --git a/source/ui-tag.c b/source/ui-tag.c index 56862bb..6811e0c 100644 --- a/source/ui-tag.c +++ b/source/ui-tag.c @@ -131,6 +131,16 @@ static void print_lightweight_tag(const char *revname, struct object *obj) cgit_print_layout_end(); } +/* + * A tag pointing at another tag links the inner one by object id, since no ref + * names it. + */ +static int tag_by_id(const char *name, struct object_id *oid) +{ + return !get_oid_hex(name, oid) && + odb_read_object_info(the_repository->objects, oid, NULL) == OBJ_TAG; +} + void cgit_print_tag(char *revname) { struct strbuf fullref = STRBUF_INIT; @@ -141,13 +151,14 @@ void cgit_print_tag(char *revname) revname = ctx.qry.head; strbuf_addf(&fullref, "refs/tags/%s", revname); - if (repo_get_oid(the_repository, fullref.buf, &oid)) { + if (repo_get_oid(the_repository, fullref.buf, &oid) && !tag_by_id(revname, &oid)) { cgit_print_error_page(404, "Not Found", "Bad tag reference: %s", revname); goto cleanup; } obj = parse_object(the_repository, &oid); if (!obj) { - cgit_print_error_page(500, "Internal Server Error", "Bad object id: %s", oid_to_hex(&oid)); + cgit_print_error_page(500, "Internal Server Error", "Unable to read object %s", + oid_to_hex(&oid)); goto cleanup; } if (obj->type == OBJ_TAG) diff --git a/source/ui-tree.c b/source/ui-tree.c index aab448e..4d829fe 100644 --- a/source/ui-tree.c +++ b/source/ui-tree.c @@ -20,6 +20,9 @@ #define HEXDUMP_ROW_HALF (HEXDUMP_ROW_BYTES / 2) #define HEXDUMP_GAP_WIDTH 4 +// How many single-child directories a listing row follows into one path. +#define MAX_DIR_CHAIN_LEVELS 15 + enum walk_state { WALK_LOOKING, WALK_LISTING, @@ -41,10 +44,8 @@ struct walk_tree_context { size_t entries_nr, entries_alloc; }; -/* - * The count runs past one as soon as a second entry or a file turns up, which - * ends the descent. - */ +// The count runs past one as soon as a second entry or a file turns up, which +// ends the descent. struct only_child { struct strbuf *path; struct object_id oid; @@ -56,20 +57,24 @@ struct only_child { * The anchors are handed over in batches rather than a write per line, and * never built whole, which would come to several times the blob's size. */ +static void add_linenumber(struct strbuf *numbers, unsigned long lineno) +{ + strbuf_addf(numbers, "<a id='n%lu' href='#n%lu'>%lu</a>\n", lineno, lineno, lineno); +} + static void print_linenumbers(const char *buf, unsigned long size) { - const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n"; struct strbuf numbers = STRBUF_INIT; unsigned long lineno = 0, idx = 0; if (size) { - strbuf_addf(&numbers, numberfmt, ++lineno); + add_linenumber(&numbers, ++lineno); // The newline that ends the last line must not open a line of // its own, so the final byte is left out of the scan. while (idx < size - 1) { if (buf[idx] == '\n') { - strbuf_addf(&numbers, numberfmt, ++lineno); + add_linenumber(&numbers, ++lineno); if (numbers.len >= HTML_BATCH) { html_raw(numbers.buf, numbers.len); strbuf_reset(&numbers); @@ -136,7 +141,7 @@ static void print_binary_buffer(char *buf, unsigned long size) for (idx = 0; idx < HEXDUMP_ROW_BYTES && offset + idx < size; idx++) { int gap = idx == HEXDUMP_ROW_HALF ? HEXDUMP_GAP_WIDTH : 1; - strbuf_addf(&row, "%*s%02x", gap, "", buf[idx] & 0xff); + strbuf_addf(&row, "%*s%02x", gap, "", (unsigned char)buf[idx]); } strbuf_addstr(&row, " </td><td class='hex'>"); html_raw(row.buf, row.len); @@ -166,7 +171,7 @@ static bool print_object(const struct object_id *oid, const char *path, type = odb_read_object_info(the_repository->objects, oid, &size); if (type == OBJ_BAD) { - cgit_print_error_page(404, "Not Found", "Bad object name: %s", oid_to_hex(oid)); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", oid_to_hex(oid)); return false; } @@ -180,7 +185,8 @@ static bool print_object(const struct object_id *oid, const char *path, buf = odb_read_object(the_repository->objects, oid, &type, &size); if (!buf) { - cgit_print_error_page(500, "Internal Server Error", "Error reading object %s", oid_to_hex(oid)); + cgit_print_error_page(500, "Internal Server Error", "Unable to read object %s", + oid_to_hex(oid)); return false; } // buffer_is_binary only sniffs the front of the blob, and a NUL past @@ -239,10 +245,13 @@ static void print_dir_chain(const struct object_id *oid, char *name, char *rev, struct pathspec paths = { .nr = 0 }; + int levels = 0; oidcpy(&child.oid, oid); - while (child.count == 1) { + // Each level links the whole path so far, so a chain of thousands of + // single directories would make one row quadratic in size. + while (child.count == 1 && levels++ < MAX_DIR_CHAIN_LEVELS) { cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev, fullpath->buf); tree = lookup_tree(the_repository, &child.oid); @@ -305,17 +314,22 @@ static void print_ls_row(const struct object_id *oid, const char *pathname, strbuf_addf(&class, " %s", ext + 1); cgit_tree_link(name, NULL, class.buf, ctx.qry.head, walk->rev, fullpath.buf); } - if (S_ISLNK(mode)) { + // A target longer than any path is no link, and reading it would pull + // a blob of any size into memory. + if (S_ISLNK(mode) && size <= PATH_MAX) { html(" -> "); buf = odb_read_object(the_repository->objects, oid, &type, &size); if (!buf) { - htmlf("Error reading object: %s", oid_to_hex(oid)); + htmlf("Unable to read object %s", oid_to_hex(oid)); goto cleanup; } strbuf_addbuf(&linkpath, &fullpath); strbuf_addf(&linkpath, "/../%s", buf); - strbuf_normalize_path(&linkpath); - cgit_tree_link(buf, NULL, class.buf, ctx.qry.head, walk->rev, linkpath.buf); + // A target climbing above the tree root has nothing to link to. + if (strbuf_normalize_path(&linkpath)) + html_txt(buf); + else + cgit_tree_link(buf, NULL, class.buf, ctx.qry.head, walk->rev, linkpath.buf); free(buf); strbuf_release(&linkpath); } @@ -455,8 +469,10 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base, return 0; } -// Either argument may be null, in which case the head of the current query -// stands in for the revision and the listing starts at the root of the tree. +/* + * Either argument may be null, in which case the head of the current query + * stands in for the revision and the listing starts at the root of the tree. + */ void cgit_print_tree(const char *rev, char *path) { struct object_id oid; @@ -482,18 +498,18 @@ void cgit_print_tree(const char *rev, char *path) rev = ctx.qry.head; if (repo_get_oid(the_repository, rev, &oid)) { - cgit_print_error_page(404, "Not Found", "Invalid revision name: %s", rev); + cgit_print_error_page(404, "Not Found", "Bad object id: %s", rev); return; } commit = lookup_commit_reference(the_repository, &oid); if (!commit || repo_parse_commit(the_repository, commit)) { - cgit_print_error_page(404, "Not Found", "Invalid commit reference: %s", rev); + cgit_print_error_page(404, "Not Found", "Not a commit: %s", rev); return; } walk.rev = xstrdup(rev); - if (path == NULL) { + if (!path) { ls_tree(get_commit_tree_oid(commit), &walk); goto cleanup; } @@ -502,10 +518,11 @@ void cgit_print_tree(const char *rev, char *path) if (walk.state == WALK_LISTING) { ls_flush(&walk); ls_tail(); - } else if (walk.state == WALK_BLOB_SHOWN) + } else if (walk.state == WALK_BLOB_SHOWN) { cgit_print_layout_end(); - else if (walk.state == WALK_LOOKING) + } else if (walk.state == WALK_LOOKING) { cgit_print_error_page(404, "Not Found", "Path not found"); + } // WALK_ERROR_SHOWN is left alone, since the error page print_object // put out is already complete. |
