summaryrefslogtreecommitdiffstatshomepage
path: root/ui-plain.c
blob: 1b2b67297b215ef632ebd0d36bd9cef413e4dee2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* ui-plain.c: functions for output of plain blobs by path
 *
 * Copyright (C) 2008 Lars Hjemli
 *
 * Licensed under GNU General Public License v2
 *   (see COPYING for full license text)
 */

#include "cgit.h"
#include "html.h"
#include "ui-shared.h"

int match_baselen;
int match;

static void print_object(const unsigned char *sha1, const char *path)
{
	enum object_type type;
	char *buf, *ext;
	unsigned long size;
	struct string_list_item *mime;

	type = sha1_object_info(sha1, &size);
	if (type == OBJ_BAD) {
		html_status(404, "Not found", 0);
		return;
	}

	buf = read_sha1_file(sha1, &type, &size);
	if (!buf) {
		html_status(404, "Not found", 0);
		return;
	}
	ctx.page.mimetype = NULL;
	ext = strrchr(path, '.');
	if (ext && *(++ext)) {
		mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
		if (mime)
			ctx.page.mimetype = (char *)mime->util;
	}
	if (!ctx.page.mimetype) {
		if (buffer_is_binary(buf, size))
			ctx.page.mimetype = "application/octet-stream";
		else
			ctx.page.mimetype = "text/plain";
	}
	ctx.page.filename = fmt("%s", path);
	ctx.page.size = size;
	ctx.page.etag = sha1_to_hex(sha1);
	cgit_print_http_headers(&ctx);
	html_raw(buf, size);
	match = 1;
}

static void print_dir(const unsigned char *sha1, const char *path,
		      const char *base)
{
	char *fullpath;
	if (path[0] || base[0])
		fullpath = fmt("/%s%s/", base, path);
	else
		fullpath = "/";
	ctx.page.etag = sha1_to_hex(sha1);
	cgit_print_http_headers(&ctx);
	htmlf("<html><head><title>%s</title></head>\n<body>\n"
	      " <h2>%s</h2>\n <ul>\n", fullpath, fullpath);
	if (path[0] || base[0])
	      html("  <li><a href=\"../\">../</a></li>\n");
	match = 2;
}

static void print_dir_entry(const unsigned char *sha1, const char *path,
			    unsigned mode)
{
	const char *sep = "";
	if (S_ISDIR(mode))
		sep = "/";
	htmlf("  <li><a href=\"%s%s\">%s%s</a></li>\n", path, sep, path, sep);
	match = 2;
}

static void print_dir_tail(void)
{
	html(" </ul>\n</body></html>\n");
}

static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
		     const char *pathname, unsigned mode, int stage,
		     void *cbdata)
{
	if (baselen == match_baselen) {
		if (S_ISREG(mode))
			print_object(sha1, pathname);
		else if (S_ISDIR(mode)) {
			print_dir(sha1, pathname, base);
			return READ_TREE_RECURSIVE;
		}
	}
	else if (baselen > match_baselen)
		print_dir_entry(sha1, pathname, mode);
	else if (S_ISDIR(mode))
		return READ_TREE_RECURSIVE;

	return 0;
}

static int basedir_len(const char *path)
{
	char *p = strrchr(path, '/');
	if (p)
		return p - path + 1;
	return 0;
}

void cgit_print_plain(struct cgit_context *ctx)
{
	const char *rev = ctx->qry.sha1;
	unsigned char sha1[20];
	struct commit *commit;
	const char *paths[] = {ctx->qry.path, NULL};

	if (!rev)
		rev = ctx->qry.head;

	if (get_sha1(rev, sha1)) {
		html_status(404, "Not found", 0);
		return;
	}
	commit = lookup_commit_reference(sha1);
	if (!commit || parse_commit(commit)) {
		html_status(404, "Not found", 0);
		return;
	}
	if (!paths[0]) {
		paths[0] = "";
		match_baselen = -1;
		print_dir(commit->tree->object.sha1, "", "");
	}
	else
		match_baselen = basedir_len(paths[0]);
	read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
	if (!match)
		html_status(404, "Not found", 0);
	else if (match == 2)
		print_dir_tail();
}
ss="w"> const char *pagename, const char *query) { return cgit_fileurl(reponame,pagename,0,query); } const char *cgit_repobasename(const char *reponame) { /* I assume we don't need to store more than one repo basename */ static char rvbuf[1024]; int p; const char *rv; strncpy(rvbuf,reponame,sizeof(rvbuf)); if(rvbuf[sizeof(rvbuf)-1]) die("cgit_repobasename: truncated repository name '%s'", reponame); p = strlen(rvbuf)-1; /* strip trailing slashes */ while(p && rvbuf[p]=='/') rvbuf[p--]=0; /* strip trailing .git */ if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { p -= 3; rvbuf[p--] = 0; } /* strip more trailing slashes if any */ while( p && rvbuf[p]=='/') rvbuf[p--]=0; /* find last slash in the remaining string */ rv = strrchr(rvbuf,'/'); if(rv) return ++rv; return rvbuf; } char *cgit_currurl() { if (!ctx.cfg.virtual_root) return ctx.cfg.script_name; else if (ctx.qry.page) return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); else if (ctx.qry.repo) return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); else return fmt("%s/", ctx.cfg.virtual_root); } static char *repolink(char *title, char *class, char *page, char *head, char *path) { char *delim = "?"; html("<a"); if (title) { html(" title='"); html_attr(title); html("'"); } if (class) { html(" class='"); html_attr(class); html("'"); } html(" href='"); if (ctx.cfg.virtual_root) { html_attr(ctx.cfg.virtual_root); if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') html("/"); html_attr(ctx.repo->url); if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') html("/"); if (page) { html(page); html("/"); if (path) html_attr(path); } } else { html(ctx.cfg.script_name); html("?url="); html_attr(ctx.repo->url); if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') html("/"); if (page) { html(page); html("/"); if (path) html_attr(path); } delim = "&amp;"; } if (head && strcmp(head, ctx.repo->defbranch)) { html(delim); html("h="); html_attr(head); delim = "&amp;"; } return fmt("%s", delim); } static void reporevlink(char *page, char *name, char *title, char *class, char *head, char *rev, char *path) { char *delim; delim = repolink(title, class, page, head, path); if (rev && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_attr(rev); } html("'>"); html_txt(name); html("</a>"); } void cgit_tree_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("tree", name, title, class, head, rev, path); } void cgit_log_link(char *name, char *title, char *class, char *head, char *rev, char *path, int ofs, char *grep, char *pattern) { char *delim; delim = repolink(title, class, "log", head, path); if (rev && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_attr(rev); delim = "&"; } if (grep && pattern) { html(delim); html("qt="); html_attr(grep); delim = "&"; html(delim); html("q="); html_attr(pattern); } if (ofs > 0) { html(delim); html("ofs="); htmlf("%d", ofs); } html("'>"); html_txt(name); html("</a>"); } void cgit_commit_link(char *name, char *title, char *class, char *head, char *rev) { if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { name[ctx.cfg.max_msg_len] = '\0'; name[ctx.cfg.max_msg_len - 1] = '.'; name[ctx.cfg.max_msg_len - 2] = '.'; name[ctx.cfg.max_msg_len - 3] = '.'; } reporevlink("commit", name, title, class, head, rev, NULL); } void cgit_refs_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("refs", name, title, class, head, rev, path); } void cgit_snapshot_link(char *name, char *title, char *class, char *head, char *rev, char *archivename) { reporevlink("snapshot", name, title, class, head, rev, archivename); } void cgit_diff_link(char *name, char *title, char *class, char *head, char *new_rev, char *old_rev, char *path) { char *delim; delim = repolink(title, class, "diff", head, path); if (new_rev && strcmp(new_rev, ctx.qry.head)) { html(delim); html("id="); html_attr(new_rev); delim = "&amp;"; } if (old_rev) { html(delim); html("id2="); html_attr(old_rev); } html("'>"); html_txt(name); html("</a>"); } void cgit_patch_link(char *name, char *title, char *class, char *head, char *rev) { reporevlink("patch", name, title, class, head, rev, NULL); } void cgit_object_link(struct object *obj) { char *page, *arg, *url; if (obj->type == OBJ_COMMIT) { cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, ctx.qry.head, sha1_to_hex(obj->sha1)); return; } else if (obj->type == OBJ_TREE) { page = "tree"; arg = "id"; } else if (obj->type == OBJ_TAG) { page = "tag"; arg = "id"; } else { page = "blob"; arg = "id"; } url = cgit_pageurl(ctx.qry.repo, page, fmt("%s=%s", arg, sha1_to_hex(obj->sha1))); html_link_open(url, NULL, NULL); htmlf("%s %s", typename(obj->type), sha1_to_hex(obj->sha1)); html_link_close(); } void cgit_print_date(time_t secs, char *format) { char buf[64]; struct tm *time; if (!secs) return; time = gmtime(&secs); strftime(buf, sizeof(buf)-1, format, time); html_txt(buf); } void cgit_print_age(time_t t, time_t max_relative, char *format) { time_t now, secs; if (!t) return; time(&now); secs = now - t; if (secs > max_relative && max_relative >= 0) { cgit_print_date(t, format); return; } if (secs < TM_HOUR * 2) { htmlf("<span class='age-mins'>%.0f min.</span>", secs * 1.0 / TM_MIN); return; } if (secs < TM_DAY * 2) { htmlf("<span class='age-hours'>%.0f hours</span>", secs * 1.0 / TM_HOUR); return; } if (secs < TM_WEEK * 2) { htmlf("<span class='age-days'>%.0f days</span>", secs * 1.0 / TM_DAY); return; } if (secs < TM_MONTH * 2) { htmlf("<span class='age-weeks'>%.0f weeks</span>", secs * 1.0 / TM_WEEK); return; } if (secs < TM_YEAR * 2) { htmlf("<span class='age-months'>%.0f months</span>", secs * 1.0 / TM_MONTH); return; } htmlf("<span class='age-years'>%.0f years</span>", secs * 1.0 / TM_YEAR); } void cgit_print_http_headers(struct cgit_context *ctx) { if (ctx->page.mimetype && ctx->page.charset) htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, ctx->page.charset); else if (ctx->page.mimetype) htmlf("Content-Type: %s\n", ctx->page.mimetype); if (ctx->page.filename) htmlf("Content-Disposition: inline; filename=\"%s\"\n", ctx->page.filename); htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); htmlf("Expires: %s\n", http_date(ctx->page.expires)); html("\n"); } void cgit_print_docstart(struct cgit_context *ctx) { html(cgit_doctype); html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); html("<head>\n"); html("<title>"); html_txt(ctx->page.title); html("</title>\n"); htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); if (ctx->cfg.robots && *ctx->cfg.robots) htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); html("<link rel='stylesheet' type='text/css' href='"); html_attr(ctx->cfg.css); html("'/>\n"); html("</head>\n"); html("<body>\n"); } void cgit_print_docend() { html("</div>\n</body>\n</html>\n"); } int print_branch_option(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { char *name = (char *)refname; html_option(name, name, ctx.qry.head); return 0; } int print_archive_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct tag *tag; struct taginfo *info; struct object *obj; char buf[256], *url; unsigned char fileid[20]; int *header = (int *)cb_data; if (prefixcmp(refname, "refs/archives")) return 0; strncpy(buf, refname+14, sizeof(buf)); obj = parse_object(sha1); if (!obj) return 1; if (obj->type == OBJ_TAG) { tag = lookup_tag(sha1); if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) return 0; hashcpy(fileid, tag->tagged->sha1); } else if (obj->type != OBJ_BLOB) { return 0; } else { hashcpy(fileid, sha1); } if (!*header) { html("<h1>download</h1>\n"); *header = 1; } url = cgit_pageurl(ctx.qry.repo, "blob", fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), buf)); html_link_open(url, NULL, "menu"); html_txt(strlpart(buf, 20)); html_link_close(); return 0; } void add_hidden_formfields(int incl_head, int incl_search, char *page) { char *url; if (!ctx.cfg.virtual_root) { url = fmt("%s/%s", ctx.qry.repo, page); if (ctx.qry.path) url = fmt("%s/%s", url, ctx.qry.path); html_hidden("url", url); } if (incl_head && strcmp(ctx.qry.head, ctx.repo->defbranch)) html_hidden("h", ctx.qry.head); if (ctx.qry.sha1) html_hidden("id", ctx.qry.sha1); if (ctx.qry.sha2) html_hidden("id2", ctx.qry.sha2); if (incl_search) { if (ctx.qry.grep) html_hidden("qt", ctx.qry.grep); if (ctx.qry.search) html_hidden("q", ctx.qry.search); } } char *hc(struct cgit_cmd *cmd, const char *page) { return (strcmp(cmd->name, page) ? NULL : "active"); } void cgit_print_pageheader(struct cgit_context *ctx) { struct cgit_cmd *cmd = cgit_get_cmd(ctx); html("<table id='header'>\n"); html("<tr>\n"); html("<td class='logo' rowspan='2'><a href='"); if (ctx->cfg.logo_link) html_attr(ctx->cfg.logo_link); else html_attr(cgit_rooturl()); html("'><img src='"); html_attr(ctx->cfg.logo); html("' alt='cgit logo'/></a></td>\n"); html("<td class='main'>"); if (ctx->repo) { reporevlink(NULL, ctx->repo->name, NULL, hc(cmd, "summary"), ctx->qry.head, NULL, NULL); html(" : "); html_txt(ctx->qry.page); html("</td><td class='form'>"); html("<form method='get' action=''>\n"); add_hidden_formfields(0, 1, ctx->qry.page); html("<select name='h' onchange='this.form.submit();'>\n"); for_each_branch_ref(print_branch_option, ctx->qry.head); html("</select> "); html("<input type='submit' name='' value='switch'/>"); html("</form>"); } else html_txt(ctx->cfg.root_title); html("</td></tr>\n"); html("<tr><td class='sub'"); if (ctx->repo) { html(" colspan='2'>"); html_txt(ctx->repo->desc); } else { html(">"); if (ctx->cfg.root_desc) html_txt(ctx->cfg.root_desc); else if (ctx->cfg.index_info) html_include(ctx->cfg.index_info); } html("</td></tr></table>\n"); html("<table class='tabs'><tr><td>\n"); if (ctx->repo) { reporevlink(NULL, "summary", NULL, hc(cmd, "summary"), ctx->qry.head, NULL, NULL); cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, ctx->qry.sha1, NULL); cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, NULL, NULL, 0, NULL, NULL); cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, ctx->qry.sha1, NULL); cgit_commit_link("commit", NULL, hc(cmd, "commit"), ctx->qry.head, ctx->qry.sha1); cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, ctx->qry.sha1, ctx->qry.sha2, NULL); html("</td><td class='form'>"); html("<form class='right' method='get' action='"); if (ctx->cfg.virtual_root) html_attr(cgit_fileurl(ctx->qry.repo, "log", ctx->qry.path, NULL)); html("'>\n"); add_hidden_formfields(1, 0, "log"); html("<select name='qt'>\n"); html_option("grep", "log msg", ctx->qry.grep); html_option("author", "author", ctx->qry.grep); html_option("committer", "committer", ctx->qry.grep); html("</select>\n"); html("<input class='txt' type='text' size='10' name='q' value='"); html_attr(ctx->qry.search); html("'/>\n"); html("<input type='submit' value='search'/>\n"); html("</form>\n"); } else { html("<a class='active' href='"); html_attr(cgit_rooturl()); html("'>index</a>\n"); html("</td><td class='form'>"); html("<form method='get' action='"); html_attr(cgit_rooturl()); html("'>\n"); html("<input type='text' name='q' size='10' value='"); html_attr(ctx->qry.search); html("'/>\n"); html("<input type='submit' value='search'/>\n"); html("</form>"); } html("</td></tr></table>\n"); html("<div class='content'>"); } void cgit_print_filemode(unsigned short mode) { if (S_ISDIR(mode)) html("d"); else if (S_ISLNK(mode)) html("l"); else if (S_ISGITLINK(mode)) html("m"); else html("-"); html_fileperm(mode >> 6); html_fileperm(mode >> 3); html_fileperm(mode); } void cgit_print_snapshot_links(const char *repo, const char *head, const char *hex, int snapshots) { const struct cgit_snapshot_format* f; char *filename; for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(snapshots & f->bit)) continue; filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, f->suffix); cgit_snapshot_link(filename, NULL, NULL, (char *)head, (char *)hex, filename); html("<br/>"); } }