/*
* 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("url);
if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
html("/");
if (page) {
html_url_arg(page);
html("/");
if (path)
html_url_arg(path);
}
delim = "&";
}
if (head && ctx.repo->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("