2 * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
4 * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
5 * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 #include <netinet/in.h>
22 #include <sys/queue.h>
24 #include <sys/types.h>
36 #include "got_error.h"
37 #include "got_object.h"
38 #include "got_reference.h"
39 #include "got_repository.h"
41 #include "got_cancel.h"
42 #include "got_worktree.h"
44 #include "got_commit_graph.h"
45 #include "got_blame.h"
46 #include "got_privsep.h"
56 static const struct querystring_keys querystring_keys[] = {
61 { "headref", HEADREF },
62 { "index_page", INDEX_PAGE },
67 static const struct action_keys action_keys[] = {
71 { "commits", COMMITS },
75 { "summary", SUMMARY },
81 static const struct got_error *gotweb_init_querystring(struct querystring **);
82 static const struct got_error *gotweb_parse_querystring(struct querystring **,
84 static const struct got_error *gotweb_assign_querystring(struct querystring **,
86 static const struct got_error *gotweb_render_header(struct request *);
87 static const struct got_error *gotweb_render_footer(struct request *);
88 static const struct got_error *gotweb_render_index(struct request *);
89 static const struct got_error *gotweb_init_repo_dir(struct repo_dir **,
91 static const struct got_error *gotweb_load_got_path(struct request *c,
93 static const struct got_error *gotweb_get_repo_description(char **,
94 struct server *, char *);
95 static const struct got_error *gotweb_get_clone_url(char **, struct server *,
97 static const struct got_error *gotweb_render_navs(struct request *);
98 static const struct got_error *gotweb_render_blame(struct request *);
99 static const struct got_error *gotweb_render_briefs(struct request *);
100 static const struct got_error *gotweb_render_commits(struct request *);
101 static const struct got_error *gotweb_render_diff(struct request *);
102 static const struct got_error *gotweb_render_summary(struct request *);
103 static const struct got_error *gotweb_render_tag(struct request *);
104 static const struct got_error *gotweb_render_tags(struct request *);
105 static const struct got_error *gotweb_render_tree(struct request *);
106 static const struct got_error *gotweb_render_branches(struct request *);
108 static void gotweb_free_querystring(struct querystring *);
109 static void gotweb_free_repo_dir(struct repo_dir *);
111 struct server *gotweb_get_server(uint8_t *, uint8_t *, uint8_t *);
114 gotweb_process_request(struct request *c)
116 const struct got_error *error = NULL, *error2 = NULL;
117 struct server *srv = NULL;
118 struct querystring *qs = NULL;
119 struct repo_dir *repo_dir = NULL;
120 uint8_t err[] = "gotwebd experienced an error: ";
123 /* init the transport */
124 error = gotweb_init_transport(&c->t);
126 log_warnx("%s: %s", __func__, error->msg);
129 /* don't process any further if client disconnected */
130 if (c->sock->client_status == CLIENT_DISCONNECT)
132 /* get the gotwebd server */
133 srv = gotweb_get_server(c->server_name, c->document_root, c->http_host);
135 log_warnx("%s: error server is NULL", __func__);
139 /* parse our querystring */
140 error = gotweb_init_querystring(&qs);
142 log_warnx("%s: %s", __func__, error->msg);
146 error = gotweb_parse_querystring(&qs, c->querystring);
148 log_warnx("%s: %s", __func__, error->msg);
153 * certain actions require a commit id in the querystring. this stops
154 * bad actors from exploiting this by manually manipulating the
158 if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
159 qs->action == DIFF)) {
160 error2 = got_error(GOT_ERR_QUERYSTRING);
164 if (qs->action != INDEX) {
165 error = gotweb_init_repo_dir(&repo_dir, qs->path);
168 error = gotweb_load_got_path(c, repo_dir);
169 c->t->repo_dir = repo_dir;
170 if (error && error->code != GOT_ERR_LONELY_PACKIDX)
174 /* render top of page */
175 if (qs != NULL && qs->action == BLOB) {
176 error = got_get_repo_commits(c, 1);
179 error = got_output_file_blob(c);
181 log_warnx("%s: %s", __func__, error->msg);
187 error = gotweb_render_content_type(c, "text/html");
189 log_warnx("%s: %s", __func__, error->msg);
195 error = gotweb_render_header(c);
197 log_warnx("%s: %s", __func__, error->msg);
208 error = gotweb_render_blame(c);
210 log_warnx("%s: %s", __func__, error->msg);
215 error = gotweb_render_briefs(c);
217 log_warnx("%s: %s", __func__, error->msg);
222 error = gotweb_render_commits(c);
224 log_warnx("%s: %s", __func__, error->msg);
229 error = gotweb_render_diff(c);
231 log_warnx("%s: %s", __func__, error->msg);
236 error = gotweb_render_index(c);
238 log_warnx("%s: %s", __func__, error->msg);
243 error = gotweb_render_summary(c);
245 log_warnx("%s: %s", __func__, error->msg);
250 error = gotweb_render_tag(c);
252 log_warnx("%s: %s", __func__, error->msg);
257 error = gotweb_render_tags(c);
259 log_warnx("%s: %s", __func__, error->msg);
264 error = gotweb_render_tree(c);
266 log_warnx("%s: %s", __func__, error->msg);
272 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
274 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
276 if (fcgi_gen_response(c, "</div>\n") == -1)
283 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
285 if (fcgi_gen_response(c, err) == -1)
288 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
291 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
294 if (html && fcgi_gen_response(c, "</div>\n") == -1)
297 if (c->t->repo != NULL && qs->action != INDEX)
298 got_repo_close(c->t->repo);
299 if (html && srv != NULL)
300 gotweb_render_footer(c);
304 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
307 struct server *srv = NULL;
309 /* check against document_root first */
310 if (strlen(server_name) > 0)
311 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
312 if (strcmp(srv->name, server_name) == 0)
315 /* check against document_root second */
316 if (strlen(document_root) > 0)
317 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
318 if (strcmp(srv->name, document_root) == 0)
321 /* check against subdomain third */
322 if (strlen(subdomain) > 0)
323 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
324 if (strcmp(srv->name, subdomain) == 0)
327 /* if those fail, send first server */
328 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
335 const struct got_error *
336 gotweb_init_transport(struct transport **t)
338 const struct got_error *error = NULL;
340 *t = calloc(1, sizeof(**t));
342 return got_error_from_errno2("%s: calloc", __func__);
344 TAILQ_INIT(&(*t)->repo_commits);
345 TAILQ_INIT(&(*t)->repo_tags);
348 (*t)->repo_dir = NULL;
350 (*t)->next_id = NULL;
351 (*t)->prev_id = NULL;
358 static const struct got_error *
359 gotweb_init_querystring(struct querystring **qs)
361 const struct got_error *error = NULL;
363 *qs = calloc(1, sizeof(**qs));
365 return got_error_from_errno2("%s: calloc", __func__);
367 (*qs)->action = INDEX;
368 (*qs)->commit = NULL;
370 (*qs)->folder = NULL;
371 (*qs)->headref = strdup("HEAD");
372 if ((*qs)->headref == NULL) {
373 return got_error_from_errno2("%s: strdup", __func__);
375 (*qs)->index_page = 0;
376 (*qs)->index_page_str = NULL;
382 static const struct got_error *
383 gotweb_parse_querystring(struct querystring **qs, char *qst)
385 const struct got_error *error = NULL;
386 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
387 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
394 return got_error_from_errno2("%s: strdup", __func__);
399 while (tok1_pair != NULL) {
400 strsep(&tok1_end, "&");
402 tok2 = strdup(tok1_pair);
405 return got_error_from_errno2("%s: strdup", __func__);
411 while (tok2_pair != NULL) {
412 strsep(&tok2_end, "=");
414 error = gotweb_assign_querystring(qs, tok2_pair,
419 tok2_pair = tok2_end;
422 tok1_pair = tok1_end;
432 static const struct got_error *
433 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
435 const struct got_error *error = NULL;
439 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
440 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
443 switch (querystring_keys[el_cnt].element) {
445 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
446 if (strcmp(value, action_keys[a_cnt].name) != 0)
448 else if (strcmp(value,
449 action_keys[a_cnt].name) == 0){
451 action_keys[a_cnt].action;
459 (*qs)->commit = strdup(value);
460 if ((*qs)->commit == NULL) {
461 error = got_error_from_errno2("%s: strdup",
467 (*qs)->file = strdup(value);
468 if ((*qs)->file == NULL) {
469 error = got_error_from_errno2("%s: strdup",
475 (*qs)->folder = strdup(value);
476 if ((*qs)->folder == NULL) {
477 error = got_error_from_errno2("%s: strdup",
483 (*qs)->headref = strdup(value);
484 if ((*qs)->headref == NULL) {
485 error = got_error_from_errno2("%s: strdup",
491 if (strlen(value) == 0)
493 (*qs)->index_page_str = strdup(value);
494 if ((*qs)->index_page_str == NULL) {
495 error = got_error_from_errno2("%s: strdup",
499 (*qs)->index_page = strtonum(value, INT64_MIN,
502 error = got_error_from_errno3("%s: strtonum %s",
506 if ((*qs)->index_page < 0) {
507 (*qs)->index_page = 0;
508 sprintf((*qs)->index_page_str, "%d", 0);
512 (*qs)->path = strdup(value);
513 if ((*qs)->path == NULL) {
514 error = got_error_from_errno2("%s: strdup",
520 if (strlen(value) == 0)
522 (*qs)->page_str = strdup(value);
523 if ((*qs)->page_str == NULL) {
524 error = got_error_from_errno2("%s: strdup",
528 (*qs)->page = strtonum(value, INT64_MIN,
531 error = got_error_from_errno3("%s: strtonum %s",
535 if ((*qs)->page < 0) {
537 sprintf((*qs)->page_str, "%d", 0);
549 gotweb_free_repo_tag(struct repo_tag *rt)
552 free(rt->commit_msg);
560 gotweb_free_repo_commit(struct repo_commit *rc)
570 free(rc->commit_msg);
576 gotweb_free_querystring(struct querystring *qs)
583 free(qs->index_page_str);
591 gotweb_free_repo_dir(struct repo_dir *repo_dir)
593 if (repo_dir != NULL) {
594 free(repo_dir->name);
595 free(repo_dir->owner);
596 free(repo_dir->description);
599 free(repo_dir->path);
605 gotweb_free_transport(struct transport *t)
607 struct repo_commit *rc = NULL, *trc = NULL;
608 struct repo_tag *rt = NULL, *trt = NULL;
610 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
611 TAILQ_REMOVE(&t->repo_commits, rc, entry);
612 gotweb_free_repo_commit(rc);
614 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
615 TAILQ_REMOVE(&t->repo_tags, rt, entry);
616 gotweb_free_repo_tag(rt);
618 gotweb_free_repo_dir(t->repo_dir);
619 gotweb_free_querystring(t->qs);
627 const struct got_error *
628 gotweb_render_content_type(struct request *c, const uint8_t *type)
630 const struct got_error *error = NULL;
633 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
634 error = got_error_from_errno2("%s: asprintf", __func__);
638 fcgi_gen_response(c, h);
645 const struct got_error *
646 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
649 const struct got_error *error = NULL;
652 if (asprintf(&h, "Content-type: %s\r\n"
653 "Content-disposition: attachment; filename=%s\r\n\r\n",
655 error = got_error_from_errno2("%s: asprintf", __func__);
659 fcgi_gen_response(c, h);
666 static const struct got_error *
667 gotweb_render_header(struct request *c)
669 const struct got_error *error = NULL;
670 struct server *srv = c->srv;
671 struct querystring *qs = c->t->qs;
672 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
673 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
675 if (strlen(c->document_root) > 0) {
676 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
677 error = got_error_from_errno2("%s: asprintf", __func__);
681 if (asprintf(&droot, "/") == -1) {
682 error = got_error_from_errno2("%s: asprintf", __func__);
687 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
688 error = got_error_from_errno2("%s: asprintf", __func__);
692 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
693 droot, srv->custom_css) == -1) {
694 error = got_error_from_errno2("%s: asprintf", __func__);
697 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
698 srv->logo_url) == -1) {
699 error = got_error_from_errno2("%s: asprintf", __func__);
702 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
703 droot, srv->logo) == -1) {
704 error = got_error_from_errno2("%s: asprintf", __func__);
707 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
708 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
709 srv->site_link) == -1) {
710 error = got_error_from_errno2("%s: asprintf", __func__);
713 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
714 "&action=summary' alt='summlink'>%s</a>", c->document_root,
715 qs->index_page, qs->path, qs->path) == -1) {
716 error = got_error_from_errno2("%s: asprintf", __func__);
720 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
722 if (fcgi_gen_response(c, title) == -1)
724 if (fcgi_gen_response(c, "<meta name='viewport' "
725 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
727 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
729 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
730 "content='#da532c'/>\n") == -1)
732 if (fcgi_gen_response(c,
733 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
735 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
736 "href='/apple-touch-icon.png'/>\n") == -1)
738 if (fcgi_gen_response(c,
739 "<link rel='icon' type='image/png' sizes='32x32' "
740 "href='/favicon-32x32.png'/>\n") == -1)
742 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
743 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
745 if (fcgi_gen_response(c, "<link rel='manifest' "
746 "href='/site.webmanifest'/>\n") == -1)
748 if (fcgi_gen_response(c, "<link rel='mask-icon' "
749 "href='/safari-pinned-tab.svg'/>\n") == -1)
751 if (fcgi_gen_response(c, css) == -1)
753 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
755 if (fcgi_gen_response(c,
756 "<div id='header'>\n<div id='got_link'>") == -1)
758 if (fcgi_gen_response(c, gotlink) == -1)
760 if (fcgi_gen_response(c, gotimg) == -1)
762 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
764 if (fcgi_gen_response(c,
765 "<div id='site_path'>\n<div id='site_link'>") == -1)
767 if (fcgi_gen_response(c, sitelink) == -1)
770 if (qs->path != NULL) {
771 if (fcgi_gen_response(c, " / ") == -1)
773 if (fcgi_gen_response(c, summlink) == -1)
776 if (qs->action != INDEX) {
777 if (fcgi_gen_response(c, " / ") == -1)
781 if (fcgi_gen_response(c, "blame") == -1)
785 if (fcgi_gen_response(c, "briefs") == -1)
789 if (fcgi_gen_response(c, "commits") == -1)
793 if (fcgi_gen_response(c, "diff") == -1)
797 if (fcgi_gen_response(c, "summary") == -1)
801 if (fcgi_gen_response(c, "tag") == -1)
805 if (fcgi_gen_response(c, "tags") == -1)
809 if (fcgi_gen_response(c, "tree") == -1)
818 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
831 static const struct got_error *
832 gotweb_render_footer(struct request *c)
834 const struct got_error *error = NULL;
835 struct server *srv = c->srv;
836 char *siteowner = NULL;
838 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
840 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
842 if (srv->show_site_owner) {
843 error = gotweb_escape_html(&siteowner, srv->site_owner);
846 if (fcgi_gen_response(c, siteowner) == -1)
849 if (fcgi_gen_response(c, " ") == -1)
851 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
858 static const struct got_error *
859 gotweb_render_navs(struct request *c)
861 const struct got_error *error = NULL;
862 struct transport *t = c->t;
863 struct querystring *qs = t->qs;
864 struct server *srv = c->srv;
865 char *nhref = NULL, *phref = NULL;
868 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
870 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
875 if (qs->index_page > 0) {
876 if (asprintf(&phref, "index_page=%d",
877 qs->index_page - 1) == -1) {
878 error = got_error_from_errno2("%s: asprintf",
886 if (t->prev_id && qs->commit != NULL &&
887 strcmp(qs->commit, t->prev_id) != 0) {
888 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
889 "&action=briefs&commit=%s&headref=%s",
890 qs->index_page, qs->path, qs->page - 1, t->prev_id,
891 qs->headref) == -1) {
892 error = got_error_from_errno2("%s: asprintf",
900 if (t->prev_id && qs->commit != NULL &&
901 strcmp(qs->commit, t->prev_id) != 0) {
902 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
903 "&action=commits&commit=%s&headref=%s&folder=%s"
905 qs->index_page, qs->path, qs->page - 1, t->prev_id,
906 qs->headref, qs->folder ? qs->folder : "",
907 qs->file ? qs->file : "") == -1) {
908 error = got_error_from_errno2("%s: asprintf",
916 if (t->prev_id && qs->commit != NULL &&
917 strcmp(qs->commit, t->prev_id) != 0) {
918 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
919 "&action=tags&commit=%s&headref=%s",
920 qs->index_page, qs->path, qs->page - 1, t->prev_id,
921 qs->headref) == -1) {
922 error = got_error_from_errno2("%s: asprintf",
935 if (fcgi_gen_response(c, "<a href='?") == -1)
937 if (fcgi_gen_response(c, phref) == -1)
939 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
942 if (fcgi_gen_response(c, "</div>\n") == -1)
944 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
951 if (t->next_disp == srv->max_repos_display &&
952 t->repos_total != (qs->index_page + 1) *
953 srv->max_repos_display) {
954 if (asprintf(&nhref, "index_page=%d",
955 qs->index_page + 1) == -1) {
956 error = got_error_from_errno2("%s: asprintf",
965 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
966 "&action=briefs&commit=%s&headref=%s",
967 qs->index_page, qs->path, qs->page + 1, t->next_id,
968 qs->headref) == -1) {
969 error = got_error_from_errno2("%s: asprintf",
978 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
979 "&action=commits&commit=%s&headref=%s&folder=%s"
981 qs->index_page, qs->path, qs->page + 1, t->next_id,
982 qs->headref, qs->folder ? qs->folder : "",
983 qs->file ? qs->file : "") == -1) {
984 error = got_error_from_errno2("%s: asprintf",
993 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
994 "&action=tags&commit=%s&headref=%s",
995 qs->index_page, qs->path, qs->page + 1, t->next_id,
996 qs->headref) == -1) {
997 error = got_error_from_errno2("%s: asprintf",
1009 if (fcgi_gen_response(c, "<a href='?") == -1)
1011 if (fcgi_gen_response(c, nhref) == -1)
1013 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1016 fcgi_gen_response(c, "</div>\n");
1027 static const struct got_error *
1028 gotweb_render_index(struct request *c)
1030 const struct got_error *error = NULL;
1031 struct server *srv = c->srv;
1032 struct transport *t = c->t;
1033 struct querystring *qs = t->qs;
1034 struct repo_dir *repo_dir = NULL;
1036 struct dirent **sd_dent;
1037 char *c_path = NULL;
1039 unsigned int d_cnt, d_i, d_disp = 0;
1041 d = opendir(srv->repos_path);
1043 error = got_error_from_errno2("opendir", srv->repos_path);
1047 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1049 error = got_error_from_errno2("scandir", srv->repos_path);
1053 /* get total count of repos */
1054 for (d_i = 0; d_i < d_cnt; d_i++) {
1055 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1056 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1059 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1060 sd_dent[d_i]->d_name) == -1) {
1061 error = got_error_from_errno("asprintf");
1065 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1066 !got_path_dir_is_empty(c_path))
1072 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1074 if (fcgi_gen_response(c,
1075 "<div id='index_header_project'>Project</div>\n") == -1)
1077 if (srv->show_repo_description)
1078 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1079 "Description</div>\n") == -1)
1081 if (srv->show_repo_owner)
1082 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1083 "Owner</div>\n") == -1)
1085 if (srv->show_repo_age)
1086 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1087 "Last Change</div>\n") == -1)
1089 if (fcgi_gen_response(c, "</div>\n") == -1)
1092 for (d_i = 0; d_i < d_cnt; d_i++) {
1093 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1094 break; /* account for parent and self */
1096 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1097 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1100 if (qs->index_page > 0 && (qs->index_page *
1101 srv->max_repos_display) > t->prev_disp) {
1106 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1110 error = gotweb_load_got_path(c, repo_dir);
1111 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1115 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1118 if (lstat(repo_dir->path, &st) == 0 &&
1119 S_ISDIR(st.st_mode) &&
1120 !got_path_dir_is_empty(repo_dir->path))
1123 gotweb_free_repo_dir(repo_dir);
1130 if (fcgi_gen_response(c, "<div class='index_wrapper'>\n") == -1)
1132 if (fcgi_gen_response(c, "<div class='index_project'>") == -1)
1135 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1137 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1139 if (fcgi_gen_response(c, "&path=") == -1)
1141 if (fcgi_gen_response(c, repo_dir->name) == -1)
1143 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1145 if (fcgi_gen_response(c, repo_dir->name) == -1)
1147 if (fcgi_gen_response(c, "</a>") == -1)
1150 if (fcgi_gen_response(c, "</div>\n") == -1)
1153 if (srv->show_repo_description) {
1154 if (fcgi_gen_response(c,
1155 "<div class='index_project_description'>\n") == -1)
1157 if (fcgi_gen_response(c, repo_dir->description) == -1)
1159 if (fcgi_gen_response(c, "</div>\n") == -1)
1163 if (srv->show_repo_owner) {
1164 if (fcgi_gen_response(c,
1165 "<div class='index_project_owner'>") == -1)
1167 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1169 if (fcgi_gen_response(c, "</div>\n") == -1)
1173 if (srv->show_repo_age) {
1174 if (fcgi_gen_response(c,
1175 "<div class='index_project_age'>") == -1)
1177 if (fcgi_gen_response(c, repo_dir->age) == -1)
1179 if (fcgi_gen_response(c, "</div>\n") == -1)
1183 if (fcgi_gen_response(c, "<div class='navs_wrapper'>") == -1)
1185 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1188 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1190 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1192 if (fcgi_gen_response(c, "&path=") == -1)
1194 if (fcgi_gen_response(c, repo_dir->name) == -1)
1196 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1198 if (fcgi_gen_response(c, "summary") == -1)
1200 if (fcgi_gen_response(c, "</a> | ") == -1)
1203 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1205 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1207 if (fcgi_gen_response(c, "&path=") == -1)
1209 if (fcgi_gen_response(c, repo_dir->name) == -1)
1211 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1213 if (fcgi_gen_response(c, "commit briefs") == -1)
1215 if (fcgi_gen_response(c, "</a> | ") == -1)
1218 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1220 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1222 if (fcgi_gen_response(c, "&path=") == -1)
1224 if (fcgi_gen_response(c, repo_dir->name) == -1)
1226 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1228 if (fcgi_gen_response(c, "commits") == -1)
1230 if (fcgi_gen_response(c, "</a> | ") == -1)
1233 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1235 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1237 if (fcgi_gen_response(c, "&path=") == -1)
1239 if (fcgi_gen_response(c, repo_dir->name) == -1)
1241 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1243 if (fcgi_gen_response(c, "tags") == -1)
1245 if (fcgi_gen_response(c, "</a> | ") == -1)
1248 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1250 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1252 if (fcgi_gen_response(c, "&path=") == -1)
1254 if (fcgi_gen_response(c, repo_dir->name) == -1)
1256 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1258 if (fcgi_gen_response(c, "tree") == -1)
1260 if (fcgi_gen_response(c, "</a>") == -1)
1263 if (fcgi_gen_response(c, "</div>") == -1)
1265 if (fcgi_gen_response(c,
1266 "<div class='dotted_line'></div>\n") == -1)
1268 if (fcgi_gen_response(c, "</div>\n") == -1)
1270 if (fcgi_gen_response(c, "</div>\n") == -1)
1273 gotweb_free_repo_dir(repo_dir);
1275 error = got_repo_close(t->repo);
1279 if (d_disp == srv->max_repos_display)
1282 if (srv->max_repos_display == 0)
1284 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1286 if (t->repos_total <= srv->max_repos ||
1287 t->repos_total <= srv->max_repos_display)
1290 error = gotweb_render_navs(c);
1294 fcgi_gen_response(c, "</div>\n");
1296 if (d != NULL && closedir(d) == EOF && error == NULL)
1297 error = got_error_from_errno("closedir");
1301 static const struct got_error *
1302 gotweb_render_blame(struct request *c)
1304 const struct got_error *error = NULL;
1305 struct transport *t = c->t;
1306 struct repo_commit *rc = NULL;
1309 error = got_get_repo_commits(c, 1);
1313 rc = TAILQ_FIRST(&t->repo_commits);
1315 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1319 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1321 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1323 if (fcgi_gen_response(c, "</div>\n") == -1)
1326 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1329 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1331 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1334 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
1337 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
1339 if (fcgi_gen_response(c, age ? age : "") == -1)
1341 if (fcgi_gen_response(c, "</div>\n") == -1)
1344 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1347 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1349 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1351 if (fcgi_gen_response(c, "</div>\n") == -1)
1354 if (fcgi_gen_response(c, "</div>\n") == -1)
1356 if (fcgi_gen_response(c, "</div>\n") == -1)
1359 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
1361 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1364 error = got_output_file_blame(c);
1368 fcgi_gen_response(c, "</div>\n");
1370 fcgi_gen_response(c, "</div>\n");
1374 static const struct got_error *
1375 gotweb_render_briefs(struct request *c)
1377 const struct got_error *error = NULL;
1378 struct repo_commit *rc = NULL;
1379 struct server *srv = c->srv;
1380 struct transport *t = c->t;
1381 struct querystring *qs = t->qs;
1382 struct repo_dir *repo_dir = t->repo_dir;
1383 char *smallerthan, *newline;
1386 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1388 if (fcgi_gen_response(c,
1389 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1391 if (fcgi_gen_response(c, "</div>\n") == -1)
1394 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1397 if (qs->action == SUMMARY) {
1398 qs->action = BRIEFS;
1399 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1401 error = got_get_repo_commits(c, srv->max_commits_display);
1405 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1406 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1409 if (fcgi_gen_response(c, "<div class='briefs_age'>") == -1)
1411 if (fcgi_gen_response(c, age ? age : "") == -1)
1413 if (fcgi_gen_response(c, "</div>\n") == -1)
1416 if (fcgi_gen_response(c, "<div class='briefs_author'>") == -1)
1418 smallerthan = strchr(rc->author, '<');
1420 *smallerthan = '\0';
1421 if (fcgi_gen_response(c, rc->author) == -1)
1423 if (fcgi_gen_response(c, "</div>\n") == -1)
1426 if (fcgi_gen_response(c, "<div class='briefs_log'>") == -1)
1428 newline = strchr(rc->commit_msg, '\n');
1432 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1434 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1436 if (fcgi_gen_response(c, "&path=") == -1)
1438 if (fcgi_gen_response(c, repo_dir->name) == -1)
1440 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1442 if (fcgi_gen_response(c, rc->commit_id) == -1)
1444 if (fcgi_gen_response(c, "&headref=") == -1)
1446 if (fcgi_gen_response(c, qs->headref) == -1)
1448 if (fcgi_gen_response(c, "'>") == -1)
1450 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1452 if (fcgi_gen_response(c, "</a>") == -1)
1455 if (fcgi_gen_response(c,
1456 " <span class='refs_str'>(") == -1)
1458 if (fcgi_gen_response(c, rc->refs_str) == -1)
1460 if (fcgi_gen_response(c, ")</span>") == -1)
1463 if (fcgi_gen_response(c, "</div>\n") == -1)
1466 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
1468 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1470 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1472 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1474 if (fcgi_gen_response(c, "&path=") == -1)
1476 if (fcgi_gen_response(c, repo_dir->name) == -1)
1478 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1480 if (fcgi_gen_response(c, rc->commit_id) == -1)
1482 if (fcgi_gen_response(c, "&headref=") == -1)
1484 if (fcgi_gen_response(c, qs->headref) == -1)
1486 if (fcgi_gen_response(c, "'>") == -1)
1488 if (fcgi_gen_response(c, "diff") == -1)
1490 if (fcgi_gen_response(c, "</a>") == -1)
1493 if (fcgi_gen_response(c, " | ") == -1)
1496 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1498 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1500 if (fcgi_gen_response(c, "&path=") == -1)
1502 if (fcgi_gen_response(c, repo_dir->name) == -1)
1504 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1506 if (fcgi_gen_response(c, rc->commit_id) == -1)
1508 if (fcgi_gen_response(c, "&headref=") == -1)
1510 if (fcgi_gen_response(c, qs->headref) == -1)
1512 if (fcgi_gen_response(c, "'>") == -1)
1514 if (fcgi_gen_response(c, "tree") == -1)
1516 if (fcgi_gen_response(c, "</a>") == -1)
1518 if (fcgi_gen_response(c, "</div>\n") == -1)
1520 if (fcgi_gen_response(c, "</div>\n") == -1)
1522 if (fcgi_gen_response(c,
1523 "<div class='dotted_line'></div>\n") == -1)
1530 if (t->next_id || t->prev_id) {
1531 error = gotweb_render_navs(c);
1535 fcgi_gen_response(c, "</div>\n");
1541 static const struct got_error *
1542 gotweb_render_commits(struct request *c)
1544 const struct got_error *error = NULL;
1545 struct repo_commit *rc = NULL;
1546 struct server *srv = c->srv;
1547 struct transport *t = c->t;
1548 struct querystring *qs = t->qs;
1549 struct repo_dir *repo_dir = t->repo_dir;
1550 char *age = NULL, *author = NULL;
1551 /* int commit_found = 0; */
1553 if (fcgi_gen_response(c, "<div class='commits_title_wrapper'>\n") == -1)
1555 if (fcgi_gen_response(c,
1556 "<div class='commits_title'>Commits</div>\n") == -1)
1558 if (fcgi_gen_response(c, "</div>\n") == -1)
1561 if (fcgi_gen_response(c, "<div class='commits_content'>\n") == -1)
1564 error = got_get_repo_commits(c, srv->max_commits_display);
1568 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1569 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1572 error = gotweb_escape_html(&author, rc->author);
1576 if (fcgi_gen_response(c,
1577 "<div class='commits_header_wrapper'>\n") == -1)
1579 if (fcgi_gen_response(c, "<div class='commits_header'>\n") == -1)
1583 if (fcgi_gen_response(c, "<div class='header_commit_title'>Commit:"
1586 if (fcgi_gen_response(c, "<div class='header_commit'>") == -1)
1588 if (fcgi_gen_response(c, rc->commit_id) == -1)
1590 if (fcgi_gen_response(c, "</div>\n") == -1)
1593 if (fcgi_gen_response(c, "<div class='header_author_title'>Author:"
1596 if (fcgi_gen_response(c, "<div class='header_author'>") == -1)
1598 if (fcgi_gen_response(c, author ? author : "") == -1)
1600 if (fcgi_gen_response(c, "</div>\n") == -1)
1603 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
1606 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
1608 if (fcgi_gen_response(c, age ? age : "") == -1)
1610 if (fcgi_gen_response(c, "</div>\n") == -1)
1613 if (fcgi_gen_response(c, "</div>\n") == -1)
1615 if (fcgi_gen_response(c, "</div>\n") == -1)
1618 if (fcgi_gen_response(c,
1619 "<div class='dotted_line'></div>\n") == -1)
1621 if (fcgi_gen_response(c, "<div class='commit'>\n") == -1)
1624 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1627 if (fcgi_gen_response(c, "</div>\n") == -1)
1629 if (fcgi_gen_response(c, "</div>\n") == -1)
1632 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
1634 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1636 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1638 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1640 if (fcgi_gen_response(c, "&path=") == -1)
1642 if (fcgi_gen_response(c, repo_dir->name) == -1)
1644 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1646 if (fcgi_gen_response(c, rc->commit_id) == -1)
1648 if (fcgi_gen_response(c, "'>") == -1)
1650 if (fcgi_gen_response(c, "diff") == -1)
1652 if (fcgi_gen_response(c, "</a>") == -1)
1655 if (fcgi_gen_response(c, " | ") == -1)
1658 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1660 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1662 if (fcgi_gen_response(c, "&path=") == -1)
1664 if (fcgi_gen_response(c, repo_dir->name) == -1)
1666 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1668 if (fcgi_gen_response(c, rc->commit_id) == -1)
1670 if (fcgi_gen_response(c, "'>") == -1)
1672 if (fcgi_gen_response(c, "tree") == -1)
1674 if (fcgi_gen_response(c, "</a>") == -1)
1676 if (fcgi_gen_response(c, "</div>\n") == -1)
1678 if (fcgi_gen_response(c, "</div>\n") == -1)
1680 if (fcgi_gen_response(c,
1681 "<div class='dotted_line'></div>\n") == -1)
1689 if (t->next_id || t->prev_id) {
1690 error = gotweb_render_navs(c);
1694 if (fcgi_gen_response(c, "</div>\n") == -1)
1701 static const struct got_error *
1702 gotweb_render_branches(struct request *c)
1704 const struct got_error *error = NULL;
1705 struct got_reflist_head refs;
1706 struct got_reflist_entry *re;
1707 struct transport *t = c->t;
1708 struct querystring *qs = t->qs;
1709 struct got_repository *repo = t->repo;
1714 error = got_ref_list(&refs, repo, "refs/heads",
1715 got_ref_cmp_by_name, NULL);
1719 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1721 if (fcgi_gen_response(c,
1722 "<div id='branches_title'>Branches</div>\n") == -1)
1724 if (fcgi_gen_response(c, "</div>\n") == -1)
1727 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1730 TAILQ_FOREACH(re, &refs, entry) {
1731 char *refname = NULL;
1733 if (got_ref_is_symbolic(re->ref))
1736 refname = strdup(got_ref_get_name(re->ref));
1737 if (refname == NULL) {
1738 error = got_error_from_errno("strdup");
1741 if (strncmp(refname, "refs/heads/", 11) != 0)
1744 error = got_get_repo_age(&age, c, qs->path, refname,
1749 if (strncmp(refname, "refs/heads/", 11) == 0)
1752 if (fcgi_gen_response(c, "<div class='branches_wrapper'>") == -1)
1755 if (fcgi_gen_response(c, "<div class='branches_age'>") == -1)
1757 if (fcgi_gen_response(c, age ? age : "") == -1)
1759 if (fcgi_gen_response(c, "</div>\n") == -1)
1762 if (fcgi_gen_response(c, "<div class='branches_space'>") == -1)
1764 if (fcgi_gen_response(c, " ") == -1)
1766 if (fcgi_gen_response(c, "</div>\n") == -1)
1769 if (fcgi_gen_response(c, "<div class='branch'>") == -1)
1771 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1773 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1775 if (fcgi_gen_response(c, "&path=") == -1)
1777 if (fcgi_gen_response(c, qs->path) == -1)
1779 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1781 if (fcgi_gen_response(c, refname) == -1)
1783 if (fcgi_gen_response(c, "'>") == -1)
1785 if (fcgi_gen_response(c, refname) == -1)
1787 if (fcgi_gen_response(c, "</a>") == -1)
1789 if (fcgi_gen_response(c, "</div>\n") == -1)
1792 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
1794 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1797 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1799 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1801 if (fcgi_gen_response(c, "&path=") == -1)
1803 if (fcgi_gen_response(c, qs->path) == -1)
1805 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1807 if (fcgi_gen_response(c, refname) == -1)
1809 if (fcgi_gen_response(c, "'>") == -1)
1811 if (fcgi_gen_response(c, "summary") == -1)
1813 if (fcgi_gen_response(c, "</a>") == -1)
1816 if (fcgi_gen_response(c, " | ") == -1)
1819 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1821 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1823 if (fcgi_gen_response(c, "&path=") == -1)
1825 if (fcgi_gen_response(c, qs->path) == -1)
1827 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1829 if (fcgi_gen_response(c, refname) == -1)
1831 if (fcgi_gen_response(c, "'>") == -1)
1833 if (fcgi_gen_response(c, "commit briefs") == -1)
1835 if (fcgi_gen_response(c, "</a>") == -1)
1838 if (fcgi_gen_response(c, " | ") == -1)
1841 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1843 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1845 if (fcgi_gen_response(c, "&path=") == -1)
1847 if (fcgi_gen_response(c, qs->path) == -1)
1849 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1851 if (fcgi_gen_response(c, refname) == -1)
1853 if (fcgi_gen_response(c, "'>") == -1)
1855 if (fcgi_gen_response(c, "commits") == -1)
1857 if (fcgi_gen_response(c, "</a>") == -1)
1860 if (fcgi_gen_response(c, "</div>\n") == -1)
1862 if (fcgi_gen_response(c, "</div>\n") == -1)
1865 if (fcgi_gen_response(c,
1866 "<div class='dotted_line'></div>\n") == -1)
1869 /* branches_wrapper */
1870 if (fcgi_gen_response(c, "</div>\n") == -1)
1877 fcgi_gen_response(c, "</div>\n");
1882 static const struct got_error *
1883 gotweb_render_tree(struct request *c)
1885 const struct got_error *error = NULL;
1886 struct transport *t = c->t;
1887 struct repo_commit *rc = NULL;
1890 error = got_get_repo_commits(c, 1);
1894 rc = TAILQ_FIRST(&t->repo_commits);
1896 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1900 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1902 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1904 if (fcgi_gen_response(c, "</div>\n") == -1)
1907 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1910 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1912 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1915 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1918 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1920 if (fcgi_gen_response(c, rc->tree_id) == -1)
1922 if (fcgi_gen_response(c, "</div>\n") == -1)
1925 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
1928 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
1930 if (fcgi_gen_response(c, age ? age : "") == -1)
1932 if (fcgi_gen_response(c, "</div>\n") == -1)
1935 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1938 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1940 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1942 if (fcgi_gen_response(c, "</div>\n") == -1)
1945 if (fcgi_gen_response(c, "</div>\n") == -1)
1947 if (fcgi_gen_response(c, "</div>\n") == -1)
1950 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
1952 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1955 error = got_output_repo_tree(c);
1959 fcgi_gen_response(c, "</div>\n");
1960 fcgi_gen_response(c, "</div>\n");
1965 static const struct got_error *
1966 gotweb_render_diff(struct request *c)
1968 const struct got_error *error = NULL;
1969 struct transport *t = c->t;
1970 struct repo_commit *rc = NULL;
1971 char *age = NULL, *author = NULL;
1973 error = got_get_repo_commits(c, 1);
1977 rc = TAILQ_FIRST(&t->repo_commits);
1979 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1982 error = gotweb_escape_html(&author, rc->author);
1986 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1988 if (fcgi_gen_response(c,
1989 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1991 if (fcgi_gen_response(c, "</div>\n") == -1)
1994 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1996 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
1998 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
2001 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2004 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2006 if (fcgi_gen_response(c, rc->parent_id) == -1)
2008 if (fcgi_gen_response(c, "<br />") == -1)
2010 if (fcgi_gen_response(c, rc->commit_id) == -1)
2012 if (fcgi_gen_response(c, "</div>\n") == -1)
2015 if (fcgi_gen_response(c, "<div class='header_commit_title'>Commit:"
2018 if (fcgi_gen_response(c, "<div class='header_commit'>") == -1)
2020 if (fcgi_gen_response(c, rc->commit_id) == -1)
2022 if (fcgi_gen_response(c, "</div>\n") == -1)
2025 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2028 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2030 if (fcgi_gen_response(c, rc->tree_id) == -1)
2032 if (fcgi_gen_response(c, "</div>\n") == -1)
2035 if (fcgi_gen_response(c, "<div class='header_author_title'>Author:"
2038 if (fcgi_gen_response(c, "<div class='header_author'>") == -1)
2040 if (fcgi_gen_response(c, author ? author : "") == -1)
2042 if (fcgi_gen_response(c, "</div>\n") == -1)
2045 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
2048 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
2050 if (fcgi_gen_response(c, age ? age : "") == -1)
2052 if (fcgi_gen_response(c, "</div>\n") == -1)
2055 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2058 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2060 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2062 if (fcgi_gen_response(c, "</div>\n") == -1)
2064 if (fcgi_gen_response(c, "</div>\n") == -1)
2066 if (fcgi_gen_response(c, "</div>\n") == -1)
2069 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
2071 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2074 error = got_output_repo_diff(c);
2078 fcgi_gen_response(c, "</div>\n");
2080 fcgi_gen_response(c, "</div>\n");
2086 static const struct got_error *
2087 gotweb_render_summary(struct request *c)
2089 const struct got_error *error = NULL;
2090 struct transport *t = c->t;
2091 struct server *srv = c->srv;
2093 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2096 if (!srv->show_repo_description)
2099 if (fcgi_gen_response(c, "<div id='description_title'>"
2100 "Description:</div>\n") == -1)
2102 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2104 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2106 if (fcgi_gen_response(c, "</div>\n") == -1)
2109 if (!srv->show_repo_owner)
2112 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2113 "Owner:</div>\n") == -1)
2115 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2117 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2119 if (fcgi_gen_response(c, "</div>\n") == -1)
2122 if (!srv->show_repo_age)
2125 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2126 "Last Change:</div>\n") == -1)
2128 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2130 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2132 if (fcgi_gen_response(c, "</div>\n") == -1)
2135 if (!srv->show_repo_cloneurl)
2138 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2139 "Clone URL:</div>\n") == -1)
2141 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2143 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2145 if (fcgi_gen_response(c, "</div>\n") == -1)
2148 if (fcgi_gen_response(c, "</div>\n") == -1)
2150 if (fcgi_gen_response(c, "</div>\n") == -1)
2153 error = gotweb_render_briefs(c);
2155 log_warnx("%s: %s", __func__, error->msg);
2159 error = gotweb_render_tags(c);
2161 log_warnx("%s: %s", __func__, error->msg);
2165 error = gotweb_render_branches(c);
2167 log_warnx("%s: %s", __func__, error->msg);
2172 static const struct got_error *
2173 gotweb_render_tag(struct request *c)
2175 const struct got_error *error = NULL;
2176 struct repo_tag *rt = NULL;
2177 struct transport *t = c->t;
2178 char *age = NULL, *author = NULL;
2180 error = got_get_repo_tags(c, 1);
2184 if (t->tag_count == 0) {
2185 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2190 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2192 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2195 error = gotweb_escape_html(&author, rt->tagger);
2199 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2201 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2203 if (fcgi_gen_response(c, "</div>\n") == -1)
2206 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2208 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2210 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2213 if (fcgi_gen_response(c, "<div class='header_commit_title'>Commit:"
2216 if (fcgi_gen_response(c, "<div class='header_commit'>") == -1)
2218 if (fcgi_gen_response(c, rt->commit_id) == -1)
2221 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2224 if (fcgi_gen_response(c, " <span class='refs_str'>(") == -1)
2226 if (fcgi_gen_response(c, rt->tag_name) == -1)
2228 if (fcgi_gen_response(c, ")</span>") == -1)
2231 if (fcgi_gen_response(c, "</div>\n") == -1)
2234 if (fcgi_gen_response(c, "<div class='header_author_title'>Tagger:"
2237 if (fcgi_gen_response(c, "<div class='header_author'>") == -1)
2239 if (fcgi_gen_response(c, author ? author : "") == -1)
2241 if (fcgi_gen_response(c, "</div>\n") == -1)
2244 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
2247 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
2249 if (fcgi_gen_response(c, age ? age : "") == -1)
2251 if (fcgi_gen_response(c, "</div>\n") == -1)
2254 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2257 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2259 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2261 if (fcgi_gen_response(c, "</div>\n") == -1)
2263 if (fcgi_gen_response(c, "</div>\n") == -1)
2266 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
2268 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2271 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2274 if (fcgi_gen_response(c, "</div>\n") == -1)
2276 fcgi_gen_response(c, "</div>\n");
2283 static const struct got_error *
2284 gotweb_render_tags(struct request *c)
2286 const struct got_error *error = NULL;
2287 struct repo_tag *rt = NULL;
2288 struct server *srv = c->srv;
2289 struct transport *t = c->t;
2290 struct querystring *qs = t->qs;
2291 struct repo_dir *repo_dir = t->repo_dir;
2294 int commit_found = 0;
2296 if (qs->action == BRIEFS) {
2298 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2300 error = got_get_repo_tags(c, srv->max_commits_display);
2304 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2306 if (fcgi_gen_response(c,
2307 "<div id='tags_title'>Tags</div>\n") == -1)
2309 if (fcgi_gen_response(c, "</div>\n") == -1)
2312 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2315 if (t->tag_count == 0) {
2316 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2318 if (fcgi_gen_response(c,
2319 "This repository contains no tags\n") == -1)
2321 if (fcgi_gen_response(c, "</div>\n") == -1)
2323 if (fcgi_gen_response(c, "</div>\n") == -1)
2327 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2328 if (commit_found == 0 && qs->commit != NULL) {
2329 if (strcmp(qs->commit, rt->commit_id) != 0)
2334 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2337 if (fcgi_gen_response(c, "<div class='tag_age'>") == -1)
2339 if (fcgi_gen_response(c, age ? age : "") == -1)
2341 if (fcgi_gen_response(c, "</div>\n") == -1)
2344 if (fcgi_gen_response(c, "<div class='tag'>") == -1)
2346 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2348 if (fcgi_gen_response(c, rt->tag_name) == -1)
2350 if (fcgi_gen_response(c, "</div>\n") == -1)
2353 if (fcgi_gen_response(c, "<div class='tag_log'>") == -1)
2355 if (rt->tag_commit != NULL) {
2356 newline = strchr(rt->tag_commit, '\n');
2361 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2363 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2365 if (fcgi_gen_response(c, "&path=") == -1)
2367 if (fcgi_gen_response(c, repo_dir->name) == -1)
2369 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2371 if (fcgi_gen_response(c, rt->commit_id) == -1)
2373 if (fcgi_gen_response(c, "'>") == -1)
2375 if (rt->tag_commit != NULL &&
2376 fcgi_gen_response(c, rt->tag_commit) == -1)
2378 if (fcgi_gen_response(c, "</a>") == -1)
2380 if (fcgi_gen_response(c, "</div>\n") == -1)
2383 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
2385 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
2388 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2390 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2392 if (fcgi_gen_response(c, "&path=") == -1)
2394 if (fcgi_gen_response(c, repo_dir->name) == -1)
2396 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2398 if (fcgi_gen_response(c, rt->commit_id) == -1)
2400 if (fcgi_gen_response(c, "'>") == -1)
2402 if (fcgi_gen_response(c, "tag") == -1)
2404 if (fcgi_gen_response(c, "</a>") == -1)
2407 if (fcgi_gen_response(c, " | ") == -1)
2410 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2412 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2414 if (fcgi_gen_response(c, "&path=") == -1)
2416 if (fcgi_gen_response(c, repo_dir->name) == -1)
2418 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2420 if (fcgi_gen_response(c, rt->commit_id) == -1)
2422 if (fcgi_gen_response(c, "'>") == -1)
2424 if (fcgi_gen_response(c, "commit briefs") == -1)
2426 if (fcgi_gen_response(c, "</a>") == -1)
2429 if (fcgi_gen_response(c, " | ") == -1)
2432 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2434 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2436 if (fcgi_gen_response(c, "&path=") == -1)
2438 if (fcgi_gen_response(c, repo_dir->name) == -1)
2440 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2442 if (fcgi_gen_response(c, rt->commit_id) == -1)
2444 if (fcgi_gen_response(c, "'>") == -1)
2446 if (fcgi_gen_response(c, "commits") == -1)
2448 if (fcgi_gen_response(c, "</a>") == -1)
2451 if (fcgi_gen_response(c, "</div>\n") == -1)
2453 if (fcgi_gen_response(c, "</div>\n") == -1)
2455 if (fcgi_gen_response(c,
2456 "<div class='dotted_line'></div>\n") == -1)
2462 if (t->next_id || t->prev_id) {
2463 error = gotweb_render_navs(c);
2467 fcgi_gen_response(c, "</div>\n");
2473 const struct got_error *
2474 gotweb_escape_html(char **escaped_html, const char *orig_html)
2476 const struct got_error *error = NULL;
2477 struct escape_pair {
2488 size_t orig_len, len;
2491 orig_len = strlen(orig_html);
2493 for (i = 0; i < orig_len; i++) {
2494 for (j = 0; j < nitems(esc); j++) {
2495 if (orig_html[i] != esc[j].c)
2497 len += strlen(esc[j].s) - 1 /* escaped char */;
2501 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2502 if (*escaped_html == NULL)
2503 return got_error_from_errno("calloc");
2506 for (i = 0; i < orig_len; i++) {
2508 for (j = 0; j < nitems(esc); j++) {
2509 if (orig_html[i] != esc[j].c)
2512 if (strlcat(*escaped_html, esc[j].s, len + 1)
2514 error = got_error(GOT_ERR_NO_SPACE);
2517 x += strlen(esc[j].s);
2522 (*escaped_html)[x] = orig_html[i];
2528 free(*escaped_html);
2529 *escaped_html = NULL;
2531 (*escaped_html)[x] = '\0';
2537 static const struct got_error *
2538 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2540 const struct got_error *error = NULL;
2541 struct socket *sock = c->sock;
2542 struct server *srv = c->srv;
2543 struct transport *t = c->t;
2548 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2549 GOTWEB_GIT_DIR) == -1)
2550 return got_error_from_errno("asprintf");
2552 dt = opendir(dir_test);
2556 repo_dir->path = strdup(dir_test);
2557 if (repo_dir->path == NULL) {
2559 error = got_error_from_errno("strdup");
2566 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2567 GOTWEB_GOT_DIR) == -1) {
2569 error = got_error_from_errno("asprintf");
2573 dt = opendir(dir_test);
2578 error = got_error(GOT_ERR_NOT_GIT_REPO);
2582 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2583 repo_dir->name) == -1) {
2584 error = got_error_from_errno("asprintf");
2589 repo_dir->path = strdup(dir_test);
2590 if (repo_dir->path == NULL) {
2592 error = got_error_from_errno("strdup");
2596 dt = opendir(dir_test);
2598 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2603 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2606 error = gotweb_get_repo_description(&repo_dir->description, srv,
2610 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2613 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2617 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2621 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2622 error = got_error_from_errno("closedir");
2626 static const struct got_error *
2627 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2629 const struct got_error *error;
2631 *repo_dir = calloc(1, sizeof(**repo_dir));
2632 if (*repo_dir == NULL)
2633 return got_error_from_errno("calloc");
2635 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2636 error = got_error_from_errno("asprintf");
2641 (*repo_dir)->owner = NULL;
2642 (*repo_dir)->description = NULL;
2643 (*repo_dir)->url = NULL;
2644 (*repo_dir)->age = NULL;
2645 (*repo_dir)->path = NULL;
2650 static const struct got_error *
2651 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2653 const struct got_error *error = NULL;
2655 char *d_file = NULL;
2659 *description = NULL;
2660 if (srv->show_repo_description == 0)
2663 if (asprintf(&d_file, "%s/description", dir) == -1)
2664 return got_error_from_errno("asprintf");
2666 f = fopen(d_file, "r");
2668 if (errno == ENOENT || errno == EACCES)
2670 error = got_error_from_errno2("fopen", d_file);
2674 if (fseek(f, 0, SEEK_END) == -1) {
2675 error = got_ferror(f, GOT_ERR_IO);
2680 error = got_ferror(f, GOT_ERR_IO);
2687 if (fseek(f, 0, SEEK_SET) == -1) {
2688 error = got_ferror(f, GOT_ERR_IO);
2691 *description = calloc(len + 1, sizeof(**description));
2692 if (*description == NULL) {
2693 error = got_error_from_errno("calloc");
2697 n = fread(*description, 1, len, f);
2698 if (n == 0 && ferror(f))
2699 error = got_ferror(f, GOT_ERR_IO);
2701 if (f != NULL && fclose(f) == EOF && error == NULL)
2702 error = got_error_from_errno("fclose");
2707 static const struct got_error *
2708 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2710 const struct got_error *error = NULL;
2712 char *d_file = NULL;
2718 if (srv->show_repo_cloneurl == 0)
2721 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2722 return got_error_from_errno("asprintf");
2724 f = fopen(d_file, "r");
2726 if (errno != ENOENT && errno != EACCES)
2727 error = got_error_from_errno2("fopen", d_file);
2731 if (fseek(f, 0, SEEK_END) == -1) {
2732 error = got_ferror(f, GOT_ERR_IO);
2737 error = got_ferror(f, GOT_ERR_IO);
2743 if (fseek(f, 0, SEEK_SET) == -1) {
2744 error = got_ferror(f, GOT_ERR_IO);
2748 *url = calloc(len + 1, sizeof(**url));
2750 error = got_error_from_errno("calloc");
2754 n = fread(*url, 1, len, f);
2755 if (n == 0 && ferror(f))
2756 error = got_ferror(f, GOT_ERR_IO);
2758 if (f != NULL && fclose(f) == EOF && error == NULL)
2759 error = got_error_from_errno("fclose");
2764 const struct got_error *
2765 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2768 long long diff_time;
2769 const char *years = "years ago", *months = "months ago";
2770 const char *weeks = "weeks ago", *days = "days ago";
2771 const char *hours = "hours ago", *minutes = "minutes ago";
2772 const char *seconds = "seconds ago", *now = "right now";
2780 diff_time = time(NULL) - committer_time;
2781 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2782 if (asprintf(repo_age, "%lld %s",
2783 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2784 return got_error_from_errno("asprintf");
2785 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2786 if (asprintf(repo_age, "%lld %s",
2787 (diff_time / 60 / 60 / 24 / (365 / 12)),
2789 return got_error_from_errno("asprintf");
2790 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2791 if (asprintf(repo_age, "%lld %s",
2792 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2793 return got_error_from_errno("asprintf");
2794 } else if (diff_time > 60 * 60 * 24 * 2) {
2795 if (asprintf(repo_age, "%lld %s",
2796 (diff_time / 60 / 60 / 24), days) == -1)
2797 return got_error_from_errno("asprintf");
2798 } else if (diff_time > 60 * 60 * 2) {
2799 if (asprintf(repo_age, "%lld %s",
2800 (diff_time / 60 / 60), hours) == -1)
2801 return got_error_from_errno("asprintf");
2802 } else if (diff_time > 60 * 2) {
2803 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2805 return got_error_from_errno("asprintf");
2806 } else if (diff_time > 2) {
2807 if (asprintf(repo_age, "%lld %s", diff_time,
2809 return got_error_from_errno("asprintf");
2811 if (asprintf(repo_age, "%s", now) == -1)
2812 return got_error_from_errno("asprintf");
2816 if (gmtime_r(&committer_time, &tm) == NULL)
2817 return got_error_from_errno("gmtime_r");
2819 s = asctime_r(&tm, datebuf);
2821 return got_error_from_errno("asctime_r");
2823 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2824 return got_error_from_errno("asprintf");