Blob


1 /*
2 * Copyright (c) 2019, 2020 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2018, 2019 Stefan Sperling <stsp@openbsd.org>
4 *
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.
8 *
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.
16 */
18 #include <sys/queue.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
22 #include <ctype.h>
23 #include <dirent.h>
24 #include <err.h>
25 #include <errno.h>
26 #include <regex.h>
27 #include <stdarg.h>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
34 #include <got_object.h>
35 #include <got_reference.h>
36 #include <got_repository.h>
37 #include <got_path.h>
38 #include <got_cancel.h>
39 #include <got_worktree.h>
40 #include <got_diff.h>
41 #include <got_commit_graph.h>
42 #include <got_blame.h>
43 #include <got_privsep.h>
44 #include <got_opentemp.h>
46 #include <kcgi.h>
47 #include <kcgihtml.h>
49 #include "buf.h"
50 #include "gotweb.h"
51 #include "gotweb_ui.h"
53 #ifndef nitems
54 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
55 #endif
57 struct gw_trans {
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;
64 struct kreq *gw_req;
65 char *repo_name;
66 char *repo_path;
67 char *commit;
68 char *repo_file;
69 char *repo_folder;
70 char *action_name;
71 char *headref;
72 unsigned int action;
73 unsigned int page;
74 unsigned int repos_total;
75 enum kmime mime;
76 };
78 struct gw_header {
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;
84 char *path;
86 char *refs_str;
87 char *commit_id; /* id_str1 */
88 char *parent_id; /* id_str2 */
89 char *tree_id;
90 const char *author;
91 const char *committer;
92 char *commit_msg;
93 time_t committer_time;
94 };
96 struct gw_dir {
97 TAILQ_ENTRY(gw_dir) entry;
98 char *name;
99 char *owner;
100 char *description;
101 char *url;
102 char *age;
103 char *path;
104 };
106 enum gw_key {
107 KEY_ACTION,
108 KEY_COMMIT_ID,
109 KEY_FILE,
110 KEY_FOLDER,
111 KEY_HEADREF,
112 KEY_PAGE,
113 KEY_PATH,
114 KEY__ZMAX
115 };
117 enum gw_tmpl {
118 TEMPL_CONTENT,
119 TEMPL_HEAD,
120 TEMPL_HEADER,
121 TEMPL_SEARCH,
122 TEMPL_SITEPATH,
123 TEMPL_SITEOWNER,
124 TEMPL_TITLE,
125 TEMPL__MAX
126 };
128 enum gw_ref_tm {
129 TM_DIFF,
130 TM_LONG,
131 };
133 enum gw_tags {
134 TAGBRIEF,
135 TAGFULL,
136 };
138 static const char *const gw_templs[TEMPL__MAX] = {
139 "content",
140 "head",
141 "header",
142 "search",
143 "sitepath",
144 "siteowner",
145 "title",
146 };
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" },
156 };
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 *,
164 char *);
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 **,
169 struct gw_trans *);
170 static const struct got_error *gw_get_file_read_blob(char **, size_t *,
171 struct gw_trans *);
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 *,
174 struct gw_header *);
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 *,
179 char *);
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 *,
185 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 *,
188 char*);
189 static const struct got_error *gw_gen_author_header(struct gw_trans *,
190 const char *);
191 static const struct got_error *gw_gen_age_header(struct gw_trans *,
192 const char *);
193 static const struct got_error *gw_gen_committer_header(struct gw_trans *,
194 const char *);
195 static const struct got_error *gw_gen_commit_msg_header(struct gw_trans*,
196 char *);
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,
202 enum kmime);
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 *,
214 struct gw_header *);
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 *,
220 struct gw_dir *);
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 *);
237 char *template;
238 };
240 enum gw_query_actions {
241 GW_BLAME,
242 GW_BLOB,
243 GW_BRIEFS,
244 GW_COMMITS,
245 GW_DIFF,
246 GW_ERR,
247 GW_INDEX,
248 GW_SUMMARY,
249 GW_TAG,
250 GW_TREE,
251 };
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" },
264 };
266 static const struct got_error *
267 gw_kcgi_error(enum kcgi_err kerr)
269 if (kerr == KCGI_OK)
270 return NULL;
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) {
300 char *full_path;
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();
314 if (err != NULL)
315 return err;
317 if (unveil(NULL, NULL) != 0)
318 return got_error_from_errno("unveil");
320 return NULL;
323 static const struct got_error *
324 gw_empty_string(char **s)
326 *s = strdup("");
327 if (*s == NULL)
328 return got_error_from_errno("strdup");
329 return NULL;
332 static int
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;
345 enum kcgi_err kerr;
347 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
348 NULL) == -1)
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);
355 if (error)
356 goto done;
358 error = gw_get_header(gw_trans, header, 1);
359 if (error)
360 goto done;
362 error = gw_get_file_blame_blob(&blame_html, gw_trans);
363 if (error)
364 goto done;
366 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
367 if (error)
368 goto done;
369 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
370 error = got_error_from_errno("asprintf");
371 goto done;
374 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
375 if (error)
376 goto done;
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");
380 goto done;
383 if (asprintf(&blame, blame_wrapper, blame_html_disp) == -1) {
384 error = got_error_from_errno("asprintf");
385 goto done;
388 kerr = khttp_puts(gw_trans->gw_req, blame);
389 if (kerr != KCGI_OK)
390 error = gw_kcgi_error(kerr != KCGI_OK);
391 done:
392 got_ref_list_free(&header->refs);
393 gw_free_headers(header);
394 free(blame_html_disp);
395 free(blame_html);
396 free(blame);
397 free(escaped_commit_msg);
398 return error;
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;
407 size_t filesize = 0;
408 enum kcgi_err kerr;
410 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
411 NULL) == -1)
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);
418 if (error)
419 goto done;
421 error = gw_get_header(gw_trans, header, 1);
422 if (error)
423 goto done;
425 error = gw_get_file_read_blob(&content, &filesize, gw_trans);
426 if (error)
427 goto done;
429 if (isbinary(content, filesize))
430 gw_trans->mime = KMIME_APP_OCTET_STREAM;
431 else
432 gw_trans->mime = KMIME_TEXT_PLAIN;
434 error = gw_display_index(gw_trans);
435 if (error)
436 goto done;
438 kerr = khttp_write(gw_trans->gw_req, content, filesize);
439 if (kerr != KCGI_OK)
440 error = gw_kcgi_error(kerr != KCGI_OK);
441 done:
442 got_ref_list_free(&header->refs);
443 gw_free_headers(header);
444 free(content);
445 return error;
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",
457 NULL) == -1)
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);
464 if (error)
465 goto done;
467 error = gw_get_header(gw_trans, header, 1);
468 if (error)
469 goto done;
471 /* diff title */
472 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
473 "diff_title_wrapper", KATTR__MAX);
474 if (kerr != KCGI_OK)
475 goto done;
476 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
477 "diff_title", KATTR__MAX);
478 if (kerr != KCGI_OK)
479 goto done;
480 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Diff");
481 if (kerr != KCGI_OK)
482 goto done;
483 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
484 if (kerr != KCGI_OK)
485 goto done;
487 /* diff content */
488 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
489 "diff_content", KATTR__MAX);
490 if (kerr != KCGI_OK)
491 goto done;
493 /* diff header */
494 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
495 "diff_header_wrapper", KATTR__MAX);
496 if (kerr != KCGI_OK)
497 goto done;
498 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
499 "diff_header", KATTR__MAX);
500 if (kerr != KCGI_OK)
501 goto done;
502 error = gw_gen_diff_header(gw_trans, header->parent_id,
503 header->commit_id);
504 if (error)
505 goto done;
506 error = gw_gen_commit_header(gw_trans, header->commit_id,
507 header->refs_str);
508 if (error)
509 goto done;
510 error = gw_gen_tree_header(gw_trans, header->tree_id);
511 if (error)
512 goto done;
513 error = gw_gen_author_header(gw_trans, header->author);
514 if (error)
515 goto done;
516 error = gw_gen_committer_header(gw_trans, header->author);
517 if (error)
518 goto done;
519 error = gw_get_time_str(&age, header->committer_time,
520 TM_LONG);
521 if (error)
522 goto done;
523 error = gw_gen_age_header(gw_trans, age ?age : "");
524 if (error)
525 goto done;
526 /*
527 * XXX: keeping this for now, since kcgihtml does not convert
528 * \n into <br /> yet.
529 */
530 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
531 if (error)
532 goto done;
533 error = gw_gen_commit_msg_header(gw_trans, header->commit_msg);
534 if (error)
535 goto done;
536 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
537 if (kerr != KCGI_OK)
538 goto done;
539 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
540 "dotted_line", KATTR__MAX);
541 if (kerr != KCGI_OK)
542 goto done;
543 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
544 if (kerr != KCGI_OK)
545 goto done;
547 /* diff */
548 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
549 "diff", KATTR__MAX);
550 if (kerr != KCGI_OK)
551 goto done;
552 error = gw_get_diff(gw_trans, header);
553 if (error)
554 goto done;
556 /* diff content close */
557 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
558 if (kerr != KCGI_OK)
559 goto done;
560 done:
561 got_ref_list_free(&header->refs);
562 gw_free_headers(header);
563 free(age);
564 free(escaped_commit_msg);
565 if (error == NULL && kerr != KCGI_OK)
566 error = gw_kcgi_error(kerr != KCGI_OK);
567 return error;
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;
577 enum kcgi_err kerr;
579 if (pledge("stdio rpath proc exec sendfd unveil",
580 NULL) == -1) {
581 error = got_error_from_errno("pledge");
582 return error;
585 error = gw_apply_unveil(gw_trans->gw_conf->got_repos_path, NULL);
586 if (error)
587 return error;
589 error = gw_load_got_paths(gw_trans);
590 if (error)
591 return error;
593 kerr = khttp_puts(gw_trans->gw_req, index_projects_header);
594 if (kerr != KCGI_OK)
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);
602 if (kerr != KCGI_OK)
603 error = gw_kcgi_error(kerr != KCGI_OK);
604 free(html);
605 return error;
608 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry)
609 dir_c++;
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) {
614 prev_disp++;
615 continue;
618 prev_disp++;
620 if (error)
621 return error;
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 : "",
628 gw_dir->age,
629 navs) == -1)
630 return got_error_from_errno("asprintf");
632 kerr = khttp_puts(gw_trans->gw_req, html);
633 free(navs);
634 free(html);
635 if (kerr != KCGI_OK)
636 return gw_kcgi_error(kerr != KCGI_OK);
638 if (gw_trans->gw_conf->got_max_repos_display == 0)
639 continue;
641 if (next_disp == gw_trans->gw_conf->got_max_repos_display) {
642 kerr = khttp_puts(gw_trans->gw_req, np_wrapper_start);
643 if (kerr != KCGI_OK)
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);
650 if (kerr != KCGI_OK)
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);
661 free(prev);
662 if (kerr != KCGI_OK)
663 return gw_kcgi_error(kerr != KCGI_OK);
666 kerr = khttp_puts(gw_trans->gw_req, div_end);
667 if (kerr != KCGI_OK)
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);
677 free(next);
678 if (kerr != KCGI_OK)
679 return gw_kcgi_error(kerr != KCGI_OK);
680 kerr = khttp_puts(gw_trans->gw_req, div_end);
681 if (kerr != KCGI_OK)
682 error = gw_kcgi_error(kerr != KCGI_OK);
683 next_disp = 0;
684 break;
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);
692 if (kerr != KCGI_OK)
693 return gw_kcgi_error(kerr != KCGI_OK);
696 next_disp++;
698 return error;
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",
714 NULL) == -1) {
715 error = got_error_from_errno("pledge");
716 goto done;
719 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
720 if (error)
721 goto done;
723 error = gw_get_header(gw_trans, header,
724 gw_trans->gw_conf->got_max_commits_display);
725 if (error)
726 goto done;
728 /* commit briefs header */
729 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
730 "commits_title_wrapper", KATTR__MAX);
731 if (kerr != KCGI_OK)
732 goto done;
733 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
734 "commits_title", KATTR__MAX);
735 if (kerr != KCGI_OK)
736 goto done;
737 kerr = khtml_puts(gw_trans->gw_html_req, "Commits");
738 if (kerr != KCGI_OK)
739 goto done;
740 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
741 if (kerr != KCGI_OK)
742 goto done;
744 /* commit content */
745 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
746 "commits_content", KATTR__MAX);
747 if (kerr != KCGI_OK)
748 goto done;
749 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
750 /* commit line */
751 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
752 "commits_line_wrapper", KATTR__MAX);
753 if (kerr != KCGI_OK)
754 goto done;
755 error = gw_gen_commit_header(gw_trans, n_header->commit_id,
756 n_header->refs_str);
757 if (error)
758 goto done;
759 error = gw_gen_author_header(gw_trans, n_header->author);
760 if (error)
761 goto done;
762 error = gw_gen_committer_header(gw_trans, n_header->author);
763 if (error)
764 goto done;
765 error = gw_get_time_str(&age, n_header->committer_time,
766 TM_LONG);
767 if (error)
768 goto done;
769 error = gw_gen_age_header(gw_trans, age ?age : "");
770 if (error)
771 goto done;
772 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
773 if (kerr != KCGI_OK)
774 goto done;
776 /* dotted line */
777 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
778 "dotted_line", KATTR__MAX);
779 if (kerr != KCGI_OK)
780 goto done;
781 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
782 if (kerr != KCGI_OK)
783 goto done;
785 /* commit */
786 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
787 "commit", KATTR__MAX);
788 if (kerr != KCGI_OK)
789 goto done;
790 /*
791 * XXX: keeping this for now, since kcgihtml does not convert
792 * \n into <br /> yet.
793 */
794 error = gw_html_escape(&escaped_commit_msg,
795 n_header->commit_msg);
796 if (error)
797 goto done;
798 kerr = khttp_puts(gw_trans->gw_req, escaped_commit_msg);
799 if (kerr != KCGI_OK)
800 goto done;
801 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
802 if (kerr != KCGI_OK)
803 goto done;
805 /* navs */
807 /* XXX: create gen code for this */
808 /* build diff nav */
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");
812 goto done;
814 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
815 KATTR_ID, "navs_wrapper", KATTR__MAX);
816 if (kerr != KCGI_OK)
817 goto done;
818 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
819 KATTR_ID, "navs", KATTR__MAX);
820 if (kerr != KCGI_OK)
821 goto done;
822 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
823 KATTR_HREF, href_diff, KATTR__MAX);
824 if (kerr != KCGI_OK)
825 goto done;
826 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
827 if (kerr != KCGI_OK)
828 goto done;
829 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
830 if (kerr != KCGI_OK)
831 goto done;
833 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
834 if (kerr != KCGI_OK)
835 goto done;
837 /* XXX: create gen code for this */
838 /* build tree nav */
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");
842 goto done;
844 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
845 KATTR_HREF, href_tree, KATTR__MAX);
846 if (kerr != KCGI_OK)
847 goto done;
848 khtml_puts(gw_trans->gw_html_req, "tree");
849 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
850 if (kerr != KCGI_OK)
851 goto done;
852 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
853 if (kerr != KCGI_OK)
854 goto done;
856 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
857 "solid_line", KATTR__MAX);
858 if (kerr != KCGI_OK)
859 goto done;
860 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
861 if (kerr != KCGI_OK)
862 goto done;
864 free(age);
865 age = NULL;
866 free(escaped_commit_msg);
867 escaped_commit_msg = NULL;
869 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
870 done:
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);
875 free(age);
876 free(href_diff);
877 free(href_tree);
878 free(escaped_commit_msg);
879 if (error == NULL && kerr != KCGI_OK)
880 error = gw_kcgi_error(kerr != KCGI_OK);
881 return error;
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",
898 NULL) == -1) {
899 error = got_error_from_errno("pledge");
900 goto done;
903 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
904 if (error)
905 goto done;
907 if (gw_trans->action == GW_SUMMARY)
908 error = gw_get_header(gw_trans, header, D_MAXSLCOMMDISP);
909 else
910 error = gw_get_header(gw_trans, header,
911 gw_trans->gw_conf->got_max_commits_display);
912 if (error)
913 goto done;
915 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
916 error = gw_get_time_str(&age, n_header->committer_time,
917 TM_DIFF);
918 if (error)
919 goto done;
921 /* briefs wrapper */
922 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
923 KATTR_ID, "briefs_wrapper", KATTR__MAX);
924 if (kerr != KCGI_OK)
925 goto done;
927 /* briefs age */
928 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
929 KATTR_ID, "briefs_age", KATTR__MAX);
930 if (kerr != KCGI_OK)
931 goto done;
932 if (asprintf(&age_html, "%s", age ? age : "") == -1) {
933 error = got_error_from_errno("asprintf");
934 goto done;
936 kerr = khtml_puts(gw_trans->gw_html_req, age_html);
937 if (kerr != KCGI_OK)
938 goto done;
939 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
940 if (kerr != KCGI_OK)
941 goto done;
943 /* briefs author */
944 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
945 KATTR_ID, "briefs_author", KATTR__MAX);
946 if (kerr != KCGI_OK)
947 goto done;
948 smallerthan = strchr(n_header->author, '<');
949 if (smallerthan)
950 *smallerthan = '\0';
951 kerr = khtml_puts(gw_trans->gw_html_req, n_header->author);
952 if (kerr != KCGI_OK)
953 goto done;
954 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
955 if (kerr != KCGI_OK)
956 goto done;
958 /* briefs log */
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");
962 goto done;
964 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
965 KATTR_ID, "briefs_log", KATTR__MAX);
966 if (kerr != KCGI_OK)
967 goto done;
968 newline = strchr(n_header->commit_msg, '\n');
969 if (newline)
970 *newline = '\0';
971 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
972 KATTR_HREF, href_diff, KATTR__MAX);
973 if (kerr != KCGI_OK)
974 goto done;
975 kerr = khtml_puts(gw_trans->gw_html_req, n_header->commit_msg);
976 if (kerr != KCGI_OK)
977 goto done;
978 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
979 if (kerr != KCGI_OK)
980 goto done;
982 /* build diff nav */
983 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
984 KATTR_ID, "navs_wrapper", KATTR__MAX);
985 if (kerr != KCGI_OK)
986 goto done;
987 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
988 KATTR_ID, "navs", KATTR__MAX);
989 if (kerr != KCGI_OK)
990 goto done;
991 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
992 KATTR_HREF, href_diff, KATTR__MAX);
993 if (kerr != KCGI_OK)
994 goto done;
995 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
996 if (kerr != KCGI_OK)
997 goto done;
998 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
999 if (kerr != KCGI_OK)
1000 goto done;
1002 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
1003 if (kerr != KCGI_OK)
1004 goto done;
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");
1010 goto done;
1012 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1013 KATTR_HREF, href_tree, KATTR__MAX);
1014 if (kerr != KCGI_OK)
1015 goto done;
1016 khtml_puts(gw_trans->gw_html_req, "tree");
1017 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1018 if (kerr != KCGI_OK)
1019 goto done;
1020 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1021 if (kerr != KCGI_OK)
1022 goto done;
1024 /* dotted line */
1025 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1026 KATTR_ID, "dotted_line", KATTR__MAX);
1027 if (kerr != KCGI_OK)
1028 goto done;
1029 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1030 if (kerr != KCGI_OK)
1031 goto done;
1033 free(age);
1034 age = NULL;
1035 free(age_html);
1036 age_html = NULL;
1037 free(href_diff);
1038 href_diff = NULL;
1039 free(href_tree);
1040 href_tree = NULL;
1042 done:
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);
1047 free(age);
1048 free(age_html);
1049 free(href_diff);
1050 free(href_tree);
1051 if (error == NULL && kerr != KCGI_OK)
1052 error = gw_kcgi_error(kerr != KCGI_OK);
1053 return error;
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);
1074 /* description */
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)
1081 goto done;
1082 kerr = khtml_puts(gw_trans->gw_html_req, "Description: ");
1083 if (kerr != KCGI_OK)
1084 goto done;
1085 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1086 if (kerr != KCGI_OK)
1087 goto done;
1088 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1089 KATTR_ID, "description", KATTR__MAX);
1090 if (kerr != KCGI_OK)
1091 goto done;
1092 kerr = khtml_puts(gw_trans->gw_html_req,
1093 gw_trans->gw_dir->description);
1094 if (kerr != KCGI_OK)
1095 goto done;
1096 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1097 if (kerr != KCGI_OK)
1098 goto done;
1101 /* repo owner */
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)
1107 goto done;
1108 kerr = khtml_puts(gw_trans->gw_html_req, "Owner: ");
1109 if (kerr != KCGI_OK)
1110 goto done;
1111 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1112 if (kerr != KCGI_OK)
1113 goto done;
1114 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1115 KATTR_ID, "repo_owner", KATTR__MAX);
1116 if (kerr != KCGI_OK)
1117 goto done;
1118 kerr = khtml_puts(gw_trans->gw_html_req,
1119 gw_trans->gw_dir->owner);
1120 if (kerr != KCGI_OK)
1121 goto done;
1122 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1123 if (kerr != KCGI_OK)
1124 goto done;
1127 /* last change */
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);
1131 if (error)
1132 goto done;
1133 if (age != NULL) {
1134 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1135 KATTR_ID, "last_change_title", KATTR__MAX);
1136 if (kerr != KCGI_OK)
1137 goto done;
1138 kerr = khtml_puts(gw_trans->gw_html_req,
1139 "Last Change: ");
1140 if (kerr != KCGI_OK)
1141 goto done;
1142 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1143 if (kerr != KCGI_OK)
1144 goto done;
1145 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1146 KATTR_ID, "last_change", KATTR__MAX);
1147 if (kerr != KCGI_OK)
1148 goto done;
1149 kerr = khtml_puts(gw_trans->gw_html_req, age);
1150 if (kerr != KCGI_OK)
1151 goto done;
1152 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1153 if (kerr != KCGI_OK)
1154 goto done;
1158 /* cloneurl */
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)
1165 goto done;
1166 kerr = khtml_puts(gw_trans->gw_html_req, "Clone URL: ");
1167 if (kerr != KCGI_OK)
1168 goto done;
1169 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1170 if (kerr != KCGI_OK)
1171 goto done;
1172 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1173 KATTR_ID, "cloneurl", KATTR__MAX);
1174 if (kerr != KCGI_OK)
1175 goto done;
1176 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->gw_dir->url);
1177 if (kerr != KCGI_OK)
1178 goto done;
1179 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1180 if (kerr != KCGI_OK)
1181 goto done;
1184 /* close summary wrapper */
1185 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1186 if (kerr != KCGI_OK)
1187 goto done;
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)
1193 goto done;
1194 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1195 "briefs_title", KATTR__MAX);
1196 if (kerr != KCGI_OK)
1197 goto done;
1198 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Briefs");
1199 if (kerr != KCGI_OK)
1200 goto done;
1201 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1202 if (kerr != KCGI_OK)
1203 goto done;
1204 error = gw_briefs(gw_trans);
1205 if (error)
1206 goto done;
1208 /* tags */
1209 error = gw_get_repo_tags(&tags, gw_trans, NULL, D_MAXSLCOMMDISP,
1210 TAGBRIEF);
1211 if (error)
1212 goto done;
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)
1218 goto done;
1219 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1220 KATTR_ID, "summary_tags_title", KATTR__MAX);
1221 if (kerr != KCGI_OK)
1222 goto done;
1223 kerr = khtml_puts(gw_trans->gw_html_req, "Tags");
1224 if (kerr != KCGI_OK)
1225 goto done;
1226 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1227 if (kerr != KCGI_OK)
1228 goto done;
1229 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1230 KATTR_ID, "summary_tags_content", KATTR__MAX);
1231 if (kerr != KCGI_OK)
1232 goto done;
1233 kerr = khttp_puts(gw_trans->gw_req, tags);
1234 if (kerr != KCGI_OK)
1235 goto done;
1236 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1237 if (kerr != KCGI_OK)
1238 goto done;
1241 /* heads */
1242 error = gw_get_repo_heads(&heads, gw_trans);
1243 if (error)
1244 goto done;
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)
1249 goto done;
1250 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1251 KATTR_ID, "summary_heads_title", KATTR__MAX);
1252 if (kerr != KCGI_OK)
1253 goto done;
1254 kerr = khtml_puts(gw_trans->gw_html_req, "Heads");
1255 if (kerr != KCGI_OK)
1256 goto done;
1257 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1258 if (kerr != KCGI_OK)
1259 goto done;
1260 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1261 KATTR_ID, "summary_heads_content", KATTR__MAX);
1262 if (kerr != KCGI_OK)
1263 goto done;
1264 kerr = khttp_puts(gw_trans->gw_req, heads);
1265 if (kerr != KCGI_OK)
1266 goto done;
1267 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1268 if (kerr != KCGI_OK)
1269 goto done;
1271 done:
1272 free(age);
1273 free(tags);
1274 free(heads);
1275 if (error == NULL && kerr != KCGI_OK)
1276 error = gw_kcgi_error(kerr != KCGI_OK);
1277 return error;
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;
1287 enum kcgi_err kerr;
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);
1296 if (error)
1297 goto done;
1299 error = gw_get_header(gw_trans, header, 1);
1300 if (error)
1301 goto done;
1303 error = gw_get_repo_tree(&tree_html, gw_trans);
1304 if (error)
1305 goto done;
1307 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
1308 if (error)
1309 goto done;
1310 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
1311 error = got_error_from_errno("asprintf");
1312 goto done;
1314 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1315 if (error)
1316 goto done;
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");
1321 goto done;
1324 if (asprintf(&tree, tree_wrapper, tree_html_disp) == -1) {
1325 error = got_error_from_errno("asprintf");
1326 goto done;
1329 kerr = khttp_puts(gw_trans->gw_req, tree);
1330 if (kerr != KCGI_OK)
1331 error = gw_kcgi_error(kerr != KCGI_OK);
1332 done:
1333 got_ref_list_free(&header->refs);
1334 gw_free_headers(header);
1335 free(tree_html_disp);
1336 free(tree_html);
1337 free(tree);
1338 free(age);
1339 free(age_html);
1340 free(escaped_commit_msg);
1341 return error;
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;
1351 enum kcgi_err kerr;
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);
1360 if (error)
1361 goto done;
1363 error = gw_get_header(gw_trans, header, 1);
1364 if (error)
1365 goto done;
1367 error = gw_get_repo_tags(&tag_html, gw_trans, header, 1, TAGFULL);
1368 if (error)
1369 goto done;
1371 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1372 if (error)
1373 goto done;
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");
1379 goto done;
1382 if (asprintf(&tag, tag_wrapper, tag_html_disp) == -1) {
1383 error = got_error_from_errno("asprintf");
1384 goto done;
1387 kerr = khttp_puts(gw_trans->gw_req, tag);
1388 if (kerr != KCGI_OK)
1389 error = gw_kcgi_error(kerr != KCGI_OK);
1390 done:
1391 got_ref_list_free(&header->refs);
1392 gw_free_headers(header);
1393 free(tag_html_disp);
1394 free(tag_html);
1395 free(tag);
1396 free(escaped_commit_msg);
1397 return error;
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;
1404 DIR *dt;
1405 char *dir_test;
1406 int opened = 0;
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);
1414 if (dt == NULL) {
1415 free(dir_test);
1416 } else {
1417 gw_dir->path = strdup(dir_test);
1418 opened = 1;
1419 goto done;
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);
1428 if (dt == NULL)
1429 free(dir_test);
1430 else {
1431 opened = 1;
1432 error = got_error(GOT_ERR_NOT_GIT_REPO);
1433 goto errored;
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);
1442 done:
1443 error = gw_get_repo_description(&gw_dir->description, gw_trans,
1444 gw_dir->path);
1445 if (error)
1446 goto errored;
1447 error = gw_get_repo_owner(&gw_dir->owner, gw_trans, gw_dir->path);
1448 if (error)
1449 goto errored;
1450 error = gw_get_repo_age(&gw_dir->age, gw_trans, gw_dir->path,
1451 "refs/heads", TM_DIFF);
1452 if (error)
1453 goto errored;
1454 error = gw_get_clone_url(&gw_dir->url, gw_trans, gw_dir->path);
1455 errored:
1456 free(dir_test);
1457 if (opened)
1458 closedir(dt);
1459 return error;
1462 static const struct got_error *
1463 gw_load_got_paths(struct gw_trans *gw_trans)
1465 const struct got_error *error = NULL;
1466 DIR *d;
1467 struct dirent **sd_dent;
1468 struct gw_dir *gw_dir;
1469 struct stat st;
1470 unsigned int d_cnt, d_i;
1472 d = opendir(gw_trans->gw_conf->got_repos_path);
1473 if (d == NULL) {
1474 error = got_error_from_errno2("opendir",
1475 gw_trans->gw_conf->got_repos_path);
1476 return error;
1479 d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
1480 alphasort);
1481 if (d_cnt == -1) {
1482 error = got_error_from_errno2("scandir",
1483 gw_trans->gw_conf->got_repos_path);
1484 return error;
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)
1494 continue;
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)
1501 continue;
1502 else if (error)
1503 return error;
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,
1508 entry);
1509 gw_trans->repos_total++;
1513 closedir(d);
1514 return error;
1517 static const struct got_error *
1518 gw_parse_querystring(struct gw_trans *gw_trans)
1520 const struct got_error *error = NULL;
1521 struct kpair *p;
1522 struct gw_query_action *action = NULL;
1523 unsigned int i;
1525 if (gw_trans->gw_req->fieldnmap[0]) {
1526 error = got_error_from_errno("bad parse");
1527 return error;
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)
1542 continue;
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)
1549 return
1550 got_error_from_errno(
1551 "asprintf");
1553 break;
1556 action = NULL;
1559 if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
1560 if (asprintf(&gw_trans->commit, "%s",
1561 p->parsed.s) == -1)
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",
1566 p->parsed.s) == -1)
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",
1571 p->parsed.s) == -1)
1572 return got_error_from_errno("asprintf");
1574 if ((p = gw_trans->gw_req->fieldmap[KEY_HEADREF]))
1575 if (asprintf(&gw_trans->headref, "%s",
1576 p->parsed.s) == -1)
1577 return got_error_from_errno("asprintf");
1579 if (action == NULL) {
1580 error = got_error_from_errno("invalid action");
1581 return error;
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);
1588 if (error)
1589 return error;
1590 } else
1591 gw_trans->action = GW_INDEX;
1593 if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
1594 gw_trans->page = p->parsed.i;
1596 return error;
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)
1605 return NULL;
1607 if (asprintf(&gw_dir->name, "%s", dir) == -1)
1608 return NULL;
1610 return gw_dir;
1613 static const struct got_error *
1614 gw_display_open(struct gw_trans *gw_trans, enum khttp code, enum kmime mime)
1616 enum kcgi_err kerr;
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",
1622 khttps[code]);
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",
1626 kmimetypes[mime]);
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",
1630 "nosniff");
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",
1637 "1; mode=block");
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;
1657 enum kcgi_err kerr;
1659 error = gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
1660 if (error)
1661 return error;
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));
1679 static void
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)
1683 return;
1685 if (khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0) != KCGI_OK)
1686 return;
1687 khtml_puts(gw_trans->gw_html_req, err->msg);
1688 khtml_close(gw_trans->gw_html_req);
1691 static int
1692 gw_template(size_t key, void *arg)
1694 const struct got_error *error = NULL;
1695 enum kcgi_err kerr;
1696 struct gw_trans *gw_trans = arg;
1697 char *gw_site_link, *img_src = NULL;
1699 switch (key) {
1700 case (TEMPL_HEAD):
1701 kerr = khttp_puts(gw_trans->gw_req, head);
1702 if (kerr != KCGI_OK)
1703 return 0;
1704 break;
1705 case(TEMPL_HEADER):
1706 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1707 KATTR_ID, "got_link", KATTR__MAX);
1708 if (kerr != KCGI_OK)
1709 return 0;
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)
1714 return 0;
1715 if (asprintf(&img_src, "/%s",
1716 gw_trans->gw_conf->got_logo) == -1)
1717 return 0;
1718 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_IMG,
1719 KATTR_SRC, img_src, KATTR__MAX);
1720 if (kerr != KCGI_OK) {
1721 free(img_src);
1722 return 0;
1724 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1725 if (kerr != KCGI_OK) {
1726 free(img_src);
1727 return 0;
1729 break;
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) {
1735 free(gw_site_link);
1736 return 0;
1739 free(gw_site_link);
1740 break;
1741 case(TEMPL_TITLE):
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)
1746 return 0;
1748 break;
1749 case (TEMPL_SEARCH):
1750 kerr = khttp_puts(gw_trans->gw_req, search);
1751 if (kerr != KCGI_OK)
1752 return 0;
1753 break;
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)
1760 return 0;
1761 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1762 KATTR_ID, "site_owner", KATTR__MAX);
1763 if (kerr != KCGI_OK)
1764 return 0;
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)
1769 return 0;
1771 break;
1772 case(TEMPL_CONTENT):
1773 error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
1774 if (error) {
1775 kerr = khttp_puts(gw_trans->gw_req, error->msg);
1776 if (kerr != KCGI_OK)
1777 return 0;
1779 break;
1780 default:
1781 return 0;
1783 return 1;
1786 static char *
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("");
1794 return return_html;
1796 } else
1797 ref_str = strdup("");
1799 if (asprintf(&return_html, header_commit_html, str1, ref_str) == -1)
1800 return_html = strdup("");
1802 free(ref_str);
1803 return return_html;
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");
1816 goto done;
1818 } else
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)
1824 goto done;
1825 kerr = khtml_puts(gw_trans->gw_html_req, "Commit: ");
1826 if (kerr != KCGI_OK)
1827 goto done;
1828 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1829 if (kerr != KCGI_OK)
1830 goto done;
1831 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1832 KATTR_ID, "header_commit", KATTR__MAX);
1833 if (kerr != KCGI_OK)
1834 goto done;
1835 kerr = khtml_puts(gw_trans->gw_html_req, str1);
1836 if (kerr != KCGI_OK)
1837 goto done;
1838 kerr = khtml_puts(gw_trans->gw_html_req, " ");
1839 if (kerr != KCGI_OK)
1840 goto done;
1841 kerr = khtml_puts(gw_trans->gw_html_req, ref_str);
1842 if (kerr != KCGI_OK)
1843 goto done;
1844 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1845 if (kerr != KCGI_OK)
1846 goto done;
1847 done:
1848 if (error == NULL && kerr != KCGI_OK)
1849 error = gw_kcgi_error(kerr != KCGI_OK);
1850 return error;
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)
1862 goto done;
1863 kerr = khtml_puts(gw_trans->gw_html_req, "Diff: ");
1864 if (kerr != KCGI_OK)
1865 goto done;
1866 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1867 if (kerr != KCGI_OK)
1868 goto done;
1869 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1870 KATTR_ID, "header_diff", KATTR__MAX);
1871 if (kerr != KCGI_OK)
1872 goto done;
1873 kerr = khtml_puts(gw_trans->gw_html_req, str1);
1874 if (kerr != KCGI_OK)
1875 goto done;
1876 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_BR, KATTR__MAX);
1877 if (kerr != KCGI_OK)
1878 goto done;
1879 kerr = khtml_puts(gw_trans->gw_html_req, str2);
1880 if (kerr != KCGI_OK)
1881 goto done;
1882 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1883 if (kerr != KCGI_OK)
1884 goto done;
1885 done:
1886 if (error == NULL && kerr != KCGI_OK)
1887 error = gw_kcgi_error(kerr != KCGI_OK);
1888 return error;
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)
1900 goto done;
1901 kerr = khtml_puts(gw_trans->gw_html_req, "Date: ");
1902 if (kerr != KCGI_OK)
1903 goto done;
1904 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1905 if (kerr != KCGI_OK)
1906 goto done;
1907 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1908 KATTR_ID, "header_age", KATTR__MAX);
1909 if (kerr != KCGI_OK)
1910 goto done;
1911 kerr = khtml_puts(gw_trans->gw_html_req, str);
1912 if (kerr != KCGI_OK)
1913 goto done;
1914 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1915 if (kerr != KCGI_OK)
1916 goto done;
1917 done:
1918 if (error == NULL && kerr != KCGI_OK)
1919 error = gw_kcgi_error(kerr != KCGI_OK);
1920 return error;
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;
1927 enum kcgi_err kerr;
1929 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1930 KATTR_ID, "header_author_title", KATTR__MAX);
1931 if (kerr != KCGI_OK)
1932 goto done;
1933 kerr = khtml_puts(gw_trans->gw_html_req, "Author: ");
1934 if (kerr != KCGI_OK)
1935 goto done;
1936 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1937 if (kerr != KCGI_OK)
1938 goto done;
1939 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1940 KATTR_ID, "header_author", KATTR__MAX);
1941 if (kerr != KCGI_OK)
1942 goto done;
1943 kerr = khtml_puts(gw_trans->gw_html_req, str);
1944 if (kerr != KCGI_OK)
1945 goto done;
1946 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1947 if (kerr != KCGI_OK)
1948 goto done;
1949 done:
1950 if (error == NULL && kerr != KCGI_OK)
1951 error = gw_kcgi_error(kerr != KCGI_OK);
1952 return error;
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;
1959 enum kcgi_err kerr;
1961 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1962 KATTR_ID, "header_committer_title", KATTR__MAX);
1963 if (kerr != KCGI_OK)
1964 goto done;
1965 kerr = khtml_puts(gw_trans->gw_html_req, "Committer: ");
1966 if (kerr != KCGI_OK)
1967 goto done;
1968 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1969 if (kerr != KCGI_OK)
1970 goto done;
1971 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1972 KATTR_ID, "header_committer", KATTR__MAX);
1973 if (kerr != KCGI_OK)
1974 goto done;
1975 kerr = khtml_puts(gw_trans->gw_html_req, str);
1976 if (kerr != KCGI_OK)
1977 goto done;
1978 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1979 if (kerr != KCGI_OK)
1980 goto done;
1981 done:
1982 if (error == NULL && kerr != KCGI_OK)
1983 error = gw_kcgi_error(kerr != KCGI_OK);
1984 return error;
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;
1991 enum kcgi_err kerr;
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)
1996 goto done;
1997 kerr = khtml_puts(gw_trans->gw_html_req, "Message: ");
1998 if (kerr != KCGI_OK)
1999 goto done;
2000 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2001 if (kerr != KCGI_OK)
2002 goto done;
2003 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2004 KATTR_ID, "header_commit_msg", KATTR__MAX);
2005 if (kerr != KCGI_OK)
2006 goto done;
2007 kerr = khttp_puts(gw_trans->gw_req, str);
2008 if (kerr != KCGI_OK)
2009 goto done;
2010 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2011 if (kerr != KCGI_OK)
2012 goto done;
2013 done:
2014 if (error == NULL && kerr != KCGI_OK)
2015 error = gw_kcgi_error(kerr != KCGI_OK);
2016 return error;
2019 /* XXX: slated for deletion */
2020 static char *
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("");
2028 return return_html;
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;
2035 enum kcgi_err kerr;
2037 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2038 KATTR_ID, "header_tree_title", KATTR__MAX);
2039 if (kerr != KCGI_OK)
2040 goto done;
2041 kerr = khtml_puts(gw_trans->gw_html_req, "Tree: ");
2042 if (kerr != KCGI_OK)
2043 goto done;
2044 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2045 if (kerr != KCGI_OK)
2046 goto done;
2047 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2048 KATTR_ID, "header_tree", KATTR__MAX);
2049 if (kerr != KCGI_OK)
2050 goto done;
2051 kerr = khtml_puts(gw_trans->gw_html_req, str);
2052 if (kerr != KCGI_OK)
2053 goto done;
2054 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2055 if (kerr != KCGI_OK)
2056 goto done;
2057 done:
2058 if (error == NULL && kerr != KCGI_OK)
2059 error = gw_kcgi_error(kerr != KCGI_OK);
2060 return error;
2063 static const struct got_error *
2064 gw_get_repo_description(char **description, struct gw_trans *gw_trans,
2065 char *dir)
2067 const struct got_error *error = NULL;
2068 FILE *f = NULL;
2069 char *d_file = NULL;
2070 unsigned int len;
2071 size_t n;
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");
2081 if (f == NULL) {
2082 if (errno == ENOENT || errno == EACCES)
2083 return gw_empty_string(description);
2084 error = got_error_from_errno2("fopen", d_file);
2085 goto done;
2088 if (fseek(f, 0, SEEK_END) == -1) {
2089 error = got_ferror(f, GOT_ERR_IO);
2090 goto done;
2092 len = ftell(f);
2093 if (len == -1) {
2094 error = got_ferror(f, GOT_ERR_IO);
2095 goto done;
2097 if (fseek(f, 0, SEEK_SET) == -1) {
2098 error = got_ferror(f, GOT_ERR_IO);
2099 goto done;
2101 *description = calloc(len + 1, sizeof(**description));
2102 if (*description == NULL) {
2103 error = got_error_from_errno("calloc");
2104 goto done;
2107 n = fread(*description, 1, len, f);
2108 if (n == 0 && ferror(f))
2109 error = got_ferror(f, GOT_ERR_IO);
2110 done:
2111 if (f != NULL && fclose(f) == -1 && error == NULL)
2112 error = got_error_from_errno("fclose");
2113 free(d_file);
2114 return error;
2117 static const struct got_error *
2118 gw_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2120 struct tm tm;
2121 time_t diff_time;
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";
2126 char *s;
2127 char datebuf[29];
2129 *repo_age = NULL;
2131 switch (ref_tm) {
2132 case TM_DIFF:
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)),
2141 months) == -1)
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),
2157 minutes) == -1)
2158 return got_error_from_errno("asprintf");
2159 } else if (diff_time > 2) {
2160 if (asprintf(repo_age, "%lld %s", diff_time,
2161 seconds) == -1)
2162 return got_error_from_errno("asprintf");
2163 } else {
2164 if (asprintf(repo_age, "%s", now) == -1)
2165 return got_error_from_errno("asprintf");
2167 break;
2168 case TM_LONG:
2169 if (gmtime_r(&committer_time, &tm) == NULL)
2170 return got_error_from_errno("gmtime_r");
2172 s = asctime_r(&tm, datebuf);
2173 if (s == NULL)
2174 return got_error_from_errno("asctime_r");
2176 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2177 return got_error_from_errno("asprintf");
2178 break;
2180 return NULL;
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;
2194 int is_head = 0;
2195 time_t committer_time = 0, cmp_time = 0;
2196 const char *refname;
2198 *repo_age = NULL;
2199 SIMPLEQ_INIT(&refs);
2201 if (repo_ref == NULL)
2202 return NULL;
2204 if (strncmp(repo_ref, "refs/heads/", 11) == 0)
2205 is_head = 1;
2207 if (gw_trans->gw_conf->got_show_repo_age == 0)
2208 return NULL;
2210 error = got_repo_open(&repo, dir, NULL);
2211 if (error)
2212 goto done;
2214 if (is_head)
2215 error = got_ref_list(&refs, repo, "refs/heads",
2216 got_ref_cmp_by_name, NULL);
2217 else
2218 error = got_ref_list(&refs, repo, repo_ref,
2219 got_ref_cmp_by_name, NULL);
2220 if (error)
2221 goto done;
2223 SIMPLEQ_FOREACH(re, &refs, entry) {
2224 if (is_head)
2225 refname = strdup(repo_ref);
2226 else
2227 refname = got_ref_get_name(re->ref);
2228 error = got_ref_open(&head_ref, repo, refname, 0);
2229 if (error)
2230 goto done;
2232 error = got_ref_resolve(&id, repo, head_ref);
2233 got_ref_close(head_ref);
2234 if (error)
2235 goto done;
2237 error = got_object_open_as_commit(&commit, repo, id);
2238 if (error)
2239 goto done;
2241 committer_time =
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);
2252 done:
2253 got_ref_list_free(&refs);
2254 free(id);
2255 return error;
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;
2262 FILE *f = NULL;
2263 struct got_object_id *id1 = NULL, *id2 = NULL;
2264 char *label1 = NULL, *label2 = NULL, *line = NULL;
2265 int obj_type;
2266 size_t linesize = 0;
2267 ssize_t linelen;
2268 enum kcgi_err kerr = KCGI_OK;
2270 f = got_opentemp();
2271 if (f == NULL)
2272 return NULL;
2274 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2275 if (error)
2276 goto done;
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);
2281 if (error)
2282 goto done;
2285 error = got_repo_match_object_id(&id2, &label2,
2286 header->commit_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2287 if (error)
2288 goto done;
2290 error = got_object_get_type(&obj_type, header->repo, id2);
2291 if (error)
2292 goto done;
2293 switch (obj_type) {
2294 case GOT_OBJ_TYPE_BLOB:
2295 error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, 3, 0,
2296 header->repo, f);
2297 break;
2298 case GOT_OBJ_TYPE_TREE:
2299 error = got_diff_objects_as_trees(id1, id2, "", "", 3, 0,
2300 header->repo, f);
2301 break;
2302 case GOT_OBJ_TYPE_COMMIT:
2303 error = got_diff_objects_as_commits(id1, id2, 3, 0,
2304 header->repo, f);
2305 break;
2306 default:
2307 error = got_error(GOT_ERR_OBJ_TYPE);
2309 if (error)
2310 goto done;
2312 if (fseek(f, 0, SEEK_SET) == -1) {
2313 error = got_ferror(f, GOT_ERR_IO);
2314 goto done;
2317 while ((linelen = getline(&line, &linesize, f)) != -1) {
2318 error = gw_colordiff_line(gw_trans, line);
2319 if (error)
2320 goto done;
2321 /* XXX: KHTML_PRETTY breaks this */
2322 kerr = khtml_puts(gw_trans->gw_html_req, line);
2323 if (kerr != KCGI_OK)
2324 goto done;
2325 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2326 if (kerr != KCGI_OK)
2327 goto done;
2329 if (linelen == -1 && ferror(f))
2330 error = got_error_from_errno("getline");
2331 done:
2332 if (f && fclose(f) == -1 && error == NULL)
2333 error = got_error_from_errno("fclose");
2334 free(line);
2335 free(label1);
2336 free(label2);
2337 free(id1);
2338 free(id2);
2340 if (error == NULL && kerr != KCGI_OK)
2341 error = gw_kcgi_error(kerr != KCGI_OK);
2342 return error;
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;
2352 *owner = NULL;
2354 if (gw_trans->gw_conf->got_show_repo_owner == 0)
2355 return NULL;
2357 error = got_repo_open(&repo, dir, NULL);
2358 if (error)
2359 return error;
2360 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
2361 if (gitconfig_owner) {
2362 *owner = strdup(gitconfig_owner);
2363 if (*owner == NULL)
2364 error = got_error_from_errno("strdup");
2366 got_repo_close(repo);
2367 return error;
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;
2374 FILE *f;
2375 char *d_file = NULL;
2376 unsigned int len;
2377 size_t n;
2379 *url = NULL;
2381 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2382 return got_error_from_errno("asprintf");
2384 f = fopen(d_file, "r");
2385 if (f == NULL) {
2386 if (errno != ENOENT && errno != EACCES)
2387 error = got_error_from_errno2("fopen", d_file);
2388 goto done;
2391 if (fseek(f, 0, SEEK_END) == -1) {
2392 error = got_ferror(f, GOT_ERR_IO);
2393 goto done;
2395 len = ftell(f);
2396 if (len == -1) {
2397 error = got_ferror(f, GOT_ERR_IO);
2398 goto done;
2400 if (fseek(f, 0, SEEK_SET) == -1) {
2401 error = got_ferror(f, GOT_ERR_IO);
2402 goto done;
2405 *url = calloc(len + 1, sizeof(**url));
2406 if (*url == NULL) {
2407 error = got_error_from_errno("calloc");
2408 goto done;
2411 n = fread(*url, 1, len, f);
2412 if (n == 0 && ferror(f))
2413 error = got_ferror(f, GOT_ERR_IO);
2414 done:
2415 if (f && fclose(f) == -1 && error == NULL)
2416 error = got_error_from_errno("fclose");
2417 free(d_file);
2418 return NULL;
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;
2435 size_t newsize;
2436 struct got_tag_object *tag = NULL;
2438 *tag_html = NULL;
2440 SIMPLEQ_INIT(&refs);
2442 error = buf_alloc(&diffbuf, 0);
2443 if (error)
2444 return NULL;
2446 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2447 if (error)
2448 goto done;
2450 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo);
2451 if (error)
2452 goto done;
2454 SIMPLEQ_FOREACH(re, &refs, entry) {
2455 const char *refname;
2456 const char *tagger;
2457 const char *tag_commit;
2458 time_t tagger_time;
2459 struct got_object_id *id;
2461 refname = got_ref_get_name(re->ref);
2462 if (strncmp(refname, "refs/tags/", 10) != 0)
2463 continue;
2464 refname += 10;
2465 refstr = got_ref_to_str(re->ref);
2466 if (refstr == NULL) {
2467 error = got_error_from_errno("got_ref_to_str");
2468 goto done;
2471 error = got_ref_resolve(&id, repo, re->ref);
2472 if (error)
2473 goto done;
2474 error = got_object_open_as_tag(&tag, repo, id);
2475 free(id);
2476 if (error)
2477 goto done;
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));
2484 if (error)
2485 goto done;
2487 tag_commit0 = strdup(got_object_tag_get_message(tag));
2488 if (tag_commit0 == NULL) {
2489 error = got_error_from_errno("strdup");
2490 goto done;
2493 tag_commit = tag_commit0;
2494 while (*tag_commit == '\n')
2495 tag_commit++;
2497 switch (tag_type) {
2498 case TAGBRIEF:
2499 newline = strchr(tag_commit, '\n');
2500 if (newline)
2501 *newline = '\0';
2503 error = gw_get_time_str(&age, tagger_time, TM_DIFF);
2504 if (error)
2505 goto done;
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");
2512 goto done;
2515 if (asprintf(&tag_row, tags_row, age ? age : "",
2516 refname, tag_commit, tags_navs_disp) == -1) {
2517 error = got_error_from_errno("asprintf");
2518 goto done;
2521 free(tags_navs_disp);
2522 break;
2523 case TAGFULL:
2524 error = gw_get_time_str(&age, tagger_time, TM_LONG);
2525 if (error)
2526 goto done;
2527 error = gw_html_escape(&escaped_tagger, tagger);
2528 if (error)
2529 goto done;
2530 error = gw_html_escape(&escaped_tag_commit, tag_commit);
2531 if (error)
2532 goto done;
2533 if (asprintf(&tag_row, tag_info, age ? age : "",
2534 escaped_tagger, escaped_tag_commit) == -1) {
2535 error = got_error_from_errno("asprintf");
2536 goto done;
2538 break;
2539 default:
2540 break;
2543 error = buf_puts(&newsize, diffbuf, tag_row);
2544 if (error)
2545 goto done;
2547 if (limit && --limit == 0)
2548 break;
2550 got_object_tag_close(tag);
2551 tag = NULL;
2552 free(id_str);
2553 id_str = NULL;
2554 free(refstr);
2555 refstr = NULL;
2556 free(age);
2557 age = NULL;
2558 free(age_html);
2559 age_html = NULL;
2560 free(escaped_tagger);
2561 escaped_tagger = NULL;
2562 free(escaped_tag_commit);
2563 escaped_tag_commit = NULL;
2564 free(tag_commit0);
2565 tag_commit0 = NULL;
2566 free(tag_row);
2567 tag_row = 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");
2576 done:
2577 if (tag)
2578 got_object_tag_close(tag);
2579 free(id_str);
2580 free(refstr);
2581 free(age);
2582 free(age_html);
2583 free(escaped_tagger);
2584 free(escaped_tag_commit);
2585 free(tag_commit0);
2586 free(tag_row);
2587 buf_free(diffbuf);
2588 got_ref_list_free(&refs);
2589 if (repo)
2590 got_repo_close(repo);
2591 return error;
2594 static void
2595 gw_free_headers(struct gw_header *header)
2597 free(header->id);
2598 free(header->path);
2599 if (header->commit != NULL)
2600 got_object_commit_close(header->commit);
2601 if (header->repo)
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 *
2611 gw_init_header()
2613 struct gw_header *header;
2615 header = malloc(sizeof(*header));
2616 if (header == NULL)
2617 return NULL;
2619 header->repo = NULL;
2620 header->commit = NULL;
2621 header->id = NULL;
2622 header->path = NULL;
2623 SIMPLEQ_INIT(&header->refs);
2625 return header;
2628 static const struct got_error *
2629 gw_get_commits(struct gw_trans * gw_trans, struct gw_header *header,
2630 int limit)
2632 const struct got_error *error = NULL;
2633 struct got_commit_graph *graph = NULL;
2635 error = got_commit_graph_open(&graph, header->path, 0);
2636 if (error)
2637 goto done;
2639 error = got_commit_graph_iter_start(graph, header->id, header->repo,
2640 NULL, NULL);
2641 if (error)
2642 goto done;
2644 for (;;) {
2645 error = got_commit_graph_iter_next(&header->id, graph,
2646 header->repo, NULL, NULL);
2647 if (error) {
2648 if (error->code == GOT_ERR_ITER_COMPLETED)
2649 error = NULL;
2650 goto done;
2652 if (header->id == NULL)
2653 goto done;
2655 error = got_object_open_as_commit(&header->commit, header->repo,
2656 header->id);
2657 if (error)
2658 goto done;
2660 error = gw_get_commit(gw_trans, header);
2661 if (limit > 1) {
2662 struct gw_header *n_header = NULL;
2663 if ((n_header = gw_init_header()) == NULL) {
2664 error = got_error_from_errno("malloc");
2665 goto done;
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,
2677 entry);
2679 if (error || (limit && --limit == 0))
2680 break;
2682 done:
2683 if (graph)
2684 got_commit_graph_close(graph);
2685 return error;
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;
2697 /*print commit*/
2698 SIMPLEQ_FOREACH(re, &header->refs, entry) {
2699 char *s;
2700 const char *name;
2701 struct got_tag_object *tag = NULL;
2702 int cmp;
2704 name = got_ref_get_name(re->ref);
2705 if (strcmp(name, GOT_REF_HEAD) == 0)
2706 continue;
2707 if (strncmp(name, "refs/", 5) == 0)
2708 name += 5;
2709 if (strncmp(name, "got/", 4) == 0)
2710 continue;
2711 if (strncmp(name, "heads/", 6) == 0)
2712 name += 6;
2713 if (strncmp(name, "remotes/", 8) == 0)
2714 name += 8;
2715 if (strncmp(name, "tags/", 5) == 0) {
2716 error = got_object_open_as_tag(&tag, header->repo,
2717 re->id);
2718 if (error) {
2719 if (error->code != GOT_ERR_OBJ_TYPE)
2720 continue;
2722 * Ref points at something other
2723 * than a tag.
2725 error = NULL;
2726 tag = NULL;
2729 cmp = got_object_id_cmp(tag ?
2730 got_object_tag_get_object_id(tag) : re->id, header->id);
2731 if (tag)
2732 got_object_tag_close(tag);
2733 if (cmp != 0)
2734 continue;
2735 s = refs_str;
2736 if (asprintf(&refs_str, "%s%s%s", s ? s : "",
2737 s ? ", " : "", name) == -1) {
2738 error = got_error_from_errno("asprintf");
2739 free(s);
2740 return error;
2742 header->refs_str = strdup(refs_str);
2743 free(s);
2746 if (refs_str == NULL)
2747 header->refs_str = strdup("");
2748 free(refs_str);
2750 error = got_object_id_str(&header->commit_id, header->id);
2751 if (error)
2752 return error;
2754 error = got_object_id_str(&header->tree_id,
2755 got_object_commit_get_tree_id(header->commit));
2756 if (error)
2757 return error;
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);
2764 free (parent_id);
2765 error = got_object_id_str(&header->parent_id, id2);
2766 if (error)
2767 return error;
2768 free(id2);
2769 } else
2770 header->parent_id = strdup("/dev/null");
2771 } else
2772 header->parent_id = strdup("");
2774 header->committer_time =
2775 got_object_commit_get_committer_time(header->commit);
2777 header->author =
2778 got_object_commit_get_author(header->commit);
2779 header->committer =
2780 got_object_commit_get_committer(header->commit);
2781 if (error)
2782 return error;
2784 /* XXX Doesn't the log message require escaping? */
2785 error = got_object_commit_get_logmsg(&commit_msg0, header->commit);
2786 if (error)
2787 return error;
2789 commit_msg = commit_msg0;
2790 while (*commit_msg == '\n')
2791 commit_msg++;
2793 header->commit_msg = strdup(commit_msg);
2794 free(commit_msg0);
2795 return error;
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);
2805 if (error)
2806 return error;
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);
2812 if (error)
2813 return error;
2815 error = got_ref_resolve(&header->id, header->repo, head_ref);
2816 got_ref_close(head_ref);
2817 if (error)
2818 return error;
2820 error = got_object_open_as_commit(&header->commit,
2821 header->repo, header->id);
2822 } else {
2823 struct got_reference *ref;
2824 error = got_ref_open(&ref, header->repo, gw_trans->commit, 0);
2825 if (error == NULL) {
2826 int obj_type;
2827 error = got_ref_resolve(&header->id, header->repo, ref);
2828 got_ref_close(ref);
2829 if (error)
2830 return error;
2831 error = got_object_get_type(&obj_type, header->repo,
2832 header->id);
2833 if (error)
2834 return error;
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);
2839 if (error)
2840 return error;
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);
2845 return error;
2847 free(header->id);
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);
2854 if (error)
2855 return error;
2856 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
2857 error = got_error(GOT_ERR_OBJ_TYPE);
2858 return error;
2860 error = got_object_open_as_commit(&header->commit,
2861 header->repo, header->id);
2862 if (error)
2863 return error;
2865 if (header->commit == NULL) {
2866 error = got_repo_match_object_id_prefix(&header->id,
2867 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2868 header->repo);
2869 if (error)
2870 return error;
2872 error = got_repo_match_object_id_prefix(&header->id,
2873 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2874 header->repo);
2877 error = got_repo_map_path(&in_repo_path, header->repo,
2878 gw_trans->repo_path, 1);
2879 if (error)
2880 return error;
2882 if (in_repo_path) {
2883 header->path = strdup(in_repo_path);
2885 free(in_repo_path);
2887 error = got_ref_list(&header->refs, header->repo, NULL,
2888 got_ref_cmp_by_name, NULL);
2889 if (error)
2890 return error;
2892 error = gw_get_commits(gw_trans, header, limit);
2893 return error;
2896 struct blame_line {
2897 int annotated;
2898 char *id_str;
2899 char *committer;
2900 char datebuf[11]; /* YYYY-MM-DD + NUL */
2903 struct gw_blame_cb_args {
2904 struct blame_line *lines;
2905 int nlines;
2906 int nlines_prec;
2907 int lineno_cur;
2908 off_t *line_offsets;
2909 FILE *f;
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;
2921 char *line = NULL;
2922 size_t linesize = 0, newsize;
2923 struct got_commit_object *commit = NULL;
2924 off_t offset;
2925 struct tm tm;
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);
2932 if (lineno == -1)
2933 return NULL; /* no change in this commit */
2935 /* Annotate this line. */
2936 bline = &a->lines[lineno - 1];
2937 if (bline->annotated)
2938 return NULL;
2939 err = got_object_id_str(&bline->id_str, id);
2940 if (err)
2941 return err;
2943 err = got_object_open_as_commit(&commit, a->repo, id);
2944 if (err)
2945 goto done;
2947 bline->committer = strdup(got_object_commit_get_committer(commit));
2948 if (bline->committer == NULL) {
2949 err = got_error_from_errno("strdup");
2950 goto done;
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);
2959 goto done;
2961 bline->annotated = 1;
2963 /* Print lines annotated so far. */
2964 bline = &a->lines[a->lineno_cur - 1];
2965 if (!bline->annotated)
2966 goto done;
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");
2971 goto done;
2974 while (bline->annotated) {
2975 char *smallerthan, *at, *nl, *committer, *blame_row = NULL,
2976 *line_escape = NULL;
2977 size_t len;
2979 if (getline(&line, &linesize, a->f) == -1) {
2980 if (ferror(a->f))
2981 err = got_error_from_errno("getline");
2982 break;
2985 committer = bline->committer;
2986 smallerthan = strchr(committer, '<');
2987 if (smallerthan && smallerthan[1] != '\0')
2988 committer = smallerthan + 1;
2989 at = strchr(committer, '@');
2990 if (at)
2991 *at = '\0';
2992 len = strlen(committer);
2993 if (len >= 9)
2994 committer[8] = '\0';
2996 nl = strchr(line, '\n');
2997 if (nl)
2998 *nl = '\0';
3000 err = gw_html_escape(&line_escape, line);
3001 if (err)
3002 goto err;
3004 if (a->gw_trans->repo_folder == NULL)
3005 a->gw_trans->repo_folder = strdup("");
3006 if (a->gw_trans->repo_folder == NULL)
3007 goto err;
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);
3012 a->lineno_cur++;
3013 err = buf_puts(&newsize, a->blamebuf, blame_row);
3014 if (err)
3015 return err;
3017 bline = &a->lines[a->lineno_cur - 1];
3018 err:
3019 free(line_escape);
3020 free(blame_row);
3022 done:
3023 if (commit)
3024 got_object_commit_close(commit);
3025 free(line);
3026 return err;
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;
3039 int i, obj_type;
3040 size_t filesize;
3042 *blame_html = NULL;
3044 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3045 if (error)
3046 return error;
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");
3053 goto done;
3056 error = got_repo_map_path(&in_repo_path, repo, path, 1);
3057 if (error)
3058 goto done;
3060 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
3061 GOT_OBJ_TYPE_COMMIT, 1, repo);
3062 if (error)
3063 goto done;
3065 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
3066 if (error)
3067 goto done;
3069 if (obj_id == NULL) {
3070 error = got_error(GOT_ERR_NO_OBJ);
3071 goto done;
3074 error = got_object_get_type(&obj_type, repo, obj_id);
3075 if (error)
3076 goto done;
3078 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3079 error = got_error(GOT_ERR_OBJ_TYPE);
3080 goto done;
3083 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
3084 if (error)
3085 goto done;
3087 error = buf_alloc(&bca.blamebuf, 0);
3088 if (error)
3089 goto done;
3091 bca.f = got_opentemp();
3092 if (bca.f == NULL) {
3093 error = got_error_from_errno("got_opentemp");
3094 goto done;
3096 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
3097 &bca.line_offsets, bca.f, blob);
3098 if (error || bca.nlines == 0)
3099 goto done;
3101 /* Don't include \n at EOF in the blame line count. */
3102 if (bca.line_offsets[bca.nlines - 1] == filesize)
3103 bca.nlines--;
3105 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
3106 if (bca.lines == NULL) {
3107 error = got_error_from_errno("calloc");
3108 goto done;
3110 bca.lineno_cur = 1;
3111 bca.nlines_prec = 0;
3112 i = bca.nlines;
3113 while (i > 0) {
3114 i /= 10;
3115 bca.nlines_prec++;
3117 bca.repo = repo;
3118 bca.gw_trans = gw_trans;
3120 error = got_blame(in_repo_path, commit_id, repo, gw_blame_cb, &bca,
3121 NULL, NULL);
3122 if (error)
3123 goto done;
3124 if (buf_len(bca.blamebuf) > 0) {
3125 error = buf_putc(bca.blamebuf, '\0');
3126 if (error)
3127 goto done;
3128 *blame_html = strdup(buf_get(bca.blamebuf));
3129 if (*blame_html == NULL) {
3130 error = got_error_from_errno("strdup");
3131 goto done;
3134 done:
3135 free(bca.line_offsets);
3136 free(bca.blamebuf);
3137 free(in_repo_path);
3138 free(commit_id);
3139 free(obj_id);
3140 free(path);
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);
3147 free(bca.lines);
3148 if (bca.f && fclose(bca.f) == EOF && error == NULL)
3149 error = got_error_from_errno("fclose");
3150 if (blob)
3151 got_object_blob_close(blob);
3152 if (repo)
3153 got_repo_close(repo);
3154 return error;
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;
3166 int obj_type;
3167 size_t n;
3168 FILE *f = NULL;
3170 *blobstr = NULL;
3171 *filesize = 0;
3173 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3174 if (error)
3175 return error;
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");
3182 goto done;
3185 error = got_repo_map_path(&in_repo_path, repo, path, 1);
3186 if (error)
3187 goto done;
3189 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
3190 GOT_OBJ_TYPE_COMMIT, 1, repo);
3191 if (error)
3192 goto done;
3194 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
3195 if (error)
3196 goto done;
3198 if (obj_id == NULL) {
3199 error = got_error(GOT_ERR_NO_OBJ);
3200 goto done;
3203 error = got_object_get_type(&obj_type, repo, obj_id);
3204 if (error)
3205 goto done;
3207 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3208 error = got_error(GOT_ERR_OBJ_TYPE);
3209 goto done;
3212 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
3213 if (error)
3214 goto done;
3216 f = got_opentemp();
3217 if (f == NULL) {
3218 error = got_error_from_errno("got_opentemp");
3219 goto done;
3221 error = got_object_blob_dump_to_file(filesize, NULL, NULL, f, blob);
3222 if (error)
3223 goto done;
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");
3229 goto done;
3232 n = fread(*blobstr, 1, *filesize, f);
3233 if (n == 0) {
3234 if (ferror(f))
3235 error = got_ferror(f, GOT_ERR_IO);
3236 goto done;
3238 done:
3239 free(in_repo_path);
3240 free(commit_id);
3241 free(obj_id);
3242 free(path);
3243 if (blob)
3244 got_object_blob_close(blob);
3245 if (repo)
3246 got_repo_close(repo);
3247 if (f != NULL && fclose(f) == -1 && error == NULL)
3248 error = got_error_from_errno("fclose");
3249 if (error) {
3250 free(*blobstr);
3251 *blobstr = NULL;
3252 *filesize = 0;
3254 return error;
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;
3265 size_t newsize;
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;
3273 *tree_html = NULL;
3275 error = buf_alloc(&diffbuf, 0);
3276 if (error)
3277 return error;
3279 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3280 if (error)
3281 goto done;
3283 if (gw_trans->repo_folder != NULL)
3284 path = strdup(gw_trans->repo_folder);
3285 else {
3286 error = got_repo_map_path(&in_repo_path, repo,
3287 gw_trans->repo_path, 1);
3288 if (error)
3289 goto done;
3290 free(path);
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);
3297 if (error)
3298 goto done;
3299 error = got_ref_resolve(&commit_id, repo, head_ref);
3300 if (error)
3301 goto done;
3302 got_ref_close(head_ref);
3304 } else {
3305 error = got_repo_match_object_id(&commit_id, NULL,
3306 gw_trans->commit, GOT_OBJ_TYPE_COMMIT, 1, repo);
3307 if (error)
3308 goto done;
3311 error = got_object_id_str(&gw_trans->commit, commit_id);
3312 if (error)
3313 goto done;
3315 error = got_object_id_by_path(&tree_id, repo, commit_id, path);
3316 if (error)
3317 goto done;
3319 error = got_object_open_as_tree(&tree, repo, tree_id);
3320 if (error)
3321 goto done;
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 = "";
3327 mode_t mode;
3329 te = got_object_tree_get_entry(tree, i);
3331 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
3332 if (error)
3333 goto done;
3335 mode = got_tree_entry_get_mode(te);
3336 if (got_object_tree_entry_is_submodule(te))
3337 modestr = "$";
3338 else if (S_ISLNK(mode))
3339 modestr = "@";
3340 else if (S_ISDIR(mode))
3341 modestr = "/";
3342 else if (mode & S_IXUSR)
3343 modestr = "*";
3345 if (class_flip == 0) {
3346 class = "back_lightgray";
3347 class_flip = 1;
3348 } else {
3349 class = "back_white";
3350 class_flip = 0;
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(
3358 "asprintf");
3359 goto done;
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");
3367 goto done;
3369 if (asprintf(&tree_row, tree_line, class, url_html,
3370 class) == -1) {
3371 error = got_error_from_errno("asprintf");
3372 goto done;
3374 } else {
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");
3381 goto done;
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 : "",
3392 "blame") == -1) {
3393 error = got_error_from_errno("asprintf");
3394 goto done;
3398 error = buf_puts(&newsize, diffbuf, tree_row);
3399 if (error)
3400 goto done;
3402 free(id_str);
3403 id_str = NULL;
3404 free(url_html);
3405 url_html = NULL;
3406 free(tree_row);
3407 tree_row = NULL;
3408 free(build_folder);
3409 build_folder = NULL;
3412 if (buf_len(diffbuf) > 0) {
3413 error = buf_putc(diffbuf, '\0');
3414 if (error)
3415 goto done;
3416 *tree_html = strdup(buf_get(diffbuf));
3417 if (*tree_html == NULL) {
3418 error = got_error_from_errno("strdup");
3419 goto done;
3422 done:
3423 if (tree)
3424 got_object_tree_close(tree);
3425 if (repo)
3426 got_repo_close(repo);
3428 free(id_str);
3429 free(url_html);
3430 free(tree_row);
3431 free(in_repo_path);
3432 free(tree_id);
3433 free(diffbuf);
3434 free(build_folder);
3435 return error;
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;
3447 size_t newsize;
3449 *head_html = NULL;
3451 SIMPLEQ_INIT(&refs);
3453 error = buf_alloc(&diffbuf, 0);
3454 if (error)
3455 return NULL;
3457 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3458 if (error)
3459 goto done;
3461 error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
3462 NULL);
3463 if (error)
3464 goto done;
3466 SIMPLEQ_FOREACH(re, &refs, entry) {
3467 char *refname;
3469 refname = strdup(got_ref_get_name(re->ref));
3470 if (refname == NULL) {
3471 error = got_error_from_errno("got_ref_to_str");
3472 goto done;
3475 if (strncmp(refname, "refs/heads/", 11) != 0) {
3476 free(refname);
3477 continue;
3480 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
3481 refname, TM_DIFF);
3482 if (error)
3483 goto done;
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,
3488 refname) == -1) {
3489 error = got_error_from_errno("asprintf");
3490 goto done;
3493 if (strncmp(refname, "refs/heads/", 11) == 0)
3494 refname += 11;
3496 if (asprintf(&head_row, heads_row, age, refname,
3497 head_navs_disp) == -1) {
3498 error = got_error_from_errno("asprintf");
3499 goto done;
3502 error = buf_puts(&newsize, diffbuf, head_row);
3504 free(head_navs_disp);
3505 free(head_row);
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");
3514 done:
3515 buf_free(diffbuf);
3516 got_ref_list_free(&refs);
3517 if (repo)
3518 got_repo_close(repo);
3519 return error;
3522 static char *
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)
3530 return NULL;
3532 if (gw_trans->action_name != NULL &&
3533 asprintf(&action, " / %s", gw_trans->action_name) == -1) {
3534 free(repo);
3535 return NULL;
3538 if (asprintf(&link, site_link, GOTWEB,
3539 gw_trans->gw_conf->got_site_link,
3540 repo ? repo : "", action ? action : "") == -1) {
3541 free(repo);
3542 free(action);
3543 return NULL;
3546 free(repo);
3547 free(action);
3548 return link;
3551 static const struct got_error *
3552 gw_colordiff_line(struct gw_trans *gw_trans, char *buf)
3554 const struct got_error *error = NULL;
3555 char *color = 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);
3588 return error;
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 {
3600 char c;
3601 const char *s;
3602 } esc[] = {
3603 { '>', "&gt;" },
3604 { '<', "&lt;" },
3605 { '&', "&amp;" },
3606 { '"', "&quot;" },
3607 { '\'', "&apos;" },
3608 { '\n', "<br />" },
3610 size_t orig_len, len;
3611 int i, j, x;
3613 orig_len = strlen(orig_html);
3614 len = orig_len;
3615 for (i = 0; i < orig_len; i++) {
3616 for (j = 0; j < nitems(esc); j++) {
3617 if (orig_html[i] != esc[j].c)
3618 continue;
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");
3627 x = 0;
3628 for (i = 0; i < orig_len; i++) {
3629 int escaped = 0;
3630 for (j = 0; j < nitems(esc); j++) {
3631 if (orig_html[i] != esc[j].c)
3632 continue;
3634 if (strlcat(*escaped_html, esc[j].s, len + 1)
3635 >= len + 1) {
3636 error = got_error(GOT_ERR_NO_SPACE);
3637 goto done;
3639 x += strlen(esc[j].s);
3640 escaped = 1;
3641 break;
3643 if (!escaped) {
3644 (*escaped_html)[x] = orig_html[i];
3645 x++;
3648 done:
3649 if (error) {
3650 free(*escaped_html);
3651 *escaped_html = NULL;
3652 } else {
3653 (*escaped_html)[x] = '\0';
3655 return error;
3658 int
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";
3665 int gw_malloc = 1;
3666 enum kcgi_err kerr;
3668 if ((gw_trans = malloc(sizeof(struct gw_trans))) == NULL)
3669 errx(1, "malloc");
3671 if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
3672 errx(1, "malloc");
3674 if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
3675 errx(1, "malloc");
3677 if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
3678 errx(1, "malloc");
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);
3683 goto done;
3686 if ((gw_trans->gw_conf =
3687 malloc(sizeof(struct gotweb_conf))) == NULL) {
3688 gw_malloc = 0;
3689 error = got_error_from_errno("malloc");
3690 goto done;
3693 TAILQ_INIT(&gw_trans->gw_dirs);
3694 TAILQ_INIT(&gw_trans->gw_headers);
3696 gw_trans->page = 0;
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);
3707 if (error)
3708 goto done;
3710 error = gw_parse_querystring(gw_trans);
3711 if (error)
3712 goto done;
3714 if (gw_trans->action == GW_BLOB)
3715 error = gw_blob(gw_trans);
3716 else
3717 error = gw_display_index(gw_trans);
3718 done:
3719 if (error) {
3720 gw_trans->mime = KMIME_TEXT_PLAIN;
3721 gw_trans->action = GW_ERR;
3722 gw_display_error(gw_trans, error);
3724 if (gw_malloc) {
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) {
3740 free(dir->name);
3741 free(dir->description);
3742 free(dir->age);
3743 free(dir->url);
3744 free(dir->path);
3745 free(dir);
3750 khttp_free(gw_trans->gw_req);
3751 return 0;