2 * Copyright (c) 2019, 2020 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2018, 2019 Stefan Sperling <stsp@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <sys/queue.h>
20 #include <sys/types.h>
34 #include <got_object.h>
35 #include <got_reference.h>
36 #include <got_repository.h>
38 #include <got_cancel.h>
39 #include <got_worktree.h>
41 #include <got_commit_graph.h>
42 #include <got_blame.h>
43 #include <got_privsep.h>
44 #include <got_opentemp.h>
51 #include "gotweb_ui.h"
54 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
58 TAILQ_HEAD(headers, gw_header) gw_headers;
59 TAILQ_HEAD(dirs, gw_dir) gw_dirs;
60 struct gw_dir *gw_dir;
61 struct gotweb_conf *gw_conf;
62 struct ktemplate *gw_tmpl;
63 struct khtmlreq *gw_html_req;
74 unsigned int repos_total;
79 TAILQ_ENTRY(gw_header) entry;
80 struct got_repository *repo;
81 struct got_reflist_head refs;
82 struct got_commit_object *commit;
83 struct got_object_id *id;
87 char *commit_id; /* id_str1 */
88 char *parent_id; /* id_str2 */
93 time_t committer_time;
97 TAILQ_ENTRY(gw_dir) entry;
138 static const char *const gw_templs[TEMPL__MAX] = {
148 static const struct kvalid gw_keys[KEY__ZMAX] = {
149 { kvalid_stringne, "action" },
150 { kvalid_stringne, "commit" },
151 { kvalid_stringne, "file" },
152 { kvalid_stringne, "folder" },
153 { kvalid_stringne, "headref" },
154 { kvalid_int, "page" },
155 { kvalid_stringne, "path" },
158 static struct gw_dir *gw_init_gw_dir(char *);
159 static struct gw_header *gw_init_header(void);
161 static const struct got_error *gw_get_repo_description(char **,
162 struct gw_trans *, char *);
163 static const struct got_error *gw_get_repo_owner(char **, struct gw_trans *,
165 static const struct got_error *gw_get_time_str(char **, time_t, int);
166 static const struct got_error *gw_get_repo_age(char **, struct gw_trans *,
167 char *, char *, int);
168 static const struct got_error *gw_get_file_blame_blob(char **,
170 static const struct got_error *gw_get_file_read_blob(char **, size_t *,
172 static const struct got_error *gw_get_repo_tree(char **, struct gw_trans *);
173 static const struct got_error *gw_get_diff(char **, struct gw_trans *,
175 static const struct got_error *gw_get_repo_tags(char **, struct gw_trans *,
176 struct gw_header *, int, int);
177 static const struct got_error *gw_get_repo_heads(char **, struct gw_trans *);
178 static const struct got_error *gw_get_clone_url(char **, struct gw_trans *,
180 static char *gw_get_site_link(struct gw_trans *);
181 static const struct got_error *gw_html_escape(char **, const char *);
182 static const struct got_error *gw_colordiff_line(char **, char *);
184 static char *gw_gen_commit_header(char *, char*);
185 static char *gw_gen_diff_header(char *, char*);
186 static char *gw_gen_author_header(const char *);
187 static char *gw_gen_committer_header(char *);
188 static char *gw_gen_commit_msg_header(char *);
189 static char *gw_gen_tree_header(char *);
191 static void gw_free_headers(struct gw_header *);
192 static const struct got_error* gw_display_open(struct gw_trans *, enum khttp,
194 static const struct got_error* gw_display_index(struct gw_trans *);
195 static void gw_display_error(struct gw_trans *,
196 const struct got_error *);
198 static int gw_template(size_t, void *);
200 static const struct got_error* gw_get_header(struct gw_trans *,
201 struct gw_header *, int);
202 static const struct got_error* gw_get_commits(struct gw_trans *,
203 struct gw_header *, int);
204 static const struct got_error* gw_get_commit(struct gw_trans *,
206 static const struct got_error* gw_apply_unveil(const char *, const char *);
207 static const struct got_error* gw_blame_cb(void *, int, int,
208 struct got_object_id *);
209 static const struct got_error* gw_load_got_paths(struct gw_trans *);
210 static const struct got_error* gw_load_got_path(struct gw_trans *,
212 static const struct got_error* gw_parse_querystring(struct gw_trans *);
214 static const struct got_error* gw_blame(struct gw_trans *);
215 static const struct got_error* gw_blob(struct gw_trans *);
216 static const struct got_error* gw_diff(struct gw_trans *);
217 static const struct got_error* gw_index(struct gw_trans *);
218 static const struct got_error* gw_commits(struct gw_trans *);
219 static const struct got_error* gw_briefs(struct gw_trans *);
220 static const struct got_error* gw_summary(struct gw_trans *);
221 static const struct got_error* gw_tree(struct gw_trans *);
222 static const struct got_error* gw_tag(struct gw_trans *);
224 struct gw_query_action {
225 unsigned int func_id;
226 const char *func_name;
227 const struct got_error *(*func_main)(struct gw_trans *);
231 enum gw_query_actions {
244 static struct gw_query_action gw_query_funcs[] = {
245 { GW_BLAME, "blame", gw_blame, "gw_tmpl/blame.tmpl" },
246 { GW_BLOB, "blob", NULL, NULL },
247 { GW_BRIEFS, "briefs", gw_briefs, "gw_tmpl/briefs.tmpl" },
248 { GW_COMMITS, "commits", gw_commits, "gw_tmpl/commit.tmpl" },
249 { GW_DIFF, "diff", gw_diff, "gw_tmpl/diff.tmpl" },
250 { GW_ERR, NULL, NULL, "gw_tmpl/err.tmpl" },
251 { GW_INDEX, "index", gw_index, "gw_tmpl/index.tmpl" },
252 { GW_SUMMARY, "summary", gw_summary, "gw_tmpl/summry.tmpl" },
253 { GW_TAG, "tag", gw_tag, "gw_tmpl/tag.tmpl" },
254 { GW_TREE, "tree", gw_tree, "gw_tmpl/tree.tmpl" },
257 static const struct got_error *
258 gw_kcgi_error(enum kcgi_err kerr)
263 if (kerr == KCGI_EXIT || kerr == KCGI_HUP)
264 return got_error(GOT_ERR_CANCELLED);
266 if (kerr == KCGI_ENOMEM)
267 return got_error_set_errno(ENOMEM, kcgi_strerror(kerr));
269 if (kerr == KCGI_ENFILE)
270 return got_error_set_errno(ENFILE, kcgi_strerror(kerr));
272 if (kerr == KCGI_EAGAIN)
273 return got_error_set_errno(EAGAIN, kcgi_strerror(kerr));
275 if (kerr == KCGI_FORM)
276 return got_error_msg(GOT_ERR_IO, kcgi_strerror(kerr));
278 return got_error_from_errno(kcgi_strerror(kerr));
281 static const struct got_error *
282 gw_apply_unveil(const char *repo_path, const char *repo_file)
284 const struct got_error *err;
286 if (repo_path && repo_file) {
288 if (asprintf(&full_path, "%s/%s", repo_path, repo_file) == -1)
289 return got_error_from_errno("asprintf unveil");
290 if (unveil(full_path, "r") != 0)
291 return got_error_from_errno2("unveil", full_path);
294 if (repo_path && unveil(repo_path, "r") != 0)
295 return got_error_from_errno2("unveil", repo_path);
297 if (unveil("/tmp", "rwc") != 0)
298 return got_error_from_errno2("unveil", "/tmp");
300 err = got_privsep_unveil_exec_helpers();
304 if (unveil(NULL, NULL) != 0)
305 return got_error_from_errno("unveil");
310 static const struct got_error *
311 gw_empty_string(char **s)
315 return got_error_from_errno("strdup");
320 isbinary(const char *buf, size_t n)
322 return (memchr(buf, '\0', n) != NULL);
326 static const struct got_error *
327 gw_blame(struct gw_trans *gw_trans)
329 const struct got_error *error = NULL;
330 struct gw_header *header = NULL;
331 char *blame = NULL, *blame_html = NULL, *blame_html_disp = NULL;
332 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
335 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
337 return got_error_from_errno("pledge");
339 if ((header = gw_init_header()) == NULL)
340 return got_error_from_errno("malloc");
342 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
346 error = gw_get_header(gw_trans, header, 1);
350 error = gw_get_file_blame_blob(&blame_html, gw_trans);
354 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
357 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
358 error = got_error_from_errno("asprintf");
362 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
365 if (asprintf(&blame_html_disp, blame_header, age_html,
366 gw_gen_commit_msg_header(escaped_commit_msg), blame_html) == -1) {
367 error = got_error_from_errno("asprintf");
371 if (asprintf(&blame, blame_wrapper, blame_html_disp) == -1) {
372 error = got_error_from_errno("asprintf");
376 kerr = khttp_puts(gw_trans->gw_req, blame);
378 error = gw_kcgi_error(kerr);
380 got_ref_list_free(&header->refs);
381 gw_free_headers(header);
382 free(blame_html_disp);
385 free(escaped_commit_msg);
389 static const struct got_error *
390 gw_blob(struct gw_trans *gw_trans)
392 const struct got_error *error = NULL;
393 struct gw_header *header = NULL;
394 char *content = NULL;
398 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
400 return got_error_from_errno("pledge");
402 if ((header = gw_init_header()) == NULL)
403 return got_error_from_errno("malloc");
405 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
409 error = gw_get_header(gw_trans, header, 1);
413 error = gw_get_file_read_blob(&content, &filesize, gw_trans);
417 if (isbinary(content, filesize))
418 gw_trans->mime = KMIME_APP_OCTET_STREAM;
420 gw_trans->mime = KMIME_TEXT_PLAIN;
422 error = gw_display_index(gw_trans);
426 kerr = khttp_write(gw_trans->gw_req, content, filesize);
428 error = gw_kcgi_error(kerr);
430 got_ref_list_free(&header->refs);
431 gw_free_headers(header);
436 static const struct got_error *
437 gw_diff(struct gw_trans *gw_trans)
439 const struct got_error *error = NULL;
440 struct gw_header *header = NULL;
441 char *diff = NULL, *diff_html = NULL, *diff_html_disp = NULL;
442 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
445 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
447 return got_error_from_errno("pledge");
449 if ((header = gw_init_header()) == NULL)
450 return got_error_from_errno("malloc");
452 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
456 error = gw_get_header(gw_trans, header, 1);
460 error = gw_get_diff(&diff_html, gw_trans, header);
464 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
467 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
468 error = got_error_from_errno("asprintf");
471 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
474 if (asprintf(&diff_html_disp, diff_header,
475 gw_gen_diff_header(header->parent_id, header->commit_id),
476 gw_gen_commit_header(header->commit_id, header->refs_str),
477 gw_gen_tree_header(header->tree_id),
478 gw_gen_author_header(header->author),
479 gw_gen_committer_header(header->committer), age_html,
480 gw_gen_commit_msg_header(escaped_commit_msg),
481 diff_html ? diff_html : "") == -1) {
482 error = got_error_from_errno("asprintf");
486 if (asprintf(&diff, diff_wrapper, diff_html_disp) == -1) {
487 error = got_error_from_errno("asprintf");
491 kerr = khttp_puts(gw_trans->gw_req, diff);
493 error = gw_kcgi_error(kerr);
495 got_ref_list_free(&header->refs);
496 gw_free_headers(header);
497 free(diff_html_disp);
502 free(escaped_commit_msg);
506 static const struct got_error *
507 gw_index(struct gw_trans *gw_trans)
509 const struct got_error *error = NULL;
510 struct gw_dir *gw_dir = NULL;
511 char *html, *navs, *next, *prev;
512 unsigned int prev_disp = 0, next_disp = 1, dir_c = 0;
515 if (pledge("stdio rpath proc exec sendfd unveil",
517 error = got_error_from_errno("pledge");
521 error = gw_apply_unveil(gw_trans->gw_conf->got_repos_path, NULL);
525 error = gw_load_got_paths(gw_trans);
529 kerr = khttp_puts(gw_trans->gw_req, index_projects_header);
531 return gw_kcgi_error(kerr);
533 if (TAILQ_EMPTY(&gw_trans->gw_dirs)) {
534 if (asprintf(&html, index_projects_empty,
535 gw_trans->gw_conf->got_repos_path) == -1)
536 return got_error_from_errno("asprintf");
537 kerr = khttp_puts(gw_trans->gw_req, html);
539 error = gw_kcgi_error(kerr);
544 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry)
547 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry) {
548 if (gw_trans->page > 0 && (gw_trans->page *
549 gw_trans->gw_conf->got_max_repos_display) > prev_disp) {
558 if(asprintf(&navs, index_navs, gw_dir->name, gw_dir->name,
559 gw_dir->name, gw_dir->name) == -1)
560 return got_error_from_errno("asprintf");
562 if (asprintf(&html, index_projects, gw_dir->name, gw_dir->name,
563 gw_dir->description, gw_dir->owner ? gw_dir->owner : "",
566 return got_error_from_errno("asprintf");
568 kerr = khttp_puts(gw_trans->gw_req, html);
572 return gw_kcgi_error(kerr);
574 if (gw_trans->gw_conf->got_max_repos_display == 0)
577 if (next_disp == gw_trans->gw_conf->got_max_repos_display) {
578 kerr = khttp_puts(gw_trans->gw_req, np_wrapper_start);
580 return gw_kcgi_error(kerr);
581 } else if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
582 (gw_trans->page > 0) &&
583 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
584 prev_disp == gw_trans->repos_total)) {
585 kerr = khttp_puts(gw_trans->gw_req, np_wrapper_start);
587 return gw_kcgi_error(kerr);
590 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
591 (gw_trans->page > 0) &&
592 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
593 prev_disp == gw_trans->repos_total)) {
594 if (asprintf(&prev, nav_prev, gw_trans->page - 1) == -1)
595 return got_error_from_errno("asprintf");
596 kerr = khttp_puts(gw_trans->gw_req, prev);
599 return gw_kcgi_error(kerr);
602 kerr = khttp_puts(gw_trans->gw_req, div_end);
604 return gw_kcgi_error(kerr);
606 if (gw_trans->gw_conf->got_max_repos_display > 0 &&
607 next_disp == gw_trans->gw_conf->got_max_repos_display &&
608 dir_c != (gw_trans->page + 1) *
609 gw_trans->gw_conf->got_max_repos_display) {
610 if (asprintf(&next, nav_next, gw_trans->page + 1) == -1)
611 return got_error_from_errno("calloc");
612 kerr = khttp_puts(gw_trans->gw_req, next);
615 return gw_kcgi_error(kerr);
616 kerr = khttp_puts(gw_trans->gw_req, div_end);
618 error = gw_kcgi_error(kerr);
623 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
624 (gw_trans->page > 0) &&
625 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
626 prev_disp == gw_trans->repos_total)) {
627 kerr = khttp_puts(gw_trans->gw_req, div_end);
629 return gw_kcgi_error(kerr);
637 static const struct got_error *
638 gw_commits(struct gw_trans *gw_trans)
640 const struct got_error *error = NULL;
641 char *commits_html, *commits_navs_html;
642 struct gw_header *header = NULL, *n_header = NULL;
643 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
646 if ((header = gw_init_header()) == NULL)
647 return got_error_from_errno("malloc");
649 if (pledge("stdio rpath proc exec sendfd unveil",
651 error = got_error_from_errno("pledge");
655 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
659 error = gw_get_header(gw_trans, header,
660 gw_trans->gw_conf->got_max_commits_display);
664 kerr = khttp_puts(gw_trans->gw_req, commits_wrapper);
665 if (kerr != KCGI_OK) {
666 error = gw_kcgi_error(kerr);
669 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
670 if (asprintf(&commits_navs_html, commits_navs,
671 gw_trans->repo_name, n_header->commit_id,
672 gw_trans->repo_name, n_header->commit_id,
673 gw_trans->repo_name, n_header->commit_id) == -1) {
674 error = got_error_from_errno("asprintf");
677 error = gw_get_time_str(&age, n_header->committer_time,
681 if (asprintf(&age_html, header_age_html, age ? age : "")
683 error = got_error_from_errno("asprintf");
686 error = gw_html_escape(&escaped_commit_msg,
687 n_header->commit_msg);
690 if (asprintf(&commits_html, commits_line,
691 gw_gen_commit_header(n_header->commit_id,
693 gw_gen_author_header(n_header->author),
694 gw_gen_committer_header(n_header->committer),
695 age_html, escaped_commit_msg,
696 commits_navs_html) == -1) {
697 error = got_error_from_errno("asprintf");
704 free(escaped_commit_msg);
705 escaped_commit_msg = NULL;
706 kerr = khttp_puts(gw_trans->gw_req, commits_html);
707 if (kerr != KCGI_OK) {
708 error = gw_kcgi_error(kerr);
712 kerr = khttp_puts(gw_trans->gw_req, div_end);
714 error = gw_kcgi_error(kerr);
716 got_ref_list_free(&header->refs);
717 gw_free_headers(header);
718 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
719 gw_free_headers(n_header);
722 free(escaped_commit_msg);
726 static const struct got_error *
727 gw_briefs(struct gw_trans *gw_trans)
729 const struct got_error *error = NULL;
730 struct gw_header *header = NULL, *n_header = NULL;
731 char *age = NULL, *age_html = NULL;
732 char *href_diff = NULL, *href_tree = NULL;
733 char *newline, *smallerthan;
736 if ((header = gw_init_header()) == NULL)
737 return got_error_from_errno("malloc");
739 if (pledge("stdio rpath proc exec sendfd unveil",
741 error = got_error_from_errno("pledge");
745 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
749 if (gw_trans->action == GW_SUMMARY)
750 error = gw_get_header(gw_trans, header, D_MAXSLCOMMDISP);
752 error = gw_get_header(gw_trans, header,
753 gw_trans->gw_conf->got_max_commits_display);
757 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
758 error = gw_get_time_str(&age, n_header->committer_time,
764 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
765 KATTR_ID, "briefs_wrapper", KATTR__MAX);
770 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
771 KATTR_ID, "briefs_age", KATTR__MAX);
774 if (asprintf(&age_html, "%s", age ? age : "") == -1) {
775 error = got_error_from_errno("asprintf");
778 kerr = khtml_puts(gw_trans->gw_html_req, age_html);
781 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
786 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
787 KATTR_ID, "briefs_author", KATTR__MAX);
790 smallerthan = strchr(n_header->author, '<');
793 kerr = khtml_puts(gw_trans->gw_html_req, n_header->author);
796 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
801 if (asprintf(&href_diff, "?path=%s&action=diff&commit=%s",
802 gw_trans->repo_name, n_header->commit_id) == -1) {
803 error = got_error_from_errno("asprintf");
806 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
807 KATTR_ID, "briefs_log", KATTR__MAX);
810 newline = strchr(n_header->commit_msg, '\n');
813 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
814 KATTR_HREF, href_diff, KATTR__MAX);
817 kerr = khtml_puts(gw_trans->gw_html_req, n_header->commit_msg);
820 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
825 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
826 KATTR_ID, "navs_wrapper", KATTR__MAX);
829 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
830 KATTR_ID, "navs", KATTR__MAX);
833 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
834 KATTR_HREF, href_diff, KATTR__MAX);
837 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
840 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
844 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
849 if (asprintf(&href_tree, "?path=%s&action=tree&commit=%s",
850 gw_trans->repo_name, n_header->commit_id) == -1) {
851 error = got_error_from_errno("asprintf");
854 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
855 KATTR_HREF, href_tree, KATTR__MAX);
858 khtml_puts(gw_trans->gw_html_req, "tree");
859 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
862 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
867 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
868 KATTR_ID, "dotted_line", KATTR__MAX);
871 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
885 got_ref_list_free(&header->refs);
886 gw_free_headers(header);
887 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
888 gw_free_headers(n_header);
896 static const struct got_error *
897 gw_summary(struct gw_trans *gw_trans)
899 const struct got_error *error = NULL;
900 char *age = NULL, *tags = NULL, *heads = NULL;
903 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
904 return got_error_from_errno("pledge");
906 /* unveil is applied with gw_briefs below */
908 /* summary wrapper */
909 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
910 "summary_wrapper", KATTR__MAX);
912 return gw_kcgi_error(kerr);
915 if (gw_trans->gw_conf->got_show_repo_description &&
916 gw_trans->gw_dir->description != NULL &&
917 (strcmp(gw_trans->gw_dir->description, "") != 0)) {
918 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
919 KATTR_ID, "description_title", KATTR__MAX);
920 if (kerr != KCGI_OK) {
921 error = gw_kcgi_error(kerr);
924 kerr = khtml_puts(gw_trans->gw_html_req, "Description: ");
925 if (kerr != KCGI_OK) {
926 error = gw_kcgi_error(kerr);
929 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
930 if (kerr != KCGI_OK) {
931 error = gw_kcgi_error(kerr);
934 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
935 KATTR_ID, "description", KATTR__MAX);
936 if (kerr != KCGI_OK) {
937 error = gw_kcgi_error(kerr);
940 kerr = khtml_puts(gw_trans->gw_html_req,
941 gw_trans->gw_dir->description);
942 if (kerr != KCGI_OK) {
943 error = gw_kcgi_error(kerr);
946 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
947 if (kerr != KCGI_OK) {
948 error = gw_kcgi_error(kerr);
954 if (gw_trans->gw_conf->got_show_repo_owner &&
955 gw_trans->gw_dir->owner != NULL) {
956 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
957 KATTR_ID, "repo_owner_title", KATTR__MAX);
958 if (kerr != KCGI_OK) {
959 error = gw_kcgi_error(kerr);
962 kerr = khtml_puts(gw_trans->gw_html_req, "Owner: ");
963 if (kerr != KCGI_OK) {
964 error = gw_kcgi_error(kerr);
967 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
968 if (kerr != KCGI_OK) {
969 error = gw_kcgi_error(kerr);
972 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
973 KATTR_ID, "repo_owner", KATTR__MAX);
974 if (kerr != KCGI_OK) {
975 error = gw_kcgi_error(kerr);
978 kerr = khtml_puts(gw_trans->gw_html_req,
979 gw_trans->gw_dir->owner);
980 if (kerr != KCGI_OK) {
981 error = gw_kcgi_error(kerr);
984 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
985 if (kerr != KCGI_OK) {
986 error = gw_kcgi_error(kerr);
992 if (gw_trans->gw_conf->got_show_repo_age) {
993 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
994 "refs/heads", TM_LONG);
998 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
999 KATTR_ID, "last_change_title", KATTR__MAX);
1000 if (kerr != KCGI_OK) {
1001 error = gw_kcgi_error(kerr);
1004 kerr = khtml_puts(gw_trans->gw_html_req,
1006 if (kerr != KCGI_OK) {
1007 error = gw_kcgi_error(kerr);
1010 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1011 if (kerr != KCGI_OK) {
1012 error = gw_kcgi_error(kerr);
1015 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1016 KATTR_ID, "last_change", KATTR__MAX);
1017 if (kerr != KCGI_OK) {
1018 error = gw_kcgi_error(kerr);
1021 kerr = khtml_puts(gw_trans->gw_html_req, age);
1022 if (kerr != KCGI_OK) {
1023 error = gw_kcgi_error(kerr);
1026 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1027 if (kerr != KCGI_OK) {
1028 error = gw_kcgi_error(kerr);
1035 if (gw_trans->gw_conf->got_show_repo_cloneurl &&
1036 gw_trans->gw_dir->url != NULL &&
1037 (strcmp(gw_trans->gw_dir->url, "") != 0)) {
1038 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1039 KATTR_ID, "cloneurl_title", KATTR__MAX);
1040 if (kerr != KCGI_OK) {
1041 error = gw_kcgi_error(kerr);
1044 kerr = khtml_puts(gw_trans->gw_html_req, "Clone URL: ");
1045 if (kerr != KCGI_OK) {
1046 error = gw_kcgi_error(kerr);
1049 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1050 if (kerr != KCGI_OK) {
1051 error = gw_kcgi_error(kerr);
1054 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1055 KATTR_ID, "cloneurl", KATTR__MAX);
1056 if (kerr != KCGI_OK) {
1057 error = gw_kcgi_error(kerr);
1060 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->gw_dir->url);
1061 if (kerr != KCGI_OK) {
1062 error = gw_kcgi_error(kerr);
1065 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1066 if (kerr != KCGI_OK) {
1067 error = gw_kcgi_error(kerr);
1072 /* close summary wrapper */
1073 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1074 if (kerr != KCGI_OK) {
1075 error = gw_kcgi_error(kerr);
1079 /* commit briefs header */
1080 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1081 "briefs_title_wrapper", KATTR__MAX);
1082 if (kerr != KCGI_OK) {
1083 error = gw_kcgi_error(kerr);
1086 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1087 "briefs_title", KATTR__MAX);
1088 if (kerr != KCGI_OK) {
1089 error = gw_kcgi_error(kerr);
1092 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Briefs");
1093 if (kerr != KCGI_OK) {
1094 error = gw_kcgi_error(kerr);
1097 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1098 if (kerr != KCGI_OK) {
1099 error = gw_kcgi_error(kerr);
1102 error = gw_briefs(gw_trans);
1107 error = gw_get_repo_tags(&tags, gw_trans, NULL, D_MAXSLCOMMDISP,
1112 if (tags != NULL && strcmp(tags, "") != 0) {
1113 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1114 KATTR_ID, "summary_tags_title_wrapper", KATTR__MAX);
1115 if (kerr != KCGI_OK) {
1116 error = gw_kcgi_error(kerr);
1119 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1120 KATTR_ID, "summary_tags_title", KATTR__MAX);
1121 if (kerr != KCGI_OK) {
1122 error = gw_kcgi_error(kerr);
1125 kerr = khtml_puts(gw_trans->gw_html_req, "Tags");
1126 if (kerr != KCGI_OK) {
1127 error = gw_kcgi_error(kerr);
1130 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1131 if (kerr != KCGI_OK) {
1132 error = gw_kcgi_error(kerr);
1135 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1136 KATTR_ID, "summary_tags_content", KATTR__MAX);
1137 if (kerr != KCGI_OK) {
1138 error = gw_kcgi_error(kerr);
1141 kerr = khttp_puts(gw_trans->gw_req, tags);
1142 if (kerr != KCGI_OK) {
1143 error = gw_kcgi_error(kerr);
1146 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1147 if (kerr != KCGI_OK) {
1148 error = gw_kcgi_error(kerr);
1154 error = gw_get_repo_heads(&heads, gw_trans);
1157 if (heads != NULL && strcmp(heads, "") != 0) {
1158 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1159 KATTR_ID, "summary_heads_title_wrapper", KATTR__MAX);
1160 if (kerr != KCGI_OK) {
1161 error = gw_kcgi_error(kerr);
1164 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1165 KATTR_ID, "summary_heads_title", KATTR__MAX);
1166 if (kerr != KCGI_OK) {
1167 error = gw_kcgi_error(kerr);
1170 kerr = khtml_puts(gw_trans->gw_html_req, "Heads");
1171 if (kerr != KCGI_OK) {
1172 error = gw_kcgi_error(kerr);
1175 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1176 if (kerr != KCGI_OK) {
1177 error = gw_kcgi_error(kerr);
1180 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1181 KATTR_ID, "summary_heads_content", KATTR__MAX);
1182 if (kerr != KCGI_OK) {
1183 error = gw_kcgi_error(kerr);
1186 kerr = khttp_puts(gw_trans->gw_req, heads);
1187 if (kerr != KCGI_OK) {
1188 error = gw_kcgi_error(kerr);
1191 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1192 if (kerr != KCGI_OK) {
1193 error = gw_kcgi_error(kerr);
1204 static const struct got_error *
1205 gw_tree(struct gw_trans *gw_trans)
1207 const struct got_error *error = NULL;
1208 struct gw_header *header = NULL;
1209 char *tree = NULL, *tree_html = NULL, *tree_html_disp = NULL;
1210 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
1213 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1214 return got_error_from_errno("pledge");
1216 if ((header = gw_init_header()) == NULL)
1217 return got_error_from_errno("malloc");
1219 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1223 error = gw_get_header(gw_trans, header, 1);
1227 error = gw_get_repo_tree(&tree_html, gw_trans);
1231 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
1234 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
1235 error = got_error_from_errno("asprintf");
1238 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1241 if (asprintf(&tree_html_disp, tree_header, age_html,
1242 gw_gen_commit_msg_header(escaped_commit_msg),
1243 tree_html ? tree_html : "") == -1) {
1244 error = got_error_from_errno("asprintf");
1248 if (asprintf(&tree, tree_wrapper, tree_html_disp) == -1) {
1249 error = got_error_from_errno("asprintf");
1253 kerr = khttp_puts(gw_trans->gw_req, tree);
1254 if (kerr != KCGI_OK)
1255 error = gw_kcgi_error(kerr);
1257 got_ref_list_free(&header->refs);
1258 gw_free_headers(header);
1259 free(tree_html_disp);
1264 free(escaped_commit_msg);
1268 static const struct got_error *
1269 gw_tag(struct gw_trans *gw_trans)
1271 const struct got_error *error = NULL;
1272 struct gw_header *header = NULL;
1273 char *tag = NULL, *tag_html = NULL, *tag_html_disp = NULL;
1274 char *escaped_commit_msg = NULL;
1277 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1278 return got_error_from_errno("pledge");
1280 if ((header = gw_init_header()) == NULL)
1281 return got_error_from_errno("malloc");
1283 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1287 error = gw_get_header(gw_trans, header, 1);
1291 error = gw_get_repo_tags(&tag_html, gw_trans, header, 1, TAGFULL);
1295 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1298 if (asprintf(&tag_html_disp, tag_header,
1299 gw_gen_commit_header(header->commit_id, header->refs_str),
1300 gw_gen_commit_msg_header(escaped_commit_msg),
1301 tag_html ? tag_html : "") == -1) {
1302 error = got_error_from_errno("asprintf");
1306 if (asprintf(&tag, tag_wrapper, tag_html_disp) == -1) {
1307 error = got_error_from_errno("asprintf");
1311 kerr = khttp_puts(gw_trans->gw_req, tag);
1312 if (kerr != KCGI_OK)
1313 error = gw_kcgi_error(kerr);
1315 got_ref_list_free(&header->refs);
1316 gw_free_headers(header);
1317 free(tag_html_disp);
1320 free(escaped_commit_msg);
1324 static const struct got_error *
1325 gw_load_got_path(struct gw_trans *gw_trans, struct gw_dir *gw_dir)
1327 const struct got_error *error = NULL;
1332 if (asprintf(&dir_test, "%s/%s/%s",
1333 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1334 GOTWEB_GIT_DIR) == -1)
1335 return got_error_from_errno("asprintf");
1337 dt = opendir(dir_test);
1341 gw_dir->path = strdup(dir_test);
1346 if (asprintf(&dir_test, "%s/%s/%s",
1347 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1348 GOTWEB_GOT_DIR) == -1)
1349 return got_error_from_errno("asprintf");
1351 dt = opendir(dir_test);
1356 error = got_error(GOT_ERR_NOT_GIT_REPO);
1360 if (asprintf(&dir_test, "%s/%s",
1361 gw_trans->gw_conf->got_repos_path, gw_dir->name) == -1)
1362 return got_error_from_errno("asprintf");
1364 gw_dir->path = strdup(dir_test);
1367 error = gw_get_repo_description(&gw_dir->description, gw_trans,
1371 error = gw_get_repo_owner(&gw_dir->owner, gw_trans, gw_dir->path);
1374 error = gw_get_repo_age(&gw_dir->age, gw_trans, gw_dir->path,
1375 "refs/heads", TM_DIFF);
1378 error = gw_get_clone_url(&gw_dir->url, gw_trans, gw_dir->path);
1386 static const struct got_error *
1387 gw_load_got_paths(struct gw_trans *gw_trans)
1389 const struct got_error *error = NULL;
1391 struct dirent **sd_dent;
1392 struct gw_dir *gw_dir;
1394 unsigned int d_cnt, d_i;
1396 d = opendir(gw_trans->gw_conf->got_repos_path);
1398 error = got_error_from_errno2("opendir",
1399 gw_trans->gw_conf->got_repos_path);
1403 d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
1406 error = got_error_from_errno2("scandir",
1407 gw_trans->gw_conf->got_repos_path);
1411 for (d_i = 0; d_i < d_cnt; d_i++) {
1412 if (gw_trans->gw_conf->got_max_repos > 0 &&
1413 (d_i - 2) == gw_trans->gw_conf->got_max_repos)
1414 break; /* account for parent and self */
1416 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1417 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1420 if ((gw_dir = gw_init_gw_dir(sd_dent[d_i]->d_name)) == NULL)
1421 return got_error_from_errno("gw_dir malloc");
1423 error = gw_load_got_path(gw_trans, gw_dir);
1424 if (error && error->code == GOT_ERR_NOT_GIT_REPO)
1429 if (lstat(gw_dir->path, &st) == 0 && S_ISDIR(st.st_mode) &&
1430 !got_path_dir_is_empty(gw_dir->path)) {
1431 TAILQ_INSERT_TAIL(&gw_trans->gw_dirs, gw_dir,
1433 gw_trans->repos_total++;
1441 static const struct got_error *
1442 gw_parse_querystring(struct gw_trans *gw_trans)
1444 const struct got_error *error = NULL;
1446 struct gw_query_action *action = NULL;
1449 if (gw_trans->gw_req->fieldnmap[0]) {
1450 error = got_error_from_errno("bad parse");
1452 } else if ((p = gw_trans->gw_req->fieldmap[KEY_PATH])) {
1453 /* define gw_trans->repo_path */
1454 if (asprintf(&gw_trans->repo_name, "%s", p->parsed.s) == -1)
1455 return got_error_from_errno("asprintf");
1457 if (asprintf(&gw_trans->repo_path, "%s/%s",
1458 gw_trans->gw_conf->got_repos_path, p->parsed.s) == -1)
1459 return got_error_from_errno("asprintf");
1461 /* get action and set function */
1462 if ((p = gw_trans->gw_req->fieldmap[KEY_ACTION]))
1463 for (i = 0; i < nitems(gw_query_funcs); i++) {
1464 action = &gw_query_funcs[i];
1465 if (action->func_name == NULL)
1468 if (strcmp(action->func_name,
1469 p->parsed.s) == 0) {
1470 gw_trans->action = i;
1471 if (asprintf(&gw_trans->action_name,
1472 "%s", action->func_name) == -1)
1474 got_error_from_errno(
1483 if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
1484 if (asprintf(&gw_trans->commit, "%s",
1486 return got_error_from_errno("asprintf");
1488 if ((p = gw_trans->gw_req->fieldmap[KEY_FILE]))
1489 if (asprintf(&gw_trans->repo_file, "%s",
1491 return got_error_from_errno("asprintf");
1493 if ((p = gw_trans->gw_req->fieldmap[KEY_FOLDER]))
1494 if (asprintf(&gw_trans->repo_folder, "%s",
1496 return got_error_from_errno("asprintf");
1498 if ((p = gw_trans->gw_req->fieldmap[KEY_HEADREF]))
1499 if (asprintf(&gw_trans->headref, "%s",
1501 return got_error_from_errno("asprintf");
1503 if (action == NULL) {
1504 error = got_error_from_errno("invalid action");
1507 if ((gw_trans->gw_dir =
1508 gw_init_gw_dir(gw_trans->repo_name)) == NULL)
1509 return got_error_from_errno("gw_dir malloc");
1511 error = gw_load_got_path(gw_trans, gw_trans->gw_dir);
1515 gw_trans->action = GW_INDEX;
1517 if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
1518 gw_trans->page = p->parsed.i;
1523 static struct gw_dir *
1524 gw_init_gw_dir(char *dir)
1526 struct gw_dir *gw_dir;
1528 if ((gw_dir = malloc(sizeof(*gw_dir))) == NULL)
1531 if (asprintf(&gw_dir->name, "%s", dir) == -1)
1537 static const struct got_error *
1538 gw_display_open(struct gw_trans *gw_trans, enum khttp code, enum kmime mime)
1542 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_ALLOW], "GET");
1543 if (kerr != KCGI_OK)
1544 return gw_kcgi_error(kerr);
1545 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_STATUS], "%s",
1547 if (kerr != KCGI_OK)
1548 return gw_kcgi_error(kerr);
1549 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_CONTENT_TYPE], "%s",
1551 if (kerr != KCGI_OK)
1552 return gw_kcgi_error(kerr);
1553 kerr = khttp_head(gw_trans->gw_req, "X-Content-Type-Options",
1555 if (kerr != KCGI_OK)
1556 return gw_kcgi_error(kerr);
1557 kerr = khttp_head(gw_trans->gw_req, "X-Frame-Options", "DENY");
1558 if (kerr != KCGI_OK)
1559 return gw_kcgi_error(kerr);
1560 kerr = khttp_head(gw_trans->gw_req, "X-XSS-Protection",
1562 if (kerr != KCGI_OK)
1563 return gw_kcgi_error(kerr);
1565 if (gw_trans->mime == KMIME_APP_OCTET_STREAM) {
1566 kerr = khttp_head(gw_trans->gw_req,
1567 kresps[KRESP_CONTENT_DISPOSITION],
1568 "attachment; filename=%s", gw_trans->repo_file);
1569 if (kerr != KCGI_OK)
1570 return gw_kcgi_error(kerr);
1573 kerr = khttp_body(gw_trans->gw_req);
1574 return gw_kcgi_error(kerr);
1577 static const struct got_error *
1578 gw_display_index(struct gw_trans *gw_trans)
1580 const struct got_error *error;
1583 error = gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
1587 kerr = khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, KHTML_PRETTY);
1589 return gw_kcgi_error(kerr);
1591 if (gw_trans->action != GW_BLOB) {
1592 kerr = khttp_template(gw_trans->gw_req, gw_trans->gw_tmpl,
1593 gw_query_funcs[gw_trans->action].template);
1594 if (kerr != KCGI_OK) {
1595 khtml_close(gw_trans->gw_html_req);
1596 return gw_kcgi_error(kerr);
1600 return gw_kcgi_error(khtml_close(gw_trans->gw_html_req));
1604 gw_display_error(struct gw_trans *gw_trans, const struct got_error *err)
1606 if (gw_display_open(gw_trans, KHTTP_200, gw_trans->mime) != NULL)
1609 if (khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0) != KCGI_OK)
1611 khtml_puts(gw_trans->gw_html_req, err->msg);
1612 khtml_close(gw_trans->gw_html_req);
1616 gw_template(size_t key, void *arg)
1618 const struct got_error *error = NULL;
1620 struct gw_trans *gw_trans = arg;
1621 char *gw_site_link, *img_src = NULL;
1625 kerr = khttp_puts(gw_trans->gw_req, head);
1626 if (kerr != KCGI_OK)
1630 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1631 KATTR_ID, "got_link", KATTR__MAX);
1632 if (kerr != KCGI_OK)
1634 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1635 KATTR_HREF, gw_trans->gw_conf->got_logo_url,
1636 KATTR_TARGET, "_sotd", KATTR__MAX);
1637 if (kerr != KCGI_OK)
1639 if (asprintf(&img_src, "/%s",
1640 gw_trans->gw_conf->got_logo) == -1)
1642 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_IMG,
1643 KATTR_SRC, img_src, KATTR__MAX);
1644 if (kerr != KCGI_OK) {
1648 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1649 if (kerr != KCGI_OK) {
1654 case (TEMPL_SITEPATH):
1655 gw_site_link = gw_get_site_link(gw_trans);
1656 if (gw_site_link != NULL) {
1657 kerr = khttp_puts(gw_trans->gw_req, gw_site_link);
1658 if (kerr != KCGI_OK) {
1666 if (gw_trans->gw_conf->got_site_name != NULL) {
1667 kerr = khtml_puts(gw_trans->gw_html_req,
1668 gw_trans->gw_conf->got_site_name);
1669 if (kerr != KCGI_OK)
1673 case (TEMPL_SEARCH):
1674 kerr = khttp_puts(gw_trans->gw_req, search);
1675 if (kerr != KCGI_OK)
1678 case(TEMPL_SITEOWNER):
1679 if (gw_trans->gw_conf->got_site_owner != NULL &&
1680 gw_trans->gw_conf->got_show_site_owner) {
1681 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1682 KATTR_ID, "site_owner_wrapper", KATTR__MAX);
1683 if (kerr != KCGI_OK)
1685 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1686 KATTR_ID, "site_owner", KATTR__MAX);
1687 if (kerr != KCGI_OK)
1689 kerr = khtml_puts(gw_trans->gw_html_req,
1690 gw_trans->gw_conf->got_site_owner);
1691 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1692 if (kerr != KCGI_OK)
1696 case(TEMPL_CONTENT):
1697 error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
1699 kerr = khttp_puts(gw_trans->gw_req, error->msg);
1700 if (kerr != KCGI_OK)
1711 gw_gen_commit_header(char *str1, char *str2)
1713 char *return_html = NULL, *ref_str = NULL;
1715 if (strcmp(str2, "") != 0) {
1716 if (asprintf(&ref_str, "(%s)", str2) == -1) {
1717 return_html = strdup("");
1721 ref_str = strdup("");
1724 if (asprintf(&return_html, header_commit_html, str1, ref_str) == -1)
1725 return_html = strdup("");
1732 gw_gen_diff_header(char *str1, char *str2)
1734 char *return_html = NULL;
1736 if (asprintf(&return_html, header_diff_html, str1, str2) == -1)
1737 return_html = strdup("");
1743 gw_gen_author_header(const char *str)
1745 char *return_html = NULL;
1747 if (asprintf(&return_html, header_author_html, str) == -1)
1748 return_html = strdup("");
1754 gw_gen_committer_header(char *str)
1756 char *return_html = NULL;
1758 if (asprintf(&return_html, header_committer_html, str) == -1)
1759 return_html = strdup("");
1765 gw_gen_commit_msg_header(char *str)
1767 char *return_html = NULL;
1769 if (asprintf(&return_html, header_commit_msg_html, str) == -1)
1770 return_html = strdup("");
1776 gw_gen_tree_header(char *str)
1778 char *return_html = NULL;
1780 if (asprintf(&return_html, header_tree_html, str) == -1)
1781 return_html = strdup("");
1786 static const struct got_error *
1787 gw_get_repo_description(char **description, struct gw_trans *gw_trans,
1790 const struct got_error *error = NULL;
1792 char *d_file = NULL;
1796 *description = NULL;
1797 if (gw_trans->gw_conf->got_show_repo_description == 0)
1798 return gw_empty_string(description);
1800 if (asprintf(&d_file, "%s/description", dir) == -1)
1801 return got_error_from_errno("asprintf");
1803 f = fopen(d_file, "r");
1805 if (errno == ENOENT || errno == EACCES)
1806 return gw_empty_string(description);
1807 error = got_error_from_errno2("fopen", d_file);
1811 if (fseek(f, 0, SEEK_END) == -1) {
1812 error = got_ferror(f, GOT_ERR_IO);
1817 error = got_ferror(f, GOT_ERR_IO);
1820 if (fseek(f, 0, SEEK_SET) == -1) {
1821 error = got_ferror(f, GOT_ERR_IO);
1824 *description = calloc(len + 1, sizeof(**description));
1825 if (*description == NULL) {
1826 error = got_error_from_errno("calloc");
1830 n = fread(*description, 1, len, f);
1831 if (n == 0 && ferror(f))
1832 error = got_ferror(f, GOT_ERR_IO);
1834 if (f != NULL && fclose(f) == -1 && error == NULL)
1835 error = got_error_from_errno("fclose");
1840 static const struct got_error *
1841 gw_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
1845 char *years = "years ago", *months = "months ago";
1846 char *weeks = "weeks ago", *days = "days ago", *hours = "hours ago";
1847 char *minutes = "minutes ago", *seconds = "seconds ago";
1848 char *now = "right now";
1856 diff_time = time(NULL) - committer_time;
1857 if (diff_time > 60 * 60 * 24 * 365 * 2) {
1858 if (asprintf(repo_age, "%lld %s",
1859 (diff_time / 60 / 60 / 24 / 365), years) == -1)
1860 return got_error_from_errno("asprintf");
1861 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
1862 if (asprintf(repo_age, "%lld %s",
1863 (diff_time / 60 / 60 / 24 / (365 / 12)),
1865 return got_error_from_errno("asprintf");
1866 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
1867 if (asprintf(repo_age, "%lld %s",
1868 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
1869 return got_error_from_errno("asprintf");
1870 } else if (diff_time > 60 * 60 * 24 * 2) {
1871 if (asprintf(repo_age, "%lld %s",
1872 (diff_time / 60 / 60 / 24), days) == -1)
1873 return got_error_from_errno("asprintf");
1874 } else if (diff_time > 60 * 60 * 2) {
1875 if (asprintf(repo_age, "%lld %s",
1876 (diff_time / 60 / 60), hours) == -1)
1877 return got_error_from_errno("asprintf");
1878 } else if (diff_time > 60 * 2) {
1879 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
1881 return got_error_from_errno("asprintf");
1882 } else if (diff_time > 2) {
1883 if (asprintf(repo_age, "%lld %s", diff_time,
1885 return got_error_from_errno("asprintf");
1887 if (asprintf(repo_age, "%s", now) == -1)
1888 return got_error_from_errno("asprintf");
1892 if (gmtime_r(&committer_time, &tm) == NULL)
1893 return got_error_from_errno("gmtime_r");
1895 s = asctime_r(&tm, datebuf);
1897 return got_error_from_errno("asctime_r");
1899 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
1900 return got_error_from_errno("asprintf");
1906 static const struct got_error *
1907 gw_get_repo_age(char **repo_age, struct gw_trans *gw_trans, char *dir,
1908 char *repo_ref, int ref_tm)
1910 const struct got_error *error = NULL;
1911 struct got_object_id *id = NULL;
1912 struct got_repository *repo = NULL;
1913 struct got_commit_object *commit = NULL;
1914 struct got_reflist_head refs;
1915 struct got_reflist_entry *re;
1916 struct got_reference *head_ref;
1918 time_t committer_time = 0, cmp_time = 0;
1919 const char *refname;
1922 SIMPLEQ_INIT(&refs);
1924 if (repo_ref == NULL)
1927 if (strncmp(repo_ref, "refs/heads/", 11) == 0)
1930 if (gw_trans->gw_conf->got_show_repo_age == 0)
1933 error = got_repo_open(&repo, dir, NULL);
1938 error = got_ref_list(&refs, repo, "refs/heads",
1939 got_ref_cmp_by_name, NULL);
1941 error = got_ref_list(&refs, repo, repo_ref,
1942 got_ref_cmp_by_name, NULL);
1946 SIMPLEQ_FOREACH(re, &refs, entry) {
1948 refname = strdup(repo_ref);
1950 refname = got_ref_get_name(re->ref);
1951 error = got_ref_open(&head_ref, repo, refname, 0);
1955 error = got_ref_resolve(&id, repo, head_ref);
1956 got_ref_close(head_ref);
1960 error = got_object_open_as_commit(&commit, repo, id);
1965 got_object_commit_get_committer_time(commit);
1967 if (cmp_time < committer_time)
1968 cmp_time = committer_time;
1971 if (cmp_time != 0) {
1972 committer_time = cmp_time;
1973 error = gw_get_time_str(repo_age, committer_time, ref_tm);
1976 got_ref_list_free(&refs);
1981 static const struct got_error *
1982 gw_get_diff(char **diff_html, struct gw_trans *gw_trans,
1983 struct gw_header *header)
1985 const struct got_error *error;
1987 struct got_object_id *id1 = NULL, *id2 = NULL;
1988 struct buf *diffbuf = NULL;
1989 char *label1 = NULL, *label2 = NULL, *line = NULL;
1990 char *diff_line_html = NULL;
1992 size_t newsize, linesize = 0;
1999 error = buf_alloc(&diffbuf, 0);
2003 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2007 if (strncmp(header->parent_id, "/dev/null", 9) != 0) {
2008 error = got_repo_match_object_id(&id1, &label1,
2009 header->parent_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2014 error = got_repo_match_object_id(&id2, &label2,
2015 header->commit_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2019 error = got_object_get_type(&obj_type, header->repo, id2);
2023 case GOT_OBJ_TYPE_BLOB:
2024 error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, 3, 0,
2027 case GOT_OBJ_TYPE_TREE:
2028 error = got_diff_objects_as_trees(id1, id2, "", "", 3, 0,
2031 case GOT_OBJ_TYPE_COMMIT:
2032 error = got_diff_objects_as_commits(id1, id2, 3, 0,
2036 error = got_error(GOT_ERR_OBJ_TYPE);
2041 if (fseek(f, 0, SEEK_SET) == -1) {
2042 error = got_ferror(f, GOT_ERR_IO);
2046 while ((linelen = getline(&line, &linesize, f)) != -1) {
2048 error = gw_html_escape(&escaped_line, line);
2052 error = gw_colordiff_line(&diff_line_html, escaped_line);
2056 error = buf_puts(&newsize, diffbuf, diff_line_html);
2060 error = buf_puts(&newsize, diffbuf, div_end);
2064 if (linelen == -1 && ferror(f)) {
2065 error = got_error_from_errno("getline");
2069 if (buf_len(diffbuf) > 0) {
2070 error = buf_putc(diffbuf, '\0');
2073 *diff_html = strdup(buf_get(diffbuf));
2074 if (*diff_html == NULL)
2075 error = got_error_from_errno("strdup");
2078 if (f && fclose(f) == -1 && error == NULL)
2079 error = got_error_from_errno("fclose");
2080 free(diff_line_html);
2091 static const struct got_error *
2092 gw_get_repo_owner(char **owner, struct gw_trans *gw_trans, char *dir)
2094 const struct got_error *error = NULL;
2095 struct got_repository *repo;
2096 const char *gitconfig_owner;
2100 if (gw_trans->gw_conf->got_show_repo_owner == 0)
2103 error = got_repo_open(&repo, dir, NULL);
2106 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
2107 if (gitconfig_owner) {
2108 *owner = strdup(gitconfig_owner);
2110 error = got_error_from_errno("strdup");
2112 got_repo_close(repo);
2116 static const struct got_error *
2117 gw_get_clone_url(char **url, struct gw_trans *gw_trans, char *dir)
2119 const struct got_error *error = NULL;
2121 char *d_file = NULL;
2127 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2128 return got_error_from_errno("asprintf");
2130 f = fopen(d_file, "r");
2132 if (errno != ENOENT && errno != EACCES)
2133 error = got_error_from_errno2("fopen", d_file);
2137 if (fseek(f, 0, SEEK_END) == -1) {
2138 error = got_ferror(f, GOT_ERR_IO);
2143 error = got_ferror(f, GOT_ERR_IO);
2146 if (fseek(f, 0, SEEK_SET) == -1) {
2147 error = got_ferror(f, GOT_ERR_IO);
2151 *url = calloc(len + 1, sizeof(**url));
2153 error = got_error_from_errno("calloc");
2157 n = fread(*url, 1, len, f);
2158 if (n == 0 && ferror(f))
2159 error = got_ferror(f, GOT_ERR_IO);
2161 if (f && fclose(f) == -1 && error == NULL)
2162 error = got_error_from_errno("fclose");
2167 static const struct got_error *
2168 gw_get_repo_tags(char **tag_html, struct gw_trans *gw_trans,
2169 struct gw_header *header, int limit, int tag_type)
2171 const struct got_error *error = NULL;
2172 struct got_repository *repo = NULL;
2173 struct got_reflist_head refs;
2174 struct got_reflist_entry *re;
2175 char *tag_row = NULL, *tags_navs_disp = NULL;
2176 char *age = NULL, *age_html = NULL, *newline;
2177 char *escaped_tagger = NULL, *escaped_tag_commit = NULL;
2178 char *id_str = NULL, *refstr = NULL;
2179 char *tag_commit0 = NULL;
2180 struct buf *diffbuf = NULL;
2182 struct got_tag_object *tag = NULL;
2186 SIMPLEQ_INIT(&refs);
2188 error = buf_alloc(&diffbuf, 0);
2192 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2196 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo);
2200 SIMPLEQ_FOREACH(re, &refs, entry) {
2201 const char *refname;
2203 const char *tag_commit;
2205 struct got_object_id *id;
2207 refname = got_ref_get_name(re->ref);
2208 if (strncmp(refname, "refs/tags/", 10) != 0)
2211 refstr = got_ref_to_str(re->ref);
2212 if (refstr == NULL) {
2213 error = got_error_from_errno("got_ref_to_str");
2217 error = got_ref_resolve(&id, repo, re->ref);
2220 error = got_object_open_as_tag(&tag, repo, id);
2225 tagger = got_object_tag_get_tagger(tag);
2226 tagger_time = got_object_tag_get_tagger_time(tag);
2228 error = got_object_id_str(&id_str,
2229 got_object_tag_get_object_id(tag));
2233 tag_commit0 = strdup(got_object_tag_get_message(tag));
2234 if (tag_commit0 == NULL) {
2235 error = got_error_from_errno("strdup");
2239 tag_commit = tag_commit0;
2240 while (*tag_commit == '\n')
2245 newline = strchr(tag_commit, '\n');
2249 error = gw_get_time_str(&age, tagger_time, TM_DIFF);
2253 if (asprintf(&tags_navs_disp, tags_navs,
2254 gw_trans->repo_name, id_str, gw_trans->repo_name,
2255 id_str, gw_trans->repo_name, id_str,
2256 gw_trans->repo_name, id_str) == -1) {
2257 error = got_error_from_errno("asprintf");
2261 if (asprintf(&tag_row, tags_row, age ? age : "",
2262 refname, tag_commit, tags_navs_disp) == -1) {
2263 error = got_error_from_errno("asprintf");
2267 free(tags_navs_disp);
2270 error = gw_get_time_str(&age, tagger_time, TM_LONG);
2273 error = gw_html_escape(&escaped_tagger, tagger);
2276 error = gw_html_escape(&escaped_tag_commit, tag_commit);
2279 if (asprintf(&tag_row, tag_info, age ? age : "",
2280 escaped_tagger, escaped_tag_commit) == -1) {
2281 error = got_error_from_errno("asprintf");
2289 error = buf_puts(&newsize, diffbuf, tag_row);
2293 if (limit && --limit == 0)
2296 got_object_tag_close(tag);
2306 free(escaped_tagger);
2307 escaped_tagger = NULL;
2308 free(escaped_tag_commit);
2309 escaped_tag_commit = NULL;
2316 if (buf_len(diffbuf) > 0) {
2317 error = buf_putc(diffbuf, '\0');
2318 *tag_html = strdup(buf_get(diffbuf));
2319 if (*tag_html == NULL)
2320 error = got_error_from_errno("strdup");
2324 got_object_tag_close(tag);
2329 free(escaped_tagger);
2330 free(escaped_tag_commit);
2334 got_ref_list_free(&refs);
2336 got_repo_close(repo);
2341 gw_free_headers(struct gw_header *header)
2345 if (header->commit != NULL)
2346 got_object_commit_close(header->commit);
2348 got_repo_close(header->repo);
2349 free(header->refs_str);
2350 free(header->commit_id);
2351 free(header->parent_id);
2352 free(header->tree_id);
2353 free(header->committer);
2354 free(header->commit_msg);
2357 static struct gw_header *
2360 struct gw_header *header;
2362 header = malloc(sizeof(*header));
2366 header->repo = NULL;
2367 header->commit = NULL;
2369 header->path = NULL;
2370 SIMPLEQ_INIT(&header->refs);
2375 static const struct got_error *
2376 gw_get_commits(struct gw_trans * gw_trans, struct gw_header *header,
2379 const struct got_error *error = NULL;
2380 struct got_commit_graph *graph = NULL;
2382 error = got_commit_graph_open(&graph, header->path, 0);
2386 error = got_commit_graph_iter_start(graph, header->id, header->repo,
2392 error = got_commit_graph_iter_next(&header->id, graph,
2393 header->repo, NULL, NULL);
2395 if (error->code == GOT_ERR_ITER_COMPLETED)
2399 if (header->id == NULL)
2402 error = got_object_open_as_commit(&header->commit, header->repo,
2407 error = gw_get_commit(gw_trans, header);
2409 struct gw_header *n_header = NULL;
2410 if ((n_header = gw_init_header()) == NULL) {
2411 error = got_error_from_errno("malloc");
2415 n_header->refs_str = strdup(header->refs_str);
2416 n_header->commit_id = strdup(header->commit_id);
2417 n_header->parent_id = strdup(header->parent_id);
2418 n_header->tree_id = strdup(header->tree_id);
2419 n_header->author = strdup(header->author);
2420 n_header->committer = strdup(header->committer);
2421 n_header->commit_msg = strdup(header->commit_msg);
2422 n_header->committer_time = header->committer_time;
2423 TAILQ_INSERT_TAIL(&gw_trans->gw_headers, n_header,
2426 if (error || (limit && --limit == 0))
2431 got_commit_graph_close(graph);
2435 static const struct got_error *
2436 gw_get_commit(struct gw_trans *gw_trans, struct gw_header *header)
2438 const struct got_error *error = NULL;
2439 struct got_reflist_entry *re;
2440 struct got_object_id *id2 = NULL;
2441 struct got_object_qid *parent_id;
2442 char *refs_str = NULL, *commit_msg = NULL, *commit_msg0;
2445 SIMPLEQ_FOREACH(re, &header->refs, entry) {
2448 struct got_tag_object *tag = NULL;
2451 name = got_ref_get_name(re->ref);
2452 if (strcmp(name, GOT_REF_HEAD) == 0)
2454 if (strncmp(name, "refs/", 5) == 0)
2456 if (strncmp(name, "got/", 4) == 0)
2458 if (strncmp(name, "heads/", 6) == 0)
2460 if (strncmp(name, "remotes/", 8) == 0)
2462 if (strncmp(name, "tags/", 5) == 0) {
2463 error = got_object_open_as_tag(&tag, header->repo,
2466 if (error->code != GOT_ERR_OBJ_TYPE)
2469 * Ref points at something other
2476 cmp = got_object_id_cmp(tag ?
2477 got_object_tag_get_object_id(tag) : re->id, header->id);
2479 got_object_tag_close(tag);
2483 if (asprintf(&refs_str, "%s%s%s", s ? s : "",
2484 s ? ", " : "", name) == -1) {
2485 error = got_error_from_errno("asprintf");
2489 header->refs_str = strdup(refs_str);
2493 if (refs_str == NULL)
2494 header->refs_str = strdup("");
2497 error = got_object_id_str(&header->commit_id, header->id);
2501 error = got_object_id_str(&header->tree_id,
2502 got_object_commit_get_tree_id(header->commit));
2506 if (gw_trans->action == GW_DIFF) {
2507 parent_id = SIMPLEQ_FIRST(
2508 got_object_commit_get_parent_ids(header->commit));
2509 if (parent_id != NULL) {
2510 id2 = got_object_id_dup(parent_id->id);
2512 error = got_object_id_str(&header->parent_id, id2);
2517 header->parent_id = strdup("/dev/null");
2519 header->parent_id = strdup("");
2521 header->committer_time =
2522 got_object_commit_get_committer_time(header->commit);
2525 got_object_commit_get_author(header->commit);
2526 error = gw_html_escape(&header->committer,
2527 got_object_commit_get_committer(header->commit));
2531 /* XXX Doesn't the log message require escaping? */
2532 error = got_object_commit_get_logmsg(&commit_msg0, header->commit);
2536 commit_msg = commit_msg0;
2537 while (*commit_msg == '\n')
2540 header->commit_msg = strdup(commit_msg);
2545 static const struct got_error *
2546 gw_get_header(struct gw_trans *gw_trans, struct gw_header *header, int limit)
2548 const struct got_error *error = NULL;
2549 char *in_repo_path = NULL;
2551 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2555 if (gw_trans->commit == NULL) {
2556 struct got_reference *head_ref;
2557 error = got_ref_open(&head_ref, header->repo,
2558 gw_trans->headref, 0);
2562 error = got_ref_resolve(&header->id, header->repo, head_ref);
2563 got_ref_close(head_ref);
2567 error = got_object_open_as_commit(&header->commit,
2568 header->repo, header->id);
2570 struct got_reference *ref;
2571 error = got_ref_open(&ref, header->repo, gw_trans->commit, 0);
2572 if (error == NULL) {
2574 error = got_ref_resolve(&header->id, header->repo, ref);
2578 error = got_object_get_type(&obj_type, header->repo,
2582 if (obj_type == GOT_OBJ_TYPE_TAG) {
2583 struct got_tag_object *tag;
2584 error = got_object_open_as_tag(&tag,
2585 header->repo, header->id);
2588 if (got_object_tag_get_object_type(tag) !=
2589 GOT_OBJ_TYPE_COMMIT) {
2590 got_object_tag_close(tag);
2591 error = got_error(GOT_ERR_OBJ_TYPE);
2595 header->id = got_object_id_dup(
2596 got_object_tag_get_object_id(tag));
2597 if (header->id == NULL)
2598 error = got_error_from_errno(
2599 "got_object_id_dup");
2600 got_object_tag_close(tag);
2603 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
2604 error = got_error(GOT_ERR_OBJ_TYPE);
2607 error = got_object_open_as_commit(&header->commit,
2608 header->repo, header->id);
2612 if (header->commit == NULL) {
2613 error = got_repo_match_object_id_prefix(&header->id,
2614 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2619 error = got_repo_match_object_id_prefix(&header->id,
2620 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2624 error = got_repo_map_path(&in_repo_path, header->repo,
2625 gw_trans->repo_path, 1);
2630 header->path = strdup(in_repo_path);
2634 error = got_ref_list(&header->refs, header->repo, NULL,
2635 got_ref_cmp_by_name, NULL);
2639 error = gw_get_commits(gw_trans, header, limit);
2647 char datebuf[11]; /* YYYY-MM-DD + NUL */
2650 struct gw_blame_cb_args {
2651 struct blame_line *lines;
2655 off_t *line_offsets;
2657 struct got_repository *repo;
2658 struct gw_trans *gw_trans;
2659 struct buf *blamebuf;
2662 static const struct got_error *
2663 gw_blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
2665 const struct got_error *err = NULL;
2666 struct gw_blame_cb_args *a = arg;
2667 struct blame_line *bline;
2669 size_t linesize = 0, newsize;
2670 struct got_commit_object *commit = NULL;
2673 time_t committer_time;
2675 if (nlines != a->nlines ||
2676 (lineno != -1 && lineno < 1) || lineno > a->nlines)
2677 return got_error(GOT_ERR_RANGE);
2680 return NULL; /* no change in this commit */
2682 /* Annotate this line. */
2683 bline = &a->lines[lineno - 1];
2684 if (bline->annotated)
2686 err = got_object_id_str(&bline->id_str, id);
2690 err = got_object_open_as_commit(&commit, a->repo, id);
2694 bline->committer = strdup(got_object_commit_get_committer(commit));
2695 if (bline->committer == NULL) {
2696 err = got_error_from_errno("strdup");
2700 committer_time = got_object_commit_get_committer_time(commit);
2701 if (localtime_r(&committer_time, &tm) == NULL)
2702 return got_error_from_errno("localtime_r");
2703 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
2704 &tm) >= sizeof(bline->datebuf)) {
2705 err = got_error(GOT_ERR_NO_SPACE);
2708 bline->annotated = 1;
2710 /* Print lines annotated so far. */
2711 bline = &a->lines[a->lineno_cur - 1];
2712 if (!bline->annotated)
2715 offset = a->line_offsets[a->lineno_cur - 1];
2716 if (fseeko(a->f, offset, SEEK_SET) == -1) {
2717 err = got_error_from_errno("fseeko");
2721 while (bline->annotated) {
2722 char *smallerthan, *at, *nl, *committer, *blame_row = NULL,
2723 *line_escape = NULL;
2726 if (getline(&line, &linesize, a->f) == -1) {
2728 err = got_error_from_errno("getline");
2732 committer = bline->committer;
2733 smallerthan = strchr(committer, '<');
2734 if (smallerthan && smallerthan[1] != '\0')
2735 committer = smallerthan + 1;
2736 at = strchr(committer, '@');
2739 len = strlen(committer);
2741 committer[8] = '\0';
2743 nl = strchr(line, '\n');
2747 err = gw_html_escape(&line_escape, line);
2751 if (a->gw_trans->repo_folder == NULL)
2752 a->gw_trans->repo_folder = strdup("");
2753 if (a->gw_trans->repo_folder == NULL)
2755 asprintf(&blame_row, blame_line, a->nlines_prec, a->lineno_cur,
2756 a->gw_trans->repo_name, bline->id_str,
2757 a->gw_trans->repo_file, a->gw_trans->repo_folder,
2758 bline->id_str, bline->datebuf, committer, line_escape);
2760 err = buf_puts(&newsize, a->blamebuf, blame_row);
2764 bline = &a->lines[a->lineno_cur - 1];
2771 got_object_commit_close(commit);
2776 static const struct got_error *
2777 gw_get_file_blame_blob(char **blame_html, struct gw_trans *gw_trans)
2779 const struct got_error *error = NULL;
2780 struct got_repository *repo = NULL;
2781 struct got_object_id *obj_id = NULL;
2782 struct got_object_id *commit_id = NULL;
2783 struct got_blob_object *blob = NULL;
2784 char *path = NULL, *in_repo_path = NULL;
2785 struct gw_blame_cb_args bca;
2791 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2795 if (asprintf(&path, "%s%s%s",
2796 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2797 gw_trans->repo_folder ? "/" : "",
2798 gw_trans->repo_file) == -1) {
2799 error = got_error_from_errno("asprintf");
2803 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2807 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2808 GOT_OBJ_TYPE_COMMIT, 1, repo);
2812 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2816 if (obj_id == NULL) {
2817 error = got_error(GOT_ERR_NO_OBJ);
2821 error = got_object_get_type(&obj_type, repo, obj_id);
2825 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2826 error = got_error(GOT_ERR_OBJ_TYPE);
2830 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2834 error = buf_alloc(&bca.blamebuf, 0);
2838 bca.f = got_opentemp();
2839 if (bca.f == NULL) {
2840 error = got_error_from_errno("got_opentemp");
2843 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
2844 &bca.line_offsets, bca.f, blob);
2845 if (error || bca.nlines == 0)
2848 /* Don't include \n at EOF in the blame line count. */
2849 if (bca.line_offsets[bca.nlines - 1] == filesize)
2852 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
2853 if (bca.lines == NULL) {
2854 error = got_error_from_errno("calloc");
2858 bca.nlines_prec = 0;
2865 bca.gw_trans = gw_trans;
2867 error = got_blame(in_repo_path, commit_id, repo, gw_blame_cb, &bca,
2871 if (buf_len(bca.blamebuf) > 0) {
2872 error = buf_putc(bca.blamebuf, '\0');
2875 *blame_html = strdup(buf_get(bca.blamebuf));
2876 if (*blame_html == NULL) {
2877 error = got_error_from_errno("strdup");
2882 free(bca.line_offsets);
2889 for (i = 0; i < bca.nlines; i++) {
2890 struct blame_line *bline = &bca.lines[i];
2891 free(bline->id_str);
2892 free(bline->committer);
2895 if (bca.f && fclose(bca.f) == EOF && error == NULL)
2896 error = got_error_from_errno("fclose");
2898 got_object_blob_close(blob);
2900 got_repo_close(repo);
2904 static const struct got_error *
2905 gw_get_file_read_blob(char **blobstr, size_t *filesize, struct gw_trans *gw_trans)
2907 const struct got_error *error = NULL;
2908 struct got_repository *repo = NULL;
2909 struct got_object_id *obj_id = NULL;
2910 struct got_object_id *commit_id = NULL;
2911 struct got_blob_object *blob = NULL;
2912 char *path = NULL, *in_repo_path = NULL;
2920 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2924 if (asprintf(&path, "%s%s%s",
2925 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2926 gw_trans->repo_folder ? "/" : "",
2927 gw_trans->repo_file) == -1) {
2928 error = got_error_from_errno("asprintf");
2932 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2936 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2937 GOT_OBJ_TYPE_COMMIT, 1, repo);
2941 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2945 if (obj_id == NULL) {
2946 error = got_error(GOT_ERR_NO_OBJ);
2950 error = got_object_get_type(&obj_type, repo, obj_id);
2954 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2955 error = got_error(GOT_ERR_OBJ_TYPE);
2959 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2965 error = got_error_from_errno("got_opentemp");
2968 error = got_object_blob_dump_to_file(filesize, NULL, NULL, f, blob);
2972 /* XXX This will fail on large files... */
2973 *blobstr = calloc(*filesize + 1, sizeof(**blobstr));
2974 if (*blobstr == NULL) {
2975 error = got_error_from_errno("calloc");
2979 n = fread(*blobstr, 1, *filesize, f);
2982 error = got_ferror(f, GOT_ERR_IO);
2991 got_object_blob_close(blob);
2993 got_repo_close(repo);
2994 if (f != NULL && fclose(f) == -1 && error == NULL)
2995 error = got_error_from_errno("fclose");
3004 static const struct got_error *
3005 gw_get_repo_tree(char **tree_html, struct gw_trans *gw_trans)
3007 const struct got_error *error = NULL;
3008 struct got_repository *repo = NULL;
3009 struct got_object_id *tree_id = NULL, *commit_id = NULL;
3010 struct got_tree_object *tree = NULL;
3011 struct buf *diffbuf = NULL;
3013 char *path = NULL, *in_repo_path = NULL, *tree_row = NULL;
3014 char *id_str = NULL;
3015 char *build_folder = NULL;
3016 char *url_html = NULL;
3017 const char *class = NULL;
3018 int nentries, i, class_flip = 0;
3022 error = buf_alloc(&diffbuf, 0);
3026 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3030 if (gw_trans->repo_folder != NULL)
3031 path = strdup(gw_trans->repo_folder);
3033 error = got_repo_map_path(&in_repo_path, repo,
3034 gw_trans->repo_path, 1);
3038 path = in_repo_path;
3041 if (gw_trans->commit == NULL) {
3042 struct got_reference *head_ref;
3043 error = got_ref_open(&head_ref, repo, gw_trans->headref, 0);
3046 error = got_ref_resolve(&commit_id, repo, head_ref);
3049 got_ref_close(head_ref);
3052 error = got_repo_match_object_id(&commit_id, NULL,
3053 gw_trans->commit, GOT_OBJ_TYPE_COMMIT, 1, repo);
3058 error = got_object_id_str(&gw_trans->commit, commit_id);
3062 error = got_object_id_by_path(&tree_id, repo, commit_id, path);
3066 error = got_object_open_as_tree(&tree, repo, tree_id);
3070 nentries = got_object_tree_get_nentries(tree);
3071 for (i = 0; i < nentries; i++) {
3072 struct got_tree_entry *te;
3073 const char *modestr = "";
3076 te = got_object_tree_get_entry(tree, i);
3078 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
3082 mode = got_tree_entry_get_mode(te);
3083 if (got_object_tree_entry_is_submodule(te))
3085 else if (S_ISLNK(mode))
3087 else if (S_ISDIR(mode))
3089 else if (mode & S_IXUSR)
3092 if (class_flip == 0) {
3093 class = "back_lightgray";
3096 class = "back_white";
3100 if (S_ISDIR(mode)) {
3101 if (asprintf(&build_folder, "%s%s%s",
3102 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3103 gw_trans->repo_folder ? "/" : "",
3104 got_tree_entry_get_name(te)) == -1) {
3105 error = got_error_from_errno(
3110 if (asprintf(&url_html, folder_html,
3111 gw_trans->repo_name, gw_trans->action_name,
3112 gw_trans->commit, build_folder,
3113 got_tree_entry_get_name(te), modestr) == -1) {
3114 error = got_error_from_errno("asprintf");
3117 if (asprintf(&tree_row, tree_line, class, url_html,
3119 error = got_error_from_errno("asprintf");
3123 if (asprintf(&url_html, file_html, gw_trans->repo_name,
3124 "blob", gw_trans->commit,
3125 got_tree_entry_get_name(te),
3126 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3127 got_tree_entry_get_name(te), modestr) == -1) {
3128 error = got_error_from_errno("asprintf");
3132 if (asprintf(&tree_row, tree_line_with_navs, class,
3133 url_html, class, gw_trans->repo_name, "blob",
3134 gw_trans->commit, got_tree_entry_get_name(te),
3135 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3136 "blob", gw_trans->repo_name,
3137 "blame", gw_trans->commit,
3138 got_tree_entry_get_name(te),
3139 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3141 error = got_error_from_errno("asprintf");
3146 error = buf_puts(&newsize, diffbuf, tree_row);
3157 build_folder = NULL;
3160 if (buf_len(diffbuf) > 0) {
3161 error = buf_putc(diffbuf, '\0');
3164 *tree_html = strdup(buf_get(diffbuf));
3165 if (*tree_html == NULL) {
3166 error = got_error_from_errno("strdup");
3172 got_object_tree_close(tree);
3174 got_repo_close(repo);
3186 static const struct got_error *
3187 gw_get_repo_heads(char **head_html, struct gw_trans *gw_trans)
3189 const struct got_error *error = NULL;
3190 struct got_repository *repo = NULL;
3191 struct got_reflist_head refs;
3192 struct got_reflist_entry *re;
3193 char *head_row = NULL, *head_navs_disp = NULL, *age = NULL;
3194 struct buf *diffbuf = NULL;
3199 SIMPLEQ_INIT(&refs);
3201 error = buf_alloc(&diffbuf, 0);
3205 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3209 error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
3214 SIMPLEQ_FOREACH(re, &refs, entry) {
3217 refname = strdup(got_ref_get_name(re->ref));
3218 if (refname == NULL) {
3219 error = got_error_from_errno("got_ref_to_str");
3223 if (strncmp(refname, "refs/heads/", 11) != 0) {
3228 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
3233 if (asprintf(&head_navs_disp, heads_navs, gw_trans->repo_name,
3234 refname, gw_trans->repo_name, refname,
3235 gw_trans->repo_name, refname, gw_trans->repo_name,
3237 error = got_error_from_errno("asprintf");
3241 if (strncmp(refname, "refs/heads/", 11) == 0)
3244 if (asprintf(&head_row, heads_row, age, refname,
3245 head_navs_disp) == -1) {
3246 error = got_error_from_errno("asprintf");
3250 error = buf_puts(&newsize, diffbuf, head_row);
3252 free(head_navs_disp);
3256 if (buf_len(diffbuf) > 0) {
3257 error = buf_putc(diffbuf, '\0');
3258 *head_html = strdup(buf_get(diffbuf));
3259 if (*head_html == NULL)
3260 error = got_error_from_errno("strdup");
3264 got_ref_list_free(&refs);
3266 got_repo_close(repo);
3271 gw_get_site_link(struct gw_trans *gw_trans)
3273 char *link = NULL, *repo = NULL, *action = NULL;
3275 if (gw_trans->repo_name != NULL &&
3276 asprintf(&repo, " / <a href='?path=%s&action=summary'>%s</a>",
3277 gw_trans->repo_name, gw_trans->repo_name) == -1)
3280 if (gw_trans->action_name != NULL &&
3281 asprintf(&action, " / %s", gw_trans->action_name) == -1) {
3286 if (asprintf(&link, site_link, GOTWEB,
3287 gw_trans->gw_conf->got_site_link,
3288 repo ? repo : "", action ? action : "") == -1) {
3299 static const struct got_error *
3300 gw_colordiff_line(char **colorized_line, char *buf)
3302 const struct got_error *error = NULL;
3303 char *div_diff_line_div = NULL, *color = NULL;
3304 struct buf *diffbuf = NULL;
3307 *colorized_line = NULL;
3309 error = buf_alloc(&diffbuf, 0);
3313 if (strncmp(buf, "-", 1) == 0)
3314 color = "diff_minus";
3315 else if (strncmp(buf, "+", 1) == 0)
3316 color = "diff_plus";
3317 else if (strncmp(buf, "@@", 2) == 0)
3318 color = "diff_chunk_header";
3319 else if (strncmp(buf, "@@", 2) == 0)
3320 color = "diff_chunk_header";
3321 else if (strncmp(buf, "commit +", 8) == 0)
3322 color = "diff_meta";
3323 else if (strncmp(buf, "commit -", 8) == 0)
3324 color = "diff_meta";
3325 else if (strncmp(buf, "blob +", 6) == 0)
3326 color = "diff_meta";
3327 else if (strncmp(buf, "blob -", 6) == 0)
3328 color = "diff_meta";
3329 else if (strncmp(buf, "file +", 6) == 0)
3330 color = "diff_meta";
3331 else if (strncmp(buf, "file -", 6) == 0)
3332 color = "diff_meta";
3333 else if (strncmp(buf, "from:", 5) == 0)
3334 color = "diff_author";
3335 else if (strncmp(buf, "via:", 4) == 0)
3336 color = "diff_author";
3337 else if (strncmp(buf, "date:", 5) == 0)
3338 color = "diff_date";
3340 if (asprintf(&div_diff_line_div, div_diff_line, color ? color : "")
3342 error = got_error_from_errno("asprintf");
3346 error = buf_puts(&newsize, diffbuf, div_diff_line_div);
3350 error = buf_puts(&newsize, diffbuf, buf);
3354 if (buf_len(diffbuf) > 0) {
3355 error = buf_putc(diffbuf, '\0');
3356 *colorized_line = strdup(buf_get(diffbuf));
3357 if (*colorized_line == NULL)
3358 error = got_error_from_errno("strdup");
3362 free(div_diff_line_div);
3367 * XXX This function should not exist.
3368 * We should let khtml_puts(3) handle HTML escaping.
3370 static const struct got_error *
3371 gw_html_escape(char **escaped_html, const char *orig_html)
3373 const struct got_error *error = NULL;
3374 struct escape_pair {
3385 size_t orig_len, len;
3388 orig_len = strlen(orig_html);
3390 for (i = 0; i < orig_len; i++) {
3391 for (j = 0; j < nitems(esc); j++) {
3392 if (orig_html[i] != esc[j].c)
3394 len += strlen(esc[j].s) - 1 /* escaped char */;
3398 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
3399 if (*escaped_html == NULL)
3400 return got_error_from_errno("calloc");
3403 for (i = 0; i < orig_len; i++) {
3405 for (j = 0; j < nitems(esc); j++) {
3406 if (orig_html[i] != esc[j].c)
3409 if (strlcat(*escaped_html, esc[j].s, len + 1)
3411 error = got_error(GOT_ERR_NO_SPACE);
3414 x += strlen(esc[j].s);
3419 (*escaped_html)[x] = orig_html[i];
3425 free(*escaped_html);
3426 *escaped_html = NULL;
3428 (*escaped_html)[x] = '\0';
3434 main(int argc, char *argv[])
3436 const struct got_error *error = NULL;
3437 struct gw_trans *gw_trans;
3438 struct gw_dir *dir = NULL, *tdir;
3439 const char *page = "index";
3443 if ((gw_trans = malloc(sizeof(struct gw_trans))) == NULL)
3446 if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
3449 if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
3452 if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
3455 kerr = khttp_parse(gw_trans->gw_req, gw_keys, KEY__ZMAX, &page, 1, 0);
3456 if (kerr != KCGI_OK) {
3457 error = gw_kcgi_error(kerr);
3461 if ((gw_trans->gw_conf =
3462 malloc(sizeof(struct gotweb_conf))) == NULL) {
3464 error = got_error_from_errno("malloc");
3468 TAILQ_INIT(&gw_trans->gw_dirs);
3469 TAILQ_INIT(&gw_trans->gw_headers);
3472 gw_trans->repos_total = 0;
3473 gw_trans->repo_path = NULL;
3474 gw_trans->commit = NULL;
3475 gw_trans->headref = strdup(GOT_REF_HEAD);
3476 gw_trans->mime = KMIME_TEXT_HTML;
3477 gw_trans->gw_tmpl->key = gw_templs;
3478 gw_trans->gw_tmpl->keysz = TEMPL__MAX;
3479 gw_trans->gw_tmpl->arg = gw_trans;
3480 gw_trans->gw_tmpl->cb = gw_template;
3481 error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
3485 error = gw_parse_querystring(gw_trans);
3489 if (gw_trans->action == GW_BLOB)
3490 error = gw_blob(gw_trans);
3492 error = gw_display_index(gw_trans);
3495 gw_trans->mime = KMIME_TEXT_PLAIN;
3496 gw_trans->action = GW_ERR;
3497 gw_display_error(gw_trans, error);
3500 free(gw_trans->gw_conf->got_repos_path);
3501 free(gw_trans->gw_conf->got_site_name);
3502 free(gw_trans->gw_conf->got_site_owner);
3503 free(gw_trans->gw_conf->got_site_link);
3504 free(gw_trans->gw_conf->got_logo);
3505 free(gw_trans->gw_conf->got_logo_url);
3506 free(gw_trans->gw_conf);
3507 free(gw_trans->commit);
3508 free(gw_trans->repo_path);
3509 free(gw_trans->repo_name);
3510 free(gw_trans->repo_file);
3511 free(gw_trans->action_name);
3512 free(gw_trans->headref);
3514 TAILQ_FOREACH_SAFE(dir, &gw_trans->gw_dirs, entry, tdir) {
3516 free(dir->description);
3525 khttp_free(gw_trans->gw_req);