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;
734 enum kcgi_err kerr = KCGI_OK;
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);
893 if (error == NULL && kerr != KCGI_OK)
894 error = gw_kcgi_error(kerr);
898 static const struct got_error *
899 gw_summary(struct gw_trans *gw_trans)
901 const struct got_error *error = NULL;
902 char *age = NULL, *tags = NULL, *heads = NULL;
905 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
906 return got_error_from_errno("pledge");
908 /* unveil is applied with gw_briefs below */
910 /* summary wrapper */
911 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
912 "summary_wrapper", KATTR__MAX);
914 return gw_kcgi_error(kerr);
917 if (gw_trans->gw_conf->got_show_repo_description &&
918 gw_trans->gw_dir->description != NULL &&
919 (strcmp(gw_trans->gw_dir->description, "") != 0)) {
920 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
921 KATTR_ID, "description_title", KATTR__MAX);
922 if (kerr != KCGI_OK) {
923 error = gw_kcgi_error(kerr);
926 kerr = khtml_puts(gw_trans->gw_html_req, "Description: ");
927 if (kerr != KCGI_OK) {
928 error = gw_kcgi_error(kerr);
931 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
932 if (kerr != KCGI_OK) {
933 error = gw_kcgi_error(kerr);
936 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
937 KATTR_ID, "description", KATTR__MAX);
938 if (kerr != KCGI_OK) {
939 error = gw_kcgi_error(kerr);
942 kerr = khtml_puts(gw_trans->gw_html_req,
943 gw_trans->gw_dir->description);
944 if (kerr != KCGI_OK) {
945 error = gw_kcgi_error(kerr);
948 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
949 if (kerr != KCGI_OK) {
950 error = gw_kcgi_error(kerr);
956 if (gw_trans->gw_conf->got_show_repo_owner &&
957 gw_trans->gw_dir->owner != NULL) {
958 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
959 KATTR_ID, "repo_owner_title", KATTR__MAX);
960 if (kerr != KCGI_OK) {
961 error = gw_kcgi_error(kerr);
964 kerr = khtml_puts(gw_trans->gw_html_req, "Owner: ");
965 if (kerr != KCGI_OK) {
966 error = gw_kcgi_error(kerr);
969 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
970 if (kerr != KCGI_OK) {
971 error = gw_kcgi_error(kerr);
974 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
975 KATTR_ID, "repo_owner", KATTR__MAX);
976 if (kerr != KCGI_OK) {
977 error = gw_kcgi_error(kerr);
980 kerr = khtml_puts(gw_trans->gw_html_req,
981 gw_trans->gw_dir->owner);
982 if (kerr != KCGI_OK) {
983 error = gw_kcgi_error(kerr);
986 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
987 if (kerr != KCGI_OK) {
988 error = gw_kcgi_error(kerr);
994 if (gw_trans->gw_conf->got_show_repo_age) {
995 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
996 "refs/heads", TM_LONG);
1000 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1001 KATTR_ID, "last_change_title", KATTR__MAX);
1002 if (kerr != KCGI_OK) {
1003 error = gw_kcgi_error(kerr);
1006 kerr = khtml_puts(gw_trans->gw_html_req,
1008 if (kerr != KCGI_OK) {
1009 error = gw_kcgi_error(kerr);
1012 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1013 if (kerr != KCGI_OK) {
1014 error = gw_kcgi_error(kerr);
1017 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1018 KATTR_ID, "last_change", KATTR__MAX);
1019 if (kerr != KCGI_OK) {
1020 error = gw_kcgi_error(kerr);
1023 kerr = khtml_puts(gw_trans->gw_html_req, age);
1024 if (kerr != KCGI_OK) {
1025 error = gw_kcgi_error(kerr);
1028 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1029 if (kerr != KCGI_OK) {
1030 error = gw_kcgi_error(kerr);
1037 if (gw_trans->gw_conf->got_show_repo_cloneurl &&
1038 gw_trans->gw_dir->url != NULL &&
1039 (strcmp(gw_trans->gw_dir->url, "") != 0)) {
1040 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1041 KATTR_ID, "cloneurl_title", KATTR__MAX);
1042 if (kerr != KCGI_OK) {
1043 error = gw_kcgi_error(kerr);
1046 kerr = khtml_puts(gw_trans->gw_html_req, "Clone URL: ");
1047 if (kerr != KCGI_OK) {
1048 error = gw_kcgi_error(kerr);
1051 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1052 if (kerr != KCGI_OK) {
1053 error = gw_kcgi_error(kerr);
1056 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1057 KATTR_ID, "cloneurl", KATTR__MAX);
1058 if (kerr != KCGI_OK) {
1059 error = gw_kcgi_error(kerr);
1062 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->gw_dir->url);
1063 if (kerr != KCGI_OK) {
1064 error = gw_kcgi_error(kerr);
1067 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1068 if (kerr != KCGI_OK) {
1069 error = gw_kcgi_error(kerr);
1074 /* close summary wrapper */
1075 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1076 if (kerr != KCGI_OK) {
1077 error = gw_kcgi_error(kerr);
1081 /* commit briefs header */
1082 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1083 "briefs_title_wrapper", KATTR__MAX);
1084 if (kerr != KCGI_OK) {
1085 error = gw_kcgi_error(kerr);
1088 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1089 "briefs_title", KATTR__MAX);
1090 if (kerr != KCGI_OK) {
1091 error = gw_kcgi_error(kerr);
1094 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Briefs");
1095 if (kerr != KCGI_OK) {
1096 error = gw_kcgi_error(kerr);
1099 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1100 if (kerr != KCGI_OK) {
1101 error = gw_kcgi_error(kerr);
1104 error = gw_briefs(gw_trans);
1109 error = gw_get_repo_tags(&tags, gw_trans, NULL, D_MAXSLCOMMDISP,
1114 if (tags != NULL && strcmp(tags, "") != 0) {
1115 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1116 KATTR_ID, "summary_tags_title_wrapper", KATTR__MAX);
1117 if (kerr != KCGI_OK) {
1118 error = gw_kcgi_error(kerr);
1121 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1122 KATTR_ID, "summary_tags_title", KATTR__MAX);
1123 if (kerr != KCGI_OK) {
1124 error = gw_kcgi_error(kerr);
1127 kerr = khtml_puts(gw_trans->gw_html_req, "Tags");
1128 if (kerr != KCGI_OK) {
1129 error = gw_kcgi_error(kerr);
1132 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1133 if (kerr != KCGI_OK) {
1134 error = gw_kcgi_error(kerr);
1137 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1138 KATTR_ID, "summary_tags_content", KATTR__MAX);
1139 if (kerr != KCGI_OK) {
1140 error = gw_kcgi_error(kerr);
1143 kerr = khttp_puts(gw_trans->gw_req, tags);
1144 if (kerr != KCGI_OK) {
1145 error = gw_kcgi_error(kerr);
1148 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1149 if (kerr != KCGI_OK) {
1150 error = gw_kcgi_error(kerr);
1156 error = gw_get_repo_heads(&heads, gw_trans);
1159 if (heads != NULL && strcmp(heads, "") != 0) {
1160 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1161 KATTR_ID, "summary_heads_title_wrapper", KATTR__MAX);
1162 if (kerr != KCGI_OK) {
1163 error = gw_kcgi_error(kerr);
1166 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1167 KATTR_ID, "summary_heads_title", KATTR__MAX);
1168 if (kerr != KCGI_OK) {
1169 error = gw_kcgi_error(kerr);
1172 kerr = khtml_puts(gw_trans->gw_html_req, "Heads");
1173 if (kerr != KCGI_OK) {
1174 error = gw_kcgi_error(kerr);
1177 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1178 if (kerr != KCGI_OK) {
1179 error = gw_kcgi_error(kerr);
1182 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1183 KATTR_ID, "summary_heads_content", KATTR__MAX);
1184 if (kerr != KCGI_OK) {
1185 error = gw_kcgi_error(kerr);
1188 kerr = khttp_puts(gw_trans->gw_req, heads);
1189 if (kerr != KCGI_OK) {
1190 error = gw_kcgi_error(kerr);
1193 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1194 if (kerr != KCGI_OK) {
1195 error = gw_kcgi_error(kerr);
1206 static const struct got_error *
1207 gw_tree(struct gw_trans *gw_trans)
1209 const struct got_error *error = NULL;
1210 struct gw_header *header = NULL;
1211 char *tree = NULL, *tree_html = NULL, *tree_html_disp = NULL;
1212 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
1215 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1216 return got_error_from_errno("pledge");
1218 if ((header = gw_init_header()) == NULL)
1219 return got_error_from_errno("malloc");
1221 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1225 error = gw_get_header(gw_trans, header, 1);
1229 error = gw_get_repo_tree(&tree_html, gw_trans);
1233 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
1236 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
1237 error = got_error_from_errno("asprintf");
1240 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1243 if (asprintf(&tree_html_disp, tree_header, age_html,
1244 gw_gen_commit_msg_header(escaped_commit_msg),
1245 tree_html ? tree_html : "") == -1) {
1246 error = got_error_from_errno("asprintf");
1250 if (asprintf(&tree, tree_wrapper, tree_html_disp) == -1) {
1251 error = got_error_from_errno("asprintf");
1255 kerr = khttp_puts(gw_trans->gw_req, tree);
1256 if (kerr != KCGI_OK)
1257 error = gw_kcgi_error(kerr);
1259 got_ref_list_free(&header->refs);
1260 gw_free_headers(header);
1261 free(tree_html_disp);
1266 free(escaped_commit_msg);
1270 static const struct got_error *
1271 gw_tag(struct gw_trans *gw_trans)
1273 const struct got_error *error = NULL;
1274 struct gw_header *header = NULL;
1275 char *tag = NULL, *tag_html = NULL, *tag_html_disp = NULL;
1276 char *escaped_commit_msg = NULL;
1279 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1280 return got_error_from_errno("pledge");
1282 if ((header = gw_init_header()) == NULL)
1283 return got_error_from_errno("malloc");
1285 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1289 error = gw_get_header(gw_trans, header, 1);
1293 error = gw_get_repo_tags(&tag_html, gw_trans, header, 1, TAGFULL);
1297 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1300 if (asprintf(&tag_html_disp, tag_header,
1301 gw_gen_commit_header(header->commit_id, header->refs_str),
1302 gw_gen_commit_msg_header(escaped_commit_msg),
1303 tag_html ? tag_html : "") == -1) {
1304 error = got_error_from_errno("asprintf");
1308 if (asprintf(&tag, tag_wrapper, tag_html_disp) == -1) {
1309 error = got_error_from_errno("asprintf");
1313 kerr = khttp_puts(gw_trans->gw_req, tag);
1314 if (kerr != KCGI_OK)
1315 error = gw_kcgi_error(kerr);
1317 got_ref_list_free(&header->refs);
1318 gw_free_headers(header);
1319 free(tag_html_disp);
1322 free(escaped_commit_msg);
1326 static const struct got_error *
1327 gw_load_got_path(struct gw_trans *gw_trans, struct gw_dir *gw_dir)
1329 const struct got_error *error = NULL;
1334 if (asprintf(&dir_test, "%s/%s/%s",
1335 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1336 GOTWEB_GIT_DIR) == -1)
1337 return got_error_from_errno("asprintf");
1339 dt = opendir(dir_test);
1343 gw_dir->path = strdup(dir_test);
1348 if (asprintf(&dir_test, "%s/%s/%s",
1349 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1350 GOTWEB_GOT_DIR) == -1)
1351 return got_error_from_errno("asprintf");
1353 dt = opendir(dir_test);
1358 error = got_error(GOT_ERR_NOT_GIT_REPO);
1362 if (asprintf(&dir_test, "%s/%s",
1363 gw_trans->gw_conf->got_repos_path, gw_dir->name) == -1)
1364 return got_error_from_errno("asprintf");
1366 gw_dir->path = strdup(dir_test);
1369 error = gw_get_repo_description(&gw_dir->description, gw_trans,
1373 error = gw_get_repo_owner(&gw_dir->owner, gw_trans, gw_dir->path);
1376 error = gw_get_repo_age(&gw_dir->age, gw_trans, gw_dir->path,
1377 "refs/heads", TM_DIFF);
1380 error = gw_get_clone_url(&gw_dir->url, gw_trans, gw_dir->path);
1388 static const struct got_error *
1389 gw_load_got_paths(struct gw_trans *gw_trans)
1391 const struct got_error *error = NULL;
1393 struct dirent **sd_dent;
1394 struct gw_dir *gw_dir;
1396 unsigned int d_cnt, d_i;
1398 d = opendir(gw_trans->gw_conf->got_repos_path);
1400 error = got_error_from_errno2("opendir",
1401 gw_trans->gw_conf->got_repos_path);
1405 d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
1408 error = got_error_from_errno2("scandir",
1409 gw_trans->gw_conf->got_repos_path);
1413 for (d_i = 0; d_i < d_cnt; d_i++) {
1414 if (gw_trans->gw_conf->got_max_repos > 0 &&
1415 (d_i - 2) == gw_trans->gw_conf->got_max_repos)
1416 break; /* account for parent and self */
1418 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1419 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1422 if ((gw_dir = gw_init_gw_dir(sd_dent[d_i]->d_name)) == NULL)
1423 return got_error_from_errno("gw_dir malloc");
1425 error = gw_load_got_path(gw_trans, gw_dir);
1426 if (error && error->code == GOT_ERR_NOT_GIT_REPO)
1431 if (lstat(gw_dir->path, &st) == 0 && S_ISDIR(st.st_mode) &&
1432 !got_path_dir_is_empty(gw_dir->path)) {
1433 TAILQ_INSERT_TAIL(&gw_trans->gw_dirs, gw_dir,
1435 gw_trans->repos_total++;
1443 static const struct got_error *
1444 gw_parse_querystring(struct gw_trans *gw_trans)
1446 const struct got_error *error = NULL;
1448 struct gw_query_action *action = NULL;
1451 if (gw_trans->gw_req->fieldnmap[0]) {
1452 error = got_error_from_errno("bad parse");
1454 } else if ((p = gw_trans->gw_req->fieldmap[KEY_PATH])) {
1455 /* define gw_trans->repo_path */
1456 if (asprintf(&gw_trans->repo_name, "%s", p->parsed.s) == -1)
1457 return got_error_from_errno("asprintf");
1459 if (asprintf(&gw_trans->repo_path, "%s/%s",
1460 gw_trans->gw_conf->got_repos_path, p->parsed.s) == -1)
1461 return got_error_from_errno("asprintf");
1463 /* get action and set function */
1464 if ((p = gw_trans->gw_req->fieldmap[KEY_ACTION]))
1465 for (i = 0; i < nitems(gw_query_funcs); i++) {
1466 action = &gw_query_funcs[i];
1467 if (action->func_name == NULL)
1470 if (strcmp(action->func_name,
1471 p->parsed.s) == 0) {
1472 gw_trans->action = i;
1473 if (asprintf(&gw_trans->action_name,
1474 "%s", action->func_name) == -1)
1476 got_error_from_errno(
1485 if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
1486 if (asprintf(&gw_trans->commit, "%s",
1488 return got_error_from_errno("asprintf");
1490 if ((p = gw_trans->gw_req->fieldmap[KEY_FILE]))
1491 if (asprintf(&gw_trans->repo_file, "%s",
1493 return got_error_from_errno("asprintf");
1495 if ((p = gw_trans->gw_req->fieldmap[KEY_FOLDER]))
1496 if (asprintf(&gw_trans->repo_folder, "%s",
1498 return got_error_from_errno("asprintf");
1500 if ((p = gw_trans->gw_req->fieldmap[KEY_HEADREF]))
1501 if (asprintf(&gw_trans->headref, "%s",
1503 return got_error_from_errno("asprintf");
1505 if (action == NULL) {
1506 error = got_error_from_errno("invalid action");
1509 if ((gw_trans->gw_dir =
1510 gw_init_gw_dir(gw_trans->repo_name)) == NULL)
1511 return got_error_from_errno("gw_dir malloc");
1513 error = gw_load_got_path(gw_trans, gw_trans->gw_dir);
1517 gw_trans->action = GW_INDEX;
1519 if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
1520 gw_trans->page = p->parsed.i;
1525 static struct gw_dir *
1526 gw_init_gw_dir(char *dir)
1528 struct gw_dir *gw_dir;
1530 if ((gw_dir = malloc(sizeof(*gw_dir))) == NULL)
1533 if (asprintf(&gw_dir->name, "%s", dir) == -1)
1539 static const struct got_error *
1540 gw_display_open(struct gw_trans *gw_trans, enum khttp code, enum kmime mime)
1544 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_ALLOW], "GET");
1545 if (kerr != KCGI_OK)
1546 return gw_kcgi_error(kerr);
1547 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_STATUS], "%s",
1549 if (kerr != KCGI_OK)
1550 return gw_kcgi_error(kerr);
1551 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_CONTENT_TYPE], "%s",
1553 if (kerr != KCGI_OK)
1554 return gw_kcgi_error(kerr);
1555 kerr = khttp_head(gw_trans->gw_req, "X-Content-Type-Options",
1557 if (kerr != KCGI_OK)
1558 return gw_kcgi_error(kerr);
1559 kerr = khttp_head(gw_trans->gw_req, "X-Frame-Options", "DENY");
1560 if (kerr != KCGI_OK)
1561 return gw_kcgi_error(kerr);
1562 kerr = khttp_head(gw_trans->gw_req, "X-XSS-Protection",
1564 if (kerr != KCGI_OK)
1565 return gw_kcgi_error(kerr);
1567 if (gw_trans->mime == KMIME_APP_OCTET_STREAM) {
1568 kerr = khttp_head(gw_trans->gw_req,
1569 kresps[KRESP_CONTENT_DISPOSITION],
1570 "attachment; filename=%s", gw_trans->repo_file);
1571 if (kerr != KCGI_OK)
1572 return gw_kcgi_error(kerr);
1575 kerr = khttp_body(gw_trans->gw_req);
1576 return gw_kcgi_error(kerr);
1579 static const struct got_error *
1580 gw_display_index(struct gw_trans *gw_trans)
1582 const struct got_error *error;
1585 error = gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
1589 kerr = khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, KHTML_PRETTY);
1591 return gw_kcgi_error(kerr);
1593 if (gw_trans->action != GW_BLOB) {
1594 kerr = khttp_template(gw_trans->gw_req, gw_trans->gw_tmpl,
1595 gw_query_funcs[gw_trans->action].template);
1596 if (kerr != KCGI_OK) {
1597 khtml_close(gw_trans->gw_html_req);
1598 return gw_kcgi_error(kerr);
1602 return gw_kcgi_error(khtml_close(gw_trans->gw_html_req));
1606 gw_display_error(struct gw_trans *gw_trans, const struct got_error *err)
1608 if (gw_display_open(gw_trans, KHTTP_200, gw_trans->mime) != NULL)
1611 if (khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0) != KCGI_OK)
1613 khtml_puts(gw_trans->gw_html_req, err->msg);
1614 khtml_close(gw_trans->gw_html_req);
1618 gw_template(size_t key, void *arg)
1620 const struct got_error *error = NULL;
1622 struct gw_trans *gw_trans = arg;
1623 char *gw_site_link, *img_src = NULL;
1627 kerr = khttp_puts(gw_trans->gw_req, head);
1628 if (kerr != KCGI_OK)
1632 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1633 KATTR_ID, "got_link", KATTR__MAX);
1634 if (kerr != KCGI_OK)
1636 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1637 KATTR_HREF, gw_trans->gw_conf->got_logo_url,
1638 KATTR_TARGET, "_sotd", KATTR__MAX);
1639 if (kerr != KCGI_OK)
1641 if (asprintf(&img_src, "/%s",
1642 gw_trans->gw_conf->got_logo) == -1)
1644 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_IMG,
1645 KATTR_SRC, img_src, KATTR__MAX);
1646 if (kerr != KCGI_OK) {
1650 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1651 if (kerr != KCGI_OK) {
1656 case (TEMPL_SITEPATH):
1657 gw_site_link = gw_get_site_link(gw_trans);
1658 if (gw_site_link != NULL) {
1659 kerr = khttp_puts(gw_trans->gw_req, gw_site_link);
1660 if (kerr != KCGI_OK) {
1668 if (gw_trans->gw_conf->got_site_name != NULL) {
1669 kerr = khtml_puts(gw_trans->gw_html_req,
1670 gw_trans->gw_conf->got_site_name);
1671 if (kerr != KCGI_OK)
1675 case (TEMPL_SEARCH):
1676 kerr = khttp_puts(gw_trans->gw_req, search);
1677 if (kerr != KCGI_OK)
1680 case(TEMPL_SITEOWNER):
1681 if (gw_trans->gw_conf->got_site_owner != NULL &&
1682 gw_trans->gw_conf->got_show_site_owner) {
1683 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1684 KATTR_ID, "site_owner_wrapper", KATTR__MAX);
1685 if (kerr != KCGI_OK)
1687 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1688 KATTR_ID, "site_owner", KATTR__MAX);
1689 if (kerr != KCGI_OK)
1691 kerr = khtml_puts(gw_trans->gw_html_req,
1692 gw_trans->gw_conf->got_site_owner);
1693 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1694 if (kerr != KCGI_OK)
1698 case(TEMPL_CONTENT):
1699 error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
1701 kerr = khttp_puts(gw_trans->gw_req, error->msg);
1702 if (kerr != KCGI_OK)
1713 gw_gen_commit_header(char *str1, char *str2)
1715 char *return_html = NULL, *ref_str = NULL;
1717 if (strcmp(str2, "") != 0) {
1718 if (asprintf(&ref_str, "(%s)", str2) == -1) {
1719 return_html = strdup("");
1723 ref_str = strdup("");
1726 if (asprintf(&return_html, header_commit_html, str1, ref_str) == -1)
1727 return_html = strdup("");
1734 gw_gen_diff_header(char *str1, char *str2)
1736 char *return_html = NULL;
1738 if (asprintf(&return_html, header_diff_html, str1, str2) == -1)
1739 return_html = strdup("");
1745 gw_gen_author_header(const char *str)
1747 char *return_html = NULL;
1749 if (asprintf(&return_html, header_author_html, str) == -1)
1750 return_html = strdup("");
1756 gw_gen_committer_header(char *str)
1758 char *return_html = NULL;
1760 if (asprintf(&return_html, header_committer_html, str) == -1)
1761 return_html = strdup("");
1767 gw_gen_commit_msg_header(char *str)
1769 char *return_html = NULL;
1771 if (asprintf(&return_html, header_commit_msg_html, str) == -1)
1772 return_html = strdup("");
1778 gw_gen_tree_header(char *str)
1780 char *return_html = NULL;
1782 if (asprintf(&return_html, header_tree_html, str) == -1)
1783 return_html = strdup("");
1788 static const struct got_error *
1789 gw_get_repo_description(char **description, struct gw_trans *gw_trans,
1792 const struct got_error *error = NULL;
1794 char *d_file = NULL;
1798 *description = NULL;
1799 if (gw_trans->gw_conf->got_show_repo_description == 0)
1800 return gw_empty_string(description);
1802 if (asprintf(&d_file, "%s/description", dir) == -1)
1803 return got_error_from_errno("asprintf");
1805 f = fopen(d_file, "r");
1807 if (errno == ENOENT || errno == EACCES)
1808 return gw_empty_string(description);
1809 error = got_error_from_errno2("fopen", d_file);
1813 if (fseek(f, 0, SEEK_END) == -1) {
1814 error = got_ferror(f, GOT_ERR_IO);
1819 error = got_ferror(f, GOT_ERR_IO);
1822 if (fseek(f, 0, SEEK_SET) == -1) {
1823 error = got_ferror(f, GOT_ERR_IO);
1826 *description = calloc(len + 1, sizeof(**description));
1827 if (*description == NULL) {
1828 error = got_error_from_errno("calloc");
1832 n = fread(*description, 1, len, f);
1833 if (n == 0 && ferror(f))
1834 error = got_ferror(f, GOT_ERR_IO);
1836 if (f != NULL && fclose(f) == -1 && error == NULL)
1837 error = got_error_from_errno("fclose");
1842 static const struct got_error *
1843 gw_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
1847 char *years = "years ago", *months = "months ago";
1848 char *weeks = "weeks ago", *days = "days ago", *hours = "hours ago";
1849 char *minutes = "minutes ago", *seconds = "seconds ago";
1850 char *now = "right now";
1858 diff_time = time(NULL) - committer_time;
1859 if (diff_time > 60 * 60 * 24 * 365 * 2) {
1860 if (asprintf(repo_age, "%lld %s",
1861 (diff_time / 60 / 60 / 24 / 365), years) == -1)
1862 return got_error_from_errno("asprintf");
1863 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
1864 if (asprintf(repo_age, "%lld %s",
1865 (diff_time / 60 / 60 / 24 / (365 / 12)),
1867 return got_error_from_errno("asprintf");
1868 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
1869 if (asprintf(repo_age, "%lld %s",
1870 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
1871 return got_error_from_errno("asprintf");
1872 } else if (diff_time > 60 * 60 * 24 * 2) {
1873 if (asprintf(repo_age, "%lld %s",
1874 (diff_time / 60 / 60 / 24), days) == -1)
1875 return got_error_from_errno("asprintf");
1876 } else if (diff_time > 60 * 60 * 2) {
1877 if (asprintf(repo_age, "%lld %s",
1878 (diff_time / 60 / 60), hours) == -1)
1879 return got_error_from_errno("asprintf");
1880 } else if (diff_time > 60 * 2) {
1881 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
1883 return got_error_from_errno("asprintf");
1884 } else if (diff_time > 2) {
1885 if (asprintf(repo_age, "%lld %s", diff_time,
1887 return got_error_from_errno("asprintf");
1889 if (asprintf(repo_age, "%s", now) == -1)
1890 return got_error_from_errno("asprintf");
1894 if (gmtime_r(&committer_time, &tm) == NULL)
1895 return got_error_from_errno("gmtime_r");
1897 s = asctime_r(&tm, datebuf);
1899 return got_error_from_errno("asctime_r");
1901 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
1902 return got_error_from_errno("asprintf");
1908 static const struct got_error *
1909 gw_get_repo_age(char **repo_age, struct gw_trans *gw_trans, char *dir,
1910 char *repo_ref, int ref_tm)
1912 const struct got_error *error = NULL;
1913 struct got_object_id *id = NULL;
1914 struct got_repository *repo = NULL;
1915 struct got_commit_object *commit = NULL;
1916 struct got_reflist_head refs;
1917 struct got_reflist_entry *re;
1918 struct got_reference *head_ref;
1920 time_t committer_time = 0, cmp_time = 0;
1921 const char *refname;
1924 SIMPLEQ_INIT(&refs);
1926 if (repo_ref == NULL)
1929 if (strncmp(repo_ref, "refs/heads/", 11) == 0)
1932 if (gw_trans->gw_conf->got_show_repo_age == 0)
1935 error = got_repo_open(&repo, dir, NULL);
1940 error = got_ref_list(&refs, repo, "refs/heads",
1941 got_ref_cmp_by_name, NULL);
1943 error = got_ref_list(&refs, repo, repo_ref,
1944 got_ref_cmp_by_name, NULL);
1948 SIMPLEQ_FOREACH(re, &refs, entry) {
1950 refname = strdup(repo_ref);
1952 refname = got_ref_get_name(re->ref);
1953 error = got_ref_open(&head_ref, repo, refname, 0);
1957 error = got_ref_resolve(&id, repo, head_ref);
1958 got_ref_close(head_ref);
1962 error = got_object_open_as_commit(&commit, repo, id);
1967 got_object_commit_get_committer_time(commit);
1969 if (cmp_time < committer_time)
1970 cmp_time = committer_time;
1973 if (cmp_time != 0) {
1974 committer_time = cmp_time;
1975 error = gw_get_time_str(repo_age, committer_time, ref_tm);
1978 got_ref_list_free(&refs);
1983 static const struct got_error *
1984 gw_get_diff(char **diff_html, struct gw_trans *gw_trans,
1985 struct gw_header *header)
1987 const struct got_error *error;
1989 struct got_object_id *id1 = NULL, *id2 = NULL;
1990 struct buf *diffbuf = NULL;
1991 char *label1 = NULL, *label2 = NULL, *line = NULL;
1992 char *diff_line_html = NULL;
1994 size_t newsize, linesize = 0;
2001 error = buf_alloc(&diffbuf, 0);
2005 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2009 if (strncmp(header->parent_id, "/dev/null", 9) != 0) {
2010 error = got_repo_match_object_id(&id1, &label1,
2011 header->parent_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2016 error = got_repo_match_object_id(&id2, &label2,
2017 header->commit_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2021 error = got_object_get_type(&obj_type, header->repo, id2);
2025 case GOT_OBJ_TYPE_BLOB:
2026 error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, 3, 0,
2029 case GOT_OBJ_TYPE_TREE:
2030 error = got_diff_objects_as_trees(id1, id2, "", "", 3, 0,
2033 case GOT_OBJ_TYPE_COMMIT:
2034 error = got_diff_objects_as_commits(id1, id2, 3, 0,
2038 error = got_error(GOT_ERR_OBJ_TYPE);
2043 if (fseek(f, 0, SEEK_SET) == -1) {
2044 error = got_ferror(f, GOT_ERR_IO);
2048 while ((linelen = getline(&line, &linesize, f)) != -1) {
2050 error = gw_html_escape(&escaped_line, line);
2054 error = gw_colordiff_line(&diff_line_html, escaped_line);
2058 error = buf_puts(&newsize, diffbuf, diff_line_html);
2062 error = buf_puts(&newsize, diffbuf, div_end);
2066 if (linelen == -1 && ferror(f)) {
2067 error = got_error_from_errno("getline");
2071 if (buf_len(diffbuf) > 0) {
2072 error = buf_putc(diffbuf, '\0');
2075 *diff_html = strdup(buf_get(diffbuf));
2076 if (*diff_html == NULL)
2077 error = got_error_from_errno("strdup");
2080 if (f && fclose(f) == -1 && error == NULL)
2081 error = got_error_from_errno("fclose");
2082 free(diff_line_html);
2093 static const struct got_error *
2094 gw_get_repo_owner(char **owner, struct gw_trans *gw_trans, char *dir)
2096 const struct got_error *error = NULL;
2097 struct got_repository *repo;
2098 const char *gitconfig_owner;
2102 if (gw_trans->gw_conf->got_show_repo_owner == 0)
2105 error = got_repo_open(&repo, dir, NULL);
2108 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
2109 if (gitconfig_owner) {
2110 *owner = strdup(gitconfig_owner);
2112 error = got_error_from_errno("strdup");
2114 got_repo_close(repo);
2118 static const struct got_error *
2119 gw_get_clone_url(char **url, struct gw_trans *gw_trans, char *dir)
2121 const struct got_error *error = NULL;
2123 char *d_file = NULL;
2129 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2130 return got_error_from_errno("asprintf");
2132 f = fopen(d_file, "r");
2134 if (errno != ENOENT && errno != EACCES)
2135 error = got_error_from_errno2("fopen", d_file);
2139 if (fseek(f, 0, SEEK_END) == -1) {
2140 error = got_ferror(f, GOT_ERR_IO);
2145 error = got_ferror(f, GOT_ERR_IO);
2148 if (fseek(f, 0, SEEK_SET) == -1) {
2149 error = got_ferror(f, GOT_ERR_IO);
2153 *url = calloc(len + 1, sizeof(**url));
2155 error = got_error_from_errno("calloc");
2159 n = fread(*url, 1, len, f);
2160 if (n == 0 && ferror(f))
2161 error = got_ferror(f, GOT_ERR_IO);
2163 if (f && fclose(f) == -1 && error == NULL)
2164 error = got_error_from_errno("fclose");
2169 static const struct got_error *
2170 gw_get_repo_tags(char **tag_html, struct gw_trans *gw_trans,
2171 struct gw_header *header, int limit, int tag_type)
2173 const struct got_error *error = NULL;
2174 struct got_repository *repo = NULL;
2175 struct got_reflist_head refs;
2176 struct got_reflist_entry *re;
2177 char *tag_row = NULL, *tags_navs_disp = NULL;
2178 char *age = NULL, *age_html = NULL, *newline;
2179 char *escaped_tagger = NULL, *escaped_tag_commit = NULL;
2180 char *id_str = NULL, *refstr = NULL;
2181 char *tag_commit0 = NULL;
2182 struct buf *diffbuf = NULL;
2184 struct got_tag_object *tag = NULL;
2188 SIMPLEQ_INIT(&refs);
2190 error = buf_alloc(&diffbuf, 0);
2194 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2198 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo);
2202 SIMPLEQ_FOREACH(re, &refs, entry) {
2203 const char *refname;
2205 const char *tag_commit;
2207 struct got_object_id *id;
2209 refname = got_ref_get_name(re->ref);
2210 if (strncmp(refname, "refs/tags/", 10) != 0)
2213 refstr = got_ref_to_str(re->ref);
2214 if (refstr == NULL) {
2215 error = got_error_from_errno("got_ref_to_str");
2219 error = got_ref_resolve(&id, repo, re->ref);
2222 error = got_object_open_as_tag(&tag, repo, id);
2227 tagger = got_object_tag_get_tagger(tag);
2228 tagger_time = got_object_tag_get_tagger_time(tag);
2230 error = got_object_id_str(&id_str,
2231 got_object_tag_get_object_id(tag));
2235 tag_commit0 = strdup(got_object_tag_get_message(tag));
2236 if (tag_commit0 == NULL) {
2237 error = got_error_from_errno("strdup");
2241 tag_commit = tag_commit0;
2242 while (*tag_commit == '\n')
2247 newline = strchr(tag_commit, '\n');
2251 error = gw_get_time_str(&age, tagger_time, TM_DIFF);
2255 if (asprintf(&tags_navs_disp, tags_navs,
2256 gw_trans->repo_name, id_str, gw_trans->repo_name,
2257 id_str, gw_trans->repo_name, id_str,
2258 gw_trans->repo_name, id_str) == -1) {
2259 error = got_error_from_errno("asprintf");
2263 if (asprintf(&tag_row, tags_row, age ? age : "",
2264 refname, tag_commit, tags_navs_disp) == -1) {
2265 error = got_error_from_errno("asprintf");
2269 free(tags_navs_disp);
2272 error = gw_get_time_str(&age, tagger_time, TM_LONG);
2275 error = gw_html_escape(&escaped_tagger, tagger);
2278 error = gw_html_escape(&escaped_tag_commit, tag_commit);
2281 if (asprintf(&tag_row, tag_info, age ? age : "",
2282 escaped_tagger, escaped_tag_commit) == -1) {
2283 error = got_error_from_errno("asprintf");
2291 error = buf_puts(&newsize, diffbuf, tag_row);
2295 if (limit && --limit == 0)
2298 got_object_tag_close(tag);
2308 free(escaped_tagger);
2309 escaped_tagger = NULL;
2310 free(escaped_tag_commit);
2311 escaped_tag_commit = NULL;
2318 if (buf_len(diffbuf) > 0) {
2319 error = buf_putc(diffbuf, '\0');
2320 *tag_html = strdup(buf_get(diffbuf));
2321 if (*tag_html == NULL)
2322 error = got_error_from_errno("strdup");
2326 got_object_tag_close(tag);
2331 free(escaped_tagger);
2332 free(escaped_tag_commit);
2336 got_ref_list_free(&refs);
2338 got_repo_close(repo);
2343 gw_free_headers(struct gw_header *header)
2347 if (header->commit != NULL)
2348 got_object_commit_close(header->commit);
2350 got_repo_close(header->repo);
2351 free(header->refs_str);
2352 free(header->commit_id);
2353 free(header->parent_id);
2354 free(header->tree_id);
2355 free(header->committer);
2356 free(header->commit_msg);
2359 static struct gw_header *
2362 struct gw_header *header;
2364 header = malloc(sizeof(*header));
2368 header->repo = NULL;
2369 header->commit = NULL;
2371 header->path = NULL;
2372 SIMPLEQ_INIT(&header->refs);
2377 static const struct got_error *
2378 gw_get_commits(struct gw_trans * gw_trans, struct gw_header *header,
2381 const struct got_error *error = NULL;
2382 struct got_commit_graph *graph = NULL;
2384 error = got_commit_graph_open(&graph, header->path, 0);
2388 error = got_commit_graph_iter_start(graph, header->id, header->repo,
2394 error = got_commit_graph_iter_next(&header->id, graph,
2395 header->repo, NULL, NULL);
2397 if (error->code == GOT_ERR_ITER_COMPLETED)
2401 if (header->id == NULL)
2404 error = got_object_open_as_commit(&header->commit, header->repo,
2409 error = gw_get_commit(gw_trans, header);
2411 struct gw_header *n_header = NULL;
2412 if ((n_header = gw_init_header()) == NULL) {
2413 error = got_error_from_errno("malloc");
2417 n_header->refs_str = strdup(header->refs_str);
2418 n_header->commit_id = strdup(header->commit_id);
2419 n_header->parent_id = strdup(header->parent_id);
2420 n_header->tree_id = strdup(header->tree_id);
2421 n_header->author = strdup(header->author);
2422 n_header->committer = strdup(header->committer);
2423 n_header->commit_msg = strdup(header->commit_msg);
2424 n_header->committer_time = header->committer_time;
2425 TAILQ_INSERT_TAIL(&gw_trans->gw_headers, n_header,
2428 if (error || (limit && --limit == 0))
2433 got_commit_graph_close(graph);
2437 static const struct got_error *
2438 gw_get_commit(struct gw_trans *gw_trans, struct gw_header *header)
2440 const struct got_error *error = NULL;
2441 struct got_reflist_entry *re;
2442 struct got_object_id *id2 = NULL;
2443 struct got_object_qid *parent_id;
2444 char *refs_str = NULL, *commit_msg = NULL, *commit_msg0;
2447 SIMPLEQ_FOREACH(re, &header->refs, entry) {
2450 struct got_tag_object *tag = NULL;
2453 name = got_ref_get_name(re->ref);
2454 if (strcmp(name, GOT_REF_HEAD) == 0)
2456 if (strncmp(name, "refs/", 5) == 0)
2458 if (strncmp(name, "got/", 4) == 0)
2460 if (strncmp(name, "heads/", 6) == 0)
2462 if (strncmp(name, "remotes/", 8) == 0)
2464 if (strncmp(name, "tags/", 5) == 0) {
2465 error = got_object_open_as_tag(&tag, header->repo,
2468 if (error->code != GOT_ERR_OBJ_TYPE)
2471 * Ref points at something other
2478 cmp = got_object_id_cmp(tag ?
2479 got_object_tag_get_object_id(tag) : re->id, header->id);
2481 got_object_tag_close(tag);
2485 if (asprintf(&refs_str, "%s%s%s", s ? s : "",
2486 s ? ", " : "", name) == -1) {
2487 error = got_error_from_errno("asprintf");
2491 header->refs_str = strdup(refs_str);
2495 if (refs_str == NULL)
2496 header->refs_str = strdup("");
2499 error = got_object_id_str(&header->commit_id, header->id);
2503 error = got_object_id_str(&header->tree_id,
2504 got_object_commit_get_tree_id(header->commit));
2508 if (gw_trans->action == GW_DIFF) {
2509 parent_id = SIMPLEQ_FIRST(
2510 got_object_commit_get_parent_ids(header->commit));
2511 if (parent_id != NULL) {
2512 id2 = got_object_id_dup(parent_id->id);
2514 error = got_object_id_str(&header->parent_id, id2);
2519 header->parent_id = strdup("/dev/null");
2521 header->parent_id = strdup("");
2523 header->committer_time =
2524 got_object_commit_get_committer_time(header->commit);
2527 got_object_commit_get_author(header->commit);
2528 error = gw_html_escape(&header->committer,
2529 got_object_commit_get_committer(header->commit));
2533 /* XXX Doesn't the log message require escaping? */
2534 error = got_object_commit_get_logmsg(&commit_msg0, header->commit);
2538 commit_msg = commit_msg0;
2539 while (*commit_msg == '\n')
2542 header->commit_msg = strdup(commit_msg);
2547 static const struct got_error *
2548 gw_get_header(struct gw_trans *gw_trans, struct gw_header *header, int limit)
2550 const struct got_error *error = NULL;
2551 char *in_repo_path = NULL;
2553 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2557 if (gw_trans->commit == NULL) {
2558 struct got_reference *head_ref;
2559 error = got_ref_open(&head_ref, header->repo,
2560 gw_trans->headref, 0);
2564 error = got_ref_resolve(&header->id, header->repo, head_ref);
2565 got_ref_close(head_ref);
2569 error = got_object_open_as_commit(&header->commit,
2570 header->repo, header->id);
2572 struct got_reference *ref;
2573 error = got_ref_open(&ref, header->repo, gw_trans->commit, 0);
2574 if (error == NULL) {
2576 error = got_ref_resolve(&header->id, header->repo, ref);
2580 error = got_object_get_type(&obj_type, header->repo,
2584 if (obj_type == GOT_OBJ_TYPE_TAG) {
2585 struct got_tag_object *tag;
2586 error = got_object_open_as_tag(&tag,
2587 header->repo, header->id);
2590 if (got_object_tag_get_object_type(tag) !=
2591 GOT_OBJ_TYPE_COMMIT) {
2592 got_object_tag_close(tag);
2593 error = got_error(GOT_ERR_OBJ_TYPE);
2597 header->id = got_object_id_dup(
2598 got_object_tag_get_object_id(tag));
2599 if (header->id == NULL)
2600 error = got_error_from_errno(
2601 "got_object_id_dup");
2602 got_object_tag_close(tag);
2605 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
2606 error = got_error(GOT_ERR_OBJ_TYPE);
2609 error = got_object_open_as_commit(&header->commit,
2610 header->repo, header->id);
2614 if (header->commit == NULL) {
2615 error = got_repo_match_object_id_prefix(&header->id,
2616 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2621 error = got_repo_match_object_id_prefix(&header->id,
2622 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2626 error = got_repo_map_path(&in_repo_path, header->repo,
2627 gw_trans->repo_path, 1);
2632 header->path = strdup(in_repo_path);
2636 error = got_ref_list(&header->refs, header->repo, NULL,
2637 got_ref_cmp_by_name, NULL);
2641 error = gw_get_commits(gw_trans, header, limit);
2649 char datebuf[11]; /* YYYY-MM-DD + NUL */
2652 struct gw_blame_cb_args {
2653 struct blame_line *lines;
2657 off_t *line_offsets;
2659 struct got_repository *repo;
2660 struct gw_trans *gw_trans;
2661 struct buf *blamebuf;
2664 static const struct got_error *
2665 gw_blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
2667 const struct got_error *err = NULL;
2668 struct gw_blame_cb_args *a = arg;
2669 struct blame_line *bline;
2671 size_t linesize = 0, newsize;
2672 struct got_commit_object *commit = NULL;
2675 time_t committer_time;
2677 if (nlines != a->nlines ||
2678 (lineno != -1 && lineno < 1) || lineno > a->nlines)
2679 return got_error(GOT_ERR_RANGE);
2682 return NULL; /* no change in this commit */
2684 /* Annotate this line. */
2685 bline = &a->lines[lineno - 1];
2686 if (bline->annotated)
2688 err = got_object_id_str(&bline->id_str, id);
2692 err = got_object_open_as_commit(&commit, a->repo, id);
2696 bline->committer = strdup(got_object_commit_get_committer(commit));
2697 if (bline->committer == NULL) {
2698 err = got_error_from_errno("strdup");
2702 committer_time = got_object_commit_get_committer_time(commit);
2703 if (localtime_r(&committer_time, &tm) == NULL)
2704 return got_error_from_errno("localtime_r");
2705 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
2706 &tm) >= sizeof(bline->datebuf)) {
2707 err = got_error(GOT_ERR_NO_SPACE);
2710 bline->annotated = 1;
2712 /* Print lines annotated so far. */
2713 bline = &a->lines[a->lineno_cur - 1];
2714 if (!bline->annotated)
2717 offset = a->line_offsets[a->lineno_cur - 1];
2718 if (fseeko(a->f, offset, SEEK_SET) == -1) {
2719 err = got_error_from_errno("fseeko");
2723 while (bline->annotated) {
2724 char *smallerthan, *at, *nl, *committer, *blame_row = NULL,
2725 *line_escape = NULL;
2728 if (getline(&line, &linesize, a->f) == -1) {
2730 err = got_error_from_errno("getline");
2734 committer = bline->committer;
2735 smallerthan = strchr(committer, '<');
2736 if (smallerthan && smallerthan[1] != '\0')
2737 committer = smallerthan + 1;
2738 at = strchr(committer, '@');
2741 len = strlen(committer);
2743 committer[8] = '\0';
2745 nl = strchr(line, '\n');
2749 err = gw_html_escape(&line_escape, line);
2753 if (a->gw_trans->repo_folder == NULL)
2754 a->gw_trans->repo_folder = strdup("");
2755 if (a->gw_trans->repo_folder == NULL)
2757 asprintf(&blame_row, blame_line, a->nlines_prec, a->lineno_cur,
2758 a->gw_trans->repo_name, bline->id_str,
2759 a->gw_trans->repo_file, a->gw_trans->repo_folder,
2760 bline->id_str, bline->datebuf, committer, line_escape);
2762 err = buf_puts(&newsize, a->blamebuf, blame_row);
2766 bline = &a->lines[a->lineno_cur - 1];
2773 got_object_commit_close(commit);
2778 static const struct got_error *
2779 gw_get_file_blame_blob(char **blame_html, struct gw_trans *gw_trans)
2781 const struct got_error *error = NULL;
2782 struct got_repository *repo = NULL;
2783 struct got_object_id *obj_id = NULL;
2784 struct got_object_id *commit_id = NULL;
2785 struct got_blob_object *blob = NULL;
2786 char *path = NULL, *in_repo_path = NULL;
2787 struct gw_blame_cb_args bca;
2793 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2797 if (asprintf(&path, "%s%s%s",
2798 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2799 gw_trans->repo_folder ? "/" : "",
2800 gw_trans->repo_file) == -1) {
2801 error = got_error_from_errno("asprintf");
2805 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2809 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2810 GOT_OBJ_TYPE_COMMIT, 1, repo);
2814 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2818 if (obj_id == NULL) {
2819 error = got_error(GOT_ERR_NO_OBJ);
2823 error = got_object_get_type(&obj_type, repo, obj_id);
2827 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2828 error = got_error(GOT_ERR_OBJ_TYPE);
2832 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2836 error = buf_alloc(&bca.blamebuf, 0);
2840 bca.f = got_opentemp();
2841 if (bca.f == NULL) {
2842 error = got_error_from_errno("got_opentemp");
2845 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
2846 &bca.line_offsets, bca.f, blob);
2847 if (error || bca.nlines == 0)
2850 /* Don't include \n at EOF in the blame line count. */
2851 if (bca.line_offsets[bca.nlines - 1] == filesize)
2854 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
2855 if (bca.lines == NULL) {
2856 error = got_error_from_errno("calloc");
2860 bca.nlines_prec = 0;
2867 bca.gw_trans = gw_trans;
2869 error = got_blame(in_repo_path, commit_id, repo, gw_blame_cb, &bca,
2873 if (buf_len(bca.blamebuf) > 0) {
2874 error = buf_putc(bca.blamebuf, '\0');
2877 *blame_html = strdup(buf_get(bca.blamebuf));
2878 if (*blame_html == NULL) {
2879 error = got_error_from_errno("strdup");
2884 free(bca.line_offsets);
2891 for (i = 0; i < bca.nlines; i++) {
2892 struct blame_line *bline = &bca.lines[i];
2893 free(bline->id_str);
2894 free(bline->committer);
2897 if (bca.f && fclose(bca.f) == EOF && error == NULL)
2898 error = got_error_from_errno("fclose");
2900 got_object_blob_close(blob);
2902 got_repo_close(repo);
2906 static const struct got_error *
2907 gw_get_file_read_blob(char **blobstr, size_t *filesize, struct gw_trans *gw_trans)
2909 const struct got_error *error = NULL;
2910 struct got_repository *repo = NULL;
2911 struct got_object_id *obj_id = NULL;
2912 struct got_object_id *commit_id = NULL;
2913 struct got_blob_object *blob = NULL;
2914 char *path = NULL, *in_repo_path = NULL;
2922 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2926 if (asprintf(&path, "%s%s%s",
2927 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2928 gw_trans->repo_folder ? "/" : "",
2929 gw_trans->repo_file) == -1) {
2930 error = got_error_from_errno("asprintf");
2934 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2938 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2939 GOT_OBJ_TYPE_COMMIT, 1, repo);
2943 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2947 if (obj_id == NULL) {
2948 error = got_error(GOT_ERR_NO_OBJ);
2952 error = got_object_get_type(&obj_type, repo, obj_id);
2956 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2957 error = got_error(GOT_ERR_OBJ_TYPE);
2961 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2967 error = got_error_from_errno("got_opentemp");
2970 error = got_object_blob_dump_to_file(filesize, NULL, NULL, f, blob);
2974 /* XXX This will fail on large files... */
2975 *blobstr = calloc(*filesize + 1, sizeof(**blobstr));
2976 if (*blobstr == NULL) {
2977 error = got_error_from_errno("calloc");
2981 n = fread(*blobstr, 1, *filesize, f);
2984 error = got_ferror(f, GOT_ERR_IO);
2993 got_object_blob_close(blob);
2995 got_repo_close(repo);
2996 if (f != NULL && fclose(f) == -1 && error == NULL)
2997 error = got_error_from_errno("fclose");
3006 static const struct got_error *
3007 gw_get_repo_tree(char **tree_html, struct gw_trans *gw_trans)
3009 const struct got_error *error = NULL;
3010 struct got_repository *repo = NULL;
3011 struct got_object_id *tree_id = NULL, *commit_id = NULL;
3012 struct got_tree_object *tree = NULL;
3013 struct buf *diffbuf = NULL;
3015 char *path = NULL, *in_repo_path = NULL, *tree_row = NULL;
3016 char *id_str = NULL;
3017 char *build_folder = NULL;
3018 char *url_html = NULL;
3019 const char *class = NULL;
3020 int nentries, i, class_flip = 0;
3024 error = buf_alloc(&diffbuf, 0);
3028 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3032 if (gw_trans->repo_folder != NULL)
3033 path = strdup(gw_trans->repo_folder);
3035 error = got_repo_map_path(&in_repo_path, repo,
3036 gw_trans->repo_path, 1);
3040 path = in_repo_path;
3043 if (gw_trans->commit == NULL) {
3044 struct got_reference *head_ref;
3045 error = got_ref_open(&head_ref, repo, gw_trans->headref, 0);
3048 error = got_ref_resolve(&commit_id, repo, head_ref);
3051 got_ref_close(head_ref);
3054 error = got_repo_match_object_id(&commit_id, NULL,
3055 gw_trans->commit, GOT_OBJ_TYPE_COMMIT, 1, repo);
3060 error = got_object_id_str(&gw_trans->commit, commit_id);
3064 error = got_object_id_by_path(&tree_id, repo, commit_id, path);
3068 error = got_object_open_as_tree(&tree, repo, tree_id);
3072 nentries = got_object_tree_get_nentries(tree);
3073 for (i = 0; i < nentries; i++) {
3074 struct got_tree_entry *te;
3075 const char *modestr = "";
3078 te = got_object_tree_get_entry(tree, i);
3080 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
3084 mode = got_tree_entry_get_mode(te);
3085 if (got_object_tree_entry_is_submodule(te))
3087 else if (S_ISLNK(mode))
3089 else if (S_ISDIR(mode))
3091 else if (mode & S_IXUSR)
3094 if (class_flip == 0) {
3095 class = "back_lightgray";
3098 class = "back_white";
3102 if (S_ISDIR(mode)) {
3103 if (asprintf(&build_folder, "%s%s%s",
3104 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3105 gw_trans->repo_folder ? "/" : "",
3106 got_tree_entry_get_name(te)) == -1) {
3107 error = got_error_from_errno(
3112 if (asprintf(&url_html, folder_html,
3113 gw_trans->repo_name, gw_trans->action_name,
3114 gw_trans->commit, build_folder,
3115 got_tree_entry_get_name(te), modestr) == -1) {
3116 error = got_error_from_errno("asprintf");
3119 if (asprintf(&tree_row, tree_line, class, url_html,
3121 error = got_error_from_errno("asprintf");
3125 if (asprintf(&url_html, file_html, gw_trans->repo_name,
3126 "blob", gw_trans->commit,
3127 got_tree_entry_get_name(te),
3128 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3129 got_tree_entry_get_name(te), modestr) == -1) {
3130 error = got_error_from_errno("asprintf");
3134 if (asprintf(&tree_row, tree_line_with_navs, class,
3135 url_html, class, gw_trans->repo_name, "blob",
3136 gw_trans->commit, got_tree_entry_get_name(te),
3137 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3138 "blob", gw_trans->repo_name,
3139 "blame", gw_trans->commit,
3140 got_tree_entry_get_name(te),
3141 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3143 error = got_error_from_errno("asprintf");
3148 error = buf_puts(&newsize, diffbuf, tree_row);
3159 build_folder = NULL;
3162 if (buf_len(diffbuf) > 0) {
3163 error = buf_putc(diffbuf, '\0');
3166 *tree_html = strdup(buf_get(diffbuf));
3167 if (*tree_html == NULL) {
3168 error = got_error_from_errno("strdup");
3174 got_object_tree_close(tree);
3176 got_repo_close(repo);
3188 static const struct got_error *
3189 gw_get_repo_heads(char **head_html, struct gw_trans *gw_trans)
3191 const struct got_error *error = NULL;
3192 struct got_repository *repo = NULL;
3193 struct got_reflist_head refs;
3194 struct got_reflist_entry *re;
3195 char *head_row = NULL, *head_navs_disp = NULL, *age = NULL;
3196 struct buf *diffbuf = NULL;
3201 SIMPLEQ_INIT(&refs);
3203 error = buf_alloc(&diffbuf, 0);
3207 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3211 error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
3216 SIMPLEQ_FOREACH(re, &refs, entry) {
3219 refname = strdup(got_ref_get_name(re->ref));
3220 if (refname == NULL) {
3221 error = got_error_from_errno("got_ref_to_str");
3225 if (strncmp(refname, "refs/heads/", 11) != 0) {
3230 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
3235 if (asprintf(&head_navs_disp, heads_navs, gw_trans->repo_name,
3236 refname, gw_trans->repo_name, refname,
3237 gw_trans->repo_name, refname, gw_trans->repo_name,
3239 error = got_error_from_errno("asprintf");
3243 if (strncmp(refname, "refs/heads/", 11) == 0)
3246 if (asprintf(&head_row, heads_row, age, refname,
3247 head_navs_disp) == -1) {
3248 error = got_error_from_errno("asprintf");
3252 error = buf_puts(&newsize, diffbuf, head_row);
3254 free(head_navs_disp);
3258 if (buf_len(diffbuf) > 0) {
3259 error = buf_putc(diffbuf, '\0');
3260 *head_html = strdup(buf_get(diffbuf));
3261 if (*head_html == NULL)
3262 error = got_error_from_errno("strdup");
3266 got_ref_list_free(&refs);
3268 got_repo_close(repo);
3273 gw_get_site_link(struct gw_trans *gw_trans)
3275 char *link = NULL, *repo = NULL, *action = NULL;
3277 if (gw_trans->repo_name != NULL &&
3278 asprintf(&repo, " / <a href='?path=%s&action=summary'>%s</a>",
3279 gw_trans->repo_name, gw_trans->repo_name) == -1)
3282 if (gw_trans->action_name != NULL &&
3283 asprintf(&action, " / %s", gw_trans->action_name) == -1) {
3288 if (asprintf(&link, site_link, GOTWEB,
3289 gw_trans->gw_conf->got_site_link,
3290 repo ? repo : "", action ? action : "") == -1) {
3301 static const struct got_error *
3302 gw_colordiff_line(char **colorized_line, char *buf)
3304 const struct got_error *error = NULL;
3305 char *div_diff_line_div = NULL, *color = NULL;
3306 struct buf *diffbuf = NULL;
3309 *colorized_line = NULL;
3311 error = buf_alloc(&diffbuf, 0);
3315 if (strncmp(buf, "-", 1) == 0)
3316 color = "diff_minus";
3317 else if (strncmp(buf, "+", 1) == 0)
3318 color = "diff_plus";
3319 else if (strncmp(buf, "@@", 2) == 0)
3320 color = "diff_chunk_header";
3321 else if (strncmp(buf, "@@", 2) == 0)
3322 color = "diff_chunk_header";
3323 else if (strncmp(buf, "commit +", 8) == 0)
3324 color = "diff_meta";
3325 else if (strncmp(buf, "commit -", 8) == 0)
3326 color = "diff_meta";
3327 else if (strncmp(buf, "blob +", 6) == 0)
3328 color = "diff_meta";
3329 else if (strncmp(buf, "blob -", 6) == 0)
3330 color = "diff_meta";
3331 else if (strncmp(buf, "file +", 6) == 0)
3332 color = "diff_meta";
3333 else if (strncmp(buf, "file -", 6) == 0)
3334 color = "diff_meta";
3335 else if (strncmp(buf, "from:", 5) == 0)
3336 color = "diff_author";
3337 else if (strncmp(buf, "via:", 4) == 0)
3338 color = "diff_author";
3339 else if (strncmp(buf, "date:", 5) == 0)
3340 color = "diff_date";
3342 if (asprintf(&div_diff_line_div, div_diff_line, color ? color : "")
3344 error = got_error_from_errno("asprintf");
3348 error = buf_puts(&newsize, diffbuf, div_diff_line_div);
3352 error = buf_puts(&newsize, diffbuf, buf);
3356 if (buf_len(diffbuf) > 0) {
3357 error = buf_putc(diffbuf, '\0');
3358 *colorized_line = strdup(buf_get(diffbuf));
3359 if (*colorized_line == NULL)
3360 error = got_error_from_errno("strdup");
3364 free(div_diff_line_div);
3369 * XXX This function should not exist.
3370 * We should let khtml_puts(3) handle HTML escaping.
3372 static const struct got_error *
3373 gw_html_escape(char **escaped_html, const char *orig_html)
3375 const struct got_error *error = NULL;
3376 struct escape_pair {
3387 size_t orig_len, len;
3390 orig_len = strlen(orig_html);
3392 for (i = 0; i < orig_len; i++) {
3393 for (j = 0; j < nitems(esc); j++) {
3394 if (orig_html[i] != esc[j].c)
3396 len += strlen(esc[j].s) - 1 /* escaped char */;
3400 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
3401 if (*escaped_html == NULL)
3402 return got_error_from_errno("calloc");
3405 for (i = 0; i < orig_len; i++) {
3407 for (j = 0; j < nitems(esc); j++) {
3408 if (orig_html[i] != esc[j].c)
3411 if (strlcat(*escaped_html, esc[j].s, len + 1)
3413 error = got_error(GOT_ERR_NO_SPACE);
3416 x += strlen(esc[j].s);
3421 (*escaped_html)[x] = orig_html[i];
3427 free(*escaped_html);
3428 *escaped_html = NULL;
3430 (*escaped_html)[x] = '\0';
3436 main(int argc, char *argv[])
3438 const struct got_error *error = NULL;
3439 struct gw_trans *gw_trans;
3440 struct gw_dir *dir = NULL, *tdir;
3441 const char *page = "index";
3445 if ((gw_trans = malloc(sizeof(struct gw_trans))) == NULL)
3448 if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
3451 if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
3454 if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
3457 kerr = khttp_parse(gw_trans->gw_req, gw_keys, KEY__ZMAX, &page, 1, 0);
3458 if (kerr != KCGI_OK) {
3459 error = gw_kcgi_error(kerr);
3463 if ((gw_trans->gw_conf =
3464 malloc(sizeof(struct gotweb_conf))) == NULL) {
3466 error = got_error_from_errno("malloc");
3470 TAILQ_INIT(&gw_trans->gw_dirs);
3471 TAILQ_INIT(&gw_trans->gw_headers);
3474 gw_trans->repos_total = 0;
3475 gw_trans->repo_path = NULL;
3476 gw_trans->commit = NULL;
3477 gw_trans->headref = strdup(GOT_REF_HEAD);
3478 gw_trans->mime = KMIME_TEXT_HTML;
3479 gw_trans->gw_tmpl->key = gw_templs;
3480 gw_trans->gw_tmpl->keysz = TEMPL__MAX;
3481 gw_trans->gw_tmpl->arg = gw_trans;
3482 gw_trans->gw_tmpl->cb = gw_template;
3483 error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
3487 error = gw_parse_querystring(gw_trans);
3491 if (gw_trans->action == GW_BLOB)
3492 error = gw_blob(gw_trans);
3494 error = gw_display_index(gw_trans);
3497 gw_trans->mime = KMIME_TEXT_PLAIN;
3498 gw_trans->action = GW_ERR;
3499 gw_display_error(gw_trans, error);
3502 free(gw_trans->gw_conf->got_repos_path);
3503 free(gw_trans->gw_conf->got_site_name);
3504 free(gw_trans->gw_conf->got_site_owner);
3505 free(gw_trans->gw_conf->got_site_link);
3506 free(gw_trans->gw_conf->got_logo);
3507 free(gw_trans->gw_conf->got_logo_url);
3508 free(gw_trans->gw_conf);
3509 free(gw_trans->commit);
3510 free(gw_trans->repo_path);
3511 free(gw_trans->repo_name);
3512 free(gw_trans->repo_file);
3513 free(gw_trans->action_name);
3514 free(gw_trans->headref);
3516 TAILQ_FOREACH_SAFE(dir, &gw_trans->gw_dirs, entry, tdir) {
3518 free(dir->description);
3527 khttp_free(gw_trans->gw_req);