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 <imsg.h>
30 #include <sha1.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
36 #include "got_error.h"
37 #include "got_object.h"
38 #include "got_reference.h"
39 #include "got_repository.h"
40 #include "got_path.h"
41 #include "got_cancel.h"
42 #include "got_worktree.h"
43 #include "got_diff.h"
44 #include "got_commit_graph.h"
45 #include "got_blame.h"
46 #include "got_privsep.h"
48 #include "proc.h"
49 #include "gotwebd.h"
51 enum gotweb_ref_tm {
52 TM_DIFF,
53 TM_LONG,
54 };
56 static const struct querystring_keys querystring_keys[] = {
57 { "action", ACTION },
58 { "commit", COMMIT },
59 { "file", RFILE },
60 { "folder", FOLDER },
61 { "headref", HEADREF },
62 { "index_page", INDEX_PAGE },
63 { "path", PATH },
64 { "page", PAGE },
65 };
67 static const struct action_keys action_keys[] = {
68 { "blame", BLAME },
69 { "blob", BLOB },
70 { "briefs", BRIEFS },
71 { "commits", COMMITS },
72 { "diff", DIFF },
73 { "error", ERR },
74 { "index", INDEX },
75 { "summary", SUMMARY },
76 { "tag", TAG },
77 { "tags", TAGS },
78 { "tree", TREE },
79 };
81 static const struct got_error *gotweb_init_querystring(struct querystring **);
82 static const struct got_error *gotweb_parse_querystring(struct querystring **,
83 char *);
84 static const struct got_error *gotweb_assign_querystring(struct querystring **,
85 char *, char *);
86 static const struct got_error *gotweb_render_header(struct request *);
87 static const struct got_error *gotweb_render_footer(struct request *);
88 static const struct got_error *gotweb_render_index(struct request *);
89 static const struct got_error *gotweb_init_repo_dir(struct repo_dir **,
90 const char *);
91 static const struct got_error *gotweb_load_got_path(struct request *c,
92 struct repo_dir *);
93 static const struct got_error *gotweb_get_repo_description(char **,
94 struct server *, char *);
95 static const struct got_error *gotweb_get_clone_url(char **, struct server *,
96 char *);
97 static const struct got_error *gotweb_render_navs(struct request *);
98 static const struct got_error *gotweb_render_blame(struct request *);
99 static const struct got_error *gotweb_render_briefs(struct request *);
100 static const struct got_error *gotweb_render_commits(struct request *);
101 static const struct got_error *gotweb_render_diff(struct request *);
102 static const struct got_error *gotweb_render_summary(struct request *);
103 static const struct got_error *gotweb_render_tag(struct request *);
104 static const struct got_error *gotweb_render_tags(struct request *);
105 static const struct got_error *gotweb_render_tree(struct request *);
106 static const struct got_error *gotweb_render_branches(struct request *);
108 static void gotweb_free_querystring(struct querystring *);
109 static void gotweb_free_repo_dir(struct repo_dir *);
111 struct server *gotweb_get_server(uint8_t *, uint8_t *, uint8_t *);
113 void
114 gotweb_process_request(struct request *c)
116 const struct got_error *error = NULL, *error2 = NULL;
117 struct server *srv = NULL;
118 struct querystring *qs = NULL;
119 struct repo_dir *repo_dir = NULL;
120 uint8_t err[] = "gotwebd experienced an error: ";
121 int html = 0;
123 /* init the transport */
124 error = gotweb_init_transport(&c->t);
125 if (error) {
126 log_warnx("%s: %s", __func__, error->msg);
127 goto err;
129 /* don't process any further if client disconnected */
130 if (c->sock->client_status == CLIENT_DISCONNECT)
131 return;
132 /* get the gotwebd server */
133 srv = gotweb_get_server(c->server_name, c->document_root, c->http_host);
134 if (srv == NULL) {
135 log_warnx("%s: error server is NULL", __func__);
136 goto err;
138 c->srv = srv;
139 /* parse our querystring */
140 error = gotweb_init_querystring(&qs);
141 if (error) {
142 log_warnx("%s: %s", __func__, error->msg);
143 goto err;
145 c->t->qs = qs;
146 error = gotweb_parse_querystring(&qs, c->querystring);
147 if (error) {
148 gotweb_free_querystring(qs);
149 log_warnx("%s: %s", __func__, error->msg);
150 goto err;
153 /*
154 * certain actions require a commit id in the querystring. this stops
155 * bad actors from exploiting this by manually manipulating the
156 * querystring.
157 */
159 if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
160 qs->action == DIFF)) {
161 error2 = got_error(GOT_ERR_QUERYSTRING);
162 goto render;
165 if (qs->action != INDEX) {
166 error = gotweb_init_repo_dir(&repo_dir, qs->path);
167 if (error)
168 goto done;
169 error = gotweb_load_got_path(c, repo_dir);
170 c->t->repo_dir = repo_dir;
171 if (error && error->code != GOT_ERR_LONELY_PACKIDX)
172 goto err;
175 /* render top of page */
176 if (qs != NULL && qs->action == BLOB) {
177 error = got_get_repo_commits(c, 1);
178 if (error)
179 goto done;
180 error = got_output_file_blob(c);
181 if (error) {
182 log_warnx("%s: %s", __func__, error->msg);
183 goto err;
185 goto done;
186 } else {
187 render:
188 error = gotweb_render_content_type(c, "text/html");
189 if (error) {
190 log_warnx("%s: %s", __func__, error->msg);
191 goto err;
193 html = 1;
196 error = gotweb_render_header(c);
197 if (error) {
198 log_warnx("%s: %s", __func__, error->msg);
199 goto err;
202 if (error2) {
203 error = error2;
204 goto err;
207 switch(qs->action) {
208 case BLAME:
209 error = gotweb_render_blame(c);
210 if (error) {
211 log_warnx("%s: %s", __func__, error->msg);
212 goto err;
214 break;
215 case BRIEFS:
216 error = gotweb_render_briefs(c);
217 if (error) {
218 log_warnx("%s: %s", __func__, error->msg);
219 goto err;
221 break;
222 case COMMITS:
223 error = gotweb_render_commits(c);
224 if (error) {
225 log_warnx("%s: %s", __func__, error->msg);
226 goto err;
228 break;
229 case DIFF:
230 error = gotweb_render_diff(c);
231 if (error) {
232 log_warnx("%s: %s", __func__, error->msg);
233 goto err;
235 break;
236 case INDEX:
237 error = gotweb_render_index(c);
238 if (error) {
239 log_warnx("%s: %s", __func__, error->msg);
240 goto err;
242 break;
243 case SUMMARY:
244 error = gotweb_render_summary(c);
245 if (error) {
246 log_warnx("%s: %s", __func__, error->msg);
247 goto err;
249 break;
250 case TAG:
251 error = gotweb_render_tag(c);
252 if (error) {
253 log_warnx("%s: %s", __func__, error->msg);
254 goto err;
256 break;
257 case TAGS:
258 error = gotweb_render_tags(c);
259 if (error) {
260 log_warnx("%s: %s", __func__, error->msg);
261 goto err;
263 break;
264 case TREE:
265 error = gotweb_render_tree(c);
266 if (error) {
267 log_warnx("%s: %s", __func__, error->msg);
268 goto err;
270 break;
271 case ERR:
272 default:
273 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
274 goto err;
275 if (fcgi_gen_response(c, "Error: Bad Querystring\n") == -1)
276 goto err;
277 if (fcgi_gen_response(c, "</div>\n") == -1)
278 goto err;
279 break;
282 goto done;
283 err:
284 if (html && fcgi_gen_response(c, "<div id='err_content'>") == -1)
285 return;
286 if (fcgi_gen_response(c, err) == -1)
287 return;
288 if (error) {
289 if (fcgi_gen_response(c, (uint8_t *)error->msg) == -1)
290 return;
291 } else {
292 if (fcgi_gen_response(c, "see daemon logs for details") == -1)
293 return;
295 if (html && fcgi_gen_response(c, "</div>\n") == -1)
296 return;
297 done:
298 if (c->t->repo != NULL && qs->action != INDEX)
299 got_repo_close(c->t->repo);
300 if (html && srv != NULL)
301 gotweb_render_footer(c);
304 struct server *
305 gotweb_get_server(uint8_t *server_name, uint8_t *document_root,
306 uint8_t *subdomain)
308 struct server *srv = NULL;
310 /* check against document_root first */
311 if (strlen(server_name) > 0)
312 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
313 if (strcmp(srv->name, server_name) == 0)
314 goto done;
316 /* check against document_root second */
317 if (strlen(document_root) > 0)
318 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
319 if (strcmp(srv->name, document_root) == 0)
320 goto done;
322 /* check against subdomain third */
323 if (strlen(subdomain) > 0)
324 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
325 if (strcmp(srv->name, subdomain) == 0)
326 goto done;
328 /* if those fail, send first server */
329 TAILQ_FOREACH(srv, gotwebd_env->servers, entry)
330 if (srv != NULL)
331 break;
332 done:
333 return srv;
334 };
336 const struct got_error *
337 gotweb_init_transport(struct transport **t)
339 const struct got_error *error = NULL;
341 *t = calloc(1, sizeof(**t));
342 if (*t == NULL)
343 return got_error_from_errno2("%s: calloc", __func__);
345 TAILQ_INIT(&(*t)->repo_commits);
346 TAILQ_INIT(&(*t)->repo_tags);
348 (*t)->repo = NULL;
349 (*t)->repo_dir = NULL;
350 (*t)->qs = NULL;
351 (*t)->next_id = NULL;
352 (*t)->prev_id = NULL;
353 (*t)->next_disp = 0;
354 (*t)->prev_disp = 0;
356 return error;
359 static const struct got_error *
360 gotweb_init_querystring(struct querystring **qs)
362 const struct got_error *error = NULL;
364 *qs = calloc(1, sizeof(**qs));
365 if (*qs == NULL)
366 return got_error_from_errno2("%s: calloc", __func__);
368 (*qs)->action = INDEX;
369 (*qs)->commit = NULL;
370 (*qs)->file = NULL;
371 (*qs)->folder = NULL;
372 (*qs)->headref = strdup("HEAD");
373 if ((*qs)->headref == NULL) {
374 return got_error_from_errno2("%s: strdup", __func__);
376 (*qs)->index_page = 0;
377 (*qs)->index_page_str = NULL;
378 (*qs)->path = NULL;
380 return error;
383 static const struct got_error *
384 gotweb_parse_querystring(struct querystring **qs, char *qst)
386 const struct got_error *error = NULL;
387 char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL;
388 char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL;
390 if (qst == NULL)
391 return error;
393 tok1 = strdup(qst);
394 if (tok1 == NULL)
395 return got_error_from_errno2("%s: strdup", __func__);
397 tok1_pair = tok1;
398 tok1_end = tok1;
400 while (tok1_pair != NULL) {
401 strsep(&tok1_end, "&");
403 tok2 = strdup(tok1_pair);
404 if (tok2 == NULL) {
405 free(tok1);
406 return got_error_from_errno2("%s: strdup", __func__);
409 tok2_pair = tok2;
410 tok2_end = tok2;
412 while (tok2_pair != NULL) {
413 strsep(&tok2_end, "=");
414 if (tok2_end) {
415 error = gotweb_assign_querystring(qs, tok2_pair,
416 tok2_end);
417 if (error)
418 goto err;
420 tok2_pair = tok2_end;
422 free(tok2);
423 tok1_pair = tok1_end;
425 free(tok1);
426 return error;
427 err:
428 free(tok2);
429 free(tok1);
430 return error;
433 static const struct got_error *
434 gotweb_assign_querystring(struct querystring **qs, char *key, char *value)
436 const struct got_error *error = NULL;
437 const char *errstr;
438 int a_cnt, el_cnt;
440 for (el_cnt = 0; el_cnt < QSELEM__MAX; el_cnt++) {
441 if (strcmp(key, querystring_keys[el_cnt].name) != 0)
442 continue;
444 switch (querystring_keys[el_cnt].element) {
445 case ACTION:
446 for (a_cnt = 0; a_cnt < ACTIONS__MAX; a_cnt++) {
447 if (strcmp(value, action_keys[a_cnt].name) != 0)
448 continue;
449 else if (strcmp(value,
450 action_keys[a_cnt].name) == 0){
451 (*qs)->action =
452 action_keys[a_cnt].action;
453 goto qa_found;
456 (*qs)->action = ERR;
457 qa_found:
458 break;
459 case COMMIT:
460 (*qs)->commit = strdup(value);
461 if ((*qs)->commit == NULL) {
462 error = got_error_from_errno2("%s: strdup",
463 __func__);
464 goto done;
466 break;
467 case RFILE:
468 (*qs)->file = strdup(value);
469 if ((*qs)->file == NULL) {
470 error = got_error_from_errno2("%s: strdup",
471 __func__);
472 goto done;
474 break;
475 case FOLDER:
476 (*qs)->folder = strdup(value);
477 if ((*qs)->folder == NULL) {
478 error = got_error_from_errno2("%s: strdup",
479 __func__);
480 goto done;
482 break;
483 case HEADREF:
484 (*qs)->headref = strdup(value);
485 if ((*qs)->headref == NULL) {
486 error = got_error_from_errno2("%s: strdup",
487 __func__);
488 goto done;
490 break;
491 case INDEX_PAGE:
492 if (strlen(value) == 0)
493 break;
494 (*qs)->index_page_str = strdup(value);
495 if ((*qs)->index_page_str == NULL) {
496 error = got_error_from_errno2("%s: strdup",
497 __func__);
498 goto done;
500 (*qs)->index_page = strtonum(value, INT64_MIN,
501 INT64_MAX, &errstr);
502 if (errstr) {
503 error = got_error_from_errno3("%s: strtonum %s",
504 __func__, errstr);
505 goto done;
507 if ((*qs)->index_page < 0) {
508 (*qs)->index_page = 0;
509 sprintf((*qs)->index_page_str, "%d", 0);
511 break;
512 case PATH:
513 (*qs)->path = strdup(value);
514 if ((*qs)->path == NULL) {
515 error = got_error_from_errno2("%s: strdup",
516 __func__);
517 goto done;
519 break;
520 case PAGE:
521 if (strlen(value) == 0)
522 break;
523 (*qs)->page_str = strdup(value);
524 if ((*qs)->page_str == NULL) {
525 error = got_error_from_errno2("%s: strdup",
526 __func__);
527 goto done;
529 (*qs)->page = strtonum(value, INT64_MIN,
530 INT64_MAX, &errstr);
531 if (errstr) {
532 error = got_error_from_errno3("%s: strtonum %s",
533 __func__, errstr);
534 goto done;
536 if ((*qs)->page < 0) {
537 (*qs)->page = 0;
538 sprintf((*qs)->page_str, "%d", 0);
540 break;
541 default:
542 break;
545 done:
546 return error;
549 void
550 gotweb_free_repo_tag(struct repo_tag *rt)
552 if (rt != NULL) {
553 free(rt->commit_msg);
554 free(rt->commit_id);
555 free(rt->tagger);
557 free(rt);
560 void
561 gotweb_free_repo_commit(struct repo_commit *rc)
563 if (rc != NULL) {
564 free(rc->path);
565 free(rc->refs_str);
566 free(rc->commit_id);
567 free(rc->parent_id);
568 free(rc->tree_id);
569 free(rc->author);
570 free(rc->committer);
571 free(rc->commit_msg);
573 free(rc);
576 static void
577 gotweb_free_querystring(struct querystring *qs)
579 if (qs != NULL) {
580 free(qs->commit);
581 free(qs->file);
582 free(qs->folder);
583 free(qs->headref);
584 free(qs->index_page_str);
585 free(qs->path);
586 free(qs->page_str);
588 free(qs);
591 static void
592 gotweb_free_repo_dir(struct repo_dir *repo_dir)
594 if (repo_dir != NULL) {
595 free(repo_dir->name);
596 free(repo_dir->owner);
597 free(repo_dir->description);
598 free(repo_dir->url);
599 free(repo_dir->age);
600 free(repo_dir->path);
602 free(repo_dir);
605 void
606 gotweb_free_transport(struct transport *t)
608 struct repo_commit *rc = NULL, *trc = NULL;
609 struct repo_tag *rt = NULL, *trt = NULL;
611 TAILQ_FOREACH_SAFE(rc, &t->repo_commits, entry, trc) {
612 TAILQ_REMOVE(&t->repo_commits, rc, entry);
613 gotweb_free_repo_commit(rc);
615 TAILQ_FOREACH_SAFE(rt, &t->repo_tags, entry, trt) {
616 TAILQ_REMOVE(&t->repo_tags, rt, entry);
617 gotweb_free_repo_tag(rt);
619 gotweb_free_repo_dir(t->repo_dir);
620 gotweb_free_querystring(t->qs);
621 if (t != NULL) {
622 free(t->next_id);
623 free(t->prev_id);
625 free(t);
628 const struct got_error *
629 gotweb_render_content_type(struct request *c, const uint8_t *type)
631 const struct got_error *error = NULL;
632 char *h = NULL;
634 if (asprintf(&h, "Content-type: %s\r\n\r\n", type) == -1) {
635 error = got_error_from_errno2("%s: asprintf", __func__);
636 goto done;
639 fcgi_gen_response(c, h);
640 done:
641 free(h);
643 return error;
646 const struct got_error *
647 gotweb_render_content_type_file(struct request *c, const uint8_t *type,
648 char *file)
650 const struct got_error *error = NULL;
651 char *h = NULL;
653 if (asprintf(&h, "Content-type: %s\r\n"
654 "Content-disposition: attachment; filename=%s\r\n\r\n",
655 type, file) == -1) {
656 error = got_error_from_errno2("%s: asprintf", __func__);
657 goto done;
660 fcgi_gen_response(c, h);
661 done:
662 free(h);
664 return error;
667 static const struct got_error *
668 gotweb_render_header(struct request *c)
670 const struct got_error *error = NULL;
671 struct server *srv = c->srv;
672 struct querystring *qs = c->t->qs;
673 char *title = NULL, *droot = NULL, *css = NULL, *gotlink = NULL;
674 char *gotimg = NULL, *sitelink = NULL, *summlink = NULL;
676 if (strlen(c->document_root) > 0) {
677 if (asprintf(&droot, "/%s/", c->document_root) == -1) {
678 error = got_error_from_errno2("%s: asprintf", __func__);
679 goto done;
681 } else {
682 if (asprintf(&droot, "/") == -1) {
683 error = got_error_from_errno2("%s: asprintf", __func__);
684 goto done;
688 if (asprintf(&title, "<title>%s</title>\n", srv->site_name) == -1) {
689 error = got_error_from_errno2("%s: asprintf", __func__);
690 goto done;
692 if (asprintf(&css,
693 "<link rel='stylesheet' type='text/css' href='%s%s'/>\n",
694 droot, srv->custom_css) == -1) {
695 error = got_error_from_errno2("%s: asprintf", __func__);
696 goto done;
698 if (asprintf(&gotlink, "<a href='%s' target='_sotd'>",
699 srv->logo_url) == -1) {
700 error = got_error_from_errno2("%s: asprintf", __func__);
701 goto done;
703 if (asprintf(&gotimg, "<img src='%s%s' alt='logo' id='logo'/></a>",
704 droot, srv->logo) == -1) {
705 error = got_error_from_errno2("%s: asprintf", __func__);
706 goto done;
708 if (asprintf(&sitelink, "<a href='/%s?index_page=%d' "
709 "alt='sitelink'>%s</a>", c->document_root, qs->index_page,
710 srv->site_link) == -1) {
711 error = got_error_from_errno2("%s: asprintf", __func__);
712 goto done;
714 if (asprintf(&summlink, "<a href='/%s?index_page=%d&path=%s"
715 "&action=summary' alt='summlink'>%s</a>", c->document_root,
716 qs->index_page, qs->path, qs->path) == -1) {
717 error = got_error_from_errno2("%s: asprintf", __func__);
718 goto done;
721 if (fcgi_gen_response(c, "<!DOCTYPE html>\n<head>\n") == -1)
722 goto done;
723 if (fcgi_gen_response(c, title) == -1)
724 goto done;
725 if (fcgi_gen_response(c, "<meta name='viewport' "
726 "content='initial-scale=.75, user-scalable=yes'/>\n") == -1)
727 goto done;
728 if (fcgi_gen_response(c, "<meta charset='utf-8'/>\n") == -1)
729 goto done;
730 if (fcgi_gen_response(c, "<meta name='msapplication-TileColor' "
731 "content='#da532c'/>\n") == -1)
732 goto done;
733 if (fcgi_gen_response(c,
734 "<meta name='theme-color' content='#ffffff'/>\n") == -1)
735 goto done;
736 if (fcgi_gen_response(c, "<link rel='apple-touch-icon' sizes='180x180' "
737 "href='/apple-touch-icon.png'/>\n") == -1)
738 goto done;
739 if (fcgi_gen_response(c,
740 "<link rel='icon' type='image/png' sizes='32x32' "
741 "href='/favicon-32x32.png'/>\n") == -1)
742 goto done;
743 if (fcgi_gen_response(c, "<link rel='icon' type='image/png' "
744 "sizes='16x16' href='/favicon-16x16.png'/>\n") == -1)
745 goto done;
746 if (fcgi_gen_response(c, "<link rel='manifest' "
747 "href='/site.webmanifest'/>\n") == -1)
748 goto done;
749 if (fcgi_gen_response(c, "<link rel='mask-icon' "
750 "href='/safari-pinned-tab.svg'/>\n") == -1)
751 goto done;
752 if (fcgi_gen_response(c, css) == -1)
753 goto done;
754 if (fcgi_gen_response(c, "</head>\n<body>\n<div id='gw_body'>\n") == -1)
755 goto done;
756 if (fcgi_gen_response(c,
757 "<div id='header'>\n<div id='got_link'>") == -1)
758 goto done;
759 if (fcgi_gen_response(c, gotlink) == -1)
760 goto done;
761 if (fcgi_gen_response(c, gotimg) == -1)
762 goto done;
763 if (fcgi_gen_response(c, "</div>\n</div>\n") == -1)
764 goto done;
765 if (fcgi_gen_response(c,
766 "<div id='site_path'>\n<div id='site_link'>") == -1)
767 goto done;
768 if (fcgi_gen_response(c, sitelink) == -1)
769 goto done;
770 if (qs != NULL) {
771 if (qs->path != NULL) {
772 if (fcgi_gen_response(c, " / ") == -1)
773 goto done;
774 if (fcgi_gen_response(c, summlink) == -1)
775 goto done;
777 if (qs->action != INDEX) {
778 if (fcgi_gen_response(c, " / ") == -1)
779 goto done;
780 switch(qs->action) {
781 case(BLAME):
782 if (fcgi_gen_response(c, "blame") == -1)
783 goto done;
784 break;
785 case(BRIEFS):
786 if (fcgi_gen_response(c, "briefs") == -1)
787 goto done;
788 break;
789 case(COMMITS):
790 if (fcgi_gen_response(c, "commits") == -1)
791 goto done;
792 break;
793 case(DIFF):
794 if (fcgi_gen_response(c, "diff") == -1)
795 goto done;
796 break;
797 case(SUMMARY):
798 if (fcgi_gen_response(c, "summary") == -1)
799 goto done;
800 break;
801 case(TAG):
802 if (fcgi_gen_response(c, "tag") == -1)
803 goto done;
804 break;
805 case(TAGS):
806 if (fcgi_gen_response(c, "tags") == -1)
807 goto done;
808 break;
809 case(TREE):
810 if (fcgi_gen_response(c, "tree") == -1)
811 goto done;
812 break;
813 default:
814 break;
819 fcgi_gen_response(c, "</div>\n</div>\n<div id='content'>\n");
820 done:
821 free(title);
822 free(droot);
823 free(css);
824 free(gotlink);
825 free(gotimg);
826 free(sitelink);
827 free(summlink);
829 return error;
832 static const struct got_error *
833 gotweb_render_footer(struct request *c)
835 const struct got_error *error = NULL;
836 struct server *srv = c->srv;
837 char *siteowner = NULL;
839 if (fcgi_gen_response(c, "<div id='site_owner_wrapper'>\n") == -1)
840 goto done;
841 if (fcgi_gen_response(c, "<div id='site_owner'>") == -1)
842 goto done;
843 if (srv->show_site_owner) {
844 error = gotweb_escape_html(&siteowner, srv->site_owner);
845 if (error)
846 goto done;
847 if (fcgi_gen_response(c, siteowner) == -1)
848 goto done;
849 } else
850 if (fcgi_gen_response(c, "&nbsp;") == -1)
851 goto done;
852 fcgi_gen_response(c, "</div>\n</div>\n</div>\n</body>\n</html>");
853 done:
854 free(siteowner);
856 return error;
859 static const struct got_error *
860 gotweb_render_navs(struct request *c)
862 const struct got_error *error = NULL;
863 struct transport *t = c->t;
864 struct querystring *qs = t->qs;
865 struct server *srv = c->srv;
866 char *nhref = NULL, *phref = NULL;
867 int disp = 0;
869 if (fcgi_gen_response(c, "<div id='np_wrapper'>\n") == -1)
870 goto done;
871 if (fcgi_gen_response(c, "<div id='nav_prev'>") == -1)
872 goto done;
874 switch(qs->action) {
875 case INDEX:
876 if (qs->index_page > 0) {
877 if (asprintf(&phref, "index_page=%d",
878 qs->index_page - 1) == -1) {
879 error = got_error_from_errno2("%s: asprintf",
880 __func__);
881 goto done;
883 disp = 1;
885 break;
886 case BRIEFS:
887 if (t->prev_id && qs->commit != NULL &&
888 strcmp(qs->commit, t->prev_id) != 0) {
889 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
890 "&action=briefs&commit=%s&headref=%s",
891 qs->index_page, qs->path, qs->page - 1, t->prev_id,
892 qs->headref) == -1) {
893 error = got_error_from_errno2("%s: asprintf",
894 __func__);
895 goto done;
897 disp = 1;
899 break;
900 case COMMITS:
901 if (t->prev_id && qs->commit != NULL &&
902 strcmp(qs->commit, t->prev_id) != 0) {
903 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
904 "&action=commits&commit=%s&headref=%s&folder=%s"
905 "&file=%s",
906 qs->index_page, qs->path, qs->page - 1, t->prev_id,
907 qs->headref, qs->folder ? qs->folder : "",
908 qs->file ? qs->file : "") == -1) {
909 error = got_error_from_errno2("%s: asprintf",
910 __func__);
911 goto done;
913 disp = 1;
915 break;
916 case TAGS:
917 if (t->prev_id && qs->commit != NULL &&
918 strcmp(qs->commit, t->prev_id) != 0) {
919 if (asprintf(&phref, "index_page=%d&path=%s&page=%d"
920 "&action=tags&commit=%s&headref=%s",
921 qs->index_page, qs->path, qs->page - 1, t->prev_id,
922 qs->headref) == -1) {
923 error = got_error_from_errno2("%s: asprintf",
924 __func__);
925 goto done;
927 disp = 1;
929 break;
930 default:
931 disp = 0;
932 break;
935 if (disp) {
936 if (fcgi_gen_response(c, "<a href='?") == -1)
937 goto done;
938 if (fcgi_gen_response(c, phref) == -1)
939 goto done;
940 if (fcgi_gen_response(c, "'>Previous</a>") == -1)
941 goto done;
943 if (fcgi_gen_response(c, "</div>\n") == -1)
944 goto done;
945 if (fcgi_gen_response(c, "<div id='nav_next'>") == -1)
946 goto done;
948 disp = 0;
950 switch(qs->action) {
951 case INDEX:
952 if (t->next_disp == srv->max_repos_display &&
953 t->repos_total != (qs->index_page + 1) *
954 srv->max_repos_display) {
955 if (asprintf(&nhref, "index_page=%d",
956 qs->index_page + 1) == -1) {
957 error = got_error_from_errno2("%s: asprintf",
958 __func__);
959 goto done;
961 disp = 1;
963 break;
964 case BRIEFS:
965 if (t->next_id) {
966 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
967 "&action=briefs&commit=%s&headref=%s",
968 qs->index_page, qs->path, qs->page + 1, t->next_id,
969 qs->headref) == -1) {
970 error = got_error_from_errno2("%s: asprintf",
971 __func__);
972 goto done;
974 disp = 1;
976 break;
977 case COMMITS:
978 if (t->next_id) {
979 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
980 "&action=commits&commit=%s&headref=%s&folder=%s"
981 "&file=%s",
982 qs->index_page, qs->path, qs->page + 1, t->next_id,
983 qs->headref, qs->folder ? qs->folder : "",
984 qs->file ? qs->file : "") == -1) {
985 error = got_error_from_errno2("%s: asprintf",
986 __func__);
987 goto done;
989 disp = 1;
991 break;
992 case TAGS:
993 if (t->next_id) {
994 if (asprintf(&nhref, "index_page=%d&path=%s&page=%d"
995 "&action=tags&commit=%s&headref=%s",
996 qs->index_page, qs->path, qs->page + 1, t->next_id,
997 qs->headref) == -1) {
998 error = got_error_from_errno2("%s: asprintf",
999 __func__);
1000 goto done;
1002 disp = 1;
1004 break;
1005 default:
1006 disp = 0;
1007 break;
1009 if (disp) {
1010 if (fcgi_gen_response(c, "<a href='?") == -1)
1011 goto done;
1012 if (fcgi_gen_response(c, nhref) == -1)
1013 goto done;
1014 if (fcgi_gen_response(c, "'>Next</a>") == -1)
1015 goto done;
1017 fcgi_gen_response(c, "</div>\n");
1018 done:
1019 free(t->next_id);
1020 t->next_id = NULL;
1021 free(t->prev_id);
1022 t->prev_id = NULL;
1023 free(phref);
1024 free(nhref);
1025 return error;
1028 static const struct got_error *
1029 gotweb_render_index(struct request *c)
1031 const struct got_error *error = NULL;
1032 struct server *srv = c->srv;
1033 struct transport *t = c->t;
1034 struct querystring *qs = t->qs;
1035 struct repo_dir *repo_dir = NULL;
1036 DIR *d;
1037 struct dirent **sd_dent;
1038 char *c_path = NULL;
1039 struct stat st;
1040 unsigned int d_cnt, d_i, d_disp = 0;
1042 d = opendir(srv->repos_path);
1043 if (d == NULL) {
1044 error = got_error_from_errno2("opendir", srv->repos_path);
1045 return error;
1048 d_cnt = scandir(srv->repos_path, &sd_dent, NULL, alphasort);
1049 if (d_cnt == -1) {
1050 error = got_error_from_errno2("scandir", srv->repos_path);
1051 goto done;
1054 /* get total count of repos */
1055 for (d_i = 0; d_i < d_cnt; d_i++) {
1056 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1057 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1058 continue;
1060 if (asprintf(&c_path, "%s/%s", srv->repos_path,
1061 sd_dent[d_i]->d_name) == -1) {
1062 error = got_error_from_errno("asprintf");
1063 return error;
1066 if (lstat(c_path, &st) == 0 && S_ISDIR(st.st_mode) &&
1067 !got_path_dir_is_empty(c_path))
1068 t->repos_total++;
1069 free(c_path);
1070 c_path = NULL;
1073 if (fcgi_gen_response(c, "<div id='index_header'>\n") == -1)
1074 goto done;
1075 if (fcgi_gen_response(c,
1076 "<div id='index_header_project'>Project</div>\n") == -1)
1077 goto done;
1078 if (srv->show_repo_description)
1079 if (fcgi_gen_response(c, "<div id='index_header_description'>"
1080 "Description</div>\n") == -1)
1081 goto done;
1082 if (srv->show_repo_owner)
1083 if (fcgi_gen_response(c, "<div id='index_header_owner'>"
1084 "Owner</div>\n") == -1)
1085 goto done;
1086 if (srv->show_repo_age)
1087 if (fcgi_gen_response(c, "<div id='index_header_age'>"
1088 "Last Change</div>\n") == -1)
1089 goto done;
1090 if (fcgi_gen_response(c, "</div>\n") == -1)
1091 goto done;
1093 for (d_i = 0; d_i < d_cnt; d_i++) {
1094 if (srv->max_repos > 0 && (d_i - 2) == srv->max_repos)
1095 break; /* account for parent and self */
1097 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1098 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1099 continue;
1101 if (qs->index_page > 0 && (qs->index_page *
1102 srv->max_repos_display) > t->prev_disp) {
1103 t->prev_disp++;
1104 continue;
1107 error = gotweb_init_repo_dir(&repo_dir, sd_dent[d_i]->d_name);
1108 if (error)
1109 goto done;
1111 error = gotweb_load_got_path(c, repo_dir);
1112 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1113 error = NULL;
1114 continue;
1116 else if (error && error->code != GOT_ERR_LONELY_PACKIDX)
1117 goto done;
1119 if (lstat(repo_dir->path, &st) == 0 &&
1120 S_ISDIR(st.st_mode) &&
1121 !got_path_dir_is_empty(repo_dir->path))
1122 goto render;
1123 else {
1124 gotweb_free_repo_dir(repo_dir);
1125 repo_dir = NULL;
1126 continue;
1128 render:
1129 d_disp++;
1130 t->prev_disp++;
1131 if (fcgi_gen_response(c, "<div id='index_wrapper'>\n") == -1)
1132 goto done;
1133 if (fcgi_gen_response(c, "<div id='index_project'>") == -1)
1134 goto done;
1136 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1137 goto done;
1138 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1139 goto done;
1140 if (fcgi_gen_response(c, "&path=") == -1)
1141 goto done;
1142 if (fcgi_gen_response(c, repo_dir->name) == -1)
1143 goto done;
1144 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1145 goto done;
1146 if (fcgi_gen_response(c, repo_dir->name) == -1)
1147 goto done;
1148 if (fcgi_gen_response(c, "</a>") == -1)
1149 goto done;
1151 if (fcgi_gen_response(c, "</div>\n") == -1)
1152 goto done;
1154 if (srv->show_repo_description) {
1155 if (fcgi_gen_response(c,
1156 "<div id='index_project_description'>\n") == -1)
1157 goto done;
1158 if (fcgi_gen_response(c, repo_dir->description) == -1)
1159 goto done;
1160 if (fcgi_gen_response(c, "</div>\n") == -1)
1161 goto done;
1164 if (srv->show_repo_owner) {
1165 if (fcgi_gen_response(c,
1166 "<div id='index_project_owner'>") == -1)
1167 goto done;
1168 if (fcgi_gen_response(c, repo_dir->owner) == -1)
1169 goto done;
1170 if (fcgi_gen_response(c, "</div>\n") == -1)
1171 goto done;
1174 if (srv->show_repo_age) {
1175 if (fcgi_gen_response(c,
1176 "<div id='index_project_age'>") == -1)
1177 goto done;
1178 if (fcgi_gen_response(c, repo_dir->age) == -1)
1179 goto done;
1180 if (fcgi_gen_response(c, "</div>\n") == -1)
1181 goto done;
1184 if (fcgi_gen_response(c, "<div id='navs_wrapper'>") == -1)
1185 goto done;
1186 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1187 goto done;;
1189 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1190 goto done;
1191 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1192 goto done;
1193 if (fcgi_gen_response(c, "&path=") == -1)
1194 goto done;
1195 if (fcgi_gen_response(c, repo_dir->name) == -1)
1196 goto done;
1197 if (fcgi_gen_response(c, "&action=summary'>") == -1)
1198 goto done;
1199 if (fcgi_gen_response(c, "summary") == -1)
1200 goto done;
1201 if (fcgi_gen_response(c, "</a> | ") == -1)
1202 goto done;
1204 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1205 goto done;
1206 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1207 goto done;
1208 if (fcgi_gen_response(c, "&path=") == -1)
1209 goto done;
1210 if (fcgi_gen_response(c, repo_dir->name) == -1)
1211 goto done;
1212 if (fcgi_gen_response(c, "&action=briefs'>") == -1)
1213 goto done;
1214 if (fcgi_gen_response(c, "commit briefs") == -1)
1215 goto done;
1216 if (fcgi_gen_response(c, "</a> | ") == -1)
1217 goto done;
1219 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1220 goto done;
1221 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1222 goto done;
1223 if (fcgi_gen_response(c, "&path=") == -1)
1224 goto done;
1225 if (fcgi_gen_response(c, repo_dir->name) == -1)
1226 goto done;
1227 if (fcgi_gen_response(c, "&action=commits'>") == -1)
1228 goto done;
1229 if (fcgi_gen_response(c, "commits") == -1)
1230 goto done;
1231 if (fcgi_gen_response(c, "</a> | ") == -1)
1232 goto done;
1234 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1235 goto done;
1236 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1237 goto done;
1238 if (fcgi_gen_response(c, "&path=") == -1)
1239 goto done;
1240 if (fcgi_gen_response(c, repo_dir->name) == -1)
1241 goto done;
1242 if (fcgi_gen_response(c, "&action=tags'>") == -1)
1243 goto done;
1244 if (fcgi_gen_response(c, "tags") == -1)
1245 goto done;
1246 if (fcgi_gen_response(c, "</a> | ") == -1)
1247 goto done;
1249 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1250 goto done;
1251 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1252 goto done;
1253 if (fcgi_gen_response(c, "&path=") == -1)
1254 goto done;
1255 if (fcgi_gen_response(c, repo_dir->name) == -1)
1256 goto done;
1257 if (fcgi_gen_response(c, "&action=tree'>") == -1)
1258 goto done;
1259 if (fcgi_gen_response(c, "tree") == -1)
1260 goto done;
1261 if (fcgi_gen_response(c, "</a>") == -1)
1262 goto done;
1264 if (fcgi_gen_response(c, "</div>") == -1)
1265 goto done;
1266 if (fcgi_gen_response(c,
1267 "<div id='dotted_line'></div>\n") == -1)
1268 goto done;
1269 if (fcgi_gen_response(c, "</div>\n") == -1)
1270 goto done;
1271 if (fcgi_gen_response(c, "</div>\n") == -1)
1272 goto done;
1274 gotweb_free_repo_dir(repo_dir);
1275 repo_dir = NULL;
1276 error = got_repo_close(t->repo);
1277 if (error)
1278 goto done;
1279 t->next_disp++;
1280 if (d_disp == srv->max_repos_display)
1281 break;
1283 if (srv->max_repos_display == 0)
1284 goto div;
1285 if (srv->max_repos > 0 && srv->max_repos < srv->max_repos_display)
1286 goto div;
1287 if (t->repos_total <= srv->max_repos ||
1288 t->repos_total <= srv->max_repos_display)
1289 goto div;
1291 error = gotweb_render_navs(c);
1292 if (error)
1293 goto done;
1294 div:
1295 fcgi_gen_response(c, "</div>\n");
1296 done:
1297 if (d != NULL && closedir(d) == EOF && error == NULL)
1298 error = got_error_from_errno("closedir");
1299 return error;
1302 static const struct got_error *
1303 gotweb_render_blame(struct request *c)
1305 const struct got_error *error = NULL;
1306 struct transport *t = c->t;
1307 struct repo_commit *rc = NULL;
1308 char *age = NULL;
1310 error = got_get_repo_commits(c, 1);
1311 if (error)
1312 return error;
1314 rc = TAILQ_FIRST(&t->repo_commits);
1316 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1317 if (error)
1318 goto done;
1320 if (fcgi_gen_response(c, "<div id='blame_title_wrapper'>\n") == -1)
1321 goto done;
1322 if (fcgi_gen_response(c, "<div id='blame_title'>Blame</div>\n") == -1)
1323 goto done;
1324 if (fcgi_gen_response(c, "</div>\n") == -1)
1325 goto done;
1327 if (fcgi_gen_response(c, "<div id='blame_content'>\n") == -1)
1328 goto done;
1330 if (fcgi_gen_response(c, "<div id='blame_header_wrapper'>\n") == -1)
1331 goto done;
1332 if (fcgi_gen_response(c, "<div id='blame_header'>\n") == -1)
1333 goto done;
1335 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1336 "</div>\n") == -1)
1337 goto done;
1338 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1339 goto done;
1340 if (fcgi_gen_response(c, age ? age : "") == -1)
1341 goto done;
1342 if (fcgi_gen_response(c, "</div>\n") == -1)
1343 goto done;
1345 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1346 "</div>\n") == -1)
1347 goto done;
1348 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1349 goto done;
1350 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1351 goto done;
1352 if (fcgi_gen_response(c, "</div>\n") == -1)
1353 goto done;
1355 if (fcgi_gen_response(c, "</div>\n") == -1)
1356 goto done;
1357 if (fcgi_gen_response(c, "</div>\n") == -1)
1358 goto done;
1360 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1361 goto done;
1362 if (fcgi_gen_response(c, "<div id='blame'>\n") == -1)
1363 goto done;
1365 error = got_output_file_blame(c);
1366 if (error)
1367 goto done;
1369 fcgi_gen_response(c, "</div>\n");
1370 done:
1371 fcgi_gen_response(c, "</div>\n");
1372 return error;
1375 static const struct got_error *
1376 gotweb_render_briefs(struct request *c)
1378 const struct got_error *error = NULL;
1379 struct repo_commit *rc = NULL;
1380 struct server *srv = c->srv;
1381 struct transport *t = c->t;
1382 struct querystring *qs = t->qs;
1383 struct repo_dir *repo_dir = t->repo_dir;
1384 char *smallerthan, *newline;
1385 char *age = NULL;
1387 if (fcgi_gen_response(c, "<div id='briefs_title_wrapper'>\n") == -1)
1388 goto done;
1389 if (fcgi_gen_response(c,
1390 "<div id='briefs_title'>Commit Briefs</div>\n") == -1)
1391 goto done;
1392 if (fcgi_gen_response(c, "</div>\n") == -1)
1393 goto done;
1395 if (fcgi_gen_response(c, "<div id='briefs_content'>\n") == -1)
1396 goto done;
1398 if (qs->action == SUMMARY) {
1399 qs->action = BRIEFS;
1400 error = got_get_repo_commits(c, D_MAXSLCOMMDISP);
1401 } else
1402 error = got_get_repo_commits(c, srv->max_commits_display);
1403 if (error)
1404 goto done;
1406 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1407 error = gotweb_get_time_str(&age, rc->committer_time, TM_DIFF);
1408 if (error)
1409 goto done;
1410 if (fcgi_gen_response(c, "<div id='briefs_age'>") == -1)
1411 goto done;
1412 if (fcgi_gen_response(c, age ? age : "") == -1)
1413 goto done;
1414 if (fcgi_gen_response(c, "</div>\n") == -1)
1415 goto done;
1417 if (fcgi_gen_response(c, "<div id='briefs_author'>") == -1)
1418 goto done;
1419 smallerthan = strchr(rc->author, '<');
1420 if (smallerthan)
1421 *smallerthan = '\0';
1422 if (fcgi_gen_response(c, rc->author) == -1)
1423 goto done;
1424 if (fcgi_gen_response(c, "</div>\n") == -1)
1425 goto done;
1427 if (fcgi_gen_response(c, "<div id='briefs_log'>") == -1)
1428 goto done;
1429 newline = strchr(rc->commit_msg, '\n');
1430 if (newline)
1431 *newline = '\0';
1433 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1434 goto done;
1435 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1436 goto done;
1437 if (fcgi_gen_response(c, "&path=") == -1)
1438 goto done;
1439 if (fcgi_gen_response(c, repo_dir->name) == -1)
1440 goto done;
1441 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1442 goto done;
1443 if (fcgi_gen_response(c, rc->commit_id) == -1)
1444 goto done;
1445 if (fcgi_gen_response(c, "&headref=") == -1)
1446 goto done;
1447 if (fcgi_gen_response(c, qs->headref) == -1)
1448 goto done;
1449 if (fcgi_gen_response(c, "'>") == -1)
1450 goto done;
1451 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1452 goto done;
1453 if (fcgi_gen_response(c, "</a>") == -1)
1454 goto done;
1455 if (rc->refs_str) {
1456 if (fcgi_gen_response(c,
1457 " <span id='refs_str'>(") == -1)
1458 goto done;
1459 if (fcgi_gen_response(c, rc->refs_str) == -1)
1460 goto done;
1461 if (fcgi_gen_response(c, ")</span>") == -1)
1462 goto done;
1464 if (fcgi_gen_response(c, "</div>\n") == -1)
1465 goto done;
1467 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1468 goto done;
1469 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1470 goto done;
1471 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1472 goto done;
1473 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1474 goto done;
1475 if (fcgi_gen_response(c, "&path=") == -1)
1476 goto done;
1477 if (fcgi_gen_response(c, repo_dir->name) == -1)
1478 goto done;
1479 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1480 goto done;
1481 if (fcgi_gen_response(c, rc->commit_id) == -1)
1482 goto done;
1483 if (fcgi_gen_response(c, "&headref=") == -1)
1484 goto done;
1485 if (fcgi_gen_response(c, qs->headref) == -1)
1486 goto done;
1487 if (fcgi_gen_response(c, "'>") == -1)
1488 goto done;
1489 if (fcgi_gen_response(c, "diff") == -1)
1490 goto done;
1491 if (fcgi_gen_response(c, "</a>") == -1)
1492 goto done;
1494 if (fcgi_gen_response(c, " | ") == -1)
1495 goto done;
1497 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1498 goto done;
1499 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1500 goto done;
1501 if (fcgi_gen_response(c, "&path=") == -1)
1502 goto done;
1503 if (fcgi_gen_response(c, repo_dir->name) == -1)
1504 goto done;
1505 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1506 goto done;
1507 if (fcgi_gen_response(c, rc->commit_id) == -1)
1508 goto done;
1509 if (fcgi_gen_response(c, "&headref=") == -1)
1510 goto done;
1511 if (fcgi_gen_response(c, qs->headref) == -1)
1512 goto done;
1513 if (fcgi_gen_response(c, "'>") == -1)
1514 goto done;
1515 if (fcgi_gen_response(c, "tree") == -1)
1516 goto done;
1517 if (fcgi_gen_response(c, "</a>") == -1)
1518 goto done;
1519 if (fcgi_gen_response(c, "</div>\n") == -1)
1520 goto done;
1521 if (fcgi_gen_response(c, "</div>\n") == -1)
1522 goto done;
1523 if (fcgi_gen_response(c,
1524 "<div id='dotted_line'></div>\n") == -1)
1525 goto done;
1527 free(age);
1528 age = NULL;
1531 if (t->next_id || t->prev_id) {
1532 error = gotweb_render_navs(c);
1533 if (error)
1534 goto done;
1536 fcgi_gen_response(c, "</div>\n");
1537 done:
1538 free(age);
1539 return error;
1542 static const struct got_error *
1543 gotweb_render_commits(struct request *c)
1545 const struct got_error *error = NULL;
1546 struct repo_commit *rc = NULL;
1547 struct server *srv = c->srv;
1548 struct transport *t = c->t;
1549 struct querystring *qs = t->qs;
1550 struct repo_dir *repo_dir = t->repo_dir;
1551 char *age = NULL, *author = NULL;
1552 /* int commit_found = 0; */
1554 if (fcgi_gen_response(c, "<div id='commits_title_wrapper'>\n") == -1)
1555 goto done;
1556 if (fcgi_gen_response(c,
1557 "<div id='commits_title'>Commits</div>\n") == -1)
1558 goto done;
1559 if (fcgi_gen_response(c, "</div>\n") == -1)
1560 goto done;
1562 if (fcgi_gen_response(c, "<div id='commits_content'>\n") == -1)
1563 goto done;
1565 error = got_get_repo_commits(c, srv->max_commits_display);
1566 if (error)
1567 goto done;
1569 TAILQ_FOREACH(rc, &t->repo_commits, entry) {
1570 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1571 if (error)
1572 goto done;
1573 error = gotweb_escape_html(&author, rc->author);
1574 if (error)
1575 goto done;
1577 if (fcgi_gen_response(c,
1578 "<div id='commits_header_wrapper'>\n") == -1)
1579 goto done;
1580 if (fcgi_gen_response(c, "<div id='commits_header'>\n") == -1)
1581 goto done;
1584 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
1585 "</div>\n") == -1)
1586 goto done;
1587 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
1588 goto done;
1589 if (fcgi_gen_response(c, rc->commit_id) == -1)
1590 goto done;
1591 if (fcgi_gen_response(c, "</div>\n") == -1)
1592 goto done;
1594 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
1595 "</div>\n") == -1)
1596 goto done;
1597 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
1598 goto done;
1599 if (fcgi_gen_response(c, author ? author : "") == -1)
1600 goto done;
1601 if (fcgi_gen_response(c, "</div>\n") == -1)
1602 goto done;
1604 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1605 "</div>\n") == -1)
1606 goto done;
1607 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1608 goto done;
1609 if (fcgi_gen_response(c, age ? age : "") == -1)
1610 goto done;
1611 if (fcgi_gen_response(c, "</div>\n") == -1)
1612 goto done;
1614 if (fcgi_gen_response(c, "</div>\n") == -1)
1615 goto done;
1616 if (fcgi_gen_response(c, "</div>\n") == -1)
1617 goto done;
1619 if (fcgi_gen_response(c,
1620 "<div id='dotted_line'></div>\n") == -1)
1621 goto done;
1622 if (fcgi_gen_response(c, "<div id='commit'>\n") == -1)
1623 goto done;
1625 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1626 goto done;
1628 if (fcgi_gen_response(c, "</div>\n") == -1)
1629 goto done;
1630 if (fcgi_gen_response(c, "</div>\n") == -1)
1631 goto done;
1633 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1634 goto done;
1635 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1636 goto done;
1637 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1638 goto done;
1639 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1640 goto done;
1641 if (fcgi_gen_response(c, "&path=") == -1)
1642 goto done;
1643 if (fcgi_gen_response(c, repo_dir->name) == -1)
1644 goto done;
1645 if (fcgi_gen_response(c, "&action=diff&commit=") == -1)
1646 goto done;
1647 if (fcgi_gen_response(c, rc->commit_id) == -1)
1648 goto done;
1649 if (fcgi_gen_response(c, "'>") == -1)
1650 goto done;
1651 if (fcgi_gen_response(c, "diff") == -1)
1652 goto done;
1653 if (fcgi_gen_response(c, "</a>") == -1)
1654 goto done;
1656 if (fcgi_gen_response(c, " | ") == -1)
1657 goto done;
1659 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1660 goto done;
1661 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1662 goto done;
1663 if (fcgi_gen_response(c, "&path=") == -1)
1664 goto done;
1665 if (fcgi_gen_response(c, repo_dir->name) == -1)
1666 goto done;
1667 if (fcgi_gen_response(c, "&action=tree&commit=") == -1)
1668 goto done;
1669 if (fcgi_gen_response(c, rc->commit_id) == -1)
1670 goto done;
1671 if (fcgi_gen_response(c, "'>") == -1)
1672 goto done;
1673 if (fcgi_gen_response(c, "tree") == -1)
1674 goto done;
1675 if (fcgi_gen_response(c, "</a>") == -1)
1676 goto done;
1677 if (fcgi_gen_response(c, "</div>\n") == -1)
1678 goto done;
1679 if (fcgi_gen_response(c, "</div>\n") == -1)
1680 goto done;
1681 if (fcgi_gen_response(c,
1682 "<div id='dotted_line'></div>\n") == -1)
1683 goto done;
1684 free(age);
1685 age = NULL;
1686 free(author);
1687 author = NULL;
1690 if (t->next_id || t->prev_id) {
1691 error = gotweb_render_navs(c);
1692 if (error)
1693 goto done;
1695 if (fcgi_gen_response(c, "</div>\n") == -1)
1696 goto done;
1697 fcgi_gen_response(c, "</div>\n");
1698 done:
1699 free(age);
1700 return error;
1703 static const struct got_error *
1704 gotweb_render_branches(struct request *c)
1706 const struct got_error *error = NULL;
1707 struct got_reflist_head refs;
1708 struct got_reflist_entry *re;
1709 struct transport *t = c->t;
1710 struct querystring *qs = t->qs;
1711 struct got_repository *repo = t->repo;
1712 char *age = NULL;
1714 TAILQ_INIT(&refs);
1716 error = got_ref_list(&refs, repo, "refs/heads",
1717 got_ref_cmp_by_name, NULL);
1718 if (error)
1719 goto done;
1721 if (fcgi_gen_response(c, "<div id='branches_title_wrapper'>\n") == -1)
1722 goto done;
1723 if (fcgi_gen_response(c,
1724 "<div id='branches_title'>Branches</div>\n") == -1)
1725 goto done;
1726 if (fcgi_gen_response(c, "</div>\n") == -1)
1727 goto done;
1729 if (fcgi_gen_response(c, "<div id='branches_content'>\n") == -1)
1730 goto done;
1732 TAILQ_FOREACH(re, &refs, entry) {
1733 char *refname = NULL;
1735 if (got_ref_is_symbolic(re->ref))
1736 continue;
1738 refname = strdup(got_ref_get_name(re->ref));
1739 if (refname == NULL) {
1740 error = got_error_from_errno("strdup");
1741 goto done;
1743 if (strncmp(refname, "refs/heads/", 11) != 0)
1744 continue;
1746 error = got_get_repo_age(&age, c, qs->path, refname,
1747 TM_DIFF);
1748 if (error)
1749 goto done;
1751 if (strncmp(refname, "refs/heads/", 11) == 0)
1752 refname += 11;
1754 if (fcgi_gen_response(c, "<div id='branches_wrapper'>") == -1)
1755 goto done;
1757 if (fcgi_gen_response(c, "<div id='branches_age'>") == -1)
1758 goto done;
1759 if (fcgi_gen_response(c, age ? age : "") == -1)
1760 goto done;
1761 if (fcgi_gen_response(c, "</div>\n") == -1)
1762 goto done;
1764 if (fcgi_gen_response(c, "<div id='branches_space'>") == -1)
1765 goto done;
1766 if (fcgi_gen_response(c, "&nbsp;") == -1)
1767 goto done;
1768 if (fcgi_gen_response(c, "</div>\n") == -1)
1769 goto done;
1771 if (fcgi_gen_response(c, "<div id='branch'>") == -1)
1772 goto done;
1773 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1774 goto done;
1775 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1776 goto done;
1777 if (fcgi_gen_response(c, "&path=") == -1)
1778 goto done;
1779 if (fcgi_gen_response(c, qs->path) == -1)
1780 goto done;
1781 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1782 goto done;
1783 if (fcgi_gen_response(c, refname) == -1)
1784 goto done;
1785 if (fcgi_gen_response(c, "'>") == -1)
1786 goto done;
1787 if (fcgi_gen_response(c, refname) == -1)
1788 goto done;
1789 if (fcgi_gen_response(c, "</a>") == -1)
1790 goto done;
1791 if (fcgi_gen_response(c, "</div>\n") == -1)
1792 goto done;
1794 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
1795 goto done;
1796 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
1797 goto done;
1799 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1800 goto done;
1801 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1802 goto done;
1803 if (fcgi_gen_response(c, "&path=") == -1)
1804 goto done;
1805 if (fcgi_gen_response(c, qs->path) == -1)
1806 goto done;
1807 if (fcgi_gen_response(c, "&action=summary&headref=") == -1)
1808 goto done;
1809 if (fcgi_gen_response(c, refname) == -1)
1810 goto done;
1811 if (fcgi_gen_response(c, "'>") == -1)
1812 goto done;
1813 if (fcgi_gen_response(c, "summary") == -1)
1814 goto done;
1815 if (fcgi_gen_response(c, "</a>") == -1)
1816 goto done;
1818 if (fcgi_gen_response(c, " | ") == -1)
1819 goto done;
1821 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1822 goto done;
1823 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1824 goto done;
1825 if (fcgi_gen_response(c, "&path=") == -1)
1826 goto done;
1827 if (fcgi_gen_response(c, qs->path) == -1)
1828 goto done;
1829 if (fcgi_gen_response(c, "&action=briefs&headref=") == -1)
1830 goto done;
1831 if (fcgi_gen_response(c, refname) == -1)
1832 goto done;
1833 if (fcgi_gen_response(c, "'>") == -1)
1834 goto done;
1835 if (fcgi_gen_response(c, "commit briefs") == -1)
1836 goto done;
1837 if (fcgi_gen_response(c, "</a>") == -1)
1838 goto done;
1840 if (fcgi_gen_response(c, " | ") == -1)
1841 goto done;
1843 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
1844 goto done;
1845 if (fcgi_gen_response(c, qs->index_page_str) == -1)
1846 goto done;
1847 if (fcgi_gen_response(c, "&path=") == -1)
1848 goto done;
1849 if (fcgi_gen_response(c, qs->path) == -1)
1850 goto done;
1851 if (fcgi_gen_response(c, "&action=commits&headref=") == -1)
1852 goto done;
1853 if (fcgi_gen_response(c, refname) == -1)
1854 goto done;
1855 if (fcgi_gen_response(c, "'>") == -1)
1856 goto done;
1857 if (fcgi_gen_response(c, "commits") == -1)
1858 goto done;
1859 if (fcgi_gen_response(c, "</a>") == -1)
1860 goto done;
1862 if (fcgi_gen_response(c, "</div>\n") == -1)
1863 goto done;
1864 if (fcgi_gen_response(c, "</div>\n") == -1)
1865 goto done;
1867 if (fcgi_gen_response(c,
1868 "<div id='dotted_line'></div>\n") == -1)
1869 goto done;
1871 free(age);
1872 age = NULL;
1875 fcgi_gen_response(c, "</div>\n");
1876 done:
1877 return error;
1880 static const struct got_error *
1881 gotweb_render_tree(struct request *c)
1883 const struct got_error *error = NULL;
1884 struct transport *t = c->t;
1885 struct repo_commit *rc = NULL;
1886 char *age = NULL;
1888 error = got_get_repo_commits(c, 1);
1889 if (error)
1890 return error;
1892 rc = TAILQ_FIRST(&t->repo_commits);
1894 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1895 if (error)
1896 goto done;
1898 if (fcgi_gen_response(c, "<div id='tree_title_wrapper'>\n") == -1)
1899 goto done;
1900 if (fcgi_gen_response(c, "<div id='tree_title'>Tree</div>\n") == -1)
1901 goto done;
1902 if (fcgi_gen_response(c, "</div>\n") == -1)
1903 goto done;
1905 if (fcgi_gen_response(c, "<div id='tree_content'>\n") == -1)
1906 goto done;
1908 if (fcgi_gen_response(c, "<div id='tree_header_wrapper'>\n") == -1)
1909 goto done;
1910 if (fcgi_gen_response(c, "<div id='tree_header'>\n") == -1)
1911 goto done;
1913 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
1914 "</div>\n") == -1)
1915 goto done;
1916 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
1917 goto done;
1918 if (fcgi_gen_response(c, rc->tree_id) == -1)
1919 goto done;
1920 if (fcgi_gen_response(c, "</div>\n") == -1)
1921 goto done;
1923 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
1924 "</div>\n") == -1)
1925 goto done;
1926 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
1927 goto done;
1928 if (fcgi_gen_response(c, age ? age : "") == -1)
1929 goto done;
1930 if (fcgi_gen_response(c, "</div>\n") == -1)
1931 goto done;
1933 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
1934 "</div>\n") == -1)
1935 goto done;
1936 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
1937 goto done;
1938 if (fcgi_gen_response(c, rc->commit_msg) == -1)
1939 goto done;
1940 if (fcgi_gen_response(c, "</div>\n") == -1)
1941 goto done;
1943 if (fcgi_gen_response(c, "</div>\n") == -1)
1944 goto done;
1945 if (fcgi_gen_response(c, "</div>\n") == -1)
1946 goto done;
1948 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
1949 goto done;
1950 if (fcgi_gen_response(c, "<div id='tree'>\n") == -1)
1951 goto done;
1953 error = got_output_repo_tree(c);
1954 if (error)
1955 goto done;
1957 fcgi_gen_response(c, "</div>\n");
1958 fcgi_gen_response(c, "</div>\n");
1959 done:
1960 return error;
1963 static const struct got_error *
1964 gotweb_render_diff(struct request *c)
1966 const struct got_error *error = NULL;
1967 struct transport *t = c->t;
1968 struct repo_commit *rc = NULL;
1969 char *age = NULL, *author = NULL;
1971 error = got_get_repo_commits(c, 1);
1972 if (error)
1973 return error;
1975 rc = TAILQ_FIRST(&t->repo_commits);
1977 error = gotweb_get_time_str(&age, rc->committer_time, TM_LONG);
1978 if (error)
1979 goto done;
1980 error = gotweb_escape_html(&author, rc->author);
1981 if (error)
1982 goto done;
1984 if (fcgi_gen_response(c, "<div id='diff_title_wrapper'>\n") == -1)
1985 goto done;
1986 if (fcgi_gen_response(c,
1987 "<div id='diff_title'>Commit Diff</div>\n") == -1)
1988 goto done;
1989 if (fcgi_gen_response(c, "</div>\n") == -1)
1990 goto done;
1992 if (fcgi_gen_response(c, "<div id='diff_content'>\n") == -1)
1993 goto done;
1994 if (fcgi_gen_response(c, "<div id='diff_header_wrapper'>\n") == -1)
1995 goto done;
1996 if (fcgi_gen_response(c, "<div id='diff_header'>\n") == -1)
1997 goto done;
1999 if (fcgi_gen_response(c, "<div id='header_diff_title'>Diff:"
2000 "</div>\n") == -1)
2001 goto done;
2002 if (fcgi_gen_response(c, "<div id='header_diff'>") == -1)
2003 goto done;
2004 if (fcgi_gen_response(c, rc->parent_id) == -1)
2005 goto done;
2006 if (fcgi_gen_response(c, "<br />") == -1)
2007 goto done;
2008 if (fcgi_gen_response(c, rc->commit_id) == -1)
2009 goto done;
2010 if (fcgi_gen_response(c, "</div>\n") == -1)
2011 goto done;
2013 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2014 "</div>\n") == -1)
2015 goto done;
2016 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2017 goto done;
2018 if (fcgi_gen_response(c, rc->commit_id) == -1)
2019 goto done;
2020 if (fcgi_gen_response(c, "</div>\n") == -1)
2021 goto done;
2023 if (fcgi_gen_response(c, "<div id='header_tree_title'>Tree:"
2024 "</div>\n") == -1)
2025 goto done;
2026 if (fcgi_gen_response(c, "<div id='header_tree'>") == -1)
2027 goto done;
2028 if (fcgi_gen_response(c, rc->tree_id) == -1)
2029 goto done;
2030 if (fcgi_gen_response(c, "</div>\n") == -1)
2031 goto done;
2033 if (fcgi_gen_response(c, "<div id='header_author_title'>Author:"
2034 "</div>\n") == -1)
2035 goto done;
2036 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2037 goto done;
2038 if (fcgi_gen_response(c, author ? author : "") == -1)
2039 goto done;
2040 if (fcgi_gen_response(c, "</div>\n") == -1)
2041 goto done;
2043 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2044 "</div>\n") == -1)
2045 goto done;
2046 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2047 goto done;
2048 if (fcgi_gen_response(c, age ? age : "") == -1)
2049 goto done;
2050 if (fcgi_gen_response(c, "</div>\n") == -1)
2051 goto done;
2053 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2054 "</div>\n") == -1)
2055 goto done;
2056 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2057 goto done;
2058 if (fcgi_gen_response(c, rc->commit_msg) == -1)
2059 goto done;
2060 if (fcgi_gen_response(c, "</div>\n") == -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;
2067 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2068 goto done;
2069 if (fcgi_gen_response(c, "<div id='diff'>\n") == -1)
2070 goto done;
2072 error = got_output_repo_diff(c);
2073 if (error)
2074 goto done;
2076 fcgi_gen_response(c, "</div>\n");
2077 done:
2078 fcgi_gen_response(c, "</div>\n");
2079 free(age);
2080 free(author);
2081 return error;
2084 static const struct got_error *
2085 gotweb_render_summary(struct request *c)
2087 const struct got_error *error = NULL;
2088 struct transport *t = c->t;
2089 struct server *srv = c->srv;
2091 if (fcgi_gen_response(c, "<div id='summary_wrapper'>\n") == -1)
2092 goto done;
2094 if (!srv->show_repo_description)
2095 goto owner;
2097 if (fcgi_gen_response(c, "<div id='description_title'>"
2098 "Description:</div>\n") == -1)
2099 goto done;
2100 if (fcgi_gen_response(c, "<div id='description'>") == -1)
2101 goto done;
2102 if (fcgi_gen_response(c, t->repo_dir->description) == -1)
2103 goto done;
2104 if (fcgi_gen_response(c, "</div>\n") == -1)
2105 goto done;
2106 owner:
2107 if (!srv->show_repo_owner)
2108 goto last_change;
2110 if (fcgi_gen_response(c, "<div id='repo_owner_title'>"
2111 "Owner:</div>\n") == -1)
2112 goto done;
2113 if (fcgi_gen_response(c, "<div id='repo_owner'>") == -1)
2114 goto done;
2115 if (fcgi_gen_response(c, t->repo_dir->owner) == -1)
2116 goto done;
2117 if (fcgi_gen_response(c, "</div>\n") == -1)
2118 goto done;
2119 last_change:
2120 if (!srv->show_repo_age)
2121 goto clone_url;
2123 if (fcgi_gen_response(c, "<div id='last_change_title'>"
2124 "Last Change:</div>\n") == -1)
2125 goto done;
2126 if (fcgi_gen_response(c, "<div id='last_change'>") == -1)
2127 goto done;
2128 if (fcgi_gen_response(c, t->repo_dir->age) == -1)
2129 goto done;
2130 if (fcgi_gen_response(c, "</div>\n") == -1)
2131 goto done;
2132 clone_url:
2133 if (!srv->show_repo_cloneurl)
2134 goto content;
2136 if (fcgi_gen_response(c, "<div id='cloneurl_title'>"
2137 "Clone URL:</div>\n") == -1)
2138 goto done;
2139 if (fcgi_gen_response(c, "<div id='cloneurl'>") == -1)
2140 goto done;
2141 if (fcgi_gen_response(c, t->repo_dir->url) == -1)
2142 goto done;
2143 if (fcgi_gen_response(c, "</div>\n") == -1)
2144 goto done;
2145 content:
2146 if (fcgi_gen_response(c, "</div>\n") == -1)
2147 goto done;
2148 if (fcgi_gen_response(c, "</div>\n") == -1)
2149 goto done;
2151 error = gotweb_render_briefs(c);
2152 if (error) {
2153 log_warnx("%s: %s", __func__, error->msg);
2154 goto done;
2157 error = gotweb_render_tags(c);
2158 if (error) {
2159 log_warnx("%s: %s", __func__, error->msg);
2160 goto done;
2163 error = gotweb_render_branches(c);
2164 if (error)
2165 log_warnx("%s: %s", __func__, error->msg);
2166 done:
2167 return error;
2170 static const struct got_error *
2171 gotweb_render_tag(struct request *c)
2173 const struct got_error *error = NULL;
2174 struct repo_tag *rt = NULL;
2175 struct transport *t = c->t;
2176 char *age = NULL, *author = NULL;
2178 error = got_get_repo_tags(c, 1);
2179 if (error)
2180 goto done;
2182 if (t->tag_count == 0) {
2183 error = got_error_set_errno(GOT_ERR_BAD_OBJ_ID,
2184 "bad commit id");
2185 goto done;
2188 rt = TAILQ_LAST(&t->repo_tags, repo_tags_head);
2190 error = gotweb_get_time_str(&age, rt->tagger_time, TM_LONG);
2191 if (error)
2192 goto done;
2193 error = gotweb_escape_html(&author, rt->tagger);
2194 if (error)
2195 goto done;
2197 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2198 goto done;
2199 if (fcgi_gen_response(c, "<div id='tags_title'>Tag</div>\n") == -1)
2200 goto done;
2201 if (fcgi_gen_response(c, "</div>\n") == -1)
2202 goto done;
2204 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2205 goto done;
2206 if (fcgi_gen_response(c, "<div id='tag_header_wrapper'>\n") == -1)
2207 goto done;
2208 if (fcgi_gen_response(c, "<div id='tag_header'>\n") == -1)
2209 goto done;
2211 if (fcgi_gen_response(c, "<div id='header_commit_title'>Commit:"
2212 "</div>\n") == -1)
2213 goto done;
2214 if (fcgi_gen_response(c, "<div id='header_commit'>") == -1)
2215 goto done;
2216 if (fcgi_gen_response(c, rt->commit_id) == -1)
2217 goto done;
2219 if (strncmp(rt->tag_name, "refs/", 5) == 0)
2220 rt->tag_name += 5;
2222 if (fcgi_gen_response(c, " <span id='refs_str'>(") == -1)
2223 goto done;
2224 if (fcgi_gen_response(c, rt->tag_name) == -1)
2225 goto done;
2226 if (fcgi_gen_response(c, ")</span>") == -1)
2227 goto done;
2229 if (fcgi_gen_response(c, "</div>\n") == -1)
2230 goto done;
2232 if (fcgi_gen_response(c, "<div id='header_author_title'>Tagger:"
2233 "</div>\n") == -1)
2234 goto done;
2235 if (fcgi_gen_response(c, "<div id='header_author'>") == -1)
2236 goto done;
2237 if (fcgi_gen_response(c, author ? author : "") == -1)
2238 goto done;
2239 if (fcgi_gen_response(c, "</div>\n") == -1)
2240 goto done;
2242 if (fcgi_gen_response(c, "<div id='header_age_title'>Date:"
2243 "</div>\n") == -1)
2244 goto done;
2245 if (fcgi_gen_response(c, "<div id='header_age'>") == -1)
2246 goto done;
2247 if (fcgi_gen_response(c, age ? age : "") == -1)
2248 goto done;
2249 if (fcgi_gen_response(c, "</div>\n") == -1)
2250 goto done;
2252 if (fcgi_gen_response(c, "<div id='header_commit_msg_title'>Message:"
2253 "</div>\n") == -1)
2254 goto done;
2255 if (fcgi_gen_response(c, "<div id='header_commit_msg'>") == -1)
2256 goto done;
2257 if (fcgi_gen_response(c, rt->commit_msg) == -1)
2258 goto done;
2259 if (fcgi_gen_response(c, "</div>\n") == -1)
2260 goto done;
2261 if (fcgi_gen_response(c, "</div>\n") == -1)
2262 goto done;
2264 if (fcgi_gen_response(c, "<div id='dotted_line'></div>\n") == -1)
2265 goto done;
2266 if (fcgi_gen_response(c, "<div id='tag_commit'>\n") == -1)
2267 goto done;
2269 if (fcgi_gen_response(c, rt->tag_commit) == -1)
2270 goto done;
2272 if (fcgi_gen_response(c, "</div>\n") == -1)
2273 goto done;
2274 fcgi_gen_response(c, "</div>\n");
2275 done:
2276 free(age);
2277 free(author);
2278 return error;
2281 static const struct got_error *
2282 gotweb_render_tags(struct request *c)
2284 const struct got_error *error = NULL;
2285 struct repo_tag *rt = NULL;
2286 struct server *srv = c->srv;
2287 struct transport *t = c->t;
2288 struct querystring *qs = t->qs;
2289 struct repo_dir *repo_dir = t->repo_dir;
2290 char *newline;
2291 char *age = NULL;
2292 int commit_found = 0;
2294 if (qs->action == BRIEFS) {
2295 qs->action = TAGS;
2296 error = got_get_repo_tags(c, D_MAXSLCOMMDISP);
2297 } else
2298 error = got_get_repo_tags(c, srv->max_commits_display);
2299 if (error)
2300 goto done;
2302 if (fcgi_gen_response(c, "<div id='tags_title_wrapper'>\n") == -1)
2303 goto done;
2304 if (fcgi_gen_response(c,
2305 "<div id='tags_title'>Tags</div>\n") == -1)
2306 goto done;
2307 if (fcgi_gen_response(c, "</div>\n") == -1)
2308 goto done;
2310 if (fcgi_gen_response(c, "<div id='tags_content'>\n") == -1)
2311 goto done;
2313 if (t->tag_count == 0) {
2314 if (fcgi_gen_response(c, "<div id='err_content'>") == -1)
2315 goto done;
2316 if (fcgi_gen_response(c,
2317 "This repository contains no tags\n") == -1)
2318 goto done;
2319 if (fcgi_gen_response(c, "</div>\n") == -1)
2320 goto done;
2321 if (fcgi_gen_response(c, "</div>\n") == -1)
2322 goto done;
2325 TAILQ_FOREACH(rt, &t->repo_tags, entry) {
2326 if (commit_found == 0 && qs->commit != NULL) {
2327 if (strcmp(qs->commit, rt->commit_id) != 0)
2328 continue;
2329 else
2330 commit_found = 1;
2332 error = gotweb_get_time_str(&age, rt->tagger_time, TM_DIFF);
2333 if (error)
2334 goto done;
2335 if (fcgi_gen_response(c, "<div id='tag_age'>") == -1)
2336 goto done;
2337 if (fcgi_gen_response(c, age ? age : "") == -1)
2338 goto done;
2339 if (fcgi_gen_response(c, "</div>\n") == -1)
2340 goto done;
2342 if (fcgi_gen_response(c, "<div id='tag'>") == -1)
2343 goto done;
2344 if (strncmp(rt->tag_name, "refs/tags/", 10) == 0)
2345 rt->tag_name += 10;
2346 if (fcgi_gen_response(c, rt->tag_name) == -1)
2347 goto done;
2348 if (fcgi_gen_response(c, "</div>\n") == -1)
2349 goto done;
2351 if (fcgi_gen_response(c, "<div id='tags_log'>") == -1)
2352 goto done;
2353 if (rt->tag_commit != NULL) {
2354 newline = strchr(rt->tag_commit, '\n');
2355 if (newline)
2356 *newline = '\0';
2359 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2360 goto done;
2361 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2362 goto done;
2363 if (fcgi_gen_response(c, "&path=") == -1)
2364 goto done;
2365 if (fcgi_gen_response(c, repo_dir->name) == -1)
2366 goto done;
2367 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2368 goto done;
2369 if (fcgi_gen_response(c, rt->commit_id) == -1)
2370 goto done;
2371 if (fcgi_gen_response(c, "'>") == -1)
2372 goto done;
2373 if (rt->tag_commit != NULL &&
2374 fcgi_gen_response(c, rt->tag_commit) == -1)
2375 goto done;
2376 if (fcgi_gen_response(c, "</a>") == -1)
2377 goto done;
2378 if (fcgi_gen_response(c, "</div>\n") == -1)
2379 goto done;
2381 if (fcgi_gen_response(c, "<div id='navs_wrapper'>\n") == -1)
2382 goto done;
2383 if (fcgi_gen_response(c, "<div id='navs'>") == -1)
2384 goto done;
2386 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2387 goto done;
2388 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2389 goto done;
2390 if (fcgi_gen_response(c, "&path=") == -1)
2391 goto done;
2392 if (fcgi_gen_response(c, repo_dir->name) == -1)
2393 goto done;
2394 if (fcgi_gen_response(c, "&action=tag&commit=") == -1)
2395 goto done;
2396 if (fcgi_gen_response(c, rt->commit_id) == -1)
2397 goto done;
2398 if (fcgi_gen_response(c, "'>") == -1)
2399 goto done;
2400 if (fcgi_gen_response(c, "tag") == -1)
2401 goto done;
2402 if (fcgi_gen_response(c, "</a>") == -1)
2403 goto done;
2405 if (fcgi_gen_response(c, " | ") == -1)
2406 goto done;
2408 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2409 goto done;
2410 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2411 goto done;
2412 if (fcgi_gen_response(c, "&path=") == -1)
2413 goto done;
2414 if (fcgi_gen_response(c, repo_dir->name) == -1)
2415 goto done;
2416 if (fcgi_gen_response(c, "&action=briefs&commit=") == -1)
2417 goto done;
2418 if (fcgi_gen_response(c, rt->commit_id) == -1)
2419 goto done;
2420 if (fcgi_gen_response(c, "'>") == -1)
2421 goto done;
2422 if (fcgi_gen_response(c, "commit briefs") == -1)
2423 goto done;
2424 if (fcgi_gen_response(c, "</a>") == -1)
2425 goto done;
2427 if (fcgi_gen_response(c, " | ") == -1)
2428 goto done;
2430 if (fcgi_gen_response(c, "<a href='?index_page=") == -1)
2431 goto done;
2432 if (fcgi_gen_response(c, qs->index_page_str) == -1)
2433 goto done;
2434 if (fcgi_gen_response(c, "&path=") == -1)
2435 goto done;
2436 if (fcgi_gen_response(c, repo_dir->name) == -1)
2437 goto done;
2438 if (fcgi_gen_response(c, "&action=commits&commit=") == -1)
2439 goto done;
2440 if (fcgi_gen_response(c, rt->commit_id) == -1)
2441 goto done;
2442 if (fcgi_gen_response(c, "'>") == -1)
2443 goto done;
2444 if (fcgi_gen_response(c, "commits") == -1)
2445 goto done;
2446 if (fcgi_gen_response(c, "</a>") == -1)
2447 goto done;
2449 if (fcgi_gen_response(c, "</div>\n") == -1)
2450 goto done;
2451 if (fcgi_gen_response(c, "</div>\n") == -1)
2452 goto done;
2453 if (fcgi_gen_response(c,
2454 "<div id='dotted_line'></div>\n") == -1)
2455 goto done;
2457 free(age);
2458 age = NULL;
2460 if (t->next_id || t->prev_id) {
2461 error = gotweb_render_navs(c);
2462 if (error)
2463 goto done;
2465 fcgi_gen_response(c, "</div>\n");
2466 done:
2467 free(age);
2468 return error;
2471 const struct got_error *
2472 gotweb_escape_html(char **escaped_html, const char *orig_html)
2474 const struct got_error *error = NULL;
2475 struct escape_pair {
2476 char c;
2477 const char *s;
2478 } esc[] = {
2479 { '>', "&gt;" },
2480 { '<', "&lt;" },
2481 { '&', "&amp;" },
2482 { '"', "&quot;" },
2483 { '\'', "&apos;" },
2484 { '\n', "<br />" },
2486 size_t orig_len, len;
2487 int i, j, x;
2489 orig_len = strlen(orig_html);
2490 len = orig_len;
2491 for (i = 0; i < orig_len; i++) {
2492 for (j = 0; j < nitems(esc); j++) {
2493 if (orig_html[i] != esc[j].c)
2494 continue;
2495 len += strlen(esc[j].s) - 1 /* escaped char */;
2499 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
2500 if (*escaped_html == NULL)
2501 return got_error_from_errno("calloc");
2503 x = 0;
2504 for (i = 0; i < orig_len; i++) {
2505 int escaped = 0;
2506 for (j = 0; j < nitems(esc); j++) {
2507 if (orig_html[i] != esc[j].c)
2508 continue;
2510 if (strlcat(*escaped_html, esc[j].s, len + 1)
2511 >= len + 1) {
2512 error = got_error(GOT_ERR_NO_SPACE);
2513 goto done;
2515 x += strlen(esc[j].s);
2516 escaped = 1;
2517 break;
2519 if (!escaped) {
2520 (*escaped_html)[x] = orig_html[i];
2521 x++;
2524 done:
2525 if (error) {
2526 free(*escaped_html);
2527 *escaped_html = NULL;
2528 } else {
2529 (*escaped_html)[x] = '\0';
2532 return error;
2535 static const struct got_error *
2536 gotweb_load_got_path(struct request *c, struct repo_dir *repo_dir)
2538 const struct got_error *error = NULL;
2539 struct socket *sock = c->sock;
2540 struct server *srv = c->srv;
2541 struct transport *t = c->t;
2542 DIR *dt;
2543 char *dir_test;
2544 int opened = 0;
2546 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2547 GOTWEB_GIT_DIR) == -1)
2548 return got_error_from_errno("asprintf");
2550 dt = opendir(dir_test);
2551 if (dt == NULL) {
2552 free(dir_test);
2553 } else {
2554 repo_dir->path = strdup(dir_test);
2555 if (repo_dir->path == NULL) {
2556 opened = 1;
2557 error = got_error_from_errno("strdup");
2558 goto err;
2560 opened = 1;
2561 goto done;
2564 if (asprintf(&dir_test, "%s/%s/%s", srv->repos_path, repo_dir->name,
2565 GOTWEB_GOT_DIR) == -1) {
2566 dir_test = NULL;
2567 error = got_error_from_errno("asprintf");
2568 goto err;
2571 dt = opendir(dir_test);
2572 if (dt == NULL)
2573 free(dir_test);
2574 else {
2575 opened = 1;
2576 error = got_error(GOT_ERR_NOT_GIT_REPO);
2577 goto err;
2580 if (asprintf(&dir_test, "%s/%s", srv->repos_path,
2581 repo_dir->name) == -1) {
2582 error = got_error_from_errno("asprintf");
2583 dir_test = NULL;
2584 goto err;
2587 repo_dir->path = strdup(dir_test);
2588 if (repo_dir->path == NULL) {
2589 opened = 1;
2590 error = got_error_from_errno("strdup");
2591 goto err;
2594 dt = opendir(dir_test);
2595 if (dt == NULL) {
2596 error = got_error_path(repo_dir->name, GOT_ERR_NOT_GIT_REPO);
2597 goto err;
2598 } else
2599 opened = 1;
2600 done:
2601 error = got_repo_open(&t->repo, repo_dir->path, NULL, sock->pack_fds);
2602 if (error)
2603 goto err;
2604 error = gotweb_get_repo_description(&repo_dir->description, srv,
2605 repo_dir->path);
2606 if (error)
2607 goto err;
2608 error = got_get_repo_owner(&repo_dir->owner, c, repo_dir->path);
2609 if (error)
2610 goto err;
2611 error = got_get_repo_age(&repo_dir->age, c, repo_dir->path,
2612 NULL, TM_DIFF);
2613 if (error)
2614 goto err;
2615 error = gotweb_get_clone_url(&repo_dir->url, srv, repo_dir->path);
2616 err:
2617 free(dir_test);
2618 if (opened)
2619 if (dt != NULL && closedir(dt) == EOF && error == NULL)
2620 error = got_error_from_errno("closedir");
2621 return error;
2624 static const struct got_error *
2625 gotweb_init_repo_dir(struct repo_dir **repo_dir, const char *dir)
2627 const struct got_error *error;
2629 *repo_dir = calloc(1, sizeof(**repo_dir));
2630 if (*repo_dir == NULL)
2631 return got_error_from_errno("calloc");
2633 if (asprintf(&(*repo_dir)->name, "%s", dir) == -1) {
2634 error = got_error_from_errno("asprintf");
2635 free(*repo_dir);
2636 *repo_dir = NULL;
2637 return error;
2639 (*repo_dir)->owner = NULL;
2640 (*repo_dir)->description = NULL;
2641 (*repo_dir)->url = NULL;
2642 (*repo_dir)->age = NULL;
2643 (*repo_dir)->path = NULL;
2645 return NULL;
2648 static const struct got_error *
2649 gotweb_get_repo_description(char **description, struct server *srv, char *dir)
2651 const struct got_error *error = NULL;
2652 FILE *f = NULL;
2653 char *d_file = NULL;
2654 unsigned int len;
2655 size_t n;
2657 *description = NULL;
2658 if (srv->show_repo_description == 0)
2659 return NULL;
2661 if (asprintf(&d_file, "%s/description", dir) == -1)
2662 return got_error_from_errno("asprintf");
2664 f = fopen(d_file, "r");
2665 if (f == NULL) {
2666 if (errno == ENOENT || errno == EACCES)
2667 return NULL;
2668 error = got_error_from_errno2("fopen", d_file);
2669 goto done;
2672 if (fseek(f, 0, SEEK_END) == -1) {
2673 error = got_ferror(f, GOT_ERR_IO);
2674 goto done;
2676 len = ftell(f);
2677 if (len == -1) {
2678 error = got_ferror(f, GOT_ERR_IO);
2679 goto done;
2682 if (len == 0)
2683 goto done;
2685 if (fseek(f, 0, SEEK_SET) == -1) {
2686 error = got_ferror(f, GOT_ERR_IO);
2687 goto done;
2689 *description = calloc(len + 1, sizeof(**description));
2690 if (*description == NULL) {
2691 error = got_error_from_errno("calloc");
2692 goto done;
2695 n = fread(*description, 1, len, f);
2696 if (n == 0 && ferror(f))
2697 error = got_ferror(f, GOT_ERR_IO);
2698 done:
2699 if (f != NULL && fclose(f) == EOF && error == NULL)
2700 error = got_error_from_errno("fclose");
2701 free(d_file);
2702 return error;
2705 static const struct got_error *
2706 gotweb_get_clone_url(char **url, struct server *srv, char *dir)
2708 const struct got_error *error = NULL;
2709 FILE *f;
2710 char *d_file = NULL;
2711 unsigned int len;
2712 size_t n;
2714 *url = NULL;
2716 if (srv->show_repo_cloneurl == 0)
2717 return NULL;
2719 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2720 return got_error_from_errno("asprintf");
2722 f = fopen(d_file, "r");
2723 if (f == NULL) {
2724 if (errno != ENOENT && errno != EACCES)
2725 error = got_error_from_errno2("fopen", d_file);
2726 goto done;
2729 if (fseek(f, 0, SEEK_END) == -1) {
2730 error = got_ferror(f, GOT_ERR_IO);
2731 goto done;
2733 len = ftell(f);
2734 if (len == -1) {
2735 error = got_ferror(f, GOT_ERR_IO);
2736 goto done;
2738 if (len == 0)
2739 goto done;
2741 if (fseek(f, 0, SEEK_SET) == -1) {
2742 error = got_ferror(f, GOT_ERR_IO);
2743 goto done;
2746 *url = calloc(len + 1, sizeof(**url));
2747 if (*url == NULL) {
2748 error = got_error_from_errno("calloc");
2749 goto done;
2752 n = fread(*url, 1, len, f);
2753 if (n == 0 && ferror(f))
2754 error = got_ferror(f, GOT_ERR_IO);
2755 done:
2756 if (f != NULL && fclose(f) == EOF && error == NULL)
2757 error = got_error_from_errno("fclose");
2758 free(d_file);
2759 return error;
2762 const struct got_error *
2763 gotweb_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2765 struct tm tm;
2766 long long diff_time;
2767 const char *years = "years ago", *months = "months ago";
2768 const char *weeks = "weeks ago", *days = "days ago";
2769 const char *hours = "hours ago", *minutes = "minutes ago";
2770 const char *seconds = "seconds ago", *now = "right now";
2771 char *s;
2772 char datebuf[29];
2774 *repo_age = NULL;
2776 switch (ref_tm) {
2777 case TM_DIFF:
2778 diff_time = time(NULL) - committer_time;
2779 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2780 if (asprintf(repo_age, "%lld %s",
2781 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2782 return got_error_from_errno("asprintf");
2783 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2784 if (asprintf(repo_age, "%lld %s",
2785 (diff_time / 60 / 60 / 24 / (365 / 12)),
2786 months) == -1)
2787 return got_error_from_errno("asprintf");
2788 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2789 if (asprintf(repo_age, "%lld %s",
2790 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2791 return got_error_from_errno("asprintf");
2792 } else if (diff_time > 60 * 60 * 24 * 2) {
2793 if (asprintf(repo_age, "%lld %s",
2794 (diff_time / 60 / 60 / 24), days) == -1)
2795 return got_error_from_errno("asprintf");
2796 } else if (diff_time > 60 * 60 * 2) {
2797 if (asprintf(repo_age, "%lld %s",
2798 (diff_time / 60 / 60), hours) == -1)
2799 return got_error_from_errno("asprintf");
2800 } else if (diff_time > 60 * 2) {
2801 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2802 minutes) == -1)
2803 return got_error_from_errno("asprintf");
2804 } else if (diff_time > 2) {
2805 if (asprintf(repo_age, "%lld %s", diff_time,
2806 seconds) == -1)
2807 return got_error_from_errno("asprintf");
2808 } else {
2809 if (asprintf(repo_age, "%s", now) == -1)
2810 return got_error_from_errno("asprintf");
2812 break;
2813 case TM_LONG:
2814 if (gmtime_r(&committer_time, &tm) == NULL)
2815 return got_error_from_errno("gmtime_r");
2817 s = asctime_r(&tm, datebuf);
2818 if (s == NULL)
2819 return got_error_from_errno("asctime_r");
2821 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2822 return got_error_from_errno("asprintf");
2823 break;
2825 return NULL;