/* * 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. */ #define USE_THE_REPOSITORY_VARIABLE #include "cgit.h" #include "html.h" #include "shared.h" #include "ui-shared.h" #include "ui-snapshot.h" #define DEFAULT_HTTP_PORT 80 #define SHORT_HASH_LEN 10 // A max-msg-len below this leaves too little of a commit subject to be worth // truncating, and the ellipsis is paid for out of that same budget. #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 // cgit_fmt exits the request rather than truncate, so a path is only worked // 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) { // 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("repo_basename: truncated repository name '%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; } static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root) { const char *delim = "?"; if (always_root || page) html_attr(root_url()); else { char *currenturl = cgit_currenturl(); html_attr(currenturl); free(currenturl); } if (page) { 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); } } 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) { html(""); html_txt(name); html(""); } /* * Open an anchor and write the part of its href naming the repository, the * page and the path, leaving the href quote open for the caller to add its own * arguments and close. The return is the delimiter to put before the first of * those, which is still a question mark unless a query string has been opened. */ static const char *repolink(const char *title, const char *class, const char *page, const char *head, const char *path) { const char *delim = "?"; html("defbranch && strcmp(head, ctx.repo->defbranch)) { html(delim); html("h="); html_url_arg(head); delim = "&"; } return cgit_fmt("%s", 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 "&"; } return delim; } static void reporevlink(const char *page, const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { const char *delim; delim = repolink(title, class, page, head, path); emit_rev_arg(delim, rev); html("'>"); html_txt(name); html(""); } static void emit_diff_args(const char *delim) { if (ctx.qry.difftype) { html(delim); htmlf("dt=%d", ctx.qry.difftype); delim = "&"; } if (ctx.qry.context > 0 && ctx.qry.context != DEFAULT_DIFF_CONTEXT) { html(delim); html("context="); htmlf("%d", ctx.qry.context); delim = "&"; } if (ctx.qry.ignorews) { html(delim); html("ignorews=1"); delim = "&"; } if (ctx.qry.follow) { html(delim); html("follow=1"); } } 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; if (ctx.cfg.local_time) mode.local = 1; return mode; } /* * Dates render as time elements, whose datetime attribute wants the strict * ISO 8601 form with the T separator rather than the space git prints by * default. */ static void print_abs_date(time_t t, int tz) { html("