{! /* * Copyright (c) 2022 Omar Polo * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "got_error.h" #include "got_object.h" #include "got_reference.h" #include "gotwebd.h" #include "tmpl.h" enum gotweb_ref_tm { TM_DIFF, TM_LONG, }; static int breadcumbs(struct template *); static int datetime(struct template *, time_t, int); static int gotweb_render_blob_line(struct template *, const char *, size_t); static int gotweb_render_tree_item(struct template *, struct got_tree_entry *); static int blame_line(struct template *, const char *, struct blame_line *, int, int); static inline int gotweb_render_more(struct template *, int); static inline int tree_listing(struct template *); static inline int diff_line(struct template *, char *); static inline int tag_item(struct template *, struct repo_tag *); static inline int branch(struct template *, struct got_reflist_entry *); static inline int rss_tag_item(struct template *, struct repo_tag *); static inline int rss_author(struct template *, char *); static inline char * nextsep(char *s, char **t) { char *q; while (*s == '/') s++; *t = s; if (*s == '\0') return NULL; q = strchr(s, '/'); if (q == NULL) q = strchr(s, '\0'); return q; } !} {{ define datetime(struct template *tp, time_t t, int fmt) }} {! struct tm tm; char rfc3339[64]; char datebuf[64]; if (gmtime_r(&t, &tm) == NULL) return -1; if (strftime(rfc3339, sizeof(rfc3339), "%FT%TZ", &tm) == 0) return -1; if (fmt != TM_DIFF && asctime_r(&tm, datebuf) == NULL) return -1; !} {{ end }} {{ define breadcumbs(struct template *tp) }} {! struct request *c = tp->tp_arg; struct querystring *qs = c->t->qs; struct gotweb_url url; const char *folder = qs->folder; const char *action = "tree"; char *t, *s = NULL, *dir = NULL; char ch; memset(&url, 0, sizeof(url)); url.index_page = -1; url.action = TREE; url.path = qs->path; url.commit = qs->commit; if (qs->action != TREE && qs->action != BLOB) { action = gotweb_action_name(qs->action); url.action = qs->action; } if (folder && *folder != '\0') { while (*folder == '/') folder++; dir = strdup(folder); if (dir == NULL) return (-1); s = dir; } !} {{ " / " }} {{ action }} {{ " / " }} {{ if dir }} {{ while (s = nextsep(s, &t)) != NULL }} {! ch = *s; *s = '\0'; url.folder = dir; !} {{ t }} {{ " / " }} {! *s = ch; !} {{ end }} {{ end }} {{ if qs->file }} {{ qs->file }} {{ end}} {{ finally }} {! free(dir); !} {{ end }} {{ define gotweb_render_page(struct template *tp, int (*body)(struct template *)) }} {! struct request *c = tp->tp_arg; struct server *srv = c->srv; struct querystring *qs = c->t->qs; struct gotweb_url u_path; const char *prfx = c->document_uri; const char *css = srv->custom_css; memset(&u_path, 0, sizeof(u_path)); u_path.index_page = -1; u_path.action = SUMMARY; !} {{ srv->site_name }}
{{ render body(tp) }}

{{ if srv->show_site_owner }} {{ srv->site_owner }} {{ end }}

{{ end }} {{ define gotweb_render_error(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; !}
{{ if t->error }} {{ t->error->msg }} {{ else }} See daemon logs for details {{ end }}
{{ end }} {{ define gotweb_render_repo_table_hdr(struct template *tp) }} {! struct request *c = tp->tp_arg; struct server *srv = c->srv; !}
Project
{{ if srv->show_repo_description }}
Description
{{ end }} {{ if srv->show_repo_owner }}
Owner
{{ end }} {{ if srv->show_repo_age }}
Last Change
{{ end }}
{{ end }} {{ define gotweb_render_repo_fragment(struct template *tp, struct repo_dir *repo_dir) }} {! struct request *c = tp->tp_arg; struct server *srv = c->srv; struct gotweb_url summary = { .action = SUMMARY, .index_page = -1, .path = repo_dir->name, }, briefs = { .action = BRIEFS, .index_page = -1, .path = repo_dir->name, }, commits = { .action = COMMITS, .index_page = -1, .path = repo_dir->name, }, tags = { .action = TAGS, .index_page = -1, .path = repo_dir->name, }, tree = { .action = TREE, .index_page = -1, .path = repo_dir->name, }, rss = { .action = RSS, .index_page = -1, .path = repo_dir->name, }; !}
{{ if srv->show_repo_description }}
{{ repo_dir->description }}
{{ end }} {{ if srv->show_repo_owner }}
{{ repo_dir->owner }}
{{ end }} {{ if srv->show_repo_age }}
{{ render datetime(tp, repo_dir->age, TM_DIFF) }}
{{ end }}
{{ end }} {{ define gotweb_render_briefs(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = c->t->qs; struct repo_commit *rc; struct repo_dir *repo_dir = t->repo_dir; struct gotweb_url diff_url, patch_url, tree_url; char *tmp, *body; diff_url = (struct gotweb_url){ .action = DIFF, .index_page = -1, .path = repo_dir->name, .headref = qs->headref, }; patch_url = (struct gotweb_url){ .action = PATCH, .index_page = -1, .path = repo_dir->name, .headref = qs->headref, }; tree_url = (struct gotweb_url){ .action = TREE, .index_page = -1, .path = repo_dir->name, .headref = qs->headref, }; !}

Commit Briefs

{{ tailq-foreach rc &t->repo_commits entry }} {! diff_url.commit = rc->commit_id; patch_url.commit = rc->commit_id; tree_url.commit = rc->commit_id; tmp = strchr(rc->committer, '<'); if (tmp) *tmp = '\0'; body = strchr(rc->commit_msg, '\n'); if (body) { *body++ = '\0'; while (*body == '\n') body++; } !}

{{ render datetime(tp, rc->committer_time, TM_DIFF) }} {{" "}} {{ rc->committer }}

{{ if body && *body != '\0' }}
{{ rc->commit_msg }} {{ if rc->refs_str }} {{ " " }} ({{ rc->refs_str }}) {{ end }} {{ " " }} {{ "\n" }}

{{ body }}

{{ else }}

{{ rc->commit_msg }} {{ if rc->refs_str }} {{ " " }} ({{ rc->refs_str }}) {{ end }}

{{ end }}

{{ end }} {{ render gotweb_render_more(tp, BRIEFS) }}
{{ end }} {{ define gotweb_render_more(struct template *tp, int action) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = t->qs; struct gotweb_url more = { .action = action, .index_page = -1, .path = qs->path, .commit = t->more_id, .headref = qs->headref, .file = qs->file, }; if (action == TAGS) more.commit = t->tags_more_id; !} {{ if more.commit }} {{ end }} {{ end }} {{ define gotweb_render_navs(struct template *tp) }} {! struct request *c = tp->tp_arg; struct gotweb_url prev, next; int have_prev, have_next; gotweb_index_navs(c, &prev, &have_prev, &next, &have_next); !}
{{ end }} {{ define gotweb_render_commits(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct repo_dir *repo_dir = t->repo_dir; struct repo_commit *rc; struct gotweb_url diff, patch, tree; diff = (struct gotweb_url){ .action = DIFF, .index_page = -1, .path = repo_dir->name, }; patch = (struct gotweb_url){ .action = PATCH, .index_page = -1, .path = repo_dir->name, }; tree = (struct gotweb_url){ .action = TREE, .index_page = -1, .path = repo_dir->name, }; !}

Commits

{{ tailq-foreach rc &t->repo_commits entry }} {! diff.commit = rc->commit_id; patch.commit = rc->commit_id; tree.commit = rc->commit_id; !}
{{ "\n" }} {{ rc->commit_msg }}

{{ end }} {{ render gotweb_render_more(tp, COMMITS) }}
{{ end }} {{ define gotweb_render_blob(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = t->qs; struct got_blob_object *blob = t->blob; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); struct gotweb_url briefs_url, blame_url, raw_url; memset(&briefs_url, 0, sizeof(briefs_url)); briefs_url.index_page = -1, briefs_url.action = BRIEFS, briefs_url.path = qs->path, briefs_url.commit = qs->commit, briefs_url.folder = qs->folder, briefs_url.file = qs->file, memcpy(&blame_url, &briefs_url, sizeof(blame_url)); blame_url.action = BLAME; memcpy(&raw_url, &briefs_url, sizeof(raw_url)); raw_url.action = BLOBRAW; !}

Blob


      {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
    
{{ end }} {{ define gotweb_render_blob_line(struct template *tp, const char *line, size_t no) }} {! char lineno[16]; int r; r = snprintf(lineno, sizeof(lineno), "%zu", no); if (r < 0 || (size_t)r >= sizeof(lineno)) return -1; !}
{{ lineno }}{{" "}} {{ line }}
{{ end }} {{ define tree_listing(struct template *tp) }} {! const struct got_error *error; struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = c->t->qs; struct gotweb_url url; char *readme = NULL; int binary; const uint8_t *buf; size_t len; !} {{ render got_output_repo_tree(c, &readme, gotweb_render_tree_item) }}
{{ if readme }} {! error = got_open_blob_for_output(&t->blob, &t->fd, &binary, c, qs->folder, readme, qs->commit); if (error) { free(readme); return (-1); } memset(&url, 0, sizeof(url)); url.index_page = -1; url.action = BLOB; url.path = t->qs->path; url.file = readme; url.folder = t->qs->folder; url.commit = t->qs->commit; !} {{ if !binary }}

{{ readme }}

        {!
		for (;;) {
			error = got_object_blob_read_block(&len, t->blob);
			if (error) {
				free(readme);
				return (-1);
			}
			if (len == 0)
				break;
			buf = got_object_blob_get_read_buf(t->blob);
			if (tp_write_htmlescape(tp, buf, len) == -1) {
				free(readme);
				return (-1);
			}
		}
        !}
      
{{ end }} {{ end }} {{ finally }} {! free(readme); !} {{ end }} {{ define gotweb_render_tree(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); !}

Tree


{{ render tree_listing(tp) }}
{{ end }} {{ define gotweb_render_tree_item(struct template *tp, struct got_tree_entry *te) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = t->qs; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); const char *modestr = ""; const char *name; const char *folder; char *dir = NULL; mode_t mode; struct gotweb_url url = { .index_page = -1, .commit = rc->commit_id, .path = qs->path, }; name = got_tree_entry_get_name(te); mode = got_tree_entry_get_mode(te); folder = qs->folder ? qs->folder : ""; if (S_ISDIR(mode)) { if (asprintf(&dir, "%s/%s", folder, name) == -1) return (-1); url.action = TREE; url.folder = dir; } else { url.action = BLOB; url.folder = folder; url.file = name; } if (got_object_tree_entry_is_submodule(te)) modestr = "$"; else if (S_ISLNK(mode)) modestr = "@"; else if (S_ISDIR(mode)) modestr = "/"; else if (mode & S_IXUSR) modestr = "*"; !} {{ if S_ISDIR(mode) }} {{ name }}{{ modestr }} {{ else }} {{ name }}{{ modestr }} {! url.action = COMMITS; !} commits {{ " | " }} {! url.action = BLAME; !} blame {{ end }} {{ finally }} {! free(dir); !} {{ end }} {{ define gotweb_render_tags(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = t->qs; struct repo_tag *rt; int commit_found; commit_found = qs->commit == NULL; !}

Tags

{{ if t->tag_count == 0 }}
This repository contains no tags
{{ else }} {{ tailq-foreach rt &t->repo_tags entry }} {{ if commit_found || !strcmp(qs->commit, rt->commit_id) }} {! commit_found = 1; !} {{ render tag_item(tp, rt) }} {{ end }} {{ end }} {{ render gotweb_render_more(tp, TAGS) }} {{ end }}
{{ end }} {{ define tag_item(struct template *tp, struct repo_tag *rt) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct repo_dir *repo_dir = t->repo_dir; char *tag_name = rt->tag_name; char *msg = rt->tag_commit; char *nl; struct gotweb_url url = { .action = TAG, .index_page = -1, .path = repo_dir->name, .commit = rt->commit_id, }; if (strncmp(tag_name, "refs/tags/", 10) == 0) tag_name += 10; if (msg) { nl = strchr(msg, '\n'); if (nl) *nl = '\0'; } !}
{{ render datetime(tp, rt->tagger_time, TM_DIFF) }}
{{ tag_name }}

{{ end }} {{ define gotweb_render_tag(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct repo_tag *rt; const char *tag_name; rt = TAILQ_LAST(&t->repo_tags, repo_tags_head); tag_name = rt->tag_name; if (strncmp(tag_name, "refs/", 5) == 0) tag_name += 5; !}

Tag

{{ end }} {{ define gotweb_render_diff(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = t->qs; FILE *fp = t->fp; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); char *line = NULL; size_t linesize = 0; ssize_t linelen; struct gotweb_url patch_url, tree_url = { .action = TREE, .index_page = -1, .path = qs->path, .commit = rc->commit_id, }; memcpy(&patch_url, &tree_url, sizeof(patch_url)); patch_url.action = PATCH; !}

Commit Diff


    {{ while (linelen = getline(&line, &linesize, fp)) != -1 }}
      {{ render diff_line(tp, line) }}
    {{ end }}
  
{{ finally }} {! free(line); !} {{ end }} {{ define diff_line(struct template *tp, char *line )}} {! const char *color = NULL; char *nl; if (!strncmp(line, "-", 1)) color = "diff_minus"; else if (!strncmp(line, "+", 1)) color = "diff_plus"; else if (!strncmp(line, "@@", 2)) color = "diff_chunk_header"; else if (!strncmp(line, "commit +", 8) || !strncmp(line, "commit -", 8) || !strncmp(line, "blob +", 6) || !strncmp(line, "blob -", 6) || !strncmp(line, "file +", 6) || !strncmp(line, "file -", 6)) color = "diff_meta"; else if (!strncmp(line, "from:", 5) || !strncmp(line, "via:", 4)) color = "diff_author"; else if (!strncmp(line, "date:", 5)) color = "diff_date"; nl = strchr(line, '\n'); if (nl) *nl = '\0'; !} {{ line }}{{"\n"}} {{ end }} {{ define gotweb_render_branches(struct template *tp, struct got_reflist_head *refs) }} {! struct got_reflist_entry *re; !}

Branches

{{ tailq-foreach re refs entry }} {{ if !got_ref_is_symbolic(re->ref) }} {{ render branch(tp, re) }} {{ end }} {{ end }}
{{ end }} {{ define branch(struct template *tp, struct got_reflist_entry *re) }} {! const struct got_error *err; struct request *c = tp->tp_arg; struct querystring *qs = c->t->qs; const char *refname; time_t age; struct gotweb_url url = { .action = SUMMARY, .index_page = -1, .path = qs->path, }; refname = got_ref_get_name(re->ref); err = got_get_repo_age(&age, c, refname); if (err) { log_warnx("%s: %s", __func__, err->msg); return -1; } if (strncmp(refname, "refs/heads/", 11) == 0) refname += 11; url.headref = refname; !}
{{ render datetime(tp, age, TM_DIFF) }}

{{ end }} {{ define gotweb_render_summary(struct template *tp) }} {! struct request *c = tp->tp_arg; struct server *srv = c->srv; struct transport *t = c->t; struct got_reflist_head *refs = &t->refs; !} {{ render gotweb_render_briefs(tp) }} {{ render gotweb_render_branches(tp, refs) }} {{ render gotweb_render_tags(tp) }}

Tree

{{ render tree_listing(tp) }}
{{ end }} {{ define gotweb_render_blame(struct template *tp) }} {! const struct got_error *err; struct request *c = tp->tp_arg; struct transport *t = c->t; struct querystring *qs = t->qs; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); struct gotweb_url briefs_url, blob_url, raw_url; memset(&briefs_url, 0, sizeof(briefs_url)); briefs_url.index_page = -1, briefs_url.action = BRIEFS, briefs_url.path = qs->path, briefs_url.commit = qs->commit, briefs_url.folder = qs->folder, briefs_url.file = qs->file, memcpy(&blob_url, &briefs_url, sizeof(blob_url)); blob_url.action = BLOB; memcpy(&raw_url, &briefs_url, sizeof(raw_url)); raw_url.action = BLOBRAW; !}

Blame


    {!
	err = got_output_file_blame(c, &blame_line);
	if (err && err->code != GOT_ERR_CANCELLED)
		log_warnx("%s: got_output_file_blame: %s", __func__,
		    err->msg);
	if (err)
		return (-1);
    !}
  
{{ end }} {{ define blame_line(struct template *tp, const char *line, struct blame_line *bline, int lprec, int lcur) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct repo_dir *repo_dir = t->repo_dir; char *committer, *s; struct gotweb_url url = { .action = DIFF, .index_page = -1, .path = repo_dir->name, .commit = bline->id_str, }; s = strchr(bline->committer, '<'); committer = s ? s + 1 : bline->committer; s = strchr(committer, '@'); if (s) *s = '\0'; !}
{{ printf "%*d ", lprec, lcur }} {{ printf "%.8s", bline->id_str }} {{" "}} {{ bline->datebuf }} {{" "}} {{ printf "%.9s", committer }} {{" "}} {{ line }}
{{ end }} {{ define gotweb_render_patch(struct template *tp) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); struct tm tm; char buf[BUFSIZ], datebuf[64]; size_t r; if (gmtime_r(&rc->committer_time, &tm) == NULL || strftime(datebuf, sizeof(datebuf), "%a %b %d %T %Y UTC", &tm) == 0) return (-1); !} commit {{ rc->commit_id }} {{ "\n" }} from: {{ rc->author | unsafe }} {{ "\n" }} {{ if strcmp(rc->committer, rc->author) != 0 }} via: {{ rc->committer | unsafe }} {{ "\n" }} {{ end }} date: {{ datebuf }} {{ "\n" }} {{ "\n" }} {{ rc->commit_msg | unsafe }} {{ "\n" }} {! if (template_flush(tp) == -1) return (-1); for (;;) { r = fread(buf, 1, sizeof(buf), t->fp); if (fcgi_write(c, buf, r) == -1 || r != sizeof(buf)) break; } !} {{ end }} {{ define gotweb_render_rss(struct template *tp) }} {! struct request *c = tp->tp_arg; struct server *srv = c->srv; struct transport *t = c->t; struct repo_dir *repo_dir = t->repo_dir; struct repo_tag *rt; struct gotweb_url summary = { .action = SUMMARY, .index_page = -1, .path = repo_dir->name, }; !} Tags of {{ repo_dir->name }} {{ if srv->show_repo_description }} {{ repo_dir->description }} {{ end }} {{ tailq-foreach rt &t->repo_tags entry }} {{ render rss_tag_item(tp, rt) }} {{ end }} {{ end }} {{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }} {! struct request *c = tp->tp_arg; struct transport *t = c->t; struct repo_dir *repo_dir = t->repo_dir; struct tm tm; char rfc822[128]; int r; char *tag_name = rt->tag_name; struct gotweb_url tag = { .action = TAG, .index_page = -1, .path = repo_dir->name, .commit = rt->commit_id, }; if (strncmp(tag_name, "refs/tags/", 10) == 0) tag_name += 10; if (gmtime_r(&rt->tagger_time, &tm) == NULL) return -1; r = strftime(rfc822, sizeof(rfc822), "%a, %d %b %Y %H:%M:%S GMT", &tm); if (r == 0) return 0; !} {{ repo_dir->name }} {{" "}} {{ tag_name }} {{ rt->tag_commit }}]]> {{ render rss_author(tp, rt->tagger) }} {{ rt->commit_id }} {{ rfc822 }} {{ end }} {{ define rss_author(struct template *tp, char *author) }} {! char *t, *mail; /* what to do if the author name contains a paren? */ if (strchr(author, '(') != NULL || strchr(author, ')') != NULL) return 0; t = strchr(author, '<'); if (t == NULL) return 0; *t = '\0'; mail = t+1; while (isspace((unsigned char)*--t)) *t = '\0'; t = strchr(mail, '>'); if (t == NULL) return 0; *t = '\0'; !} {{ mail }} {{" "}} ({{ author }}) {{ end }}