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 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 = got_output_file_blob(c);
180 if (error) {
181 log_warnx("%s: %s", __func__, error->msg);
182 goto err;
184 goto done;
185 } else {
186 render:
187 error = gotweb_render_content_type(c, "text/html");
188 if (error) {
189 log_warnx("%s: %s", __func__, error->msg);
190 goto err;
192 html = 1;
195 error = gotweb_render_header(c);
196 if (error) {
197 log_warnx("%s: %s", __func__, error->msg);
198 goto err;
201 if (error2) {
202 error = error2;
203 goto err;
206 switch(qs->action) {
207 case BLAME:
208 error = gotweb_render_blame(c);
209 if (error) {
210 log_warnx("%s: %s", __func__, error->msg);
211 goto err;
213 break;
214 case BRIEFS:
215 error = gotweb_render_briefs(c);
216 if (error) {
217 log_warnx("%s: %s", __func__, error->msg);
218 goto err;
220 break;
221 case COMMITS:
222 error = gotweb_render_commits(c);
223 if (error) {
224 log_warnx("%s: %s", __func__, error->msg);
225 goto err;
227 break;
228 case DIFF:
229 error = gotweb_render_diff(c);
230 if (error) {
231 log_warnx("%s: %s", __func__, error->msg);
232 goto err;
234 break;
235 case INDEX:
236 error = gotweb_render_index(c);
237 if (error) {
238 log_warnx("%s: %s", __func__, error->msg);
239 goto err;
241 break;
242 case SUMMARY:
243 error = gotweb_render_summary(c);
244 if (error) {
245 log_warnx("%s: %s", __func__, error->msg);
246 goto err;
248 break;
249 case TAG:
250 error = gotweb_render_tag(c);
251 if (error) {
252 log_warnx("%s: %s", __func__, error->msg);
253 goto err;
255 break;
256 case TAGS:
257 error = gotweb_render_tags(c);
258 if (error) {
259 log_warnx("%s: %s", __func__, error->msg);
260 goto err;
262 break;
263 case TREE:
264 error = gotweb_render_tree(c);
265 if (error) {
266 log_warnx("%s: %s", __func__, error->msg);
267 goto err;
269 break;
270 case ERR:
271 default:
272 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
273 goto err;
274 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
275 goto err;
276 if (fcgi_gen_response(c, "</div>\n") == -1)
277 goto err;
278 break;
281 goto done;
282 err:
283 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
284 return;
285 if (fcgi_gen_response(c, err) == -1)
286 return;
287 if (error) {
288 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
289 return;
290 } else {
291 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
292 return;
294 if (html && fcgi_gen_response(c, "</div>\n") == -1)
295 return;
296 done:
297 if (c->t->repo != NULL && qs->action != INDEX)
298 got_repo_close(c->t->repo);
299 if (html && srv != NULL)
300 gotweb_render_footer(c);
303 struct server *
304 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
305 uint8_t *subdomain)
307 struct server *srv = NULL;
309 /* check against document_root first */
310 if (strlen(server_name) > 0)
311 TAILQ_FOREACH(srv, &gotwebd_env->servers, entry)
312 if (strcmp(srv->name, server_name) == 0)
313 goto done;
315 /* check against document_root second */
316 if (strlen(document_root) > 0)
317 TAILQ_FOREACH(srv, &gotwebd_env->servers, entry)
318 if (strcmp(srv->name, document_root) == 0)
319 goto done;
321 /* check against subdomain third */
322 if (strlen(subdomain) > 0)
323 TAILQ_FOREACH(srv, &gotwebd_env->servers, entry)
324 if (strcmp(srv->name, subdomain) == 0)
325 goto done;
327 /* if those fail, send first server */
328 TAILQ_FOREACH(srv, &gotwebd_env->servers, entry)
329 if (srv != NULL)
330 break;
331 done:
332 return srv;
333 };
335 const struct got_error *
336 gotweb_init_transport(struct transport **t)
338 const struct got_error *error = NULL;
340 *t = calloc(1, sizeof(**t));
341 if (*t == NULL)
342 return got_error_from_errno2("%s: calloc", __func__);
344 TAILQ_INIT(&(*t)->repo_commits);
345 TAILQ_INIT(&(*t)->repo_tags);
347 (*t)->repo = NULL;
348 (*t)->repo_dir = NULL;
349 (*t)->qs = NULL;
350 (*t)->next_id = NULL;
351 (*t)->prev_id = NULL;
352 (*t)->next_disp = 0;
353 (*t)->prev_disp = 0;
355 return error;
358 static const struct got_error *
359 gotweb_init_querystring(struct querystring **qs)
361 const struct got_error *error = NULL;
363 *qs = calloc(1, sizeof(**qs));
364 if (*qs == NULL)
365 return got_error_from_errno2("%s: calloc", __func__);
367 (*qs)->action = INDEX;
368 (*qs)->commit = NULL;
369 (*qs)->file = NULL;
370 (*qs)->folder = NULL;
371 (*qs)->headref = strdup("HEAD");
372 if ((*qs)->headref == NULL) {
373 return got_error_from_errno2("%s: strdup", __func__);
375 (*qs)->index_page = 0;
376 (*qs)->index_page_str = NULL;
377 (*qs)->path = NULL;
379 return error;
382 static const struct got_error *
383 gotweb_parse_querystring(struct querystring **qs, char *qst)
385 const struct got_error *error = NULL;
386 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
387 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
389 if (qst == NULL)
390 return error;
392 tok1 = strdup(qst);
393 if (tok1 == NULL)
394 return got_error_from_errno2("%s: strdup", __func__);
396 tok1_pair = tok1;
397 tok1_end = tok1;
399 while (tok1_pair != NULL) {
400 strsep(&tok1_end, "&");
402 tok2 = strdup(tok1_pair);
403 if (tok2 == NULL) {
404 free(tok1);
405 return got_error_from_errno2("%s: strdup", __func__);
408 tok2_pair = tok2;
409 tok2_end = tok2;
411 while (tok2_pair != NULL) {
412 strsep(&tok2_end, "=");
413 if (tok2_end) {
414 error = gotweb_assign_querystring(qs, tok2_pair,
415 tok2_end);
416 if (error)
417 goto err;
419 tok2_pair = tok2_end;
421 free(tok2);
422 tok1_pair = tok1_end;
424 free(tok1);
425 return error;
426 err:
427 free(tok2);
428 free(tok1);
429 return error;
432 static const struct got_error *
433 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
435 const struct got_error *error = NULL;
436 const char *errstr;
437 int a_cnt, el_cnt;
439 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
440 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
441 continue;
443 switch (querystring_keys[el_cnt].element) {
444 case ACTION:
445 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
446 if (strcmp(value, action_keys[a_cnt].name) != 0)
447 continue;
448 else if (strcmp(value,
449 action_keys[a_cnt].name) == 0){
450 (*qs)->action =
451 action_keys[a_cnt].action;
452 goto qa_found;
455 (*qs)->action = ERR;
456 qa_found:
457 break;
458 case COMMIT:
459 (*qs)->commit = strdup(value);
460 if ((*qs)->commit == NULL) {
461 error = got_error_from_errno2("%s: strdup",
462 __func__);
463 goto done;
465 break;
466 case RFILE:
467 (*qs)->file = strdup(value);
468 if ((*qs)->file == NULL) {
469 error = got_error_from_errno2("%s: strdup",
470 __func__);
471 goto done;
473 break;
474 case FOLDER:
475 (*qs)->folder = strdup(value);
476 if ((*qs)->folder == NULL) {
477 error = got_error_from_errno2("%s: strdup",
478 __func__);
479 goto done;
481 break;
482 case HEADREF:
483 (*qs)->headref = strdup(value);
484 if ((*qs)->headref == NULL) {
485 error = got_error_from_errno2("%s: strdup",
486 __func__);
487 goto done;
489 break;
490 case INDEX_PAGE:
491 if (strlen(value) == 0)
492 break;
493 (*qs)->index_page_str = strdup(value);
494 if ((*qs)->index_page_str == NULL) {
495 error = got_error_from_errno2("%s: strdup",
496 __func__);
497 goto done;
499 (*qs)->index_page = strtonum(value, INT64_MIN,
500 INT64_MAX, &errstr);
501 if (errstr) {
502 error = got_error_from_errno3("%s: strtonum %s",
503 __func__, errstr);
504 goto done;
506 if ((*qs)->index_page < 0) {
507 (*qs)->index_page = 0;
508 sprintf((*qs)->index_page_str, "%d", 0);
510 break;
511 case PATH:
512 (*qs)->path = strdup(value);
513 if ((*qs)->path == NULL) {
514 error = got_error_from_errno2("%s: strdup",
515 __func__);
516 goto done;
518 break;
519 case PAGE:
520 if (strlen(value) == 0)
521 break;
522 (*qs)->page_str = strdup(value);
523 if ((*qs)->page_str == NULL) {
524 error = got_error_from_errno2("%s: strdup",
525 __func__);
526 goto done;
528 (*qs)->page = strtonum(value, INT64_MIN,
529 INT64_MAX, &errstr);
530 if (errstr) {
531 error = got_error_from_errno3("%s: strtonum %s",
532 __func__, errstr);
533 goto done;
535 if ((*qs)->page < 0) {
536 (*qs)->page = 0;
537 sprintf((*qs)->page_str, "%d", 0);
539 break;
540 default:
541 break;
544 done:
545 return error;
548 void
549 gotweb_free_repo_tag(struct repo_tag *rt)
551 if (rt != NULL) {
552 free(rt->commit_msg);
553 free(rt->commit_id);
554 free(rt->tagger);
556 free(rt);
559 void
560 gotweb_free_repo_commit(struct repo_commit *rc)
562 if (rc != NULL) {
563 free(rc->path);
564 free(rc->refs_str);
565 free(rc->commit_id);
566 free(rc->parent_id);
567 free(rc->tree_id);
568 free(rc->author);
569 free(rc->committer);
570 free(rc->commit_msg);
572 free(rc);
575 static void
576 gotweb_free_querystring(struct querystring *qs)
578 if (qs != NULL) {
579 free(qs->commit);
580 free(qs->file);
581 free(qs->folder);
582 free(qs->headref);
583 free(qs->index_page_str);
584 free(qs->path);
585 free(qs->page_str);
587 free(qs);
590 static void
591 gotweb_free_repo_dir(struct repo_dir *repo_dir)
593 if (repo_dir != NULL) {
594 free(repo_dir->name);
595 free(repo_dir->owner);
596 free(repo_dir->description);
597 free(repo_dir->url);
598 free(repo_dir->age);
599 free(repo_dir->path);
601 free(repo_dir);
604 void
605 gotweb_free_transport(struct transport *t)
607 struct repo_commit *rc = NULL, *trc = NULL;
608 struct repo_tag *rt = NULL, *trt = NULL;
610 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
611 TAILQ_REMOVE(&t->repo_commits, rc, entry);
612 gotweb_free_repo_commit(rc);
614 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
615 TAILQ_REMOVE(&t->repo_tags, rt, entry);
616 gotweb_free_repo_tag(rt);
618 gotweb_free_repo_dir(t->repo_dir);
619 gotweb_free_querystring(t->qs);
620 if (t != NULL) {
621 free(t->next_id);
622 free(t->prev_id);
624 free(t);
627 const struct got_error *
628 gotweb_render_content_type(struct request *c, const uint8_t *type)
630 const struct got_error *error = NULL;
631 char *h = NULL;
633 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
634 error = got_error_from_errno2("%s: asprintf", __func__);
635 goto done;
638 fcgi_gen_response(c, h);
639 done:
640 free(h);
642 return error;
645 const struct got_error *
646 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
647 char *file)
649 const struct got_error *error = NULL;
650 char *h = NULL;
652 if (asprintf(&h, "Content-type: %s\r\n"
653 "Content-disposition: attachment; filename=%s\r\n\r\n",
654 type, file) == -1) {
655 error = got_error_from_errno2("%s: asprintf", __func__);
656 goto done;
659 fcgi_gen_response(c, h);
660 done:
661 free(h);
663 return error;
666 static const struct got_error *
667 gotweb_render_header(struct request *c)
669 const struct got_error *error = NULL;
670 struct server *srv = c->srv;
671 struct querystring *qs = c->t->qs;
672 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
673 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
675 if (strlen(c->document_root) > 0) {
676 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
677 error = got_error_from_errno2("%s: asprintf", __func__);
678 goto done;
680 } else {
681 if (asprintf(&droot, "/") == -1) {
682 error = got_error_from_errno2("%s: asprintf", __func__);
683 goto done;
687 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
688 error = got_error_from_errno2("%s: asprintf", __func__);
689 goto done;
691 if (asprintf(&css,
692 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
693 droot, srv->custom_css) == -1) {
694 error = got_error_from_errno2("%s: asprintf", __func__);
695 goto done;
697 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
698 srv->logo_url) == -1) {
699 error = got_error_from_errno2("%s: asprintf", __func__);
700 goto done;
702 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
703 droot, srv->logo) == -1) {
704 error = got_error_from_errno2("%s: asprintf", __func__);
705 goto done;
707 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
708 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
709 srv->site_link) == -1) {
710 error = got_error_from_errno2("%s: asprintf", __func__);
711 goto done;
713 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
714 "&action=summary' alt='summlink'>%s</a>", c->document_root,
715 qs->index_page, qs->path, qs->path) == -1) {
716 error = got_error_from_errno2("%s: asprintf", __func__);
717 goto done;
720 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
721 goto done;
722 if (fcgi_gen_response(c, title) == -1)
723 goto done;
724 if (fcgi_gen_response(c, "<meta name='viewport' "
725 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
726 goto done;
727 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
728 goto done;
729 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
730 "content='#da532c'/>\n") == -1)
731 goto done;
732 if (fcgi_gen_response(c,
733 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
734 goto done;
735 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
736 "href='/apple-touch-icon.png'/>\n") == -1)
737 goto done;
738 if (fcgi_gen_response(c,
739 "<link rel='icon' type='image/png' sizes='32x32' "
740 "href='/favicon-32x32.png'/>\n") == -1)
741 goto done;
742 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
743 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
744 goto done;
745 if (fcgi_gen_response(c, "<link rel='manifest' "
746 "href='/site.webmanifest'/>\n") == -1)
747 goto done;
748 if (fcgi_gen_response(c, "<link rel='mask-icon' "
749 "href='/safari-pinned-tab.svg'/>\n") == -1)
750 goto done;
751 if (fcgi_gen_response(c, css) == -1)
752 goto done;
753 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
754 goto done;
755 if (fcgi_gen_response(c,
756 "<div id='header'>\n<div id='got_link'>") == -1)
757 goto done;
758 if (fcgi_gen_response(c, gotlink) == -1)
759 goto done;
760 if (fcgi_gen_response(c, gotimg) == -1)
761 goto done;
762 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
763 goto done;
764 if (fcgi_gen_response(c,
765 "<div id='site_path'>\n<div id='site_link'>") == -1)
766 goto done;
767 if (fcgi_gen_response(c, sitelink) == -1)
768 goto done;
769 if (qs != NULL) {
770 if (qs->path != NULL) {
771 if (fcgi_gen_response(c, " / ") == -1)
772 goto done;
773 if (fcgi_gen_response(c, summlink) == -1)
774 goto done;
776 if (qs->action != INDEX) {
777 if (fcgi_gen_response(c, " / ") == -1)
778 goto done;
779 switch(qs->action) {
780 case(BLAME):
781 if (fcgi_gen_response(c, "blame") == -1)
782 goto done;
783 break;
784 case(BRIEFS):
785 if (fcgi_gen_response(c, "briefs") == -1)
786 goto done;
787 break;
788 case(COMMITS):
789 if (fcgi_gen_response(c, "commits") == -1)
790 goto done;
791 break;
792 case(DIFF):
793 if (fcgi_gen_response(c, "diff") == -1)
794 goto done;
795 break;
796 case(SUMMARY):
797 if (fcgi_gen_response(c, "summary") == -1)
798 goto done;
799 break;
800 case(TAG):
801 if (fcgi_gen_response(c, "tag") == -1)
802 goto done;
803 break;
804 case(TAGS):
805 if (fcgi_gen_response(c, "tags") == -1)
806 goto done;
807 break;
808 case(TREE):
809 if (fcgi_gen_response(c, "tree") == -1)
810 goto done;
811 break;
812 default:
813 break;
818 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
819 done:
820 free(title);
821 free(droot);
822 free(css);
823 free(gotlink);
824 free(gotimg);
825 free(sitelink);
826 free(summlink);
828 return error;
831 static const struct got_error *
832 gotweb_render_footer(struct request *c)
834 const struct got_error *error = NULL;
835 struct server *srv = c->srv;
836 char *siteowner = NULL;
838 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
839 goto done;
840 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
841 goto done;
842 if (srv->show_site_owner) {
843 error = gotweb_escape_html(&siteowner, srv->site_owner);
844 if (error)
845 goto done;
846 if (fcgi_gen_response(c, siteowner) == -1)
847 goto done;
848 } else
849 if (fcgi_gen_response(c, "&nbsp;") == -1)
850 goto done;
851 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
852 done:
853 free(siteowner);
855 return error;
858 static const struct got_error *
859 gotweb_render_navs(struct request *c)
861 const struct got_error *error = NULL;
862 struct transport *t = c->t;
863 struct querystring *qs = t->qs;
864 struct server *srv = c->srv;
865 char *nhref = NULL, *phref = NULL;
866 int disp = 0;
868 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
869 goto done;
870 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
871 goto done;
873 switch(qs->action) {
874 case INDEX:
875 if (qs->index_page > 0) {
876 if (asprintf(&phref, "index_page=%d",
877 qs->index_page - 1) == -1) {
878 error = got_error_from_errno2("%s: asprintf",
879 __func__);
880 goto done;
882 disp = 1;
884 break;
885 case BRIEFS:
886 if (t->prev_id && qs->commit != NULL &&
887 strcmp(qs->commit, t->prev_id) != 0) {
888 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
889 "&action=briefs&commit=%s&headref=%s",
890 qs->index_page, qs->path, qs->page - 1, t->prev_id,
891 qs->headref) == -1) {
892 error = got_error_from_errno2("%s: asprintf",
893 __func__);
894 goto done;
896 disp = 1;
898 break;
899 case COMMITS:
900 if (t->prev_id && qs->commit != NULL &&
901 strcmp(qs->commit, t->prev_id) != 0) {
902 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
903 "&action=commits&commit=%s&headref=%s&folder=%s"
904 "&file=%s",
905 qs->index_page, qs->path, qs->page - 1, t->prev_id,
906 qs->headref, qs->folder ? qs->folder : "",
907 qs->file ? qs->file : "") == -1) {
908 error = got_error_from_errno2("%s: asprintf",
909 __func__);
910 goto done;
912 disp = 1;
914 break;
915 case TAGS:
916 if (t->prev_id && qs->commit != NULL &&
917 strcmp(qs->commit, t->prev_id) != 0) {
918 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
919 "&action=tags&commit=%s&headref=%s",
920 qs->index_page, qs->path, qs->page - 1, t->prev_id,
921 qs->headref) == -1) {
922 error = got_error_from_errno2("%s: asprintf",
923 __func__);
924 goto done;
926 disp = 1;
928 break;
929 default:
930 disp = 0;
931 break;
934 if (disp) {
935 if (fcgi_gen_response(c, "<a href='?") == -1)
936 goto done;
937 if (fcgi_gen_response(c, phref) == -1)
938 goto done;
939 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
940 goto done;
942 if (fcgi_gen_response(c, "</div>\n") == -1)
943 goto done;
944 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
945 goto done;
947 disp = 0;
949 switch(qs->action) {
950 case INDEX:
951 if (t->next_disp == srv->max_repos_display &&
952 t->repos_total != (qs->index_page + 1) *
953 srv->max_repos_display) {
954 if (asprintf(&nhref, "index_page=%d",
955 qs->index_page + 1) == -1) {
956 error = got_error_from_errno2("%s: asprintf",
957 __func__);
958 goto done;
960 disp = 1;
962 break;
963 case BRIEFS:
964 if (t->next_id) {
965 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
966 "&action=briefs&commit=%s&headref=%s",
967 qs->index_page, qs->path, qs->page + 1, t->next_id,
968 qs->headref) == -1) {
969 error = got_error_from_errno2("%s: asprintf",
970 __func__);
971 goto done;
973 disp = 1;
975 break;
976 case COMMITS:
977 if (t->next_id) {
978 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
979 "&action=commits&commit=%s&headref=%s&folder=%s"
980 "&file=%s",
981 qs->index_page, qs->path, qs->page + 1, t->next_id,
982 qs->headref, qs->folder ? qs->folder : "",
983 qs->file ? qs->file : "") == -1) {
984 error = got_error_from_errno2("%s: asprintf",
985 __func__);
986 goto done;
988 disp = 1;
990 break;
991 case TAGS:
992 if (t->next_id) {
993 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
994 "&action=tags&commit=%s&headref=%s",
995 qs->index_page, qs->path, qs->page + 1, t->next_id,
996 qs->headref) == -1) {
997 error = got_error_from_errno2("%s: asprintf",
998 __func__);
999 goto done;
1001 disp = 1;
1003 break;
1004 default:
1005 disp = 0;
1006 break;
1008 if (disp) {
1009 if (fcgi_gen_response(c, "<a href='?") == -1)
1010 goto done;
1011 if (fcgi_gen_response(c, nhref) == -1)
1012 goto done;
1013 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1014 goto done;
1016 fcgi_gen_response(c, "</div>\n");
1017 done:
1018 free(t->next_id);
1019 t->next_id = NULL;
1020 free(t->prev_id);
1021 t->prev_id = NULL;
1022 free(phref);
1023 free(nhref);
1024 return error;
1027 static const struct got_error *
1028 gotweb_render_index(struct request *c)
1030 const struct got_error *error = NULL;
1031 struct server *srv = c->srv;
1032 struct transport *t = c->t;
1033 struct querystring *qs = t->qs;
1034 struct repo_dir *repo_dir = NULL;
1035 DIR *d;
1036 struct dirent **sd_dent;
1037 char *c_path = NULL;
1038 struct stat st;
1039 unsigned int d_cnt, d_i, d_disp = 0;
1041 d = opendir(srv->repos_path);
1042 if (d == NULL) {
1043 error = got_error_from_errno2("opendir", srv->repos_path);
1044 return error;
1047 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1048 if (d_cnt == -1) {
1049 error = got_error_from_errno2("scandir", srv->repos_path);
1050 goto done;
1053 /* get total count of repos */
1054 for (d_i = 0; d_i < d_cnt; d_i++) {
1055 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1056 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1057 continue;
1059 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1060 sd_dent[d_i]->d_name) == -1) {
1061 error = got_error_from_errno("asprintf");
1062 return error;
1065 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1066 !got_path_dir_is_empty(c_path))
1067 t->repos_total++;
1068 free(c_path);
1069 c_path = NULL;
1072 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1073 goto done;
1074 if (fcgi_gen_response(c,
1075 "<div id='index_header_project'>Project</div>\n") == -1)
1076 goto done;
1077 if (srv->show_repo_description)
1078 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1079 "Description</div>\n") == -1)
1080 goto done;
1081 if (srv->show_repo_owner)
1082 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1083 "Owner</div>\n") == -1)
1084 goto done;
1085 if (srv->show_repo_age)
1086 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1087 "Last Change</div>\n") == -1)
1088 goto done;
1089 if (fcgi_gen_response(c, "</div>\n") == -1)
1090 goto done;
1092 for (d_i = 0; d_i < d_cnt; d_i++) {
1093 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1094 break; /* account for parent and self */
1096 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1097 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1098 continue;
1100 if (qs->index_page > 0 && (qs->index_page *
1101 srv->max_repos_display) > t->prev_disp) {
1102 t->prev_disp++;
1103 continue;
1106 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1107 if (error)
1108 goto done;
1110 error = gotweb_load_got_path(c, repo_dir);
1111 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1112 error = NULL;
1113 continue;
1115 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1116 goto done;
1118 if (lstat(repo_dir->path, &st) == 0 &&
1119 S_ISDIR(st.st_mode) &&
1120 !got_path_dir_is_empty(repo_dir->path))
1121 goto render;
1122 else {
1123 gotweb_free_repo_dir(repo_dir);
1124 repo_dir = NULL;
1125 continue;
1127 render:
1128 d_disp++;
1129 t->prev_disp++;
1130 if (fcgi_gen_response(c, "<div class='index_wrapper'>\n") == -1)
1131 goto done;
1132 if (fcgi_gen_response(c, "<div class='index_project'>") == -1)
1133 goto done;
1135 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1136 goto done;
1137 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1138 goto done;
1139 if (fcgi_gen_response(c, "&path=") == -1)
1140 goto done;
1141 if (fcgi_gen_response(c, repo_dir->name) == -1)
1142 goto done;
1143 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1144 goto done;
1145 if (fcgi_gen_response(c, repo_dir->name) == -1)
1146 goto done;
1147 if (fcgi_gen_response(c, "</a>") == -1)
1148 goto done;
1150 if (fcgi_gen_response(c, "</div>\n") == -1)
1151 goto done;
1153 if (srv->show_repo_description) {
1154 if (fcgi_gen_response(c,
1155 "<div class='index_project_description'>\n") == -1)
1156 goto done;
1157 if (fcgi_gen_response(c, repo_dir->description) == -1)
1158 goto done;
1159 if (fcgi_gen_response(c, "</div>\n") == -1)
1160 goto done;
1163 if (srv->show_repo_owner) {
1164 if (fcgi_gen_response(c,
1165 "<div class='index_project_owner'>") == -1)
1166 goto done;
1167 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1168 goto done;
1169 if (fcgi_gen_response(c, "</div>\n") == -1)
1170 goto done;
1173 if (srv->show_repo_age) {
1174 if (fcgi_gen_response(c,
1175 "<div class='index_project_age'>") == -1)
1176 goto done;
1177 if (fcgi_gen_response(c, repo_dir->age) == -1)
1178 goto done;
1179 if (fcgi_gen_response(c, "</div>\n") == -1)
1180 goto done;
1183 if (fcgi_gen_response(c, "<div class='navs_wrapper'>") == -1)
1184 goto done;
1185 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1186 goto done;;
1188 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1189 goto done;
1190 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1191 goto done;
1192 if (fcgi_gen_response(c, "&path=") == -1)
1193 goto done;
1194 if (fcgi_gen_response(c, repo_dir->name) == -1)
1195 goto done;
1196 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1197 goto done;
1198 if (fcgi_gen_response(c, "summary") == -1)
1199 goto done;
1200 if (fcgi_gen_response(c, "</a> | ") == -1)
1201 goto done;
1203 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1204 goto done;
1205 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1206 goto done;
1207 if (fcgi_gen_response(c, "&path=") == -1)
1208 goto done;
1209 if (fcgi_gen_response(c, repo_dir->name) == -1)
1210 goto done;
1211 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1212 goto done;
1213 if (fcgi_gen_response(c, "commit briefs") == -1)
1214 goto done;
1215 if (fcgi_gen_response(c, "</a> | ") == -1)
1216 goto done;
1218 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1219 goto done;
1220 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1221 goto done;
1222 if (fcgi_gen_response(c, "&path=") == -1)
1223 goto done;
1224 if (fcgi_gen_response(c, repo_dir->name) == -1)
1225 goto done;
1226 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1227 goto done;
1228 if (fcgi_gen_response(c, "commits") == -1)
1229 goto done;
1230 if (fcgi_gen_response(c, "</a> | ") == -1)
1231 goto done;
1233 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1234 goto done;
1235 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1236 goto done;
1237 if (fcgi_gen_response(c, "&path=") == -1)
1238 goto done;
1239 if (fcgi_gen_response(c, repo_dir->name) == -1)
1240 goto done;
1241 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1242 goto done;
1243 if (fcgi_gen_response(c, "tags") == -1)
1244 goto done;
1245 if (fcgi_gen_response(c, "</a> | ") == -1)
1246 goto done;
1248 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1249 goto done;
1250 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1251 goto done;
1252 if (fcgi_gen_response(c, "&path=") == -1)
1253 goto done;
1254 if (fcgi_gen_response(c, repo_dir->name) == -1)
1255 goto done;
1256 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1257 goto done;
1258 if (fcgi_gen_response(c, "tree") == -1)
1259 goto done;
1260 if (fcgi_gen_response(c, "</a>") == -1)
1261 goto done;
1263 if (fcgi_gen_response(c, "</div>") == -1)
1264 goto done;
1265 if (fcgi_gen_response(c,
1266 "<div class='dotted_line'></div>\n") == -1)
1267 goto done;
1268 if (fcgi_gen_response(c, "</div>\n") == -1)
1269 goto done;
1270 if (fcgi_gen_response(c, "</div>\n") == -1)
1271 goto done;
1273 gotweb_free_repo_dir(repo_dir);
1274 repo_dir = NULL;
1275 error = got_repo_close(t->repo);
1276 if (error)
1277 goto done;
1278 t->next_disp++;
1279 if (d_disp == srv->max_repos_display)
1280 break;
1282 if (srv->max_repos_display == 0)
1283 goto div;
1284 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1285 goto div;
1286 if (t->repos_total <= srv->max_repos ||
1287 t->repos_total <= srv->max_repos_display)
1288 goto div;
1290 error = gotweb_render_navs(c);
1291 if (error)
1292 goto done;
1293 div:
1294 fcgi_gen_response(c, "</div>\n");
1295 done:
1296 if (d != NULL && closedir(d) == EOF && error == NULL)
1297 error = got_error_from_errno("closedir");
1298 return error;
1301 static const struct got_error *
1302 gotweb_render_blame(struct request *c)
1304 const struct got_error *error = NULL;
1305 struct transport *t = c->t;
1306 struct repo_commit *rc = NULL;
1307 char *age = NULL;
1309 error = got_get_repo_commits(c, 1);
1310 if (error)
1311 return error;
1313 rc = TAILQ_FIRST(&t->repo_commits);
1315 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1316 if (error)
1317 goto done;
1319 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1320 goto done;
1321 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1322 goto done;
1323 if (fcgi_gen_response(c, "</div>\n") == -1)
1324 goto done;
1326 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1327 goto done;
1329 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1330 goto done;
1331 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1332 goto done;
1334 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
1335 "</div>\n") == -1)
1336 goto done;
1337 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
1338 goto done;
1339 if (fcgi_gen_response(c, age ? age : "") == -1)
1340 goto done;
1341 if (fcgi_gen_response(c, "</div>\n") == -1)
1342 goto done;
1344 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1345 "</div>\n") == -1)
1346 goto done;
1347 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1348 goto done;
1349 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1350 goto done;
1351 if (fcgi_gen_response(c, "</div>\n") == -1)
1352 goto done;
1354 if (fcgi_gen_response(c, "</div>\n") == -1)
1355 goto done;
1356 if (fcgi_gen_response(c, "</div>\n") == -1)
1357 goto done;
1359 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
1360 goto done;
1361 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1362 goto done;
1364 error = got_output_file_blame(c);
1365 if (error)
1366 goto done;
1368 fcgi_gen_response(c, "</div>\n");
1369 done:
1370 fcgi_gen_response(c, "</div>\n");
1371 return error;
1374 static const struct got_error *
1375 gotweb_render_briefs(struct request *c)
1377 const struct got_error *error = NULL;
1378 struct repo_commit *rc = NULL;
1379 struct server *srv = c->srv;
1380 struct transport *t = c->t;
1381 struct querystring *qs = t->qs;
1382 struct repo_dir *repo_dir = t->repo_dir;
1383 char *smallerthan, *newline;
1384 char *age = NULL;
1386 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1387 goto done;
1388 if (fcgi_gen_response(c,
1389 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1390 goto done;
1391 if (fcgi_gen_response(c, "</div>\n") == -1)
1392 goto done;
1394 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1395 goto done;
1397 if (qs->action == SUMMARY) {
1398 qs->action = BRIEFS;
1399 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1400 } else
1401 error = got_get_repo_commits(c, srv->max_commits_display);
1402 if (error)
1403 goto done;
1405 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1406 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1407 if (error)
1408 goto done;
1409 if (fcgi_gen_response(c, "<div class='briefs_age'>") == -1)
1410 goto done;
1411 if (fcgi_gen_response(c, age ? age : "") == -1)
1412 goto done;
1413 if (fcgi_gen_response(c, "</div>\n") == -1)
1414 goto done;
1416 if (fcgi_gen_response(c, "<div class='briefs_author'>") == -1)
1417 goto done;
1418 smallerthan = strchr(rc->author, '<');
1419 if (smallerthan)
1420 *smallerthan = '\0';
1421 if (fcgi_gen_response(c, rc->author) == -1)
1422 goto done;
1423 if (fcgi_gen_response(c, "</div>\n") == -1)
1424 goto done;
1426 if (fcgi_gen_response(c, "<div class='briefs_log'>") == -1)
1427 goto done;
1428 newline = strchr(rc->commit_msg, '\n');
1429 if (newline)
1430 *newline = '\0';
1432 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1433 goto done;
1434 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1435 goto done;
1436 if (fcgi_gen_response(c, "&path=") == -1)
1437 goto done;
1438 if (fcgi_gen_response(c, repo_dir->name) == -1)
1439 goto done;
1440 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1441 goto done;
1442 if (fcgi_gen_response(c, rc->commit_id) == -1)
1443 goto done;
1444 if (fcgi_gen_response(c, "&headref=") == -1)
1445 goto done;
1446 if (fcgi_gen_response(c, qs->headref) == -1)
1447 goto done;
1448 if (fcgi_gen_response(c, "'>") == -1)
1449 goto done;
1450 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1451 goto done;
1452 if (fcgi_gen_response(c, "</a>") == -1)
1453 goto done;
1454 if (rc->refs_str) {
1455 if (fcgi_gen_response(c,
1456 " <span class='refs_str'>(") == -1)
1457 goto done;
1458 if (fcgi_gen_response(c, rc->refs_str) == -1)
1459 goto done;
1460 if (fcgi_gen_response(c, ")</span>") == -1)
1461 goto done;
1463 if (fcgi_gen_response(c, "</div>\n") == -1)
1464 goto done;
1466 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
1467 goto done;
1468 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1469 goto done;
1470 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1471 goto done;
1472 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1473 goto done;
1474 if (fcgi_gen_response(c, "&path=") == -1)
1475 goto done;
1476 if (fcgi_gen_response(c, repo_dir->name) == -1)
1477 goto done;
1478 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1479 goto done;
1480 if (fcgi_gen_response(c, rc->commit_id) == -1)
1481 goto done;
1482 if (fcgi_gen_response(c, "&headref=") == -1)
1483 goto done;
1484 if (fcgi_gen_response(c, qs->headref) == -1)
1485 goto done;
1486 if (fcgi_gen_response(c, "'>") == -1)
1487 goto done;
1488 if (fcgi_gen_response(c, "diff") == -1)
1489 goto done;
1490 if (fcgi_gen_response(c, "</a>") == -1)
1491 goto done;
1493 if (fcgi_gen_response(c, " | ") == -1)
1494 goto done;
1496 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1497 goto done;
1498 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1499 goto done;
1500 if (fcgi_gen_response(c, "&path=") == -1)
1501 goto done;
1502 if (fcgi_gen_response(c, repo_dir->name) == -1)
1503 goto done;
1504 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1505 goto done;
1506 if (fcgi_gen_response(c, rc->commit_id) == -1)
1507 goto done;
1508 if (fcgi_gen_response(c, "&headref=") == -1)
1509 goto done;
1510 if (fcgi_gen_response(c, qs->headref) == -1)
1511 goto done;
1512 if (fcgi_gen_response(c, "'>") == -1)
1513 goto done;
1514 if (fcgi_gen_response(c, "tree") == -1)
1515 goto done;
1516 if (fcgi_gen_response(c, "</a>") == -1)
1517 goto done;
1518 if (fcgi_gen_response(c, "</div>\n") == -1)
1519 goto done;
1520 if (fcgi_gen_response(c, "</div>\n") == -1)
1521 goto done;
1522 if (fcgi_gen_response(c,
1523 "<div class='dotted_line'></div>\n") == -1)
1524 goto done;
1526 free(age);
1527 age = NULL;
1530 if (t->next_id || t->prev_id) {
1531 error = gotweb_render_navs(c);
1532 if (error)
1533 goto done;
1535 fcgi_gen_response(c, "</div>\n");
1536 done:
1537 free(age);
1538 return error;
1541 static const struct got_error *
1542 gotweb_render_commits(struct request *c)
1544 const struct got_error *error = NULL;
1545 struct repo_commit *rc = NULL;
1546 struct server *srv = c->srv;
1547 struct transport *t = c->t;
1548 struct querystring *qs = t->qs;
1549 struct repo_dir *repo_dir = t->repo_dir;
1550 char *age = NULL, *author = NULL;
1551 /* int commit_found = 0; */
1553 if (fcgi_gen_response(c, "<div class='commits_title_wrapper'>\n") == -1)
1554 goto done;
1555 if (fcgi_gen_response(c,
1556 "<div class='commits_title'>Commits</div>\n") == -1)
1557 goto done;
1558 if (fcgi_gen_response(c, "</div>\n") == -1)
1559 goto done;
1561 if (fcgi_gen_response(c, "<div class='commits_content'>\n") == -1)
1562 goto done;
1564 error = got_get_repo_commits(c, srv->max_commits_display);
1565 if (error)
1566 goto done;
1568 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1569 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1570 if (error)
1571 goto done;
1572 error = gotweb_escape_html(&author, rc->author);
1573 if (error)
1574 goto done;
1576 if (fcgi_gen_response(c,
1577 "<div class='commits_header_wrapper'>\n") == -1)
1578 goto done;
1579 if (fcgi_gen_response(c, "<div class='commits_header'>\n") == -1)
1580 goto done;
1583 if (fcgi_gen_response(c, "<div class='header_commit_title'>Commit:"
1584 "</div>\n") == -1)
1585 goto done;
1586 if (fcgi_gen_response(c, "<div class='header_commit'>") == -1)
1587 goto done;
1588 if (fcgi_gen_response(c, rc->commit_id) == -1)
1589 goto done;
1590 if (fcgi_gen_response(c, "</div>\n") == -1)
1591 goto done;
1593 if (fcgi_gen_response(c, "<div class='header_author_title'>Author:"
1594 "</div>\n") == -1)
1595 goto done;
1596 if (fcgi_gen_response(c, "<div class='header_author'>") == -1)
1597 goto done;
1598 if (fcgi_gen_response(c, author ? author : "") == -1)
1599 goto done;
1600 if (fcgi_gen_response(c, "</div>\n") == -1)
1601 goto done;
1603 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
1604 "</div>\n") == -1)
1605 goto done;
1606 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
1607 goto done;
1608 if (fcgi_gen_response(c, age ? age : "") == -1)
1609 goto done;
1610 if (fcgi_gen_response(c, "</div>\n") == -1)
1611 goto done;
1613 if (fcgi_gen_response(c, "</div>\n") == -1)
1614 goto done;
1615 if (fcgi_gen_response(c, "</div>\n") == -1)
1616 goto done;
1618 if (fcgi_gen_response(c,
1619 "<div class='dotted_line'></div>\n") == -1)
1620 goto done;
1621 if (fcgi_gen_response(c, "<div class='commit'>\n") == -1)
1622 goto done;
1624 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1625 goto done;
1627 if (fcgi_gen_response(c, "</div>\n") == -1)
1628 goto done;
1629 if (fcgi_gen_response(c, "</div>\n") == -1)
1630 goto done;
1632 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
1633 goto done;
1634 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1635 goto done;
1636 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1637 goto done;
1638 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1639 goto done;
1640 if (fcgi_gen_response(c, "&path=") == -1)
1641 goto done;
1642 if (fcgi_gen_response(c, repo_dir->name) == -1)
1643 goto done;
1644 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1645 goto done;
1646 if (fcgi_gen_response(c, rc->commit_id) == -1)
1647 goto done;
1648 if (fcgi_gen_response(c, "'>") == -1)
1649 goto done;
1650 if (fcgi_gen_response(c, "diff") == -1)
1651 goto done;
1652 if (fcgi_gen_response(c, "</a>") == -1)
1653 goto done;
1655 if (fcgi_gen_response(c, " | ") == -1)
1656 goto done;
1658 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1659 goto done;
1660 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1661 goto done;
1662 if (fcgi_gen_response(c, "&path=") == -1)
1663 goto done;
1664 if (fcgi_gen_response(c, repo_dir->name) == -1)
1665 goto done;
1666 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1667 goto done;
1668 if (fcgi_gen_response(c, rc->commit_id) == -1)
1669 goto done;
1670 if (fcgi_gen_response(c, "'>") == -1)
1671 goto done;
1672 if (fcgi_gen_response(c, "tree") == -1)
1673 goto done;
1674 if (fcgi_gen_response(c, "</a>") == -1)
1675 goto done;
1676 if (fcgi_gen_response(c, "</div>\n") == -1)
1677 goto done;
1678 if (fcgi_gen_response(c, "</div>\n") == -1)
1679 goto done;
1680 if (fcgi_gen_response(c,
1681 "<div class='dotted_line'></div>\n") == -1)
1682 goto done;
1683 free(age);
1684 age = NULL;
1685 free(author);
1686 author = NULL;
1689 if (t->next_id || t->prev_id) {
1690 error = gotweb_render_navs(c);
1691 if (error)
1692 goto done;
1694 if (fcgi_gen_response(c, "</div>\n") == -1)
1695 goto done;
1696 done:
1697 free(age);
1698 return error;
1701 static const struct got_error *
1702 gotweb_render_branches(struct request *c)
1704 const struct got_error *error = NULL;
1705 struct got_reflist_head refs;
1706 struct got_reflist_entry *re;
1707 struct transport *t = c->t;
1708 struct querystring *qs = t->qs;
1709 struct got_repository *repo = t->repo;
1710 char *age = NULL;
1712 TAILQ_INIT(&refs);
1714 error = got_ref_list(&refs, repo, "refs/heads",
1715 got_ref_cmp_by_name, NULL);
1716 if (error)
1717 goto done;
1719 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1720 goto done;
1721 if (fcgi_gen_response(c,
1722 "<div id='branches_title'>Branches</div>\n") == -1)
1723 goto done;
1724 if (fcgi_gen_response(c, "</div>\n") == -1)
1725 goto done;
1727 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1728 goto done;
1730 TAILQ_FOREACH(re, &refs, entry) {
1731 char *refname = NULL;
1733 if (got_ref_is_symbolic(re->ref))
1734 continue;
1736 refname = strdup(got_ref_get_name(re->ref));
1737 if (refname == NULL) {
1738 error = got_error_from_errno("strdup");
1739 goto done;
1741 if (strncmp(refname, "refs/heads/", 11) != 0)
1742 continue;
1744 error = got_get_repo_age(&age, c, qs->path, refname,
1745 TM_DIFF);
1746 if (error)
1747 goto done;
1749 if (strncmp(refname, "refs/heads/", 11) == 0)
1750 refname += 11;
1752 if (fcgi_gen_response(c, "<div class='branches_wrapper'>") == -1)
1753 goto done;
1755 if (fcgi_gen_response(c, "<div class='branches_age'>") == -1)
1756 goto done;
1757 if (fcgi_gen_response(c, age ? age : "") == -1)
1758 goto done;
1759 if (fcgi_gen_response(c, "</div>\n") == -1)
1760 goto done;
1762 if (fcgi_gen_response(c, "<div class='branches_space'>") == -1)
1763 goto done;
1764 if (fcgi_gen_response(c, "&nbsp;") == -1)
1765 goto done;
1766 if (fcgi_gen_response(c, "</div>\n") == -1)
1767 goto done;
1769 if (fcgi_gen_response(c, "<div class='branch'>") == -1)
1770 goto done;
1771 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1772 goto done;
1773 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1774 goto done;
1775 if (fcgi_gen_response(c, "&path=") == -1)
1776 goto done;
1777 if (fcgi_gen_response(c, qs->path) == -1)
1778 goto done;
1779 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1780 goto done;
1781 if (fcgi_gen_response(c, refname) == -1)
1782 goto done;
1783 if (fcgi_gen_response(c, "'>") == -1)
1784 goto done;
1785 if (fcgi_gen_response(c, refname) == -1)
1786 goto done;
1787 if (fcgi_gen_response(c, "</a>") == -1)
1788 goto done;
1789 if (fcgi_gen_response(c, "</div>\n") == -1)
1790 goto done;
1792 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
1793 goto done;
1794 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
1795 goto done;
1797 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1798 goto done;
1799 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1800 goto done;
1801 if (fcgi_gen_response(c, "&path=") == -1)
1802 goto done;
1803 if (fcgi_gen_response(c, qs->path) == -1)
1804 goto done;
1805 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1806 goto done;
1807 if (fcgi_gen_response(c, refname) == -1)
1808 goto done;
1809 if (fcgi_gen_response(c, "'>") == -1)
1810 goto done;
1811 if (fcgi_gen_response(c, "summary") == -1)
1812 goto done;
1813 if (fcgi_gen_response(c, "</a>") == -1)
1814 goto done;
1816 if (fcgi_gen_response(c, " | ") == -1)
1817 goto done;
1819 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1820 goto done;
1821 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1822 goto done;
1823 if (fcgi_gen_response(c, "&path=") == -1)
1824 goto done;
1825 if (fcgi_gen_response(c, qs->path) == -1)
1826 goto done;
1827 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1828 goto done;
1829 if (fcgi_gen_response(c, refname) == -1)
1830 goto done;
1831 if (fcgi_gen_response(c, "'>") == -1)
1832 goto done;
1833 if (fcgi_gen_response(c, "commit briefs") == -1)
1834 goto done;
1835 if (fcgi_gen_response(c, "</a>") == -1)
1836 goto done;
1838 if (fcgi_gen_response(c, " | ") == -1)
1839 goto done;
1841 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1842 goto done;
1843 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1844 goto done;
1845 if (fcgi_gen_response(c, "&path=") == -1)
1846 goto done;
1847 if (fcgi_gen_response(c, qs->path) == -1)
1848 goto done;
1849 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1850 goto done;
1851 if (fcgi_gen_response(c, refname) == -1)
1852 goto done;
1853 if (fcgi_gen_response(c, "'>") == -1)
1854 goto done;
1855 if (fcgi_gen_response(c, "commits") == -1)
1856 goto done;
1857 if (fcgi_gen_response(c, "</a>") == -1)
1858 goto done;
1860 if (fcgi_gen_response(c, "</div>\n") == -1)
1861 goto done;
1862 if (fcgi_gen_response(c, "</div>\n") == -1)
1863 goto done;
1865 if (fcgi_gen_response(c,
1866 "<div class='dotted_line'></div>\n") == -1)
1867 goto done;
1869 /* branches_wrapper */
1870 if (fcgi_gen_response(c, "</div>\n") == -1)
1871 goto done;
1873 free(age);
1874 age = NULL;
1877 fcgi_gen_response(c, "</div>\n");
1878 done:
1879 return error;
1882 static const struct got_error *
1883 gotweb_render_tree(struct request *c)
1885 const struct got_error *error = NULL;
1886 struct transport *t = c->t;
1887 struct repo_commit *rc = NULL;
1888 char *age = NULL;
1890 error = got_get_repo_commits(c, 1);
1891 if (error)
1892 return error;
1894 rc = TAILQ_FIRST(&t->repo_commits);
1896 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1897 if (error)
1898 goto done;
1900 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1901 goto done;
1902 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1903 goto done;
1904 if (fcgi_gen_response(c, "</div>\n") == -1)
1905 goto done;
1907 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1908 goto done;
1910 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1911 goto done;
1912 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1913 goto done;
1915 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1916 "</div>\n") == -1)
1917 goto done;
1918 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1919 goto done;
1920 if (fcgi_gen_response(c, rc->tree_id) == -1)
1921 goto done;
1922 if (fcgi_gen_response(c, "</div>\n") == -1)
1923 goto done;
1925 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
1926 "</div>\n") == -1)
1927 goto done;
1928 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
1929 goto done;
1930 if (fcgi_gen_response(c, age ? age : "") == -1)
1931 goto done;
1932 if (fcgi_gen_response(c, "</div>\n") == -1)
1933 goto done;
1935 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1936 "</div>\n") == -1)
1937 goto done;
1938 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1939 goto done;
1940 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1941 goto done;
1942 if (fcgi_gen_response(c, "</div>\n") == -1)
1943 goto done;
1945 if (fcgi_gen_response(c, "</div>\n") == -1)
1946 goto done;
1947 if (fcgi_gen_response(c, "</div>\n") == -1)
1948 goto done;
1950 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
1951 goto done;
1952 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1953 goto done;
1955 error = got_output_repo_tree(c);
1956 if (error)
1957 goto done;
1959 fcgi_gen_response(c, "</div>\n");
1960 fcgi_gen_response(c, "</div>\n");
1961 done:
1962 return error;
1965 static const struct got_error *
1966 gotweb_render_diff(struct request *c)
1968 const struct got_error *error = NULL;
1969 struct transport *t = c->t;
1970 struct repo_commit *rc = NULL;
1971 char *age = NULL, *author = NULL;
1973 error = got_get_repo_commits(c, 1);
1974 if (error)
1975 return error;
1977 rc = TAILQ_FIRST(&t->repo_commits);
1979 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1980 if (error)
1981 goto done;
1982 error = gotweb_escape_html(&author, rc->author);
1983 if (error)
1984 goto done;
1986 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1987 goto done;
1988 if (fcgi_gen_response(c,
1989 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1990 goto done;
1991 if (fcgi_gen_response(c, "</div>\n") == -1)
1992 goto done;
1994 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1995 goto done;
1996 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
1997 goto done;
1998 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
1999 goto done;
2001 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2002 "</div>\n") == -1)
2003 goto done;
2004 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2005 goto done;
2006 if (fcgi_gen_response(c, rc->parent_id) == -1)
2007 goto done;
2008 if (fcgi_gen_response(c, "<br />") == -1)
2009 goto done;
2010 if (fcgi_gen_response(c, rc->commit_id) == -1)
2011 goto done;
2012 if (fcgi_gen_response(c, "</div>\n") == -1)
2013 goto done;
2015 if (fcgi_gen_response(c, "<div class='header_commit_title'>Commit:"
2016 "</div>\n") == -1)
2017 goto done;
2018 if (fcgi_gen_response(c, "<div class='header_commit'>") == -1)
2019 goto done;
2020 if (fcgi_gen_response(c, rc->commit_id) == -1)
2021 goto done;
2022 if (fcgi_gen_response(c, "</div>\n") == -1)
2023 goto done;
2025 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2026 "</div>\n") == -1)
2027 goto done;
2028 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2029 goto done;
2030 if (fcgi_gen_response(c, rc->tree_id) == -1)
2031 goto done;
2032 if (fcgi_gen_response(c, "</div>\n") == -1)
2033 goto done;
2035 if (fcgi_gen_response(c, "<div class='header_author_title'>Author:"
2036 "</div>\n") == -1)
2037 goto done;
2038 if (fcgi_gen_response(c, "<div class='header_author'>") == -1)
2039 goto done;
2040 if (fcgi_gen_response(c, author ? author : "") == -1)
2041 goto done;
2042 if (fcgi_gen_response(c, "</div>\n") == -1)
2043 goto done;
2045 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
2046 "</div>\n") == -1)
2047 goto done;
2048 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
2049 goto done;
2050 if (fcgi_gen_response(c, age ? age : "") == -1)
2051 goto done;
2052 if (fcgi_gen_response(c, "</div>\n") == -1)
2053 goto done;
2055 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2056 "</div>\n") == -1)
2057 goto done;
2058 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2059 goto done;
2060 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2061 goto done;
2062 if (fcgi_gen_response(c, "</div>\n") == -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;
2069 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
2070 goto done;
2071 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2072 goto done;
2074 error = got_output_repo_diff(c);
2075 if (error)
2076 goto done;
2078 fcgi_gen_response(c, "</div>\n");
2079 done:
2080 fcgi_gen_response(c, "</div>\n");
2081 free(age);
2082 free(author);
2083 return error;
2086 static const struct got_error *
2087 gotweb_render_summary(struct request *c)
2089 const struct got_error *error = NULL;
2090 struct transport *t = c->t;
2091 struct server *srv = c->srv;
2093 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2094 goto done;
2096 if (!srv->show_repo_description)
2097 goto owner;
2099 if (fcgi_gen_response(c, "<div id='description_title'>"
2100 "Description:</div>\n") == -1)
2101 goto done;
2102 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2103 goto done;
2104 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2105 goto done;
2106 if (fcgi_gen_response(c, "</div>\n") == -1)
2107 goto done;
2108 owner:
2109 if (!srv->show_repo_owner)
2110 goto last_change;
2112 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2113 "Owner:</div>\n") == -1)
2114 goto done;
2115 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2116 goto done;
2117 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2118 goto done;
2119 if (fcgi_gen_response(c, "</div>\n") == -1)
2120 goto done;
2121 last_change:
2122 if (!srv->show_repo_age)
2123 goto clone_url;
2125 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2126 "Last Change:</div>\n") == -1)
2127 goto done;
2128 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2129 goto done;
2130 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2131 goto done;
2132 if (fcgi_gen_response(c, "</div>\n") == -1)
2133 goto done;
2134 clone_url:
2135 if (!srv->show_repo_cloneurl)
2136 goto content;
2138 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2139 "Clone URL:</div>\n") == -1)
2140 goto done;
2141 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2142 goto done;
2143 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2144 goto done;
2145 if (fcgi_gen_response(c, "</div>\n") == -1)
2146 goto done;
2147 content:
2148 if (fcgi_gen_response(c, "</div>\n") == -1)
2149 goto done;
2150 if (fcgi_gen_response(c, "</div>\n") == -1)
2151 goto done;
2153 error = gotweb_render_briefs(c);
2154 if (error) {
2155 log_warnx("%s: %s", __func__, error->msg);
2156 goto done;
2159 error = gotweb_render_tags(c);
2160 if (error) {
2161 log_warnx("%s: %s", __func__, error->msg);
2162 goto done;
2165 error = gotweb_render_branches(c);
2166 if (error)
2167 log_warnx("%s: %s", __func__, error->msg);
2168 done:
2169 return error;
2172 static const struct got_error *
2173 gotweb_render_tag(struct request *c)
2175 const struct got_error *error = NULL;
2176 struct repo_tag *rt = NULL;
2177 struct transport *t = c->t;
2178 char *age = NULL, *author = NULL;
2180 error = got_get_repo_tags(c, 1);
2181 if (error)
2182 goto done;
2184 if (t->tag_count == 0) {
2185 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2186 "bad commit id");
2187 goto done;
2190 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2192 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2193 if (error)
2194 goto done;
2195 error = gotweb_escape_html(&author, rt->tagger);
2196 if (error)
2197 goto done;
2199 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2200 goto done;
2201 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2202 goto done;
2203 if (fcgi_gen_response(c, "</div>\n") == -1)
2204 goto done;
2206 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2207 goto done;
2208 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2209 goto done;
2210 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2211 goto done;
2213 if (fcgi_gen_response(c, "<div class='header_commit_title'>Commit:"
2214 "</div>\n") == -1)
2215 goto done;
2216 if (fcgi_gen_response(c, "<div class='header_commit'>") == -1)
2217 goto done;
2218 if (fcgi_gen_response(c, rt->commit_id) == -1)
2219 goto done;
2221 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2222 rt->tag_name += 5;
2224 if (fcgi_gen_response(c, " <span class='refs_str'>(") == -1)
2225 goto done;
2226 if (fcgi_gen_response(c, rt->tag_name) == -1)
2227 goto done;
2228 if (fcgi_gen_response(c, ")</span>") == -1)
2229 goto done;
2231 if (fcgi_gen_response(c, "</div>\n") == -1)
2232 goto done;
2234 if (fcgi_gen_response(c, "<div class='header_author_title'>Tagger:"
2235 "</div>\n") == -1)
2236 goto done;
2237 if (fcgi_gen_response(c, "<div class='header_author'>") == -1)
2238 goto done;
2239 if (fcgi_gen_response(c, author ? author : "") == -1)
2240 goto done;
2241 if (fcgi_gen_response(c, "</div>\n") == -1)
2242 goto done;
2244 if (fcgi_gen_response(c, "<div class='header_age_title'>Date:"
2245 "</div>\n") == -1)
2246 goto done;
2247 if (fcgi_gen_response(c, "<div class='header_age'>") == -1)
2248 goto done;
2249 if (fcgi_gen_response(c, age ? age : "") == -1)
2250 goto done;
2251 if (fcgi_gen_response(c, "</div>\n") == -1)
2252 goto done;
2254 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2255 "</div>\n") == -1)
2256 goto done;
2257 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2258 goto done;
2259 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2260 goto done;
2261 if (fcgi_gen_response(c, "</div>\n") == -1)
2262 goto done;
2263 if (fcgi_gen_response(c, "</div>\n") == -1)
2264 goto done;
2266 if (fcgi_gen_response(c, "<div class='dotted_line'></div>\n") == -1)
2267 goto done;
2268 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2269 goto done;
2271 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2272 goto done;
2274 if (fcgi_gen_response(c, "</div>\n") == -1)
2275 goto done;
2276 fcgi_gen_response(c, "</div>\n");
2277 done:
2278 free(age);
2279 free(author);
2280 return error;
2283 static const struct got_error *
2284 gotweb_render_tags(struct request *c)
2286 const struct got_error *error = NULL;
2287 struct repo_tag *rt = NULL;
2288 struct server *srv = c->srv;
2289 struct transport *t = c->t;
2290 struct querystring *qs = t->qs;
2291 struct repo_dir *repo_dir = t->repo_dir;
2292 char *newline;
2293 char *age = NULL;
2294 int commit_found = 0;
2296 if (qs->action == BRIEFS) {
2297 qs->action = TAGS;
2298 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2299 } else
2300 error = got_get_repo_tags(c, srv->max_commits_display);
2301 if (error)
2302 goto done;
2304 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2305 goto done;
2306 if (fcgi_gen_response(c,
2307 "<div id='tags_title'>Tags</div>\n") == -1)
2308 goto done;
2309 if (fcgi_gen_response(c, "</div>\n") == -1)
2310 goto done;
2312 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2313 goto done;
2315 if (t->tag_count == 0) {
2316 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2317 goto done;
2318 if (fcgi_gen_response(c,
2319 "This repository contains no tags\n") == -1)
2320 goto done;
2321 if (fcgi_gen_response(c, "</div>\n") == -1)
2322 goto done;
2323 if (fcgi_gen_response(c, "</div>\n") == -1)
2324 goto done;
2327 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2328 if (commit_found == 0 && qs->commit != NULL) {
2329 if (strcmp(qs->commit, rt->commit_id) != 0)
2330 continue;
2331 else
2332 commit_found = 1;
2334 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2335 if (error)
2336 goto done;
2337 if (fcgi_gen_response(c, "<div class='tag_age'>") == -1)
2338 goto done;
2339 if (fcgi_gen_response(c, age ? age : "") == -1)
2340 goto done;
2341 if (fcgi_gen_response(c, "</div>\n") == -1)
2342 goto done;
2344 if (fcgi_gen_response(c, "<div class='tag'>") == -1)
2345 goto done;
2346 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2347 rt->tag_name += 10;
2348 if (fcgi_gen_response(c, rt->tag_name) == -1)
2349 goto done;
2350 if (fcgi_gen_response(c, "</div>\n") == -1)
2351 goto done;
2353 if (fcgi_gen_response(c, "<div class='tag_log'>") == -1)
2354 goto done;
2355 if (rt->tag_commit != NULL) {
2356 newline = strchr(rt->tag_commit, '\n');
2357 if (newline)
2358 *newline = '\0';
2361 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2362 goto done;
2363 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2364 goto done;
2365 if (fcgi_gen_response(c, "&path=") == -1)
2366 goto done;
2367 if (fcgi_gen_response(c, repo_dir->name) == -1)
2368 goto done;
2369 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2370 goto done;
2371 if (fcgi_gen_response(c, rt->commit_id) == -1)
2372 goto done;
2373 if (fcgi_gen_response(c, "'>") == -1)
2374 goto done;
2375 if (rt->tag_commit != NULL &&
2376 fcgi_gen_response(c, rt->tag_commit) == -1)
2377 goto done;
2378 if (fcgi_gen_response(c, "</a>") == -1)
2379 goto done;
2380 if (fcgi_gen_response(c, "</div>\n") == -1)
2381 goto done;
2383 if (fcgi_gen_response(c, "<div class='navs_wrapper'>\n") == -1)
2384 goto done;
2385 if (fcgi_gen_response(c, "<div class='navs'>") == -1)
2386 goto done;
2388 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2389 goto done;
2390 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2391 goto done;
2392 if (fcgi_gen_response(c, "&path=") == -1)
2393 goto done;
2394 if (fcgi_gen_response(c, repo_dir->name) == -1)
2395 goto done;
2396 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2397 goto done;
2398 if (fcgi_gen_response(c, rt->commit_id) == -1)
2399 goto done;
2400 if (fcgi_gen_response(c, "'>") == -1)
2401 goto done;
2402 if (fcgi_gen_response(c, "tag") == -1)
2403 goto done;
2404 if (fcgi_gen_response(c, "</a>") == -1)
2405 goto done;
2407 if (fcgi_gen_response(c, " | ") == -1)
2408 goto done;
2410 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2411 goto done;
2412 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2413 goto done;
2414 if (fcgi_gen_response(c, "&path=") == -1)
2415 goto done;
2416 if (fcgi_gen_response(c, repo_dir->name) == -1)
2417 goto done;
2418 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2419 goto done;
2420 if (fcgi_gen_response(c, rt->commit_id) == -1)
2421 goto done;
2422 if (fcgi_gen_response(c, "'>") == -1)
2423 goto done;
2424 if (fcgi_gen_response(c, "commit briefs") == -1)
2425 goto done;
2426 if (fcgi_gen_response(c, "</a>") == -1)
2427 goto done;
2429 if (fcgi_gen_response(c, " | ") == -1)
2430 goto done;
2432 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2433 goto done;
2434 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2435 goto done;
2436 if (fcgi_gen_response(c, "&path=") == -1)
2437 goto done;
2438 if (fcgi_gen_response(c, repo_dir->name) == -1)
2439 goto done;
2440 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2441 goto done;
2442 if (fcgi_gen_response(c, rt->commit_id) == -1)
2443 goto done;
2444 if (fcgi_gen_response(c, "'>") == -1)
2445 goto done;
2446 if (fcgi_gen_response(c, "commits") == -1)
2447 goto done;
2448 if (fcgi_gen_response(c, "</a>") == -1)
2449 goto done;
2451 if (fcgi_gen_response(c, "</div>\n") == -1)
2452 goto done;
2453 if (fcgi_gen_response(c, "</div>\n") == -1)
2454 goto done;
2455 if (fcgi_gen_response(c,
2456 "<div class='dotted_line'></div>\n") == -1)
2457 goto done;
2459 free(age);
2460 age = NULL;
2462 if (t->next_id || t->prev_id) {
2463 error = gotweb_render_navs(c);
2464 if (error)
2465 goto done;
2467 fcgi_gen_response(c, "</div>\n");
2468 done:
2469 free(age);
2470 return error;
2473 const struct got_error *
2474 gotweb_escape_html(char **escaped_html, const char *orig_html)
2476 const struct got_error *error = NULL;
2477 struct escape_pair {
2478 char c;
2479 const char *s;
2480 } esc[] = {
2481 { '>', "&gt;" },
2482 { '<', "&lt;" },
2483 { '&', "&amp;" },
2484 { '"', "&quot;" },
2485 { '\'', "&apos;" },
2486 { '\n', "<br />" },
2488 size_t orig_len, len;
2489 int i, j, x;
2491 orig_len = strlen(orig_html);
2492 len = orig_len;
2493 for (i = 0; i < orig_len; i++) {
2494 for (j = 0; j < nitems(esc); j++) {
2495 if (orig_html[i] != esc[j].c)
2496 continue;
2497 len += strlen(esc[j].s) - 1 /* escaped char */;
2501 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2502 if (*escaped_html == NULL)
2503 return got_error_from_errno("calloc");
2505 x = 0;
2506 for (i = 0; i < orig_len; i++) {
2507 int escaped = 0;
2508 for (j = 0; j < nitems(esc); j++) {
2509 if (orig_html[i] != esc[j].c)
2510 continue;
2512 if (strlcat(*escaped_html, esc[j].s, len + 1)
2513 >= len + 1) {
2514 error = got_error(GOT_ERR_NO_SPACE);
2515 goto done;
2517 x += strlen(esc[j].s);
2518 escaped = 1;
2519 break;
2521 if (!escaped) {
2522 (*escaped_html)[x] = orig_html[i];
2523 x++;
2526 done:
2527 if (error) {
2528 free(*escaped_html);
2529 *escaped_html = NULL;
2530 } else {
2531 (*escaped_html)[x] = '\0';
2534 return error;
2537 static const struct got_error *
2538 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2540 const struct got_error *error = NULL;
2541 struct socket *sock = c->sock;
2542 struct server *srv = c->srv;
2543 struct transport *t = c->t;
2544 DIR *dt;
2545 char *dir_test;
2546 int opened = 0;
2548 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2549 GOTWEB_GIT_DIR) == -1)
2550 return got_error_from_errno("asprintf");
2552 dt = opendir(dir_test);
2553 if (dt == NULL) {
2554 free(dir_test);
2555 } else {
2556 repo_dir->path = strdup(dir_test);
2557 if (repo_dir->path == NULL) {
2558 opened = 1;
2559 error = got_error_from_errno("strdup");
2560 goto err;
2562 opened = 1;
2563 goto done;
2566 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2567 GOTWEB_GOT_DIR) == -1) {
2568 dir_test = NULL;
2569 error = got_error_from_errno("asprintf");
2570 goto err;
2573 dt = opendir(dir_test);
2574 if (dt == NULL)
2575 free(dir_test);
2576 else {
2577 opened = 1;
2578 error = got_error(GOT_ERR_NOT_GIT_REPO);
2579 goto err;
2582 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2583 repo_dir->name) == -1) {
2584 error = got_error_from_errno("asprintf");
2585 dir_test = NULL;
2586 goto err;
2589 repo_dir->path = strdup(dir_test);
2590 if (repo_dir->path == NULL) {
2591 opened = 1;
2592 error = got_error_from_errno("strdup");
2593 goto err;
2596 dt = opendir(dir_test);
2597 if (dt == NULL) {
2598 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2599 goto err;
2600 } else
2601 opened = 1;
2602 done:
2603 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2604 if (error)
2605 goto err;
2606 error = gotweb_get_repo_description(&repo_dir->description, srv,
2607 repo_dir->path);
2608 if (error)
2609 goto err;
2610 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2611 if (error)
2612 goto err;
2613 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2614 NULL, TM_DIFF);
2615 if (error)
2616 goto err;
2617 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2618 err:
2619 free(dir_test);
2620 if (opened)
2621 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2622 error = got_error_from_errno("closedir");
2623 return error;
2626 static const struct got_error *
2627 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2629 const struct got_error *error;
2631 *repo_dir = calloc(1, sizeof(**repo_dir));
2632 if (*repo_dir == NULL)
2633 return got_error_from_errno("calloc");
2635 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2636 error = got_error_from_errno("asprintf");
2637 free(*repo_dir);
2638 *repo_dir = NULL;
2639 return error;
2641 (*repo_dir)->owner = NULL;
2642 (*repo_dir)->description = NULL;
2643 (*repo_dir)->url = NULL;
2644 (*repo_dir)->age = NULL;
2645 (*repo_dir)->path = NULL;
2647 return NULL;
2650 static const struct got_error *
2651 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2653 const struct got_error *error = NULL;
2654 FILE *f = NULL;
2655 char *d_file = NULL;
2656 unsigned int len;
2657 size_t n;
2659 *description = NULL;
2660 if (srv->show_repo_description == 0)
2661 return NULL;
2663 if (asprintf(&d_file, "%s/description", dir) == -1)
2664 return got_error_from_errno("asprintf");
2666 f = fopen(d_file, "r");
2667 if (f == NULL) {
2668 if (errno == ENOENT || errno == EACCES)
2669 return NULL;
2670 error = got_error_from_errno2("fopen", d_file);
2671 goto done;
2674 if (fseek(f, 0, SEEK_END) == -1) {
2675 error = got_ferror(f, GOT_ERR_IO);
2676 goto done;
2678 len = ftell(f);
2679 if (len == -1) {
2680 error = got_ferror(f, GOT_ERR_IO);
2681 goto done;
2684 if (len == 0)
2685 goto done;
2687 if (fseek(f, 0, SEEK_SET) == -1) {
2688 error = got_ferror(f, GOT_ERR_IO);
2689 goto done;
2691 *description = calloc(len + 1, sizeof(**description));
2692 if (*description == NULL) {
2693 error = got_error_from_errno("calloc");
2694 goto done;
2697 n = fread(*description, 1, len, f);
2698 if (n == 0 && ferror(f))
2699 error = got_ferror(f, GOT_ERR_IO);
2700 done:
2701 if (f != NULL && fclose(f) == EOF && error == NULL)
2702 error = got_error_from_errno("fclose");
2703 free(d_file);
2704 return error;
2707 static const struct got_error *
2708 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2710 const struct got_error *error = NULL;
2711 FILE *f;
2712 char *d_file = NULL;
2713 unsigned int len;
2714 size_t n;
2716 *url = NULL;
2718 if (srv->show_repo_cloneurl == 0)
2719 return NULL;
2721 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2722 return got_error_from_errno("asprintf");
2724 f = fopen(d_file, "r");
2725 if (f == NULL) {
2726 if (errno != ENOENT && errno != EACCES)
2727 error = got_error_from_errno2("fopen", d_file);
2728 goto done;
2731 if (fseek(f, 0, SEEK_END) == -1) {
2732 error = got_ferror(f, GOT_ERR_IO);
2733 goto done;
2735 len = ftell(f);
2736 if (len == -1) {
2737 error = got_ferror(f, GOT_ERR_IO);
2738 goto done;
2740 if (len == 0)
2741 goto done;
2743 if (fseek(f, 0, SEEK_SET) == -1) {
2744 error = got_ferror(f, GOT_ERR_IO);
2745 goto done;
2748 *url = calloc(len + 1, sizeof(**url));
2749 if (*url == NULL) {
2750 error = got_error_from_errno("calloc");
2751 goto done;
2754 n = fread(*url, 1, len, f);
2755 if (n == 0 && ferror(f))
2756 error = got_ferror(f, GOT_ERR_IO);
2757 done:
2758 if (f != NULL && fclose(f) == EOF && error == NULL)
2759 error = got_error_from_errno("fclose");
2760 free(d_file);
2761 return error;
2764 const struct got_error *
2765 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2767 struct tm tm;
2768 long long diff_time;
2769 const char *years = "years ago", *months = "months ago";
2770 const char *weeks = "weeks ago", *days = "days ago";
2771 const char *hours = "hours ago", *minutes = "minutes ago";
2772 const char *seconds = "seconds ago", *now = "right now";
2773 char *s;
2774 char datebuf[29];
2776 *repo_age = NULL;
2778 switch (ref_tm) {
2779 case TM_DIFF:
2780 diff_time = time(NULL) - committer_time;
2781 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2782 if (asprintf(repo_age, "%lld %s",
2783 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2784 return got_error_from_errno("asprintf");
2785 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2786 if (asprintf(repo_age, "%lld %s",
2787 (diff_time / 60 / 60 / 24 / (365 / 12)),
2788 months) == -1)
2789 return got_error_from_errno("asprintf");
2790 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2791 if (asprintf(repo_age, "%lld %s",
2792 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2793 return got_error_from_errno("asprintf");
2794 } else if (diff_time > 60 * 60 * 24 * 2) {
2795 if (asprintf(repo_age, "%lld %s",
2796 (diff_time / 60 / 60 / 24), days) == -1)
2797 return got_error_from_errno("asprintf");
2798 } else if (diff_time > 60 * 60 * 2) {
2799 if (asprintf(repo_age, "%lld %s",
2800 (diff_time / 60 / 60), hours) == -1)
2801 return got_error_from_errno("asprintf");
2802 } else if (diff_time > 60 * 2) {
2803 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2804 minutes) == -1)
2805 return got_error_from_errno("asprintf");
2806 } else if (diff_time > 2) {
2807 if (asprintf(repo_age, "%lld %s", diff_time,
2808 seconds) == -1)
2809 return got_error_from_errno("asprintf");
2810 } else {
2811 if (asprintf(repo_age, "%s", now) == -1)
2812 return got_error_from_errno("asprintf");
2814 break;
2815 case TM_LONG:
2816 if (gmtime_r(&committer_time, &tm) == NULL)
2817 return got_error_from_errno("gmtime_r");
2819 s = asctime_r(&tm, datebuf);
2820 if (s == NULL)
2821 return got_error_from_errno("asctime_r");
2823 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2824 return got_error_from_errno("asprintf");
2825 break;
2827 return NULL;