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/stat.h>
23 #include <sys/types.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <event.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
33 #include "got_error.h"
34 #include "got_object.h"
35 #include "got_reference.h"
36 #include "got_repository.h"
37 #include "got_path.h"
38 #include "got_cancel.h"
39 #include "got_worktree.h"
40 #include "got_diff.h"
41 #include "got_commit_graph.h"
42 #include "got_blame.h"
43 #include "got_privsep.h"
45 #include "proc.h"
46 #include "gotwebd.h"
48 #include "got_compat.h"
50 enum gotweb_ref_tm {
51 TM_DIFF,
52 TM_LONG,
53 };
55 static const struct querystring_keys querystring_keys[] = {
56 { "action", ACTION },
57 { "commit", COMMIT },
58 { "file", RFILE },
59 { "folder", FOLDER },
60 { "headref", HEADREF },
61 { "index_page", INDEX_PAGE },
62 { "path", PATH },
63 { "page", PAGE },
64 };
66 static const struct action_keys action_keys[] = {
67 { "blame", BLAME },
68 { "blob", BLOB },
69 { "briefs", BRIEFS },
70 { "commits", COMMITS },
71 { "diff", DIFF },
72 { "error", ERR },
73 { "index", INDEX },
74 { "summary", SUMMARY },
75 { "tag", TAG },
76 { "tags", TAGS },
77 { "tree", TREE },
78 };
80 static const struct got_error *gotweb_init_querystring(struct querystring **);
81 static const struct got_error *gotweb_parse_querystring(struct querystring **,
82 char *);
83 static const struct got_error *gotweb_assign_querystring(struct querystring **,
84 char *, char *);
85 static const struct got_error *gotweb_render_header(struct request *);
86 static const struct got_error *gotweb_render_footer(struct request *);
87 static const struct got_error *gotweb_render_index(struct request *);
88 static const struct got_error *gotweb_init_repo_dir(struct repo_dir **,
89 const char *);
90 static const struct got_error *gotweb_load_got_path(struct request *c,
91 struct repo_dir *);
92 static const struct got_error *gotweb_get_repo_description(char **,
93 struct server *, char *);
94 static const struct got_error *gotweb_get_clone_url(char **, struct server *,
95 char *);
96 static const struct got_error *gotweb_render_navs(struct request *);
97 static const struct got_error *gotweb_render_blame(struct request *);
98 static const struct got_error *gotweb_render_briefs(struct request *);
99 static const struct got_error *gotweb_render_commits(struct request *);
100 static const struct got_error *gotweb_render_diff(struct request *);
101 static const struct got_error *gotweb_render_summary(struct request *);
102 static const struct got_error *gotweb_render_tag(struct request *);
103 static const struct got_error *gotweb_render_tags(struct request *);
104 static const struct got_error *gotweb_render_tree(struct request *);
105 static const struct got_error *gotweb_render_branches(struct request *);
107 static void gotweb_free_querystring(struct querystring *);
108 static void gotweb_free_repo_dir(struct repo_dir *);
110 struct server *gotweb_get_server(uint8_t *, uint8_t *, uint8_t *);
112 void
113 gotweb_process_request(struct request *c)
115 const struct got_error *error = NULL, *error2 = NULL;
116 struct server *srv = NULL;
117 struct querystring *qs = NULL;
118 struct repo_dir *repo_dir = NULL;
119 uint8_t err[] = "gotwebd experienced an error: ";
120 int html = 0;
122 /* init the transport */
123 error = gotweb_init_transport(&c->t);
124 if (error) {
125 log_warnx("%s: %s", __func__, error->msg);
126 goto err;
128 /* don't process any further if client disconnected */
129 if (c->sock->client_status == CLIENT_DISCONNECT)
130 return;
131 /* get the gotwebd server */
132 srv = gotweb_get_server(c->server_name, c->document_root, c->http_host);
133 if (srv == NULL) {
134 log_warnx("%s: error server is NULL", __func__);
135 goto err;
137 c->srv = srv;
138 /* parse our querystring */
139 error = gotweb_init_querystring(&qs);
140 if (error) {
141 log_warnx("%s: %s", __func__, error->msg);
142 goto err;
144 c->t->qs = qs;
145 error = gotweb_parse_querystring(&qs, c->querystring);
146 if (error) {
147 gotweb_free_querystring(qs);
148 log_warnx("%s: %s", __func__, error->msg);
149 goto err;
152 /*
153 * certain actions require a commit id in the querystring. this stops
154 * bad actors from exploiting this by manually manipulating the
155 * querystring.
156 */
158 if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
159 qs->action == DIFF)) {
160 error2 = got_error(GOT_ERR_QUERYSTRING);
161 goto render;
164 if (qs->action != INDEX) {
165 error = gotweb_init_repo_dir(&repo_dir, qs->path);
166 if (error)
167 goto done;
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)
171 goto err;
174 /* render top of page */
175 if (qs != NULL && qs->action == BLOB) {
176 error = got_get_repo_commits(c, 1);
177 if (error)
178 goto done;
179 error = gotweb_render_content_type(c, "text/plain");
180 if (error) {
181 log_warnx("%s: %s", __func__, error->msg);
182 goto err;
184 error = got_output_file_blob(c);
185 if (error) {
186 log_warnx("%s: %s", __func__, error->msg);
187 goto err;
189 goto done;
190 } else {
191 render:
192 error = gotweb_render_content_type(c, "text/html");
193 if (error) {
194 log_warnx("%s: %s", __func__, error->msg);
195 goto err;
197 html = 1;
200 error = gotweb_render_header(c);
201 if (error) {
202 log_warnx("%s: %s", __func__, error->msg);
203 goto err;
206 if (error2) {
207 error = error2;
208 goto err;
211 switch(qs->action) {
212 case BLAME:
213 error = gotweb_render_blame(c);
214 if (error) {
215 log_warnx("%s: %s", __func__, error->msg);
216 goto err;
218 break;
219 case BRIEFS:
220 error = gotweb_render_briefs(c);
221 if (error) {
222 log_warnx("%s: %s", __func__, error->msg);
223 goto err;
225 break;
226 case COMMITS:
227 error = gotweb_render_commits(c);
228 if (error) {
229 log_warnx("%s: %s", __func__, error->msg);
230 goto err;
232 break;
233 case DIFF:
234 error = gotweb_render_diff(c);
235 if (error) {
236 log_warnx("%s: %s", __func__, error->msg);
237 goto err;
239 break;
240 case INDEX:
241 error = gotweb_render_index(c);
242 if (error) {
243 log_warnx("%s: %s", __func__, error->msg);
244 goto err;
246 break;
247 case SUMMARY:
248 error = gotweb_render_summary(c);
249 if (error) {
250 log_warnx("%s: %s", __func__, error->msg);
251 goto err;
253 break;
254 case TAG:
255 error = gotweb_render_tag(c);
256 if (error) {
257 log_warnx("%s: %s", __func__, error->msg);
258 goto err;
260 break;
261 case TAGS:
262 error = gotweb_render_tags(c);
263 if (error) {
264 log_warnx("%s: %s", __func__, error->msg);
265 goto err;
267 break;
268 case TREE:
269 error = gotweb_render_tree(c);
270 if (error) {
271 log_warnx("%s: %s", __func__, error->msg);
272 goto err;
274 break;
275 case ERR:
276 default:
277 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
278 goto err;
279 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
280 goto err;
281 if (fcgi_gen_response(c, "</div>\n") == -1)
282 goto err;
283 break;
286 goto done;
287 err:
288 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
289 return;
290 if (fcgi_gen_response(c, err) == -1)
291 return;
292 if (error) {
293 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
294 return;
295 } else {
296 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
297 return;
299 if (html && fcgi_gen_response(c, "</div>\n") == -1)
300 return;
301 done:
302 if (c->t->repo != NULL && qs->action != INDEX)
303 got_repo_close(c->t->repo);
304 if (html && srv != NULL)
305 gotweb_render_footer(c);
308 struct server *
309 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
310 uint8_t *subdomain)
312 struct server *srv = NULL;
314 /* check against document_root first */
315 if (strlen(server_name) > 0)
316 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
317 if (strcmp(srv->name, server_name) == 0)
318 goto done;
320 /* check against document_root second */
321 if (strlen(document_root) > 0)
322 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
323 if (strcmp(srv->name, document_root) == 0)
324 goto done;
326 /* check against subdomain third */
327 if (strlen(subdomain) > 0)
328 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
329 if (strcmp(srv->name, subdomain) == 0)
330 goto done;
332 /* if those fail, send first server */
333 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
334 if (srv != NULL)
335 break;
336 done:
337 return srv;
338 };
340 const struct got_error *
341 gotweb_init_transport(struct transport **t)
343 const struct got_error *error = NULL;
345 *t = calloc(1, sizeof(**t));
346 if (*t == NULL)
347 return got_error_from_errno2("%s: calloc", __func__);
349 TAILQ_INIT(&(*t)->repo_commits);
350 TAILQ_INIT(&(*t)->repo_tags);
352 (*t)->repo = NULL;
353 (*t)->repo_dir = NULL;
354 (*t)->qs = NULL;
355 (*t)->next_id = NULL;
356 (*t)->prev_id = NULL;
357 (*t)->next_disp = 0;
358 (*t)->prev_disp = 0;
360 return error;
363 static const struct got_error *
364 gotweb_init_querystring(struct querystring **qs)
366 const struct got_error *error = NULL;
368 *qs = calloc(1, sizeof(**qs));
369 if (*qs == NULL)
370 return got_error_from_errno2("%s: calloc", __func__);
372 (*qs)->action = INDEX;
373 (*qs)->commit = NULL;
374 (*qs)->file = NULL;
375 (*qs)->folder = NULL;
376 (*qs)->headref = strdup("HEAD");
377 if ((*qs)->headref == NULL) {
378 return got_error_from_errno2("%s: strdup", __func__);
380 (*qs)->index_page = 0;
381 (*qs)->index_page_str = NULL;
382 (*qs)->path = NULL;
384 return error;
387 static const struct got_error *
388 gotweb_parse_querystring(struct querystring **qs, char *qst)
390 const struct got_error *error = NULL;
391 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
392 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
394 if (qst == NULL)
395 return error;
397 tok1 = strdup(qst);
398 if (tok1 == NULL)
399 return got_error_from_errno2("%s: strdup", __func__);
401 tok1_pair = tok1;
402 tok1_end = tok1;
404 while (tok1_pair != NULL) {
405 strsep(&tok1_end, "&");
407 tok2 = strdup(tok1_pair);
408 if (tok2 == NULL) {
409 free(tok1);
410 return got_error_from_errno2("%s: strdup", __func__);
413 tok2_pair = tok2;
414 tok2_end = tok2;
416 while (tok2_pair != NULL) {
417 strsep(&tok2_end, "=");
418 if (tok2_end) {
419 error = gotweb_assign_querystring(qs, tok2_pair,
420 tok2_end);
421 if (error)
422 goto err;
424 tok2_pair = tok2_end;
426 free(tok2);
427 tok1_pair = tok1_end;
429 free(tok1);
430 return error;
431 err:
432 free(tok2);
433 free(tok1);
434 return error;
437 static const struct got_error *
438 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
440 const struct got_error *error = NULL;
441 const char *errstr;
442 int a_cnt, el_cnt;
444 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
445 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
446 continue;
448 switch (querystring_keys[el_cnt].element) {
449 case ACTION:
450 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
451 if (strcmp(value, action_keys[a_cnt].name) != 0)
452 continue;
453 else if (strcmp(value,
454 action_keys[a_cnt].name) == 0){
455 (*qs)->action =
456 action_keys[a_cnt].action;
457 goto qa_found;
460 (*qs)->action = ERR;
461 qa_found:
462 break;
463 case COMMIT:
464 (*qs)->commit = strdup(value);
465 if ((*qs)->commit == NULL) {
466 error = got_error_from_errno2("%s: strdup",
467 __func__);
468 goto done;
470 break;
471 case RFILE:
472 (*qs)->file = strdup(value);
473 if ((*qs)->file == NULL) {
474 error = got_error_from_errno2("%s: strdup",
475 __func__);
476 goto done;
478 break;
479 case FOLDER:
480 (*qs)->folder = strdup(value);
481 if ((*qs)->folder == NULL) {
482 error = got_error_from_errno2("%s: strdup",
483 __func__);
484 goto done;
486 break;
487 case HEADREF:
488 (*qs)->headref = strdup(value);
489 if ((*qs)->headref == NULL) {
490 error = got_error_from_errno2("%s: strdup",
491 __func__);
492 goto done;
494 break;
495 case INDEX_PAGE:
496 if (strlen(value) == 0)
497 break;
498 (*qs)->index_page_str = strdup(value);
499 if ((*qs)->index_page_str == NULL) {
500 error = got_error_from_errno2("%s: strdup",
501 __func__);
502 goto done;
504 (*qs)->index_page = strtonum(value, INT64_MIN,
505 INT64_MAX, &errstr);
506 if (errstr) {
507 error = got_error_from_errno3("%s: strtonum %s",
508 __func__, errstr);
509 goto done;
511 if ((*qs)->index_page < 0) {
512 (*qs)->index_page = 0;
513 sprintf((*qs)->index_page_str, "%d", 0);
515 break;
516 case PATH:
517 (*qs)->path = strdup(value);
518 if ((*qs)->path == NULL) {
519 error = got_error_from_errno2("%s: strdup",
520 __func__);
521 goto done;
523 break;
524 case PAGE:
525 if (strlen(value) == 0)
526 break;
527 (*qs)->page_str = strdup(value);
528 if ((*qs)->page_str == NULL) {
529 error = got_error_from_errno2("%s: strdup",
530 __func__);
531 goto done;
533 (*qs)->page = strtonum(value, INT64_MIN,
534 INT64_MAX, &errstr);
535 if (errstr) {
536 error = got_error_from_errno3("%s: strtonum %s",
537 __func__, errstr);
538 goto done;
540 if ((*qs)->page < 0) {
541 (*qs)->page = 0;
542 sprintf((*qs)->page_str, "%d", 0);
544 break;
545 default:
546 break;
549 done:
550 return error;
553 void
554 gotweb_free_repo_tag(struct repo_tag *rt)
556 if (rt != NULL) {
557 free(rt->commit_msg);
558 free(rt->commit_id);
559 free(rt->tagger);
561 free(rt);
564 void
565 gotweb_free_repo_commit(struct repo_commit *rc)
567 if (rc != NULL) {
568 free(rc->path);
569 free(rc->refs_str);
570 free(rc->commit_id);
571 free(rc->parent_id);
572 free(rc->tree_id);
573 free(rc->author);
574 free(rc->committer);
575 free(rc->commit_msg);
577 free(rc);
580 static void
581 gotweb_free_querystring(struct querystring *qs)
583 if (qs != NULL) {
584 free(qs->commit);
585 free(qs->file);
586 free(qs->folder);
587 free(qs->headref);
588 free(qs->index_page_str);
589 free(qs->path);
590 free(qs->page_str);
592 free(qs);
595 static void
596 gotweb_free_repo_dir(struct repo_dir *repo_dir)
598 if (repo_dir != NULL) {
599 free(repo_dir->name);
600 free(repo_dir->owner);
601 free(repo_dir->description);
602 free(repo_dir->url);
603 free(repo_dir->age);
604 free(repo_dir->path);
606 free(repo_dir);
609 void
610 gotweb_free_transport(struct transport *t)
612 struct repo_commit *rc = NULL, *trc = NULL;
613 struct repo_tag *rt = NULL, *trt = NULL;
615 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
616 TAILQ_REMOVE(&t->repo_commits, rc, entry);
617 gotweb_free_repo_commit(rc);
619 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
620 TAILQ_REMOVE(&t->repo_tags, rt, entry);
621 gotweb_free_repo_tag(rt);
623 gotweb_free_repo_dir(t->repo_dir);
624 gotweb_free_querystring(t->qs);
625 if (t != NULL) {
626 free(t->next_id);
627 free(t->prev_id);
629 free(t);
632 const struct got_error *
633 gotweb_render_content_type(struct request *c, const uint8_t *type)
635 const struct got_error *error = NULL;
636 char *h = NULL;
638 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
639 error = got_error_from_errno2("%s: asprintf", __func__);
640 goto done;
643 fcgi_gen_response(c, h);
644 done:
645 free(h);
647 return error;
650 const struct got_error *
651 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
652 char *file)
654 const struct got_error *error = NULL;
655 char *h = NULL;
657 if (asprintf(&h, "Content-type: %s\r\n"
658 "Content-disposition: attachment; filename=%s\r\n\r\n",
659 type, file) == -1) {
660 error = got_error_from_errno2("%s: asprintf", __func__);
661 goto done;
664 fcgi_gen_response(c, h);
665 done:
666 free(h);
668 return error;
671 static const struct got_error *
672 gotweb_render_header(struct request *c)
674 const struct got_error *error = NULL;
675 struct server *srv = c->srv;
676 struct querystring *qs = c->t->qs;
677 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
678 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
680 if (strlen(c->document_root) > 0) {
681 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
682 error = got_error_from_errno2("%s: asprintf", __func__);
683 goto done;
685 } else {
686 if (asprintf(&droot, "/") == -1) {
687 error = got_error_from_errno2("%s: asprintf", __func__);
688 goto done;
692 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
693 error = got_error_from_errno2("%s: asprintf", __func__);
694 goto done;
696 if (asprintf(&css,
697 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
698 droot, srv->custom_css) == -1) {
699 error = got_error_from_errno2("%s: asprintf", __func__);
700 goto done;
702 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
703 srv->logo_url) == -1) {
704 error = got_error_from_errno2("%s: asprintf", __func__);
705 goto done;
707 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
708 droot, srv->logo) == -1) {
709 error = got_error_from_errno2("%s: asprintf", __func__);
710 goto done;
712 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
713 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
714 srv->site_link) == -1) {
715 error = got_error_from_errno2("%s: asprintf", __func__);
716 goto done;
718 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
719 "&action=summary' alt='summlink'>%s</a>", c->document_root,
720 qs->index_page, qs->path, qs->path) == -1) {
721 error = got_error_from_errno2("%s: asprintf", __func__);
722 goto done;
725 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
726 goto done;
727 if (fcgi_gen_response(c, title) == -1)
728 goto done;
729 if (fcgi_gen_response(c, "<meta name='viewport' "
730 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
731 goto done;
732 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
733 goto done;
734 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
735 "content='#da532c'/>\n") == -1)
736 goto done;
737 if (fcgi_gen_response(c,
738 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
739 goto done;
740 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
741 "href='/apple-touch-icon.png'/>\n") == -1)
742 goto done;
743 if (fcgi_gen_response(c,
744 "<link rel='icon' type='image/png' sizes='32x32' "
745 "href='/favicon-32x32.png'/>\n") == -1)
746 goto done;
747 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
748 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
749 goto done;
750 if (fcgi_gen_response(c, "<link rel='manifest' "
751 "href='/site.webmanifest'/>\n") == -1)
752 goto done;
753 if (fcgi_gen_response(c, "<link rel='mask-icon' "
754 "href='/safari-pinned-tab.svg'/>\n") == -1)
755 goto done;
756 if (fcgi_gen_response(c, css) == -1)
757 goto done;
758 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
759 goto done;
760 if (fcgi_gen_response(c,
761 "<div id='header'>\n<div id='got_link'>") == -1)
762 goto done;
763 if (fcgi_gen_response(c, gotlink) == -1)
764 goto done;
765 if (fcgi_gen_response(c, gotimg) == -1)
766 goto done;
767 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
768 goto done;
769 if (fcgi_gen_response(c,
770 "<div id='site_path'>\n<div id='site_link'>") == -1)
771 goto done;
772 if (fcgi_gen_response(c, sitelink) == -1)
773 goto done;
774 if (qs != NULL) {
775 if (qs->path != NULL) {
776 if (fcgi_gen_response(c, " / ") == -1)
777 goto done;
778 if (fcgi_gen_response(c, summlink) == -1)
779 goto done;
781 if (qs->action != INDEX) {
782 if (fcgi_gen_response(c, " / ") == -1)
783 goto done;
784 switch(qs->action) {
785 case(BLAME):
786 if (fcgi_gen_response(c, "blame") == -1)
787 goto done;
788 break;
789 case(BRIEFS):
790 if (fcgi_gen_response(c, "briefs") == -1)
791 goto done;
792 break;
793 case(COMMITS):
794 if (fcgi_gen_response(c, "commits") == -1)
795 goto done;
796 break;
797 case(DIFF):
798 if (fcgi_gen_response(c, "diff") == -1)
799 goto done;
800 break;
801 case(SUMMARY):
802 if (fcgi_gen_response(c, "summary") == -1)
803 goto done;
804 break;
805 case(TAG):
806 if (fcgi_gen_response(c, "tag") == -1)
807 goto done;
808 break;
809 case(TAGS):
810 if (fcgi_gen_response(c, "tags") == -1)
811 goto done;
812 break;
813 case(TREE):
814 if (fcgi_gen_response(c, "tree") == -1)
815 goto done;
816 break;
817 default:
818 break;
823 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
824 done:
825 free(title);
826 free(droot);
827 free(css);
828 free(gotlink);
829 free(gotimg);
830 free(sitelink);
831 free(summlink);
833 return error;
836 static const struct got_error *
837 gotweb_render_footer(struct request *c)
839 const struct got_error *error = NULL;
840 struct server *srv = c->srv;
841 char *siteowner = NULL;
843 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
844 goto done;
845 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
846 goto done;
847 if (srv->show_site_owner) {
848 error = gotweb_escape_html(&siteowner, srv->site_owner);
849 if (error)
850 goto done;
851 if (fcgi_gen_response(c, siteowner) == -1)
852 goto done;
853 } else
854 if (fcgi_gen_response(c, "&nbsp;") == -1)
855 goto done;
856 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
857 done:
858 free(siteowner);
860 return error;
863 static const struct got_error *
864 gotweb_render_navs(struct request *c)
866 const struct got_error *error = NULL;
867 struct transport *t = c->t;
868 struct querystring *qs = t->qs;
869 struct server *srv = c->srv;
870 char *nhref = NULL, *phref = NULL;
871 int disp = 0;
873 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
874 goto done;
875 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
876 goto done;
878 switch(qs->action) {
879 case INDEX:
880 if (qs->index_page > 0) {
881 if (asprintf(&phref, "index_page=%d",
882 qs->index_page - 1) == -1) {
883 error = got_error_from_errno2("%s: asprintf",
884 __func__);
885 goto done;
887 disp = 1;
889 break;
890 case BRIEFS:
891 if (t->prev_id && qs->commit != NULL &&
892 strcmp(qs->commit, t->prev_id) != 0) {
893 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
894 "&action=briefs&commit=%s&headref=%s",
895 qs->index_page, qs->path, qs->page - 1, t->prev_id,
896 qs->headref) == -1) {
897 error = got_error_from_errno2("%s: asprintf",
898 __func__);
899 goto done;
901 disp = 1;
903 break;
904 case COMMITS:
905 if (t->prev_id && qs->commit != NULL &&
906 strcmp(qs->commit, t->prev_id) != 0) {
907 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
908 "&action=commits&commit=%s&headref=%s&folder=%s"
909 "&file=%s",
910 qs->index_page, qs->path, qs->page - 1, t->prev_id,
911 qs->headref, qs->folder ? qs->folder : "",
912 qs->file ? qs->file : "") == -1) {
913 error = got_error_from_errno2("%s: asprintf",
914 __func__);
915 goto done;
917 disp = 1;
919 break;
920 case TAGS:
921 if (t->prev_id && qs->commit != NULL &&
922 strcmp(qs->commit, t->prev_id) != 0) {
923 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
924 "&action=tags&commit=%s&headref=%s",
925 qs->index_page, qs->path, qs->page - 1, t->prev_id,
926 qs->headref) == -1) {
927 error = got_error_from_errno2("%s: asprintf",
928 __func__);
929 goto done;
931 disp = 1;
933 break;
934 default:
935 disp = 0;
936 break;
939 if (disp) {
940 if (fcgi_gen_response(c, "<a href='?") == -1)
941 goto done;
942 if (fcgi_gen_response(c, phref) == -1)
943 goto done;
944 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
945 goto done;
947 if (fcgi_gen_response(c, "</div>\n") == -1)
948 goto done;
949 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
950 goto done;
952 disp = 0;
954 switch(qs->action) {
955 case INDEX:
956 if (t->next_disp == srv->max_repos_display &&
957 t->repos_total != (qs->index_page + 1) *
958 srv->max_repos_display) {
959 if (asprintf(&nhref, "index_page=%d",
960 qs->index_page + 1) == -1) {
961 error = got_error_from_errno2("%s: asprintf",
962 __func__);
963 goto done;
965 disp = 1;
967 break;
968 case BRIEFS:
969 if (t->next_id) {
970 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
971 "&action=briefs&commit=%s&headref=%s",
972 qs->index_page, qs->path, qs->page + 1, t->next_id,
973 qs->headref) == -1) {
974 error = got_error_from_errno2("%s: asprintf",
975 __func__);
976 goto done;
978 disp = 1;
980 break;
981 case COMMITS:
982 if (t->next_id) {
983 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
984 "&action=commits&commit=%s&headref=%s&folder=%s"
985 "&file=%s",
986 qs->index_page, qs->path, qs->page + 1, t->next_id,
987 qs->headref, qs->folder ? qs->folder : "",
988 qs->file ? qs->file : "") == -1) {
989 error = got_error_from_errno2("%s: asprintf",
990 __func__);
991 goto done;
993 disp = 1;
995 break;
996 case TAGS:
997 if (t->next_id) {
998 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
999 "&action=tags&commit=%s&headref=%s",
1000 qs->index_page, qs->path, qs->page + 1, t->next_id,
1001 qs->headref) == -1) {
1002 error = got_error_from_errno2("%s: asprintf",
1003 __func__);
1004 goto done;
1006 disp = 1;
1008 break;
1009 default:
1010 disp = 0;
1011 break;
1013 if (disp) {
1014 if (fcgi_gen_response(c, "<a href='?") == -1)
1015 goto done;
1016 if (fcgi_gen_response(c, nhref) == -1)
1017 goto done;
1018 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1019 goto done;
1021 fcgi_gen_response(c, "</div>\n");
1022 done:
1023 free(t->next_id);
1024 t->next_id = NULL;
1025 free(t->prev_id);
1026 t->prev_id = NULL;
1027 free(phref);
1028 free(nhref);
1029 return error;
1032 static const struct got_error *
1033 gotweb_render_index(struct request *c)
1035 const struct got_error *error = NULL;
1036 struct server *srv = c->srv;
1037 struct transport *t = c->t;
1038 struct querystring *qs = t->qs;
1039 struct repo_dir *repo_dir = NULL;
1040 DIR *d;
1041 struct dirent **sd_dent;
1042 char *c_path = NULL;
1043 struct stat st;
1044 unsigned int d_cnt, d_i, d_disp = 0;
1046 d = opendir(srv->repos_path);
1047 if (d == NULL) {
1048 error = got_error_from_errno2("opendir", srv->repos_path);
1049 return error;
1052 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1053 if (d_cnt == -1) {
1054 error = got_error_from_errno2("scandir", srv->repos_path);
1055 goto done;
1058 /* get total count of repos */
1059 for (d_i = 0; d_i < d_cnt; d_i++) {
1060 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1061 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1062 continue;
1064 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1065 sd_dent[d_i]->d_name) == -1) {
1066 error = got_error_from_errno("asprintf");
1067 return error;
1070 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1071 !got_path_dir_is_empty(c_path))
1072 t->repos_total++;
1073 free(c_path);
1074 c_path = NULL;
1077 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1078 goto done;
1079 if (fcgi_gen_response(c,
1080 "<div id='index_header_project'>Project</div>\n") == -1)
1081 goto done;
1082 if (srv->show_repo_description)
1083 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1084 "Description</div>\n") == -1)
1085 goto done;
1086 if (srv->show_repo_owner)
1087 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1088 "Owner</div>\n") == -1)
1089 goto done;
1090 if (srv->show_repo_age)
1091 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1092 "Last Change</div>\n") == -1)
1093 goto done;
1094 if (fcgi_gen_response(c, "</div>\n") == -1)
1095 goto done;
1097 for (d_i = 0; d_i < d_cnt; d_i++) {
1098 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1099 break; /* account for parent and self */
1101 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1102 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1103 continue;
1105 if (qs->index_page > 0 && (qs->index_page *
1106 srv->max_repos_display) > t->prev_disp) {
1107 t->prev_disp++;
1108 continue;
1111 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1112 if (error)
1113 goto done;
1115 error = gotweb_load_got_path(c, repo_dir);
1116 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1117 error = NULL;
1118 continue;
1120 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1121 goto done;
1123 if (lstat(repo_dir->path, &st) == 0 &&
1124 S_ISDIR(st.st_mode) &&
1125 !got_path_dir_is_empty(repo_dir->path))
1126 goto render;
1127 else {
1128 gotweb_free_repo_dir(repo_dir);
1129 repo_dir = NULL;
1130 continue;
1132 render:
1133 d_disp++;
1134 t->prev_disp++;
1135 if (fcgi_gen_response(c, "<div id='index_wrapper'>\n") == -1)
1136 goto done;
1137 if (fcgi_gen_response(c, "<div id='index_project'>") == -1)
1138 goto done;
1140 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1141 goto done;
1142 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1143 goto done;
1144 if (fcgi_gen_response(c, "&path=") == -1)
1145 goto done;
1146 if (fcgi_gen_response(c, repo_dir->name) == -1)
1147 goto done;
1148 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1149 goto done;
1150 if (fcgi_gen_response(c, repo_dir->name) == -1)
1151 goto done;
1152 if (fcgi_gen_response(c, "</a>") == -1)
1153 goto done;
1155 if (fcgi_gen_response(c, "</div>\n") == -1)
1156 goto done;
1158 if (srv->show_repo_description) {
1159 if (fcgi_gen_response(c,
1160 "<div id='index_project_description'>\n") == -1)
1161 goto done;
1162 if (fcgi_gen_response(c, repo_dir->description) == -1)
1163 goto done;
1164 if (fcgi_gen_response(c, "</div>\n") == -1)
1165 goto done;
1168 if (srv->show_repo_owner) {
1169 if (fcgi_gen_response(c,
1170 "<div id='index_project_owner'>") == -1)
1171 goto done;
1172 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1173 goto done;
1174 if (fcgi_gen_response(c, "</div>\n") == -1)
1175 goto done;
1178 if (srv->show_repo_age) {
1179 if (fcgi_gen_response(c,
1180 "<div id='index_project_age'>") == -1)
1181 goto done;
1182 if (fcgi_gen_response(c, repo_dir->age) == -1)
1183 goto done;
1184 if (fcgi_gen_response(c, "</div>\n") == -1)
1185 goto done;
1188 if (fcgi_gen_response(c, "<div id='navs_wrapper'>") == -1)
1189 goto done;
1190 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1191 goto done;;
1193 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1194 goto done;
1195 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1196 goto done;
1197 if (fcgi_gen_response(c, "&path=") == -1)
1198 goto done;
1199 if (fcgi_gen_response(c, repo_dir->name) == -1)
1200 goto done;
1201 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1202 goto done;
1203 if (fcgi_gen_response(c, "summary") == -1)
1204 goto done;
1205 if (fcgi_gen_response(c, "</a> | ") == -1)
1206 goto done;
1208 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1209 goto done;
1210 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1211 goto done;
1212 if (fcgi_gen_response(c, "&path=") == -1)
1213 goto done;
1214 if (fcgi_gen_response(c, repo_dir->name) == -1)
1215 goto done;
1216 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1217 goto done;
1218 if (fcgi_gen_response(c, "commit briefs") == -1)
1219 goto done;
1220 if (fcgi_gen_response(c, "</a> | ") == -1)
1221 goto done;
1223 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1224 goto done;
1225 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1226 goto done;
1227 if (fcgi_gen_response(c, "&path=") == -1)
1228 goto done;
1229 if (fcgi_gen_response(c, repo_dir->name) == -1)
1230 goto done;
1231 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1232 goto done;
1233 if (fcgi_gen_response(c, "commits") == -1)
1234 goto done;
1235 if (fcgi_gen_response(c, "</a> | ") == -1)
1236 goto done;
1238 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1239 goto done;
1240 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1241 goto done;
1242 if (fcgi_gen_response(c, "&path=") == -1)
1243 goto done;
1244 if (fcgi_gen_response(c, repo_dir->name) == -1)
1245 goto done;
1246 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1247 goto done;
1248 if (fcgi_gen_response(c, "tags") == -1)
1249 goto done;
1250 if (fcgi_gen_response(c, "</a> | ") == -1)
1251 goto done;
1253 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1254 goto done;
1255 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1256 goto done;
1257 if (fcgi_gen_response(c, "&path=") == -1)
1258 goto done;
1259 if (fcgi_gen_response(c, repo_dir->name) == -1)
1260 goto done;
1261 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1262 goto done;
1263 if (fcgi_gen_response(c, "tree") == -1)
1264 goto done;
1265 if (fcgi_gen_response(c, "</a>") == -1)
1266 goto done;
1268 if (fcgi_gen_response(c, "</div>") == -1)
1269 goto done;
1270 if (fcgi_gen_response(c,
1271 "<div id='dotted_line'></div>\n") == -1)
1272 goto done;
1273 if (fcgi_gen_response(c, "</div>\n") == -1)
1274 goto done;
1275 if (fcgi_gen_response(c, "</div>\n") == -1)
1276 goto done;
1278 gotweb_free_repo_dir(repo_dir);
1279 repo_dir = NULL;
1280 error = got_repo_close(t->repo);
1281 if (error)
1282 goto done;
1283 t->next_disp++;
1284 if (d_disp == srv->max_repos_display)
1285 break;
1287 if (srv->max_repos_display == 0)
1288 goto div;
1289 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1290 goto div;
1291 if (t->repos_total <= srv->max_repos ||
1292 t->repos_total <= srv->max_repos_display)
1293 goto div;
1295 error = gotweb_render_navs(c);
1296 if (error)
1297 goto done;
1298 div:
1299 fcgi_gen_response(c, "</div>\n");
1300 done:
1301 if (d != NULL && closedir(d) == EOF && error == NULL)
1302 error = got_error_from_errno("closedir");
1303 return error;
1306 static const struct got_error *
1307 gotweb_render_blame(struct request *c)
1309 const struct got_error *error = NULL;
1310 struct transport *t = c->t;
1311 struct repo_commit *rc = NULL;
1312 char *age = NULL;
1314 error = got_get_repo_commits(c, 1);
1315 if (error)
1316 return error;
1318 rc = TAILQ_FIRST(&t->repo_commits);
1320 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1321 if (error)
1322 goto done;
1324 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1325 goto done;
1326 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1327 goto done;
1328 if (fcgi_gen_response(c, "</div>\n") == -1)
1329 goto done;
1331 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1332 goto done;
1334 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1335 goto done;
1336 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1337 goto done;
1339 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1340 "</div>\n") == -1)
1341 goto done;
1342 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1343 goto done;
1344 if (fcgi_gen_response(c, age ? age : "") == -1)
1345 goto done;
1346 if (fcgi_gen_response(c, "</div>\n") == -1)
1347 goto done;
1349 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1350 "</div>\n") == -1)
1351 goto done;
1352 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1353 goto done;
1354 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1355 goto done;
1356 if (fcgi_gen_response(c, "</div>\n") == -1)
1357 goto done;
1359 if (fcgi_gen_response(c, "</div>\n") == -1)
1360 goto done;
1361 if (fcgi_gen_response(c, "</div>\n") == -1)
1362 goto done;
1364 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1365 goto done;
1366 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1367 goto done;
1369 error = got_output_file_blame(c);
1370 if (error)
1371 goto done;
1373 fcgi_gen_response(c, "</div>\n");
1374 done:
1375 fcgi_gen_response(c, "</div>\n");
1376 return error;
1379 static const struct got_error *
1380 gotweb_render_briefs(struct request *c)
1382 const struct got_error *error = NULL;
1383 struct repo_commit *rc = NULL;
1384 struct server *srv = c->srv;
1385 struct transport *t = c->t;
1386 struct querystring *qs = t->qs;
1387 struct repo_dir *repo_dir = t->repo_dir;
1388 char *smallerthan, *newline;
1389 char *age = NULL;
1391 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1392 goto done;
1393 if (fcgi_gen_response(c,
1394 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1395 goto done;
1396 if (fcgi_gen_response(c, "</div>\n") == -1)
1397 goto done;
1399 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1400 goto done;
1402 if (qs->action == SUMMARY) {
1403 qs->action = BRIEFS;
1404 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1405 } else
1406 error = got_get_repo_commits(c, srv->max_commits_display);
1407 if (error)
1408 goto done;
1410 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1411 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1412 if (error)
1413 goto done;
1414 if (fcgi_gen_response(c, "<div id='briefs_age'>") == -1)
1415 goto done;
1416 if (fcgi_gen_response(c, age ? age : "") == -1)
1417 goto done;
1418 if (fcgi_gen_response(c, "</div>\n") == -1)
1419 goto done;
1421 if (fcgi_gen_response(c, "<div id='briefs_author'>") == -1)
1422 goto done;
1423 smallerthan = strchr(rc->author, '<');
1424 if (smallerthan)
1425 *smallerthan = '\0';
1426 if (fcgi_gen_response(c, rc->author) == -1)
1427 goto done;
1428 if (fcgi_gen_response(c, "</div>\n") == -1)
1429 goto done;
1431 if (fcgi_gen_response(c, "<div id='briefs_log'>") == -1)
1432 goto done;
1433 newline = strchr(rc->commit_msg, '\n');
1434 if (newline)
1435 *newline = '\0';
1437 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1438 goto done;
1439 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1440 goto done;
1441 if (fcgi_gen_response(c, "&path=") == -1)
1442 goto done;
1443 if (fcgi_gen_response(c, repo_dir->name) == -1)
1444 goto done;
1445 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1446 goto done;
1447 if (fcgi_gen_response(c, rc->commit_id) == -1)
1448 goto done;
1449 if (fcgi_gen_response(c, "&headref=") == -1)
1450 goto done;
1451 if (fcgi_gen_response(c, qs->headref) == -1)
1452 goto done;
1453 if (fcgi_gen_response(c, "'>") == -1)
1454 goto done;
1455 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1456 goto done;
1457 if (fcgi_gen_response(c, "</a>") == -1)
1458 goto done;
1459 if (rc->refs_str) {
1460 if (fcgi_gen_response(c,
1461 " <span id='refs_str'>(") == -1)
1462 goto done;
1463 if (fcgi_gen_response(c, rc->refs_str) == -1)
1464 goto done;
1465 if (fcgi_gen_response(c, ")</span>") == -1)
1466 goto done;
1468 if (fcgi_gen_response(c, "</div>\n") == -1)
1469 goto done;
1471 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1472 goto done;
1473 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1474 goto done;
1475 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1476 goto done;
1477 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1478 goto done;
1479 if (fcgi_gen_response(c, "&path=") == -1)
1480 goto done;
1481 if (fcgi_gen_response(c, repo_dir->name) == -1)
1482 goto done;
1483 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1484 goto done;
1485 if (fcgi_gen_response(c, rc->commit_id) == -1)
1486 goto done;
1487 if (fcgi_gen_response(c, "&headref=") == -1)
1488 goto done;
1489 if (fcgi_gen_response(c, qs->headref) == -1)
1490 goto done;
1491 if (fcgi_gen_response(c, "'>") == -1)
1492 goto done;
1493 if (fcgi_gen_response(c, "diff") == -1)
1494 goto done;
1495 if (fcgi_gen_response(c, "</a>") == -1)
1496 goto done;
1498 if (fcgi_gen_response(c, " | ") == -1)
1499 goto done;
1501 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1502 goto done;
1503 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1504 goto done;
1505 if (fcgi_gen_response(c, "&path=") == -1)
1506 goto done;
1507 if (fcgi_gen_response(c, repo_dir->name) == -1)
1508 goto done;
1509 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1510 goto done;
1511 if (fcgi_gen_response(c, rc->commit_id) == -1)
1512 goto done;
1513 if (fcgi_gen_response(c, "&headref=") == -1)
1514 goto done;
1515 if (fcgi_gen_response(c, qs->headref) == -1)
1516 goto done;
1517 if (fcgi_gen_response(c, "'>") == -1)
1518 goto done;
1519 if (fcgi_gen_response(c, "tree") == -1)
1520 goto done;
1521 if (fcgi_gen_response(c, "</a>") == -1)
1522 goto done;
1523 if (fcgi_gen_response(c, "</div>\n") == -1)
1524 goto done;
1525 if (fcgi_gen_response(c, "</div>\n") == -1)
1526 goto done;
1527 if (fcgi_gen_response(c,
1528 "<div id='dotted_line'></div>\n") == -1)
1529 goto done;
1531 free(age);
1532 age = NULL;
1535 if (t->next_id || t->prev_id) {
1536 error = gotweb_render_navs(c);
1537 if (error)
1538 goto done;
1540 fcgi_gen_response(c, "</div>\n");
1541 done:
1542 free(age);
1543 return error;
1546 static const struct got_error *
1547 gotweb_render_commits(struct request *c)
1549 const struct got_error *error = NULL;
1550 struct repo_commit *rc = NULL;
1551 struct server *srv = c->srv;
1552 struct transport *t = c->t;
1553 struct querystring *qs = t->qs;
1554 struct repo_dir *repo_dir = t->repo_dir;
1555 char *age = NULL, *author = NULL;
1556 /* int commit_found = 0; */
1558 if (fcgi_gen_response(c, "<div id='commits_title_wrapper'>\n") == -1)
1559 goto done;
1560 if (fcgi_gen_response(c,
1561 "<div id='commits_title'>Commits</div>\n") == -1)
1562 goto done;
1563 if (fcgi_gen_response(c, "</div>\n") == -1)
1564 goto done;
1566 if (fcgi_gen_response(c, "<div id='commits_content'>\n") == -1)
1567 goto done;
1569 error = got_get_repo_commits(c, srv->max_commits_display);
1570 if (error)
1571 goto done;
1573 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1574 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1575 if (error)
1576 goto done;
1577 error = gotweb_escape_html(&author, rc->author);
1578 if (error)
1579 goto done;
1581 if (fcgi_gen_response(c,
1582 "<div id='commits_header_wrapper'>\n") == -1)
1583 goto done;
1584 if (fcgi_gen_response(c, "<div id='commits_header'>\n") == -1)
1585 goto done;
1588 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
1589 "</div>\n") == -1)
1590 goto done;
1591 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
1592 goto done;
1593 if (fcgi_gen_response(c, rc->commit_id) == -1)
1594 goto done;
1595 if (fcgi_gen_response(c, "</div>\n") == -1)
1596 goto done;
1598 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
1599 "</div>\n") == -1)
1600 goto done;
1601 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
1602 goto done;
1603 if (fcgi_gen_response(c, author ? author : "") == -1)
1604 goto done;
1605 if (fcgi_gen_response(c, "</div>\n") == -1)
1606 goto done;
1608 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1609 "</div>\n") == -1)
1610 goto done;
1611 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1612 goto done;
1613 if (fcgi_gen_response(c, age ? age : "") == -1)
1614 goto done;
1615 if (fcgi_gen_response(c, "</div>\n") == -1)
1616 goto done;
1618 if (fcgi_gen_response(c, "</div>\n") == -1)
1619 goto done;
1620 if (fcgi_gen_response(c, "</div>\n") == -1)
1621 goto done;
1623 if (fcgi_gen_response(c,
1624 "<div id='dotted_line'></div>\n") == -1)
1625 goto done;
1626 if (fcgi_gen_response(c, "<div id='commit'>\n") == -1)
1627 goto done;
1629 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1630 goto done;
1632 if (fcgi_gen_response(c, "</div>\n") == -1)
1633 goto done;
1634 if (fcgi_gen_response(c, "</div>\n") == -1)
1635 goto done;
1637 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1638 goto done;
1639 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1640 goto done;
1641 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1642 goto done;
1643 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1644 goto done;
1645 if (fcgi_gen_response(c, "&path=") == -1)
1646 goto done;
1647 if (fcgi_gen_response(c, repo_dir->name) == -1)
1648 goto done;
1649 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1650 goto done;
1651 if (fcgi_gen_response(c, rc->commit_id) == -1)
1652 goto done;
1653 if (fcgi_gen_response(c, "'>") == -1)
1654 goto done;
1655 if (fcgi_gen_response(c, "diff") == -1)
1656 goto done;
1657 if (fcgi_gen_response(c, "</a>") == -1)
1658 goto done;
1660 if (fcgi_gen_response(c, " | ") == -1)
1661 goto done;
1663 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1664 goto done;
1665 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1666 goto done;
1667 if (fcgi_gen_response(c, "&path=") == -1)
1668 goto done;
1669 if (fcgi_gen_response(c, repo_dir->name) == -1)
1670 goto done;
1671 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1672 goto done;
1673 if (fcgi_gen_response(c, rc->commit_id) == -1)
1674 goto done;
1675 if (fcgi_gen_response(c, "'>") == -1)
1676 goto done;
1677 if (fcgi_gen_response(c, "tree") == -1)
1678 goto done;
1679 if (fcgi_gen_response(c, "</a>") == -1)
1680 goto done;
1681 if (fcgi_gen_response(c, "</div>\n") == -1)
1682 goto done;
1683 if (fcgi_gen_response(c, "</div>\n") == -1)
1684 goto done;
1685 if (fcgi_gen_response(c,
1686 "<div id='dotted_line'></div>\n") == -1)
1687 goto done;
1688 free(age);
1689 age = NULL;
1690 free(author);
1691 author = NULL;
1694 if (t->next_id || t->prev_id) {
1695 error = gotweb_render_navs(c);
1696 if (error)
1697 goto done;
1699 if (fcgi_gen_response(c, "</div>\n") == -1)
1700 goto done;
1701 fcgi_gen_response(c, "</div>\n");
1702 done:
1703 free(age);
1704 return error;
1707 static const struct got_error *
1708 gotweb_render_branches(struct request *c)
1710 const struct got_error *error = NULL;
1711 struct got_reflist_head refs;
1712 struct got_reflist_entry *re;
1713 struct transport *t = c->t;
1714 struct querystring *qs = t->qs;
1715 struct got_repository *repo = t->repo;
1716 char *age = NULL;
1718 TAILQ_INIT(&refs);
1720 error = got_ref_list(&refs, repo, "refs/heads",
1721 got_ref_cmp_by_name, NULL);
1722 if (error)
1723 goto done;
1725 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1726 goto done;
1727 if (fcgi_gen_response(c,
1728 "<div id='branches_title'>Branches</div>\n") == -1)
1729 goto done;
1730 if (fcgi_gen_response(c, "</div>\n") == -1)
1731 goto done;
1733 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1734 goto done;
1736 TAILQ_FOREACH(re, &refs, entry) {
1737 char *refname = NULL;
1739 if (got_ref_is_symbolic(re->ref))
1740 continue;
1742 refname = strdup(got_ref_get_name(re->ref));
1743 if (refname == NULL) {
1744 error = got_error_from_errno("strdup");
1745 goto done;
1747 if (strncmp(refname, "refs/heads/", 11) != 0)
1748 continue;
1750 error = got_get_repo_age(&age, c, qs->path, refname,
1751 TM_DIFF);
1752 if (error)
1753 goto done;
1755 if (strncmp(refname, "refs/heads/", 11) == 0)
1756 refname += 11;
1758 if (fcgi_gen_response(c, "<div id='branches_wrapper'>") == -1)
1759 goto done;
1761 if (fcgi_gen_response(c, "<div id='branches_age'>") == -1)
1762 goto done;
1763 if (fcgi_gen_response(c, age ? age : "") == -1)
1764 goto done;
1765 if (fcgi_gen_response(c, "</div>\n") == -1)
1766 goto done;
1768 if (fcgi_gen_response(c, "<div id='branches_space'>") == -1)
1769 goto done;
1770 if (fcgi_gen_response(c, "&nbsp;") == -1)
1771 goto done;
1772 if (fcgi_gen_response(c, "</div>\n") == -1)
1773 goto done;
1775 if (fcgi_gen_response(c, "<div id='branch'>") == -1)
1776 goto done;
1777 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1778 goto done;
1779 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1780 goto done;
1781 if (fcgi_gen_response(c, "&path=") == -1)
1782 goto done;
1783 if (fcgi_gen_response(c, qs->path) == -1)
1784 goto done;
1785 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1786 goto done;
1787 if (fcgi_gen_response(c, refname) == -1)
1788 goto done;
1789 if (fcgi_gen_response(c, "'>") == -1)
1790 goto done;
1791 if (fcgi_gen_response(c, refname) == -1)
1792 goto done;
1793 if (fcgi_gen_response(c, "</a>") == -1)
1794 goto done;
1795 if (fcgi_gen_response(c, "</div>\n") == -1)
1796 goto done;
1798 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1799 goto done;
1800 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1801 goto done;
1803 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1804 goto done;
1805 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1806 goto done;
1807 if (fcgi_gen_response(c, "&path=") == -1)
1808 goto done;
1809 if (fcgi_gen_response(c, qs->path) == -1)
1810 goto done;
1811 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1812 goto done;
1813 if (fcgi_gen_response(c, refname) == -1)
1814 goto done;
1815 if (fcgi_gen_response(c, "'>") == -1)
1816 goto done;
1817 if (fcgi_gen_response(c, "summary") == -1)
1818 goto done;
1819 if (fcgi_gen_response(c, "</a>") == -1)
1820 goto done;
1822 if (fcgi_gen_response(c, " | ") == -1)
1823 goto done;
1825 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1826 goto done;
1827 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1828 goto done;
1829 if (fcgi_gen_response(c, "&path=") == -1)
1830 goto done;
1831 if (fcgi_gen_response(c, qs->path) == -1)
1832 goto done;
1833 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1834 goto done;
1835 if (fcgi_gen_response(c, refname) == -1)
1836 goto done;
1837 if (fcgi_gen_response(c, "'>") == -1)
1838 goto done;
1839 if (fcgi_gen_response(c, "commit briefs") == -1)
1840 goto done;
1841 if (fcgi_gen_response(c, "</a>") == -1)
1842 goto done;
1844 if (fcgi_gen_response(c, " | ") == -1)
1845 goto done;
1847 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1848 goto done;
1849 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1850 goto done;
1851 if (fcgi_gen_response(c, "&path=") == -1)
1852 goto done;
1853 if (fcgi_gen_response(c, qs->path) == -1)
1854 goto done;
1855 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1856 goto done;
1857 if (fcgi_gen_response(c, refname) == -1)
1858 goto done;
1859 if (fcgi_gen_response(c, "'>") == -1)
1860 goto done;
1861 if (fcgi_gen_response(c, "commits") == -1)
1862 goto done;
1863 if (fcgi_gen_response(c, "</a>") == -1)
1864 goto done;
1866 if (fcgi_gen_response(c, "</div>\n") == -1)
1867 goto done;
1868 if (fcgi_gen_response(c, "</div>\n") == -1)
1869 goto done;
1871 if (fcgi_gen_response(c,
1872 "<div id='dotted_line'></div>\n") == -1)
1873 goto done;
1875 free(age);
1876 age = NULL;
1879 fcgi_gen_response(c, "</div>\n");
1880 done:
1881 return error;
1884 static const struct got_error *
1885 gotweb_render_tree(struct request *c)
1887 const struct got_error *error = NULL;
1888 struct transport *t = c->t;
1889 struct repo_commit *rc = NULL;
1890 char *age = NULL;
1892 error = got_get_repo_commits(c, 1);
1893 if (error)
1894 return error;
1896 rc = TAILQ_FIRST(&t->repo_commits);
1898 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1899 if (error)
1900 goto done;
1902 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1903 goto done;
1904 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1905 goto done;
1906 if (fcgi_gen_response(c, "</div>\n") == -1)
1907 goto done;
1909 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1910 goto done;
1912 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1913 goto done;
1914 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1915 goto done;
1917 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1918 "</div>\n") == -1)
1919 goto done;
1920 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1921 goto done;
1922 if (fcgi_gen_response(c, rc->tree_id) == -1)
1923 goto done;
1924 if (fcgi_gen_response(c, "</div>\n") == -1)
1925 goto done;
1927 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1928 "</div>\n") == -1)
1929 goto done;
1930 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1931 goto done;
1932 if (fcgi_gen_response(c, age ? age : "") == -1)
1933 goto done;
1934 if (fcgi_gen_response(c, "</div>\n") == -1)
1935 goto done;
1937 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1938 "</div>\n") == -1)
1939 goto done;
1940 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1941 goto done;
1942 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1943 goto done;
1944 if (fcgi_gen_response(c, "</div>\n") == -1)
1945 goto done;
1947 if (fcgi_gen_response(c, "</div>\n") == -1)
1948 goto done;
1949 if (fcgi_gen_response(c, "</div>\n") == -1)
1950 goto done;
1952 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1953 goto done;
1954 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1955 goto done;
1957 error = got_output_repo_tree(c);
1958 if (error)
1959 goto done;
1961 fcgi_gen_response(c, "</div>\n");
1962 fcgi_gen_response(c, "</div>\n");
1963 done:
1964 return error;
1967 static const struct got_error *
1968 gotweb_render_diff(struct request *c)
1970 const struct got_error *error = NULL;
1971 struct transport *t = c->t;
1972 struct repo_commit *rc = NULL;
1973 char *age = NULL, *author = NULL;
1975 error = got_get_repo_commits(c, 1);
1976 if (error)
1977 return error;
1979 rc = TAILQ_FIRST(&t->repo_commits);
1981 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1982 if (error)
1983 goto done;
1984 error = gotweb_escape_html(&author, rc->author);
1985 if (error)
1986 goto done;
1988 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1989 goto done;
1990 if (fcgi_gen_response(c,
1991 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1992 goto done;
1993 if (fcgi_gen_response(c, "</div>\n") == -1)
1994 goto done;
1996 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1997 goto done;
1998 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
1999 goto done;
2000 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
2001 goto done;
2003 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2004 "</div>\n") == -1)
2005 goto done;
2006 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2007 goto done;
2008 if (fcgi_gen_response(c, rc->parent_id) == -1)
2009 goto done;
2010 if (fcgi_gen_response(c, "<br />") == -1)
2011 goto done;
2012 if (fcgi_gen_response(c, rc->commit_id) == -1)
2013 goto done;
2014 if (fcgi_gen_response(c, "</div>\n") == -1)
2015 goto done;
2017 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2018 "</div>\n") == -1)
2019 goto done;
2020 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2021 goto done;
2022 if (fcgi_gen_response(c, rc->commit_id) == -1)
2023 goto done;
2024 if (fcgi_gen_response(c, "</div>\n") == -1)
2025 goto done;
2027 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2028 "</div>\n") == -1)
2029 goto done;
2030 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2031 goto done;
2032 if (fcgi_gen_response(c, rc->tree_id) == -1)
2033 goto done;
2034 if (fcgi_gen_response(c, "</div>\n") == -1)
2035 goto done;
2037 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
2038 "</div>\n") == -1)
2039 goto done;
2040 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2041 goto done;
2042 if (fcgi_gen_response(c, author ? author : "") == -1)
2043 goto done;
2044 if (fcgi_gen_response(c, "</div>\n") == -1)
2045 goto done;
2047 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2048 "</div>\n") == -1)
2049 goto done;
2050 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2051 goto done;
2052 if (fcgi_gen_response(c, age ? age : "") == -1)
2053 goto done;
2054 if (fcgi_gen_response(c, "</div>\n") == -1)
2055 goto done;
2057 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2058 "</div>\n") == -1)
2059 goto done;
2060 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2061 goto done;
2062 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2063 goto done;
2064 if (fcgi_gen_response(c, "</div>\n") == -1)
2065 goto done;
2066 if (fcgi_gen_response(c, "</div>\n") == -1)
2067 goto done;
2068 if (fcgi_gen_response(c, "</div>\n") == -1)
2069 goto done;
2071 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2072 goto done;
2073 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2074 goto done;
2076 error = got_output_repo_diff(c);
2077 if (error)
2078 goto done;
2080 fcgi_gen_response(c, "</div>\n");
2081 done:
2082 fcgi_gen_response(c, "</div>\n");
2083 free(age);
2084 free(author);
2085 return error;
2088 static const struct got_error *
2089 gotweb_render_summary(struct request *c)
2091 const struct got_error *error = NULL;
2092 struct transport *t = c->t;
2093 struct server *srv = c->srv;
2095 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2096 goto done;
2098 if (!srv->show_repo_description)
2099 goto owner;
2101 if (fcgi_gen_response(c, "<div id='description_title'>"
2102 "Description:</div>\n") == -1)
2103 goto done;
2104 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2105 goto done;
2106 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2107 goto done;
2108 if (fcgi_gen_response(c, "</div>\n") == -1)
2109 goto done;
2110 owner:
2111 if (!srv->show_repo_owner)
2112 goto last_change;
2114 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2115 "Owner:</div>\n") == -1)
2116 goto done;
2117 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2118 goto done;
2119 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2120 goto done;
2121 if (fcgi_gen_response(c, "</div>\n") == -1)
2122 goto done;
2123 last_change:
2124 if (!srv->show_repo_age)
2125 goto clone_url;
2127 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2128 "Last Change:</div>\n") == -1)
2129 goto done;
2130 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2131 goto done;
2132 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2133 goto done;
2134 if (fcgi_gen_response(c, "</div>\n") == -1)
2135 goto done;
2136 clone_url:
2137 if (!srv->show_repo_cloneurl)
2138 goto content;
2140 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2141 "Clone URL:</div>\n") == -1)
2142 goto done;
2143 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2144 goto done;
2145 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2146 goto done;
2147 if (fcgi_gen_response(c, "</div>\n") == -1)
2148 goto done;
2149 content:
2150 if (fcgi_gen_response(c, "</div>\n") == -1)
2151 goto done;
2152 if (fcgi_gen_response(c, "</div>\n") == -1)
2153 goto done;
2155 error = gotweb_render_briefs(c);
2156 if (error) {
2157 log_warnx("%s: %s", __func__, error->msg);
2158 goto done;
2161 error = gotweb_render_tags(c);
2162 if (error) {
2163 log_warnx("%s: %s", __func__, error->msg);
2164 goto done;
2167 error = gotweb_render_branches(c);
2168 if (error)
2169 log_warnx("%s: %s", __func__, error->msg);
2170 done:
2171 return error;
2174 static const struct got_error *
2175 gotweb_render_tag(struct request *c)
2177 const struct got_error *error = NULL;
2178 struct repo_tag *rt = NULL;
2179 struct transport *t = c->t;
2180 char *age = NULL, *author = NULL;
2182 error = got_get_repo_tags(c, 1);
2183 if (error)
2184 goto done;
2186 if (t->tag_count == 0) {
2187 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2188 "bad commit id");
2189 goto done;
2192 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2194 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2195 if (error)
2196 goto done;
2197 error = gotweb_escape_html(&author, rt->tagger);
2198 if (error)
2199 goto done;
2201 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2202 goto done;
2203 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2204 goto done;
2205 if (fcgi_gen_response(c, "</div>\n") == -1)
2206 goto done;
2208 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2209 goto done;
2210 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2211 goto done;
2212 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2213 goto done;
2215 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2216 "</div>\n") == -1)
2217 goto done;
2218 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2219 goto done;
2220 if (fcgi_gen_response(c, rt->commit_id) == -1)
2221 goto done;
2223 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2224 rt->tag_name += 5;
2226 if (fcgi_gen_response(c, " <span id='refs_str'>(") == -1)
2227 goto done;
2228 if (fcgi_gen_response(c, rt->tag_name) == -1)
2229 goto done;
2230 if (fcgi_gen_response(c, ")</span>") == -1)
2231 goto done;
2233 if (fcgi_gen_response(c, "</div>\n") == -1)
2234 goto done;
2236 if (fcgi_gen_response(c, "<div id='header_author_title'>Tagger:"
2237 "</div>\n") == -1)
2238 goto done;
2239 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2240 goto done;
2241 if (fcgi_gen_response(c, author ? author : "") == -1)
2242 goto done;
2243 if (fcgi_gen_response(c, "</div>\n") == -1)
2244 goto done;
2246 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2247 "</div>\n") == -1)
2248 goto done;
2249 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2250 goto done;
2251 if (fcgi_gen_response(c, age ? age : "") == -1)
2252 goto done;
2253 if (fcgi_gen_response(c, "</div>\n") == -1)
2254 goto done;
2256 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2257 "</div>\n") == -1)
2258 goto done;
2259 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2260 goto done;
2261 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2262 goto done;
2263 if (fcgi_gen_response(c, "</div>\n") == -1)
2264 goto done;
2265 if (fcgi_gen_response(c, "</div>\n") == -1)
2266 goto done;
2268 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2269 goto done;
2270 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2271 goto done;
2273 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2274 goto done;
2276 if (fcgi_gen_response(c, "</div>\n") == -1)
2277 goto done;
2278 fcgi_gen_response(c, "</div>\n");
2279 done:
2280 free(age);
2281 free(author);
2282 return error;
2285 static const struct got_error *
2286 gotweb_render_tags(struct request *c)
2288 const struct got_error *error = NULL;
2289 struct repo_tag *rt = NULL;
2290 struct server *srv = c->srv;
2291 struct transport *t = c->t;
2292 struct querystring *qs = t->qs;
2293 struct repo_dir *repo_dir = t->repo_dir;
2294 char *newline;
2295 char *age = NULL;
2296 int commit_found = 0;
2298 if (qs->action == BRIEFS) {
2299 qs->action = TAGS;
2300 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2301 } else
2302 error = got_get_repo_tags(c, srv->max_commits_display);
2303 if (error)
2304 goto done;
2306 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2307 goto done;
2308 if (fcgi_gen_response(c,
2309 "<div id='tags_title'>Tags</div>\n") == -1)
2310 goto done;
2311 if (fcgi_gen_response(c, "</div>\n") == -1)
2312 goto done;
2314 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2315 goto done;
2317 if (t->tag_count == 0) {
2318 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2319 goto done;
2320 if (fcgi_gen_response(c,
2321 "This repository contains no tags\n") == -1)
2322 goto done;
2323 if (fcgi_gen_response(c, "</div>\n") == -1)
2324 goto done;
2325 if (fcgi_gen_response(c, "</div>\n") == -1)
2326 goto done;
2329 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2330 if (commit_found == 0 && qs->commit != NULL) {
2331 if (strcmp(qs->commit, rt->commit_id) != 0)
2332 continue;
2333 else
2334 commit_found = 1;
2336 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2337 if (error)
2338 goto done;
2339 if (fcgi_gen_response(c, "<div id='tag_age'>") == -1)
2340 goto done;
2341 if (fcgi_gen_response(c, age ? age : "") == -1)
2342 goto done;
2343 if (fcgi_gen_response(c, "</div>\n") == -1)
2344 goto done;
2346 if (fcgi_gen_response(c, "<div id='tag'>") == -1)
2347 goto done;
2348 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2349 rt->tag_name += 10;
2350 if (fcgi_gen_response(c, rt->tag_name) == -1)
2351 goto done;
2352 if (fcgi_gen_response(c, "</div>\n") == -1)
2353 goto done;
2355 if (fcgi_gen_response(c, "<div id='tags_log'>") == -1)
2356 goto done;
2357 if (rt->tag_commit != NULL) {
2358 newline = strchr(rt->tag_commit, '\n');
2359 if (newline)
2360 *newline = '\0';
2363 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2364 goto done;
2365 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2366 goto done;
2367 if (fcgi_gen_response(c, "&path=") == -1)
2368 goto done;
2369 if (fcgi_gen_response(c, repo_dir->name) == -1)
2370 goto done;
2371 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2372 goto done;
2373 if (fcgi_gen_response(c, rt->commit_id) == -1)
2374 goto done;
2375 if (fcgi_gen_response(c, "'>") == -1)
2376 goto done;
2377 if (rt->tag_commit != NULL &&
2378 fcgi_gen_response(c, rt->tag_commit) == -1)
2379 goto done;
2380 if (fcgi_gen_response(c, "</a>") == -1)
2381 goto done;
2382 if (fcgi_gen_response(c, "</div>\n") == -1)
2383 goto done;
2385 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
2386 goto done;
2387 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
2388 goto done;
2390 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2391 goto done;
2392 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2393 goto done;
2394 if (fcgi_gen_response(c, "&path=") == -1)
2395 goto done;
2396 if (fcgi_gen_response(c, repo_dir->name) == -1)
2397 goto done;
2398 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2399 goto done;
2400 if (fcgi_gen_response(c, rt->commit_id) == -1)
2401 goto done;
2402 if (fcgi_gen_response(c, "'>") == -1)
2403 goto done;
2404 if (fcgi_gen_response(c, "tag") == -1)
2405 goto done;
2406 if (fcgi_gen_response(c, "</a>") == -1)
2407 goto done;
2409 if (fcgi_gen_response(c, " | ") == -1)
2410 goto done;
2412 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2413 goto done;
2414 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2415 goto done;
2416 if (fcgi_gen_response(c, "&path=") == -1)
2417 goto done;
2418 if (fcgi_gen_response(c, repo_dir->name) == -1)
2419 goto done;
2420 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2421 goto done;
2422 if (fcgi_gen_response(c, rt->commit_id) == -1)
2423 goto done;
2424 if (fcgi_gen_response(c, "'>") == -1)
2425 goto done;
2426 if (fcgi_gen_response(c, "commit briefs") == -1)
2427 goto done;
2428 if (fcgi_gen_response(c, "</a>") == -1)
2429 goto done;
2431 if (fcgi_gen_response(c, " | ") == -1)
2432 goto done;
2434 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2435 goto done;
2436 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2437 goto done;
2438 if (fcgi_gen_response(c, "&path=") == -1)
2439 goto done;
2440 if (fcgi_gen_response(c, repo_dir->name) == -1)
2441 goto done;
2442 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2443 goto done;
2444 if (fcgi_gen_response(c, rt->commit_id) == -1)
2445 goto done;
2446 if (fcgi_gen_response(c, "'>") == -1)
2447 goto done;
2448 if (fcgi_gen_response(c, "commits") == -1)
2449 goto done;
2450 if (fcgi_gen_response(c, "</a>") == -1)
2451 goto done;
2453 if (fcgi_gen_response(c, "</div>\n") == -1)
2454 goto done;
2455 if (fcgi_gen_response(c, "</div>\n") == -1)
2456 goto done;
2457 if (fcgi_gen_response(c,
2458 "<div id='dotted_line'></div>\n") == -1)
2459 goto done;
2461 free(age);
2462 age = NULL;
2464 if (t->next_id || t->prev_id) {
2465 error = gotweb_render_navs(c);
2466 if (error)
2467 goto done;
2469 fcgi_gen_response(c, "</div>\n");
2470 done:
2471 free(age);
2472 return error;
2475 const struct got_error *
2476 gotweb_escape_html(char **escaped_html, const char *orig_html)
2478 const struct got_error *error = NULL;
2479 struct escape_pair {
2480 char c;
2481 const char *s;
2482 } esc[] = {
2483 { '>', "&gt;" },
2484 { '<', "&lt;" },
2485 { '&', "&amp;" },
2486 { '"', "&quot;" },
2487 { '\'', "&apos;" },
2488 { '\n', "<br />" },
2490 size_t orig_len, len;
2491 int i, j, x;
2493 orig_len = strlen(orig_html);
2494 len = orig_len;
2495 for (i = 0; i < orig_len; i++) {
2496 for (j = 0; j < nitems(esc); j++) {
2497 if (orig_html[i] != esc[j].c)
2498 continue;
2499 len += strlen(esc[j].s) - 1 /* escaped char */;
2503 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2504 if (*escaped_html == NULL)
2505 return got_error_from_errno("calloc");
2507 x = 0;
2508 for (i = 0; i < orig_len; i++) {
2509 int escaped = 0;
2510 for (j = 0; j < nitems(esc); j++) {
2511 if (orig_html[i] != esc[j].c)
2512 continue;
2514 if (strlcat(*escaped_html, esc[j].s, len + 1)
2515 >= len + 1) {
2516 error = got_error(GOT_ERR_NO_SPACE);
2517 goto done;
2519 x += strlen(esc[j].s);
2520 escaped = 1;
2521 break;
2523 if (!escaped) {
2524 (*escaped_html)[x] = orig_html[i];
2525 x++;
2528 done:
2529 if (error) {
2530 free(*escaped_html);
2531 *escaped_html = NULL;
2532 } else {
2533 (*escaped_html)[x] = '\0';
2536 return error;
2539 static const struct got_error *
2540 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2542 const struct got_error *error = NULL;
2543 struct socket *sock = c->sock;
2544 struct server *srv = c->srv;
2545 struct transport *t = c->t;
2546 DIR *dt;
2547 char *dir_test;
2548 int opened = 0;
2550 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2551 GOTWEB_GIT_DIR) == -1)
2552 return got_error_from_errno("asprintf");
2554 dt = opendir(dir_test);
2555 if (dt == NULL) {
2556 free(dir_test);
2557 } else {
2558 repo_dir->path = strdup(dir_test);
2559 if (repo_dir->path == NULL) {
2560 opened = 1;
2561 error = got_error_from_errno("strdup");
2562 goto err;
2564 opened = 1;
2565 goto done;
2568 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2569 GOTWEB_GOT_DIR) == -1) {
2570 dir_test = NULL;
2571 error = got_error_from_errno("asprintf");
2572 goto err;
2575 dt = opendir(dir_test);
2576 if (dt == NULL)
2577 free(dir_test);
2578 else {
2579 opened = 1;
2580 error = got_error(GOT_ERR_NOT_GIT_REPO);
2581 goto err;
2584 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2585 repo_dir->name) == -1) {
2586 error = got_error_from_errno("asprintf");
2587 dir_test = NULL;
2588 goto err;
2591 repo_dir->path = strdup(dir_test);
2592 if (repo_dir->path == NULL) {
2593 opened = 1;
2594 error = got_error_from_errno("strdup");
2595 goto err;
2598 dt = opendir(dir_test);
2599 if (dt == NULL) {
2600 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2601 goto err;
2602 } else
2603 opened = 1;
2604 done:
2605 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2606 if (error)
2607 goto err;
2608 error = gotweb_get_repo_description(&repo_dir->description, srv,
2609 repo_dir->path);
2610 if (error)
2611 goto err;
2612 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2613 if (error)
2614 goto err;
2615 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2616 NULL, TM_DIFF);
2617 if (error)
2618 goto err;
2619 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2620 err:
2621 free(dir_test);
2622 if (opened)
2623 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2624 error = got_error_from_errno("closedir");
2625 return error;
2628 static const struct got_error *
2629 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2631 const struct got_error *error;
2633 *repo_dir = calloc(1, sizeof(**repo_dir));
2634 if (*repo_dir == NULL)
2635 return got_error_from_errno("calloc");
2637 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2638 error = got_error_from_errno("asprintf");
2639 free(*repo_dir);
2640 *repo_dir = NULL;
2641 return error;
2643 (*repo_dir)->owner = NULL;
2644 (*repo_dir)->description = NULL;
2645 (*repo_dir)->url = NULL;
2646 (*repo_dir)->age = NULL;
2647 (*repo_dir)->path = NULL;
2649 return NULL;
2652 static const struct got_error *
2653 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2655 const struct got_error *error = NULL;
2656 FILE *f = NULL;
2657 char *d_file = NULL;
2658 unsigned int len;
2659 size_t n;
2661 *description = NULL;
2662 if (srv->show_repo_description == 0)
2663 return NULL;
2665 if (asprintf(&d_file, "%s/description", dir) == -1)
2666 return got_error_from_errno("asprintf");
2668 f = fopen(d_file, "r");
2669 if (f == NULL) {
2670 if (errno == ENOENT || errno == EACCES)
2671 return NULL;
2672 error = got_error_from_errno2("fopen", d_file);
2673 goto done;
2676 if (fseek(f, 0, SEEK_END) == -1) {
2677 error = got_ferror(f, GOT_ERR_IO);
2678 goto done;
2680 len = ftell(f);
2681 if (len == -1) {
2682 error = got_ferror(f, GOT_ERR_IO);
2683 goto done;
2686 if (len == 0)
2687 goto done;
2689 if (fseek(f, 0, SEEK_SET) == -1) {
2690 error = got_ferror(f, GOT_ERR_IO);
2691 goto done;
2693 *description = calloc(len + 1, sizeof(**description));
2694 if (*description == NULL) {
2695 error = got_error_from_errno("calloc");
2696 goto done;
2699 n = fread(*description, 1, len, f);
2700 if (n == 0 && ferror(f))
2701 error = got_ferror(f, GOT_ERR_IO);
2702 done:
2703 if (f != NULL && fclose(f) == EOF && error == NULL)
2704 error = got_error_from_errno("fclose");
2705 free(d_file);
2706 return error;
2709 static const struct got_error *
2710 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2712 const struct got_error *error = NULL;
2713 FILE *f;
2714 char *d_file = NULL;
2715 unsigned int len;
2716 size_t n;
2718 *url = NULL;
2720 if (srv->show_repo_cloneurl == 0)
2721 return NULL;
2723 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2724 return got_error_from_errno("asprintf");
2726 f = fopen(d_file, "r");
2727 if (f == NULL) {
2728 if (errno != ENOENT && errno != EACCES)
2729 error = got_error_from_errno2("fopen", d_file);
2730 goto done;
2733 if (fseek(f, 0, SEEK_END) == -1) {
2734 error = got_ferror(f, GOT_ERR_IO);
2735 goto done;
2737 len = ftell(f);
2738 if (len == -1) {
2739 error = got_ferror(f, GOT_ERR_IO);
2740 goto done;
2742 if (len == 0)
2743 goto done;
2745 if (fseek(f, 0, SEEK_SET) == -1) {
2746 error = got_ferror(f, GOT_ERR_IO);
2747 goto done;
2750 *url = calloc(len + 1, sizeof(**url));
2751 if (*url == NULL) {
2752 error = got_error_from_errno("calloc");
2753 goto done;
2756 n = fread(*url, 1, len, f);
2757 if (n == 0 && ferror(f))
2758 error = got_ferror(f, GOT_ERR_IO);
2759 done:
2760 if (f != NULL && fclose(f) == EOF && error == NULL)
2761 error = got_error_from_errno("fclose");
2762 free(d_file);
2763 return error;
2766 const struct got_error *
2767 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2769 struct tm tm;
2770 time_t diff_time;
2771 const char *years = "years ago", *months = "months ago";
2772 const char *weeks = "weeks ago", *days = "days ago";
2773 const char *hours = "hours ago", *minutes = "minutes ago";
2774 const char *seconds = "seconds ago", *now = "right now";
2775 char *s;
2776 char datebuf[29];
2778 *repo_age = NULL;
2780 switch (ref_tm) {
2781 case TM_DIFF:
2782 diff_time = time(NULL) - committer_time;
2783 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2784 if (asprintf(repo_age, "%lld %s",
2785 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2786 return got_error_from_errno("asprintf");
2787 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2788 if (asprintf(repo_age, "%lld %s",
2789 (diff_time / 60 / 60 / 24 / (365 / 12)),
2790 months) == -1)
2791 return got_error_from_errno("asprintf");
2792 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2793 if (asprintf(repo_age, "%lld %s",
2794 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2795 return got_error_from_errno("asprintf");
2796 } else if (diff_time > 60 * 60 * 24 * 2) {
2797 if (asprintf(repo_age, "%lld %s",
2798 (diff_time / 60 / 60 / 24), days) == -1)
2799 return got_error_from_errno("asprintf");
2800 } else if (diff_time > 60 * 60 * 2) {
2801 if (asprintf(repo_age, "%lld %s",
2802 (diff_time / 60 / 60), hours) == -1)
2803 return got_error_from_errno("asprintf");
2804 } else if (diff_time > 60 * 2) {
2805 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2806 minutes) == -1)
2807 return got_error_from_errno("asprintf");
2808 } else if (diff_time > 2) {
2809 if (asprintf(repo_age, "%lld %s", diff_time,
2810 seconds) == -1)
2811 return got_error_from_errno("asprintf");
2812 } else {
2813 if (asprintf(repo_age, "%s", now) == -1)
2814 return got_error_from_errno("asprintf");
2816 break;
2817 case TM_LONG:
2818 if (gmtime_r(&committer_time, &tm) == NULL)
2819 return got_error_from_errno("gmtime_r");
2821 s = asctime_r(&tm, datebuf);
2822 if (s == NULL)
2823 return got_error_from_errno("asctime_r");
2825 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2826 return got_error_from_errno("asprintf");
2827 break;
2829 return NULL;