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 */
91 const char *committer;
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(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(struct gw_trans *, char *);
184 static const struct got_error *gw_gen_commit_header(struct gw_trans *, char *,
186 static char *gw_gen_commit_header_old(char *, char*);
187 static const struct got_error *gw_gen_diff_header(struct gw_trans *, char *,
189 static const struct got_error *gw_gen_author_header(struct gw_trans *,
191 static const struct got_error *gw_gen_age_header(struct gw_trans *,
193 static const struct got_error *gw_gen_committer_header(struct gw_trans *,
195 static const struct got_error *gw_gen_commit_msg_header(struct gw_trans*,
197 static char *gw_gen_commit_msg_header_old(char *);
198 static const struct got_error *gw_gen_tree_header(struct gw_trans *, char *);
200 static void gw_free_headers(struct gw_header *);
201 static const struct got_error* gw_display_open(struct gw_trans *, enum khttp,
203 static const struct got_error* gw_display_index(struct gw_trans *);
204 static void gw_display_error(struct gw_trans *,
205 const struct got_error *);
207 static int gw_template(size_t, void *);
209 static const struct got_error* gw_get_header(struct gw_trans *,
210 struct gw_header *, int);
211 static const struct got_error* gw_get_commits(struct gw_trans *,
212 struct gw_header *, int);
213 static const struct got_error* gw_get_commit(struct gw_trans *,
215 static const struct got_error* gw_apply_unveil(const char *, const char *);
216 static const struct got_error* gw_blame_cb(void *, int, int,
217 struct got_object_id *);
218 static const struct got_error* gw_load_got_paths(struct gw_trans *);
219 static const struct got_error* gw_load_got_path(struct gw_trans *,
221 static const struct got_error* gw_parse_querystring(struct gw_trans *);
223 static const struct got_error* gw_blame(struct gw_trans *);
224 static const struct got_error* gw_blob(struct gw_trans *);
225 static const struct got_error* gw_diff(struct gw_trans *);
226 static const struct got_error* gw_index(struct gw_trans *);
227 static const struct got_error* gw_commits(struct gw_trans *);
228 static const struct got_error* gw_briefs(struct gw_trans *);
229 static const struct got_error* gw_summary(struct gw_trans *);
230 static const struct got_error* gw_tree(struct gw_trans *);
231 static const struct got_error* gw_tag(struct gw_trans *);
233 struct gw_query_action {
234 unsigned int func_id;
235 const char *func_name;
236 const struct got_error *(*func_main)(struct gw_trans *);
240 enum gw_query_actions {
253 static struct gw_query_action gw_query_funcs[] = {
254 { GW_BLAME, "blame", gw_blame, "gw_tmpl/blame.tmpl" },
255 { GW_BLOB, "blob", NULL, NULL },
256 { GW_BRIEFS, "briefs", gw_briefs, "gw_tmpl/briefs.tmpl" },
257 { GW_COMMITS, "commits", gw_commits, "gw_tmpl/commit.tmpl" },
258 { GW_DIFF, "diff", gw_diff, "gw_tmpl/diff.tmpl" },
259 { GW_ERR, NULL, NULL, "gw_tmpl/err.tmpl" },
260 { GW_INDEX, "index", gw_index, "gw_tmpl/index.tmpl" },
261 { GW_SUMMARY, "summary", gw_summary, "gw_tmpl/summry.tmpl" },
262 { GW_TAG, "tag", gw_tag, "gw_tmpl/tag.tmpl" },
263 { GW_TREE, "tree", gw_tree, "gw_tmpl/tree.tmpl" },
266 static const struct got_error *
267 gw_kcgi_error(enum kcgi_err kerr)
272 if (kerr == KCGI_EXIT || kerr == KCGI_HUP)
273 return got_error(GOT_ERR_CANCELLED);
275 if (kerr == KCGI_ENOMEM)
276 return got_error_set_errno(ENOMEM,
277 kcgi_strerror(kerr != KCGI_OK));
279 if (kerr == KCGI_ENFILE)
280 return got_error_set_errno(ENFILE,
281 kcgi_strerror(kerr != KCGI_OK));
283 if (kerr == KCGI_EAGAIN)
284 return got_error_set_errno(EAGAIN,
285 kcgi_strerror(kerr != KCGI_OK));
287 if (kerr == KCGI_FORM)
288 return got_error_msg(GOT_ERR_IO,
289 kcgi_strerror(kerr != KCGI_OK));
291 return got_error_from_errno(kcgi_strerror(kerr != KCGI_OK));
294 static const struct got_error *
295 gw_apply_unveil(const char *repo_path, const char *repo_file)
297 const struct got_error *err;
299 if (repo_path && repo_file) {
301 if (asprintf(&full_path, "%s/%s", repo_path, repo_file) == -1)
302 return got_error_from_errno("asprintf unveil");
303 if (unveil(full_path, "r") != 0)
304 return got_error_from_errno2("unveil", full_path);
307 if (repo_path && unveil(repo_path, "r") != 0)
308 return got_error_from_errno2("unveil", repo_path);
310 if (unveil("/tmp", "rwc") != 0)
311 return got_error_from_errno2("unveil", "/tmp");
313 err = got_privsep_unveil_exec_helpers();
317 if (unveil(NULL, NULL) != 0)
318 return got_error_from_errno("unveil");
323 static const struct got_error *
324 gw_empty_string(char **s)
328 return got_error_from_errno("strdup");
333 isbinary(const char *buf, size_t n)
335 return (memchr(buf, '\0', n) != NULL);
338 static const struct got_error *
339 gw_blame(struct gw_trans *gw_trans)
341 const struct got_error *error = NULL;
342 struct gw_header *header = NULL;
343 char *blame = NULL, *blame_html = NULL, *blame_html_disp = NULL;
344 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
347 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
349 return got_error_from_errno("pledge");
351 if ((header = gw_init_header()) == NULL)
352 return got_error_from_errno("malloc");
354 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
358 error = gw_get_header(gw_trans, header, 1);
362 error = gw_get_file_blame_blob(&blame_html, gw_trans);
366 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
369 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
370 error = got_error_from_errno("asprintf");
374 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
377 if (asprintf(&blame_html_disp, blame_header, age_html,
378 gw_gen_commit_msg_header_old(escaped_commit_msg), blame_html) == -1) {
379 error = got_error_from_errno("asprintf");
383 if (asprintf(&blame, blame_wrapper, blame_html_disp) == -1) {
384 error = got_error_from_errno("asprintf");
388 kerr = khttp_puts(gw_trans->gw_req, blame);
390 error = gw_kcgi_error(kerr != KCGI_OK);
392 got_ref_list_free(&header->refs);
393 gw_free_headers(header);
394 free(blame_html_disp);
397 free(escaped_commit_msg);
401 static const struct got_error *
402 gw_blob(struct gw_trans *gw_trans)
404 const struct got_error *error = NULL;
405 struct gw_header *header = NULL;
406 char *content = NULL;
410 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
412 return got_error_from_errno("pledge");
414 if ((header = gw_init_header()) == NULL)
415 return got_error_from_errno("malloc");
417 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
421 error = gw_get_header(gw_trans, header, 1);
425 error = gw_get_file_read_blob(&content, &filesize, gw_trans);
429 if (isbinary(content, filesize))
430 gw_trans->mime = KMIME_APP_OCTET_STREAM;
432 gw_trans->mime = KMIME_TEXT_PLAIN;
434 error = gw_display_index(gw_trans);
438 kerr = khttp_write(gw_trans->gw_req, content, filesize);
440 error = gw_kcgi_error(kerr != KCGI_OK);
442 got_ref_list_free(&header->refs);
443 gw_free_headers(header);
448 static const struct got_error *
449 gw_diff(struct gw_trans *gw_trans)
451 const struct got_error *error = NULL;
452 struct gw_header *header = NULL;
453 char *age = NULL, *escaped_commit_msg = NULL;
454 enum kcgi_err kerr = KCGI_OK;
456 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
458 return got_error_from_errno("pledge");
460 if ((header = gw_init_header()) == NULL)
461 return got_error_from_errno("malloc");
463 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
467 error = gw_get_header(gw_trans, header, 1);
472 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
473 "diff_title_wrapper", KATTR__MAX);
476 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
477 "diff_title", KATTR__MAX);
480 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Diff");
483 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
488 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
489 "diff_content", KATTR__MAX);
494 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
495 "diff_header_wrapper", KATTR__MAX);
498 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
499 "diff_header", KATTR__MAX);
502 error = gw_gen_diff_header(gw_trans, header->parent_id,
506 error = gw_gen_commit_header(gw_trans, header->commit_id,
510 error = gw_gen_tree_header(gw_trans, header->tree_id);
513 error = gw_gen_author_header(gw_trans, header->author);
516 error = gw_gen_committer_header(gw_trans, header->author);
519 error = gw_get_time_str(&age, header->committer_time,
523 error = gw_gen_age_header(gw_trans, age ?age : "");
527 * XXX: keeping this for now, since kcgihtml does not convert
528 * \n into <br /> yet.
530 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
533 error = gw_gen_commit_msg_header(gw_trans, header->commit_msg);
536 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
539 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
540 "dotted_line", KATTR__MAX);
543 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
548 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
552 error = gw_get_diff(gw_trans, header);
556 /* diff content close */
557 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
561 got_ref_list_free(&header->refs);
562 gw_free_headers(header);
564 free(escaped_commit_msg);
565 if (error == NULL && kerr != KCGI_OK)
566 error = gw_kcgi_error(kerr != KCGI_OK);
570 static const struct got_error *
571 gw_index(struct gw_trans *gw_trans)
573 const struct got_error *error = NULL;
574 struct gw_dir *gw_dir = NULL;
575 char *html, *navs, *next, *prev;
576 unsigned int prev_disp = 0, next_disp = 1, dir_c = 0;
579 if (pledge("stdio rpath proc exec sendfd unveil",
581 error = got_error_from_errno("pledge");
585 error = gw_apply_unveil(gw_trans->gw_conf->got_repos_path, NULL);
589 error = gw_load_got_paths(gw_trans);
593 kerr = khttp_puts(gw_trans->gw_req, index_projects_header);
595 return gw_kcgi_error(kerr != KCGI_OK);
597 if (TAILQ_EMPTY(&gw_trans->gw_dirs)) {
598 if (asprintf(&html, index_projects_empty,
599 gw_trans->gw_conf->got_repos_path) == -1)
600 return got_error_from_errno("asprintf");
601 kerr = khttp_puts(gw_trans->gw_req, html);
603 error = gw_kcgi_error(kerr != KCGI_OK);
608 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry)
611 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry) {
612 if (gw_trans->page > 0 && (gw_trans->page *
613 gw_trans->gw_conf->got_max_repos_display) > prev_disp) {
622 if(asprintf(&navs, index_navs, gw_dir->name, gw_dir->name,
623 gw_dir->name, gw_dir->name) == -1)
624 return got_error_from_errno("asprintf");
626 if (asprintf(&html, index_projects, gw_dir->name, gw_dir->name,
627 gw_dir->description, gw_dir->owner ? gw_dir->owner : "",
630 return got_error_from_errno("asprintf");
632 kerr = khttp_puts(gw_trans->gw_req, html);
636 return gw_kcgi_error(kerr != KCGI_OK);
638 if (gw_trans->gw_conf->got_max_repos_display == 0)
641 if (next_disp == gw_trans->gw_conf->got_max_repos_display) {
642 kerr = khttp_puts(gw_trans->gw_req, np_wrapper_start);
644 return gw_kcgi_error(kerr != KCGI_OK);
645 } else if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
646 (gw_trans->page > 0) &&
647 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
648 prev_disp == gw_trans->repos_total)) {
649 kerr = khttp_puts(gw_trans->gw_req, np_wrapper_start);
651 return gw_kcgi_error(kerr != KCGI_OK);
654 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
655 (gw_trans->page > 0) &&
656 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
657 prev_disp == gw_trans->repos_total)) {
658 if (asprintf(&prev, nav_prev, gw_trans->page - 1) == -1)
659 return got_error_from_errno("asprintf");
660 kerr = khttp_puts(gw_trans->gw_req, prev);
663 return gw_kcgi_error(kerr != KCGI_OK);
666 kerr = khttp_puts(gw_trans->gw_req, div_end);
668 return gw_kcgi_error(kerr != KCGI_OK);
670 if (gw_trans->gw_conf->got_max_repos_display > 0 &&
671 next_disp == gw_trans->gw_conf->got_max_repos_display &&
672 dir_c != (gw_trans->page + 1) *
673 gw_trans->gw_conf->got_max_repos_display) {
674 if (asprintf(&next, nav_next, gw_trans->page + 1) == -1)
675 return got_error_from_errno("calloc");
676 kerr = khttp_puts(gw_trans->gw_req, next);
679 return gw_kcgi_error(kerr != KCGI_OK);
680 kerr = khttp_puts(gw_trans->gw_req, div_end);
682 error = gw_kcgi_error(kerr != KCGI_OK);
687 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
688 (gw_trans->page > 0) &&
689 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
690 prev_disp == gw_trans->repos_total)) {
691 kerr = khttp_puts(gw_trans->gw_req, div_end);
693 return gw_kcgi_error(kerr != KCGI_OK);
701 static const struct got_error *
702 gw_commits(struct gw_trans *gw_trans)
704 const struct got_error *error = NULL;
705 struct gw_header *header = NULL, *n_header = NULL;
706 char *age = NULL, *escaped_commit_msg = NULL;
707 char *href_diff = NULL, *href_tree = NULL;
708 enum kcgi_err kerr = KCGI_OK;
710 if ((header = gw_init_header()) == NULL)
711 return got_error_from_errno("malloc");
713 if (pledge("stdio rpath proc exec sendfd unveil",
715 error = got_error_from_errno("pledge");
719 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
723 error = gw_get_header(gw_trans, header,
724 gw_trans->gw_conf->got_max_commits_display);
728 /* commit briefs header */
729 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
730 "commits_title_wrapper", KATTR__MAX);
733 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
734 "commits_title", KATTR__MAX);
737 kerr = khtml_puts(gw_trans->gw_html_req, "Commits");
740 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
745 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
746 "commits_content", KATTR__MAX);
749 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
751 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
752 "commits_line_wrapper", KATTR__MAX);
755 error = gw_gen_commit_header(gw_trans, n_header->commit_id,
759 error = gw_gen_author_header(gw_trans, n_header->author);
762 error = gw_gen_committer_header(gw_trans, n_header->author);
765 error = gw_get_time_str(&age, n_header->committer_time,
769 error = gw_gen_age_header(gw_trans, age ?age : "");
772 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
777 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
778 "dotted_line", KATTR__MAX);
781 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
786 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
787 "commit", KATTR__MAX);
791 * XXX: keeping this for now, since kcgihtml does not convert
792 * \n into <br /> yet.
794 error = gw_html_escape(&escaped_commit_msg,
795 n_header->commit_msg);
798 kerr = khttp_puts(gw_trans->gw_req, escaped_commit_msg);
801 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
807 /* XXX: create gen code for this */
809 if (asprintf(&href_diff, "?path=%s&action=diff&commit=%s",
810 gw_trans->repo_name, n_header->commit_id) == -1) {
811 error = got_error_from_errno("asprintf");
814 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
815 KATTR_ID, "navs_wrapper", KATTR__MAX);
818 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
819 KATTR_ID, "navs", KATTR__MAX);
822 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
823 KATTR_HREF, href_diff, KATTR__MAX);
826 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
829 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
833 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
837 /* XXX: create gen code for this */
839 if (asprintf(&href_tree, "?path=%s&action=tree&commit=%s",
840 gw_trans->repo_name, n_header->commit_id) == -1) {
841 error = got_error_from_errno("asprintf");
844 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
845 KATTR_HREF, href_tree, KATTR__MAX);
848 khtml_puts(gw_trans->gw_html_req, "tree");
849 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
852 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
856 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
857 "solid_line", KATTR__MAX);
860 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
866 free(escaped_commit_msg);
867 escaped_commit_msg = NULL;
869 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
871 got_ref_list_free(&header->refs);
872 gw_free_headers(header);
873 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
874 gw_free_headers(n_header);
878 free(escaped_commit_msg);
879 if (error == NULL && kerr != KCGI_OK)
880 error = gw_kcgi_error(kerr != KCGI_OK);
884 static const struct got_error *
885 gw_briefs(struct gw_trans *gw_trans)
887 const struct got_error *error = NULL;
888 struct gw_header *header = NULL, *n_header = NULL;
889 char *age = NULL, *age_html = NULL;
890 char *href_diff = NULL, *href_tree = NULL;
891 char *newline, *smallerthan;
892 enum kcgi_err kerr = KCGI_OK;
894 if ((header = gw_init_header()) == NULL)
895 return got_error_from_errno("malloc");
897 if (pledge("stdio rpath proc exec sendfd unveil",
899 error = got_error_from_errno("pledge");
903 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
907 if (gw_trans->action == GW_SUMMARY)
908 error = gw_get_header(gw_trans, header, D_MAXSLCOMMDISP);
910 error = gw_get_header(gw_trans, header,
911 gw_trans->gw_conf->got_max_commits_display);
915 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
916 error = gw_get_time_str(&age, n_header->committer_time,
922 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
923 KATTR_ID, "briefs_wrapper", KATTR__MAX);
928 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
929 KATTR_ID, "briefs_age", KATTR__MAX);
932 if (asprintf(&age_html, "%s", age ? age : "") == -1) {
933 error = got_error_from_errno("asprintf");
936 kerr = khtml_puts(gw_trans->gw_html_req, age_html);
939 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
944 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
945 KATTR_ID, "briefs_author", KATTR__MAX);
948 smallerthan = strchr(n_header->author, '<');
951 kerr = khtml_puts(gw_trans->gw_html_req, n_header->author);
954 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
959 if (asprintf(&href_diff, "?path=%s&action=diff&commit=%s",
960 gw_trans->repo_name, n_header->commit_id) == -1) {
961 error = got_error_from_errno("asprintf");
964 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
965 KATTR_ID, "briefs_log", KATTR__MAX);
968 newline = strchr(n_header->commit_msg, '\n');
971 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
972 KATTR_HREF, href_diff, KATTR__MAX);
975 kerr = khtml_puts(gw_trans->gw_html_req, n_header->commit_msg);
978 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
983 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
984 KATTR_ID, "navs_wrapper", KATTR__MAX);
987 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
988 KATTR_ID, "navs", KATTR__MAX);
991 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
992 KATTR_HREF, href_diff, KATTR__MAX);
995 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
998 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1002 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
1003 if (kerr != KCGI_OK)
1006 /* build tree nav */
1007 if (asprintf(&href_tree, "?path=%s&action=tree&commit=%s",
1008 gw_trans->repo_name, n_header->commit_id) == -1) {
1009 error = got_error_from_errno("asprintf");
1012 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1013 KATTR_HREF, href_tree, KATTR__MAX);
1014 if (kerr != KCGI_OK)
1016 khtml_puts(gw_trans->gw_html_req, "tree");
1017 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1018 if (kerr != KCGI_OK)
1020 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1021 if (kerr != KCGI_OK)
1025 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1026 KATTR_ID, "dotted_line", KATTR__MAX);
1027 if (kerr != KCGI_OK)
1029 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1030 if (kerr != KCGI_OK)
1043 got_ref_list_free(&header->refs);
1044 gw_free_headers(header);
1045 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
1046 gw_free_headers(n_header);
1051 if (error == NULL && kerr != KCGI_OK)
1052 error = gw_kcgi_error(kerr != KCGI_OK);
1056 static const struct got_error *
1057 gw_summary(struct gw_trans *gw_trans)
1059 const struct got_error *error = NULL;
1060 char *age = NULL, *tags = NULL, *heads = NULL;
1061 enum kcgi_err kerr = KCGI_OK;
1063 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1064 return got_error_from_errno("pledge");
1066 /* unveil is applied with gw_briefs below */
1068 /* summary wrapper */
1069 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1070 "summary_wrapper", KATTR__MAX);
1071 if (kerr != KCGI_OK)
1072 return gw_kcgi_error(kerr != KCGI_OK);
1075 if (gw_trans->gw_conf->got_show_repo_description &&
1076 gw_trans->gw_dir->description != NULL &&
1077 (strcmp(gw_trans->gw_dir->description, "") != 0)) {
1078 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1079 KATTR_ID, "description_title", KATTR__MAX);
1080 if (kerr != KCGI_OK)
1082 kerr = khtml_puts(gw_trans->gw_html_req, "Description: ");
1083 if (kerr != KCGI_OK)
1085 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1086 if (kerr != KCGI_OK)
1088 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1089 KATTR_ID, "description", KATTR__MAX);
1090 if (kerr != KCGI_OK)
1092 kerr = khtml_puts(gw_trans->gw_html_req,
1093 gw_trans->gw_dir->description);
1094 if (kerr != KCGI_OK)
1096 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1097 if (kerr != KCGI_OK)
1102 if (gw_trans->gw_conf->got_show_repo_owner &&
1103 gw_trans->gw_dir->owner != NULL) {
1104 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1105 KATTR_ID, "repo_owner_title", KATTR__MAX);
1106 if (kerr != KCGI_OK)
1108 kerr = khtml_puts(gw_trans->gw_html_req, "Owner: ");
1109 if (kerr != KCGI_OK)
1111 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1112 if (kerr != KCGI_OK)
1114 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1115 KATTR_ID, "repo_owner", KATTR__MAX);
1116 if (kerr != KCGI_OK)
1118 kerr = khtml_puts(gw_trans->gw_html_req,
1119 gw_trans->gw_dir->owner);
1120 if (kerr != KCGI_OK)
1122 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1123 if (kerr != KCGI_OK)
1128 if (gw_trans->gw_conf->got_show_repo_age) {
1129 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
1130 "refs/heads", TM_LONG);
1134 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1135 KATTR_ID, "last_change_title", KATTR__MAX);
1136 if (kerr != KCGI_OK)
1138 kerr = khtml_puts(gw_trans->gw_html_req,
1140 if (kerr != KCGI_OK)
1142 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1143 if (kerr != KCGI_OK)
1145 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1146 KATTR_ID, "last_change", KATTR__MAX);
1147 if (kerr != KCGI_OK)
1149 kerr = khtml_puts(gw_trans->gw_html_req, age);
1150 if (kerr != KCGI_OK)
1152 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1153 if (kerr != KCGI_OK)
1159 if (gw_trans->gw_conf->got_show_repo_cloneurl &&
1160 gw_trans->gw_dir->url != NULL &&
1161 (strcmp(gw_trans->gw_dir->url, "") != 0)) {
1162 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1163 KATTR_ID, "cloneurl_title", KATTR__MAX);
1164 if (kerr != KCGI_OK)
1166 kerr = khtml_puts(gw_trans->gw_html_req, "Clone URL: ");
1167 if (kerr != KCGI_OK)
1169 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1170 if (kerr != KCGI_OK)
1172 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1173 KATTR_ID, "cloneurl", KATTR__MAX);
1174 if (kerr != KCGI_OK)
1176 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->gw_dir->url);
1177 if (kerr != KCGI_OK)
1179 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1180 if (kerr != KCGI_OK)
1184 /* close summary wrapper */
1185 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1186 if (kerr != KCGI_OK)
1189 /* commit briefs header */
1190 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1191 "briefs_title_wrapper", KATTR__MAX);
1192 if (kerr != KCGI_OK)
1194 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1195 "briefs_title", KATTR__MAX);
1196 if (kerr != KCGI_OK)
1198 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Briefs");
1199 if (kerr != KCGI_OK)
1201 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1202 if (kerr != KCGI_OK)
1204 error = gw_briefs(gw_trans);
1209 error = gw_get_repo_tags(&tags, gw_trans, NULL, D_MAXSLCOMMDISP,
1214 if (tags != NULL && strcmp(tags, "") != 0) {
1215 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1216 KATTR_ID, "summary_tags_title_wrapper", KATTR__MAX);
1217 if (kerr != KCGI_OK)
1219 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1220 KATTR_ID, "summary_tags_title", KATTR__MAX);
1221 if (kerr != KCGI_OK)
1223 kerr = khtml_puts(gw_trans->gw_html_req, "Tags");
1224 if (kerr != KCGI_OK)
1226 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1227 if (kerr != KCGI_OK)
1229 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1230 KATTR_ID, "summary_tags_content", KATTR__MAX);
1231 if (kerr != KCGI_OK)
1233 kerr = khttp_puts(gw_trans->gw_req, tags);
1234 if (kerr != KCGI_OK)
1236 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1237 if (kerr != KCGI_OK)
1242 error = gw_get_repo_heads(&heads, gw_trans);
1245 if (heads != NULL && strcmp(heads, "") != 0) {
1246 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1247 KATTR_ID, "summary_heads_title_wrapper", KATTR__MAX);
1248 if (kerr != KCGI_OK)
1250 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1251 KATTR_ID, "summary_heads_title", KATTR__MAX);
1252 if (kerr != KCGI_OK)
1254 kerr = khtml_puts(gw_trans->gw_html_req, "Heads");
1255 if (kerr != KCGI_OK)
1257 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1258 if (kerr != KCGI_OK)
1260 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1261 KATTR_ID, "summary_heads_content", KATTR__MAX);
1262 if (kerr != KCGI_OK)
1264 kerr = khttp_puts(gw_trans->gw_req, heads);
1265 if (kerr != KCGI_OK)
1267 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1268 if (kerr != KCGI_OK)
1275 if (error == NULL && kerr != KCGI_OK)
1276 error = gw_kcgi_error(kerr != KCGI_OK);
1280 static const struct got_error *
1281 gw_tree(struct gw_trans *gw_trans)
1283 const struct got_error *error = NULL;
1284 struct gw_header *header = NULL;
1285 char *tree = NULL, *tree_html = NULL, *tree_html_disp = NULL;
1286 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
1289 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1290 return got_error_from_errno("pledge");
1292 if ((header = gw_init_header()) == NULL)
1293 return got_error_from_errno("malloc");
1295 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1299 error = gw_get_header(gw_trans, header, 1);
1303 error = gw_get_repo_tree(&tree_html, gw_trans);
1307 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
1310 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
1311 error = got_error_from_errno("asprintf");
1314 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1317 if (asprintf(&tree_html_disp, tree_header, age_html,
1318 gw_gen_commit_msg_header_old(escaped_commit_msg),
1319 tree_html ? tree_html : "") == -1) {
1320 error = got_error_from_errno("asprintf");
1324 if (asprintf(&tree, tree_wrapper, tree_html_disp) == -1) {
1325 error = got_error_from_errno("asprintf");
1329 kerr = khttp_puts(gw_trans->gw_req, tree);
1330 if (kerr != KCGI_OK)
1331 error = gw_kcgi_error(kerr != KCGI_OK);
1333 got_ref_list_free(&header->refs);
1334 gw_free_headers(header);
1335 free(tree_html_disp);
1340 free(escaped_commit_msg);
1344 static const struct got_error *
1345 gw_tag(struct gw_trans *gw_trans)
1347 const struct got_error *error = NULL;
1348 struct gw_header *header = NULL;
1349 char *tag = NULL, *tag_html = NULL, *tag_html_disp = NULL;
1350 char *escaped_commit_msg = NULL;
1353 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1354 return got_error_from_errno("pledge");
1356 if ((header = gw_init_header()) == NULL)
1357 return got_error_from_errno("malloc");
1359 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1363 error = gw_get_header(gw_trans, header, 1);
1367 error = gw_get_repo_tags(&tag_html, gw_trans, header, 1, TAGFULL);
1371 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1374 if (asprintf(&tag_html_disp, tag_header,
1375 gw_gen_commit_header_old(header->commit_id, header->refs_str),
1376 gw_gen_commit_msg_header_old(escaped_commit_msg),
1377 tag_html ? tag_html : "") == -1) {
1378 error = got_error_from_errno("asprintf");
1382 if (asprintf(&tag, tag_wrapper, tag_html_disp) == -1) {
1383 error = got_error_from_errno("asprintf");
1387 kerr = khttp_puts(gw_trans->gw_req, tag);
1388 if (kerr != KCGI_OK)
1389 error = gw_kcgi_error(kerr != KCGI_OK);
1391 got_ref_list_free(&header->refs);
1392 gw_free_headers(header);
1393 free(tag_html_disp);
1396 free(escaped_commit_msg);
1400 static const struct got_error *
1401 gw_load_got_path(struct gw_trans *gw_trans, struct gw_dir *gw_dir)
1403 const struct got_error *error = NULL;
1408 if (asprintf(&dir_test, "%s/%s/%s",
1409 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1410 GOTWEB_GIT_DIR) == -1)
1411 return got_error_from_errno("asprintf");
1413 dt = opendir(dir_test);
1417 gw_dir->path = strdup(dir_test);
1422 if (asprintf(&dir_test, "%s/%s/%s",
1423 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1424 GOTWEB_GOT_DIR) == -1)
1425 return got_error_from_errno("asprintf");
1427 dt = opendir(dir_test);
1432 error = got_error(GOT_ERR_NOT_GIT_REPO);
1436 if (asprintf(&dir_test, "%s/%s",
1437 gw_trans->gw_conf->got_repos_path, gw_dir->name) == -1)
1438 return got_error_from_errno("asprintf");
1440 gw_dir->path = strdup(dir_test);
1443 error = gw_get_repo_description(&gw_dir->description, gw_trans,
1447 error = gw_get_repo_owner(&gw_dir->owner, gw_trans, gw_dir->path);
1450 error = gw_get_repo_age(&gw_dir->age, gw_trans, gw_dir->path,
1451 "refs/heads", TM_DIFF);
1454 error = gw_get_clone_url(&gw_dir->url, gw_trans, gw_dir->path);
1462 static const struct got_error *
1463 gw_load_got_paths(struct gw_trans *gw_trans)
1465 const struct got_error *error = NULL;
1467 struct dirent **sd_dent;
1468 struct gw_dir *gw_dir;
1470 unsigned int d_cnt, d_i;
1472 d = opendir(gw_trans->gw_conf->got_repos_path);
1474 error = got_error_from_errno2("opendir",
1475 gw_trans->gw_conf->got_repos_path);
1479 d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
1482 error = got_error_from_errno2("scandir",
1483 gw_trans->gw_conf->got_repos_path);
1487 for (d_i = 0; d_i < d_cnt; d_i++) {
1488 if (gw_trans->gw_conf->got_max_repos > 0 &&
1489 (d_i - 2) == gw_trans->gw_conf->got_max_repos)
1490 break; /* account for parent and self */
1492 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1493 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1496 if ((gw_dir = gw_init_gw_dir(sd_dent[d_i]->d_name)) == NULL)
1497 return got_error_from_errno("gw_dir malloc");
1499 error = gw_load_got_path(gw_trans, gw_dir);
1500 if (error && error->code == GOT_ERR_NOT_GIT_REPO)
1505 if (lstat(gw_dir->path, &st) == 0 && S_ISDIR(st.st_mode) &&
1506 !got_path_dir_is_empty(gw_dir->path)) {
1507 TAILQ_INSERT_TAIL(&gw_trans->gw_dirs, gw_dir,
1509 gw_trans->repos_total++;
1517 static const struct got_error *
1518 gw_parse_querystring(struct gw_trans *gw_trans)
1520 const struct got_error *error = NULL;
1522 struct gw_query_action *action = NULL;
1525 if (gw_trans->gw_req->fieldnmap[0]) {
1526 error = got_error_from_errno("bad parse");
1528 } else if ((p = gw_trans->gw_req->fieldmap[KEY_PATH])) {
1529 /* define gw_trans->repo_path */
1530 if (asprintf(&gw_trans->repo_name, "%s", p->parsed.s) == -1)
1531 return got_error_from_errno("asprintf");
1533 if (asprintf(&gw_trans->repo_path, "%s/%s",
1534 gw_trans->gw_conf->got_repos_path, p->parsed.s) == -1)
1535 return got_error_from_errno("asprintf");
1537 /* get action and set function */
1538 if ((p = gw_trans->gw_req->fieldmap[KEY_ACTION]))
1539 for (i = 0; i < nitems(gw_query_funcs); i++) {
1540 action = &gw_query_funcs[i];
1541 if (action->func_name == NULL)
1544 if (strcmp(action->func_name,
1545 p->parsed.s) == 0) {
1546 gw_trans->action = i;
1547 if (asprintf(&gw_trans->action_name,
1548 "%s", action->func_name) == -1)
1550 got_error_from_errno(
1559 if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
1560 if (asprintf(&gw_trans->commit, "%s",
1562 return got_error_from_errno("asprintf");
1564 if ((p = gw_trans->gw_req->fieldmap[KEY_FILE]))
1565 if (asprintf(&gw_trans->repo_file, "%s",
1567 return got_error_from_errno("asprintf");
1569 if ((p = gw_trans->gw_req->fieldmap[KEY_FOLDER]))
1570 if (asprintf(&gw_trans->repo_folder, "%s",
1572 return got_error_from_errno("asprintf");
1574 if ((p = gw_trans->gw_req->fieldmap[KEY_HEADREF]))
1575 if (asprintf(&gw_trans->headref, "%s",
1577 return got_error_from_errno("asprintf");
1579 if (action == NULL) {
1580 error = got_error_from_errno("invalid action");
1583 if ((gw_trans->gw_dir =
1584 gw_init_gw_dir(gw_trans->repo_name)) == NULL)
1585 return got_error_from_errno("gw_dir malloc");
1587 error = gw_load_got_path(gw_trans, gw_trans->gw_dir);
1591 gw_trans->action = GW_INDEX;
1593 if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
1594 gw_trans->page = p->parsed.i;
1599 static struct gw_dir *
1600 gw_init_gw_dir(char *dir)
1602 struct gw_dir *gw_dir;
1604 if ((gw_dir = malloc(sizeof(*gw_dir))) == NULL)
1607 if (asprintf(&gw_dir->name, "%s", dir) == -1)
1613 static const struct got_error *
1614 gw_display_open(struct gw_trans *gw_trans, enum khttp code, enum kmime mime)
1618 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_ALLOW], "GET");
1619 if (kerr != KCGI_OK)
1620 return gw_kcgi_error(kerr != KCGI_OK);
1621 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_STATUS], "%s",
1623 if (kerr != KCGI_OK)
1624 return gw_kcgi_error(kerr != KCGI_OK);
1625 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_CONTENT_TYPE], "%s",
1627 if (kerr != KCGI_OK)
1628 return gw_kcgi_error(kerr != KCGI_OK);
1629 kerr = khttp_head(gw_trans->gw_req, "X-Content-Type-Options",
1631 if (kerr != KCGI_OK)
1632 return gw_kcgi_error(kerr != KCGI_OK);
1633 kerr = khttp_head(gw_trans->gw_req, "X-Frame-Options", "DENY");
1634 if (kerr != KCGI_OK)
1635 return gw_kcgi_error(kerr != KCGI_OK);
1636 kerr = khttp_head(gw_trans->gw_req, "X-XSS-Protection",
1638 if (kerr != KCGI_OK)
1639 return gw_kcgi_error(kerr != KCGI_OK);
1641 if (gw_trans->mime == KMIME_APP_OCTET_STREAM) {
1642 kerr = khttp_head(gw_trans->gw_req,
1643 kresps[KRESP_CONTENT_DISPOSITION],
1644 "attachment; filename=%s", gw_trans->repo_file);
1645 if (kerr != KCGI_OK)
1646 return gw_kcgi_error(kerr != KCGI_OK);
1649 kerr = khttp_body(gw_trans->gw_req);
1650 return gw_kcgi_error(kerr != KCGI_OK);
1653 static const struct got_error *
1654 gw_display_index(struct gw_trans *gw_trans)
1656 const struct got_error *error;
1659 error = gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
1663 kerr = khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0);
1664 if (kerr != KCGI_OK)
1665 return gw_kcgi_error(kerr != KCGI_OK);
1667 if (gw_trans->action != GW_BLOB) {
1668 kerr = khttp_template(gw_trans->gw_req, gw_trans->gw_tmpl,
1669 gw_query_funcs[gw_trans->action].template);
1670 if (kerr != KCGI_OK) {
1671 khtml_close(gw_trans->gw_html_req);
1672 return gw_kcgi_error(kerr != KCGI_OK);
1676 return gw_kcgi_error(khtml_close(gw_trans->gw_html_req));
1680 gw_display_error(struct gw_trans *gw_trans, const struct got_error *err)
1682 if (gw_display_open(gw_trans, KHTTP_200, gw_trans->mime) != NULL)
1685 if (khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0) != KCGI_OK)
1687 khtml_puts(gw_trans->gw_html_req, err->msg);
1688 khtml_close(gw_trans->gw_html_req);
1692 gw_template(size_t key, void *arg)
1694 const struct got_error *error = NULL;
1696 struct gw_trans *gw_trans = arg;
1697 char *gw_site_link, *img_src = NULL;
1701 kerr = khttp_puts(gw_trans->gw_req, head);
1702 if (kerr != KCGI_OK)
1706 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1707 KATTR_ID, "got_link", KATTR__MAX);
1708 if (kerr != KCGI_OK)
1710 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1711 KATTR_HREF, gw_trans->gw_conf->got_logo_url,
1712 KATTR_TARGET, "_sotd", KATTR__MAX);
1713 if (kerr != KCGI_OK)
1715 if (asprintf(&img_src, "/%s",
1716 gw_trans->gw_conf->got_logo) == -1)
1718 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_IMG,
1719 KATTR_SRC, img_src, KATTR__MAX);
1720 if (kerr != KCGI_OK) {
1724 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1725 if (kerr != KCGI_OK) {
1730 case (TEMPL_SITEPATH):
1731 gw_site_link = gw_get_site_link(gw_trans);
1732 if (gw_site_link != NULL) {
1733 kerr = khttp_puts(gw_trans->gw_req, gw_site_link);
1734 if (kerr != KCGI_OK) {
1742 if (gw_trans->gw_conf->got_site_name != NULL) {
1743 kerr = khtml_puts(gw_trans->gw_html_req,
1744 gw_trans->gw_conf->got_site_name);
1745 if (kerr != KCGI_OK)
1749 case (TEMPL_SEARCH):
1750 kerr = khttp_puts(gw_trans->gw_req, search);
1751 if (kerr != KCGI_OK)
1754 case(TEMPL_SITEOWNER):
1755 if (gw_trans->gw_conf->got_site_owner != NULL &&
1756 gw_trans->gw_conf->got_show_site_owner) {
1757 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1758 KATTR_ID, "site_owner_wrapper", KATTR__MAX);
1759 if (kerr != KCGI_OK)
1761 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1762 KATTR_ID, "site_owner", KATTR__MAX);
1763 if (kerr != KCGI_OK)
1765 kerr = khtml_puts(gw_trans->gw_html_req,
1766 gw_trans->gw_conf->got_site_owner);
1767 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1768 if (kerr != KCGI_OK)
1772 case(TEMPL_CONTENT):
1773 error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
1775 kerr = khttp_puts(gw_trans->gw_req, error->msg);
1776 if (kerr != KCGI_OK)
1787 gw_gen_commit_header_old(char *str1, char *str2)
1789 char *return_html = NULL, *ref_str = NULL;
1791 if (strcmp(str2, "") != 0) {
1792 if (asprintf(&ref_str, "(%s)", str2) == -1) {
1793 return_html = strdup("");
1797 ref_str = strdup("");
1799 if (asprintf(&return_html, header_commit_html, str1, ref_str) == -1)
1800 return_html = strdup("");
1806 static const struct got_error *
1807 gw_gen_commit_header(struct gw_trans *gw_trans, char *str1, char *str2)
1809 const struct got_error *error = NULL;
1810 char *ref_str = NULL;
1811 enum kcgi_err kerr = KCGI_OK;
1813 if (strcmp(str2, "") != 0) {
1814 if (asprintf(&ref_str, "(%s)", str2) == -1) {
1815 error = got_error_from_errno("asprintf");
1819 ref_str = strdup("");
1821 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1822 KATTR_ID, "header_commit_title", KATTR__MAX);
1823 if (kerr != KCGI_OK)
1825 kerr = khtml_puts(gw_trans->gw_html_req, "Commit: ");
1826 if (kerr != KCGI_OK)
1828 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1829 if (kerr != KCGI_OK)
1831 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1832 KATTR_ID, "header_commit", KATTR__MAX);
1833 if (kerr != KCGI_OK)
1835 kerr = khtml_puts(gw_trans->gw_html_req, str1);
1836 if (kerr != KCGI_OK)
1838 kerr = khtml_puts(gw_trans->gw_html_req, " ");
1839 if (kerr != KCGI_OK)
1841 kerr = khtml_puts(gw_trans->gw_html_req, ref_str);
1842 if (kerr != KCGI_OK)
1844 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1845 if (kerr != KCGI_OK)
1848 if (error == NULL && kerr != KCGI_OK)
1849 error = gw_kcgi_error(kerr != KCGI_OK);
1853 static const struct got_error *
1854 gw_gen_diff_header(struct gw_trans *gw_trans, char *str1, char *str2)
1856 const struct got_error *error = NULL;
1857 enum kcgi_err kerr = KCGI_OK;
1859 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1860 KATTR_ID, "header_diff_title", KATTR__MAX);
1861 if (kerr != KCGI_OK)
1863 kerr = khtml_puts(gw_trans->gw_html_req, "Diff: ");
1864 if (kerr != KCGI_OK)
1866 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1867 if (kerr != KCGI_OK)
1869 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1870 KATTR_ID, "header_diff", KATTR__MAX);
1871 if (kerr != KCGI_OK)
1873 kerr = khtml_puts(gw_trans->gw_html_req, str1);
1874 if (kerr != KCGI_OK)
1876 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_BR, KATTR__MAX);
1877 if (kerr != KCGI_OK)
1879 kerr = khtml_puts(gw_trans->gw_html_req, str2);
1880 if (kerr != KCGI_OK)
1882 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1883 if (kerr != KCGI_OK)
1886 if (error == NULL && kerr != KCGI_OK)
1887 error = gw_kcgi_error(kerr != KCGI_OK);
1891 static const struct got_error *
1892 gw_gen_age_header(struct gw_trans *gw_trans, const char *str)
1894 const struct got_error *error = NULL;
1895 enum kcgi_err kerr = KCGI_OK;
1897 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1898 KATTR_ID, "header_age_title", KATTR__MAX);
1899 if (kerr != KCGI_OK)
1901 kerr = khtml_puts(gw_trans->gw_html_req, "Date: ");
1902 if (kerr != KCGI_OK)
1904 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1905 if (kerr != KCGI_OK)
1907 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1908 KATTR_ID, "header_age", KATTR__MAX);
1909 if (kerr != KCGI_OK)
1911 kerr = khtml_puts(gw_trans->gw_html_req, str);
1912 if (kerr != KCGI_OK)
1914 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1915 if (kerr != KCGI_OK)
1918 if (error == NULL && kerr != KCGI_OK)
1919 error = gw_kcgi_error(kerr != KCGI_OK);
1923 static const struct got_error *
1924 gw_gen_author_header(struct gw_trans *gw_trans, const char *str)
1926 const struct got_error *error = NULL;
1929 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1930 KATTR_ID, "header_author_title", KATTR__MAX);
1931 if (kerr != KCGI_OK)
1933 kerr = khtml_puts(gw_trans->gw_html_req, "Author: ");
1934 if (kerr != KCGI_OK)
1936 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1937 if (kerr != KCGI_OK)
1939 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1940 KATTR_ID, "header_author", KATTR__MAX);
1941 if (kerr != KCGI_OK)
1943 kerr = khtml_puts(gw_trans->gw_html_req, str);
1944 if (kerr != KCGI_OK)
1946 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1947 if (kerr != KCGI_OK)
1950 if (error == NULL && kerr != KCGI_OK)
1951 error = gw_kcgi_error(kerr != KCGI_OK);
1955 static const struct got_error *
1956 gw_gen_committer_header(struct gw_trans *gw_trans, const char *str)
1958 const struct got_error *error = NULL;
1961 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1962 KATTR_ID, "header_committer_title", KATTR__MAX);
1963 if (kerr != KCGI_OK)
1965 kerr = khtml_puts(gw_trans->gw_html_req, "Committer: ");
1966 if (kerr != KCGI_OK)
1968 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1969 if (kerr != KCGI_OK)
1971 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1972 KATTR_ID, "header_committer", KATTR__MAX);
1973 if (kerr != KCGI_OK)
1975 kerr = khtml_puts(gw_trans->gw_html_req, str);
1976 if (kerr != KCGI_OK)
1978 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1979 if (kerr != KCGI_OK)
1982 if (error == NULL && kerr != KCGI_OK)
1983 error = gw_kcgi_error(kerr != KCGI_OK);
1987 static const struct got_error *
1988 gw_gen_commit_msg_header(struct gw_trans *gw_trans, char *str)
1990 const struct got_error *error = NULL;
1993 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1994 KATTR_ID, "header_commit_msg_title", KATTR__MAX);
1995 if (kerr != KCGI_OK)
1997 kerr = khtml_puts(gw_trans->gw_html_req, "Message: ");
1998 if (kerr != KCGI_OK)
2000 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2001 if (kerr != KCGI_OK)
2003 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2004 KATTR_ID, "header_commit_msg", KATTR__MAX);
2005 if (kerr != KCGI_OK)
2007 kerr = khttp_puts(gw_trans->gw_req, str);
2008 if (kerr != KCGI_OK)
2010 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2011 if (kerr != KCGI_OK)
2014 if (error == NULL && kerr != KCGI_OK)
2015 error = gw_kcgi_error(kerr != KCGI_OK);
2019 /* XXX: slated for deletion */
2021 gw_gen_commit_msg_header_old(char *str)
2023 char *return_html = NULL;
2025 if (asprintf(&return_html, header_commit_msg_html, str) == -1)
2026 return_html = strdup("");
2031 static const struct got_error *
2032 gw_gen_tree_header(struct gw_trans *gw_trans, char *str)
2034 const struct got_error *error = NULL;
2037 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2038 KATTR_ID, "header_tree_title", KATTR__MAX);
2039 if (kerr != KCGI_OK)
2041 kerr = khtml_puts(gw_trans->gw_html_req, "Tree: ");
2042 if (kerr != KCGI_OK)
2044 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2045 if (kerr != KCGI_OK)
2047 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2048 KATTR_ID, "header_tree", KATTR__MAX);
2049 if (kerr != KCGI_OK)
2051 kerr = khtml_puts(gw_trans->gw_html_req, str);
2052 if (kerr != KCGI_OK)
2054 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2055 if (kerr != KCGI_OK)
2058 if (error == NULL && kerr != KCGI_OK)
2059 error = gw_kcgi_error(kerr != KCGI_OK);
2063 static const struct got_error *
2064 gw_get_repo_description(char **description, struct gw_trans *gw_trans,
2067 const struct got_error *error = NULL;
2069 char *d_file = NULL;
2073 *description = NULL;
2074 if (gw_trans->gw_conf->got_show_repo_description == 0)
2075 return gw_empty_string(description);
2077 if (asprintf(&d_file, "%s/description", dir) == -1)
2078 return got_error_from_errno("asprintf");
2080 f = fopen(d_file, "r");
2082 if (errno == ENOENT || errno == EACCES)
2083 return gw_empty_string(description);
2084 error = got_error_from_errno2("fopen", d_file);
2088 if (fseek(f, 0, SEEK_END) == -1) {
2089 error = got_ferror(f, GOT_ERR_IO);
2094 error = got_ferror(f, GOT_ERR_IO);
2097 if (fseek(f, 0, SEEK_SET) == -1) {
2098 error = got_ferror(f, GOT_ERR_IO);
2101 *description = calloc(len + 1, sizeof(**description));
2102 if (*description == NULL) {
2103 error = got_error_from_errno("calloc");
2107 n = fread(*description, 1, len, f);
2108 if (n == 0 && ferror(f))
2109 error = got_ferror(f, GOT_ERR_IO);
2111 if (f != NULL && fclose(f) == -1 && error == NULL)
2112 error = got_error_from_errno("fclose");
2117 static const struct got_error *
2118 gw_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2122 char *years = "years ago", *months = "months ago";
2123 char *weeks = "weeks ago", *days = "days ago", *hours = "hours ago";
2124 char *minutes = "minutes ago", *seconds = "seconds ago";
2125 char *now = "right now";
2133 diff_time = time(NULL) - committer_time;
2134 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2135 if (asprintf(repo_age, "%lld %s",
2136 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2137 return got_error_from_errno("asprintf");
2138 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2139 if (asprintf(repo_age, "%lld %s",
2140 (diff_time / 60 / 60 / 24 / (365 / 12)),
2142 return got_error_from_errno("asprintf");
2143 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2144 if (asprintf(repo_age, "%lld %s",
2145 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2146 return got_error_from_errno("asprintf");
2147 } else if (diff_time > 60 * 60 * 24 * 2) {
2148 if (asprintf(repo_age, "%lld %s",
2149 (diff_time / 60 / 60 / 24), days) == -1)
2150 return got_error_from_errno("asprintf");
2151 } else if (diff_time > 60 * 60 * 2) {
2152 if (asprintf(repo_age, "%lld %s",
2153 (diff_time / 60 / 60), hours) == -1)
2154 return got_error_from_errno("asprintf");
2155 } else if (diff_time > 60 * 2) {
2156 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2158 return got_error_from_errno("asprintf");
2159 } else if (diff_time > 2) {
2160 if (asprintf(repo_age, "%lld %s", diff_time,
2162 return got_error_from_errno("asprintf");
2164 if (asprintf(repo_age, "%s", now) == -1)
2165 return got_error_from_errno("asprintf");
2169 if (gmtime_r(&committer_time, &tm) == NULL)
2170 return got_error_from_errno("gmtime_r");
2172 s = asctime_r(&tm, datebuf);
2174 return got_error_from_errno("asctime_r");
2176 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2177 return got_error_from_errno("asprintf");
2183 static const struct got_error *
2184 gw_get_repo_age(char **repo_age, struct gw_trans *gw_trans, char *dir,
2185 char *repo_ref, int ref_tm)
2187 const struct got_error *error = NULL;
2188 struct got_object_id *id = NULL;
2189 struct got_repository *repo = NULL;
2190 struct got_commit_object *commit = NULL;
2191 struct got_reflist_head refs;
2192 struct got_reflist_entry *re;
2193 struct got_reference *head_ref;
2195 time_t committer_time = 0, cmp_time = 0;
2196 const char *refname;
2199 SIMPLEQ_INIT(&refs);
2201 if (repo_ref == NULL)
2204 if (strncmp(repo_ref, "refs/heads/", 11) == 0)
2207 if (gw_trans->gw_conf->got_show_repo_age == 0)
2210 error = got_repo_open(&repo, dir, NULL);
2215 error = got_ref_list(&refs, repo, "refs/heads",
2216 got_ref_cmp_by_name, NULL);
2218 error = got_ref_list(&refs, repo, repo_ref,
2219 got_ref_cmp_by_name, NULL);
2223 SIMPLEQ_FOREACH(re, &refs, entry) {
2225 refname = strdup(repo_ref);
2227 refname = got_ref_get_name(re->ref);
2228 error = got_ref_open(&head_ref, repo, refname, 0);
2232 error = got_ref_resolve(&id, repo, head_ref);
2233 got_ref_close(head_ref);
2237 error = got_object_open_as_commit(&commit, repo, id);
2242 got_object_commit_get_committer_time(commit);
2244 if (cmp_time < committer_time)
2245 cmp_time = committer_time;
2248 if (cmp_time != 0) {
2249 committer_time = cmp_time;
2250 error = gw_get_time_str(repo_age, committer_time, ref_tm);
2253 got_ref_list_free(&refs);
2258 static const struct got_error *
2259 gw_get_diff(struct gw_trans *gw_trans, struct gw_header *header)
2261 const struct got_error *error;
2263 struct got_object_id *id1 = NULL, *id2 = NULL;
2264 char *label1 = NULL, *label2 = NULL, *line = NULL;
2266 size_t linesize = 0;
2268 enum kcgi_err kerr = KCGI_OK;
2274 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2278 if (strncmp(header->parent_id, "/dev/null", 9) != 0) {
2279 error = got_repo_match_object_id(&id1, &label1,
2280 header->parent_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2285 error = got_repo_match_object_id(&id2, &label2,
2286 header->commit_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2290 error = got_object_get_type(&obj_type, header->repo, id2);
2294 case GOT_OBJ_TYPE_BLOB:
2295 error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, 3, 0,
2298 case GOT_OBJ_TYPE_TREE:
2299 error = got_diff_objects_as_trees(id1, id2, "", "", 3, 0,
2302 case GOT_OBJ_TYPE_COMMIT:
2303 error = got_diff_objects_as_commits(id1, id2, 3, 0,
2307 error = got_error(GOT_ERR_OBJ_TYPE);
2312 if (fseek(f, 0, SEEK_SET) == -1) {
2313 error = got_ferror(f, GOT_ERR_IO);
2317 while ((linelen = getline(&line, &linesize, f)) != -1) {
2318 error = gw_colordiff_line(gw_trans, line);
2321 /* XXX: KHTML_PRETTY breaks this */
2322 kerr = khtml_puts(gw_trans->gw_html_req, line);
2323 if (kerr != KCGI_OK)
2325 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2326 if (kerr != KCGI_OK)
2329 if (linelen == -1 && ferror(f))
2330 error = got_error_from_errno("getline");
2332 if (f && fclose(f) == -1 && error == NULL)
2333 error = got_error_from_errno("fclose");
2340 if (error == NULL && kerr != KCGI_OK)
2341 error = gw_kcgi_error(kerr != KCGI_OK);
2345 static const struct got_error *
2346 gw_get_repo_owner(char **owner, struct gw_trans *gw_trans, char *dir)
2348 const struct got_error *error = NULL;
2349 struct got_repository *repo;
2350 const char *gitconfig_owner;
2354 if (gw_trans->gw_conf->got_show_repo_owner == 0)
2357 error = got_repo_open(&repo, dir, NULL);
2360 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
2361 if (gitconfig_owner) {
2362 *owner = strdup(gitconfig_owner);
2364 error = got_error_from_errno("strdup");
2366 got_repo_close(repo);
2370 static const struct got_error *
2371 gw_get_clone_url(char **url, struct gw_trans *gw_trans, char *dir)
2373 const struct got_error *error = NULL;
2375 char *d_file = NULL;
2381 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2382 return got_error_from_errno("asprintf");
2384 f = fopen(d_file, "r");
2386 if (errno != ENOENT && errno != EACCES)
2387 error = got_error_from_errno2("fopen", d_file);
2391 if (fseek(f, 0, SEEK_END) == -1) {
2392 error = got_ferror(f, GOT_ERR_IO);
2397 error = got_ferror(f, GOT_ERR_IO);
2400 if (fseek(f, 0, SEEK_SET) == -1) {
2401 error = got_ferror(f, GOT_ERR_IO);
2405 *url = calloc(len + 1, sizeof(**url));
2407 error = got_error_from_errno("calloc");
2411 n = fread(*url, 1, len, f);
2412 if (n == 0 && ferror(f))
2413 error = got_ferror(f, GOT_ERR_IO);
2415 if (f && fclose(f) == -1 && error == NULL)
2416 error = got_error_from_errno("fclose");
2421 static const struct got_error *
2422 gw_get_repo_tags(char **tag_html, struct gw_trans *gw_trans,
2423 struct gw_header *header, int limit, int tag_type)
2425 const struct got_error *error = NULL;
2426 struct got_repository *repo = NULL;
2427 struct got_reflist_head refs;
2428 struct got_reflist_entry *re;
2429 char *tag_row = NULL, *tags_navs_disp = NULL;
2430 char *age = NULL, *age_html = NULL, *newline;
2431 char *escaped_tagger = NULL, *escaped_tag_commit = NULL;
2432 char *id_str = NULL, *refstr = NULL;
2433 char *tag_commit0 = NULL;
2434 struct buf *diffbuf = NULL;
2436 struct got_tag_object *tag = NULL;
2440 SIMPLEQ_INIT(&refs);
2442 error = buf_alloc(&diffbuf, 0);
2446 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2450 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo);
2454 SIMPLEQ_FOREACH(re, &refs, entry) {
2455 const char *refname;
2457 const char *tag_commit;
2459 struct got_object_id *id;
2461 refname = got_ref_get_name(re->ref);
2462 if (strncmp(refname, "refs/tags/", 10) != 0)
2465 refstr = got_ref_to_str(re->ref);
2466 if (refstr == NULL) {
2467 error = got_error_from_errno("got_ref_to_str");
2471 error = got_ref_resolve(&id, repo, re->ref);
2474 error = got_object_open_as_tag(&tag, repo, id);
2479 tagger = got_object_tag_get_tagger(tag);
2480 tagger_time = got_object_tag_get_tagger_time(tag);
2482 error = got_object_id_str(&id_str,
2483 got_object_tag_get_object_id(tag));
2487 tag_commit0 = strdup(got_object_tag_get_message(tag));
2488 if (tag_commit0 == NULL) {
2489 error = got_error_from_errno("strdup");
2493 tag_commit = tag_commit0;
2494 while (*tag_commit == '\n')
2499 newline = strchr(tag_commit, '\n');
2503 error = gw_get_time_str(&age, tagger_time, TM_DIFF);
2507 if (asprintf(&tags_navs_disp, tags_navs,
2508 gw_trans->repo_name, id_str, gw_trans->repo_name,
2509 id_str, gw_trans->repo_name, id_str,
2510 gw_trans->repo_name, id_str) == -1) {
2511 error = got_error_from_errno("asprintf");
2515 if (asprintf(&tag_row, tags_row, age ? age : "",
2516 refname, tag_commit, tags_navs_disp) == -1) {
2517 error = got_error_from_errno("asprintf");
2521 free(tags_navs_disp);
2524 error = gw_get_time_str(&age, tagger_time, TM_LONG);
2527 error = gw_html_escape(&escaped_tagger, tagger);
2530 error = gw_html_escape(&escaped_tag_commit, tag_commit);
2533 if (asprintf(&tag_row, tag_info, age ? age : "",
2534 escaped_tagger, escaped_tag_commit) == -1) {
2535 error = got_error_from_errno("asprintf");
2543 error = buf_puts(&newsize, diffbuf, tag_row);
2547 if (limit && --limit == 0)
2550 got_object_tag_close(tag);
2560 free(escaped_tagger);
2561 escaped_tagger = NULL;
2562 free(escaped_tag_commit);
2563 escaped_tag_commit = NULL;
2570 if (buf_len(diffbuf) > 0) {
2571 error = buf_putc(diffbuf, '\0');
2572 *tag_html = strdup(buf_get(diffbuf));
2573 if (*tag_html == NULL)
2574 error = got_error_from_errno("strdup");
2578 got_object_tag_close(tag);
2583 free(escaped_tagger);
2584 free(escaped_tag_commit);
2588 got_ref_list_free(&refs);
2590 got_repo_close(repo);
2595 gw_free_headers(struct gw_header *header)
2599 if (header->commit != NULL)
2600 got_object_commit_close(header->commit);
2602 got_repo_close(header->repo);
2603 free(header->refs_str);
2604 free(header->commit_id);
2605 free(header->parent_id);
2606 free(header->tree_id);
2607 free(header->commit_msg);
2610 static struct gw_header *
2613 struct gw_header *header;
2615 header = malloc(sizeof(*header));
2619 header->repo = NULL;
2620 header->commit = NULL;
2622 header->path = NULL;
2623 SIMPLEQ_INIT(&header->refs);
2628 static const struct got_error *
2629 gw_get_commits(struct gw_trans * gw_trans, struct gw_header *header,
2632 const struct got_error *error = NULL;
2633 struct got_commit_graph *graph = NULL;
2635 error = got_commit_graph_open(&graph, header->path, 0);
2639 error = got_commit_graph_iter_start(graph, header->id, header->repo,
2645 error = got_commit_graph_iter_next(&header->id, graph,
2646 header->repo, NULL, NULL);
2648 if (error->code == GOT_ERR_ITER_COMPLETED)
2652 if (header->id == NULL)
2655 error = got_object_open_as_commit(&header->commit, header->repo,
2660 error = gw_get_commit(gw_trans, header);
2662 struct gw_header *n_header = NULL;
2663 if ((n_header = gw_init_header()) == NULL) {
2664 error = got_error_from_errno("malloc");
2668 n_header->refs_str = strdup(header->refs_str);
2669 n_header->commit_id = strdup(header->commit_id);
2670 n_header->parent_id = strdup(header->parent_id);
2671 n_header->tree_id = strdup(header->tree_id);
2672 n_header->author = strdup(header->author);
2673 n_header->committer = strdup(header->committer);
2674 n_header->commit_msg = strdup(header->commit_msg);
2675 n_header->committer_time = header->committer_time;
2676 TAILQ_INSERT_TAIL(&gw_trans->gw_headers, n_header,
2679 if (error || (limit && --limit == 0))
2684 got_commit_graph_close(graph);
2688 static const struct got_error *
2689 gw_get_commit(struct gw_trans *gw_trans, struct gw_header *header)
2691 const struct got_error *error = NULL;
2692 struct got_reflist_entry *re;
2693 struct got_object_id *id2 = NULL;
2694 struct got_object_qid *parent_id;
2695 char *refs_str = NULL, *commit_msg = NULL, *commit_msg0;
2698 SIMPLEQ_FOREACH(re, &header->refs, entry) {
2701 struct got_tag_object *tag = NULL;
2704 name = got_ref_get_name(re->ref);
2705 if (strcmp(name, GOT_REF_HEAD) == 0)
2707 if (strncmp(name, "refs/", 5) == 0)
2709 if (strncmp(name, "got/", 4) == 0)
2711 if (strncmp(name, "heads/", 6) == 0)
2713 if (strncmp(name, "remotes/", 8) == 0)
2715 if (strncmp(name, "tags/", 5) == 0) {
2716 error = got_object_open_as_tag(&tag, header->repo,
2719 if (error->code != GOT_ERR_OBJ_TYPE)
2722 * Ref points at something other
2729 cmp = got_object_id_cmp(tag ?
2730 got_object_tag_get_object_id(tag) : re->id, header->id);
2732 got_object_tag_close(tag);
2736 if (asprintf(&refs_str, "%s%s%s", s ? s : "",
2737 s ? ", " : "", name) == -1) {
2738 error = got_error_from_errno("asprintf");
2742 header->refs_str = strdup(refs_str);
2746 if (refs_str == NULL)
2747 header->refs_str = strdup("");
2750 error = got_object_id_str(&header->commit_id, header->id);
2754 error = got_object_id_str(&header->tree_id,
2755 got_object_commit_get_tree_id(header->commit));
2759 if (gw_trans->action == GW_DIFF) {
2760 parent_id = SIMPLEQ_FIRST(
2761 got_object_commit_get_parent_ids(header->commit));
2762 if (parent_id != NULL) {
2763 id2 = got_object_id_dup(parent_id->id);
2765 error = got_object_id_str(&header->parent_id, id2);
2770 header->parent_id = strdup("/dev/null");
2772 header->parent_id = strdup("");
2774 header->committer_time =
2775 got_object_commit_get_committer_time(header->commit);
2778 got_object_commit_get_author(header->commit);
2780 got_object_commit_get_committer(header->commit);
2784 /* XXX Doesn't the log message require escaping? */
2785 error = got_object_commit_get_logmsg(&commit_msg0, header->commit);
2789 commit_msg = commit_msg0;
2790 while (*commit_msg == '\n')
2793 header->commit_msg = strdup(commit_msg);
2798 static const struct got_error *
2799 gw_get_header(struct gw_trans *gw_trans, struct gw_header *header, int limit)
2801 const struct got_error *error = NULL;
2802 char *in_repo_path = NULL;
2804 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2808 if (gw_trans->commit == NULL) {
2809 struct got_reference *head_ref;
2810 error = got_ref_open(&head_ref, header->repo,
2811 gw_trans->headref, 0);
2815 error = got_ref_resolve(&header->id, header->repo, head_ref);
2816 got_ref_close(head_ref);
2820 error = got_object_open_as_commit(&header->commit,
2821 header->repo, header->id);
2823 struct got_reference *ref;
2824 error = got_ref_open(&ref, header->repo, gw_trans->commit, 0);
2825 if (error == NULL) {
2827 error = got_ref_resolve(&header->id, header->repo, ref);
2831 error = got_object_get_type(&obj_type, header->repo,
2835 if (obj_type == GOT_OBJ_TYPE_TAG) {
2836 struct got_tag_object *tag;
2837 error = got_object_open_as_tag(&tag,
2838 header->repo, header->id);
2841 if (got_object_tag_get_object_type(tag) !=
2842 GOT_OBJ_TYPE_COMMIT) {
2843 got_object_tag_close(tag);
2844 error = got_error(GOT_ERR_OBJ_TYPE);
2848 header->id = got_object_id_dup(
2849 got_object_tag_get_object_id(tag));
2850 if (header->id == NULL)
2851 error = got_error_from_errno(
2852 "got_object_id_dup");
2853 got_object_tag_close(tag);
2856 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
2857 error = got_error(GOT_ERR_OBJ_TYPE);
2860 error = got_object_open_as_commit(&header->commit,
2861 header->repo, header->id);
2865 if (header->commit == NULL) {
2866 error = got_repo_match_object_id_prefix(&header->id,
2867 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2872 error = got_repo_match_object_id_prefix(&header->id,
2873 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2877 error = got_repo_map_path(&in_repo_path, header->repo,
2878 gw_trans->repo_path, 1);
2883 header->path = strdup(in_repo_path);
2887 error = got_ref_list(&header->refs, header->repo, NULL,
2888 got_ref_cmp_by_name, NULL);
2892 error = gw_get_commits(gw_trans, header, limit);
2900 char datebuf[11]; /* YYYY-MM-DD + NUL */
2903 struct gw_blame_cb_args {
2904 struct blame_line *lines;
2908 off_t *line_offsets;
2910 struct got_repository *repo;
2911 struct gw_trans *gw_trans;
2912 struct buf *blamebuf;
2915 static const struct got_error *
2916 gw_blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
2918 const struct got_error *err = NULL;
2919 struct gw_blame_cb_args *a = arg;
2920 struct blame_line *bline;
2922 size_t linesize = 0, newsize;
2923 struct got_commit_object *commit = NULL;
2926 time_t committer_time;
2928 if (nlines != a->nlines ||
2929 (lineno != -1 && lineno < 1) || lineno > a->nlines)
2930 return got_error(GOT_ERR_RANGE);
2933 return NULL; /* no change in this commit */
2935 /* Annotate this line. */
2936 bline = &a->lines[lineno - 1];
2937 if (bline->annotated)
2939 err = got_object_id_str(&bline->id_str, id);
2943 err = got_object_open_as_commit(&commit, a->repo, id);
2947 bline->committer = strdup(got_object_commit_get_committer(commit));
2948 if (bline->committer == NULL) {
2949 err = got_error_from_errno("strdup");
2953 committer_time = got_object_commit_get_committer_time(commit);
2954 if (localtime_r(&committer_time, &tm) == NULL)
2955 return got_error_from_errno("localtime_r");
2956 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
2957 &tm) >= sizeof(bline->datebuf)) {
2958 err = got_error(GOT_ERR_NO_SPACE);
2961 bline->annotated = 1;
2963 /* Print lines annotated so far. */
2964 bline = &a->lines[a->lineno_cur - 1];
2965 if (!bline->annotated)
2968 offset = a->line_offsets[a->lineno_cur - 1];
2969 if (fseeko(a->f, offset, SEEK_SET) == -1) {
2970 err = got_error_from_errno("fseeko");
2974 while (bline->annotated) {
2975 char *smallerthan, *at, *nl, *committer, *blame_row = NULL,
2976 *line_escape = NULL;
2979 if (getline(&line, &linesize, a->f) == -1) {
2981 err = got_error_from_errno("getline");
2985 committer = bline->committer;
2986 smallerthan = strchr(committer, '<');
2987 if (smallerthan && smallerthan[1] != '\0')
2988 committer = smallerthan + 1;
2989 at = strchr(committer, '@');
2992 len = strlen(committer);
2994 committer[8] = '\0';
2996 nl = strchr(line, '\n');
3000 err = gw_html_escape(&line_escape, line);
3004 if (a->gw_trans->repo_folder == NULL)
3005 a->gw_trans->repo_folder = strdup("");
3006 if (a->gw_trans->repo_folder == NULL)
3008 asprintf(&blame_row, blame_line, a->nlines_prec, a->lineno_cur,
3009 a->gw_trans->repo_name, bline->id_str,
3010 a->gw_trans->repo_file, a->gw_trans->repo_folder,
3011 bline->id_str, bline->datebuf, committer, line_escape);
3013 err = buf_puts(&newsize, a->blamebuf, blame_row);
3017 bline = &a->lines[a->lineno_cur - 1];
3024 got_object_commit_close(commit);
3029 static const struct got_error *
3030 gw_get_file_blame_blob(char **blame_html, struct gw_trans *gw_trans)
3032 const struct got_error *error = NULL;
3033 struct got_repository *repo = NULL;
3034 struct got_object_id *obj_id = NULL;
3035 struct got_object_id *commit_id = NULL;
3036 struct got_blob_object *blob = NULL;
3037 char *path = NULL, *in_repo_path = NULL;
3038 struct gw_blame_cb_args bca;
3044 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3048 if (asprintf(&path, "%s%s%s",
3049 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3050 gw_trans->repo_folder ? "/" : "",
3051 gw_trans->repo_file) == -1) {
3052 error = got_error_from_errno("asprintf");
3056 error = got_repo_map_path(&in_repo_path, repo, path, 1);
3060 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
3061 GOT_OBJ_TYPE_COMMIT, 1, repo);
3065 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
3069 if (obj_id == NULL) {
3070 error = got_error(GOT_ERR_NO_OBJ);
3074 error = got_object_get_type(&obj_type, repo, obj_id);
3078 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3079 error = got_error(GOT_ERR_OBJ_TYPE);
3083 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
3087 error = buf_alloc(&bca.blamebuf, 0);
3091 bca.f = got_opentemp();
3092 if (bca.f == NULL) {
3093 error = got_error_from_errno("got_opentemp");
3096 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
3097 &bca.line_offsets, bca.f, blob);
3098 if (error || bca.nlines == 0)
3101 /* Don't include \n at EOF in the blame line count. */
3102 if (bca.line_offsets[bca.nlines - 1] == filesize)
3105 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
3106 if (bca.lines == NULL) {
3107 error = got_error_from_errno("calloc");
3111 bca.nlines_prec = 0;
3118 bca.gw_trans = gw_trans;
3120 error = got_blame(in_repo_path, commit_id, repo, gw_blame_cb, &bca,
3124 if (buf_len(bca.blamebuf) > 0) {
3125 error = buf_putc(bca.blamebuf, '\0');
3128 *blame_html = strdup(buf_get(bca.blamebuf));
3129 if (*blame_html == NULL) {
3130 error = got_error_from_errno("strdup");
3135 free(bca.line_offsets);
3142 for (i = 0; i < bca.nlines; i++) {
3143 struct blame_line *bline = &bca.lines[i];
3144 free(bline->id_str);
3145 free(bline->committer);
3148 if (bca.f && fclose(bca.f) == EOF && error == NULL)
3149 error = got_error_from_errno("fclose");
3151 got_object_blob_close(blob);
3153 got_repo_close(repo);
3157 static const struct got_error *
3158 gw_get_file_read_blob(char **blobstr, size_t *filesize, struct gw_trans *gw_trans)
3160 const struct got_error *error = NULL;
3161 struct got_repository *repo = NULL;
3162 struct got_object_id *obj_id = NULL;
3163 struct got_object_id *commit_id = NULL;
3164 struct got_blob_object *blob = NULL;
3165 char *path = NULL, *in_repo_path = NULL;
3173 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3177 if (asprintf(&path, "%s%s%s",
3178 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3179 gw_trans->repo_folder ? "/" : "",
3180 gw_trans->repo_file) == -1) {
3181 error = got_error_from_errno("asprintf");
3185 error = got_repo_map_path(&in_repo_path, repo, path, 1);
3189 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
3190 GOT_OBJ_TYPE_COMMIT, 1, repo);
3194 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
3198 if (obj_id == NULL) {
3199 error = got_error(GOT_ERR_NO_OBJ);
3203 error = got_object_get_type(&obj_type, repo, obj_id);
3207 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3208 error = got_error(GOT_ERR_OBJ_TYPE);
3212 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
3218 error = got_error_from_errno("got_opentemp");
3221 error = got_object_blob_dump_to_file(filesize, NULL, NULL, f, blob);
3225 /* XXX This will fail on large files... */
3226 *blobstr = calloc(*filesize + 1, sizeof(**blobstr));
3227 if (*blobstr == NULL) {
3228 error = got_error_from_errno("calloc");
3232 n = fread(*blobstr, 1, *filesize, f);
3235 error = got_ferror(f, GOT_ERR_IO);
3244 got_object_blob_close(blob);
3246 got_repo_close(repo);
3247 if (f != NULL && fclose(f) == -1 && error == NULL)
3248 error = got_error_from_errno("fclose");
3257 static const struct got_error *
3258 gw_get_repo_tree(char **tree_html, struct gw_trans *gw_trans)
3260 const struct got_error *error = NULL;
3261 struct got_repository *repo = NULL;
3262 struct got_object_id *tree_id = NULL, *commit_id = NULL;
3263 struct got_tree_object *tree = NULL;
3264 struct buf *diffbuf = NULL;
3266 char *path = NULL, *in_repo_path = NULL, *tree_row = NULL;
3267 char *id_str = NULL;
3268 char *build_folder = NULL;
3269 char *url_html = NULL;
3270 const char *class = NULL;
3271 int nentries, i, class_flip = 0;
3275 error = buf_alloc(&diffbuf, 0);
3279 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3283 if (gw_trans->repo_folder != NULL)
3284 path = strdup(gw_trans->repo_folder);
3286 error = got_repo_map_path(&in_repo_path, repo,
3287 gw_trans->repo_path, 1);
3291 path = in_repo_path;
3294 if (gw_trans->commit == NULL) {
3295 struct got_reference *head_ref;
3296 error = got_ref_open(&head_ref, repo, gw_trans->headref, 0);
3299 error = got_ref_resolve(&commit_id, repo, head_ref);
3302 got_ref_close(head_ref);
3305 error = got_repo_match_object_id(&commit_id, NULL,
3306 gw_trans->commit, GOT_OBJ_TYPE_COMMIT, 1, repo);
3311 error = got_object_id_str(&gw_trans->commit, commit_id);
3315 error = got_object_id_by_path(&tree_id, repo, commit_id, path);
3319 error = got_object_open_as_tree(&tree, repo, tree_id);
3323 nentries = got_object_tree_get_nentries(tree);
3324 for (i = 0; i < nentries; i++) {
3325 struct got_tree_entry *te;
3326 const char *modestr = "";
3329 te = got_object_tree_get_entry(tree, i);
3331 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
3335 mode = got_tree_entry_get_mode(te);
3336 if (got_object_tree_entry_is_submodule(te))
3338 else if (S_ISLNK(mode))
3340 else if (S_ISDIR(mode))
3342 else if (mode & S_IXUSR)
3345 if (class_flip == 0) {
3346 class = "back_lightgray";
3349 class = "back_white";
3353 if (S_ISDIR(mode)) {
3354 if (asprintf(&build_folder, "%s/%s",
3355 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3356 got_tree_entry_get_name(te)) == -1) {
3357 error = got_error_from_errno(
3362 if (asprintf(&url_html, folder_html,
3363 gw_trans->repo_name, gw_trans->action_name,
3364 gw_trans->commit, build_folder,
3365 got_tree_entry_get_name(te), modestr) == -1) {
3366 error = got_error_from_errno("asprintf");
3369 if (asprintf(&tree_row, tree_line, class, url_html,
3371 error = got_error_from_errno("asprintf");
3375 if (asprintf(&url_html, file_html, gw_trans->repo_name,
3376 "blob", gw_trans->commit,
3377 got_tree_entry_get_name(te),
3378 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3379 got_tree_entry_get_name(te), modestr) == -1) {
3380 error = got_error_from_errno("asprintf");
3384 if (asprintf(&tree_row, tree_line_with_navs, class,
3385 url_html, class, gw_trans->repo_name, "blob",
3386 gw_trans->commit, got_tree_entry_get_name(te),
3387 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3388 "blob", gw_trans->repo_name,
3389 "blame", gw_trans->commit,
3390 got_tree_entry_get_name(te),
3391 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3393 error = got_error_from_errno("asprintf");
3398 error = buf_puts(&newsize, diffbuf, tree_row);
3409 build_folder = NULL;
3412 if (buf_len(diffbuf) > 0) {
3413 error = buf_putc(diffbuf, '\0');
3416 *tree_html = strdup(buf_get(diffbuf));
3417 if (*tree_html == NULL) {
3418 error = got_error_from_errno("strdup");
3424 got_object_tree_close(tree);
3426 got_repo_close(repo);
3438 static const struct got_error *
3439 gw_get_repo_heads(char **head_html, struct gw_trans *gw_trans)
3441 const struct got_error *error = NULL;
3442 struct got_repository *repo = NULL;
3443 struct got_reflist_head refs;
3444 struct got_reflist_entry *re;
3445 char *head_row = NULL, *head_navs_disp = NULL, *age = NULL;
3446 struct buf *diffbuf = NULL;
3451 SIMPLEQ_INIT(&refs);
3453 error = buf_alloc(&diffbuf, 0);
3457 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3461 error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
3466 SIMPLEQ_FOREACH(re, &refs, entry) {
3469 refname = strdup(got_ref_get_name(re->ref));
3470 if (refname == NULL) {
3471 error = got_error_from_errno("got_ref_to_str");
3475 if (strncmp(refname, "refs/heads/", 11) != 0) {
3480 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
3485 if (asprintf(&head_navs_disp, heads_navs, gw_trans->repo_name,
3486 refname, gw_trans->repo_name, refname,
3487 gw_trans->repo_name, refname, gw_trans->repo_name,
3489 error = got_error_from_errno("asprintf");
3493 if (strncmp(refname, "refs/heads/", 11) == 0)
3496 if (asprintf(&head_row, heads_row, age, refname,
3497 head_navs_disp) == -1) {
3498 error = got_error_from_errno("asprintf");
3502 error = buf_puts(&newsize, diffbuf, head_row);
3504 free(head_navs_disp);
3508 if (buf_len(diffbuf) > 0) {
3509 error = buf_putc(diffbuf, '\0');
3510 *head_html = strdup(buf_get(diffbuf));
3511 if (*head_html == NULL)
3512 error = got_error_from_errno("strdup");
3516 got_ref_list_free(&refs);
3518 got_repo_close(repo);
3523 gw_get_site_link(struct gw_trans *gw_trans)
3525 char *link = NULL, *repo = NULL, *action = NULL;
3527 if (gw_trans->repo_name != NULL &&
3528 asprintf(&repo, " / <a href='?path=%s&action=summary'>%s</a>",
3529 gw_trans->repo_name, gw_trans->repo_name) == -1)
3532 if (gw_trans->action_name != NULL &&
3533 asprintf(&action, " / %s", gw_trans->action_name) == -1) {
3538 if (asprintf(&link, site_link, GOTWEB,
3539 gw_trans->gw_conf->got_site_link,
3540 repo ? repo : "", action ? action : "") == -1) {
3551 static const struct got_error *
3552 gw_colordiff_line(struct gw_trans *gw_trans, char *buf)
3554 const struct got_error *error = NULL;
3556 enum kcgi_err kerr = KCGI_OK;
3558 if (strncmp(buf, "-", 1) == 0)
3559 color = "diff_minus";
3560 else if (strncmp(buf, "+", 1) == 0)
3561 color = "diff_plus";
3562 else if (strncmp(buf, "@@", 2) == 0)
3563 color = "diff_chunk_header";
3564 else if (strncmp(buf, "@@", 2) == 0)
3565 color = "diff_chunk_header";
3566 else if (strncmp(buf, "commit +", 8) == 0)
3567 color = "diff_meta";
3568 else if (strncmp(buf, "commit -", 8) == 0)
3569 color = "diff_meta";
3570 else if (strncmp(buf, "blob +", 6) == 0)
3571 color = "diff_meta";
3572 else if (strncmp(buf, "blob -", 6) == 0)
3573 color = "diff_meta";
3574 else if (strncmp(buf, "file +", 6) == 0)
3575 color = "diff_meta";
3576 else if (strncmp(buf, "file -", 6) == 0)
3577 color = "diff_meta";
3578 else if (strncmp(buf, "from:", 5) == 0)
3579 color = "diff_author";
3580 else if (strncmp(buf, "via:", 4) == 0)
3581 color = "diff_author";
3582 else if (strncmp(buf, "date:", 5) == 0)
3583 color = "diff_date";
3584 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
3585 "diff_line", KATTR_CLASS, color ? color : "", KATTR__MAX);
3586 if (error == NULL && kerr != KCGI_OK)
3587 error = gw_kcgi_error(kerr != KCGI_OK);
3592 * XXX This function should not exist.
3593 * We should let khtml_puts(3) handle HTML escaping.
3595 static const struct got_error *
3596 gw_html_escape(char **escaped_html, const char *orig_html)
3598 const struct got_error *error = NULL;
3599 struct escape_pair {
3610 size_t orig_len, len;
3613 orig_len = strlen(orig_html);
3615 for (i = 0; i < orig_len; i++) {
3616 for (j = 0; j < nitems(esc); j++) {
3617 if (orig_html[i] != esc[j].c)
3619 len += strlen(esc[j].s) - 1 /* escaped char */;
3623 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
3624 if (*escaped_html == NULL)
3625 return got_error_from_errno("calloc");
3628 for (i = 0; i < orig_len; i++) {
3630 for (j = 0; j < nitems(esc); j++) {
3631 if (orig_html[i] != esc[j].c)
3634 if (strlcat(*escaped_html, esc[j].s, len + 1)
3636 error = got_error(GOT_ERR_NO_SPACE);
3639 x += strlen(esc[j].s);
3644 (*escaped_html)[x] = orig_html[i];
3650 free(*escaped_html);
3651 *escaped_html = NULL;
3653 (*escaped_html)[x] = '\0';
3659 main(int argc, char *argv[])
3661 const struct got_error *error = NULL;
3662 struct gw_trans *gw_trans;
3663 struct gw_dir *dir = NULL, *tdir;
3664 const char *page = "index";
3668 if ((gw_trans = malloc(sizeof(struct gw_trans))) == NULL)
3671 if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
3674 if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
3677 if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
3680 kerr = khttp_parse(gw_trans->gw_req, gw_keys, KEY__ZMAX, &page, 1, 0);
3681 if (kerr != KCGI_OK) {
3682 error = gw_kcgi_error(kerr != KCGI_OK);
3686 if ((gw_trans->gw_conf =
3687 malloc(sizeof(struct gotweb_conf))) == NULL) {
3689 error = got_error_from_errno("malloc");
3693 TAILQ_INIT(&gw_trans->gw_dirs);
3694 TAILQ_INIT(&gw_trans->gw_headers);
3697 gw_trans->repos_total = 0;
3698 gw_trans->repo_path = NULL;
3699 gw_trans->commit = NULL;
3700 gw_trans->headref = strdup(GOT_REF_HEAD);
3701 gw_trans->mime = KMIME_TEXT_HTML;
3702 gw_trans->gw_tmpl->key = gw_templs;
3703 gw_trans->gw_tmpl->keysz = TEMPL__MAX;
3704 gw_trans->gw_tmpl->arg = gw_trans;
3705 gw_trans->gw_tmpl->cb = gw_template;
3706 error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
3710 error = gw_parse_querystring(gw_trans);
3714 if (gw_trans->action == GW_BLOB)
3715 error = gw_blob(gw_trans);
3717 error = gw_display_index(gw_trans);
3720 gw_trans->mime = KMIME_TEXT_PLAIN;
3721 gw_trans->action = GW_ERR;
3722 gw_display_error(gw_trans, error);
3725 free(gw_trans->gw_conf->got_repos_path);
3726 free(gw_trans->gw_conf->got_site_name);
3727 free(gw_trans->gw_conf->got_site_owner);
3728 free(gw_trans->gw_conf->got_site_link);
3729 free(gw_trans->gw_conf->got_logo);
3730 free(gw_trans->gw_conf->got_logo_url);
3731 free(gw_trans->gw_conf);
3732 free(gw_trans->commit);
3733 free(gw_trans->repo_path);
3734 free(gw_trans->repo_name);
3735 free(gw_trans->repo_file);
3736 free(gw_trans->action_name);
3737 free(gw_trans->headref);
3739 TAILQ_FOREACH_SAFE(dir, &gw_trans->gw_dirs, entry, tdir) {
3741 free(dir->description);
3750 khttp_free(gw_trans->gw_req);