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;
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 return error;
896 static const struct got_error *
897 gw_summary(struct gw_trans *gw_trans)
899 const struct got_error *error = NULL;
900 char *age = NULL, *tags = NULL, *heads = NULL;
901 enum kcgi_err kerr;
903 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
904 return got_error_from_errno("pledge");
906 /* unveil is applied with gw_briefs below */
908 /* summary wrapper */
909 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
910 "summary_wrapper", KATTR__MAX);
911 if (kerr != KCGI_OK)
912 return gw_kcgi_error(kerr);
914 /* description */
915 if (gw_trans->gw_conf->got_show_repo_description &&
916 gw_trans->gw_dir->description != NULL &&
917 (strcmp(gw_trans->gw_dir->description, "") != 0)) {
918 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
919 KATTR_ID, "description_title", KATTR__MAX);
920 if (kerr != KCGI_OK) {
921 error = gw_kcgi_error(kerr);
922 goto done;
924 kerr = khtml_puts(gw_trans->gw_html_req, "Description: ");
925 if (kerr != KCGI_OK) {
926 error = gw_kcgi_error(kerr);
927 goto done;
929 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
930 if (kerr != KCGI_OK) {
931 error = gw_kcgi_error(kerr);
932 goto done;
934 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
935 KATTR_ID, "description", KATTR__MAX);
936 if (kerr != KCGI_OK) {
937 error = gw_kcgi_error(kerr);
938 goto done;
940 kerr = khtml_puts(gw_trans->gw_html_req,
941 gw_trans->gw_dir->description);
942 if (kerr != KCGI_OK) {
943 error = gw_kcgi_error(kerr);
944 goto done;
946 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
947 if (kerr != KCGI_OK) {
948 error = gw_kcgi_error(kerr);
949 goto done;
953 /* repo owner */
954 if (gw_trans->gw_conf->got_show_repo_owner &&
955 gw_trans->gw_dir->owner != NULL) {
956 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
957 KATTR_ID, "repo_owner_title", KATTR__MAX);
958 if (kerr != KCGI_OK) {
959 error = gw_kcgi_error(kerr);
960 goto done;
962 kerr = khtml_puts(gw_trans->gw_html_req, "Owner: ");
963 if (kerr != KCGI_OK) {
964 error = gw_kcgi_error(kerr);
965 goto done;
967 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
968 if (kerr != KCGI_OK) {
969 error = gw_kcgi_error(kerr);
970 goto done;
972 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
973 KATTR_ID, "repo_owner", KATTR__MAX);
974 if (kerr != KCGI_OK) {
975 error = gw_kcgi_error(kerr);
976 goto done;
978 kerr = khtml_puts(gw_trans->gw_html_req,
979 gw_trans->gw_dir->owner);
980 if (kerr != KCGI_OK) {
981 error = gw_kcgi_error(kerr);
982 goto done;
984 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
985 if (kerr != KCGI_OK) {
986 error = gw_kcgi_error(kerr);
987 goto done;
991 /* last change */
992 if (gw_trans->gw_conf->got_show_repo_age) {
993 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
994 "refs/heads", TM_LONG);
995 if (error)
996 goto done;
997 if (age != NULL) {
998 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
999 KATTR_ID, "last_change_title", KATTR__MAX);
1000 if (kerr != KCGI_OK) {
1001 error = gw_kcgi_error(kerr);
1002 goto done;
1004 kerr = khtml_puts(gw_trans->gw_html_req,
1005 "Last Change: ");
1006 if (kerr != KCGI_OK) {
1007 error = gw_kcgi_error(kerr);
1008 goto done;
1010 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1011 if (kerr != KCGI_OK) {
1012 error = gw_kcgi_error(kerr);
1013 goto done;
1015 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1016 KATTR_ID, "last_change", KATTR__MAX);
1017 if (kerr != KCGI_OK) {
1018 error = gw_kcgi_error(kerr);
1019 goto done;
1021 kerr = khtml_puts(gw_trans->gw_html_req, age);
1022 if (kerr != KCGI_OK) {
1023 error = gw_kcgi_error(kerr);
1024 goto done;
1026 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1027 if (kerr != KCGI_OK) {
1028 error = gw_kcgi_error(kerr);
1029 goto done;
1034 /* cloneurl */
1035 if (gw_trans->gw_conf->got_show_repo_cloneurl &&
1036 gw_trans->gw_dir->url != NULL &&
1037 (strcmp(gw_trans->gw_dir->url, "") != 0)) {
1038 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1039 KATTR_ID, "cloneurl_title", KATTR__MAX);
1040 if (kerr != KCGI_OK) {
1041 error = gw_kcgi_error(kerr);
1042 goto done;
1044 kerr = khtml_puts(gw_trans->gw_html_req, "Clone URL: ");
1045 if (kerr != KCGI_OK) {
1046 error = gw_kcgi_error(kerr);
1047 goto done;
1049 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1050 if (kerr != KCGI_OK) {
1051 error = gw_kcgi_error(kerr);
1052 goto done;
1054 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1055 KATTR_ID, "cloneurl", KATTR__MAX);
1056 if (kerr != KCGI_OK) {
1057 error = gw_kcgi_error(kerr);
1058 goto done;
1060 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->gw_dir->url);
1061 if (kerr != KCGI_OK) {
1062 error = gw_kcgi_error(kerr);
1063 goto done;
1065 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1066 if (kerr != KCGI_OK) {
1067 error = gw_kcgi_error(kerr);
1068 goto done;
1072 /* close summary wrapper */
1073 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1074 if (kerr != KCGI_OK) {
1075 error = gw_kcgi_error(kerr);
1076 goto done;
1079 /* commit briefs header */
1080 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1081 "briefs_title_wrapper", KATTR__MAX);
1082 if (kerr != KCGI_OK) {
1083 error = gw_kcgi_error(kerr);
1084 goto done;
1086 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1087 "briefs_title", KATTR__MAX);
1088 if (kerr != KCGI_OK) {
1089 error = gw_kcgi_error(kerr);
1090 goto done;
1092 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Briefs");
1093 if (kerr != KCGI_OK) {
1094 error = gw_kcgi_error(kerr);
1095 goto done;
1097 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1098 if (kerr != KCGI_OK) {
1099 error = gw_kcgi_error(kerr);
1100 goto done;
1102 error = gw_briefs(gw_trans);
1103 if (error)
1104 goto done;
1106 /* tags */
1107 error = gw_get_repo_tags(&tags, gw_trans, NULL, D_MAXSLCOMMDISP,
1108 TAGBRIEF);
1109 if (error)
1110 goto done;
1112 if (tags != NULL && strcmp(tags, "") != 0) {
1113 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1114 KATTR_ID, "summary_tags_title_wrapper", KATTR__MAX);
1115 if (kerr != KCGI_OK) {
1116 error = gw_kcgi_error(kerr);
1117 goto done;
1119 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1120 KATTR_ID, "summary_tags_title", KATTR__MAX);
1121 if (kerr != KCGI_OK) {
1122 error = gw_kcgi_error(kerr);
1123 goto done;
1125 kerr = khtml_puts(gw_trans->gw_html_req, "Tags");
1126 if (kerr != KCGI_OK) {
1127 error = gw_kcgi_error(kerr);
1128 goto done;
1130 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1131 if (kerr != KCGI_OK) {
1132 error = gw_kcgi_error(kerr);
1133 goto done;
1135 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1136 KATTR_ID, "summary_tags_content", KATTR__MAX);
1137 if (kerr != KCGI_OK) {
1138 error = gw_kcgi_error(kerr);
1139 goto done;
1141 kerr = khttp_puts(gw_trans->gw_req, tags);
1142 if (kerr != KCGI_OK) {
1143 error = gw_kcgi_error(kerr);
1144 goto done;
1146 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1147 if (kerr != KCGI_OK) {
1148 error = gw_kcgi_error(kerr);
1149 goto done;
1153 /* heads */
1154 error = gw_get_repo_heads(&heads, gw_trans);
1155 if (error)
1156 goto done;
1157 if (heads != NULL && strcmp(heads, "") != 0) {
1158 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1159 KATTR_ID, "summary_heads_title_wrapper", KATTR__MAX);
1160 if (kerr != KCGI_OK) {
1161 error = gw_kcgi_error(kerr);
1162 goto done;
1164 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1165 KATTR_ID, "summary_heads_title", KATTR__MAX);
1166 if (kerr != KCGI_OK) {
1167 error = gw_kcgi_error(kerr);
1168 goto done;
1170 kerr = khtml_puts(gw_trans->gw_html_req, "Heads");
1171 if (kerr != KCGI_OK) {
1172 error = gw_kcgi_error(kerr);
1173 goto done;
1175 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1176 if (kerr != KCGI_OK) {
1177 error = gw_kcgi_error(kerr);
1178 goto done;
1180 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1181 KATTR_ID, "summary_heads_content", KATTR__MAX);
1182 if (kerr != KCGI_OK) {
1183 error = gw_kcgi_error(kerr);
1184 goto done;
1186 kerr = khttp_puts(gw_trans->gw_req, heads);
1187 if (kerr != KCGI_OK) {
1188 error = gw_kcgi_error(kerr);
1189 goto done;
1191 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1192 if (kerr != KCGI_OK) {
1193 error = gw_kcgi_error(kerr);
1194 goto done;
1197 done:
1198 free(age);
1199 free(tags);
1200 free(heads);
1201 return error;
1204 static const struct got_error *
1205 gw_tree(struct gw_trans *gw_trans)
1207 const struct got_error *error = NULL;
1208 struct gw_header *header = NULL;
1209 char *tree = NULL, *tree_html = NULL, *tree_html_disp = NULL;
1210 char *age = NULL, *age_html = NULL, *escaped_commit_msg = NULL;
1211 enum kcgi_err kerr;
1213 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1214 return got_error_from_errno("pledge");
1216 if ((header = gw_init_header()) == NULL)
1217 return got_error_from_errno("malloc");
1219 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1220 if (error)
1221 goto done;
1223 error = gw_get_header(gw_trans, header, 1);
1224 if (error)
1225 goto done;
1227 error = gw_get_repo_tree(&tree_html, gw_trans);
1228 if (error)
1229 goto done;
1231 error = gw_get_time_str(&age, header->committer_time, TM_LONG);
1232 if (error)
1233 goto done;
1234 if (asprintf(&age_html, header_age_html, age ? age : "") == -1) {
1235 error = got_error_from_errno("asprintf");
1236 goto done;
1238 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1239 if (error)
1240 goto done;
1241 if (asprintf(&tree_html_disp, tree_header, age_html,
1242 gw_gen_commit_msg_header(escaped_commit_msg),
1243 tree_html ? tree_html : "") == -1) {
1244 error = got_error_from_errno("asprintf");
1245 goto done;
1248 if (asprintf(&tree, tree_wrapper, tree_html_disp) == -1) {
1249 error = got_error_from_errno("asprintf");
1250 goto done;
1253 kerr = khttp_puts(gw_trans->gw_req, tree);
1254 if (kerr != KCGI_OK)
1255 error = gw_kcgi_error(kerr);
1256 done:
1257 got_ref_list_free(&header->refs);
1258 gw_free_headers(header);
1259 free(tree_html_disp);
1260 free(tree_html);
1261 free(tree);
1262 free(age);
1263 free(age_html);
1264 free(escaped_commit_msg);
1265 return error;
1268 static const struct got_error *
1269 gw_tag(struct gw_trans *gw_trans)
1271 const struct got_error *error = NULL;
1272 struct gw_header *header = NULL;
1273 char *tag = NULL, *tag_html = NULL, *tag_html_disp = NULL;
1274 char *escaped_commit_msg = NULL;
1275 enum kcgi_err kerr;
1277 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1278 return got_error_from_errno("pledge");
1280 if ((header = gw_init_header()) == NULL)
1281 return got_error_from_errno("malloc");
1283 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1284 if (error)
1285 goto done;
1287 error = gw_get_header(gw_trans, header, 1);
1288 if (error)
1289 goto done;
1291 error = gw_get_repo_tags(&tag_html, gw_trans, header, 1, TAGFULL);
1292 if (error)
1293 goto done;
1295 error = gw_html_escape(&escaped_commit_msg, header->commit_msg);
1296 if (error)
1297 goto done;
1298 if (asprintf(&tag_html_disp, tag_header,
1299 gw_gen_commit_header(header->commit_id, header->refs_str),
1300 gw_gen_commit_msg_header(escaped_commit_msg),
1301 tag_html ? tag_html : "") == -1) {
1302 error = got_error_from_errno("asprintf");
1303 goto done;
1306 if (asprintf(&tag, tag_wrapper, tag_html_disp) == -1) {
1307 error = got_error_from_errno("asprintf");
1308 goto done;
1311 kerr = khttp_puts(gw_trans->gw_req, tag);
1312 if (kerr != KCGI_OK)
1313 error = gw_kcgi_error(kerr);
1314 done:
1315 got_ref_list_free(&header->refs);
1316 gw_free_headers(header);
1317 free(tag_html_disp);
1318 free(tag_html);
1319 free(tag);
1320 free(escaped_commit_msg);
1321 return error;
1324 static const struct got_error *
1325 gw_load_got_path(struct gw_trans *gw_trans, struct gw_dir *gw_dir)
1327 const struct got_error *error = NULL;
1328 DIR *dt;
1329 char *dir_test;
1330 int opened = 0;
1332 if (asprintf(&dir_test, "%s/%s/%s",
1333 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1334 GOTWEB_GIT_DIR) == -1)
1335 return got_error_from_errno("asprintf");
1337 dt = opendir(dir_test);
1338 if (dt == NULL) {
1339 free(dir_test);
1340 } else {
1341 gw_dir->path = strdup(dir_test);
1342 opened = 1;
1343 goto done;
1346 if (asprintf(&dir_test, "%s/%s/%s",
1347 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1348 GOTWEB_GOT_DIR) == -1)
1349 return got_error_from_errno("asprintf");
1351 dt = opendir(dir_test);
1352 if (dt == NULL)
1353 free(dir_test);
1354 else {
1355 opened = 1;
1356 error = got_error(GOT_ERR_NOT_GIT_REPO);
1357 goto errored;
1360 if (asprintf(&dir_test, "%s/%s",
1361 gw_trans->gw_conf->got_repos_path, gw_dir->name) == -1)
1362 return got_error_from_errno("asprintf");
1364 gw_dir->path = strdup(dir_test);
1366 done:
1367 error = gw_get_repo_description(&gw_dir->description, gw_trans,
1368 gw_dir->path);
1369 if (error)
1370 goto errored;
1371 error = gw_get_repo_owner(&gw_dir->owner, gw_trans, gw_dir->path);
1372 if (error)
1373 goto errored;
1374 error = gw_get_repo_age(&gw_dir->age, gw_trans, gw_dir->path,
1375 "refs/heads", TM_DIFF);
1376 if (error)
1377 goto errored;
1378 error = gw_get_clone_url(&gw_dir->url, gw_trans, gw_dir->path);
1379 errored:
1380 free(dir_test);
1381 if (opened)
1382 closedir(dt);
1383 return error;
1386 static const struct got_error *
1387 gw_load_got_paths(struct gw_trans *gw_trans)
1389 const struct got_error *error = NULL;
1390 DIR *d;
1391 struct dirent **sd_dent;
1392 struct gw_dir *gw_dir;
1393 struct stat st;
1394 unsigned int d_cnt, d_i;
1396 d = opendir(gw_trans->gw_conf->got_repos_path);
1397 if (d == NULL) {
1398 error = got_error_from_errno2("opendir",
1399 gw_trans->gw_conf->got_repos_path);
1400 return error;
1403 d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
1404 alphasort);
1405 if (d_cnt == -1) {
1406 error = got_error_from_errno2("scandir",
1407 gw_trans->gw_conf->got_repos_path);
1408 return error;
1411 for (d_i = 0; d_i < d_cnt; d_i++) {
1412 if (gw_trans->gw_conf->got_max_repos > 0 &&
1413 (d_i - 2) == gw_trans->gw_conf->got_max_repos)
1414 break; /* account for parent and self */
1416 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1417 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1418 continue;
1420 if ((gw_dir = gw_init_gw_dir(sd_dent[d_i]->d_name)) == NULL)
1421 return got_error_from_errno("gw_dir malloc");
1423 error = gw_load_got_path(gw_trans, gw_dir);
1424 if (error && error->code == GOT_ERR_NOT_GIT_REPO)
1425 continue;
1426 else if (error)
1427 return error;
1429 if (lstat(gw_dir->path, &st) == 0 && S_ISDIR(st.st_mode) &&
1430 !got_path_dir_is_empty(gw_dir->path)) {
1431 TAILQ_INSERT_TAIL(&gw_trans->gw_dirs, gw_dir,
1432 entry);
1433 gw_trans->repos_total++;
1437 closedir(d);
1438 return error;
1441 static const struct got_error *
1442 gw_parse_querystring(struct gw_trans *gw_trans)
1444 const struct got_error *error = NULL;
1445 struct kpair *p;
1446 struct gw_query_action *action = NULL;
1447 unsigned int i;
1449 if (gw_trans->gw_req->fieldnmap[0]) {
1450 error = got_error_from_errno("bad parse");
1451 return error;
1452 } else if ((p = gw_trans->gw_req->fieldmap[KEY_PATH])) {
1453 /* define gw_trans->repo_path */
1454 if (asprintf(&gw_trans->repo_name, "%s", p->parsed.s) == -1)
1455 return got_error_from_errno("asprintf");
1457 if (asprintf(&gw_trans->repo_path, "%s/%s",
1458 gw_trans->gw_conf->got_repos_path, p->parsed.s) == -1)
1459 return got_error_from_errno("asprintf");
1461 /* get action and set function */
1462 if ((p = gw_trans->gw_req->fieldmap[KEY_ACTION]))
1463 for (i = 0; i < nitems(gw_query_funcs); i++) {
1464 action = &gw_query_funcs[i];
1465 if (action->func_name == NULL)
1466 continue;
1468 if (strcmp(action->func_name,
1469 p->parsed.s) == 0) {
1470 gw_trans->action = i;
1471 if (asprintf(&gw_trans->action_name,
1472 "%s", action->func_name) == -1)
1473 return
1474 got_error_from_errno(
1475 "asprintf");
1477 break;
1480 action = NULL;
1483 if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
1484 if (asprintf(&gw_trans->commit, "%s",
1485 p->parsed.s) == -1)
1486 return got_error_from_errno("asprintf");
1488 if ((p = gw_trans->gw_req->fieldmap[KEY_FILE]))
1489 if (asprintf(&gw_trans->repo_file, "%s",
1490 p->parsed.s) == -1)
1491 return got_error_from_errno("asprintf");
1493 if ((p = gw_trans->gw_req->fieldmap[KEY_FOLDER]))
1494 if (asprintf(&gw_trans->repo_folder, "%s",
1495 p->parsed.s) == -1)
1496 return got_error_from_errno("asprintf");
1498 if ((p = gw_trans->gw_req->fieldmap[KEY_HEADREF]))
1499 if (asprintf(&gw_trans->headref, "%s",
1500 p->parsed.s) == -1)
1501 return got_error_from_errno("asprintf");
1503 if (action == NULL) {
1504 error = got_error_from_errno("invalid action");
1505 return error;
1507 if ((gw_trans->gw_dir =
1508 gw_init_gw_dir(gw_trans->repo_name)) == NULL)
1509 return got_error_from_errno("gw_dir malloc");
1511 error = gw_load_got_path(gw_trans, gw_trans->gw_dir);
1512 if (error)
1513 return error;
1514 } else
1515 gw_trans->action = GW_INDEX;
1517 if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
1518 gw_trans->page = p->parsed.i;
1520 return error;
1523 static struct gw_dir *
1524 gw_init_gw_dir(char *dir)
1526 struct gw_dir *gw_dir;
1528 if ((gw_dir = malloc(sizeof(*gw_dir))) == NULL)
1529 return NULL;
1531 if (asprintf(&gw_dir->name, "%s", dir) == -1)
1532 return NULL;
1534 return gw_dir;
1537 static const struct got_error *
1538 gw_display_open(struct gw_trans *gw_trans, enum khttp code, enum kmime mime)
1540 enum kcgi_err kerr;
1542 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_ALLOW], "GET");
1543 if (kerr != KCGI_OK)
1544 return gw_kcgi_error(kerr);
1545 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_STATUS], "%s",
1546 khttps[code]);
1547 if (kerr != KCGI_OK)
1548 return gw_kcgi_error(kerr);
1549 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_CONTENT_TYPE], "%s",
1550 kmimetypes[mime]);
1551 if (kerr != KCGI_OK)
1552 return gw_kcgi_error(kerr);
1553 kerr = khttp_head(gw_trans->gw_req, "X-Content-Type-Options",
1554 "nosniff");
1555 if (kerr != KCGI_OK)
1556 return gw_kcgi_error(kerr);
1557 kerr = khttp_head(gw_trans->gw_req, "X-Frame-Options", "DENY");
1558 if (kerr != KCGI_OK)
1559 return gw_kcgi_error(kerr);
1560 kerr = khttp_head(gw_trans->gw_req, "X-XSS-Protection",
1561 "1; mode=block");
1562 if (kerr != KCGI_OK)
1563 return gw_kcgi_error(kerr);
1565 if (gw_trans->mime == KMIME_APP_OCTET_STREAM) {
1566 kerr = khttp_head(gw_trans->gw_req,
1567 kresps[KRESP_CONTENT_DISPOSITION],
1568 "attachment; filename=%s", gw_trans->repo_file);
1569 if (kerr != KCGI_OK)
1570 return gw_kcgi_error(kerr);
1573 kerr = khttp_body(gw_trans->gw_req);
1574 return gw_kcgi_error(kerr);
1577 static const struct got_error *
1578 gw_display_index(struct gw_trans *gw_trans)
1580 const struct got_error *error;
1581 enum kcgi_err kerr;
1583 error = gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
1584 if (error)
1585 return error;
1587 kerr = khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, KHTML_PRETTY);
1588 if (kerr)
1589 return gw_kcgi_error(kerr);
1591 if (gw_trans->action != GW_BLOB) {
1592 kerr = khttp_template(gw_trans->gw_req, gw_trans->gw_tmpl,
1593 gw_query_funcs[gw_trans->action].template);
1594 if (kerr != KCGI_OK) {
1595 khtml_close(gw_trans->gw_html_req);
1596 return gw_kcgi_error(kerr);
1600 return gw_kcgi_error(khtml_close(gw_trans->gw_html_req));
1603 static void
1604 gw_display_error(struct gw_trans *gw_trans, const struct got_error *err)
1606 if (gw_display_open(gw_trans, KHTTP_200, gw_trans->mime) != NULL)
1607 return;
1609 if (khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0) != KCGI_OK)
1610 return;
1611 khtml_puts(gw_trans->gw_html_req, err->msg);
1612 khtml_close(gw_trans->gw_html_req);
1615 static int
1616 gw_template(size_t key, void *arg)
1618 const struct got_error *error = NULL;
1619 enum kcgi_err kerr;
1620 struct gw_trans *gw_trans = arg;
1621 char *gw_site_link, *img_src = NULL;
1623 switch (key) {
1624 case (TEMPL_HEAD):
1625 kerr = khttp_puts(gw_trans->gw_req, head);
1626 if (kerr != KCGI_OK)
1627 return 0;
1628 break;
1629 case(TEMPL_HEADER):
1630 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1631 KATTR_ID, "got_link", KATTR__MAX);
1632 if (kerr != KCGI_OK)
1633 return 0;
1634 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1635 KATTR_HREF, gw_trans->gw_conf->got_logo_url,
1636 KATTR_TARGET, "_sotd", KATTR__MAX);
1637 if (kerr != KCGI_OK)
1638 return 0;
1639 if (asprintf(&img_src, "/%s",
1640 gw_trans->gw_conf->got_logo) == -1)
1641 return 0;
1642 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_IMG,
1643 KATTR_SRC, img_src, KATTR__MAX);
1644 if (kerr != KCGI_OK) {
1645 free(img_src);
1646 return 0;
1648 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1649 if (kerr != KCGI_OK) {
1650 free(img_src);
1651 return 0;
1653 break;
1654 case (TEMPL_SITEPATH):
1655 gw_site_link = gw_get_site_link(gw_trans);
1656 if (gw_site_link != NULL) {
1657 kerr = khttp_puts(gw_trans->gw_req, gw_site_link);
1658 if (kerr != KCGI_OK) {
1659 free(gw_site_link);
1660 return 0;
1663 free(gw_site_link);
1664 break;
1665 case(TEMPL_TITLE):
1666 if (gw_trans->gw_conf->got_site_name != NULL) {
1667 kerr = khtml_puts(gw_trans->gw_html_req,
1668 gw_trans->gw_conf->got_site_name);
1669 if (kerr != KCGI_OK)
1670 return 0;
1672 break;
1673 case (TEMPL_SEARCH):
1674 kerr = khttp_puts(gw_trans->gw_req, search);
1675 if (kerr != KCGI_OK)
1676 return 0;
1677 break;
1678 case(TEMPL_SITEOWNER):
1679 if (gw_trans->gw_conf->got_site_owner != NULL &&
1680 gw_trans->gw_conf->got_show_site_owner) {
1681 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1682 KATTR_ID, "site_owner_wrapper", KATTR__MAX);
1683 if (kerr != KCGI_OK)
1684 return 0;
1685 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1686 KATTR_ID, "site_owner", KATTR__MAX);
1687 if (kerr != KCGI_OK)
1688 return 0;
1689 kerr = khtml_puts(gw_trans->gw_html_req,
1690 gw_trans->gw_conf->got_site_owner);
1691 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1692 if (kerr != KCGI_OK)
1693 return 0;
1695 break;
1696 case(TEMPL_CONTENT):
1697 error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
1698 if (error) {
1699 kerr = khttp_puts(gw_trans->gw_req, error->msg);
1700 if (kerr != KCGI_OK)
1701 return 0;
1703 break;
1704 default:
1705 return 0;
1707 return 1;
1710 static char *
1711 gw_gen_commit_header(char *str1, char *str2)
1713 char *return_html = NULL, *ref_str = NULL;
1715 if (strcmp(str2, "") != 0) {
1716 if (asprintf(&ref_str, "(%s)", str2) == -1) {
1717 return_html = strdup("");
1718 return return_html;
1720 } else
1721 ref_str = strdup("");
1724 if (asprintf(&return_html, header_commit_html, str1, ref_str) == -1)
1725 return_html = strdup("");
1727 free(ref_str);
1728 return return_html;
1731 static char *
1732 gw_gen_diff_header(char *str1, char *str2)
1734 char *return_html = NULL;
1736 if (asprintf(&return_html, header_diff_html, str1, str2) == -1)
1737 return_html = strdup("");
1739 return return_html;
1742 static char *
1743 gw_gen_author_header(const char *str)
1745 char *return_html = NULL;
1747 if (asprintf(&return_html, header_author_html, str) == -1)
1748 return_html = strdup("");
1750 return return_html;
1753 static char *
1754 gw_gen_committer_header(char *str)
1756 char *return_html = NULL;
1758 if (asprintf(&return_html, header_committer_html, str) == -1)
1759 return_html = strdup("");
1761 return return_html;
1764 static char *
1765 gw_gen_commit_msg_header(char *str)
1767 char *return_html = NULL;
1769 if (asprintf(&return_html, header_commit_msg_html, str) == -1)
1770 return_html = strdup("");
1772 return return_html;
1775 static char *
1776 gw_gen_tree_header(char *str)
1778 char *return_html = NULL;
1780 if (asprintf(&return_html, header_tree_html, str) == -1)
1781 return_html = strdup("");
1783 return return_html;
1786 static const struct got_error *
1787 gw_get_repo_description(char **description, struct gw_trans *gw_trans,
1788 char *dir)
1790 const struct got_error *error = NULL;
1791 FILE *f = NULL;
1792 char *d_file = NULL;
1793 unsigned int len;
1794 size_t n;
1796 *description = NULL;
1797 if (gw_trans->gw_conf->got_show_repo_description == 0)
1798 return gw_empty_string(description);
1800 if (asprintf(&d_file, "%s/description", dir) == -1)
1801 return got_error_from_errno("asprintf");
1803 f = fopen(d_file, "r");
1804 if (f == NULL) {
1805 if (errno == ENOENT || errno == EACCES)
1806 return gw_empty_string(description);
1807 error = got_error_from_errno2("fopen", d_file);
1808 goto done;
1811 if (fseek(f, 0, SEEK_END) == -1) {
1812 error = got_ferror(f, GOT_ERR_IO);
1813 goto done;
1815 len = ftell(f);
1816 if (len == -1) {
1817 error = got_ferror(f, GOT_ERR_IO);
1818 goto done;
1820 if (fseek(f, 0, SEEK_SET) == -1) {
1821 error = got_ferror(f, GOT_ERR_IO);
1822 goto done;
1824 *description = calloc(len + 1, sizeof(**description));
1825 if (*description == NULL) {
1826 error = got_error_from_errno("calloc");
1827 goto done;
1830 n = fread(*description, 1, len, f);
1831 if (n == 0 && ferror(f))
1832 error = got_ferror(f, GOT_ERR_IO);
1833 done:
1834 if (f != NULL && fclose(f) == -1 && error == NULL)
1835 error = got_error_from_errno("fclose");
1836 free(d_file);
1837 return error;
1840 static const struct got_error *
1841 gw_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
1843 struct tm tm;
1844 time_t diff_time;
1845 char *years = "years ago", *months = "months ago";
1846 char *weeks = "weeks ago", *days = "days ago", *hours = "hours ago";
1847 char *minutes = "minutes ago", *seconds = "seconds ago";
1848 char *now = "right now";
1849 char *s;
1850 char datebuf[29];
1852 *repo_age = NULL;
1854 switch (ref_tm) {
1855 case TM_DIFF:
1856 diff_time = time(NULL) - committer_time;
1857 if (diff_time > 60 * 60 * 24 * 365 * 2) {
1858 if (asprintf(repo_age, "%lld %s",
1859 (diff_time / 60 / 60 / 24 / 365), years) == -1)
1860 return got_error_from_errno("asprintf");
1861 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
1862 if (asprintf(repo_age, "%lld %s",
1863 (diff_time / 60 / 60 / 24 / (365 / 12)),
1864 months) == -1)
1865 return got_error_from_errno("asprintf");
1866 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
1867 if (asprintf(repo_age, "%lld %s",
1868 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
1869 return got_error_from_errno("asprintf");
1870 } else if (diff_time > 60 * 60 * 24 * 2) {
1871 if (asprintf(repo_age, "%lld %s",
1872 (diff_time / 60 / 60 / 24), days) == -1)
1873 return got_error_from_errno("asprintf");
1874 } else if (diff_time > 60 * 60 * 2) {
1875 if (asprintf(repo_age, "%lld %s",
1876 (diff_time / 60 / 60), hours) == -1)
1877 return got_error_from_errno("asprintf");
1878 } else if (diff_time > 60 * 2) {
1879 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
1880 minutes) == -1)
1881 return got_error_from_errno("asprintf");
1882 } else if (diff_time > 2) {
1883 if (asprintf(repo_age, "%lld %s", diff_time,
1884 seconds) == -1)
1885 return got_error_from_errno("asprintf");
1886 } else {
1887 if (asprintf(repo_age, "%s", now) == -1)
1888 return got_error_from_errno("asprintf");
1890 break;
1891 case TM_LONG:
1892 if (gmtime_r(&committer_time, &tm) == NULL)
1893 return got_error_from_errno("gmtime_r");
1895 s = asctime_r(&tm, datebuf);
1896 if (s == NULL)
1897 return got_error_from_errno("asctime_r");
1899 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
1900 return got_error_from_errno("asprintf");
1901 break;
1903 return NULL;
1906 static const struct got_error *
1907 gw_get_repo_age(char **repo_age, struct gw_trans *gw_trans, char *dir,
1908 char *repo_ref, int ref_tm)
1910 const struct got_error *error = NULL;
1911 struct got_object_id *id = NULL;
1912 struct got_repository *repo = NULL;
1913 struct got_commit_object *commit = NULL;
1914 struct got_reflist_head refs;
1915 struct got_reflist_entry *re;
1916 struct got_reference *head_ref;
1917 int is_head = 0;
1918 time_t committer_time = 0, cmp_time = 0;
1919 const char *refname;
1921 *repo_age = NULL;
1922 SIMPLEQ_INIT(&refs);
1924 if (repo_ref == NULL)
1925 return NULL;
1927 if (strncmp(repo_ref, "refs/heads/", 11) == 0)
1928 is_head = 1;
1930 if (gw_trans->gw_conf->got_show_repo_age == 0)
1931 return NULL;
1933 error = got_repo_open(&repo, dir, NULL);
1934 if (error)
1935 goto done;
1937 if (is_head)
1938 error = got_ref_list(&refs, repo, "refs/heads",
1939 got_ref_cmp_by_name, NULL);
1940 else
1941 error = got_ref_list(&refs, repo, repo_ref,
1942 got_ref_cmp_by_name, NULL);
1943 if (error)
1944 goto done;
1946 SIMPLEQ_FOREACH(re, &refs, entry) {
1947 if (is_head)
1948 refname = strdup(repo_ref);
1949 else
1950 refname = got_ref_get_name(re->ref);
1951 error = got_ref_open(&head_ref, repo, refname, 0);
1952 if (error)
1953 goto done;
1955 error = got_ref_resolve(&id, repo, head_ref);
1956 got_ref_close(head_ref);
1957 if (error)
1958 goto done;
1960 error = got_object_open_as_commit(&commit, repo, id);
1961 if (error)
1962 goto done;
1964 committer_time =
1965 got_object_commit_get_committer_time(commit);
1967 if (cmp_time < committer_time)
1968 cmp_time = committer_time;
1971 if (cmp_time != 0) {
1972 committer_time = cmp_time;
1973 error = gw_get_time_str(repo_age, committer_time, ref_tm);
1975 done:
1976 got_ref_list_free(&refs);
1977 free(id);
1978 return error;
1981 static const struct got_error *
1982 gw_get_diff(char **diff_html, struct gw_trans *gw_trans,
1983 struct gw_header *header)
1985 const struct got_error *error;
1986 FILE *f = NULL;
1987 struct got_object_id *id1 = NULL, *id2 = NULL;
1988 struct buf *diffbuf = NULL;
1989 char *label1 = NULL, *label2 = NULL, *line = NULL;
1990 char *diff_line_html = NULL;
1991 int obj_type;
1992 size_t newsize, linesize = 0;
1993 ssize_t linelen;
1995 f = got_opentemp();
1996 if (f == NULL)
1997 return NULL;
1999 error = buf_alloc(&diffbuf, 0);
2000 if (error)
2001 goto done;
2003 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2004 if (error)
2005 goto done;
2007 if (strncmp(header->parent_id, "/dev/null", 9) != 0) {
2008 error = got_repo_match_object_id(&id1, &label1,
2009 header->parent_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2010 if (error)
2011 goto done;
2014 error = got_repo_match_object_id(&id2, &label2,
2015 header->commit_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2016 if (error)
2017 goto done;
2019 error = got_object_get_type(&obj_type, header->repo, id2);
2020 if (error)
2021 goto done;
2022 switch (obj_type) {
2023 case GOT_OBJ_TYPE_BLOB:
2024 error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, 3, 0,
2025 header->repo, f);
2026 break;
2027 case GOT_OBJ_TYPE_TREE:
2028 error = got_diff_objects_as_trees(id1, id2, "", "", 3, 0,
2029 header->repo, f);
2030 break;
2031 case GOT_OBJ_TYPE_COMMIT:
2032 error = got_diff_objects_as_commits(id1, id2, 3, 0,
2033 header->repo, f);
2034 break;
2035 default:
2036 error = got_error(GOT_ERR_OBJ_TYPE);
2038 if (error)
2039 goto done;
2041 if (fseek(f, 0, SEEK_SET) == -1) {
2042 error = got_ferror(f, GOT_ERR_IO);
2043 goto done;
2046 while ((linelen = getline(&line, &linesize, f)) != -1) {
2047 char *escaped_line;
2048 error = gw_html_escape(&escaped_line, line);
2049 if (error)
2050 goto done;
2052 error = gw_colordiff_line(&diff_line_html, escaped_line);
2053 if (error)
2054 goto done;
2056 error = buf_puts(&newsize, diffbuf, diff_line_html);
2057 if (error)
2058 goto done;
2060 error = buf_puts(&newsize, diffbuf, div_end);
2061 if (error)
2062 goto done;
2064 if (linelen == -1 && ferror(f)) {
2065 error = got_error_from_errno("getline");
2066 goto done;
2069 if (buf_len(diffbuf) > 0) {
2070 error = buf_putc(diffbuf, '\0');
2071 if (error)
2072 goto done;
2073 *diff_html = strdup(buf_get(diffbuf));
2074 if (*diff_html == NULL)
2075 error = got_error_from_errno("strdup");
2077 done:
2078 if (f && fclose(f) == -1 && error == NULL)
2079 error = got_error_from_errno("fclose");
2080 free(diff_line_html);
2081 free(line);
2082 free(diffbuf);
2083 free(label1);
2084 free(label2);
2085 free(id1);
2086 free(id2);
2088 return error;
2091 static const struct got_error *
2092 gw_get_repo_owner(char **owner, struct gw_trans *gw_trans, char *dir)
2094 const struct got_error *error = NULL;
2095 struct got_repository *repo;
2096 const char *gitconfig_owner;
2098 *owner = NULL;
2100 if (gw_trans->gw_conf->got_show_repo_owner == 0)
2101 return NULL;
2103 error = got_repo_open(&repo, dir, NULL);
2104 if (error)
2105 return error;
2106 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
2107 if (gitconfig_owner) {
2108 *owner = strdup(gitconfig_owner);
2109 if (*owner == NULL)
2110 error = got_error_from_errno("strdup");
2112 got_repo_close(repo);
2113 return error;
2116 static const struct got_error *
2117 gw_get_clone_url(char **url, struct gw_trans *gw_trans, char *dir)
2119 const struct got_error *error = NULL;
2120 FILE *f;
2121 char *d_file = NULL;
2122 unsigned int len;
2123 size_t n;
2125 *url = NULL;
2127 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2128 return got_error_from_errno("asprintf");
2130 f = fopen(d_file, "r");
2131 if (f == NULL) {
2132 if (errno != ENOENT && errno != EACCES)
2133 error = got_error_from_errno2("fopen", d_file);
2134 goto done;
2137 if (fseek(f, 0, SEEK_END) == -1) {
2138 error = got_ferror(f, GOT_ERR_IO);
2139 goto done;
2141 len = ftell(f);
2142 if (len == -1) {
2143 error = got_ferror(f, GOT_ERR_IO);
2144 goto done;
2146 if (fseek(f, 0, SEEK_SET) == -1) {
2147 error = got_ferror(f, GOT_ERR_IO);
2148 goto done;
2151 *url = calloc(len + 1, sizeof(**url));
2152 if (*url == NULL) {
2153 error = got_error_from_errno("calloc");
2154 goto done;
2157 n = fread(*url, 1, len, f);
2158 if (n == 0 && ferror(f))
2159 error = got_ferror(f, GOT_ERR_IO);
2160 done:
2161 if (f && fclose(f) == -1 && error == NULL)
2162 error = got_error_from_errno("fclose");
2163 free(d_file);
2164 return NULL;
2167 static const struct got_error *
2168 gw_get_repo_tags(char **tag_html, struct gw_trans *gw_trans,
2169 struct gw_header *header, int limit, int tag_type)
2171 const struct got_error *error = NULL;
2172 struct got_repository *repo = NULL;
2173 struct got_reflist_head refs;
2174 struct got_reflist_entry *re;
2175 char *tag_row = NULL, *tags_navs_disp = NULL;
2176 char *age = NULL, *age_html = NULL, *newline;
2177 char *escaped_tagger = NULL, *escaped_tag_commit = NULL;
2178 char *id_str = NULL, *refstr = NULL;
2179 char *tag_commit0 = NULL;
2180 struct buf *diffbuf = NULL;
2181 size_t newsize;
2182 struct got_tag_object *tag = NULL;
2184 *tag_html = NULL;
2186 SIMPLEQ_INIT(&refs);
2188 error = buf_alloc(&diffbuf, 0);
2189 if (error)
2190 return NULL;
2192 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2193 if (error)
2194 goto done;
2196 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo);
2197 if (error)
2198 goto done;
2200 SIMPLEQ_FOREACH(re, &refs, entry) {
2201 const char *refname;
2202 const char *tagger;
2203 const char *tag_commit;
2204 time_t tagger_time;
2205 struct got_object_id *id;
2207 refname = got_ref_get_name(re->ref);
2208 if (strncmp(refname, "refs/tags/", 10) != 0)
2209 continue;
2210 refname += 10;
2211 refstr = got_ref_to_str(re->ref);
2212 if (refstr == NULL) {
2213 error = got_error_from_errno("got_ref_to_str");
2214 goto done;
2217 error = got_ref_resolve(&id, repo, re->ref);
2218 if (error)
2219 goto done;
2220 error = got_object_open_as_tag(&tag, repo, id);
2221 free(id);
2222 if (error)
2223 goto done;
2225 tagger = got_object_tag_get_tagger(tag);
2226 tagger_time = got_object_tag_get_tagger_time(tag);
2228 error = got_object_id_str(&id_str,
2229 got_object_tag_get_object_id(tag));
2230 if (error)
2231 goto done;
2233 tag_commit0 = strdup(got_object_tag_get_message(tag));
2234 if (tag_commit0 == NULL) {
2235 error = got_error_from_errno("strdup");
2236 goto done;
2239 tag_commit = tag_commit0;
2240 while (*tag_commit == '\n')
2241 tag_commit++;
2243 switch (tag_type) {
2244 case TAGBRIEF:
2245 newline = strchr(tag_commit, '\n');
2246 if (newline)
2247 *newline = '\0';
2249 error = gw_get_time_str(&age, tagger_time, TM_DIFF);
2250 if (error)
2251 goto done;
2253 if (asprintf(&tags_navs_disp, tags_navs,
2254 gw_trans->repo_name, id_str, gw_trans->repo_name,
2255 id_str, gw_trans->repo_name, id_str,
2256 gw_trans->repo_name, id_str) == -1) {
2257 error = got_error_from_errno("asprintf");
2258 goto done;
2261 if (asprintf(&tag_row, tags_row, age ? age : "",
2262 refname, tag_commit, tags_navs_disp) == -1) {
2263 error = got_error_from_errno("asprintf");
2264 goto done;
2267 free(tags_navs_disp);
2268 break;
2269 case TAGFULL:
2270 error = gw_get_time_str(&age, tagger_time, TM_LONG);
2271 if (error)
2272 goto done;
2273 error = gw_html_escape(&escaped_tagger, tagger);
2274 if (error)
2275 goto done;
2276 error = gw_html_escape(&escaped_tag_commit, tag_commit);
2277 if (error)
2278 goto done;
2279 if (asprintf(&tag_row, tag_info, age ? age : "",
2280 escaped_tagger, escaped_tag_commit) == -1) {
2281 error = got_error_from_errno("asprintf");
2282 goto done;
2284 break;
2285 default:
2286 break;
2289 error = buf_puts(&newsize, diffbuf, tag_row);
2290 if (error)
2291 goto done;
2293 if (limit && --limit == 0)
2294 break;
2296 got_object_tag_close(tag);
2297 tag = NULL;
2298 free(id_str);
2299 id_str = NULL;
2300 free(refstr);
2301 refstr = NULL;
2302 free(age);
2303 age = NULL;
2304 free(age_html);
2305 age_html = NULL;
2306 free(escaped_tagger);
2307 escaped_tagger = NULL;
2308 free(escaped_tag_commit);
2309 escaped_tag_commit = NULL;
2310 free(tag_commit0);
2311 tag_commit0 = NULL;
2312 free(tag_row);
2313 tag_row = NULL;
2316 if (buf_len(diffbuf) > 0) {
2317 error = buf_putc(diffbuf, '\0');
2318 *tag_html = strdup(buf_get(diffbuf));
2319 if (*tag_html == NULL)
2320 error = got_error_from_errno("strdup");
2322 done:
2323 if (tag)
2324 got_object_tag_close(tag);
2325 free(id_str);
2326 free(refstr);
2327 free(age);
2328 free(age_html);
2329 free(escaped_tagger);
2330 free(escaped_tag_commit);
2331 free(tag_commit0);
2332 free(tag_row);
2333 buf_free(diffbuf);
2334 got_ref_list_free(&refs);
2335 if (repo)
2336 got_repo_close(repo);
2337 return error;
2340 static void
2341 gw_free_headers(struct gw_header *header)
2343 free(header->id);
2344 free(header->path);
2345 if (header->commit != NULL)
2346 got_object_commit_close(header->commit);
2347 if (header->repo)
2348 got_repo_close(header->repo);
2349 free(header->refs_str);
2350 free(header->commit_id);
2351 free(header->parent_id);
2352 free(header->tree_id);
2353 free(header->committer);
2354 free(header->commit_msg);
2357 static struct gw_header *
2358 gw_init_header()
2360 struct gw_header *header;
2362 header = malloc(sizeof(*header));
2363 if (header == NULL)
2364 return NULL;
2366 header->repo = NULL;
2367 header->commit = NULL;
2368 header->id = NULL;
2369 header->path = NULL;
2370 SIMPLEQ_INIT(&header->refs);
2372 return header;
2375 static const struct got_error *
2376 gw_get_commits(struct gw_trans * gw_trans, struct gw_header *header,
2377 int limit)
2379 const struct got_error *error = NULL;
2380 struct got_commit_graph *graph = NULL;
2382 error = got_commit_graph_open(&graph, header->path, 0);
2383 if (error)
2384 goto done;
2386 error = got_commit_graph_iter_start(graph, header->id, header->repo,
2387 NULL, NULL);
2388 if (error)
2389 goto done;
2391 for (;;) {
2392 error = got_commit_graph_iter_next(&header->id, graph,
2393 header->repo, NULL, NULL);
2394 if (error) {
2395 if (error->code == GOT_ERR_ITER_COMPLETED)
2396 error = NULL;
2397 goto done;
2399 if (header->id == NULL)
2400 goto done;
2402 error = got_object_open_as_commit(&header->commit, header->repo,
2403 header->id);
2404 if (error)
2405 goto done;
2407 error = gw_get_commit(gw_trans, header);
2408 if (limit > 1) {
2409 struct gw_header *n_header = NULL;
2410 if ((n_header = gw_init_header()) == NULL) {
2411 error = got_error_from_errno("malloc");
2412 goto done;
2415 n_header->refs_str = strdup(header->refs_str);
2416 n_header->commit_id = strdup(header->commit_id);
2417 n_header->parent_id = strdup(header->parent_id);
2418 n_header->tree_id = strdup(header->tree_id);
2419 n_header->author = strdup(header->author);
2420 n_header->committer = strdup(header->committer);
2421 n_header->commit_msg = strdup(header->commit_msg);
2422 n_header->committer_time = header->committer_time;
2423 TAILQ_INSERT_TAIL(&gw_trans->gw_headers, n_header,
2424 entry);
2426 if (error || (limit && --limit == 0))
2427 break;
2429 done:
2430 if (graph)
2431 got_commit_graph_close(graph);
2432 return error;
2435 static const struct got_error *
2436 gw_get_commit(struct gw_trans *gw_trans, struct gw_header *header)
2438 const struct got_error *error = NULL;
2439 struct got_reflist_entry *re;
2440 struct got_object_id *id2 = NULL;
2441 struct got_object_qid *parent_id;
2442 char *refs_str = NULL, *commit_msg = NULL, *commit_msg0;
2444 /*print commit*/
2445 SIMPLEQ_FOREACH(re, &header->refs, entry) {
2446 char *s;
2447 const char *name;
2448 struct got_tag_object *tag = NULL;
2449 int cmp;
2451 name = got_ref_get_name(re->ref);
2452 if (strcmp(name, GOT_REF_HEAD) == 0)
2453 continue;
2454 if (strncmp(name, "refs/", 5) == 0)
2455 name += 5;
2456 if (strncmp(name, "got/", 4) == 0)
2457 continue;
2458 if (strncmp(name, "heads/", 6) == 0)
2459 name += 6;
2460 if (strncmp(name, "remotes/", 8) == 0)
2461 name += 8;
2462 if (strncmp(name, "tags/", 5) == 0) {
2463 error = got_object_open_as_tag(&tag, header->repo,
2464 re->id);
2465 if (error) {
2466 if (error->code != GOT_ERR_OBJ_TYPE)
2467 continue;
2469 * Ref points at something other
2470 * than a tag.
2472 error = NULL;
2473 tag = NULL;
2476 cmp = got_object_id_cmp(tag ?
2477 got_object_tag_get_object_id(tag) : re->id, header->id);
2478 if (tag)
2479 got_object_tag_close(tag);
2480 if (cmp != 0)
2481 continue;
2482 s = refs_str;
2483 if (asprintf(&refs_str, "%s%s%s", s ? s : "",
2484 s ? ", " : "", name) == -1) {
2485 error = got_error_from_errno("asprintf");
2486 free(s);
2487 return error;
2489 header->refs_str = strdup(refs_str);
2490 free(s);
2493 if (refs_str == NULL)
2494 header->refs_str = strdup("");
2495 free(refs_str);
2497 error = got_object_id_str(&header->commit_id, header->id);
2498 if (error)
2499 return error;
2501 error = got_object_id_str(&header->tree_id,
2502 got_object_commit_get_tree_id(header->commit));
2503 if (error)
2504 return error;
2506 if (gw_trans->action == GW_DIFF) {
2507 parent_id = SIMPLEQ_FIRST(
2508 got_object_commit_get_parent_ids(header->commit));
2509 if (parent_id != NULL) {
2510 id2 = got_object_id_dup(parent_id->id);
2511 free (parent_id);
2512 error = got_object_id_str(&header->parent_id, id2);
2513 if (error)
2514 return error;
2515 free(id2);
2516 } else
2517 header->parent_id = strdup("/dev/null");
2518 } else
2519 header->parent_id = strdup("");
2521 header->committer_time =
2522 got_object_commit_get_committer_time(header->commit);
2524 header->author =
2525 got_object_commit_get_author(header->commit);
2526 error = gw_html_escape(&header->committer,
2527 got_object_commit_get_committer(header->commit));
2528 if (error)
2529 return error;
2531 /* XXX Doesn't the log message require escaping? */
2532 error = got_object_commit_get_logmsg(&commit_msg0, header->commit);
2533 if (error)
2534 return error;
2536 commit_msg = commit_msg0;
2537 while (*commit_msg == '\n')
2538 commit_msg++;
2540 header->commit_msg = strdup(commit_msg);
2541 free(commit_msg0);
2542 return error;
2545 static const struct got_error *
2546 gw_get_header(struct gw_trans *gw_trans, struct gw_header *header, int limit)
2548 const struct got_error *error = NULL;
2549 char *in_repo_path = NULL;
2551 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2552 if (error)
2553 return error;
2555 if (gw_trans->commit == NULL) {
2556 struct got_reference *head_ref;
2557 error = got_ref_open(&head_ref, header->repo,
2558 gw_trans->headref, 0);
2559 if (error)
2560 return error;
2562 error = got_ref_resolve(&header->id, header->repo, head_ref);
2563 got_ref_close(head_ref);
2564 if (error)
2565 return error;
2567 error = got_object_open_as_commit(&header->commit,
2568 header->repo, header->id);
2569 } else {
2570 struct got_reference *ref;
2571 error = got_ref_open(&ref, header->repo, gw_trans->commit, 0);
2572 if (error == NULL) {
2573 int obj_type;
2574 error = got_ref_resolve(&header->id, header->repo, ref);
2575 got_ref_close(ref);
2576 if (error)
2577 return error;
2578 error = got_object_get_type(&obj_type, header->repo,
2579 header->id);
2580 if (error)
2581 return error;
2582 if (obj_type == GOT_OBJ_TYPE_TAG) {
2583 struct got_tag_object *tag;
2584 error = got_object_open_as_tag(&tag,
2585 header->repo, header->id);
2586 if (error)
2587 return error;
2588 if (got_object_tag_get_object_type(tag) !=
2589 GOT_OBJ_TYPE_COMMIT) {
2590 got_object_tag_close(tag);
2591 error = got_error(GOT_ERR_OBJ_TYPE);
2592 return error;
2594 free(header->id);
2595 header->id = got_object_id_dup(
2596 got_object_tag_get_object_id(tag));
2597 if (header->id == NULL)
2598 error = got_error_from_errno(
2599 "got_object_id_dup");
2600 got_object_tag_close(tag);
2601 if (error)
2602 return error;
2603 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
2604 error = got_error(GOT_ERR_OBJ_TYPE);
2605 return error;
2607 error = got_object_open_as_commit(&header->commit,
2608 header->repo, header->id);
2609 if (error)
2610 return error;
2612 if (header->commit == NULL) {
2613 error = got_repo_match_object_id_prefix(&header->id,
2614 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2615 header->repo);
2616 if (error)
2617 return error;
2619 error = got_repo_match_object_id_prefix(&header->id,
2620 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
2621 header->repo);
2624 error = got_repo_map_path(&in_repo_path, header->repo,
2625 gw_trans->repo_path, 1);
2626 if (error)
2627 return error;
2629 if (in_repo_path) {
2630 header->path = strdup(in_repo_path);
2632 free(in_repo_path);
2634 error = got_ref_list(&header->refs, header->repo, NULL,
2635 got_ref_cmp_by_name, NULL);
2636 if (error)
2637 return error;
2639 error = gw_get_commits(gw_trans, header, limit);
2640 return error;
2643 struct blame_line {
2644 int annotated;
2645 char *id_str;
2646 char *committer;
2647 char datebuf[11]; /* YYYY-MM-DD + NUL */
2650 struct gw_blame_cb_args {
2651 struct blame_line *lines;
2652 int nlines;
2653 int nlines_prec;
2654 int lineno_cur;
2655 off_t *line_offsets;
2656 FILE *f;
2657 struct got_repository *repo;
2658 struct gw_trans *gw_trans;
2659 struct buf *blamebuf;
2662 static const struct got_error *
2663 gw_blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
2665 const struct got_error *err = NULL;
2666 struct gw_blame_cb_args *a = arg;
2667 struct blame_line *bline;
2668 char *line = NULL;
2669 size_t linesize = 0, newsize;
2670 struct got_commit_object *commit = NULL;
2671 off_t offset;
2672 struct tm tm;
2673 time_t committer_time;
2675 if (nlines != a->nlines ||
2676 (lineno != -1 && lineno < 1) || lineno > a->nlines)
2677 return got_error(GOT_ERR_RANGE);
2679 if (lineno == -1)
2680 return NULL; /* no change in this commit */
2682 /* Annotate this line. */
2683 bline = &a->lines[lineno - 1];
2684 if (bline->annotated)
2685 return NULL;
2686 err = got_object_id_str(&bline->id_str, id);
2687 if (err)
2688 return err;
2690 err = got_object_open_as_commit(&commit, a->repo, id);
2691 if (err)
2692 goto done;
2694 bline->committer = strdup(got_object_commit_get_committer(commit));
2695 if (bline->committer == NULL) {
2696 err = got_error_from_errno("strdup");
2697 goto done;
2700 committer_time = got_object_commit_get_committer_time(commit);
2701 if (localtime_r(&committer_time, &tm) == NULL)
2702 return got_error_from_errno("localtime_r");
2703 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
2704 &tm) >= sizeof(bline->datebuf)) {
2705 err = got_error(GOT_ERR_NO_SPACE);
2706 goto done;
2708 bline->annotated = 1;
2710 /* Print lines annotated so far. */
2711 bline = &a->lines[a->lineno_cur - 1];
2712 if (!bline->annotated)
2713 goto done;
2715 offset = a->line_offsets[a->lineno_cur - 1];
2716 if (fseeko(a->f, offset, SEEK_SET) == -1) {
2717 err = got_error_from_errno("fseeko");
2718 goto done;
2721 while (bline->annotated) {
2722 char *smallerthan, *at, *nl, *committer, *blame_row = NULL,
2723 *line_escape = NULL;
2724 size_t len;
2726 if (getline(&line, &linesize, a->f) == -1) {
2727 if (ferror(a->f))
2728 err = got_error_from_errno("getline");
2729 break;
2732 committer = bline->committer;
2733 smallerthan = strchr(committer, '<');
2734 if (smallerthan && smallerthan[1] != '\0')
2735 committer = smallerthan + 1;
2736 at = strchr(committer, '@');
2737 if (at)
2738 *at = '\0';
2739 len = strlen(committer);
2740 if (len >= 9)
2741 committer[8] = '\0';
2743 nl = strchr(line, '\n');
2744 if (nl)
2745 *nl = '\0';
2747 err = gw_html_escape(&line_escape, line);
2748 if (err)
2749 goto err;
2751 if (a->gw_trans->repo_folder == NULL)
2752 a->gw_trans->repo_folder = strdup("");
2753 if (a->gw_trans->repo_folder == NULL)
2754 goto err;
2755 asprintf(&blame_row, blame_line, a->nlines_prec, a->lineno_cur,
2756 a->gw_trans->repo_name, bline->id_str,
2757 a->gw_trans->repo_file, a->gw_trans->repo_folder,
2758 bline->id_str, bline->datebuf, committer, line_escape);
2759 a->lineno_cur++;
2760 err = buf_puts(&newsize, a->blamebuf, blame_row);
2761 if (err)
2762 return err;
2764 bline = &a->lines[a->lineno_cur - 1];
2765 err:
2766 free(line_escape);
2767 free(blame_row);
2769 done:
2770 if (commit)
2771 got_object_commit_close(commit);
2772 free(line);
2773 return err;
2776 static const struct got_error *
2777 gw_get_file_blame_blob(char **blame_html, struct gw_trans *gw_trans)
2779 const struct got_error *error = NULL;
2780 struct got_repository *repo = NULL;
2781 struct got_object_id *obj_id = NULL;
2782 struct got_object_id *commit_id = NULL;
2783 struct got_blob_object *blob = NULL;
2784 char *path = NULL, *in_repo_path = NULL;
2785 struct gw_blame_cb_args bca;
2786 int i, obj_type;
2787 size_t filesize;
2789 *blame_html = NULL;
2791 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2792 if (error)
2793 return error;
2795 if (asprintf(&path, "%s%s%s",
2796 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2797 gw_trans->repo_folder ? "/" : "",
2798 gw_trans->repo_file) == -1) {
2799 error = got_error_from_errno("asprintf");
2800 goto done;
2803 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2804 if (error)
2805 goto done;
2807 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2808 GOT_OBJ_TYPE_COMMIT, 1, repo);
2809 if (error)
2810 goto done;
2812 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2813 if (error)
2814 goto done;
2816 if (obj_id == NULL) {
2817 error = got_error(GOT_ERR_NO_OBJ);
2818 goto done;
2821 error = got_object_get_type(&obj_type, repo, obj_id);
2822 if (error)
2823 goto done;
2825 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2826 error = got_error(GOT_ERR_OBJ_TYPE);
2827 goto done;
2830 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2831 if (error)
2832 goto done;
2834 error = buf_alloc(&bca.blamebuf, 0);
2835 if (error)
2836 goto done;
2838 bca.f = got_opentemp();
2839 if (bca.f == NULL) {
2840 error = got_error_from_errno("got_opentemp");
2841 goto done;
2843 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
2844 &bca.line_offsets, bca.f, blob);
2845 if (error || bca.nlines == 0)
2846 goto done;
2848 /* Don't include \n at EOF in the blame line count. */
2849 if (bca.line_offsets[bca.nlines - 1] == filesize)
2850 bca.nlines--;
2852 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
2853 if (bca.lines == NULL) {
2854 error = got_error_from_errno("calloc");
2855 goto done;
2857 bca.lineno_cur = 1;
2858 bca.nlines_prec = 0;
2859 i = bca.nlines;
2860 while (i > 0) {
2861 i /= 10;
2862 bca.nlines_prec++;
2864 bca.repo = repo;
2865 bca.gw_trans = gw_trans;
2867 error = got_blame(in_repo_path, commit_id, repo, gw_blame_cb, &bca,
2868 NULL, NULL);
2869 if (error)
2870 goto done;
2871 if (buf_len(bca.blamebuf) > 0) {
2872 error = buf_putc(bca.blamebuf, '\0');
2873 if (error)
2874 goto done;
2875 *blame_html = strdup(buf_get(bca.blamebuf));
2876 if (*blame_html == NULL) {
2877 error = got_error_from_errno("strdup");
2878 goto done;
2881 done:
2882 free(bca.line_offsets);
2883 free(bca.blamebuf);
2884 free(in_repo_path);
2885 free(commit_id);
2886 free(obj_id);
2887 free(path);
2889 for (i = 0; i < bca.nlines; i++) {
2890 struct blame_line *bline = &bca.lines[i];
2891 free(bline->id_str);
2892 free(bline->committer);
2894 free(bca.lines);
2895 if (bca.f && fclose(bca.f) == EOF && error == NULL)
2896 error = got_error_from_errno("fclose");
2897 if (blob)
2898 got_object_blob_close(blob);
2899 if (repo)
2900 got_repo_close(repo);
2901 return error;
2904 static const struct got_error *
2905 gw_get_file_read_blob(char **blobstr, size_t *filesize, struct gw_trans *gw_trans)
2907 const struct got_error *error = NULL;
2908 struct got_repository *repo = NULL;
2909 struct got_object_id *obj_id = NULL;
2910 struct got_object_id *commit_id = NULL;
2911 struct got_blob_object *blob = NULL;
2912 char *path = NULL, *in_repo_path = NULL;
2913 int obj_type;
2914 size_t n;
2915 FILE *f = NULL;
2917 *blobstr = NULL;
2918 *filesize = 0;
2920 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2921 if (error)
2922 return error;
2924 if (asprintf(&path, "%s%s%s",
2925 gw_trans->repo_folder ? gw_trans->repo_folder : "",
2926 gw_trans->repo_folder ? "/" : "",
2927 gw_trans->repo_file) == -1) {
2928 error = got_error_from_errno("asprintf");
2929 goto done;
2932 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2933 if (error)
2934 goto done;
2936 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
2937 GOT_OBJ_TYPE_COMMIT, 1, repo);
2938 if (error)
2939 goto done;
2941 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
2942 if (error)
2943 goto done;
2945 if (obj_id == NULL) {
2946 error = got_error(GOT_ERR_NO_OBJ);
2947 goto done;
2950 error = got_object_get_type(&obj_type, repo, obj_id);
2951 if (error)
2952 goto done;
2954 if (obj_type != GOT_OBJ_TYPE_BLOB) {
2955 error = got_error(GOT_ERR_OBJ_TYPE);
2956 goto done;
2959 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
2960 if (error)
2961 goto done;
2963 f = got_opentemp();
2964 if (f == NULL) {
2965 error = got_error_from_errno("got_opentemp");
2966 goto done;
2968 error = got_object_blob_dump_to_file(filesize, NULL, NULL, f, blob);
2969 if (error)
2970 goto done;
2972 /* XXX This will fail on large files... */
2973 *blobstr = calloc(*filesize + 1, sizeof(**blobstr));
2974 if (*blobstr == NULL) {
2975 error = got_error_from_errno("calloc");
2976 goto done;
2979 n = fread(*blobstr, 1, *filesize, f);
2980 if (n == 0) {
2981 if (ferror(f))
2982 error = got_ferror(f, GOT_ERR_IO);
2983 goto done;
2985 done:
2986 free(in_repo_path);
2987 free(commit_id);
2988 free(obj_id);
2989 free(path);
2990 if (blob)
2991 got_object_blob_close(blob);
2992 if (repo)
2993 got_repo_close(repo);
2994 if (f != NULL && fclose(f) == -1 && error == NULL)
2995 error = got_error_from_errno("fclose");
2996 if (error) {
2997 free(*blobstr);
2998 *blobstr = NULL;
2999 *filesize = 0;
3001 return error;
3004 static const struct got_error *
3005 gw_get_repo_tree(char **tree_html, struct gw_trans *gw_trans)
3007 const struct got_error *error = NULL;
3008 struct got_repository *repo = NULL;
3009 struct got_object_id *tree_id = NULL, *commit_id = NULL;
3010 struct got_tree_object *tree = NULL;
3011 struct buf *diffbuf = NULL;
3012 size_t newsize;
3013 char *path = NULL, *in_repo_path = NULL, *tree_row = NULL;
3014 char *id_str = NULL;
3015 char *build_folder = NULL;
3016 char *url_html = NULL;
3017 const char *class = NULL;
3018 int nentries, i, class_flip = 0;
3020 *tree_html = NULL;
3022 error = buf_alloc(&diffbuf, 0);
3023 if (error)
3024 return error;
3026 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3027 if (error)
3028 goto done;
3030 if (gw_trans->repo_folder != NULL)
3031 path = strdup(gw_trans->repo_folder);
3032 else {
3033 error = got_repo_map_path(&in_repo_path, repo,
3034 gw_trans->repo_path, 1);
3035 if (error)
3036 goto done;
3037 free(path);
3038 path = in_repo_path;
3041 if (gw_trans->commit == NULL) {
3042 struct got_reference *head_ref;
3043 error = got_ref_open(&head_ref, repo, gw_trans->headref, 0);
3044 if (error)
3045 goto done;
3046 error = got_ref_resolve(&commit_id, repo, head_ref);
3047 if (error)
3048 goto done;
3049 got_ref_close(head_ref);
3051 } else {
3052 error = got_repo_match_object_id(&commit_id, NULL,
3053 gw_trans->commit, GOT_OBJ_TYPE_COMMIT, 1, repo);
3054 if (error)
3055 goto done;
3058 error = got_object_id_str(&gw_trans->commit, commit_id);
3059 if (error)
3060 goto done;
3062 error = got_object_id_by_path(&tree_id, repo, commit_id, path);
3063 if (error)
3064 goto done;
3066 error = got_object_open_as_tree(&tree, repo, tree_id);
3067 if (error)
3068 goto done;
3070 nentries = got_object_tree_get_nentries(tree);
3071 for (i = 0; i < nentries; i++) {
3072 struct got_tree_entry *te;
3073 const char *modestr = "";
3074 mode_t mode;
3076 te = got_object_tree_get_entry(tree, i);
3078 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
3079 if (error)
3080 goto done;
3082 mode = got_tree_entry_get_mode(te);
3083 if (got_object_tree_entry_is_submodule(te))
3084 modestr = "$";
3085 else if (S_ISLNK(mode))
3086 modestr = "@";
3087 else if (S_ISDIR(mode))
3088 modestr = "/";
3089 else if (mode & S_IXUSR)
3090 modestr = "*";
3092 if (class_flip == 0) {
3093 class = "back_lightgray";
3094 class_flip = 1;
3095 } else {
3096 class = "back_white";
3097 class_flip = 0;
3100 if (S_ISDIR(mode)) {
3101 if (asprintf(&build_folder, "%s%s%s",
3102 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3103 gw_trans->repo_folder ? "/" : "",
3104 got_tree_entry_get_name(te)) == -1) {
3105 error = got_error_from_errno(
3106 "asprintf");
3107 goto done;
3110 if (asprintf(&url_html, folder_html,
3111 gw_trans->repo_name, gw_trans->action_name,
3112 gw_trans->commit, build_folder,
3113 got_tree_entry_get_name(te), modestr) == -1) {
3114 error = got_error_from_errno("asprintf");
3115 goto done;
3117 if (asprintf(&tree_row, tree_line, class, url_html,
3118 class) == -1) {
3119 error = got_error_from_errno("asprintf");
3120 goto done;
3122 } else {
3123 if (asprintf(&url_html, file_html, gw_trans->repo_name,
3124 "blob", gw_trans->commit,
3125 got_tree_entry_get_name(te),
3126 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3127 got_tree_entry_get_name(te), modestr) == -1) {
3128 error = got_error_from_errno("asprintf");
3129 goto done;
3132 if (asprintf(&tree_row, tree_line_with_navs, class,
3133 url_html, class, gw_trans->repo_name, "blob",
3134 gw_trans->commit, got_tree_entry_get_name(te),
3135 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3136 "blob", gw_trans->repo_name,
3137 "blame", gw_trans->commit,
3138 got_tree_entry_get_name(te),
3139 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3140 "blame") == -1) {
3141 error = got_error_from_errno("asprintf");
3142 goto done;
3146 error = buf_puts(&newsize, diffbuf, tree_row);
3147 if (error)
3148 goto done;
3150 free(id_str);
3151 id_str = NULL;
3152 free(url_html);
3153 url_html = NULL;
3154 free(tree_row);
3155 tree_row = NULL;
3156 free(build_folder);
3157 build_folder = NULL;
3160 if (buf_len(diffbuf) > 0) {
3161 error = buf_putc(diffbuf, '\0');
3162 if (error)
3163 goto done;
3164 *tree_html = strdup(buf_get(diffbuf));
3165 if (*tree_html == NULL) {
3166 error = got_error_from_errno("strdup");
3167 goto done;
3170 done:
3171 if (tree)
3172 got_object_tree_close(tree);
3173 if (repo)
3174 got_repo_close(repo);
3176 free(id_str);
3177 free(url_html);
3178 free(tree_row);
3179 free(in_repo_path);
3180 free(tree_id);
3181 free(diffbuf);
3182 free(build_folder);
3183 return error;
3186 static const struct got_error *
3187 gw_get_repo_heads(char **head_html, struct gw_trans *gw_trans)
3189 const struct got_error *error = NULL;
3190 struct got_repository *repo = NULL;
3191 struct got_reflist_head refs;
3192 struct got_reflist_entry *re;
3193 char *head_row = NULL, *head_navs_disp = NULL, *age = NULL;
3194 struct buf *diffbuf = NULL;
3195 size_t newsize;
3197 *head_html = NULL;
3199 SIMPLEQ_INIT(&refs);
3201 error = buf_alloc(&diffbuf, 0);
3202 if (error)
3203 return NULL;
3205 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3206 if (error)
3207 goto done;
3209 error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
3210 NULL);
3211 if (error)
3212 goto done;
3214 SIMPLEQ_FOREACH(re, &refs, entry) {
3215 char *refname;
3217 refname = strdup(got_ref_get_name(re->ref));
3218 if (refname == NULL) {
3219 error = got_error_from_errno("got_ref_to_str");
3220 goto done;
3223 if (strncmp(refname, "refs/heads/", 11) != 0) {
3224 free(refname);
3225 continue;
3228 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
3229 refname, TM_DIFF);
3230 if (error)
3231 goto done;
3233 if (asprintf(&head_navs_disp, heads_navs, gw_trans->repo_name,
3234 refname, gw_trans->repo_name, refname,
3235 gw_trans->repo_name, refname, gw_trans->repo_name,
3236 refname) == -1) {
3237 error = got_error_from_errno("asprintf");
3238 goto done;
3241 if (strncmp(refname, "refs/heads/", 11) == 0)
3242 refname += 11;
3244 if (asprintf(&head_row, heads_row, age, refname,
3245 head_navs_disp) == -1) {
3246 error = got_error_from_errno("asprintf");
3247 goto done;
3250 error = buf_puts(&newsize, diffbuf, head_row);
3252 free(head_navs_disp);
3253 free(head_row);
3256 if (buf_len(diffbuf) > 0) {
3257 error = buf_putc(diffbuf, '\0');
3258 *head_html = strdup(buf_get(diffbuf));
3259 if (*head_html == NULL)
3260 error = got_error_from_errno("strdup");
3262 done:
3263 buf_free(diffbuf);
3264 got_ref_list_free(&refs);
3265 if (repo)
3266 got_repo_close(repo);
3267 return error;
3270 static char *
3271 gw_get_site_link(struct gw_trans *gw_trans)
3273 char *link = NULL, *repo = NULL, *action = NULL;
3275 if (gw_trans->repo_name != NULL &&
3276 asprintf(&repo, " / <a href='?path=%s&action=summary'>%s</a>",
3277 gw_trans->repo_name, gw_trans->repo_name) == -1)
3278 return NULL;
3280 if (gw_trans->action_name != NULL &&
3281 asprintf(&action, " / %s", gw_trans->action_name) == -1) {
3282 free(repo);
3283 return NULL;
3286 if (asprintf(&link, site_link, GOTWEB,
3287 gw_trans->gw_conf->got_site_link,
3288 repo ? repo : "", action ? action : "") == -1) {
3289 free(repo);
3290 free(action);
3291 return NULL;
3294 free(repo);
3295 free(action);
3296 return link;
3299 static const struct got_error *
3300 gw_colordiff_line(char **colorized_line, char *buf)
3302 const struct got_error *error = NULL;
3303 char *div_diff_line_div = NULL, *color = NULL;
3304 struct buf *diffbuf = NULL;
3305 size_t newsize;
3307 *colorized_line = NULL;
3309 error = buf_alloc(&diffbuf, 0);
3310 if (error)
3311 return error;
3313 if (strncmp(buf, "-", 1) == 0)
3314 color = "diff_minus";
3315 else if (strncmp(buf, "+", 1) == 0)
3316 color = "diff_plus";
3317 else if (strncmp(buf, "@@", 2) == 0)
3318 color = "diff_chunk_header";
3319 else if (strncmp(buf, "@@", 2) == 0)
3320 color = "diff_chunk_header";
3321 else if (strncmp(buf, "commit +", 8) == 0)
3322 color = "diff_meta";
3323 else if (strncmp(buf, "commit -", 8) == 0)
3324 color = "diff_meta";
3325 else if (strncmp(buf, "blob +", 6) == 0)
3326 color = "diff_meta";
3327 else if (strncmp(buf, "blob -", 6) == 0)
3328 color = "diff_meta";
3329 else if (strncmp(buf, "file +", 6) == 0)
3330 color = "diff_meta";
3331 else if (strncmp(buf, "file -", 6) == 0)
3332 color = "diff_meta";
3333 else if (strncmp(buf, "from:", 5) == 0)
3334 color = "diff_author";
3335 else if (strncmp(buf, "via:", 4) == 0)
3336 color = "diff_author";
3337 else if (strncmp(buf, "date:", 5) == 0)
3338 color = "diff_date";
3340 if (asprintf(&div_diff_line_div, div_diff_line, color ? color : "")
3341 == -1) {
3342 error = got_error_from_errno("asprintf");
3343 goto done;
3346 error = buf_puts(&newsize, diffbuf, div_diff_line_div);
3347 if (error)
3348 goto done;
3350 error = buf_puts(&newsize, diffbuf, buf);
3351 if (error)
3352 goto done;
3354 if (buf_len(diffbuf) > 0) {
3355 error = buf_putc(diffbuf, '\0');
3356 *colorized_line = strdup(buf_get(diffbuf));
3357 if (*colorized_line == NULL)
3358 error = got_error_from_errno("strdup");
3360 done:
3361 free(diffbuf);
3362 free(div_diff_line_div);
3363 return error;
3367 * XXX This function should not exist.
3368 * We should let khtml_puts(3) handle HTML escaping.
3370 static const struct got_error *
3371 gw_html_escape(char **escaped_html, const char *orig_html)
3373 const struct got_error *error = NULL;
3374 struct escape_pair {
3375 char c;
3376 const char *s;
3377 } esc[] = {
3378 { '>', "&gt;" },
3379 { '<', "&lt;" },
3380 { '&', "&amp;" },
3381 { '"', "&quot;" },
3382 { '\'', "&apos;" },
3383 { '\n', "<br />" },
3385 size_t orig_len, len;
3386 int i, j, x;
3388 orig_len = strlen(orig_html);
3389 len = orig_len;
3390 for (i = 0; i < orig_len; i++) {
3391 for (j = 0; j < nitems(esc); j++) {
3392 if (orig_html[i] != esc[j].c)
3393 continue;
3394 len += strlen(esc[j].s) - 1 /* escaped char */;
3398 *escaped_html = calloc(len + 1 /* NUL */, sizeof(**escaped_html));
3399 if (*escaped_html == NULL)
3400 return got_error_from_errno("calloc");
3402 x = 0;
3403 for (i = 0; i < orig_len; i++) {
3404 int escaped = 0;
3405 for (j = 0; j < nitems(esc); j++) {
3406 if (orig_html[i] != esc[j].c)
3407 continue;
3409 if (strlcat(*escaped_html, esc[j].s, len + 1)
3410 >= len + 1) {
3411 error = got_error(GOT_ERR_NO_SPACE);
3412 goto done;
3414 x += strlen(esc[j].s);
3415 escaped = 1;
3416 break;
3418 if (!escaped) {
3419 (*escaped_html)[x] = orig_html[i];
3420 x++;
3423 done:
3424 if (error) {
3425 free(*escaped_html);
3426 *escaped_html = NULL;
3427 } else {
3428 (*escaped_html)[x] = '\0';
3430 return error;
3433 int
3434 main(int argc, char *argv[])
3436 const struct got_error *error = NULL;
3437 struct gw_trans *gw_trans;
3438 struct gw_dir *dir = NULL, *tdir;
3439 const char *page = "index";
3440 int gw_malloc = 1;
3441 enum kcgi_err kerr;
3443 if ((gw_trans = malloc(sizeof(struct gw_trans))) == NULL)
3444 errx(1, "malloc");
3446 if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
3447 errx(1, "malloc");
3449 if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
3450 errx(1, "malloc");
3452 if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
3453 errx(1, "malloc");
3455 kerr = khttp_parse(gw_trans->gw_req, gw_keys, KEY__ZMAX, &page, 1, 0);
3456 if (kerr != KCGI_OK) {
3457 error = gw_kcgi_error(kerr);
3458 goto done;
3461 if ((gw_trans->gw_conf =
3462 malloc(sizeof(struct gotweb_conf))) == NULL) {
3463 gw_malloc = 0;
3464 error = got_error_from_errno("malloc");
3465 goto done;
3468 TAILQ_INIT(&gw_trans->gw_dirs);
3469 TAILQ_INIT(&gw_trans->gw_headers);
3471 gw_trans->page = 0;
3472 gw_trans->repos_total = 0;
3473 gw_trans->repo_path = NULL;
3474 gw_trans->commit = NULL;
3475 gw_trans->headref = strdup(GOT_REF_HEAD);
3476 gw_trans->mime = KMIME_TEXT_HTML;
3477 gw_trans->gw_tmpl->key = gw_templs;
3478 gw_trans->gw_tmpl->keysz = TEMPL__MAX;
3479 gw_trans->gw_tmpl->arg = gw_trans;
3480 gw_trans->gw_tmpl->cb = gw_template;
3481 error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
3482 if (error)
3483 goto done;
3485 error = gw_parse_querystring(gw_trans);
3486 if (error)
3487 goto done;
3489 if (gw_trans->action == GW_BLOB)
3490 error = gw_blob(gw_trans);
3491 else
3492 error = gw_display_index(gw_trans);
3493 done:
3494 if (error) {
3495 gw_trans->mime = KMIME_TEXT_PLAIN;
3496 gw_trans->action = GW_ERR;
3497 gw_display_error(gw_trans, error);
3499 if (gw_malloc) {
3500 free(gw_trans->gw_conf->got_repos_path);
3501 free(gw_trans->gw_conf->got_site_name);
3502 free(gw_trans->gw_conf->got_site_owner);
3503 free(gw_trans->gw_conf->got_site_link);
3504 free(gw_trans->gw_conf->got_logo);
3505 free(gw_trans->gw_conf->got_logo_url);
3506 free(gw_trans->gw_conf);
3507 free(gw_trans->commit);
3508 free(gw_trans->repo_path);
3509 free(gw_trans->repo_name);
3510 free(gw_trans->repo_file);
3511 free(gw_trans->action_name);
3512 free(gw_trans->headref);
3514 TAILQ_FOREACH_SAFE(dir, &gw_trans->gw_dirs, entry, tdir) {
3515 free(dir->name);
3516 free(dir->description);
3517 free(dir->age);
3518 free(dir->url);
3519 free(dir->path);
3520 free(dir);
3525 khttp_free(gw_trans->gw_req);
3526 return 0;