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 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(char **, 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(char **, char *);
184 static char *gw_gen_commit_header(char *, char*);
185 static char *gw_gen_diff_header(char *, char*);
186 static char *gw_gen_author_header(const char *);
187 static char *gw_gen_committer_header(char *);
188 static char *gw_gen_commit_msg_header(char *);
189 static char *gw_gen_tree_header(char *);
191 static void gw_free_headers(struct gw_header *);
192 static const struct got_error* gw_display_open(struct gw_trans *, enum khttp,
193 enum kmime);
194 static const struct got_error* gw_display_index(struct gw_trans *);
195 static void gw_display_error(struct gw_trans *,
196 const struct got_error *);
198 static int gw_template(size_t, void *);
200 static const struct got_error* gw_get_header(struct gw_trans *,
201 struct gw_header *, int);
202 static const struct got_error* gw_get_commits(struct gw_trans *,
203 struct gw_header *, int);
204 static const struct got_error* gw_get_commit(struct gw_trans *,
205 struct gw_header *);
206 static const struct got_error* gw_apply_unveil(const char *, const char *);
207 static const struct got_error* gw_blame_cb(void *, int, int,
208 struct got_object_id *);
209 static const struct got_error* gw_load_got_paths(struct gw_trans *);
210 static const struct got_error* gw_load_got_path(struct gw_trans *,
211 struct gw_dir *);
212 static const struct got_error* gw_parse_querystring(struct gw_trans *);
214 static const struct got_error* gw_blame(struct gw_trans *);
215 static const struct got_error* gw_blob(struct gw_trans *);
216 static const struct got_error* gw_diff(struct gw_trans *);
217 static const struct got_error* gw_index(struct gw_trans *);
218 static const struct got_error* gw_commits(struct gw_trans *);
219 static const struct got_error* gw_briefs(struct gw_trans *);
220 static const struct got_error* gw_summary(struct gw_trans *);
221 static const struct got_error* gw_tree(struct gw_trans *);
222 static const struct got_error* gw_tag(struct gw_trans *);
224 struct gw_query_action {
225 unsigned int func_id;
226 const char *func_name;
227 const struct got_error *(*func_main)(struct gw_trans *);
228 char *template;
229 };
231 enum gw_query_actions {
232 GW_BLAME,
233 GW_BLOB,
234 GW_BRIEFS,
235 GW_COMMITS,
236 GW_DIFF,
237 GW_ERR,
238 GW_INDEX,
239 GW_SUMMARY,
240 GW_TAG,
241 GW_TREE,
242 };
244 static struct gw_query_action gw_query_funcs[] = {
245 { GW_BLAME, "blame", gw_blame, "gw_tmpl/blame.tmpl" },
246 { GW_BLOB, "blob", NULL, NULL },
247 { GW_BRIEFS, "briefs", gw_briefs, "gw_tmpl/briefs.tmpl" },
248 { GW_COMMITS, "commits", gw_commits, "gw_tmpl/commit.tmpl" },
249 { GW_DIFF, "diff", gw_diff, "gw_tmpl/diff.tmpl" },
250 { GW_ERR, NULL, NULL, "gw_tmpl/err.tmpl" },
251 { GW_INDEX, "index", gw_index, "gw_tmpl/index.tmpl" },
252 { GW_SUMMARY, "summary", gw_summary, "gw_tmpl/summry.tmpl" },
253 { GW_TAG, "tag", gw_tag, "gw_tmpl/tag.tmpl" },
254 { GW_TREE, "tree", gw_tree, "gw_tmpl/tree.tmpl" },
255 };
257 static const struct got_error *
258 gw_kcgi_error(enum kcgi_err kerr)
260 if (kerr == KCGI_OK)
261 return NULL;
263 if (kerr == KCGI_EXIT || kerr == KCGI_HUP)
264 return got_error(GOT_ERR_CANCELLED);
266 if (kerr == KCGI_ENOMEM)
267 return got_error_set_errno(ENOMEM, kcgi_strerror(kerr));
269 if (kerr == KCGI_ENFILE)
270 return got_error_set_errno(ENFILE, kcgi_strerror(kerr));
272 if (kerr == KCGI_EAGAIN)
273 return got_error_set_errno(EAGAIN, kcgi_strerror(kerr));
275 if (kerr == KCGI_FORM)
276 return got_error_msg(GOT_ERR_IO, kcgi_strerror(kerr));
278 return got_error_from_errno(kcgi_strerror(kerr));
281 static const struct got_error *
282 gw_apply_unveil(const char *repo_path, const char *repo_file)
284 const struct got_error *err;
286 if (repo_path && repo_file) {
287 char *full_path;
288 if (asprintf(&full_path, "%s/%s", repo_path, repo_file) == -1)
289 return got_error_from_errno("asprintf unveil");
290 if (unveil(full_path, "r") != 0)
291 return got_error_from_errno2("unveil", full_path);
294 if (repo_path && unveil(repo_path, "r") != 0)
295 return got_error_from_errno2("unveil", repo_path);
297 if (unveil("/tmp", "rwc") != 0)
298 return got_error_from_errno2("unveil", "/tmp");
300 err = got_privsep_unveil_exec_helpers();
301 if (err != NULL)
302 return err;
304 if (unveil(NULL, NULL) != 0)
305 return got_error_from_errno("unveil");
307 return NULL;
310 static const struct got_error *
311 gw_empty_string(char **s)
313 *s = strdup("");
314 if (*s == NULL)
315 return got_error_from_errno("strdup");
316 return NULL;
319 static int
320 isbinary(const char *buf, size_t n)
322 return (memchr(buf, '\0', n) != NULL);
326 static const struct got_error *
327 gw_blame(struct gw_trans *gw_trans)
329 const struct got_error *error = NULL;
330 struct gw_header *header = NULL;
331 char *blame = NULL, *blame_html = NULL, *blame_html_disp = NULL;
332 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
333 enum kcgi_err kerr;
335 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
336 NULL) == -1)
337 return got_error_from_errno("pledge");
339 if ((header = gw_init_header()) == NULL)
340 return got_error_from_errno("malloc");
342 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
343 if (error)
344 goto done;
346 error = gw_get_header(gw_trans, header, 1);
347 if (error)
348 goto done;
350 error = gw_get_file_blame_blob(&blame_html, gw_trans);
351 if (error)
352 goto done;
354 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
355 if (error)
356 goto done;
357 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
358 error = got_error_from_errno("asprintf");
359 goto done;
362 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
363 if (error)
364 goto done;
365 if (asprintf(&blame_html_disp, blame_header, age_html,
366 gw_gen_commit_msg_header(escaped_commit_msg), blame_html) == -1) {
367 error = got_error_from_errno("asprintf");
368 goto done;
371 if (asprintf(&blame, blame_wrapper, blame_html_disp) == -1) {
372 error = got_error_from_errno("asprintf");
373 goto done;
376 kerr = khttp_puts(gw_trans->gw_req, blame);
377 if (kerr != KCGI_OK)
378 error = gw_kcgi_error(kerr);
379 done:
380 got_ref_list_free(&header->refs);
381 gw_free_headers(header);
382 free(blame_html_disp);
383 free(blame_html);
384 free(blame);
385 free(escaped_commit_msg);
386 return error;
389 static const struct got_error *
390 gw_blob(struct gw_trans *gw_trans)
392 const struct got_error *error = NULL;
393 struct gw_header *header = NULL;
394 char *content = NULL;
395 size_t filesize = 0;
396 enum kcgi_err kerr;
398 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
399 NULL) == -1)
400 return got_error_from_errno("pledge");
402 if ((header = gw_init_header()) == NULL)
403 return got_error_from_errno("malloc");
405 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
406 if (error)
407 goto done;
409 error = gw_get_header(gw_trans, header, 1);
410 if (error)
411 goto done;
413 error = gw_get_file_read_blob(&content, &filesize, gw_trans);
414 if (error)
415 goto done;
417 if (isbinary(content, filesize))
418 gw_trans->mime = KMIME_APP_OCTET_STREAM;
419 else
420 gw_trans->mime = KMIME_TEXT_PLAIN;
422 error = gw_display_index(gw_trans);
423 if (error)
424 goto done;
426 kerr = khttp_write(gw_trans->gw_req, content, filesize);
427 if (kerr != KCGI_OK)
428 error = gw_kcgi_error(kerr);
429 done:
430 got_ref_list_free(&header->refs);
431 gw_free_headers(header);
432 free(content);
433 return error;
436 static const struct got_error *
437 gw_diff(struct gw_trans *gw_trans)
439 const struct got_error *error = NULL;
440 struct gw_header *header = NULL;
441 char *diff = NULL, *diff_html = NULL, *diff_html_disp = NULL;
442 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
443 enum kcgi_err kerr;
445 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
446 NULL) == -1)
447 return got_error_from_errno("pledge");
449 if ((header = gw_init_header()) == NULL)
450 return got_error_from_errno("malloc");
452 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
453 if (error)
454 goto done;
456 error = gw_get_header(gw_trans, header, 1);
457 if (error)
458 goto done;
460 error = gw_get_diff(&diff_html, gw_trans, header);
461 if (error)
462 goto done;
464 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
465 if (error)
466 goto done;
467 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
468 error = got_error_from_errno("asprintf");
469 goto done;
471 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
472 if (error)
473 goto done;
474 if (asprintf(&diff_html_disp, diff_header,
475 gw_gen_diff_header(header->parent_id, header->commit_id),
476 gw_gen_commit_header(header->commit_id, header->refs_str),
477 gw_gen_tree_header(header->tree_id),
478 gw_gen_author_header(header->author),
479 gw_gen_committer_header(header->committer), age_html,
480 gw_gen_commit_msg_header(escaped_commit_msg),
481 diff_html ? diff_html : "") == -1) {
482 error = got_error_from_errno("asprintf");
483 goto done;
486 if (asprintf(&diff, diff_wrapper, diff_html_disp) == -1) {
487 error = got_error_from_errno("asprintf");
488 goto done;
491 kerr = khttp_puts(gw_trans->gw_req, diff);
492 if (kerr != KCGI_OK)
493 error = gw_kcgi_error(kerr);
494 done:
495 got_ref_list_free(&header->refs);
496 gw_free_headers(header);
497 free(diff_html_disp);
498 free(diff_html);
499 free(diff);
500 free(age);
501 free(age_html);
502 free(escaped_commit_msg);
503 return error;
506 static const struct got_error *
507 gw_index(struct gw_trans *gw_trans)
509 const struct got_error *error = NULL;
510 struct gw_dir *gw_dir = NULL;
511 char *html, *navs, *next, *prev;
512 unsigned int prev_disp = 0, next_disp = 1, dir_c = 0;
513 enum kcgi_err kerr;
515 if (pledge("stdio rpath proc exec sendfd unveil",
516 NULL) == -1) {
517 error = got_error_from_errno("pledge");
518 return error;
521 error = gw_apply_unveil(gw_trans->gw_conf->got_repos_path, NULL);
522 if (error)
523 return error;
525 error = gw_load_got_paths(gw_trans);
526 if (error)
527 return error;
529 kerr = khttp_puts(gw_trans->gw_req, index_projects_header);
530 if (kerr != KCGI_OK)
531 return gw_kcgi_error(kerr);
533 if (TAILQ_EMPTY(&gw_trans->gw_dirs)) {
534 if (asprintf(&html, index_projects_empty,
535 gw_trans->gw_conf->got_repos_path) == -1)
536 return got_error_from_errno("asprintf");
537 kerr = khttp_puts(gw_trans->gw_req, html);
538 if (kerr != KCGI_OK)
539 error = gw_kcgi_error(kerr);
540 free(html);
541 return error;
544 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry)
545 dir_c++;
547 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry) {
548 if (gw_trans->page > 0 && (gw_trans->page *
549 gw_trans->gw_conf->got_max_repos_display) > prev_disp) {
550 prev_disp++;
551 continue;
554 prev_disp++;
556 if (error)
557 return error;
558 if(asprintf(&navs, index_navs, gw_dir->name, gw_dir->name,
559 gw_dir->name, gw_dir->name) == -1)
560 return got_error_from_errno("asprintf");
562 if (asprintf(&html, index_projects, gw_dir->name, gw_dir->name,
563 gw_dir->description, gw_dir->owner ? gw_dir->owner : "",
564 gw_dir->age,
565 navs) == -1)
566 return got_error_from_errno("asprintf");
568 kerr = khttp_puts(gw_trans->gw_req, html);
569 free(navs);
570 free(html);
571 if (kerr != KCGI_OK)
572 return gw_kcgi_error(kerr);
574 if (gw_trans->gw_conf->got_max_repos_display == 0)
575 continue;
577 if (next_disp == gw_trans->gw_conf->got_max_repos_display) {
578 kerr = khttp_puts(gw_trans->gw_req, np_wrapper_start);
579 if (kerr != KCGI_OK)
580 return gw_kcgi_error(kerr);
581 } else if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
582 (gw_trans->page > 0) &&
583 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
584 prev_disp == gw_trans->repos_total)) {
585 kerr = khttp_puts(gw_trans->gw_req, np_wrapper_start);
586 if (kerr != KCGI_OK)
587 return gw_kcgi_error(kerr);
590 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
591 (gw_trans->page > 0) &&
592 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
593 prev_disp == gw_trans->repos_total)) {
594 if (asprintf(&prev, nav_prev, gw_trans->page - 1) == -1)
595 return got_error_from_errno("asprintf");
596 kerr = khttp_puts(gw_trans->gw_req, prev);
597 free(prev);
598 if (kerr != KCGI_OK)
599 return gw_kcgi_error(kerr);
602 kerr = khttp_puts(gw_trans->gw_req, div_end);
603 if (kerr != KCGI_OK)
604 return gw_kcgi_error(kerr);
606 if (gw_trans->gw_conf->got_max_repos_display > 0 &&
607 next_disp == gw_trans->gw_conf->got_max_repos_display &&
608 dir_c != (gw_trans->page + 1) *
609 gw_trans->gw_conf->got_max_repos_display) {
610 if (asprintf(&next, nav_next, gw_trans->page + 1) == -1)
611 return got_error_from_errno("calloc");
612 kerr = khttp_puts(gw_trans->gw_req, next);
613 free(next);
614 if (kerr != KCGI_OK)
615 return gw_kcgi_error(kerr);
616 kerr = khttp_puts(gw_trans->gw_req, div_end);
617 if (kerr != KCGI_OK)
618 error = gw_kcgi_error(kerr);
619 next_disp = 0;
620 break;
623 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
624 (gw_trans->page > 0) &&
625 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
626 prev_disp == gw_trans->repos_total)) {
627 kerr = khttp_puts(gw_trans->gw_req, div_end);
628 if (kerr != KCGI_OK)
629 return gw_kcgi_error(kerr);
632 next_disp++;
634 return error;
637 static const struct got_error *
638 gw_commits(struct gw_trans *gw_trans)
640 const struct got_error *error = NULL;
641 char *commits_html, *commits_navs_html;
642 struct gw_header *header = NULL, *n_header = NULL;
643 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
644 enum kcgi_err kerr;
646 if ((header = gw_init_header()) == NULL)
647 return got_error_from_errno("malloc");
649 if (pledge("stdio rpath proc exec sendfd unveil",
650 NULL) == -1) {
651 error = got_error_from_errno("pledge");
652 goto done;
655 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
656 if (error)
657 goto done;
659 error = gw_get_header(gw_trans, header,
660 gw_trans->gw_conf->got_max_commits_display);
661 if (error)
662 goto done;
664 kerr = khttp_puts(gw_trans->gw_req, commits_wrapper);
665 if (kerr != KCGI_OK) {
666 error = gw_kcgi_error(kerr);
667 goto done;
669 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
670 if (asprintf(&commits_navs_html, commits_navs,
671 gw_trans->repo_name, n_header->commit_id,
672 gw_trans->repo_name, n_header->commit_id,
673 gw_trans->repo_name, n_header->commit_id) == -1) {
674 error = got_error_from_errno("asprintf");
675 goto done;
677 error = gw_get_time_str(&age, n_header->committer_time,
678 TM_LONG);
679 if (error)
680 goto done;
681 if (asprintf(&age_html, header_age_html, age ? age : "")
682 == -1) {
683 error = got_error_from_errno("asprintf");
684 goto done;
686 error = gw_html_escape(&escaped_commit_msg,
687 n_header->commit_msg);
688 if (error)
689 goto done;
690 if (asprintf(&commits_html, commits_line,
691 gw_gen_commit_header(n_header->commit_id,
692 n_header->refs_str),
693 gw_gen_author_header(n_header->author),
694 gw_gen_committer_header(n_header->committer),
695 age_html, escaped_commit_msg,
696 commits_navs_html) == -1) {
697 error = got_error_from_errno("asprintf");
698 goto done;
700 free(age);
701 age = NULL;
702 free(age_html);
703 age_html = NULL;
704 free(escaped_commit_msg);
705 escaped_commit_msg = NULL;
706 kerr = khttp_puts(gw_trans->gw_req, commits_html);
707 if (kerr != KCGI_OK) {
708 error = gw_kcgi_error(kerr);
709 goto done;
712 kerr = khttp_puts(gw_trans->gw_req, div_end);
713 if (kerr != KCGI_OK)
714 error = gw_kcgi_error(kerr);
715 done:
716 got_ref_list_free(&header->refs);
717 gw_free_headers(header);
718 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
719 gw_free_headers(n_header);
720 free(age);
721 free(age_html);
722 free(escaped_commit_msg);
723 return error;
726 static const struct got_error *
727 gw_briefs(struct gw_trans *gw_trans)
729 const struct got_error *error = NULL;
730 struct gw_header *header = NULL, *n_header = NULL;
731 char *age = NULL, *age_html = NULL;
732 char *href_diff = NULL, *href_tree = NULL;
733 char *newline, *smallerthan;
734 enum kcgi_err kerr = KCGI_OK;
736 if ((header = gw_init_header()) == NULL)
737 return got_error_from_errno("malloc");
739 if (pledge("stdio rpath proc exec sendfd unveil",
740 NULL) == -1) {
741 error = got_error_from_errno("pledge");
742 goto done;
745 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
746 if (error)
747 goto done;
749 if (gw_trans->action == GW_SUMMARY)
750 error = gw_get_header(gw_trans, header, D_MAXSLCOMMDISP);
751 else
752 error = gw_get_header(gw_trans, header,
753 gw_trans->gw_conf->got_max_commits_display);
754 if (error)
755 goto done;
757 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
758 error = gw_get_time_str(&age, n_header->committer_time,
759 TM_DIFF);
760 if (error)
761 goto done;
763 /* briefs wrapper */
764 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
765 KATTR_ID, "briefs_wrapper", KATTR__MAX);
766 if (kerr != KCGI_OK)
767 goto done;
769 /* briefs age */
770 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
771 KATTR_ID, "briefs_age", KATTR__MAX);
772 if (kerr != KCGI_OK)
773 goto done;
774 if (asprintf(&age_html, "%s", age ? age : "") == -1) {
775 error = got_error_from_errno("asprintf");
776 goto done;
778 kerr = khtml_puts(gw_trans->gw_html_req, age_html);
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 /* briefs author */
786 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
787 KATTR_ID, "briefs_author", KATTR__MAX);
788 if (kerr != KCGI_OK)
789 goto done;
790 smallerthan = strchr(n_header->author, '<');
791 if (smallerthan)
792 *smallerthan = '\0';
793 kerr = khtml_puts(gw_trans->gw_html_req, n_header->author);
794 if (kerr != KCGI_OK)
795 goto done;
796 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
797 if (kerr != KCGI_OK)
798 goto done;
800 /* briefs log */
801 if (asprintf(&href_diff, "?path=%s&action=diff&commit=%s",
802 gw_trans->repo_name, n_header->commit_id) == -1) {
803 error = got_error_from_errno("asprintf");
804 goto done;
806 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
807 KATTR_ID, "briefs_log", KATTR__MAX);
808 if (kerr != KCGI_OK)
809 goto done;
810 newline = strchr(n_header->commit_msg, '\n');
811 if (newline)
812 *newline = '\0';
813 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
814 KATTR_HREF, href_diff, KATTR__MAX);
815 if (kerr != KCGI_OK)
816 goto done;
817 kerr = khtml_puts(gw_trans->gw_html_req, n_header->commit_msg);
818 if (kerr != KCGI_OK)
819 goto done;
820 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
821 if (kerr != KCGI_OK)
822 goto done;
824 /* build diff nav */
825 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
826 KATTR_ID, "navs_wrapper", KATTR__MAX);
827 if (kerr != KCGI_OK)
828 goto done;
829 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
830 KATTR_ID, "navs", KATTR__MAX);
831 if (kerr != KCGI_OK)
832 goto done;
833 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
834 KATTR_HREF, href_diff, KATTR__MAX);
835 if (kerr != KCGI_OK)
836 goto done;
837 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
838 if (kerr != KCGI_OK)
839 goto done;
840 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
841 if (kerr != KCGI_OK)
842 goto done;
844 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
845 if (kerr != KCGI_OK)
846 goto done;
848 /* build tree nav */
849 if (asprintf(&href_tree, "?path=%s&action=tree&commit=%s",
850 gw_trans->repo_name, n_header->commit_id) == -1) {
851 error = got_error_from_errno("asprintf");
852 goto done;
854 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
855 KATTR_HREF, href_tree, KATTR__MAX);
856 if (kerr != KCGI_OK)
857 goto done;
858 khtml_puts(gw_trans->gw_html_req, "tree");
859 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
860 if (kerr != KCGI_OK)
861 goto done;
862 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
863 if (kerr != KCGI_OK)
864 goto done;
866 /* dotted line */
867 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
868 KATTR_ID, "dotted_line", KATTR__MAX);
869 if (kerr != KCGI_OK)
870 goto done;
871 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
872 if (kerr != KCGI_OK)
873 goto done;
875 free(age);
876 age = NULL;
877 free(age_html);
878 age_html = NULL;
879 free(href_diff);
880 href_diff = NULL;
881 free(href_tree);
882 href_tree = NULL;
884 done:
885 got_ref_list_free(&header->refs);
886 gw_free_headers(header);
887 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
888 gw_free_headers(n_header);
889 free(age);
890 free(age_html);
891 free(href_diff);
892 free(href_tree);
893 if (error == NULL && kerr != KCGI_OK)
894 error = gw_kcgi_error(kerr);
895 return error;
898 static const struct got_error *
899 gw_summary(struct gw_trans *gw_trans)
901 const struct got_error *error = NULL;
902 char *age = NULL, *tags = NULL, *heads = NULL;
903 enum kcgi_err kerr;
905 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
906 return got_error_from_errno("pledge");
908 /* unveil is applied with gw_briefs below */
910 /* summary wrapper */
911 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
912 "summary_wrapper", KATTR__MAX);
913 if (kerr != KCGI_OK)
914 return gw_kcgi_error(kerr);
916 /* description */
917 if (gw_trans->gw_conf->got_show_repo_description &&
918 gw_trans->gw_dir->description != NULL &&
919 (strcmp(gw_trans->gw_dir->description, "") != 0)) {
920 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
921 KATTR_ID, "description_title", KATTR__MAX);
922 if (kerr != KCGI_OK) {
923 error = gw_kcgi_error(kerr);
924 goto done;
926 kerr = khtml_puts(gw_trans->gw_html_req, "Description: ");
927 if (kerr != KCGI_OK) {
928 error = gw_kcgi_error(kerr);
929 goto done;
931 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
932 if (kerr != KCGI_OK) {
933 error = gw_kcgi_error(kerr);
934 goto done;
936 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
937 KATTR_ID, "description", KATTR__MAX);
938 if (kerr != KCGI_OK) {
939 error = gw_kcgi_error(kerr);
940 goto done;
942 kerr = khtml_puts(gw_trans->gw_html_req,
943 gw_trans->gw_dir->description);
944 if (kerr != KCGI_OK) {
945 error = gw_kcgi_error(kerr);
946 goto done;
948 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
949 if (kerr != KCGI_OK) {
950 error = gw_kcgi_error(kerr);
951 goto done;
955 /* repo owner */
956 if (gw_trans->gw_conf->got_show_repo_owner &&
957 gw_trans->gw_dir->owner != NULL) {
958 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
959 KATTR_ID, "repo_owner_title", KATTR__MAX);
960 if (kerr != KCGI_OK) {
961 error = gw_kcgi_error(kerr);
962 goto done;
964 kerr = khtml_puts(gw_trans->gw_html_req, "Owner: ");
965 if (kerr != KCGI_OK) {
966 error = gw_kcgi_error(kerr);
967 goto done;
969 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
970 if (kerr != KCGI_OK) {
971 error = gw_kcgi_error(kerr);
972 goto done;
974 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
975 KATTR_ID, "repo_owner", KATTR__MAX);
976 if (kerr != KCGI_OK) {
977 error = gw_kcgi_error(kerr);
978 goto done;
980 kerr = khtml_puts(gw_trans->gw_html_req,
981 gw_trans->gw_dir->owner);
982 if (kerr != KCGI_OK) {
983 error = gw_kcgi_error(kerr);
984 goto done;
986 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
987 if (kerr != KCGI_OK) {
988 error = gw_kcgi_error(kerr);
989 goto done;
993 /* last change */
994 if (gw_trans->gw_conf->got_show_repo_age) {
995 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
996 "refs/heads", TM_LONG);
997 if (error)
998 goto done;
999 if (age != NULL) {
1000 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1001 KATTR_ID, "last_change_title", KATTR__MAX);
1002 if (kerr != KCGI_OK) {
1003 error = gw_kcgi_error(kerr);
1004 goto done;
1006 kerr = khtml_puts(gw_trans->gw_html_req,
1007 "Last Change: ");
1008 if (kerr != KCGI_OK) {
1009 error = gw_kcgi_error(kerr);
1010 goto done;
1012 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1013 if (kerr != KCGI_OK) {
1014 error = gw_kcgi_error(kerr);
1015 goto done;
1017 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1018 KATTR_ID, "last_change", KATTR__MAX);
1019 if (kerr != KCGI_OK) {
1020 error = gw_kcgi_error(kerr);
1021 goto done;
1023 kerr = khtml_puts(gw_trans->gw_html_req, age);
1024 if (kerr != KCGI_OK) {
1025 error = gw_kcgi_error(kerr);
1026 goto done;
1028 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1029 if (kerr != KCGI_OK) {
1030 error = gw_kcgi_error(kerr);
1031 goto done;
1036 /* cloneurl */
1037 if (gw_trans->gw_conf->got_show_repo_cloneurl &&
1038 gw_trans->gw_dir->url != NULL &&
1039 (strcmp(gw_trans->gw_dir->url, "") != 0)) {
1040 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1041 KATTR_ID, "cloneurl_title", KATTR__MAX);
1042 if (kerr != KCGI_OK) {
1043 error = gw_kcgi_error(kerr);
1044 goto done;
1046 kerr = khtml_puts(gw_trans->gw_html_req, "Clone URL: ");
1047 if (kerr != KCGI_OK) {
1048 error = gw_kcgi_error(kerr);
1049 goto done;
1051 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1052 if (kerr != KCGI_OK) {
1053 error = gw_kcgi_error(kerr);
1054 goto done;
1056 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1057 KATTR_ID, "cloneurl", KATTR__MAX);
1058 if (kerr != KCGI_OK) {
1059 error = gw_kcgi_error(kerr);
1060 goto done;
1062 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->gw_dir->url);
1063 if (kerr != KCGI_OK) {
1064 error = gw_kcgi_error(kerr);
1065 goto done;
1067 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1068 if (kerr != KCGI_OK) {
1069 error = gw_kcgi_error(kerr);
1070 goto done;
1074 /* close summary wrapper */
1075 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1076 if (kerr != KCGI_OK) {
1077 error = gw_kcgi_error(kerr);
1078 goto done;
1081 /* commit briefs header */
1082 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1083 "briefs_title_wrapper", KATTR__MAX);
1084 if (kerr != KCGI_OK) {
1085 error = gw_kcgi_error(kerr);
1086 goto done;
1088 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1089 "briefs_title", KATTR__MAX);
1090 if (kerr != KCGI_OK) {
1091 error = gw_kcgi_error(kerr);
1092 goto done;
1094 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Briefs");
1095 if (kerr != KCGI_OK) {
1096 error = gw_kcgi_error(kerr);
1097 goto done;
1099 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1100 if (kerr != KCGI_OK) {
1101 error = gw_kcgi_error(kerr);
1102 goto done;
1104 error = gw_briefs(gw_trans);
1105 if (error)
1106 goto done;
1108 /* tags */
1109 error = gw_get_repo_tags(&tags, gw_trans, NULL, D_MAXSLCOMMDISP,
1110 TAGBRIEF);
1111 if (error)
1112 goto done;
1114 if (tags != NULL && strcmp(tags, "") != 0) {
1115 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1116 KATTR_ID, "summary_tags_title_wrapper", KATTR__MAX);
1117 if (kerr != KCGI_OK) {
1118 error = gw_kcgi_error(kerr);
1119 goto done;
1121 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1122 KATTR_ID, "summary_tags_title", KATTR__MAX);
1123 if (kerr != KCGI_OK) {
1124 error = gw_kcgi_error(kerr);
1125 goto done;
1127 kerr = khtml_puts(gw_trans->gw_html_req, "Tags");
1128 if (kerr != KCGI_OK) {
1129 error = gw_kcgi_error(kerr);
1130 goto done;
1132 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1133 if (kerr != KCGI_OK) {
1134 error = gw_kcgi_error(kerr);
1135 goto done;
1137 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1138 KATTR_ID, "summary_tags_content", KATTR__MAX);
1139 if (kerr != KCGI_OK) {
1140 error = gw_kcgi_error(kerr);
1141 goto done;
1143 kerr = khttp_puts(gw_trans->gw_req, tags);
1144 if (kerr != KCGI_OK) {
1145 error = gw_kcgi_error(kerr);
1146 goto done;
1148 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1149 if (kerr != KCGI_OK) {
1150 error = gw_kcgi_error(kerr);
1151 goto done;
1155 /* heads */
1156 error = gw_get_repo_heads(&heads, gw_trans);
1157 if (error)
1158 goto done;
1159 if (heads != NULL && strcmp(heads, "") != 0) {
1160 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1161 KATTR_ID, "summary_heads_title_wrapper", KATTR__MAX);
1162 if (kerr != KCGI_OK) {
1163 error = gw_kcgi_error(kerr);
1164 goto done;
1166 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1167 KATTR_ID, "summary_heads_title", KATTR__MAX);
1168 if (kerr != KCGI_OK) {
1169 error = gw_kcgi_error(kerr);
1170 goto done;
1172 kerr = khtml_puts(gw_trans->gw_html_req, "Heads");
1173 if (kerr != KCGI_OK) {
1174 error = gw_kcgi_error(kerr);
1175 goto done;
1177 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1178 if (kerr != KCGI_OK) {
1179 error = gw_kcgi_error(kerr);
1180 goto done;
1182 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1183 KATTR_ID, "summary_heads_content", KATTR__MAX);
1184 if (kerr != KCGI_OK) {
1185 error = gw_kcgi_error(kerr);
1186 goto done;
1188 kerr = khttp_puts(gw_trans->gw_req, heads);
1189 if (kerr != KCGI_OK) {
1190 error = gw_kcgi_error(kerr);
1191 goto done;
1193 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1194 if (kerr != KCGI_OK) {
1195 error = gw_kcgi_error(kerr);
1196 goto done;
1199 done:
1200 free(age);
1201 free(tags);
1202 free(heads);
1203 return error;
1206 static const struct got_error *
1207 gw_tree(struct gw_trans *gw_trans)
1209 const struct got_error *error = NULL;
1210 struct gw_header *header = NULL;
1211 char *tree = NULL, *tree_html = NULL, *tree_html_disp = NULL;
1212 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
1213 enum kcgi_err kerr;
1215 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1216 return got_error_from_errno("pledge");
1218 if ((header = gw_init_header()) == NULL)
1219 return got_error_from_errno("malloc");
1221 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1222 if (error)
1223 goto done;
1225 error = gw_get_header(gw_trans, header, 1);
1226 if (error)
1227 goto done;
1229 error = gw_get_repo_tree(&tree_html, gw_trans);
1230 if (error)
1231 goto done;
1233 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
1234 if (error)
1235 goto done;
1236 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
1237 error = got_error_from_errno("asprintf");
1238 goto done;
1240 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1241 if (error)
1242 goto done;
1243 if (asprintf(&tree_html_disp, tree_header, age_html,
1244 gw_gen_commit_msg_header(escaped_commit_msg),
1245 tree_html ? tree_html : "") == -1) {
1246 error = got_error_from_errno("asprintf");
1247 goto done;
1250 if (asprintf(&tree, tree_wrapper, tree_html_disp) == -1) {
1251 error = got_error_from_errno("asprintf");
1252 goto done;
1255 kerr = khttp_puts(gw_trans->gw_req, tree);
1256 if (kerr != KCGI_OK)
1257 error = gw_kcgi_error(kerr);
1258 done:
1259 got_ref_list_free(&header->refs);
1260 gw_free_headers(header);
1261 free(tree_html_disp);
1262 free(tree_html);
1263 free(tree);
1264 free(age);
1265 free(age_html);
1266 free(escaped_commit_msg);
1267 return error;
1270 static const struct got_error *
1271 gw_tag(struct gw_trans *gw_trans)
1273 const struct got_error *error = NULL;
1274 struct gw_header *header = NULL;
1275 char *tag = NULL, *tag_html = NULL, *tag_html_disp = NULL;
1276 char *escaped_commit_msg = NULL;
1277 enum kcgi_err kerr;
1279 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1280 return got_error_from_errno("pledge");
1282 if ((header = gw_init_header()) == NULL)
1283 return got_error_from_errno("malloc");
1285 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1286 if (error)
1287 goto done;
1289 error = gw_get_header(gw_trans, header, 1);
1290 if (error)
1291 goto done;
1293 error = gw_get_repo_tags(&tag_html, gw_trans, header, 1, TAGFULL);
1294 if (error)
1295 goto done;
1297 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1298 if (error)
1299 goto done;
1300 if (asprintf(&tag_html_disp, tag_header,
1301 gw_gen_commit_header(header->commit_id, header->refs_str),
1302 gw_gen_commit_msg_header(escaped_commit_msg),
1303 tag_html ? tag_html : "") == -1) {
1304 error = got_error_from_errno("asprintf");
1305 goto done;
1308 if (asprintf(&tag, tag_wrapper, tag_html_disp) == -1) {
1309 error = got_error_from_errno("asprintf");
1310 goto done;
1313 kerr = khttp_puts(gw_trans->gw_req, tag);
1314 if (kerr != KCGI_OK)
1315 error = gw_kcgi_error(kerr);
1316 done:
1317 got_ref_list_free(&header->refs);
1318 gw_free_headers(header);
1319 free(tag_html_disp);
1320 free(tag_html);
1321 free(tag);
1322 free(escaped_commit_msg);
1323 return error;
1326 static const struct got_error *
1327 gw_load_got_path(struct gw_trans *gw_trans, struct gw_dir *gw_dir)
1329 const struct got_error *error = NULL;
1330 DIR *dt;
1331 char *dir_test;
1332 int opened = 0;
1334 if (asprintf(&dir_test, "%s/%s/%s",
1335 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1336 GOTWEB_GIT_DIR) == -1)
1337 return got_error_from_errno("asprintf");
1339 dt = opendir(dir_test);
1340 if (dt == NULL) {
1341 free(dir_test);
1342 } else {
1343 gw_dir->path = strdup(dir_test);
1344 opened = 1;
1345 goto done;
1348 if (asprintf(&dir_test, "%s/%s/%s",
1349 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1350 GOTWEB_GOT_DIR) == -1)
1351 return got_error_from_errno("asprintf");
1353 dt = opendir(dir_test);
1354 if (dt == NULL)
1355 free(dir_test);
1356 else {
1357 opened = 1;
1358 error = got_error(GOT_ERR_NOT_GIT_REPO);
1359 goto errored;
1362 if (asprintf(&dir_test, "%s/%s",
1363 gw_trans->gw_conf->got_repos_path, gw_dir->name) == -1)
1364 return got_error_from_errno("asprintf");
1366 gw_dir->path = strdup(dir_test);
1368 done:
1369 error = gw_get_repo_description(&gw_dir->description, gw_trans,
1370 gw_dir->path);
1371 if (error)
1372 goto errored;
1373 error = gw_get_repo_owner(&gw_dir->owner, gw_trans, gw_dir->path);
1374 if (error)
1375 goto errored;
1376 error = gw_get_repo_age(&gw_dir->age, gw_trans, gw_dir->path,
1377 "refs/heads", TM_DIFF);
1378 if (error)
1379 goto errored;
1380 error = gw_get_clone_url(&gw_dir->url, gw_trans, gw_dir->path);
1381 errored:
1382 free(dir_test);
1383 if (opened)
1384 closedir(dt);
1385 return error;
1388 static const struct got_error *
1389 gw_load_got_paths(struct gw_trans *gw_trans)
1391 const struct got_error *error = NULL;
1392 DIR *d;
1393 struct dirent **sd_dent;
1394 struct gw_dir *gw_dir;
1395 struct stat st;
1396 unsigned int d_cnt, d_i;
1398 d = opendir(gw_trans->gw_conf->got_repos_path);
1399 if (d == NULL) {
1400 error = got_error_from_errno2("opendir",
1401 gw_trans->gw_conf->got_repos_path);
1402 return error;
1405 d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
1406 alphasort);
1407 if (d_cnt == -1) {
1408 error = got_error_from_errno2("scandir",
1409 gw_trans->gw_conf->got_repos_path);
1410 return error;
1413 for (d_i = 0; d_i < d_cnt; d_i++) {
1414 if (gw_trans->gw_conf->got_max_repos > 0 &&
1415 (d_i - 2) == gw_trans->gw_conf->got_max_repos)
1416 break; /* account for parent and self */
1418 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1419 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1420 continue;
1422 if ((gw_dir = gw_init_gw_dir(sd_dent[d_i]->d_name)) == NULL)
1423 return got_error_from_errno("gw_dir malloc");
1425 error = gw_load_got_path(gw_trans, gw_dir);
1426 if (error && error->code == GOT_ERR_NOT_GIT_REPO)
1427 continue;
1428 else if (error)
1429 return error;
1431 if (lstat(gw_dir->path, &st) == 0 && S_ISDIR(st.st_mode) &&
1432 !got_path_dir_is_empty(gw_dir->path)) {
1433 TAILQ_INSERT_TAIL(&gw_trans->gw_dirs, gw_dir,
1434 entry);
1435 gw_trans->repos_total++;
1439 closedir(d);
1440 return error;
1443 static const struct got_error *
1444 gw_parse_querystring(struct gw_trans *gw_trans)
1446 const struct got_error *error = NULL;
1447 struct kpair *p;
1448 struct gw_query_action *action = NULL;
1449 unsigned int i;
1451 if (gw_trans->gw_req->fieldnmap[0]) {
1452 error = got_error_from_errno("bad parse");
1453 return error;
1454 } else if ((p = gw_trans->gw_req->fieldmap[KEY_PATH])) {
1455 /* define gw_trans->repo_path */
1456 if (asprintf(&gw_trans->repo_name, "%s", p->parsed.s) == -1)
1457 return got_error_from_errno("asprintf");
1459 if (asprintf(&gw_trans->repo_path, "%s/%s",
1460 gw_trans->gw_conf->got_repos_path, p->parsed.s) == -1)
1461 return got_error_from_errno("asprintf");
1463 /* get action and set function */
1464 if ((p = gw_trans->gw_req->fieldmap[KEY_ACTION]))
1465 for (i = 0; i < nitems(gw_query_funcs); i++) {
1466 action = &gw_query_funcs[i];
1467 if (action->func_name == NULL)
1468 continue;
1470 if (strcmp(action->func_name,
1471 p->parsed.s) == 0) {
1472 gw_trans->action = i;
1473 if (asprintf(&gw_trans->action_name,
1474 "%s", action->func_name) == -1)
1475 return
1476 got_error_from_errno(
1477 "asprintf");
1479 break;
1482 action = NULL;
1485 if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
1486 if (asprintf(&gw_trans->commit, "%s",
1487 p->parsed.s) == -1)
1488 return got_error_from_errno("asprintf");
1490 if ((p = gw_trans->gw_req->fieldmap[KEY_FILE]))
1491 if (asprintf(&gw_trans->repo_file, "%s",
1492 p->parsed.s) == -1)
1493 return got_error_from_errno("asprintf");
1495 if ((p = gw_trans->gw_req->fieldmap[KEY_FOLDER]))
1496 if (asprintf(&gw_trans->repo_folder, "%s",
1497 p->parsed.s) == -1)
1498 return got_error_from_errno("asprintf");
1500 if ((p = gw_trans->gw_req->fieldmap[KEY_HEADREF]))
1501 if (asprintf(&gw_trans->headref, "%s",
1502 p->parsed.s) == -1)
1503 return got_error_from_errno("asprintf");
1505 if (action == NULL) {
1506 error = got_error_from_errno("invalid action");
1507 return error;
1509 if ((gw_trans->gw_dir =
1510 gw_init_gw_dir(gw_trans->repo_name)) == NULL)
1511 return got_error_from_errno("gw_dir malloc");
1513 error = gw_load_got_path(gw_trans, gw_trans->gw_dir);
1514 if (error)
1515 return error;
1516 } else
1517 gw_trans->action = GW_INDEX;
1519 if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
1520 gw_trans->page = p->parsed.i;
1522 return error;
1525 static struct gw_dir *
1526 gw_init_gw_dir(char *dir)
1528 struct gw_dir *gw_dir;
1530 if ((gw_dir = malloc(sizeof(*gw_dir))) == NULL)
1531 return NULL;
1533 if (asprintf(&gw_dir->name, "%s", dir) == -1)
1534 return NULL;
1536 return gw_dir;
1539 static const struct got_error *
1540 gw_display_open(struct gw_trans *gw_trans, enum khttp code, enum kmime mime)
1542 enum kcgi_err kerr;
1544 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_ALLOW], "GET");
1545 if (kerr != KCGI_OK)
1546 return gw_kcgi_error(kerr);
1547 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_STATUS], "%s",
1548 khttps[code]);
1549 if (kerr != KCGI_OK)
1550 return gw_kcgi_error(kerr);
1551 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_CONTENT_TYPE], "%s",
1552 kmimetypes[mime]);
1553 if (kerr != KCGI_OK)
1554 return gw_kcgi_error(kerr);
1555 kerr = khttp_head(gw_trans->gw_req, "X-Content-Type-Options",
1556 "nosniff");
1557 if (kerr != KCGI_OK)
1558 return gw_kcgi_error(kerr);
1559 kerr = khttp_head(gw_trans->gw_req, "X-Frame-Options", "DENY");
1560 if (kerr != KCGI_OK)
1561 return gw_kcgi_error(kerr);
1562 kerr = khttp_head(gw_trans->gw_req, "X-XSS-Protection",
1563 "1; mode=block");
1564 if (kerr != KCGI_OK)
1565 return gw_kcgi_error(kerr);
1567 if (gw_trans->mime == KMIME_APP_OCTET_STREAM) {
1568 kerr = khttp_head(gw_trans->gw_req,
1569 kresps[KRESP_CONTENT_DISPOSITION],
1570 "attachment; filename=%s", gw_trans->repo_file);
1571 if (kerr != KCGI_OK)
1572 return gw_kcgi_error(kerr);
1575 kerr = khttp_body(gw_trans->gw_req);
1576 return gw_kcgi_error(kerr);
1579 static const struct got_error *
1580 gw_display_index(struct gw_trans *gw_trans)
1582 const struct got_error *error;
1583 enum kcgi_err kerr;
1585 error = gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
1586 if (error)
1587 return error;
1589 kerr = khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, KHTML_PRETTY);
1590 if (kerr)
1591 return gw_kcgi_error(kerr);
1593 if (gw_trans->action != GW_BLOB) {
1594 kerr = khttp_template(gw_trans->gw_req, gw_trans->gw_tmpl,
1595 gw_query_funcs[gw_trans->action].template);
1596 if (kerr != KCGI_OK) {
1597 khtml_close(gw_trans->gw_html_req);
1598 return gw_kcgi_error(kerr);
1602 return gw_kcgi_error(khtml_close(gw_trans->gw_html_req));
1605 static void
1606 gw_display_error(struct gw_trans *gw_trans, const struct got_error *err)
1608 if (gw_display_open(gw_trans, KHTTP_200, gw_trans->mime) != NULL)
1609 return;
1611 if (khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0) != KCGI_OK)
1612 return;
1613 khtml_puts(gw_trans->gw_html_req, err->msg);
1614 khtml_close(gw_trans->gw_html_req);
1617 static int
1618 gw_template(size_t key, void *arg)
1620 const struct got_error *error = NULL;
1621 enum kcgi_err kerr;
1622 struct gw_trans *gw_trans = arg;
1623 char *gw_site_link, *img_src = NULL;
1625 switch (key) {
1626 case (TEMPL_HEAD):
1627 kerr = khttp_puts(gw_trans->gw_req, head);
1628 if (kerr != KCGI_OK)
1629 return 0;
1630 break;
1631 case(TEMPL_HEADER):
1632 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1633 KATTR_ID, "got_link", KATTR__MAX);
1634 if (kerr != KCGI_OK)
1635 return 0;
1636 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1637 KATTR_HREF, gw_trans->gw_conf->got_logo_url,
1638 KATTR_TARGET, "_sotd", KATTR__MAX);
1639 if (kerr != KCGI_OK)
1640 return 0;
1641 if (asprintf(&img_src, "/%s",
1642 gw_trans->gw_conf->got_logo) == -1)
1643 return 0;
1644 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_IMG,
1645 KATTR_SRC, img_src, KATTR__MAX);
1646 if (kerr != KCGI_OK) {
1647 free(img_src);
1648 return 0;
1650 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1651 if (kerr != KCGI_OK) {
1652 free(img_src);
1653 return 0;
1655 break;
1656 case (TEMPL_SITEPATH):
1657 gw_site_link = gw_get_site_link(gw_trans);
1658 if (gw_site_link != NULL) {
1659 kerr = khttp_puts(gw_trans->gw_req, gw_site_link);
1660 if (kerr != KCGI_OK) {
1661 free(gw_site_link);
1662 return 0;
1665 free(gw_site_link);
1666 break;
1667 case(TEMPL_TITLE):
1668 if (gw_trans->gw_conf->got_site_name != NULL) {
1669 kerr = khtml_puts(gw_trans->gw_html_req,
1670 gw_trans->gw_conf->got_site_name);
1671 if (kerr != KCGI_OK)
1672 return 0;
1674 break;
1675 case (TEMPL_SEARCH):
1676 kerr = khttp_puts(gw_trans->gw_req, search);
1677 if (kerr != KCGI_OK)
1678 return 0;
1679 break;
1680 case(TEMPL_SITEOWNER):
1681 if (gw_trans->gw_conf->got_site_owner != NULL &&
1682 gw_trans->gw_conf->got_show_site_owner) {
1683 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1684 KATTR_ID, "site_owner_wrapper", KATTR__MAX);
1685 if (kerr != KCGI_OK)
1686 return 0;
1687 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1688 KATTR_ID, "site_owner", KATTR__MAX);
1689 if (kerr != KCGI_OK)
1690 return 0;
1691 kerr = khtml_puts(gw_trans->gw_html_req,
1692 gw_trans->gw_conf->got_site_owner);
1693 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1694 if (kerr != KCGI_OK)
1695 return 0;
1697 break;
1698 case(TEMPL_CONTENT):
1699 error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
1700 if (error) {
1701 kerr = khttp_puts(gw_trans->gw_req, error->msg);
1702 if (kerr != KCGI_OK)
1703 return 0;
1705 break;
1706 default:
1707 return 0;
1709 return 1;
1712 static char *
1713 gw_gen_commit_header(char *str1, char *str2)
1715 char *return_html = NULL, *ref_str = NULL;
1717 if (strcmp(str2, "") != 0) {
1718 if (asprintf(&ref_str, "(%s)", str2) == -1) {
1719 return_html = strdup("");
1720 return return_html;
1722 } else
1723 ref_str = strdup("");
1726 if (asprintf(&return_html, header_commit_html, str1, ref_str) == -1)
1727 return_html = strdup("");
1729 free(ref_str);
1730 return return_html;
1733 static char *
1734 gw_gen_diff_header(char *str1, char *str2)
1736 char *return_html = NULL;
1738 if (asprintf(&return_html, header_diff_html, str1, str2) == -1)
1739 return_html = strdup("");
1741 return return_html;
1744 static char *
1745 gw_gen_author_header(const char *str)
1747 char *return_html = NULL;
1749 if (asprintf(&return_html, header_author_html, str) == -1)
1750 return_html = strdup("");
1752 return return_html;
1755 static char *
1756 gw_gen_committer_header(char *str)
1758 char *return_html = NULL;
1760 if (asprintf(&return_html, header_committer_html, str) == -1)
1761 return_html = strdup("");
1763 return return_html;
1766 static char *
1767 gw_gen_commit_msg_header(char *str)
1769 char *return_html = NULL;
1771 if (asprintf(&return_html, header_commit_msg_html, str) == -1)
1772 return_html = strdup("");
1774 return return_html;
1777 static char *
1778 gw_gen_tree_header(char *str)
1780 char *return_html = NULL;
1782 if (asprintf(&return_html, header_tree_html, str) == -1)
1783 return_html = strdup("");
1785 return return_html;
1788 static const struct got_error *
1789 gw_get_repo_description(char **description, struct gw_trans *gw_trans,
1790 char *dir)
1792 const struct got_error *error = NULL;
1793 FILE *f = NULL;
1794 char *d_file = NULL;
1795 unsigned int len;
1796 size_t n;
1798 *description = NULL;
1799 if (gw_trans->gw_conf->got_show_repo_description == 0)
1800 return gw_empty_string(description);
1802 if (asprintf(&d_file, "%s/description", dir) == -1)
1803 return got_error_from_errno("asprintf");
1805 f = fopen(d_file, "r");
1806 if (f == NULL) {
1807 if (errno == ENOENT || errno == EACCES)
1808 return gw_empty_string(description);
1809 error = got_error_from_errno2("fopen", d_file);
1810 goto done;
1813 if (fseek(f, 0, SEEK_END) == -1) {
1814 error = got_ferror(f, GOT_ERR_IO);
1815 goto done;
1817 len = ftell(f);
1818 if (len == -1) {
1819 error = got_ferror(f, GOT_ERR_IO);
1820 goto done;
1822 if (fseek(f, 0, SEEK_SET) == -1) {
1823 error = got_ferror(f, GOT_ERR_IO);
1824 goto done;
1826 *description = calloc(len + 1, sizeof(**description));
1827 if (*description == NULL) {
1828 error = got_error_from_errno("calloc");
1829 goto done;
1832 n = fread(*description, 1, len, f);
1833 if (n == 0 && ferror(f))
1834 error = got_ferror(f, GOT_ERR_IO);
1835 done:
1836 if (f != NULL && fclose(f) == -1 && error == NULL)
1837 error = got_error_from_errno("fclose");
1838 free(d_file);
1839 return error;
1842 static const struct got_error *
1843 gw_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
1845 struct tm tm;
1846 time_t diff_time;
1847 char *years = "years ago", *months = "months ago";
1848 char *weeks = "weeks ago", *days = "days ago", *hours = "hours ago";
1849 char *minutes = "minutes ago", *seconds = "seconds ago";
1850 char *now = "right now";
1851 char *s;
1852 char datebuf[29];
1854 *repo_age = NULL;
1856 switch (ref_tm) {
1857 case TM_DIFF:
1858 diff_time = time(NULL) - committer_time;
1859 if (diff_time > 60 * 60 * 24 * 365 * 2) {
1860 if (asprintf(repo_age, "%lld %s",
1861 (diff_time / 60 / 60 / 24 / 365), years) == -1)
1862 return got_error_from_errno("asprintf");
1863 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
1864 if (asprintf(repo_age, "%lld %s",
1865 (diff_time / 60 / 60 / 24 / (365 / 12)),
1866 months) == -1)
1867 return got_error_from_errno("asprintf");
1868 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
1869 if (asprintf(repo_age, "%lld %s",
1870 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
1871 return got_error_from_errno("asprintf");
1872 } else if (diff_time > 60 * 60 * 24 * 2) {
1873 if (asprintf(repo_age, "%lld %s",
1874 (diff_time / 60 / 60 / 24), days) == -1)
1875 return got_error_from_errno("asprintf");
1876 } else if (diff_time > 60 * 60 * 2) {
1877 if (asprintf(repo_age, "%lld %s",
1878 (diff_time / 60 / 60), hours) == -1)
1879 return got_error_from_errno("asprintf");
1880 } else if (diff_time > 60 * 2) {
1881 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
1882 minutes) == -1)
1883 return got_error_from_errno("asprintf");
1884 } else if (diff_time > 2) {
1885 if (asprintf(repo_age, "%lld %s", diff_time,
1886 seconds) == -1)
1887 return got_error_from_errno("asprintf");
1888 } else {
1889 if (asprintf(repo_age, "%s", now) == -1)
1890 return got_error_from_errno("asprintf");
1892 break;
1893 case TM_LONG:
1894 if (gmtime_r(&committer_time, &tm) == NULL)
1895 return got_error_from_errno("gmtime_r");
1897 s = asctime_r(&tm, datebuf);
1898 if (s == NULL)
1899 return got_error_from_errno("asctime_r");
1901 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
1902 return got_error_from_errno("asprintf");
1903 break;
1905 return NULL;
1908 static const struct got_error *
1909 gw_get_repo_age(char **repo_age, struct gw_trans *gw_trans, char *dir,
1910 char *repo_ref, int ref_tm)
1912 const struct got_error *error = NULL;
1913 struct got_object_id *id = NULL;
1914 struct got_repository *repo = NULL;
1915 struct got_commit_object *commit = NULL;
1916 struct got_reflist_head refs;
1917 struct got_reflist_entry *re;
1918 struct got_reference *head_ref;
1919 int is_head = 0;
1920 time_t committer_time = 0, cmp_time = 0;
1921 const char *refname;
1923 *repo_age = NULL;
1924 SIMPLEQ_INIT(&refs);
1926 if (repo_ref == NULL)
1927 return NULL;
1929 if (strncmp(repo_ref, "refs/heads/", 11) == 0)
1930 is_head = 1;
1932 if (gw_trans->gw_conf->got_show_repo_age == 0)
1933 return NULL;
1935 error = got_repo_open(&repo, dir, NULL);
1936 if (error)
1937 goto done;
1939 if (is_head)
1940 error = got_ref_list(&refs, repo, "refs/heads",
1941 got_ref_cmp_by_name, NULL);
1942 else
1943 error = got_ref_list(&refs, repo, repo_ref,
1944 got_ref_cmp_by_name, NULL);
1945 if (error)
1946 goto done;
1948 SIMPLEQ_FOREACH(re, &refs, entry) {
1949 if (is_head)
1950 refname = strdup(repo_ref);
1951 else
1952 refname = got_ref_get_name(re->ref);
1953 error = got_ref_open(&head_ref, repo, refname, 0);
1954 if (error)
1955 goto done;
1957 error = got_ref_resolve(&id, repo, head_ref);
1958 got_ref_close(head_ref);
1959 if (error)
1960 goto done;
1962 error = got_object_open_as_commit(&commit, repo, id);
1963 if (error)
1964 goto done;
1966 committer_time =
1967 got_object_commit_get_committer_time(commit);
1969 if (cmp_time < committer_time)
1970 cmp_time = committer_time;
1973 if (cmp_time != 0) {
1974 committer_time = cmp_time;
1975 error = gw_get_time_str(repo_age, committer_time, ref_tm);
1977 done:
1978 got_ref_list_free(&refs);
1979 free(id);
1980 return error;
1983 static const struct got_error *
1984 gw_get_diff(char **diff_html, struct gw_trans *gw_trans,
1985 struct gw_header *header)
1987 const struct got_error *error;
1988 FILE *f = NULL;
1989 struct got_object_id *id1 = NULL, *id2 = NULL;
1990 struct buf *diffbuf = NULL;
1991 char *label1 = NULL, *label2 = NULL, *line = NULL;
1992 char *diff_line_html = NULL;
1993 int obj_type;
1994 size_t newsize, linesize = 0;
1995 ssize_t linelen;
1997 f = got_opentemp();
1998 if (f == NULL)
1999 return NULL;
2001 error = buf_alloc(&diffbuf, 0);
2002 if (error)
2003 goto done;
2005 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2006 if (error)
2007 goto done;
2009 if (strncmp(header->parent_id, "/dev/null", 9) != 0) {
2010 error = got_repo_match_object_id(&id1, &label1,
2011 header->parent_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2012 if (error)
2013 goto done;
2016 error = got_repo_match_object_id(&id2, &label2,
2017 header->commit_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2018 if (error)
2019 goto done;
2021 error = got_object_get_type(&obj_type, header->repo, id2);
2022 if (error)
2023 goto done;
2024 switch (obj_type) {
2025 case GOT_OBJ_TYPE_BLOB:
2026 error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, 3, 0,
2027 header->repo, f);
2028 break;
2029 case GOT_OBJ_TYPE_TREE:
2030 error = got_diff_objects_as_trees(id1, id2, "", "", 3, 0,
2031 header->repo, f);
2032 break;
2033 case GOT_OBJ_TYPE_COMMIT:
2034 error = got_diff_objects_as_commits(id1, id2, 3, 0,
2035 header->repo, f);
2036 break;
2037 default:
2038 error = got_error(GOT_ERR_OBJ_TYPE);
2040 if (error)
2041 goto done;
2043 if (fseek(f, 0, SEEK_SET) == -1) {
2044 error = got_ferror(f, GOT_ERR_IO);
2045 goto done;
2048 while ((linelen = getline(&line, &linesize, f)) != -1) {
2049 char *escaped_line;
2050 error = gw_html_escape(&escaped_line, line);
2051 if (error)
2052 goto done;
2054 error = gw_colordiff_line(&diff_line_html, escaped_line);
2055 if (error)
2056 goto done;
2058 error = buf_puts(&newsize, diffbuf, diff_line_html);
2059 if (error)
2060 goto done;
2062 error = buf_puts(&newsize, diffbuf, div_end);
2063 if (error)
2064 goto done;
2066 if (linelen == -1 && ferror(f)) {
2067 error = got_error_from_errno("getline");
2068 goto done;
2071 if (buf_len(diffbuf) > 0) {
2072 error = buf_putc(diffbuf, '\0');
2073 if (error)
2074 goto done;
2075 *diff_html = strdup(buf_get(diffbuf));
2076 if (*diff_html == NULL)
2077 error = got_error_from_errno("strdup");
2079 done:
2080 if (f && fclose(f) == -1 && error == NULL)
2081 error = got_error_from_errno("fclose");
2082 free(diff_line_html);
2083 free(line);
2084 free(diffbuf);
2085 free(label1);
2086 free(label2);
2087 free(id1);
2088 free(id2);
2090 return error;
2093 static const struct got_error *
2094 gw_get_repo_owner(char **owner, struct gw_trans *gw_trans, char *dir)
2096 const struct got_error *error = NULL;
2097 struct got_repository *repo;
2098 const char *gitconfig_owner;
2100 *owner = NULL;
2102 if (gw_trans->gw_conf->got_show_repo_owner == 0)
2103 return NULL;
2105 error = got_repo_open(&repo, dir, NULL);
2106 if (error)
2107 return error;
2108 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
2109 if (gitconfig_owner) {
2110 *owner = strdup(gitconfig_owner);
2111 if (*owner == NULL)
2112 error = got_error_from_errno("strdup");
2114 got_repo_close(repo);
2115 return error;
2118 static const struct got_error *
2119 gw_get_clone_url(char **url, struct gw_trans *gw_trans, char *dir)
2121 const struct got_error *error = NULL;
2122 FILE *f;
2123 char *d_file = NULL;
2124 unsigned int len;
2125 size_t n;
2127 *url = NULL;
2129 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2130 return got_error_from_errno("asprintf");
2132 f = fopen(d_file, "r");
2133 if (f == NULL) {
2134 if (errno != ENOENT && errno != EACCES)
2135 error = got_error_from_errno2("fopen", d_file);
2136 goto done;
2139 if (fseek(f, 0, SEEK_END) == -1) {
2140 error = got_ferror(f, GOT_ERR_IO);
2141 goto done;
2143 len = ftell(f);
2144 if (len == -1) {
2145 error = got_ferror(f, GOT_ERR_IO);
2146 goto done;
2148 if (fseek(f, 0, SEEK_SET) == -1) {
2149 error = got_ferror(f, GOT_ERR_IO);
2150 goto done;
2153 *url = calloc(len + 1, sizeof(**url));
2154 if (*url == NULL) {
2155 error = got_error_from_errno("calloc");
2156 goto done;
2159 n = fread(*url, 1, len, f);
2160 if (n == 0 && ferror(f))
2161 error = got_ferror(f, GOT_ERR_IO);
2162 done:
2163 if (f && fclose(f) == -1 && error == NULL)
2164 error = got_error_from_errno("fclose");
2165 free(d_file);
2166 return NULL;
2169 static const struct got_error *
2170 gw_get_repo_tags(char **tag_html, struct gw_trans *gw_trans,
2171 struct gw_header *header, int limit, int tag_type)
2173 const struct got_error *error = NULL;
2174 struct got_repository *repo = NULL;
2175 struct got_reflist_head refs;
2176 struct got_reflist_entry *re;
2177 char *tag_row = NULL, *tags_navs_disp = NULL;
2178 char *age = NULL, *age_html = NULL, *newline;
2179 char *escaped_tagger = NULL, *escaped_tag_commit = NULL;
2180 char *id_str = NULL, *refstr = NULL;
2181 char *tag_commit0 = NULL;
2182 struct buf *diffbuf = NULL;
2183 size_t newsize;
2184 struct got_tag_object *tag = NULL;
2186 *tag_html = NULL;
2188 SIMPLEQ_INIT(&refs);
2190 error = buf_alloc(&diffbuf, 0);
2191 if (error)
2192 return NULL;
2194 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2195 if (error)
2196 goto done;
2198 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo);
2199 if (error)
2200 goto done;
2202 SIMPLEQ_FOREACH(re, &refs, entry) {
2203 const char *refname;
2204 const char *tagger;
2205 const char *tag_commit;
2206 time_t tagger_time;
2207 struct got_object_id *id;
2209 refname = got_ref_get_name(re->ref);
2210 if (strncmp(refname, "refs/tags/", 10) != 0)
2211 continue;
2212 refname += 10;
2213 refstr = got_ref_to_str(re->ref);
2214 if (refstr == NULL) {
2215 error = got_error_from_errno("got_ref_to_str");
2216 goto done;
2219 error = got_ref_resolve(&id, repo, re->ref);
2220 if (error)
2221 goto done;
2222 error = got_object_open_as_tag(&tag, repo, id);
2223 free(id);
2224 if (error)
2225 goto done;
2227 tagger = got_object_tag_get_tagger(tag);
2228 tagger_time = got_object_tag_get_tagger_time(tag);
2230 error = got_object_id_str(&id_str,
2231 got_object_tag_get_object_id(tag));
2232 if (error)
2233 goto done;
2235 tag_commit0 = strdup(got_object_tag_get_message(tag));
2236 if (tag_commit0 == NULL) {
2237 error = got_error_from_errno("strdup");
2238 goto done;
2241 tag_commit = tag_commit0;
2242 while (*tag_commit == '\n')
2243 tag_commit++;
2245 switch (tag_type) {
2246 case TAGBRIEF:
2247 newline = strchr(tag_commit, '\n');
2248 if (newline)
2249 *newline = '\0';
2251 error = gw_get_time_str(&age, tagger_time, TM_DIFF);
2252 if (error)
2253 goto done;
2255 if (asprintf(&tags_navs_disp, tags_navs,
2256 gw_trans->repo_name, id_str, gw_trans->repo_name,
2257 id_str, gw_trans->repo_name, id_str,
2258 gw_trans->repo_name, id_str) == -1) {
2259 error = got_error_from_errno("asprintf");
2260 goto done;
2263 if (asprintf(&tag_row, tags_row, age ? age : "",
2264 refname, tag_commit, tags_navs_disp) == -1) {
2265 error = got_error_from_errno("asprintf");
2266 goto done;
2269 free(tags_navs_disp);
2270 break;
2271 case TAGFULL:
2272 error = gw_get_time_str(&age, tagger_time, TM_LONG);
2273 if (error)
2274 goto done;
2275 error = gw_html_escape(&escaped_tagger, tagger);
2276 if (error)
2277 goto done;
2278 error = gw_html_escape(&escaped_tag_commit, tag_commit);
2279 if (error)
2280 goto done;
2281 if (asprintf(&tag_row, tag_info, age ? age : "",
2282 escaped_tagger, escaped_tag_commit) == -1) {
2283 error = got_error_from_errno("asprintf");
2284 goto done;
2286 break;
2287 default:
2288 break;
2291 error = buf_puts(&newsize, diffbuf, tag_row);
2292 if (error)
2293 goto done;
2295 if (limit && --limit == 0)
2296 break;
2298 got_object_tag_close(tag);
2299 tag = NULL;
2300 free(id_str);
2301 id_str = NULL;
2302 free(refstr);
2303 refstr = NULL;
2304 free(age);
2305 age = NULL;
2306 free(age_html);
2307 age_html = NULL;
2308 free(escaped_tagger);
2309 escaped_tagger = NULL;
2310 free(escaped_tag_commit);
2311 escaped_tag_commit = NULL;
2312 free(tag_commit0);
2313 tag_commit0 = NULL;
2314 free(tag_row);
2315 tag_row = NULL;
2318 if (buf_len(diffbuf) > 0) {
2319 error = buf_putc(diffbuf, '\0');
2320 *tag_html = strdup(buf_get(diffbuf));
2321 if (*tag_html == NULL)
2322 error = got_error_from_errno("strdup");
2324 done:
2325 if (tag)
2326 got_object_tag_close(tag);
2327 free(id_str);
2328 free(refstr);
2329 free(age);
2330 free(age_html);
2331 free(escaped_tagger);
2332 free(escaped_tag_commit);
2333 free(tag_commit0);
2334 free(tag_row);
2335 buf_free(diffbuf);
2336 got_ref_list_free(&refs);
2337 if (repo)
2338 got_repo_close(repo);
2339 return error;
2342 static void
2343 gw_free_headers(struct gw_header *header)
2345 free(header->id);
2346 free(header->path);
2347 if (header->commit != NULL)
2348 got_object_commit_close(header->commit);
2349 if (header->repo)
2350 got_repo_close(header->repo);
2351 free(header->refs_str);
2352 free(header->commit_id);
2353 free(header->parent_id);
2354 free(header->tree_id);
2355 free(header->committer);
2356 free(header->commit_msg);
2359 static struct gw_header *
2360 gw_init_header()
2362 struct gw_header *header;
2364 header = malloc(sizeof(*header));
2365 if (header == NULL)
2366 return NULL;
2368 header->repo = NULL;
2369 header->commit = NULL;
2370 header->id = NULL;
2371 header->path = NULL;
2372 SIMPLEQ_INIT(&header->refs);
2374 return header;
2377 static const struct got_error *
2378 gw_get_commits(struct gw_trans * gw_trans, struct gw_header *header,
2379 int limit)
2381 const struct got_error *error = NULL;
2382 struct got_commit_graph *graph = NULL;
2384 error = got_commit_graph_open(&graph, header->path, 0);
2385 if (error)
2386 goto done;
2388 error = got_commit_graph_iter_start(graph, header->id, header->repo,
2389 NULL, NULL);
2390 if (error)
2391 goto done;
2393 for (;;) {
2394 error = got_commit_graph_iter_next(&header->id, graph,
2395 header->repo, NULL, NULL);
2396 if (error) {
2397 if (error->code == GOT_ERR_ITER_COMPLETED)
2398 error = NULL;
2399 goto done;
2401 if (header->id == NULL)
2402 goto done;
2404 error = got_object_open_as_commit(&header->commit, header->repo,
2405 header->id);
2406 if (error)
2407 goto done;
2409 error = gw_get_commit(gw_trans, header);
2410 if (limit > 1) {
2411 struct gw_header *n_header = NULL;
2412 if ((n_header = gw_init_header()) == NULL) {
2413 error = got_error_from_errno("malloc");
2414 goto done;
2417 n_header->refs_str = strdup(header->refs_str);
2418 n_header->commit_id = strdup(header->commit_id);
2419 n_header->parent_id = strdup(header->parent_id);
2420 n_header->tree_id = strdup(header->tree_id);
2421 n_header->author = strdup(header->author);
2422 n_header->committer = strdup(header->committer);
2423 n_header->commit_msg = strdup(header->commit_msg);
2424 n_header->committer_time = header->committer_time;
2425 TAILQ_INSERT_TAIL(&gw_trans->gw_headers, n_header,
2426 entry);
2428 if (error || (limit && --limit == 0))
2429 break;
2431 done:
2432 if (graph)
2433 got_commit_graph_close(graph);
2434 return error;
2437 static const struct got_error *
2438 gw_get_commit(struct gw_trans *gw_trans, struct gw_header *header)
2440 const struct got_error *error = NULL;
2441 struct got_reflist_entry *re;
2442 struct got_object_id *id2 = NULL;
2443 struct got_object_qid *parent_id;
2444 char *refs_str = NULL, *commit_msg = NULL, *commit_msg0;
2446 /*print commit*/
2447 SIMPLEQ_FOREACH(re, &header->refs, entry) {
2448 char *s;
2449 const char *name;
2450 struct got_tag_object *tag = NULL;
2451 int cmp;
2453 name = got_ref_get_name(re->ref);
2454 if (strcmp(name, GOT_REF_HEAD) == 0)
2455 continue;
2456 if (strncmp(name, "refs/", 5) == 0)
2457 name += 5;
2458 if (strncmp(name, "got/", 4) == 0)
2459 continue;
2460 if (strncmp(name, "heads/", 6) == 0)
2461 name += 6;
2462 if (strncmp(name, "remotes/", 8) == 0)
2463 name += 8;
2464 if (strncmp(name, "tags/", 5) == 0) {
2465 error = got_object_open_as_tag(&tag, header->repo,
2466 re->id);
2467 if (error) {
2468 if (error->code != GOT_ERR_OBJ_TYPE)
2469 continue;
2471 * Ref points at something other
2472 * than a tag.
2474 error = NULL;
2475 tag = NULL;
2478 cmp = got_object_id_cmp(tag ?
2479 got_object_tag_get_object_id(tag) : re->id, header->id);
2480 if (tag)
2481 got_object_tag_close(tag);
2482 if (cmp != 0)
2483 continue;
2484 s = refs_str;
2485 if (asprintf(&refs_str, "%s%s%s", s ? s : "",
2486 s ? ", " : "", name) == -1) {
2487 error = got_error_from_errno("asprintf");
2488 free(s);
2489 return error;
2491 header->refs_str = strdup(refs_str);
2492 free(s);
2495 if (refs_str == NULL)
2496 header->refs_str = strdup("");
2497 free(refs_str);
2499 error = got_object_id_str(&header->commit_id, header->id);
2500 if (error)
2501 return error;
2503 error = got_object_id_str(&header->tree_id,
2504 got_object_commit_get_tree_id(header->commit));
2505 if (error)
2506 return error;
2508 if (gw_trans->action == GW_DIFF) {
2509 parent_id = SIMPLEQ_FIRST(
2510 got_object_commit_get_parent_ids(header->commit));
2511 if (parent_id != NULL) {
2512 id2 = got_object_id_dup(parent_id->id);
2513 free (parent_id);
2514 error = got_object_id_str(&header->parent_id, id2);
2515 if (error)
2516 return error;
2517 free(id2);
2518 } else
2519 header->parent_id = strdup("/dev/null");
2520 } else
2521 header->parent_id = strdup("");
2523 header->committer_time =
2524 got_object_commit_get_committer_time(header->commit);
2526 header->author =
2527 got_object_commit_get_author(header->commit);
2528 error = gw_html_escape(&header->committer,
2529 got_object_commit_get_committer(header->commit));
2530 if (error)
2531 return error;
2533 /* XXX Doesn't the log message require escaping? */
2534 error = got_object_commit_get_logmsg(&commit_msg0, header->commit);
2535 if (error)
2536 return error;
2538 commit_msg = commit_msg0;
2539 while (*commit_msg == '\n')
2540 commit_msg++;
2542 header->commit_msg = strdup(commit_msg);
2543 free(commit_msg0);
2544 return error;
2547 static const struct got_error *
2548 gw_get_header(struct gw_trans *gw_trans, struct gw_header *header, int limit)
2550 const struct got_error *error = NULL;
2551 char *in_repo_path = NULL;
2553 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2554 if (error)
2555 return error;
2557 if (gw_trans->commit == NULL) {
2558 struct got_reference *head_ref;
2559 error = got_ref_open(&head_ref, header->repo,
2560 gw_trans->headref, 0);
2561 if (error)
2562 return error;
2564 error = got_ref_resolve(&header->id, header->repo, head_ref);
2565 got_ref_close(head_ref);
2566 if (error)
2567 return error;
2569 error = got_object_open_as_commit(&header->commit,
2570 header->repo, header->id);
2571 } else {
2572 struct got_reference *ref;
2573 error = got_ref_open(&ref, header->repo, gw_trans->commit, 0);
2574 if (error == NULL) {
2575 int obj_type;
2576 error = got_ref_resolve(&header->id, header->repo, ref);
2577 got_ref_close(ref);
2578 if (error)
2579 return error;
2580 error = got_object_get_type(&obj_type, header->repo,
2581 header->id);
2582 if (error)
2583 return error;
2584 if (obj_type == GOT_OBJ_TYPE_TAG) {
2585 struct got_tag_object *tag;
2586 error = got_object_open_as_tag(&tag,
2587 header->repo, header->id);
2588 if (error)
2589 return error;
2590 if (got_object_tag_get_object_type(tag) !=
2591 GOT_OBJ_TYPE_COMMIT) {
2592 got_object_tag_close(tag);
2593 error = got_error(GOT_ERR_OBJ_TYPE);
2594 return error;
2596 free(header->id);
2597 header->id = got_object_id_dup(
2598 got_object_tag_get_object_id(tag));
2599 if (header->id == NULL)
2600 error = got_error_from_errno(
2601 "got_object_id_dup");
2602 got_object_tag_close(tag);
2603 if (error)
2604 return error;
2605 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
2606 error = got_error(GOT_ERR_OBJ_TYPE);
2607 return error;
2609 error = got_object_open_as_commit(&header->commit,
2610 header->repo, header->id);
2611 if (error)
2612 return error;
2614 if (header->commit == NULL) {
2615 error = got_repo_match_object_id_prefix(&header->id,
2616 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2617 header->repo);
2618 if (error)
2619 return error;
2621 error = got_repo_match_object_id_prefix(&header->id,
2622 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2623 header->repo);
2626 error = got_repo_map_path(&in_repo_path, header->repo,
2627 gw_trans->repo_path, 1);
2628 if (error)
2629 return error;
2631 if (in_repo_path) {
2632 header->path = strdup(in_repo_path);
2634 free(in_repo_path);
2636 error = got_ref_list(&header->refs, header->repo, NULL,
2637 got_ref_cmp_by_name, NULL);
2638 if (error)
2639 return error;
2641 error = gw_get_commits(gw_trans, header, limit);
2642 return error;
2645 struct blame_line {
2646 int annotated;
2647 char *id_str;
2648 char *committer;
2649 char datebuf[11]; /* YYYY-MM-DD + NUL */
2652 struct gw_blame_cb_args {
2653 struct blame_line *lines;
2654 int nlines;
2655 int nlines_prec;
2656 int lineno_cur;
2657 off_t *line_offsets;
2658 FILE *f;
2659 struct got_repository *repo;
2660 struct gw_trans *gw_trans;
2661 struct buf *blamebuf;
2664 static const struct got_error *
2665 gw_blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
2667 const struct got_error *err = NULL;
2668 struct gw_blame_cb_args *a = arg;
2669 struct blame_line *bline;
2670 char *line = NULL;
2671 size_t linesize = 0, newsize;
2672 struct got_commit_object *commit = NULL;
2673 off_t offset;
2674 struct tm tm;
2675 time_t committer_time;
2677 if (nlines != a->nlines ||
2678 (lineno != -1 && lineno < 1) || lineno > a->nlines)
2679 return got_error(GOT_ERR_RANGE);
2681 if (lineno == -1)
2682 return NULL; /* no change in this commit */
2684 /* Annotate this line. */
2685 bline = &a->lines[lineno - 1];
2686 if (bline->annotated)
2687 return NULL;
2688 err = got_object_id_str(&bline->id_str, id);
2689 if (err)
2690 return err;
2692 err = got_object_open_as_commit(&commit, a->repo, id);
2693 if (err)
2694 goto done;
2696 bline->committer = strdup(got_object_commit_get_committer(commit));
2697 if (bline->committer == NULL) {
2698 err = got_error_from_errno("strdup");
2699 goto done;
2702 committer_time = got_object_commit_get_committer_time(commit);
2703 if (localtime_r(&committer_time, &tm) == NULL)
2704 return got_error_from_errno("localtime_r");
2705 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
2706 &tm) >= sizeof(bline->datebuf)) {
2707 err = got_error(GOT_ERR_NO_SPACE);
2708 goto done;
2710 bline->annotated = 1;
2712 /* Print lines annotated so far. */
2713 bline = &a->lines[a->lineno_cur - 1];
2714 if (!bline->annotated)
2715 goto done;
2717 offset = a->line_offsets[a->lineno_cur - 1];
2718 if (fseeko(a->f, offset, SEEK_SET) == -1) {
2719 err = got_error_from_errno("fseeko");
2720 goto done;
2723 while (bline->annotated) {
2724 char *smallerthan, *at, *nl, *committer, *blame_row = NULL,
2725 *line_escape = NULL;
2726 size_t len;
2728 if (getline(&line, &linesize, a->f) == -1) {
2729 if (ferror(a->f))
2730 err = got_error_from_errno("getline");
2731 break;
2734 committer = bline->committer;
2735 smallerthan = strchr(committer, '<');
2736 if (smallerthan && smallerthan[1] != '\0')
2737 committer = smallerthan + 1;
2738 at = strchr(committer, '@');
2739 if (at)
2740 *at = '\0';
2741 len = strlen(committer);
2742 if (len >= 9)
2743 committer[8] = '\0';
2745 nl = strchr(line, '\n');
2746 if (nl)
2747 *nl = '\0';
2749 err = gw_html_escape(&line_escape, line);
2750 if (err)
2751 goto err;
2753 if (a->gw_trans->repo_folder == NULL)
2754 a->gw_trans->repo_folder = strdup("");
2755 if (a->gw_trans->repo_folder == NULL)
2756 goto err;
2757 asprintf(&blame_row, blame_line, a->nlines_prec, a->lineno_cur,
2758 a->gw_trans->repo_name, bline->id_str,
2759 a->gw_trans->repo_file, a->gw_trans->repo_folder,
2760 bline->id_str, bline->datebuf, committer, line_escape);
2761 a->lineno_cur++;
2762 err = buf_puts(&newsize, a->blamebuf, blame_row);
2763 if (err)
2764 return err;
2766 bline = &a->lines[a->lineno_cur - 1];
2767 err:
2768 free(line_escape);
2769 free(blame_row);
2771 done:
2772 if (commit)
2773 got_object_commit_close(commit);
2774 free(line);
2775 return err;
2778 static const struct got_error *
2779 gw_get_file_blame_blob(char **blame_html, struct gw_trans *gw_trans)
2781 const struct got_error *error = NULL;
2782 struct got_repository *repo = NULL;
2783 struct got_object_id *obj_id = NULL;
2784 struct got_object_id *commit_id = NULL;
2785 struct got_blob_object *blob = NULL;
2786 char *path = NULL, *in_repo_path = NULL;
2787 struct gw_blame_cb_args bca;
2788 int i, obj_type;
2789 size_t filesize;
2791 *blame_html = NULL;
2793 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2794 if (error)
2795 return error;
2797 if (asprintf(&path, "%s%s%s",
2798 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2799 gw_trans->repo_folder ? "/" : "",
2800 gw_trans->repo_file) == -1) {
2801 error = got_error_from_errno("asprintf");
2802 goto done;
2805 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2806 if (error)
2807 goto done;
2809 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2810 GOT_OBJ_TYPE_COMMIT, 1, repo);
2811 if (error)
2812 goto done;
2814 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2815 if (error)
2816 goto done;
2818 if (obj_id == NULL) {
2819 error = got_error(GOT_ERR_NO_OBJ);
2820 goto done;
2823 error = got_object_get_type(&obj_type, repo, obj_id);
2824 if (error)
2825 goto done;
2827 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2828 error = got_error(GOT_ERR_OBJ_TYPE);
2829 goto done;
2832 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2833 if (error)
2834 goto done;
2836 error = buf_alloc(&bca.blamebuf, 0);
2837 if (error)
2838 goto done;
2840 bca.f = got_opentemp();
2841 if (bca.f == NULL) {
2842 error = got_error_from_errno("got_opentemp");
2843 goto done;
2845 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
2846 &bca.line_offsets, bca.f, blob);
2847 if (error || bca.nlines == 0)
2848 goto done;
2850 /* Don't include \n at EOF in the blame line count. */
2851 if (bca.line_offsets[bca.nlines - 1] == filesize)
2852 bca.nlines--;
2854 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
2855 if (bca.lines == NULL) {
2856 error = got_error_from_errno("calloc");
2857 goto done;
2859 bca.lineno_cur = 1;
2860 bca.nlines_prec = 0;
2861 i = bca.nlines;
2862 while (i > 0) {
2863 i /= 10;
2864 bca.nlines_prec++;
2866 bca.repo = repo;
2867 bca.gw_trans = gw_trans;
2869 error = got_blame(in_repo_path, commit_id, repo, gw_blame_cb, &bca,
2870 NULL, NULL);
2871 if (error)
2872 goto done;
2873 if (buf_len(bca.blamebuf) > 0) {
2874 error = buf_putc(bca.blamebuf, '\0');
2875 if (error)
2876 goto done;
2877 *blame_html = strdup(buf_get(bca.blamebuf));
2878 if (*blame_html == NULL) {
2879 error = got_error_from_errno("strdup");
2880 goto done;
2883 done:
2884 free(bca.line_offsets);
2885 free(bca.blamebuf);
2886 free(in_repo_path);
2887 free(commit_id);
2888 free(obj_id);
2889 free(path);
2891 for (i = 0; i < bca.nlines; i++) {
2892 struct blame_line *bline = &bca.lines[i];
2893 free(bline->id_str);
2894 free(bline->committer);
2896 free(bca.lines);
2897 if (bca.f && fclose(bca.f) == EOF && error == NULL)
2898 error = got_error_from_errno("fclose");
2899 if (blob)
2900 got_object_blob_close(blob);
2901 if (repo)
2902 got_repo_close(repo);
2903 return error;
2906 static const struct got_error *
2907 gw_get_file_read_blob(char **blobstr, size_t *filesize, struct gw_trans *gw_trans)
2909 const struct got_error *error = NULL;
2910 struct got_repository *repo = NULL;
2911 struct got_object_id *obj_id = NULL;
2912 struct got_object_id *commit_id = NULL;
2913 struct got_blob_object *blob = NULL;
2914 char *path = NULL, *in_repo_path = NULL;
2915 int obj_type;
2916 size_t n;
2917 FILE *f = NULL;
2919 *blobstr = NULL;
2920 *filesize = 0;
2922 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2923 if (error)
2924 return error;
2926 if (asprintf(&path, "%s%s%s",
2927 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2928 gw_trans->repo_folder ? "/" : "",
2929 gw_trans->repo_file) == -1) {
2930 error = got_error_from_errno("asprintf");
2931 goto done;
2934 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2935 if (error)
2936 goto done;
2938 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2939 GOT_OBJ_TYPE_COMMIT, 1, repo);
2940 if (error)
2941 goto done;
2943 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2944 if (error)
2945 goto done;
2947 if (obj_id == NULL) {
2948 error = got_error(GOT_ERR_NO_OBJ);
2949 goto done;
2952 error = got_object_get_type(&obj_type, repo, obj_id);
2953 if (error)
2954 goto done;
2956 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2957 error = got_error(GOT_ERR_OBJ_TYPE);
2958 goto done;
2961 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2962 if (error)
2963 goto done;
2965 f = got_opentemp();
2966 if (f == NULL) {
2967 error = got_error_from_errno("got_opentemp");
2968 goto done;
2970 error = got_object_blob_dump_to_file(filesize, NULL, NULL, f, blob);
2971 if (error)
2972 goto done;
2974 /* XXX This will fail on large files... */
2975 *blobstr = calloc(*filesize + 1, sizeof(**blobstr));
2976 if (*blobstr == NULL) {
2977 error = got_error_from_errno("calloc");
2978 goto done;
2981 n = fread(*blobstr, 1, *filesize, f);
2982 if (n == 0) {
2983 if (ferror(f))
2984 error = got_ferror(f, GOT_ERR_IO);
2985 goto done;
2987 done:
2988 free(in_repo_path);
2989 free(commit_id);
2990 free(obj_id);
2991 free(path);
2992 if (blob)
2993 got_object_blob_close(blob);
2994 if (repo)
2995 got_repo_close(repo);
2996 if (f != NULL && fclose(f) == -1 && error == NULL)
2997 error = got_error_from_errno("fclose");
2998 if (error) {
2999 free(*blobstr);
3000 *blobstr = NULL;
3001 *filesize = 0;
3003 return error;
3006 static const struct got_error *
3007 gw_get_repo_tree(char **tree_html, struct gw_trans *gw_trans)
3009 const struct got_error *error = NULL;
3010 struct got_repository *repo = NULL;
3011 struct got_object_id *tree_id = NULL, *commit_id = NULL;
3012 struct got_tree_object *tree = NULL;
3013 struct buf *diffbuf = NULL;
3014 size_t newsize;
3015 char *path = NULL, *in_repo_path = NULL, *tree_row = NULL;
3016 char *id_str = NULL;
3017 char *build_folder = NULL;
3018 char *url_html = NULL;
3019 const char *class = NULL;
3020 int nentries, i, class_flip = 0;
3022 *tree_html = NULL;
3024 error = buf_alloc(&diffbuf, 0);
3025 if (error)
3026 return error;
3028 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3029 if (error)
3030 goto done;
3032 if (gw_trans->repo_folder != NULL)
3033 path = strdup(gw_trans->repo_folder);
3034 else {
3035 error = got_repo_map_path(&in_repo_path, repo,
3036 gw_trans->repo_path, 1);
3037 if (error)
3038 goto done;
3039 free(path);
3040 path = in_repo_path;
3043 if (gw_trans->commit == NULL) {
3044 struct got_reference *head_ref;
3045 error = got_ref_open(&head_ref, repo, gw_trans->headref, 0);
3046 if (error)
3047 goto done;
3048 error = got_ref_resolve(&commit_id, repo, head_ref);
3049 if (error)
3050 goto done;
3051 got_ref_close(head_ref);
3053 } else {
3054 error = got_repo_match_object_id(&commit_id, NULL,
3055 gw_trans->commit, GOT_OBJ_TYPE_COMMIT, 1, repo);
3056 if (error)
3057 goto done;
3060 error = got_object_id_str(&gw_trans->commit, commit_id);
3061 if (error)
3062 goto done;
3064 error = got_object_id_by_path(&tree_id, repo, commit_id, path);
3065 if (error)
3066 goto done;
3068 error = got_object_open_as_tree(&tree, repo, tree_id);
3069 if (error)
3070 goto done;
3072 nentries = got_object_tree_get_nentries(tree);
3073 for (i = 0; i < nentries; i++) {
3074 struct got_tree_entry *te;
3075 const char *modestr = "";
3076 mode_t mode;
3078 te = got_object_tree_get_entry(tree, i);
3080 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
3081 if (error)
3082 goto done;
3084 mode = got_tree_entry_get_mode(te);
3085 if (got_object_tree_entry_is_submodule(te))
3086 modestr = "$";
3087 else if (S_ISLNK(mode))
3088 modestr = "@";
3089 else if (S_ISDIR(mode))
3090 modestr = "/";
3091 else if (mode & S_IXUSR)
3092 modestr = "*";
3094 if (class_flip == 0) {
3095 class = "back_lightgray";
3096 class_flip = 1;
3097 } else {
3098 class = "back_white";
3099 class_flip = 0;
3102 if (S_ISDIR(mode)) {
3103 if (asprintf(&build_folder, "%s%s%s",
3104 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3105 gw_trans->repo_folder ? "/" : "",
3106 got_tree_entry_get_name(te)) == -1) {
3107 error = got_error_from_errno(
3108 "asprintf");
3109 goto done;
3112 if (asprintf(&url_html, folder_html,
3113 gw_trans->repo_name, gw_trans->action_name,
3114 gw_trans->commit, build_folder,
3115 got_tree_entry_get_name(te), modestr) == -1) {
3116 error = got_error_from_errno("asprintf");
3117 goto done;
3119 if (asprintf(&tree_row, tree_line, class, url_html,
3120 class) == -1) {
3121 error = got_error_from_errno("asprintf");
3122 goto done;
3124 } else {
3125 if (asprintf(&url_html, file_html, gw_trans->repo_name,
3126 "blob", gw_trans->commit,
3127 got_tree_entry_get_name(te),
3128 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3129 got_tree_entry_get_name(te), modestr) == -1) {
3130 error = got_error_from_errno("asprintf");
3131 goto done;
3134 if (asprintf(&tree_row, tree_line_with_navs, class,
3135 url_html, class, gw_trans->repo_name, "blob",
3136 gw_trans->commit, got_tree_entry_get_name(te),
3137 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3138 "blob", gw_trans->repo_name,
3139 "blame", gw_trans->commit,
3140 got_tree_entry_get_name(te),
3141 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3142 "blame") == -1) {
3143 error = got_error_from_errno("asprintf");
3144 goto done;
3148 error = buf_puts(&newsize, diffbuf, tree_row);
3149 if (error)
3150 goto done;
3152 free(id_str);
3153 id_str = NULL;
3154 free(url_html);
3155 url_html = NULL;
3156 free(tree_row);
3157 tree_row = NULL;
3158 free(build_folder);
3159 build_folder = NULL;
3162 if (buf_len(diffbuf) > 0) {
3163 error = buf_putc(diffbuf, '\0');
3164 if (error)
3165 goto done;
3166 *tree_html = strdup(buf_get(diffbuf));
3167 if (*tree_html == NULL) {
3168 error = got_error_from_errno("strdup");
3169 goto done;
3172 done:
3173 if (tree)
3174 got_object_tree_close(tree);
3175 if (repo)
3176 got_repo_close(repo);
3178 free(id_str);
3179 free(url_html);
3180 free(tree_row);
3181 free(in_repo_path);
3182 free(tree_id);
3183 free(diffbuf);
3184 free(build_folder);
3185 return error;
3188 static const struct got_error *
3189 gw_get_repo_heads(char **head_html, struct gw_trans *gw_trans)
3191 const struct got_error *error = NULL;
3192 struct got_repository *repo = NULL;
3193 struct got_reflist_head refs;
3194 struct got_reflist_entry *re;
3195 char *head_row = NULL, *head_navs_disp = NULL, *age = NULL;
3196 struct buf *diffbuf = NULL;
3197 size_t newsize;
3199 *head_html = NULL;
3201 SIMPLEQ_INIT(&refs);
3203 error = buf_alloc(&diffbuf, 0);
3204 if (error)
3205 return NULL;
3207 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3208 if (error)
3209 goto done;
3211 error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
3212 NULL);
3213 if (error)
3214 goto done;
3216 SIMPLEQ_FOREACH(re, &refs, entry) {
3217 char *refname;
3219 refname = strdup(got_ref_get_name(re->ref));
3220 if (refname == NULL) {
3221 error = got_error_from_errno("got_ref_to_str");
3222 goto done;
3225 if (strncmp(refname, "refs/heads/", 11) != 0) {
3226 free(refname);
3227 continue;
3230 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
3231 refname, TM_DIFF);
3232 if (error)
3233 goto done;
3235 if (asprintf(&head_navs_disp, heads_navs, gw_trans->repo_name,
3236 refname, gw_trans->repo_name, refname,
3237 gw_trans->repo_name, refname, gw_trans->repo_name,
3238 refname) == -1) {
3239 error = got_error_from_errno("asprintf");
3240 goto done;
3243 if (strncmp(refname, "refs/heads/", 11) == 0)
3244 refname += 11;
3246 if (asprintf(&head_row, heads_row, age, refname,
3247 head_navs_disp) == -1) {
3248 error = got_error_from_errno("asprintf");
3249 goto done;
3252 error = buf_puts(&newsize, diffbuf, head_row);
3254 free(head_navs_disp);
3255 free(head_row);
3258 if (buf_len(diffbuf) > 0) {
3259 error = buf_putc(diffbuf, '\0');
3260 *head_html = strdup(buf_get(diffbuf));
3261 if (*head_html == NULL)
3262 error = got_error_from_errno("strdup");
3264 done:
3265 buf_free(diffbuf);
3266 got_ref_list_free(&refs);
3267 if (repo)
3268 got_repo_close(repo);
3269 return error;
3272 static char *
3273 gw_get_site_link(struct gw_trans *gw_trans)
3275 char *link = NULL, *repo = NULL, *action = NULL;
3277 if (gw_trans->repo_name != NULL &&
3278 asprintf(&repo, " / <a href='?path=%s&action=summary'>%s</a>",
3279 gw_trans->repo_name, gw_trans->repo_name) == -1)
3280 return NULL;
3282 if (gw_trans->action_name != NULL &&
3283 asprintf(&action, " / %s", gw_trans->action_name) == -1) {
3284 free(repo);
3285 return NULL;
3288 if (asprintf(&link, site_link, GOTWEB,
3289 gw_trans->gw_conf->got_site_link,
3290 repo ? repo : "", action ? action : "") == -1) {
3291 free(repo);
3292 free(action);
3293 return NULL;
3296 free(repo);
3297 free(action);
3298 return link;
3301 static const struct got_error *
3302 gw_colordiff_line(char **colorized_line, char *buf)
3304 const struct got_error *error = NULL;
3305 char *div_diff_line_div = NULL, *color = NULL;
3306 struct buf *diffbuf = NULL;
3307 size_t newsize;
3309 *colorized_line = NULL;
3311 error = buf_alloc(&diffbuf, 0);
3312 if (error)
3313 return error;
3315 if (strncmp(buf, "-", 1) == 0)
3316 color = "diff_minus";
3317 else if (strncmp(buf, "+", 1) == 0)
3318 color = "diff_plus";
3319 else if (strncmp(buf, "@@", 2) == 0)
3320 color = "diff_chunk_header";
3321 else if (strncmp(buf, "@@", 2) == 0)
3322 color = "diff_chunk_header";
3323 else if (strncmp(buf, "commit +", 8) == 0)
3324 color = "diff_meta";
3325 else if (strncmp(buf, "commit -", 8) == 0)
3326 color = "diff_meta";
3327 else if (strncmp(buf, "blob +", 6) == 0)
3328 color = "diff_meta";
3329 else if (strncmp(buf, "blob -", 6) == 0)
3330 color = "diff_meta";
3331 else if (strncmp(buf, "file +", 6) == 0)
3332 color = "diff_meta";
3333 else if (strncmp(buf, "file -", 6) == 0)
3334 color = "diff_meta";
3335 else if (strncmp(buf, "from:", 5) == 0)
3336 color = "diff_author";
3337 else if (strncmp(buf, "via:", 4) == 0)
3338 color = "diff_author";
3339 else if (strncmp(buf, "date:", 5) == 0)
3340 color = "diff_date";
3342 if (asprintf(&div_diff_line_div, div_diff_line, color ? color : "")
3343 == -1) {
3344 error = got_error_from_errno("asprintf");
3345 goto done;
3348 error = buf_puts(&newsize, diffbuf, div_diff_line_div);
3349 if (error)
3350 goto done;
3352 error = buf_puts(&newsize, diffbuf, buf);
3353 if (error)
3354 goto done;
3356 if (buf_len(diffbuf) > 0) {
3357 error = buf_putc(diffbuf, '\0');
3358 *colorized_line = strdup(buf_get(diffbuf));
3359 if (*colorized_line == NULL)
3360 error = got_error_from_errno("strdup");
3362 done:
3363 free(diffbuf);
3364 free(div_diff_line_div);
3365 return error;
3369 * XXX This function should not exist.
3370 * We should let khtml_puts(3) handle HTML escaping.
3372 static const struct got_error *
3373 gw_html_escape(char **escaped_html, const char *orig_html)
3375 const struct got_error *error = NULL;
3376 struct escape_pair {
3377 char c;
3378 const char *s;
3379 } esc[] = {
3380 { '>', "&gt;" },
3381 { '<', "&lt;" },
3382 { '&', "&amp;" },
3383 { '"', "&quot;" },
3384 { '\'', "&apos;" },
3385 { '\n', "<br />" },
3387 size_t orig_len, len;
3388 int i, j, x;
3390 orig_len = strlen(orig_html);
3391 len = orig_len;
3392 for (i = 0; i < orig_len; i++) {
3393 for (j = 0; j < nitems(esc); j++) {
3394 if (orig_html[i] != esc[j].c)
3395 continue;
3396 len += strlen(esc[j].s) - 1 /* escaped char */;
3400 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
3401 if (*escaped_html == NULL)
3402 return got_error_from_errno("calloc");
3404 x = 0;
3405 for (i = 0; i < orig_len; i++) {
3406 int escaped = 0;
3407 for (j = 0; j < nitems(esc); j++) {
3408 if (orig_html[i] != esc[j].c)
3409 continue;
3411 if (strlcat(*escaped_html, esc[j].s, len + 1)
3412 >= len + 1) {
3413 error = got_error(GOT_ERR_NO_SPACE);
3414 goto done;
3416 x += strlen(esc[j].s);
3417 escaped = 1;
3418 break;
3420 if (!escaped) {
3421 (*escaped_html)[x] = orig_html[i];
3422 x++;
3425 done:
3426 if (error) {
3427 free(*escaped_html);
3428 *escaped_html = NULL;
3429 } else {
3430 (*escaped_html)[x] = '\0';
3432 return error;
3435 int
3436 main(int argc, char *argv[])
3438 const struct got_error *error = NULL;
3439 struct gw_trans *gw_trans;
3440 struct gw_dir *dir = NULL, *tdir;
3441 const char *page = "index";
3442 int gw_malloc = 1;
3443 enum kcgi_err kerr;
3445 if ((gw_trans = malloc(sizeof(struct gw_trans))) == NULL)
3446 errx(1, "malloc");
3448 if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
3449 errx(1, "malloc");
3451 if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
3452 errx(1, "malloc");
3454 if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
3455 errx(1, "malloc");
3457 kerr = khttp_parse(gw_trans->gw_req, gw_keys, KEY__ZMAX, &page, 1, 0);
3458 if (kerr != KCGI_OK) {
3459 error = gw_kcgi_error(kerr);
3460 goto done;
3463 if ((gw_trans->gw_conf =
3464 malloc(sizeof(struct gotweb_conf))) == NULL) {
3465 gw_malloc = 0;
3466 error = got_error_from_errno("malloc");
3467 goto done;
3470 TAILQ_INIT(&gw_trans->gw_dirs);
3471 TAILQ_INIT(&gw_trans->gw_headers);
3473 gw_trans->page = 0;
3474 gw_trans->repos_total = 0;
3475 gw_trans->repo_path = NULL;
3476 gw_trans->commit = NULL;
3477 gw_trans->headref = strdup(GOT_REF_HEAD);
3478 gw_trans->mime = KMIME_TEXT_HTML;
3479 gw_trans->gw_tmpl->key = gw_templs;
3480 gw_trans->gw_tmpl->keysz = TEMPL__MAX;
3481 gw_trans->gw_tmpl->arg = gw_trans;
3482 gw_trans->gw_tmpl->cb = gw_template;
3483 error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
3484 if (error)
3485 goto done;
3487 error = gw_parse_querystring(gw_trans);
3488 if (error)
3489 goto done;
3491 if (gw_trans->action == GW_BLOB)
3492 error = gw_blob(gw_trans);
3493 else
3494 error = gw_display_index(gw_trans);
3495 done:
3496 if (error) {
3497 gw_trans->mime = KMIME_TEXT_PLAIN;
3498 gw_trans->action = GW_ERR;
3499 gw_display_error(gw_trans, error);
3501 if (gw_malloc) {
3502 free(gw_trans->gw_conf->got_repos_path);
3503 free(gw_trans->gw_conf->got_site_name);
3504 free(gw_trans->gw_conf->got_site_owner);
3505 free(gw_trans->gw_conf->got_site_link);
3506 free(gw_trans->gw_conf->got_logo);
3507 free(gw_trans->gw_conf->got_logo_url);
3508 free(gw_trans->gw_conf);
3509 free(gw_trans->commit);
3510 free(gw_trans->repo_path);
3511 free(gw_trans->repo_name);
3512 free(gw_trans->repo_file);
3513 free(gw_trans->action_name);
3514 free(gw_trans->headref);
3516 TAILQ_FOREACH_SAFE(dir, &gw_trans->gw_dirs, entry, tdir) {
3517 free(dir->name);
3518 free(dir->description);
3519 free(dir->age);
3520 free(dir->url);
3521 free(dir->path);
3522 free(dir);
3527 khttp_free(gw_trans->gw_req);
3528 return 0;