Blob


1 /*
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>
6 *
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.
10 *
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.
18 */
20 #include <net/if.h>
21 #include <netinet/in.h>
22 #include <sys/queue.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
26 #include <dirent.h>
27 #include <errno.h>
28 #include <event.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
34 #include "got_error.h"
35 #include "got_object.h"
36 #include "got_reference.h"
37 #include "got_repository.h"
38 #include "got_path.h"
39 #include "got_cancel.h"
40 #include "got_worktree.h"
41 #include "got_diff.h"
42 #include "got_commit_graph.h"
43 #include "got_blame.h"
44 #include "got_privsep.h"
46 #include "proc.h"
47 #include "gotwebd.h"
49 #include "got_compat.h"
51 enum gotweb_ref_tm {
52 TM_DIFF,
53 TM_LONG,
54 };
56 static const struct querystring_keys querystring_keys[] = {
57 { "action", ACTION },
58 { "commit", COMMIT },
59 { "file", RFILE },
60 { "folder", FOLDER },
61 { "headref", HEADREF },
62 { "index_page", INDEX_PAGE },
63 { "path", PATH },
64 { "page", PAGE },
65 };
67 static const struct action_keys action_keys[] = {
68 { "blame", BLAME },
69 { "blob", BLOB },
70 { "briefs", BRIEFS },
71 { "commits", COMMITS },
72 { "diff", DIFF },
73 { "error", ERR },
74 { "index", INDEX },
75 { "summary", SUMMARY },
76 { "tag", TAG },
77 { "tags", TAGS },
78 { "tree", TREE },
79 };
81 static const struct got_error *gotweb_init_querystring(struct querystring **);
82 static const struct got_error *gotweb_parse_querystring(struct querystring **,
83 char *);
84 static const struct got_error *gotweb_assign_querystring(struct querystring **,
85 char *, char *);
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 **,
90 const char *);
91 static const struct got_error *gotweb_load_got_path(struct request *c,
92 struct repo_dir *);
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 *,
96 char *);
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 *);
113 void
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: ";
121 int html = 0;
123 /* init the transport */
124 error = gotweb_init_transport(&c->t);
125 if (error) {
126 log_warnx("%s: %s", __func__, error->msg);
127 goto err;
129 /* don't process any further if client disconnected */
130 if (c->sock->client_status == CLIENT_DISCONNECT)
131 return;
132 /* get the gotwebd server */
133 srv = gotweb_get_server(c->server_name, c->document_root, c->http_host);
134 if (srv == NULL) {
135 log_warnx("%s: error server is NULL", __func__);
136 goto err;
138 c->srv = srv;
139 /* parse our querystring */
140 error = gotweb_init_querystring(&qs);
141 if (error) {
142 log_warnx("%s: %s", __func__, error->msg);
143 goto err;
145 c->t->qs = qs;
146 error = gotweb_parse_querystring(&qs, c->querystring);
147 if (error) {
148 gotweb_free_querystring(qs);
149 log_warnx("%s: %s", __func__, error->msg);
150 goto err;
153 /*
154 * certain actions require a commit id in the querystring. this stops
155 * bad actors from exploiting this by manually manipulating the
156 * querystring.
157 */
159 if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
160 qs->action == DIFF)) {
161 error2 = got_error(GOT_ERR_QUERYSTRING);
162 goto render;
165 if (qs->action != INDEX) {
166 error = gotweb_init_repo_dir(&repo_dir, qs->path);
167 if (error)
168 goto done;
169 error = gotweb_load_got_path(c, repo_dir);
170 c->t->repo_dir = repo_dir;
171 if (error && error->code != GOT_ERR_LONELY_PACKIDX)
172 goto err;
175 /* render top of page */
176 if (qs != NULL && qs->action == BLOB) {
177 error = got_get_repo_commits(c, 1);
178 if (error)
179 goto done;
180 error = gotweb_render_content_type(c, "text/plain");
181 if (error) {
182 log_warnx("%s: %s", __func__, error->msg);
183 goto err;
185 error = got_output_file_blob(c);
186 if (error) {
187 log_warnx("%s: %s", __func__, error->msg);
188 goto err;
190 goto done;
191 } else {
192 render:
193 error = gotweb_render_content_type(c, "text/html");
194 if (error) {
195 log_warnx("%s: %s", __func__, error->msg);
196 goto err;
198 html = 1;
201 error = gotweb_render_header(c);
202 if (error) {
203 log_warnx("%s: %s", __func__, error->msg);
204 goto err;
207 if (error2) {
208 error = error2;
209 goto err;
212 switch(qs->action) {
213 case BLAME:
214 error = gotweb_render_blame(c);
215 if (error) {
216 log_warnx("%s: %s", __func__, error->msg);
217 goto err;
219 break;
220 case BRIEFS:
221 error = gotweb_render_briefs(c);
222 if (error) {
223 log_warnx("%s: %s", __func__, error->msg);
224 goto err;
226 break;
227 case COMMITS:
228 error = gotweb_render_commits(c);
229 if (error) {
230 log_warnx("%s: %s", __func__, error->msg);
231 goto err;
233 break;
234 case DIFF:
235 error = gotweb_render_diff(c);
236 if (error) {
237 log_warnx("%s: %s", __func__, error->msg);
238 goto err;
240 break;
241 case INDEX:
242 error = gotweb_render_index(c);
243 if (error) {
244 log_warnx("%s: %s", __func__, error->msg);
245 goto err;
247 break;
248 case SUMMARY:
249 error = gotweb_render_summary(c);
250 if (error) {
251 log_warnx("%s: %s", __func__, error->msg);
252 goto err;
254 break;
255 case TAG:
256 error = gotweb_render_tag(c);
257 if (error) {
258 log_warnx("%s: %s", __func__, error->msg);
259 goto err;
261 break;
262 case TAGS:
263 error = gotweb_render_tags(c);
264 if (error) {
265 log_warnx("%s: %s", __func__, error->msg);
266 goto err;
268 break;
269 case TREE:
270 error = gotweb_render_tree(c);
271 if (error) {
272 log_warnx("%s: %s", __func__, error->msg);
273 goto err;
275 break;
276 case ERR:
277 default:
278 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
279 goto err;
280 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
281 goto err;
282 if (fcgi_gen_response(c, "</div>\n") == -1)
283 goto err;
284 break;
287 goto done;
288 err:
289 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
290 return;
291 if (fcgi_gen_response(c, err) == -1)
292 return;
293 if (error) {
294 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
295 return;
296 } else {
297 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
298 return;
300 if (html && fcgi_gen_response(c, "</div>\n") == -1)
301 return;
302 done:
303 if (c->t->repo != NULL && qs->action != INDEX)
304 got_repo_close(c->t->repo);
305 if (html && srv != NULL)
306 gotweb_render_footer(c);
309 struct server *
310 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
311 uint8_t *subdomain)
313 struct server *srv = NULL;
315 /* check against document_root first */
316 if (strlen(server_name) > 0)
317 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
318 if (strcmp(srv->name, server_name) == 0)
319 goto done;
321 /* check against document_root second */
322 if (strlen(document_root) > 0)
323 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
324 if (strcmp(srv->name, document_root) == 0)
325 goto done;
327 /* check against subdomain third */
328 if (strlen(subdomain) > 0)
329 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
330 if (strcmp(srv->name, subdomain) == 0)
331 goto done;
333 /* if those fail, send first server */
334 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
335 if (srv != NULL)
336 break;
337 done:
338 return srv;
339 };
341 const struct got_error *
342 gotweb_init_transport(struct transport **t)
344 const struct got_error *error = NULL;
346 *t = calloc(1, sizeof(**t));
347 if (*t == NULL)
348 return got_error_from_errno2("%s: calloc", __func__);
350 TAILQ_INIT(&(*t)->repo_commits);
351 TAILQ_INIT(&(*t)->repo_tags);
353 (*t)->repo = NULL;
354 (*t)->repo_dir = NULL;
355 (*t)->qs = NULL;
356 (*t)->next_id = NULL;
357 (*t)->prev_id = NULL;
358 (*t)->next_disp = 0;
359 (*t)->prev_disp = 0;
361 return error;
364 static const struct got_error *
365 gotweb_init_querystring(struct querystring **qs)
367 const struct got_error *error = NULL;
369 *qs = calloc(1, sizeof(**qs));
370 if (*qs == NULL)
371 return got_error_from_errno2("%s: calloc", __func__);
373 (*qs)->action = INDEX;
374 (*qs)->commit = NULL;
375 (*qs)->file = NULL;
376 (*qs)->folder = NULL;
377 (*qs)->headref = strdup("HEAD");
378 if ((*qs)->headref == NULL) {
379 return got_error_from_errno2("%s: strdup", __func__);
381 (*qs)->index_page = 0;
382 (*qs)->index_page_str = NULL;
383 (*qs)->path = NULL;
385 return error;
388 static const struct got_error *
389 gotweb_parse_querystring(struct querystring **qs, char *qst)
391 const struct got_error *error = NULL;
392 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
393 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
395 if (qst == NULL)
396 return error;
398 tok1 = strdup(qst);
399 if (tok1 == NULL)
400 return got_error_from_errno2("%s: strdup", __func__);
402 tok1_pair = tok1;
403 tok1_end = tok1;
405 while (tok1_pair != NULL) {
406 strsep(&tok1_end, "&");
408 tok2 = strdup(tok1_pair);
409 if (tok2 == NULL) {
410 free(tok1);
411 return got_error_from_errno2("%s: strdup", __func__);
414 tok2_pair = tok2;
415 tok2_end = tok2;
417 while (tok2_pair != NULL) {
418 strsep(&tok2_end, "=");
419 if (tok2_end) {
420 error = gotweb_assign_querystring(qs, tok2_pair,
421 tok2_end);
422 if (error)
423 goto err;
425 tok2_pair = tok2_end;
427 free(tok2);
428 tok1_pair = tok1_end;
430 free(tok1);
431 return error;
432 err:
433 free(tok2);
434 free(tok1);
435 return error;
438 static const struct got_error *
439 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
441 const struct got_error *error = NULL;
442 const char *errstr;
443 int a_cnt, el_cnt;
445 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
446 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
447 continue;
449 switch (querystring_keys[el_cnt].element) {
450 case ACTION:
451 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
452 if (strcmp(value, action_keys[a_cnt].name) != 0)
453 continue;
454 else if (strcmp(value,
455 action_keys[a_cnt].name) == 0){
456 (*qs)->action =
457 action_keys[a_cnt].action;
458 goto qa_found;
461 (*qs)->action = ERR;
462 qa_found:
463 break;
464 case COMMIT:
465 (*qs)->commit = strdup(value);
466 if ((*qs)->commit == NULL) {
467 error = got_error_from_errno2("%s: strdup",
468 __func__);
469 goto done;
471 break;
472 case RFILE:
473 (*qs)->file = strdup(value);
474 if ((*qs)->file == NULL) {
475 error = got_error_from_errno2("%s: strdup",
476 __func__);
477 goto done;
479 break;
480 case FOLDER:
481 (*qs)->folder = strdup(value);
482 if ((*qs)->folder == NULL) {
483 error = got_error_from_errno2("%s: strdup",
484 __func__);
485 goto done;
487 break;
488 case HEADREF:
489 (*qs)->headref = strdup(value);
490 if ((*qs)->headref == NULL) {
491 error = got_error_from_errno2("%s: strdup",
492 __func__);
493 goto done;
495 break;
496 case INDEX_PAGE:
497 if (strlen(value) == 0)
498 break;
499 (*qs)->index_page_str = strdup(value);
500 if ((*qs)->index_page_str == NULL) {
501 error = got_error_from_errno2("%s: strdup",
502 __func__);
503 goto done;
505 (*qs)->index_page = strtonum(value, INT64_MIN,
506 INT64_MAX, &errstr);
507 if (errstr) {
508 error = got_error_from_errno3("%s: strtonum %s",
509 __func__, errstr);
510 goto done;
512 if ((*qs)->index_page < 0) {
513 (*qs)->index_page = 0;
514 sprintf((*qs)->index_page_str, "%d", 0);
516 break;
517 case PATH:
518 (*qs)->path = strdup(value);
519 if ((*qs)->path == NULL) {
520 error = got_error_from_errno2("%s: strdup",
521 __func__);
522 goto done;
524 break;
525 case PAGE:
526 if (strlen(value) == 0)
527 break;
528 (*qs)->page_str = strdup(value);
529 if ((*qs)->page_str == NULL) {
530 error = got_error_from_errno2("%s: strdup",
531 __func__);
532 goto done;
534 (*qs)->page = strtonum(value, INT64_MIN,
535 INT64_MAX, &errstr);
536 if (errstr) {
537 error = got_error_from_errno3("%s: strtonum %s",
538 __func__, errstr);
539 goto done;
541 if ((*qs)->page < 0) {
542 (*qs)->page = 0;
543 sprintf((*qs)->page_str, "%d", 0);
545 break;
546 default:
547 break;
550 done:
551 return error;
554 void
555 gotweb_free_repo_tag(struct repo_tag *rt)
557 if (rt != NULL) {
558 free(rt->commit_msg);
559 free(rt->commit_id);
560 free(rt->tagger);
562 free(rt);
565 void
566 gotweb_free_repo_commit(struct repo_commit *rc)
568 if (rc != NULL) {
569 free(rc->path);
570 free(rc->refs_str);
571 free(rc->commit_id);
572 free(rc->parent_id);
573 free(rc->tree_id);
574 free(rc->author);
575 free(rc->committer);
576 free(rc->commit_msg);
578 free(rc);
581 static void
582 gotweb_free_querystring(struct querystring *qs)
584 if (qs != NULL) {
585 free(qs->commit);
586 free(qs->file);
587 free(qs->folder);
588 free(qs->headref);
589 free(qs->index_page_str);
590 free(qs->path);
591 free(qs->page_str);
593 free(qs);
596 static void
597 gotweb_free_repo_dir(struct repo_dir *repo_dir)
599 if (repo_dir != NULL) {
600 free(repo_dir->name);
601 free(repo_dir->owner);
602 free(repo_dir->description);
603 free(repo_dir->url);
604 free(repo_dir->age);
605 free(repo_dir->path);
607 free(repo_dir);
610 void
611 gotweb_free_transport(struct transport *t)
613 struct repo_commit *rc = NULL, *trc = NULL;
614 struct repo_tag *rt = NULL, *trt = NULL;
616 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
617 TAILQ_REMOVE(&t->repo_commits, rc, entry);
618 gotweb_free_repo_commit(rc);
620 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
621 TAILQ_REMOVE(&t->repo_tags, rt, entry);
622 gotweb_free_repo_tag(rt);
624 gotweb_free_repo_dir(t->repo_dir);
625 gotweb_free_querystring(t->qs);
626 if (t != NULL) {
627 free(t->next_id);
628 free(t->prev_id);
630 free(t);
633 const struct got_error *
634 gotweb_render_content_type(struct request *c, const uint8_t *type)
636 const struct got_error *error = NULL;
637 char *h = NULL;
639 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
640 error = got_error_from_errno2("%s: asprintf", __func__);
641 goto done;
644 fcgi_gen_response(c, h);
645 done:
646 free(h);
648 return error;
651 const struct got_error *
652 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
653 char *file)
655 const struct got_error *error = NULL;
656 char *h = NULL;
658 if (asprintf(&h, "Content-type: %s\r\n"
659 "Content-disposition: attachment; filename=%s\r\n\r\n",
660 type, file) == -1) {
661 error = got_error_from_errno2("%s: asprintf", __func__);
662 goto done;
665 fcgi_gen_response(c, h);
666 done:
667 free(h);
669 return error;
672 static const struct got_error *
673 gotweb_render_header(struct request *c)
675 const struct got_error *error = NULL;
676 struct server *srv = c->srv;
677 struct querystring *qs = c->t->qs;
678 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
679 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
681 if (strlen(c->document_root) > 0) {
682 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
683 error = got_error_from_errno2("%s: asprintf", __func__);
684 goto done;
686 } else {
687 if (asprintf(&droot, "/") == -1) {
688 error = got_error_from_errno2("%s: asprintf", __func__);
689 goto done;
693 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
694 error = got_error_from_errno2("%s: asprintf", __func__);
695 goto done;
697 if (asprintf(&css,
698 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
699 droot, srv->custom_css) == -1) {
700 error = got_error_from_errno2("%s: asprintf", __func__);
701 goto done;
703 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
704 srv->logo_url) == -1) {
705 error = got_error_from_errno2("%s: asprintf", __func__);
706 goto done;
708 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
709 droot, srv->logo) == -1) {
710 error = got_error_from_errno2("%s: asprintf", __func__);
711 goto done;
713 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
714 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
715 srv->site_link) == -1) {
716 error = got_error_from_errno2("%s: asprintf", __func__);
717 goto done;
719 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
720 "&action=summary' alt='summlink'>%s</a>", c->document_root,
721 qs->index_page, qs->path, qs->path) == -1) {
722 error = got_error_from_errno2("%s: asprintf", __func__);
723 goto done;
726 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
727 goto done;
728 if (fcgi_gen_response(c, title) == -1)
729 goto done;
730 if (fcgi_gen_response(c, "<meta name='viewport' "
731 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
732 goto done;
733 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
734 goto done;
735 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
736 "content='#da532c'/>\n") == -1)
737 goto done;
738 if (fcgi_gen_response(c,
739 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
740 goto done;
741 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
742 "href='/apple-touch-icon.png'/>\n") == -1)
743 goto done;
744 if (fcgi_gen_response(c,
745 "<link rel='icon' type='image/png' sizes='32x32' "
746 "href='/favicon-32x32.png'/>\n") == -1)
747 goto done;
748 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
749 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
750 goto done;
751 if (fcgi_gen_response(c, "<link rel='manifest' "
752 "href='/site.webmanifest'/>\n") == -1)
753 goto done;
754 if (fcgi_gen_response(c, "<link rel='mask-icon' "
755 "href='/safari-pinned-tab.svg'/>\n") == -1)
756 goto done;
757 if (fcgi_gen_response(c, css) == -1)
758 goto done;
759 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
760 goto done;
761 if (fcgi_gen_response(c,
762 "<div id='header'>\n<div id='got_link'>") == -1)
763 goto done;
764 if (fcgi_gen_response(c, gotlink) == -1)
765 goto done;
766 if (fcgi_gen_response(c, gotimg) == -1)
767 goto done;
768 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
769 goto done;
770 if (fcgi_gen_response(c,
771 "<div id='site_path'>\n<div id='site_link'>") == -1)
772 goto done;
773 if (fcgi_gen_response(c, sitelink) == -1)
774 goto done;
775 if (qs != NULL) {
776 if (qs->path != NULL) {
777 if (fcgi_gen_response(c, " / ") == -1)
778 goto done;
779 if (fcgi_gen_response(c, summlink) == -1)
780 goto done;
782 if (qs->action != INDEX) {
783 if (fcgi_gen_response(c, " / ") == -1)
784 goto done;
785 switch(qs->action) {
786 case(BLAME):
787 if (fcgi_gen_response(c, "blame") == -1)
788 goto done;
789 break;
790 case(BRIEFS):
791 if (fcgi_gen_response(c, "briefs") == -1)
792 goto done;
793 break;
794 case(COMMITS):
795 if (fcgi_gen_response(c, "commits") == -1)
796 goto done;
797 break;
798 case(DIFF):
799 if (fcgi_gen_response(c, "diff") == -1)
800 goto done;
801 break;
802 case(SUMMARY):
803 if (fcgi_gen_response(c, "summary") == -1)
804 goto done;
805 break;
806 case(TAG):
807 if (fcgi_gen_response(c, "tag") == -1)
808 goto done;
809 break;
810 case(TAGS):
811 if (fcgi_gen_response(c, "tags") == -1)
812 goto done;
813 break;
814 case(TREE):
815 if (fcgi_gen_response(c, "tree") == -1)
816 goto done;
817 break;
818 default:
819 break;
824 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
825 done:
826 free(title);
827 free(droot);
828 free(css);
829 free(gotlink);
830 free(gotimg);
831 free(sitelink);
832 free(summlink);
834 return error;
837 static const struct got_error *
838 gotweb_render_footer(struct request *c)
840 const struct got_error *error = NULL;
841 struct server *srv = c->srv;
842 char *siteowner = NULL;
844 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
845 goto done;
846 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
847 goto done;
848 if (srv->show_site_owner) {
849 error = gotweb_escape_html(&siteowner, srv->site_owner);
850 if (error)
851 goto done;
852 if (fcgi_gen_response(c, siteowner) == -1)
853 goto done;
854 } else
855 if (fcgi_gen_response(c, "&nbsp;") == -1)
856 goto done;
857 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
858 done:
859 free(siteowner);
861 return error;
864 static const struct got_error *
865 gotweb_render_navs(struct request *c)
867 const struct got_error *error = NULL;
868 struct transport *t = c->t;
869 struct querystring *qs = t->qs;
870 struct server *srv = c->srv;
871 char *nhref = NULL, *phref = NULL;
872 int disp = 0;
874 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
875 goto done;
876 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
877 goto done;
879 switch(qs->action) {
880 case INDEX:
881 if (qs->index_page > 0) {
882 if (asprintf(&phref, "index_page=%d",
883 qs->index_page - 1) == -1) {
884 error = got_error_from_errno2("%s: asprintf",
885 __func__);
886 goto done;
888 disp = 1;
890 break;
891 case BRIEFS:
892 if (t->prev_id && qs->commit != NULL &&
893 strcmp(qs->commit, t->prev_id) != 0) {
894 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
895 "&action=briefs&commit=%s&headref=%s",
896 qs->index_page, qs->path, qs->page - 1, t->prev_id,
897 qs->headref) == -1) {
898 error = got_error_from_errno2("%s: asprintf",
899 __func__);
900 goto done;
902 disp = 1;
904 break;
905 case COMMITS:
906 if (t->prev_id && qs->commit != NULL &&
907 strcmp(qs->commit, t->prev_id) != 0) {
908 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
909 "&action=commits&commit=%s&headref=%s&folder=%s"
910 "&file=%s",
911 qs->index_page, qs->path, qs->page - 1, t->prev_id,
912 qs->headref, qs->folder ? qs->folder : "",
913 qs->file ? qs->file : "") == -1) {
914 error = got_error_from_errno2("%s: asprintf",
915 __func__);
916 goto done;
918 disp = 1;
920 break;
921 case TAGS:
922 if (t->prev_id && qs->commit != NULL &&
923 strcmp(qs->commit, t->prev_id) != 0) {
924 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
925 "&action=tags&commit=%s&headref=%s",
926 qs->index_page, qs->path, qs->page - 1, t->prev_id,
927 qs->headref) == -1) {
928 error = got_error_from_errno2("%s: asprintf",
929 __func__);
930 goto done;
932 disp = 1;
934 break;
935 default:
936 disp = 0;
937 break;
940 if (disp) {
941 if (fcgi_gen_response(c, "<a href='?") == -1)
942 goto done;
943 if (fcgi_gen_response(c, phref) == -1)
944 goto done;
945 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
946 goto done;
948 if (fcgi_gen_response(c, "</div>\n") == -1)
949 goto done;
950 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
951 goto done;
953 disp = 0;
955 switch(qs->action) {
956 case INDEX:
957 if (t->next_disp == srv->max_repos_display &&
958 t->repos_total != (qs->index_page + 1) *
959 srv->max_repos_display) {
960 if (asprintf(&nhref, "index_page=%d",
961 qs->index_page + 1) == -1) {
962 error = got_error_from_errno2("%s: asprintf",
963 __func__);
964 goto done;
966 disp = 1;
968 break;
969 case BRIEFS:
970 if (t->next_id) {
971 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
972 "&action=briefs&commit=%s&headref=%s",
973 qs->index_page, qs->path, qs->page + 1, t->next_id,
974 qs->headref) == -1) {
975 error = got_error_from_errno2("%s: asprintf",
976 __func__);
977 goto done;
979 disp = 1;
981 break;
982 case COMMITS:
983 if (t->next_id) {
984 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
985 "&action=commits&commit=%s&headref=%s&folder=%s"
986 "&file=%s",
987 qs->index_page, qs->path, qs->page + 1, t->next_id,
988 qs->headref, qs->folder ? qs->folder : "",
989 qs->file ? qs->file : "") == -1) {
990 error = got_error_from_errno2("%s: asprintf",
991 __func__);
992 goto done;
994 disp = 1;
996 break;
997 case TAGS:
998 if (t->next_id) {
999 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
1000 "&action=tags&commit=%s&headref=%s",
1001 qs->index_page, qs->path, qs->page + 1, t->next_id,
1002 qs->headref) == -1) {
1003 error = got_error_from_errno2("%s: asprintf",
1004 __func__);
1005 goto done;
1007 disp = 1;
1009 break;
1010 default:
1011 disp = 0;
1012 break;
1014 if (disp) {
1015 if (fcgi_gen_response(c, "<a href='?") == -1)
1016 goto done;
1017 if (fcgi_gen_response(c, nhref) == -1)
1018 goto done;
1019 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1020 goto done;
1022 fcgi_gen_response(c, "</div>\n");
1023 done:
1024 free(t->next_id);
1025 t->next_id = NULL;
1026 free(t->prev_id);
1027 t->prev_id = NULL;
1028 free(phref);
1029 free(nhref);
1030 return error;
1033 static const struct got_error *
1034 gotweb_render_index(struct request *c)
1036 const struct got_error *error = NULL;
1037 struct server *srv = c->srv;
1038 struct transport *t = c->t;
1039 struct querystring *qs = t->qs;
1040 struct repo_dir *repo_dir = NULL;
1041 DIR *d;
1042 struct dirent **sd_dent;
1043 char *c_path = NULL;
1044 struct stat st;
1045 unsigned int d_cnt, d_i, d_disp = 0;
1047 d = opendir(srv->repos_path);
1048 if (d == NULL) {
1049 error = got_error_from_errno2("opendir", srv->repos_path);
1050 return error;
1053 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1054 if (d_cnt == -1) {
1055 error = got_error_from_errno2("scandir", srv->repos_path);
1056 goto done;
1059 /* get total count of repos */
1060 for (d_i = 0; d_i < d_cnt; d_i++) {
1061 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1062 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1063 continue;
1065 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1066 sd_dent[d_i]->d_name) == -1) {
1067 error = got_error_from_errno("asprintf");
1068 return error;
1071 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1072 !got_path_dir_is_empty(c_path))
1073 t->repos_total++;
1074 free(c_path);
1075 c_path = NULL;
1078 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1079 goto done;
1080 if (fcgi_gen_response(c,
1081 "<div id='index_header_project'>Project</div>\n") == -1)
1082 goto done;
1083 if (srv->show_repo_description)
1084 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1085 "Description</div>\n") == -1)
1086 goto done;
1087 if (srv->show_repo_owner)
1088 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1089 "Owner</div>\n") == -1)
1090 goto done;
1091 if (srv->show_repo_age)
1092 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1093 "Last Change</div>\n") == -1)
1094 goto done;
1095 if (fcgi_gen_response(c, "</div>\n") == -1)
1096 goto done;
1098 for (d_i = 0; d_i < d_cnt; d_i++) {
1099 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1100 break; /* account for parent and self */
1102 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1103 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1104 continue;
1106 if (qs->index_page > 0 && (qs->index_page *
1107 srv->max_repos_display) > t->prev_disp) {
1108 t->prev_disp++;
1109 continue;
1112 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1113 if (error)
1114 goto done;
1116 error = gotweb_load_got_path(c, repo_dir);
1117 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1118 error = NULL;
1119 continue;
1121 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1122 goto done;
1124 if (lstat(repo_dir->path, &st) == 0 &&
1125 S_ISDIR(st.st_mode) &&
1126 !got_path_dir_is_empty(repo_dir->path))
1127 goto render;
1128 else {
1129 gotweb_free_repo_dir(repo_dir);
1130 repo_dir = NULL;
1131 continue;
1133 render:
1134 d_disp++;
1135 t->prev_disp++;
1136 if (fcgi_gen_response(c, "<div id='index_wrapper'>\n") == -1)
1137 goto done;
1138 if (fcgi_gen_response(c, "<div id='index_project'>") == -1)
1139 goto done;
1141 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1142 goto done;
1143 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1144 goto done;
1145 if (fcgi_gen_response(c, "&path=") == -1)
1146 goto done;
1147 if (fcgi_gen_response(c, repo_dir->name) == -1)
1148 goto done;
1149 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1150 goto done;
1151 if (fcgi_gen_response(c, repo_dir->name) == -1)
1152 goto done;
1153 if (fcgi_gen_response(c, "</a>") == -1)
1154 goto done;
1156 if (fcgi_gen_response(c, "</div>\n") == -1)
1157 goto done;
1159 if (srv->show_repo_description) {
1160 if (fcgi_gen_response(c,
1161 "<div id='index_project_description'>\n") == -1)
1162 goto done;
1163 if (fcgi_gen_response(c, repo_dir->description) == -1)
1164 goto done;
1165 if (fcgi_gen_response(c, "</div>\n") == -1)
1166 goto done;
1169 if (srv->show_repo_owner) {
1170 if (fcgi_gen_response(c,
1171 "<div id='index_project_owner'>") == -1)
1172 goto done;
1173 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1174 goto done;
1175 if (fcgi_gen_response(c, "</div>\n") == -1)
1176 goto done;
1179 if (srv->show_repo_age) {
1180 if (fcgi_gen_response(c,
1181 "<div id='index_project_age'>") == -1)
1182 goto done;
1183 if (fcgi_gen_response(c, repo_dir->age) == -1)
1184 goto done;
1185 if (fcgi_gen_response(c, "</div>\n") == -1)
1186 goto done;
1189 if (fcgi_gen_response(c, "<div id='navs_wrapper'>") == -1)
1190 goto done;
1191 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1192 goto done;;
1194 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1195 goto done;
1196 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1197 goto done;
1198 if (fcgi_gen_response(c, "&path=") == -1)
1199 goto done;
1200 if (fcgi_gen_response(c, repo_dir->name) == -1)
1201 goto done;
1202 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1203 goto done;
1204 if (fcgi_gen_response(c, "summary") == -1)
1205 goto done;
1206 if (fcgi_gen_response(c, "</a> | ") == -1)
1207 goto done;
1209 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1210 goto done;
1211 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1212 goto done;
1213 if (fcgi_gen_response(c, "&path=") == -1)
1214 goto done;
1215 if (fcgi_gen_response(c, repo_dir->name) == -1)
1216 goto done;
1217 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1218 goto done;
1219 if (fcgi_gen_response(c, "commit briefs") == -1)
1220 goto done;
1221 if (fcgi_gen_response(c, "</a> | ") == -1)
1222 goto done;
1224 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1225 goto done;
1226 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1227 goto done;
1228 if (fcgi_gen_response(c, "&path=") == -1)
1229 goto done;
1230 if (fcgi_gen_response(c, repo_dir->name) == -1)
1231 goto done;
1232 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1233 goto done;
1234 if (fcgi_gen_response(c, "commits") == -1)
1235 goto done;
1236 if (fcgi_gen_response(c, "</a> | ") == -1)
1237 goto done;
1239 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1240 goto done;
1241 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1242 goto done;
1243 if (fcgi_gen_response(c, "&path=") == -1)
1244 goto done;
1245 if (fcgi_gen_response(c, repo_dir->name) == -1)
1246 goto done;
1247 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1248 goto done;
1249 if (fcgi_gen_response(c, "tags") == -1)
1250 goto done;
1251 if (fcgi_gen_response(c, "</a> | ") == -1)
1252 goto done;
1254 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1255 goto done;
1256 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1257 goto done;
1258 if (fcgi_gen_response(c, "&path=") == -1)
1259 goto done;
1260 if (fcgi_gen_response(c, repo_dir->name) == -1)
1261 goto done;
1262 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1263 goto done;
1264 if (fcgi_gen_response(c, "tree") == -1)
1265 goto done;
1266 if (fcgi_gen_response(c, "</a>") == -1)
1267 goto done;
1269 if (fcgi_gen_response(c, "</div>") == -1)
1270 goto done;
1271 if (fcgi_gen_response(c,
1272 "<div id='dotted_line'></div>\n") == -1)
1273 goto done;
1274 if (fcgi_gen_response(c, "</div>\n") == -1)
1275 goto done;
1276 if (fcgi_gen_response(c, "</div>\n") == -1)
1277 goto done;
1279 gotweb_free_repo_dir(repo_dir);
1280 repo_dir = NULL;
1281 error = got_repo_close(t->repo);
1282 if (error)
1283 goto done;
1284 t->next_disp++;
1285 if (d_disp == srv->max_repos_display)
1286 break;
1288 if (srv->max_repos_display == 0)
1289 goto div;
1290 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1291 goto div;
1292 if (t->repos_total <= srv->max_repos ||
1293 t->repos_total <= srv->max_repos_display)
1294 goto div;
1296 error = gotweb_render_navs(c);
1297 if (error)
1298 goto done;
1299 div:
1300 fcgi_gen_response(c, "</div>\n");
1301 done:
1302 if (d != NULL && closedir(d) == EOF && error == NULL)
1303 error = got_error_from_errno("closedir");
1304 return error;
1307 static const struct got_error *
1308 gotweb_render_blame(struct request *c)
1310 const struct got_error *error = NULL;
1311 struct transport *t = c->t;
1312 struct repo_commit *rc = NULL;
1313 char *age = NULL;
1315 error = got_get_repo_commits(c, 1);
1316 if (error)
1317 return error;
1319 rc = TAILQ_FIRST(&t->repo_commits);
1321 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1322 if (error)
1323 goto done;
1325 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1326 goto done;
1327 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1328 goto done;
1329 if (fcgi_gen_response(c, "</div>\n") == -1)
1330 goto done;
1332 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1333 goto done;
1335 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1336 goto done;
1337 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1338 goto done;
1340 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1341 "</div>\n") == -1)
1342 goto done;
1343 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1344 goto done;
1345 if (fcgi_gen_response(c, age ? age : "") == -1)
1346 goto done;
1347 if (fcgi_gen_response(c, "</div>\n") == -1)
1348 goto done;
1350 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1351 "</div>\n") == -1)
1352 goto done;
1353 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1354 goto done;
1355 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1356 goto done;
1357 if (fcgi_gen_response(c, "</div>\n") == -1)
1358 goto done;
1360 if (fcgi_gen_response(c, "</div>\n") == -1)
1361 goto done;
1362 if (fcgi_gen_response(c, "</div>\n") == -1)
1363 goto done;
1365 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1366 goto done;
1367 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1368 goto done;
1370 error = got_output_file_blame(c);
1371 if (error)
1372 goto done;
1374 fcgi_gen_response(c, "</div>\n");
1375 done:
1376 fcgi_gen_response(c, "</div>\n");
1377 return error;
1380 static const struct got_error *
1381 gotweb_render_briefs(struct request *c)
1383 const struct got_error *error = NULL;
1384 struct repo_commit *rc = NULL;
1385 struct server *srv = c->srv;
1386 struct transport *t = c->t;
1387 struct querystring *qs = t->qs;
1388 struct repo_dir *repo_dir = t->repo_dir;
1389 char *smallerthan, *newline;
1390 char *age = NULL;
1392 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1393 goto done;
1394 if (fcgi_gen_response(c,
1395 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1396 goto done;
1397 if (fcgi_gen_response(c, "</div>\n") == -1)
1398 goto done;
1400 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1401 goto done;
1403 if (qs->action == SUMMARY) {
1404 qs->action = BRIEFS;
1405 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1406 } else
1407 error = got_get_repo_commits(c, srv->max_commits_display);
1408 if (error)
1409 goto done;
1411 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1412 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1413 if (error)
1414 goto done;
1415 if (fcgi_gen_response(c, "<div id='briefs_age'>") == -1)
1416 goto done;
1417 if (fcgi_gen_response(c, age ? age : "") == -1)
1418 goto done;
1419 if (fcgi_gen_response(c, "</div>\n") == -1)
1420 goto done;
1422 if (fcgi_gen_response(c, "<div id='briefs_author'>") == -1)
1423 goto done;
1424 smallerthan = strchr(rc->author, '<');
1425 if (smallerthan)
1426 *smallerthan = '\0';
1427 if (fcgi_gen_response(c, rc->author) == -1)
1428 goto done;
1429 if (fcgi_gen_response(c, "</div>\n") == -1)
1430 goto done;
1432 if (fcgi_gen_response(c, "<div id='briefs_log'>") == -1)
1433 goto done;
1434 newline = strchr(rc->commit_msg, '\n');
1435 if (newline)
1436 *newline = '\0';
1438 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1439 goto done;
1440 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1441 goto done;
1442 if (fcgi_gen_response(c, "&path=") == -1)
1443 goto done;
1444 if (fcgi_gen_response(c, repo_dir->name) == -1)
1445 goto done;
1446 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1447 goto done;
1448 if (fcgi_gen_response(c, rc->commit_id) == -1)
1449 goto done;
1450 if (fcgi_gen_response(c, "&headref=") == -1)
1451 goto done;
1452 if (fcgi_gen_response(c, qs->headref) == -1)
1453 goto done;
1454 if (fcgi_gen_response(c, "'>") == -1)
1455 goto done;
1456 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1457 goto done;
1458 if (fcgi_gen_response(c, "</a>") == -1)
1459 goto done;
1460 if (rc->refs_str) {
1461 if (fcgi_gen_response(c,
1462 " <span id='refs_str'>(") == -1)
1463 goto done;
1464 if (fcgi_gen_response(c, rc->refs_str) == -1)
1465 goto done;
1466 if (fcgi_gen_response(c, ")</span>") == -1)
1467 goto done;
1469 if (fcgi_gen_response(c, "</div>\n") == -1)
1470 goto done;
1472 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1473 goto done;
1474 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1475 goto done;
1476 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1477 goto done;
1478 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1479 goto done;
1480 if (fcgi_gen_response(c, "&path=") == -1)
1481 goto done;
1482 if (fcgi_gen_response(c, repo_dir->name) == -1)
1483 goto done;
1484 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1485 goto done;
1486 if (fcgi_gen_response(c, rc->commit_id) == -1)
1487 goto done;
1488 if (fcgi_gen_response(c, "&headref=") == -1)
1489 goto done;
1490 if (fcgi_gen_response(c, qs->headref) == -1)
1491 goto done;
1492 if (fcgi_gen_response(c, "'>") == -1)
1493 goto done;
1494 if (fcgi_gen_response(c, "diff") == -1)
1495 goto done;
1496 if (fcgi_gen_response(c, "</a>") == -1)
1497 goto done;
1499 if (fcgi_gen_response(c, " | ") == -1)
1500 goto done;
1502 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1503 goto done;
1504 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1505 goto done;
1506 if (fcgi_gen_response(c, "&path=") == -1)
1507 goto done;
1508 if (fcgi_gen_response(c, repo_dir->name) == -1)
1509 goto done;
1510 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1511 goto done;
1512 if (fcgi_gen_response(c, rc->commit_id) == -1)
1513 goto done;
1514 if (fcgi_gen_response(c, "&headref=") == -1)
1515 goto done;
1516 if (fcgi_gen_response(c, qs->headref) == -1)
1517 goto done;
1518 if (fcgi_gen_response(c, "'>") == -1)
1519 goto done;
1520 if (fcgi_gen_response(c, "tree") == -1)
1521 goto done;
1522 if (fcgi_gen_response(c, "</a>") == -1)
1523 goto done;
1524 if (fcgi_gen_response(c, "</div>\n") == -1)
1525 goto done;
1526 if (fcgi_gen_response(c, "</div>\n") == -1)
1527 goto done;
1528 if (fcgi_gen_response(c,
1529 "<div id='dotted_line'></div>\n") == -1)
1530 goto done;
1532 free(age);
1533 age = NULL;
1536 if (t->next_id || t->prev_id) {
1537 error = gotweb_render_navs(c);
1538 if (error)
1539 goto done;
1541 fcgi_gen_response(c, "</div>\n");
1542 done:
1543 free(age);
1544 return error;
1547 static const struct got_error *
1548 gotweb_render_commits(struct request *c)
1550 const struct got_error *error = NULL;
1551 struct repo_commit *rc = NULL;
1552 struct server *srv = c->srv;
1553 struct transport *t = c->t;
1554 struct querystring *qs = t->qs;
1555 struct repo_dir *repo_dir = t->repo_dir;
1556 char *age = NULL, *author = NULL;
1557 /* int commit_found = 0; */
1559 if (fcgi_gen_response(c, "<div id='commits_title_wrapper'>\n") == -1)
1560 goto done;
1561 if (fcgi_gen_response(c,
1562 "<div id='commits_title'>Commits</div>\n") == -1)
1563 goto done;
1564 if (fcgi_gen_response(c, "</div>\n") == -1)
1565 goto done;
1567 if (fcgi_gen_response(c, "<div id='commits_content'>\n") == -1)
1568 goto done;
1570 error = got_get_repo_commits(c, srv->max_commits_display);
1571 if (error)
1572 goto done;
1574 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1575 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1576 if (error)
1577 goto done;
1578 error = gotweb_escape_html(&author, rc->author);
1579 if (error)
1580 goto done;
1582 if (fcgi_gen_response(c,
1583 "<div id='commits_header_wrapper'>\n") == -1)
1584 goto done;
1585 if (fcgi_gen_response(c, "<div id='commits_header'>\n") == -1)
1586 goto done;
1589 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
1590 "</div>\n") == -1)
1591 goto done;
1592 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
1593 goto done;
1594 if (fcgi_gen_response(c, rc->commit_id) == -1)
1595 goto done;
1596 if (fcgi_gen_response(c, "</div>\n") == -1)
1597 goto done;
1599 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
1600 "</div>\n") == -1)
1601 goto done;
1602 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
1603 goto done;
1604 if (fcgi_gen_response(c, author ? author : "") == -1)
1605 goto done;
1606 if (fcgi_gen_response(c, "</div>\n") == -1)
1607 goto done;
1609 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1610 "</div>\n") == -1)
1611 goto done;
1612 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1613 goto done;
1614 if (fcgi_gen_response(c, age ? age : "") == -1)
1615 goto done;
1616 if (fcgi_gen_response(c, "</div>\n") == -1)
1617 goto done;
1619 if (fcgi_gen_response(c, "</div>\n") == -1)
1620 goto done;
1621 if (fcgi_gen_response(c, "</div>\n") == -1)
1622 goto done;
1624 if (fcgi_gen_response(c,
1625 "<div id='dotted_line'></div>\n") == -1)
1626 goto done;
1627 if (fcgi_gen_response(c, "<div id='commit'>\n") == -1)
1628 goto done;
1630 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1631 goto done;
1633 if (fcgi_gen_response(c, "</div>\n") == -1)
1634 goto done;
1635 if (fcgi_gen_response(c, "</div>\n") == -1)
1636 goto done;
1638 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1639 goto done;
1640 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1641 goto done;
1642 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1643 goto done;
1644 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1645 goto done;
1646 if (fcgi_gen_response(c, "&path=") == -1)
1647 goto done;
1648 if (fcgi_gen_response(c, repo_dir->name) == -1)
1649 goto done;
1650 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1651 goto done;
1652 if (fcgi_gen_response(c, rc->commit_id) == -1)
1653 goto done;
1654 if (fcgi_gen_response(c, "'>") == -1)
1655 goto done;
1656 if (fcgi_gen_response(c, "diff") == -1)
1657 goto done;
1658 if (fcgi_gen_response(c, "</a>") == -1)
1659 goto done;
1661 if (fcgi_gen_response(c, " | ") == -1)
1662 goto done;
1664 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1665 goto done;
1666 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1667 goto done;
1668 if (fcgi_gen_response(c, "&path=") == -1)
1669 goto done;
1670 if (fcgi_gen_response(c, repo_dir->name) == -1)
1671 goto done;
1672 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1673 goto done;
1674 if (fcgi_gen_response(c, rc->commit_id) == -1)
1675 goto done;
1676 if (fcgi_gen_response(c, "'>") == -1)
1677 goto done;
1678 if (fcgi_gen_response(c, "tree") == -1)
1679 goto done;
1680 if (fcgi_gen_response(c, "</a>") == -1)
1681 goto done;
1682 if (fcgi_gen_response(c, "</div>\n") == -1)
1683 goto done;
1684 if (fcgi_gen_response(c, "</div>\n") == -1)
1685 goto done;
1686 if (fcgi_gen_response(c,
1687 "<div id='dotted_line'></div>\n") == -1)
1688 goto done;
1689 free(age);
1690 age = NULL;
1691 free(author);
1692 author = NULL;
1695 if (t->next_id || t->prev_id) {
1696 error = gotweb_render_navs(c);
1697 if (error)
1698 goto done;
1700 if (fcgi_gen_response(c, "</div>\n") == -1)
1701 goto done;
1702 fcgi_gen_response(c, "</div>\n");
1703 done:
1704 free(age);
1705 return error;
1708 static const struct got_error *
1709 gotweb_render_branches(struct request *c)
1711 const struct got_error *error = NULL;
1712 struct got_reflist_head refs;
1713 struct got_reflist_entry *re;
1714 struct transport *t = c->t;
1715 struct querystring *qs = t->qs;
1716 struct got_repository *repo = t->repo;
1717 char *age = NULL;
1719 TAILQ_INIT(&refs);
1721 error = got_ref_list(&refs, repo, "refs/heads",
1722 got_ref_cmp_by_name, NULL);
1723 if (error)
1724 goto done;
1726 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1727 goto done;
1728 if (fcgi_gen_response(c,
1729 "<div id='branches_title'>Branches</div>\n") == -1)
1730 goto done;
1731 if (fcgi_gen_response(c, "</div>\n") == -1)
1732 goto done;
1734 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1735 goto done;
1737 TAILQ_FOREACH(re, &refs, entry) {
1738 char *refname = NULL;
1740 if (got_ref_is_symbolic(re->ref))
1741 continue;
1743 refname = strdup(got_ref_get_name(re->ref));
1744 if (refname == NULL) {
1745 error = got_error_from_errno("strdup");
1746 goto done;
1748 if (strncmp(refname, "refs/heads/", 11) != 0)
1749 continue;
1751 error = got_get_repo_age(&age, c, qs->path, refname,
1752 TM_DIFF);
1753 if (error)
1754 goto done;
1756 if (strncmp(refname, "refs/heads/", 11) == 0)
1757 refname += 11;
1759 if (fcgi_gen_response(c, "<div id='branches_wrapper'>") == -1)
1760 goto done;
1762 if (fcgi_gen_response(c, "<div id='branches_age'>") == -1)
1763 goto done;
1764 if (fcgi_gen_response(c, age ? age : "") == -1)
1765 goto done;
1766 if (fcgi_gen_response(c, "</div>\n") == -1)
1767 goto done;
1769 if (fcgi_gen_response(c, "<div id='branches_space'>") == -1)
1770 goto done;
1771 if (fcgi_gen_response(c, "&nbsp;") == -1)
1772 goto done;
1773 if (fcgi_gen_response(c, "</div>\n") == -1)
1774 goto done;
1776 if (fcgi_gen_response(c, "<div id='branch'>") == -1)
1777 goto done;
1778 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1779 goto done;
1780 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1781 goto done;
1782 if (fcgi_gen_response(c, "&path=") == -1)
1783 goto done;
1784 if (fcgi_gen_response(c, qs->path) == -1)
1785 goto done;
1786 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1787 goto done;
1788 if (fcgi_gen_response(c, refname) == -1)
1789 goto done;
1790 if (fcgi_gen_response(c, "'>") == -1)
1791 goto done;
1792 if (fcgi_gen_response(c, refname) == -1)
1793 goto done;
1794 if (fcgi_gen_response(c, "</a>") == -1)
1795 goto done;
1796 if (fcgi_gen_response(c, "</div>\n") == -1)
1797 goto done;
1799 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1800 goto done;
1801 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1802 goto done;
1804 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1805 goto done;
1806 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1807 goto done;
1808 if (fcgi_gen_response(c, "&path=") == -1)
1809 goto done;
1810 if (fcgi_gen_response(c, qs->path) == -1)
1811 goto done;
1812 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1813 goto done;
1814 if (fcgi_gen_response(c, refname) == -1)
1815 goto done;
1816 if (fcgi_gen_response(c, "'>") == -1)
1817 goto done;
1818 if (fcgi_gen_response(c, "summary") == -1)
1819 goto done;
1820 if (fcgi_gen_response(c, "</a>") == -1)
1821 goto done;
1823 if (fcgi_gen_response(c, " | ") == -1)
1824 goto done;
1826 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1827 goto done;
1828 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1829 goto done;
1830 if (fcgi_gen_response(c, "&path=") == -1)
1831 goto done;
1832 if (fcgi_gen_response(c, qs->path) == -1)
1833 goto done;
1834 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1835 goto done;
1836 if (fcgi_gen_response(c, refname) == -1)
1837 goto done;
1838 if (fcgi_gen_response(c, "'>") == -1)
1839 goto done;
1840 if (fcgi_gen_response(c, "commit briefs") == -1)
1841 goto done;
1842 if (fcgi_gen_response(c, "</a>") == -1)
1843 goto done;
1845 if (fcgi_gen_response(c, " | ") == -1)
1846 goto done;
1848 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1849 goto done;
1850 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1851 goto done;
1852 if (fcgi_gen_response(c, "&path=") == -1)
1853 goto done;
1854 if (fcgi_gen_response(c, qs->path) == -1)
1855 goto done;
1856 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1857 goto done;
1858 if (fcgi_gen_response(c, refname) == -1)
1859 goto done;
1860 if (fcgi_gen_response(c, "'>") == -1)
1861 goto done;
1862 if (fcgi_gen_response(c, "commits") == -1)
1863 goto done;
1864 if (fcgi_gen_response(c, "</a>") == -1)
1865 goto done;
1867 if (fcgi_gen_response(c, "</div>\n") == -1)
1868 goto done;
1869 if (fcgi_gen_response(c, "</div>\n") == -1)
1870 goto done;
1872 if (fcgi_gen_response(c,
1873 "<div id='dotted_line'></div>\n") == -1)
1874 goto done;
1876 free(age);
1877 age = NULL;
1880 fcgi_gen_response(c, "</div>\n");
1881 done:
1882 return error;
1885 static const struct got_error *
1886 gotweb_render_tree(struct request *c)
1888 const struct got_error *error = NULL;
1889 struct transport *t = c->t;
1890 struct repo_commit *rc = NULL;
1891 char *age = NULL;
1893 error = got_get_repo_commits(c, 1);
1894 if (error)
1895 return error;
1897 rc = TAILQ_FIRST(&t->repo_commits);
1899 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1900 if (error)
1901 goto done;
1903 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1904 goto done;
1905 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1906 goto done;
1907 if (fcgi_gen_response(c, "</div>\n") == -1)
1908 goto done;
1910 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1911 goto done;
1913 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1914 goto done;
1915 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1916 goto done;
1918 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1919 "</div>\n") == -1)
1920 goto done;
1921 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1922 goto done;
1923 if (fcgi_gen_response(c, rc->tree_id) == -1)
1924 goto done;
1925 if (fcgi_gen_response(c, "</div>\n") == -1)
1926 goto done;
1928 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1929 "</div>\n") == -1)
1930 goto done;
1931 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1932 goto done;
1933 if (fcgi_gen_response(c, age ? age : "") == -1)
1934 goto done;
1935 if (fcgi_gen_response(c, "</div>\n") == -1)
1936 goto done;
1938 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1939 "</div>\n") == -1)
1940 goto done;
1941 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1942 goto done;
1943 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1944 goto done;
1945 if (fcgi_gen_response(c, "</div>\n") == -1)
1946 goto done;
1948 if (fcgi_gen_response(c, "</div>\n") == -1)
1949 goto done;
1950 if (fcgi_gen_response(c, "</div>\n") == -1)
1951 goto done;
1953 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1954 goto done;
1955 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1956 goto done;
1958 error = got_output_repo_tree(c);
1959 if (error)
1960 goto done;
1962 fcgi_gen_response(c, "</div>\n");
1963 fcgi_gen_response(c, "</div>\n");
1964 done:
1965 return error;
1968 static const struct got_error *
1969 gotweb_render_diff(struct request *c)
1971 const struct got_error *error = NULL;
1972 struct transport *t = c->t;
1973 struct repo_commit *rc = NULL;
1974 char *age = NULL, *author = NULL;
1976 error = got_get_repo_commits(c, 1);
1977 if (error)
1978 return error;
1980 rc = TAILQ_FIRST(&t->repo_commits);
1982 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1983 if (error)
1984 goto done;
1985 error = gotweb_escape_html(&author, rc->author);
1986 if (error)
1987 goto done;
1989 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1990 goto done;
1991 if (fcgi_gen_response(c,
1992 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1993 goto done;
1994 if (fcgi_gen_response(c, "</div>\n") == -1)
1995 goto done;
1997 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1998 goto done;
1999 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
2000 goto done;
2001 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
2002 goto done;
2004 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2005 "</div>\n") == -1)
2006 goto done;
2007 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2008 goto done;
2009 if (fcgi_gen_response(c, rc->parent_id) == -1)
2010 goto done;
2011 if (fcgi_gen_response(c, "<br />") == -1)
2012 goto done;
2013 if (fcgi_gen_response(c, rc->commit_id) == -1)
2014 goto done;
2015 if (fcgi_gen_response(c, "</div>\n") == -1)
2016 goto done;
2018 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2019 "</div>\n") == -1)
2020 goto done;
2021 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2022 goto done;
2023 if (fcgi_gen_response(c, rc->commit_id) == -1)
2024 goto done;
2025 if (fcgi_gen_response(c, "</div>\n") == -1)
2026 goto done;
2028 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2029 "</div>\n") == -1)
2030 goto done;
2031 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2032 goto done;
2033 if (fcgi_gen_response(c, rc->tree_id) == -1)
2034 goto done;
2035 if (fcgi_gen_response(c, "</div>\n") == -1)
2036 goto done;
2038 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
2039 "</div>\n") == -1)
2040 goto done;
2041 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2042 goto done;
2043 if (fcgi_gen_response(c, author ? author : "") == -1)
2044 goto done;
2045 if (fcgi_gen_response(c, "</div>\n") == -1)
2046 goto done;
2048 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2049 "</div>\n") == -1)
2050 goto done;
2051 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2052 goto done;
2053 if (fcgi_gen_response(c, age ? age : "") == -1)
2054 goto done;
2055 if (fcgi_gen_response(c, "</div>\n") == -1)
2056 goto done;
2058 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2059 "</div>\n") == -1)
2060 goto done;
2061 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2062 goto done;
2063 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2064 goto done;
2065 if (fcgi_gen_response(c, "</div>\n") == -1)
2066 goto done;
2067 if (fcgi_gen_response(c, "</div>\n") == -1)
2068 goto done;
2069 if (fcgi_gen_response(c, "</div>\n") == -1)
2070 goto done;
2072 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2073 goto done;
2074 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2075 goto done;
2077 error = got_output_repo_diff(c);
2078 if (error)
2079 goto done;
2081 fcgi_gen_response(c, "</div>\n");
2082 done:
2083 fcgi_gen_response(c, "</div>\n");
2084 free(age);
2085 free(author);
2086 return error;
2089 static const struct got_error *
2090 gotweb_render_summary(struct request *c)
2092 const struct got_error *error = NULL;
2093 struct transport *t = c->t;
2094 struct server *srv = c->srv;
2096 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2097 goto done;
2099 if (!srv->show_repo_description)
2100 goto owner;
2102 if (fcgi_gen_response(c, "<div id='description_title'>"
2103 "Description:</div>\n") == -1)
2104 goto done;
2105 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2106 goto done;
2107 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2108 goto done;
2109 if (fcgi_gen_response(c, "</div>\n") == -1)
2110 goto done;
2111 owner:
2112 if (!srv->show_repo_owner)
2113 goto last_change;
2115 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2116 "Owner:</div>\n") == -1)
2117 goto done;
2118 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2119 goto done;
2120 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2121 goto done;
2122 if (fcgi_gen_response(c, "</div>\n") == -1)
2123 goto done;
2124 last_change:
2125 if (!srv->show_repo_age)
2126 goto clone_url;
2128 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2129 "Last Change:</div>\n") == -1)
2130 goto done;
2131 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2132 goto done;
2133 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2134 goto done;
2135 if (fcgi_gen_response(c, "</div>\n") == -1)
2136 goto done;
2137 clone_url:
2138 if (!srv->show_repo_cloneurl)
2139 goto content;
2141 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2142 "Clone URL:</div>\n") == -1)
2143 goto done;
2144 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2145 goto done;
2146 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2147 goto done;
2148 if (fcgi_gen_response(c, "</div>\n") == -1)
2149 goto done;
2150 content:
2151 if (fcgi_gen_response(c, "</div>\n") == -1)
2152 goto done;
2153 if (fcgi_gen_response(c, "</div>\n") == -1)
2154 goto done;
2156 error = gotweb_render_briefs(c);
2157 if (error) {
2158 log_warnx("%s: %s", __func__, error->msg);
2159 goto done;
2162 error = gotweb_render_tags(c);
2163 if (error) {
2164 log_warnx("%s: %s", __func__, error->msg);
2165 goto done;
2168 error = gotweb_render_branches(c);
2169 if (error)
2170 log_warnx("%s: %s", __func__, error->msg);
2171 done:
2172 return error;
2175 static const struct got_error *
2176 gotweb_render_tag(struct request *c)
2178 const struct got_error *error = NULL;
2179 struct repo_tag *rt = NULL;
2180 struct transport *t = c->t;
2181 char *age = NULL, *author = NULL;
2183 error = got_get_repo_tags(c, 1);
2184 if (error)
2185 goto done;
2187 if (t->tag_count == 0) {
2188 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2189 "bad commit id");
2190 goto done;
2193 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2195 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2196 if (error)
2197 goto done;
2198 error = gotweb_escape_html(&author, rt->tagger);
2199 if (error)
2200 goto done;
2202 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2203 goto done;
2204 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2205 goto done;
2206 if (fcgi_gen_response(c, "</div>\n") == -1)
2207 goto done;
2209 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2210 goto done;
2211 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2212 goto done;
2213 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2214 goto done;
2216 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2217 "</div>\n") == -1)
2218 goto done;
2219 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2220 goto done;
2221 if (fcgi_gen_response(c, rt->commit_id) == -1)
2222 goto done;
2224 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2225 rt->tag_name += 5;
2227 if (fcgi_gen_response(c, " <span id='refs_str'>(") == -1)
2228 goto done;
2229 if (fcgi_gen_response(c, rt->tag_name) == -1)
2230 goto done;
2231 if (fcgi_gen_response(c, ")</span>") == -1)
2232 goto done;
2234 if (fcgi_gen_response(c, "</div>\n") == -1)
2235 goto done;
2237 if (fcgi_gen_response(c, "<div id='header_author_title'>Tagger:"
2238 "</div>\n") == -1)
2239 goto done;
2240 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2241 goto done;
2242 if (fcgi_gen_response(c, author ? author : "") == -1)
2243 goto done;
2244 if (fcgi_gen_response(c, "</div>\n") == -1)
2245 goto done;
2247 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2248 "</div>\n") == -1)
2249 goto done;
2250 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2251 goto done;
2252 if (fcgi_gen_response(c, age ? age : "") == -1)
2253 goto done;
2254 if (fcgi_gen_response(c, "</div>\n") == -1)
2255 goto done;
2257 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2258 "</div>\n") == -1)
2259 goto done;
2260 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2261 goto done;
2262 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2263 goto done;
2264 if (fcgi_gen_response(c, "</div>\n") == -1)
2265 goto done;
2266 if (fcgi_gen_response(c, "</div>\n") == -1)
2267 goto done;
2269 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2270 goto done;
2271 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2272 goto done;
2274 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2275 goto done;
2277 if (fcgi_gen_response(c, "</div>\n") == -1)
2278 goto done;
2279 fcgi_gen_response(c, "</div>\n");
2280 done:
2281 free(age);
2282 free(author);
2283 return error;
2286 static const struct got_error *
2287 gotweb_render_tags(struct request *c)
2289 const struct got_error *error = NULL;
2290 struct repo_tag *rt = NULL;
2291 struct server *srv = c->srv;
2292 struct transport *t = c->t;
2293 struct querystring *qs = t->qs;
2294 struct repo_dir *repo_dir = t->repo_dir;
2295 char *newline;
2296 char *age = NULL;
2297 int commit_found = 0;
2299 if (qs->action == BRIEFS) {
2300 qs->action = TAGS;
2301 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2302 } else
2303 error = got_get_repo_tags(c, srv->max_commits_display);
2304 if (error)
2305 goto done;
2307 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2308 goto done;
2309 if (fcgi_gen_response(c,
2310 "<div id='tags_title'>Tags</div>\n") == -1)
2311 goto done;
2312 if (fcgi_gen_response(c, "</div>\n") == -1)
2313 goto done;
2315 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2316 goto done;
2318 if (t->tag_count == 0) {
2319 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2320 goto done;
2321 if (fcgi_gen_response(c,
2322 "This repository contains no tags\n") == -1)
2323 goto done;
2324 if (fcgi_gen_response(c, "</div>\n") == -1)
2325 goto done;
2326 if (fcgi_gen_response(c, "</div>\n") == -1)
2327 goto done;
2330 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2331 if (commit_found == 0 && qs->commit != NULL) {
2332 if (strcmp(qs->commit, rt->commit_id) != 0)
2333 continue;
2334 else
2335 commit_found = 1;
2337 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2338 if (error)
2339 goto done;
2340 if (fcgi_gen_response(c, "<div id='tag_age'>") == -1)
2341 goto done;
2342 if (fcgi_gen_response(c, age ? age : "") == -1)
2343 goto done;
2344 if (fcgi_gen_response(c, "</div>\n") == -1)
2345 goto done;
2347 if (fcgi_gen_response(c, "<div id='tag'>") == -1)
2348 goto done;
2349 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2350 rt->tag_name += 10;
2351 if (fcgi_gen_response(c, rt->tag_name) == -1)
2352 goto done;
2353 if (fcgi_gen_response(c, "</div>\n") == -1)
2354 goto done;
2356 if (fcgi_gen_response(c, "<div id='tags_log'>") == -1)
2357 goto done;
2358 if (rt->tag_commit != NULL) {
2359 newline = strchr(rt->tag_commit, '\n');
2360 if (newline)
2361 *newline = '\0';
2364 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2365 goto done;
2366 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2367 goto done;
2368 if (fcgi_gen_response(c, "&path=") == -1)
2369 goto done;
2370 if (fcgi_gen_response(c, repo_dir->name) == -1)
2371 goto done;
2372 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2373 goto done;
2374 if (fcgi_gen_response(c, rt->commit_id) == -1)
2375 goto done;
2376 if (fcgi_gen_response(c, "'>") == -1)
2377 goto done;
2378 if (rt->tag_commit != NULL &&
2379 fcgi_gen_response(c, rt->tag_commit) == -1)
2380 goto done;
2381 if (fcgi_gen_response(c, "</a>") == -1)
2382 goto done;
2383 if (fcgi_gen_response(c, "</div>\n") == -1)
2384 goto done;
2386 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
2387 goto done;
2388 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
2389 goto done;
2391 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2392 goto done;
2393 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2394 goto done;
2395 if (fcgi_gen_response(c, "&path=") == -1)
2396 goto done;
2397 if (fcgi_gen_response(c, repo_dir->name) == -1)
2398 goto done;
2399 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2400 goto done;
2401 if (fcgi_gen_response(c, rt->commit_id) == -1)
2402 goto done;
2403 if (fcgi_gen_response(c, "'>") == -1)
2404 goto done;
2405 if (fcgi_gen_response(c, "tag") == -1)
2406 goto done;
2407 if (fcgi_gen_response(c, "</a>") == -1)
2408 goto done;
2410 if (fcgi_gen_response(c, " | ") == -1)
2411 goto done;
2413 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2414 goto done;
2415 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2416 goto done;
2417 if (fcgi_gen_response(c, "&path=") == -1)
2418 goto done;
2419 if (fcgi_gen_response(c, repo_dir->name) == -1)
2420 goto done;
2421 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2422 goto done;
2423 if (fcgi_gen_response(c, rt->commit_id) == -1)
2424 goto done;
2425 if (fcgi_gen_response(c, "'>") == -1)
2426 goto done;
2427 if (fcgi_gen_response(c, "commit briefs") == -1)
2428 goto done;
2429 if (fcgi_gen_response(c, "</a>") == -1)
2430 goto done;
2432 if (fcgi_gen_response(c, " | ") == -1)
2433 goto done;
2435 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2436 goto done;
2437 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2438 goto done;
2439 if (fcgi_gen_response(c, "&path=") == -1)
2440 goto done;
2441 if (fcgi_gen_response(c, repo_dir->name) == -1)
2442 goto done;
2443 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2444 goto done;
2445 if (fcgi_gen_response(c, rt->commit_id) == -1)
2446 goto done;
2447 if (fcgi_gen_response(c, "'>") == -1)
2448 goto done;
2449 if (fcgi_gen_response(c, "commits") == -1)
2450 goto done;
2451 if (fcgi_gen_response(c, "</a>") == -1)
2452 goto done;
2454 if (fcgi_gen_response(c, "</div>\n") == -1)
2455 goto done;
2456 if (fcgi_gen_response(c, "</div>\n") == -1)
2457 goto done;
2458 if (fcgi_gen_response(c,
2459 "<div id='dotted_line'></div>\n") == -1)
2460 goto done;
2462 free(age);
2463 age = NULL;
2465 if (t->next_id || t->prev_id) {
2466 error = gotweb_render_navs(c);
2467 if (error)
2468 goto done;
2470 fcgi_gen_response(c, "</div>\n");
2471 done:
2472 free(age);
2473 return error;
2476 const struct got_error *
2477 gotweb_escape_html(char **escaped_html, const char *orig_html)
2479 const struct got_error *error = NULL;
2480 struct escape_pair {
2481 char c;
2482 const char *s;
2483 } esc[] = {
2484 { '>', "&gt;" },
2485 { '<', "&lt;" },
2486 { '&', "&amp;" },
2487 { '"', "&quot;" },
2488 { '\'', "&apos;" },
2489 { '\n', "<br />" },
2491 size_t orig_len, len;
2492 int i, j, x;
2494 orig_len = strlen(orig_html);
2495 len = orig_len;
2496 for (i = 0; i < orig_len; i++) {
2497 for (j = 0; j < nitems(esc); j++) {
2498 if (orig_html[i] != esc[j].c)
2499 continue;
2500 len += strlen(esc[j].s) - 1 /* escaped char */;
2504 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2505 if (*escaped_html == NULL)
2506 return got_error_from_errno("calloc");
2508 x = 0;
2509 for (i = 0; i < orig_len; i++) {
2510 int escaped = 0;
2511 for (j = 0; j < nitems(esc); j++) {
2512 if (orig_html[i] != esc[j].c)
2513 continue;
2515 if (strlcat(*escaped_html, esc[j].s, len + 1)
2516 >= len + 1) {
2517 error = got_error(GOT_ERR_NO_SPACE);
2518 goto done;
2520 x += strlen(esc[j].s);
2521 escaped = 1;
2522 break;
2524 if (!escaped) {
2525 (*escaped_html)[x] = orig_html[i];
2526 x++;
2529 done:
2530 if (error) {
2531 free(*escaped_html);
2532 *escaped_html = NULL;
2533 } else {
2534 (*escaped_html)[x] = '\0';
2537 return error;
2540 static const struct got_error *
2541 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2543 const struct got_error *error = NULL;
2544 struct socket *sock = c->sock;
2545 struct server *srv = c->srv;
2546 struct transport *t = c->t;
2547 DIR *dt;
2548 char *dir_test;
2549 int opened = 0;
2551 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2552 GOTWEB_GIT_DIR) == -1)
2553 return got_error_from_errno("asprintf");
2555 dt = opendir(dir_test);
2556 if (dt == NULL) {
2557 free(dir_test);
2558 } else {
2559 repo_dir->path = strdup(dir_test);
2560 if (repo_dir->path == NULL) {
2561 opened = 1;
2562 error = got_error_from_errno("strdup");
2563 goto err;
2565 opened = 1;
2566 goto done;
2569 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2570 GOTWEB_GOT_DIR) == -1) {
2571 dir_test = NULL;
2572 error = got_error_from_errno("asprintf");
2573 goto err;
2576 dt = opendir(dir_test);
2577 if (dt == NULL)
2578 free(dir_test);
2579 else {
2580 opened = 1;
2581 error = got_error(GOT_ERR_NOT_GIT_REPO);
2582 goto err;
2585 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2586 repo_dir->name) == -1) {
2587 error = got_error_from_errno("asprintf");
2588 dir_test = NULL;
2589 goto err;
2592 repo_dir->path = strdup(dir_test);
2593 if (repo_dir->path == NULL) {
2594 opened = 1;
2595 error = got_error_from_errno("strdup");
2596 goto err;
2599 dt = opendir(dir_test);
2600 if (dt == NULL) {
2601 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2602 goto err;
2603 } else
2604 opened = 1;
2605 done:
2606 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2607 if (error)
2608 goto err;
2609 error = gotweb_get_repo_description(&repo_dir->description, srv,
2610 repo_dir->path);
2611 if (error)
2612 goto err;
2613 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2614 if (error)
2615 goto err;
2616 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2617 NULL, TM_DIFF);
2618 if (error)
2619 goto err;
2620 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2621 err:
2622 free(dir_test);
2623 if (opened)
2624 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2625 error = got_error_from_errno("closedir");
2626 return error;
2629 static const struct got_error *
2630 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2632 const struct got_error *error;
2634 *repo_dir = calloc(1, sizeof(**repo_dir));
2635 if (*repo_dir == NULL)
2636 return got_error_from_errno("calloc");
2638 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2639 error = got_error_from_errno("asprintf");
2640 free(*repo_dir);
2641 *repo_dir = NULL;
2642 return error;
2644 (*repo_dir)->owner = NULL;
2645 (*repo_dir)->description = NULL;
2646 (*repo_dir)->url = NULL;
2647 (*repo_dir)->age = NULL;
2648 (*repo_dir)->path = NULL;
2650 return NULL;
2653 static const struct got_error *
2654 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2656 const struct got_error *error = NULL;
2657 FILE *f = NULL;
2658 char *d_file = NULL;
2659 unsigned int len;
2660 size_t n;
2662 *description = NULL;
2663 if (srv->show_repo_description == 0)
2664 return NULL;
2666 if (asprintf(&d_file, "%s/description", dir) == -1)
2667 return got_error_from_errno("asprintf");
2669 f = fopen(d_file, "r");
2670 if (f == NULL) {
2671 if (errno == ENOENT || errno == EACCES)
2672 return NULL;
2673 error = got_error_from_errno2("fopen", d_file);
2674 goto done;
2677 if (fseek(f, 0, SEEK_END) == -1) {
2678 error = got_ferror(f, GOT_ERR_IO);
2679 goto done;
2681 len = ftell(f);
2682 if (len == -1) {
2683 error = got_ferror(f, GOT_ERR_IO);
2684 goto done;
2687 if (len == 0)
2688 goto done;
2690 if (fseek(f, 0, SEEK_SET) == -1) {
2691 error = got_ferror(f, GOT_ERR_IO);
2692 goto done;
2694 *description = calloc(len + 1, sizeof(**description));
2695 if (*description == NULL) {
2696 error = got_error_from_errno("calloc");
2697 goto done;
2700 n = fread(*description, 1, len, f);
2701 if (n == 0 && ferror(f))
2702 error = got_ferror(f, GOT_ERR_IO);
2703 done:
2704 if (f != NULL && fclose(f) == EOF && error == NULL)
2705 error = got_error_from_errno("fclose");
2706 free(d_file);
2707 return error;
2710 static const struct got_error *
2711 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2713 const struct got_error *error = NULL;
2714 FILE *f;
2715 char *d_file = NULL;
2716 unsigned int len;
2717 size_t n;
2719 *url = NULL;
2721 if (srv->show_repo_cloneurl == 0)
2722 return NULL;
2724 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2725 return got_error_from_errno("asprintf");
2727 f = fopen(d_file, "r");
2728 if (f == NULL) {
2729 if (errno != ENOENT && errno != EACCES)
2730 error = got_error_from_errno2("fopen", d_file);
2731 goto done;
2734 if (fseek(f, 0, SEEK_END) == -1) {
2735 error = got_ferror(f, GOT_ERR_IO);
2736 goto done;
2738 len = ftell(f);
2739 if (len == -1) {
2740 error = got_ferror(f, GOT_ERR_IO);
2741 goto done;
2743 if (len == 0)
2744 goto done;
2746 if (fseek(f, 0, SEEK_SET) == -1) {
2747 error = got_ferror(f, GOT_ERR_IO);
2748 goto done;
2751 *url = calloc(len + 1, sizeof(**url));
2752 if (*url == NULL) {
2753 error = got_error_from_errno("calloc");
2754 goto done;
2757 n = fread(*url, 1, len, f);
2758 if (n == 0 && ferror(f))
2759 error = got_ferror(f, GOT_ERR_IO);
2760 done:
2761 if (f != NULL && fclose(f) == EOF && error == NULL)
2762 error = got_error_from_errno("fclose");
2763 free(d_file);
2764 return error;
2767 const struct got_error *
2768 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2770 struct tm tm;
2771 long long diff_time;
2772 const char *years = "years ago", *months = "months ago";
2773 const char *weeks = "weeks ago", *days = "days ago";
2774 const char *hours = "hours ago", *minutes = "minutes ago";
2775 const char *seconds = "seconds ago", *now = "right now";
2776 char *s;
2777 char datebuf[29];
2779 *repo_age = NULL;
2781 switch (ref_tm) {
2782 case TM_DIFF:
2783 diff_time = time(NULL) - committer_time;
2784 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2785 if (asprintf(repo_age, "%lld %s",
2786 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2787 return got_error_from_errno("asprintf");
2788 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2789 if (asprintf(repo_age, "%lld %s",
2790 (diff_time / 60 / 60 / 24 / (365 / 12)),
2791 months) == -1)
2792 return got_error_from_errno("asprintf");
2793 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2794 if (asprintf(repo_age, "%lld %s",
2795 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2796 return got_error_from_errno("asprintf");
2797 } else if (diff_time > 60 * 60 * 24 * 2) {
2798 if (asprintf(repo_age, "%lld %s",
2799 (diff_time / 60 / 60 / 24), days) == -1)
2800 return got_error_from_errno("asprintf");
2801 } else if (diff_time > 60 * 60 * 2) {
2802 if (asprintf(repo_age, "%lld %s",
2803 (diff_time / 60 / 60), hours) == -1)
2804 return got_error_from_errno("asprintf");
2805 } else if (diff_time > 60 * 2) {
2806 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2807 minutes) == -1)
2808 return got_error_from_errno("asprintf");
2809 } else if (diff_time > 2) {
2810 if (asprintf(repo_age, "%lld %s", diff_time,
2811 seconds) == -1)
2812 return got_error_from_errno("asprintf");
2813 } else {
2814 if (asprintf(repo_age, "%s", now) == -1)
2815 return got_error_from_errno("asprintf");
2817 break;
2818 case TM_LONG:
2819 if (gmtime_r(&committer_time, &tm) == NULL)
2820 return got_error_from_errno("gmtime_r");
2822 s = asctime_r(&tm, datebuf);
2823 if (s == NULL)
2824 return got_error_from_errno("asctime_r");
2826 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2827 return got_error_from_errno("asprintf");
2828 break;
2830 return NULL;