Diff options
Diffstat (limited to '')
| -rw-r--r-- | source/ui-shared.c | 689 |
1 file changed, 308 insertions, 381 deletions
diff --git a/source/ui-shared.c b/source/ui-shared.c index 936a6a4..81db404 100644 --- a/source/ui-shared.c +++ b/source/ui-shared.c @@ -1,17 +1,17 @@ /* * The output every cgit page has in common. A link from one page to another is - * built here because only this file knows whether a site addresses a request - * as a path below a virtual root or as a query string, and the two are spelled - * differently down to the separator between arguments. The headers, the - * document head, the header block, the footer, the error page and the age - * column are here so that a page handler is left with only its own body to - * produce. + * built here, as a path below the virtual root followed by the arguments the + * target page needs, so that every page spells its urls the same way. The + * headers, the document head, the header block, the footer, the error page and + * the age column are here so that a page handler is left with only its own body + * to produce. */ #define USE_THE_REPOSITORY_VARIABLE #include "cache.h" #include "cgit.h" +#include "filter.h" #include "html.h" #include "shared.h" #include "submodule-config.h" @@ -35,39 +35,17 @@ // into a tab title while it still leaves room to spare in that buffer. #define MAX_TITLE_PATH_LEN 256 -static const char *repo_basename(const char *reponame) +/* + * Write one argument of a query string after delim, and return the delimiter + * for the next one. + */ +static const char *url_arg(const char *delim, const char *name, const char *value) { - // The result lives in a static buffer, so it is only good until the - // next call. - static char buf[1024]; - const char *slash; - size_t len; - int last; - - len = strlcpy(buf, reponame, sizeof(buf)); - if (len >= sizeof(buf)) - die("Repository name too long: %s", reponame); - last = len - 1; - while (last && buf[last] == '/') - buf[last--] = '\0'; - if (last >= 3 && starts_with(&buf[last - 3], ".git")) { - last -= 3; - buf[last--] = '\0'; - } - while (last && buf[last] == '/') - buf[last--] = '\0'; - slash = strrchr(buf, '/'); - if (slash) - return ++slash; - return buf; -} - -static const char *root_url(void) -{ - if (ctx.cfg.virtual_root) - return ctx.cfg.virtual_root; - else - return ctx.cfg.script_name; + html(delim); + html(name); + html("="); + html_url_arg(value); + return "&"; } static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root) @@ -75,7 +53,7 @@ static void site_url(const char *page, const char *search, const char *sort, int const char *delim = "?"; if (always_root || page) - html_attr(root_url()); + html_attr(ctx.cfg.virtual_root); else { char *currenturl = cgit_currenturl(); html_attr(currenturl); @@ -86,42 +64,20 @@ static void site_url(const char *page, const char *search, const char *sort, int htmlf("?p=%s", page); delim = "&"; } - if (search) { - html(delim); - html("q="); - html_url_arg(search); - delim = "&"; - } - if (sort) { - html(delim); - html("s="); - html_url_arg(sort); - delim = "&"; - } - if (ofs) { - html(delim); - htmlf("ofs=%d", ofs); - } + if (search) + delim = url_arg(delim, "q", search); + if (sort) + delim = url_arg(delim, "s", sort); + if (ofs) + htmlf("%sofs=%d", delim, ofs); } /* - * Write the class of a link, and mark the link as the current one for - * assistive technology when the class says it is the active tab or the + * Open an anchor up to the start of its href, marking the link as the current + * one for assistive technology when the class says it is the active tab or the * current page of a pager. */ -static void link_class(const char *class) -{ - if (!class) - return; - html(" class='"); - html_attr(class); - html("'"); - if (!strcmp(class, "active") || !strcmp(class, "current")) - html(" aria-current='page'"); -} - -static void site_link(const char *page, const char *name, const char *title, - const char *class, const char *search, const char *sort, int ofs, int always_root) +static void open_link(const char *title, const char *class) { html("<a"); if (title) { @@ -129,8 +85,20 @@ static void site_link(const char *page, const char *name, const char *title, html_attr(title); html("'"); } - link_class(class); + if (class) { + html(" class='"); + html_attr(class); + html("'"); + if (!strcmp(class, "active") || !strcmp(class, "current")) + html(" aria-current='page'"); + } html(" href='"); +} + +static void site_link(const char *page, const char *name, const char *title, + const char *class, const char *search, const char *sort, int ofs, int always_root) +{ + open_link(title, class); site_url(page, search, sort, ofs, always_root); html("'>"); html_txt(name); @@ -138,36 +106,20 @@ static void site_link(const char *page, const char *name, const char *title, } /* - * Write the url naming the repository, the page and the path, and return - * whether the url has opened its query string already. + * Write the url naming the repository, the page and the path. */ -static int write_page_url(const char *page, const char *path) -{ - if (ctx.cfg.virtual_root) { - html_url_path(ctx.cfg.virtual_root); - html_url_path(ctx.repo->url); - if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') - html("/"); - if (page) { - html_url_path(page); - html("/"); - if (path) - html_url_path(path); - } - return 0; - } - html_url_path(ctx.cfg.script_name); - html("?url="); - html_url_arg(ctx.repo->url); +static void write_page_url(const char *page, const char *path) +{ + html_url_path(ctx.cfg.virtual_root); + html_url_path(ctx.repo->url); if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') html("/"); if (page) { - html_url_arg(page); + html_url_path(page); html("/"); if (path) - html_url_arg(path); + html_url_path(path); } - return 1; } /* @@ -187,34 +139,19 @@ static int head_is_named(const char *head) static const char *repolink(const char *title, const char *class, const char *page, const char *head, const char *path) { - const char *delim; + const char *delim = "?"; - html("<a"); - if (title) { - html(" title='"); - html_attr(title); - html("'"); - } - link_class(class); - html(" href='"); - delim = write_page_url(page, path) ? "&" : "?"; - if (head_is_named(head)) { - html(delim); - html("h="); - html_url_arg(head); - delim = "&"; - } - return cgit_fmt("%s", delim); + open_link(title, class); + write_page_url(page, path); + if (head_is_named(head)) + delim = url_arg(delim, "h", head); + return delim; } static const char *emit_rev_arg(const char *delim, const char *rev) { - if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) { - html(delim); - html("id="); - html_url_arg(rev); - return "&"; - } + if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) + return url_arg(delim, "id", rev); return delim; } @@ -233,14 +170,11 @@ static void reporevlink(const char *page, const char *name, const char *title, static void emit_diff_args(const char *delim) { if (ctx.qry.difftype) { - html(delim); - htmlf("dt=%d", (int)ctx.qry.difftype); + htmlf("%sdt=%d", delim, (int)ctx.qry.difftype); delim = "&"; } if (ctx.qry.context > 0 && ctx.qry.context != DEFAULT_DIFF_CONTEXT) { - html(delim); - html("context="); - htmlf("%d", ctx.qry.context); + htmlf("%scontext=%d", delim, ctx.qry.context); delim = "&"; } if (ctx.qry.ignorews) { @@ -254,51 +188,6 @@ static void emit_diff_args(const char *delim) } } -static struct string_list_item *lookup_path(struct string_list *list, const char *path) -{ - struct string_list_item *item; - - while (path && path[0]) { - if ((item = string_list_lookup(list, path))) - return item; - if (!(path = strchr(path, '/'))) - break; - path++; - } - return NULL; -} - -/* - * A repository can supply the module-link template through a repo-local - * cgitrc, so the %s placeholders are expanded here instead of the template - * being handed to printf, where a surplus conversion would read past the - * argument list. - */ -static void emit_module_link(const char *tmpl, const char **args, int nargs) -{ - struct strbuf sb = STRBUF_INIT; - int used = 0; - - while (*tmpl) { - if (*tmpl != '%') { - strbuf_addch(&sb, *tmpl++); - continue; - } - tmpl++; - if (*tmpl == '%') { - strbuf_addch(&sb, '%'); - tmpl++; - } else if (*tmpl == 's' && used < nargs) { - strbuf_addstr(&sb, args[used++]); - tmpl++; - } else { - strbuf_addch(&sb, '%'); - } - } - html_attr(sb.buf); - strbuf_release(&sb); -} - static struct date_mode configured_date_mode(void) { struct date_mode mode = ctx.cfg.date_mode; @@ -382,23 +271,6 @@ static void emit_js_link(const char *src) html("' defer></script>\n"); } -static void add_clone_urls(void (*fn)(const char *), char *urls, char *suffix) -{ - struct strbuf **url_list = strbuf_split_str(urls, ' ', 0); - int i; - - for (i = 0; url_list[i]; i++) { - strbuf_rtrim(url_list[i]); - if (url_list[i]->len == 0) - continue; - if (suffix && *suffix) - strbuf_addf(url_list[i], "/%s", suffix); - fn(url_list[i]->buf); - } - - strbuf_list_free(url_list); -} - /* * The id parameter pins a page to one commit while the rest of the chrome * keeps naming the branch. Answer that commit, or null when the request @@ -519,7 +391,7 @@ static void print_header(void) if (logo_link && *logo_link) html_attr(logo_link); else - html_attr(root_url()); + html_attr(ctx.cfg.virtual_root); html("'><img src='"); html_attr(logo); html("' alt='cgit logo'></a>\n"); @@ -551,7 +423,7 @@ static void print_header(void) // Switching branch means leaving the pinned commit behind, so // the id fields stay out of the form. ctx.qry.oid = ctx.qry.oid2 = NULL; - cgit_add_hidden_formfields(0, 1, ctx.qry.page); + cgit_add_hidden_formfields(0, 1); ctx.qry.oid = oid; ctx.qry.oid2 = oid2; html("<select name='h' aria-label='Branch'>\n"); @@ -655,10 +527,9 @@ static void print_repo_tabs(void) static void print_repo_search(void) { html("<form class='search' role='search' method='get' action='"); - if (ctx.cfg.virtual_root) - write_page_url("log", ctx.qry.vpath); + write_page_url("log", ctx.qry.vpath); html("'>\n"); - cgit_add_hidden_formfields(1, 0, "log"); + cgit_add_hidden_formfields(1, 0); html("<select name='qt' aria-label='Search type'>\n"); html_option("grep", "log msg", ctx.qry.grep); html_option("author", "author", ctx.qry.grep); @@ -743,14 +614,8 @@ static void self_link(const char *name, const char *title, const char *class) 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. - repolink(title, class, ctx.qry.page, ctx.qry.head, ctx.qry.path); - html("'>"); - html_txt(name); - html("</a>"); - } + else + reporevlink(ctx.qry.page, name, title, class, ctx.qry.head, NULL, ctx.qry.path); } static void print_path_crumbs(char *path) @@ -778,26 +643,10 @@ 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. - */ -static const char *find_last_char(const char *start, const char *end, int c) -{ - while (end > start) { - if (*--end == c) - return end; - } - return NULL; -} - 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); - va_end(cp); + html_vtxtf(fmt, ap); html("</div>\n"); } @@ -826,18 +675,18 @@ char *cgit_hosturl(void) return NULL; if (!ctx.env.server_port || atoi(ctx.env.server_port) == DEFAULT_HTTP_PORT) return xstrdup(ctx.env.server_name); - return cgit_fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port); + return xstrfmt("%s:%s", ctx.env.server_name, ctx.env.server_port); } char *cgit_currenturl(void) { - const char *root = root_url(); + const char *root = ctx.cfg.virtual_root; if (!ctx.qry.url) return xstrdup(root); if (root[0] && root[strlen(root) - 1] == '/') - return cgit_fmtalloc("%s%s", root, ctx.qry.url); - return cgit_fmtalloc("%s/%s", root, ctx.qry.url); + return xstrfmt("%s%s", root, ctx.qry.url); + return xstrfmt("%s/%s", root, ctx.qry.url); } char *cgit_currentquery(void) @@ -872,16 +721,11 @@ char *cgit_currentquery(void) char *cgit_currentfullurl(void) { - const char *root = root_url(); + char *url = cgit_currenturl(); char *query = cgit_currentquery(); - char *ret; + char *ret = xstrfmt("%s%s", url, query); - if (!ctx.qry.url) - ret = cgit_fmtalloc("%s%s", root, query); - else if (root[0] && root[strlen(root) - 1] == '/') - ret = cgit_fmtalloc("%s%s%s", root, ctx.qry.url, query); - else - ret = cgit_fmtalloc("%s/%s%s", root, ctx.qry.url, query); + free(url); free(query); return ret; } @@ -891,7 +735,7 @@ const char *cgit_loginurl(void) static const char *login_url; if (!login_url) - login_url = cgit_fmtalloc("%s?p=login", root_url()); + login_url = xstrfmt("%s?p=login", ctx.cfg.virtual_root); return login_url; } @@ -899,14 +743,9 @@ char *cgit_repourl(const char *reponame) { struct strbuf sb = STRBUF_INIT; - if (ctx.cfg.virtual_root) { - strbuf_addstr(&sb, ctx.cfg.virtual_root); - cgit_add_url_path(&sb, reponame); - strbuf_addch(&sb, '/'); - } else { - strbuf_addstr(&sb, "?r="); - cgit_add_url_path(&sb, reponame); - } + strbuf_addstr(&sb, ctx.cfg.virtual_root); + cgit_add_url_path(&sb, reponame); + strbuf_addch(&sb, '/'); return strbuf_detach(&sb, NULL); } @@ -914,21 +753,12 @@ char *cgit_fileurl(const char *reponame, const char *pagename, const char *filen const char *query) { struct strbuf sb = STRBUF_INIT; - const char *delim; - // The query joiner is a bare ampersand, because each sink escapes the - // whole string for wherever it lands. - if (ctx.cfg.virtual_root) { - strbuf_addstr(&sb, ctx.cfg.virtual_root); - delim = "?"; - } else { - strbuf_addstr(&sb, "?url="); - delim = "&"; - } + strbuf_addstr(&sb, ctx.cfg.virtual_root); cgit_add_url_path(&sb, reponame); strbuf_addf(&sb, "/%s/%s", pagename, filename ? filename : ""); if (query) - strbuf_addf(&sb, "%s%s", delim, query); + strbuf_addf(&sb, "?%s", query); return strbuf_detach(&sb, NULL); } @@ -939,26 +769,32 @@ char *cgit_pageurl(const char *reponame, const char *pagename, const char *query void cgit_tree_url(const char *head, struct strbuf *base, struct strbuf *query) { - int query_open; - html_capture_begin(base); - query_open = write_page_url("tree", NULL); + write_page_url("tree", NULL); html_capture_end(); if (!head_is_named(head)) return; html_capture_begin(query); - html(query_open ? "&" : "?"); - html("h="); + html("?h="); html_url_arg(head); html_capture_end(); } const char *cgit_snapshot_prefix(const struct cgit_repo *repo) { + static struct strbuf name = STRBUF_INIT; + const char *slash; + if (repo->snapshot_prefix) return repo->snapshot_prefix; - return repo_basename(repo->url); + strbuf_reset(&name); + strbuf_addstr(&name, repo->url); + strbuf_trim_trailing_dir_sep(&name); + strbuf_strip_suffix(&name, ".git"); + strbuf_trim_trailing_dir_sep(&name); + slash = strrchr(name.buf, '/'); + return slash ? slash + 1 : name.buf; } void cgit_index_link(const char *name, const char *title, const char *class, @@ -1004,18 +840,11 @@ void cgit_log_link(const char *name, const char *title, const char *class, delim = repolink(title, class, "log", head, path); delim = emit_rev_arg(delim, rev); if (grep && pattern) { - html(delim); - html("qt="); - html_url_arg(grep); - delim = "&"; - html(delim); - html("q="); - html_url_arg(pattern); + delim = url_arg(delim, "qt", grep); + delim = url_arg(delim, "q", pattern); } if (ofs > 0) { - html(delim); - html("ofs="); - htmlf("%d", ofs); + htmlf("%sofs=%d", delim, ofs); delim = "&"; } if (showmsg) { @@ -1067,12 +896,8 @@ void cgit_diff_link(const char *name, const char *title, const char *class, delim = repolink(title, class, "diff", head, path); delim = emit_rev_arg(delim, new_rev); - if (old_rev) { - html(delim); - html("id2="); - html_url_arg(old_rev); - delim = "&"; - } + if (old_rev) + delim = url_arg(delim, "id2", old_rev); emit_diff_args(delim); html("'>"); html_txt(name); @@ -1094,16 +919,13 @@ void cgit_stats_link(const char *name, const char *title, const char *class, void cgit_object_link(struct object *obj, int full) { const char *page; - char *shortrev, *fullrev, *name; + char *fullrev, *name; fullrev = oid_to_hex(&obj->oid); - if (full) { + if (full) name = cgit_fmt("%s %s", type_name(obj->type), fullrev); - } else { - shortrev = xstrdup(fullrev); - shortrev[SHORT_HASH_LEN] = '\0'; - name = cgit_fmt("%s %s...", type_name(obj->type), shortrev); - } + else + name = cgit_fmt("%s %.*s...", type_name(obj->type), SHORT_HASH_LEN, fullrev); if (obj->type == OBJ_COMMIT) { cgit_commit_link(name, NULL, NULL, ctx.qry.head, fullrev, NULL); return; @@ -1133,13 +955,6 @@ static const struct object_id *page_treeish(void) return state > 0 ? &oid : NULL; } -static void strip_git_suffix(struct strbuf *sb) -{ - while (sb->len && sb->buf[sb->len - 1] == '/') - strbuf_setlen(sb, sb->len - 1); - strbuf_strip_suffix(sb, ".git"); -} - /* * Reduces a submodule url to the path it names on its host, so the tail can * be compared against the urls this instance serves. A relative url points @@ -1176,7 +991,8 @@ static char *submodule_url_path(const char *url) strbuf_addstr(&sb, url); } - strip_git_suffix(&sb); + strbuf_trim_trailing_dir_sep(&sb); + strbuf_strip_suffix(&sb, ".git"); while (sb.len && sb.buf[0] == '/') strbuf_remove(&sb, 0, 1); if (!sb.len) { @@ -1274,7 +1090,7 @@ static void gitmodules_link(const char *path, const char *rev, char **module, target = repo_serving_url(url); if (target) { - char *query = cgit_fmtalloc("id=%s", rev); + char *query = xstrfmt("id=%s", rev); *module = cgit_pageurl(target->url, "tree", query); *commit = cgit_pageurl(target->url, "commit", query); @@ -1287,8 +1103,7 @@ static void gitmodules_link(const char *path, const char *rev, char **module, forge = forge_for_host(rest, strcspn(rest, "/")); if (forge) { strbuf_addstr(&sb, url); - while (sb.len && sb.buf[sb.len - 1] == '/') - strbuf_setlen(&sb, sb.len - 1); + strbuf_trim_trailing_dir_sep(&sb); if (!forge->keep_dot_git) strbuf_strip_suffix(&sb, ".git"); strbuf_addstr(&sb, forge->commit_seg); @@ -1310,8 +1125,7 @@ static void gitmodules_link(const char *path, const char *rev, char **module, while (*rest == '/') rest++; strbuf_addf(&sb, "https://%.*s/%s", (int)(colon - host), host, rest); - while (sb.len && sb.buf[sb.len - 1] == '/') - strbuf_setlen(&sb, sb.len - 1); + strbuf_trim_trailing_dir_sep(&sb); if (!forge->keep_dot_git) strbuf_strip_suffix(&sb, ".git"); *module = xstrdup(sb.buf); @@ -1325,6 +1139,51 @@ static void gitmodules_link(const char *path, const char *rev, char **module, *tooltip = url; } +static struct string_list_item *lookup_path(struct string_list *list, const char *path) +{ + struct string_list_item *item; + + while (path && path[0]) { + if ((item = string_list_lookup(list, path))) + return item; + if (!(path = strchr(path, '/'))) + break; + path++; + } + return NULL; +} + +/* + * A repository can supply the module-link template through a repo-local + * cgitrc, so the %s placeholders are expanded here instead of the template + * being handed to printf, where a surplus conversion would read past the + * argument list. + */ +static void emit_module_link(const char *tmpl, const char **args, int nargs) +{ + struct strbuf sb = STRBUF_INIT; + int used = 0; + + while (*tmpl) { + if (*tmpl != '%') { + strbuf_addch(&sb, *tmpl++); + continue; + } + tmpl++; + if (*tmpl == '%') { + strbuf_addch(&sb, '%'); + tmpl++; + } else if (*tmpl == 's' && used < nargs) { + strbuf_addstr(&sb, args[used++]); + tmpl++; + } else { + strbuf_addch(&sb, '%'); + } + } + html_attr(sb.buf); + strbuf_release(&sb); +} + void cgit_submodule_link(const char *class, char *path, const char *rev) { struct string_list *list; @@ -1355,10 +1214,7 @@ void cgit_submodule_link(const char *class, char *path, const char *rev) name = name ? name + 1 : path; if (item || ctx.repo->module_link || module) { - html("<a "); - if (class) - htmlf("class='%s' ", class); - html("href='"); + open_link(NULL, class); if (item) { const char *args[] = { rev }; emit_module_link(item->util, args, 1); @@ -1373,8 +1229,11 @@ void cgit_submodule_link(const char *class, char *path, const char *rev) html("</a>"); } else { html("<span"); - if (class) - htmlf(" class='%s'", class); + if (class) { + html(" class='"); + html_attr(class); + html("'"); + } if (tooltip) { html(" title='"); html_attr(tooltip); @@ -1404,9 +1263,8 @@ 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; + struct date_mode mode = date_mode_from_type(type); - mode.type = type; mode.local = ctx.cfg.local_time; return mode; } @@ -1450,6 +1308,42 @@ void cgit_print_age(time_t t, int tz, time_t max_relative) print_rel_date(t, tz, secs * 1.0 / SECONDS_PER_YEAR, "age-years", "years"); } +void cgit_print_date(timestamp_t date, int tz) +{ + html("<time datetime='"); + html_attr(show_date(date, tz, cgit_date_mode(DATE_ISO8601_STRICT))); + html("'>"); + html_txt(show_date(date, tz, cgit_date_mode(DATE_ISO8601))); + html("</time>"); +} + +void cgit_print_line_anchors(size_t first, size_t last) +{ + struct strbuf numbers = STRBUF_INIT; + size_t lineno; + + for (lineno = first; lineno <= last; lineno++) { + strbuf_addf(&numbers, "<a id='n%zu' href='#n%zu'>%zu</a>\n", lineno, lineno, lineno); + if (numbers.len >= HTML_BATCH) { + html_raw(numbers.buf, numbers.len); + strbuf_reset(&numbers); + } + } + html_raw(numbers.buf, numbers.len); + strbuf_release(&numbers); +} + +void cgit_print_person(const char *name, const char *email, const char *origin, int show_email) +{ + cgit_open_filter(ctx.repo->email_filter, email ? email : "", origin); + html_txt(name); + if (show_email && email && ctx.cfg.enable_plain_email) { + html(" "); + html_txt(email); + } + cgit_close_filter(ctx.repo->email_filter); +} + /* * Whether a content type carries text a charset applies to, which is every * text type and the XML types of RFC 7303, the atom feed among them. @@ -1518,11 +1412,12 @@ void cgit_redirect(const char *url, const char *query, bool permanent) html("\n\n"); } -void cgit_print_docstart(void) +static void print_docstart(void) { const struct object_id *pinned; struct string_list_item *item; - char *host = cgit_hosturl(); + const char *desc; + char *host; if (ctx.cfg.embedded) { if (ctx.cfg.header) @@ -1549,13 +1444,10 @@ void cgit_print_docstart(void) html("</title>\n"); // The placeholder a scanned repository carries instead of a description // is left out, since it describes nothing. - if (ctx.repo && ctx.repo->desc && *ctx.repo->desc && ctx.repo->desc != cgit_default_repo_desc) { - html("<meta name='description' content='"); - html_attr(ctx.repo->desc); - html("'>\n"); - } else if (!ctx.repo && ctx.cfg.root_desc && *ctx.cfg.root_desc) { + desc = ctx.repo ? ctx.repo->desc : ctx.cfg.root_desc; + if (desc && *desc && desc != cgit_default_repo_desc) { html("<meta name='description' content='"); - html_attr(ctx.cfg.root_desc); + html_attr(desc); html("'>\n"); } if (ctx.cfg.enable_version_info) @@ -1585,14 +1477,15 @@ void cgit_print_docstart(void) html_attr(ctx.cfg.favicon); html("'>\n"); } - if (host && ctx.repo && ctx.qry.head) { + if (ctx.repo && ctx.qry.head && (host = cgit_hosturl())) { html("<link rel='alternate' title='Atom feed' href='"); html(cgit_httpscheme()); html_attr(host); - html(write_page_url("atom", ctx.qry.vpath) ? "&" : "?"); - html("h="); + write_page_url("atom", ctx.qry.vpath); + html("?h="); html_url_arg(ctx.qry.head); html("' type='application/atom+xml'>\n"); + free(host); } if (ctx.repo) cgit_add_clone_urls(print_rel_vcs_link); @@ -1604,52 +1497,24 @@ void cgit_print_docstart(void) html("<body>\n"); if (ctx.cfg.header) html_include(ctx.cfg.header); - free(host); -} - -void cgit_print_docend(void) -{ - html("</main>\n"); - if (ctx.cfg.embedded) { - html("</div>\n"); - if (ctx.cfg.footer) - html_include(ctx.cfg.footer); - return; - } - if (ctx.cfg.footer) - html_include(ctx.cfg.footer); - else if (ctx.cfg.enable_version_info) - htmlf("<footer class='footer'>generated by " - "<a href='https://github.com/brycekwon/cgit'>cgit %s</a> " - "(<a href='https://git-scm.com/'>git %s</a>)</footer>\n", - cgit_version, git_version_string); - else - html("<footer class='footer'>generated by <a href='https://github.com/brycekwon/cgit'>cgit</a></footer>\n"); - html("</div>\n"); - html("</body>\n</html>\n"); } 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); -} - -void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list ap) -{ // An error page describes a condition that may not outlast the slot // 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(); + va_start(ap, fmt); // 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); + va_end(ap); return; } ctx.page.status = code; @@ -1661,6 +1526,7 @@ void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list ctx.page.filename = NULL; cgit_print_layout_start(); vprint_error(fmt, ap); + va_end(ap); cgit_print_layout_end(); } @@ -1670,16 +1536,64 @@ void cgit_print_blob_too_large(size_t size) size / 1024, ctx.cfg.max_blob_size); } -void cgit_print_layout_start(void) +struct commit *cgit_get_commit(const char *rev, struct object_id *oid) { - cgit_print_http_headers(); - cgit_print_docstart(); - cgit_print_pageheader(); + struct commit *commit; + + if (repo_get_oid(the_repository, rev, oid)) { + cgit_print_error_page(404, "Not Found", "Bad object id: %s", rev); + return NULL; + } + // lookup_commit_reference parses the commit, and fails on one git + // cannot parse. + commit = lookup_commit_reference(the_repository, oid); + if (!commit) + cgit_print_error_page(404, "Not Found", "Not a commit: %s", rev); + return commit; } -void cgit_print_layout_end(void) +int cgit_get_old_oid(const char *old_rev, const struct commit *commit, struct object_id *oid) +{ + if (old_rev) + return cgit_get_commit(old_rev, oid) ? 0 : -1; + if (commit->parents && commit->parents->item) + oidcpy(oid, &commit->parents->item->object.oid); + else + oidclr(oid, the_repository->hash_algo); + return 0; +} + +int cgit_prepare_walk(struct rev_info *rev) { - cgit_print_docend(); + if (!prepare_revision_walk(rev)) + return 0; + cgit_print_error_page(500, "Internal Server Error", "Unable to read the history"); + return -1; +} + +int cgit_refuse_pathspec_magic(const char *path) +{ + if (!path || path[0] != ':') + return 0; + cgit_print_error_page(400, "Bad Request", "Invalid path"); + return -1; +} + +static void add_clone_urls(void (*fn)(const char *), char *urls, char *suffix) +{ + struct strbuf **url_list = strbuf_split_str(urls, ' ', 0); + int i; + + for (i = 0; url_list[i]; i++) { + strbuf_rtrim(url_list[i]); + if (url_list[i]->len == 0) + continue; + if (suffix && *suffix) + strbuf_addf(url_list[i], "/%s", suffix); + fn(url_list[i]->buf); + } + + strbuf_list_free(url_list); } void cgit_add_clone_urls(void (*fn)(const char *)) @@ -1707,12 +1621,6 @@ static void capture_http_clone_url(const char *url) http_clone_url = xstrdup(url); } -/* - * One row of the clone table. Every row is a link, but a browser cannot - * follow the ssh, scp or git forms, so those rows point at the first http - * url in the clone list, or at the repository's own page when the list has - * none. A colspan of zero leaves the attribute out. - */ void cgit_print_clone_row(const char *url, int colspan) { const char *href = url; @@ -1742,19 +1650,9 @@ void cgit_print_clone_row(const char *url, int colspan) free(fallback); } -void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page) +void cgit_add_hidden_formfields(int incl_head, int incl_search) { - if (!ctx.cfg.virtual_root) { - struct strbuf url = STRBUF_INIT; - - strbuf_addf(&url, "%s/%s", ctx.qry.repo, page); - if (ctx.qry.vpath) - strbuf_addf(&url, "/%s", ctx.qry.vpath); - html_hidden("url", url.buf); - strbuf_release(&url); - } - - if (incl_head && ctx.qry.head && ctx.repo->defbranch && strcmp(ctx.qry.head, ctx.repo->defbranch)) + if (incl_head && head_is_named(ctx.qry.head)) html_hidden("h", ctx.qry.head); if (ctx.qry.oid) @@ -1772,7 +1670,7 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page } } -void cgit_print_pageheader(void) +static void print_pageheader(void) { const struct object_id *pinned; @@ -1859,6 +1757,40 @@ void cgit_print_pageheader(void) html("<main class='content'>\n"); } +void cgit_print_layout_start(void) +{ + cgit_print_http_headers(); + print_docstart(); + print_pageheader(); +} + +void cgit_print_layout_end(void) +{ + html("</main>\n"); + if (ctx.cfg.embedded) { + html("</div>\n"); + if (ctx.cfg.footer) + html_include(ctx.cfg.footer); + return; + } + if (ctx.cfg.footer) + html_include(ctx.cfg.footer); + else if (ctx.cfg.enable_version_info) + htmlf("<footer class='footer'>generated by " + "<a href='https://github.com/brycekwon/cgit'>cgit %s</a> " + "(<a href='https://git-scm.com/'>git %s</a>)</footer>\n", + cgit_version, git_version_string); + else + html("<footer class='footer'>generated by <a href='https://github.com/brycekwon/cgit'>cgit</a></footer>\n"); + html("</div>\n"); + html("</body>\n</html>\n"); +} + +static void print_fileperm(unsigned short mode) +{ + htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); +} + void cgit_print_filemode(unsigned short mode) { if (S_ISDIR(mode)) @@ -1869,9 +1801,9 @@ void cgit_print_filemode(unsigned short mode) html("m"); else html("-"); - html_fileperm(mode >> 6); - html_fileperm(mode >> 3); - html_fileperm(mode); + print_fileperm(mode >> 6); + print_fileperm(mode >> 3); + print_fileperm(mode); } // The tag names and their stems, a stem being the name with any leading v or V @@ -1956,7 +1888,7 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co html("<span class='snapshot'>"); snapshot_link(filename.buf, NULL, NULL, NULL, NULL, filename.buf); if (cgit_snapshot_get_sig(ref, f)) { - strbuf_addstr(&filename, ".asc"); + strbuf_addstr(&filename, SIG_SUFFIX); html(" ("); snapshot_link("sig", NULL, NULL, NULL, NULL, filename.buf); html(")"); @@ -1968,7 +1900,7 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co // the plain tar it expands to, which is the first // format in the table. strbuf_setlen(&filename, filename.len - strlen(f->suffix)); - strbuf_addstr(&filename, ".tar.asc"); + strbuf_addstr(&filename, ".tar" SIG_SUFFIX); html(" ("); snapshot_link("sig", NULL, NULL, NULL, NULL, filename.buf); html(")"); @@ -1982,30 +1914,25 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co void cgit_set_title_from_path(const char *path) { struct strbuf sb = STRBUF_INIT; - const char *slash, *last_slash; + char *rest, *slash; if (!path) return; // 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, '/'))) { - strbuf_add(&sb, slash + 1, last_slash - slash - 1); - strbuf_addstr(&sb, " < "); - last_slash = slash; - } - strbuf_add(&sb, path, last_slash - path); - strbuf_addf(&sb, " - %s", ctx.page.title); + rest = xstrdup(path); + while ((slash = strrchr(rest, '/'))) { + strbuf_addf(&sb, "%s < ", slash + 1); + *slash = '\0'; + } + strbuf_addf(&sb, "%s - %s", rest, ctx.page.title); + free(rest); ctx.page.title = strbuf_detach(&sb, NULL); } -/* - * Names the page ahead of the repository and the site, so that the pages of one - * repository can be told apart in tabs, history and search results. - */ void cgit_prefix_title(const char *name) { - ctx.page.title = cgit_fmtalloc("%s - %s", name, + ctx.page.title = xstrfmt("%s - %s", name, ctx.page.title ? ctx.page.title : ctx.cfg.root_title); } |
