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 "gotweb.h"
51 #ifndef nitems
52 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
53 #endif
55 struct gw_trans {
56 TAILQ_HEAD(headers, gw_header) gw_headers;
57 TAILQ_HEAD(dirs, gw_dir) gw_dirs;
58 struct gw_dir *gw_dir;
59 struct gotweb_conf *gw_conf;
60 struct ktemplate *gw_tmpl;
61 struct khtmlreq *gw_html_req;
62 struct kreq *gw_req;
63 char *repo_name;
64 char *repo_path;
65 char *commit;
66 char *repo_file;
67 char *repo_folder;
68 char *action_name;
69 char *headref;
70 unsigned int action;
71 unsigned int page;
72 unsigned int repos_total;
73 enum kmime mime;
74 };
76 struct gw_header {
77 TAILQ_ENTRY(gw_header) entry;
78 struct got_repository *repo;
79 struct got_reflist_head refs;
80 struct got_commit_object *commit;
81 struct got_object_id *id;
82 char *path;
84 char *refs_str;
85 char *commit_id; /* id_str1 */
86 char *parent_id; /* id_str2 */
87 char *tree_id;
88 char *author;
89 char *committer;
90 char *commit_msg;
91 time_t committer_time;
92 };
94 struct gw_dir {
95 TAILQ_ENTRY(gw_dir) entry;
96 char *name;
97 char *owner;
98 char *description;
99 char *url;
100 char *age;
101 char *path;
102 };
104 enum gw_key {
105 KEY_ACTION,
106 KEY_COMMIT_ID,
107 KEY_FILE,
108 KEY_FOLDER,
109 KEY_HEADREF,
110 KEY_PAGE,
111 KEY_PATH,
112 KEY__ZMAX
113 };
115 enum gw_tmpl {
116 TEMPL_CONTENT,
117 TEMPL_HEAD,
118 TEMPL_HEADER,
119 TEMPL_SEARCH,
120 TEMPL_SITEPATH,
121 TEMPL_SITEOWNER,
122 TEMPL_TITLE,
123 TEMPL__MAX
124 };
126 enum gw_ref_tm {
127 TM_DIFF,
128 TM_LONG,
129 };
131 enum gw_tags {
132 TAGBRIEF,
133 TAGFULL,
134 };
136 static const char *const gw_templs[TEMPL__MAX] = {
137 "content",
138 "head",
139 "header",
140 "search",
141 "sitepath",
142 "siteowner",
143 "title",
144 };
146 static const struct kvalid gw_keys[KEY__ZMAX] = {
147 { kvalid_stringne, "action" },
148 { kvalid_stringne, "commit" },
149 { kvalid_stringne, "file" },
150 { kvalid_stringne, "folder" },
151 { kvalid_stringne, "headref" },
152 { kvalid_int, "page" },
153 { kvalid_stringne, "path" },
154 };
156 static struct gw_dir *gw_init_gw_dir(char *);
157 static struct gw_header *gw_init_header(void);
159 static const struct got_error *gw_strdup_string(char **, char *,
160 const char *);
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_output_file_blame(struct gw_trans *);
169 static const struct got_error *gw_output_blob_buf(struct gw_trans *);
170 static const struct got_error *gw_output_repo_tree(struct gw_trans *);
171 static const struct got_error *gw_output_diff(struct gw_trans *,
172 struct gw_header *);
173 static const struct got_error *gw_output_repo_tags(struct gw_trans *,
174 struct gw_header *, int, int);
175 static const struct got_error *gw_output_repo_heads(struct gw_trans *);
176 static const struct got_error *gw_output_site_link(struct gw_trans *);
177 static const struct got_error *gw_get_clone_url(char **, struct gw_trans *,
178 char *);
179 static const struct got_error *gw_colordiff_line(struct gw_trans *, char *);
181 static const struct got_error *gw_gen_commit_header(struct gw_trans *, char *,
182 char*);
183 static const struct got_error *gw_gen_diff_header(struct gw_trans *, char *,
184 char*);
185 static const struct got_error *gw_gen_author_header(struct gw_trans *,
186 const char *);
187 static const struct got_error *gw_gen_age_header(struct gw_trans *,
188 const char *);
189 static const struct got_error *gw_gen_committer_header(struct gw_trans *,
190 const char *);
191 static const struct got_error *gw_gen_commit_msg_header(struct gw_trans*,
192 char *);
193 static const struct got_error *gw_gen_tree_header(struct gw_trans *, char *);
195 static void gw_free_headers(struct gw_header *);
196 static const struct got_error* gw_display_open(struct gw_trans *, enum khttp,
197 enum kmime);
198 static const struct got_error* gw_display_index(struct gw_trans *);
199 static void gw_display_error(struct gw_trans *,
200 const struct got_error *);
202 static int gw_template(size_t, void *);
204 static const struct got_error* gw_get_header(struct gw_trans *,
205 struct gw_header *, int);
206 static const struct got_error* gw_get_commits(struct gw_trans *,
207 struct gw_header *, int);
208 static const struct got_error* gw_get_commit(struct gw_trans *,
209 struct gw_header *);
210 static const struct got_error* gw_apply_unveil(const char *, const char *);
211 static const struct got_error* gw_blame_cb(void *, int, int,
212 struct got_object_id *);
213 static const struct got_error* gw_load_got_paths(struct gw_trans *);
214 static const struct got_error* gw_load_got_path(struct gw_trans *,
215 struct gw_dir *);
216 static const struct got_error* gw_parse_querystring(struct gw_trans *);
218 static const struct got_error* gw_blame(struct gw_trans *);
219 static const struct got_error* gw_blob(struct gw_trans *);
220 static const struct got_error* gw_diff(struct gw_trans *);
221 static const struct got_error* gw_index(struct gw_trans *);
222 static const struct got_error* gw_commits(struct gw_trans *);
223 static const struct got_error* gw_briefs(struct gw_trans *);
224 static const struct got_error* gw_summary(struct gw_trans *);
225 static const struct got_error* gw_tree(struct gw_trans *);
226 static const struct got_error* gw_tag(struct gw_trans *);
228 struct gw_query_action {
229 unsigned int func_id;
230 const char *func_name;
231 const struct got_error *(*func_main)(struct gw_trans *);
232 char *template;
233 };
235 enum gw_query_actions {
236 GW_BLAME,
237 GW_BLOB,
238 GW_BRIEFS,
239 GW_COMMITS,
240 GW_DIFF,
241 GW_ERR,
242 GW_INDEX,
243 GW_SUMMARY,
244 GW_TAG,
245 GW_TREE,
246 };
248 static struct gw_query_action gw_query_funcs[] = {
249 { GW_BLAME, "blame", gw_blame, "gw_tmpl/blame.tmpl" },
250 { GW_BLOB, "blob", NULL, NULL },
251 { GW_BRIEFS, "briefs", gw_briefs, "gw_tmpl/briefs.tmpl" },
252 { GW_COMMITS, "commits", gw_commits, "gw_tmpl/commit.tmpl" },
253 { GW_DIFF, "diff", gw_diff, "gw_tmpl/diff.tmpl" },
254 { GW_ERR, NULL, NULL, "gw_tmpl/err.tmpl" },
255 { GW_INDEX, "index", gw_index, "gw_tmpl/index.tmpl" },
256 { GW_SUMMARY, "summary", gw_summary, "gw_tmpl/summry.tmpl" },
257 { GW_TAG, "tag", gw_tag, "gw_tmpl/tag.tmpl" },
258 { GW_TREE, "tree", gw_tree, "gw_tmpl/tree.tmpl" },
259 };
261 static const struct got_error *
262 gw_kcgi_error(enum kcgi_err kerr)
264 if (kerr == KCGI_OK)
265 return NULL;
267 if (kerr == KCGI_EXIT || kerr == KCGI_HUP)
268 return got_error(GOT_ERR_CANCELLED);
270 if (kerr == KCGI_ENOMEM)
271 return got_error_set_errno(ENOMEM,
272 kcgi_strerror(kerr));
274 if (kerr == KCGI_ENFILE)
275 return got_error_set_errno(ENFILE,
276 kcgi_strerror(kerr));
278 if (kerr == KCGI_EAGAIN)
279 return got_error_set_errno(EAGAIN,
280 kcgi_strerror(kerr));
282 if (kerr == KCGI_FORM)
283 return got_error_msg(GOT_ERR_IO,
284 kcgi_strerror(kerr));
286 return got_error_from_errno(kcgi_strerror(kerr));
289 static const struct got_error *
290 gw_apply_unveil(const char *repo_path, const char *repo_file)
292 const struct got_error *err;
294 if (repo_path && repo_file) {
295 char *full_path;
296 if (asprintf(&full_path, "%s/%s", repo_path, repo_file) == -1)
297 return got_error_from_errno("asprintf unveil");
298 if (unveil(full_path, "r") != 0)
299 return got_error_from_errno2("unveil", full_path);
302 if (repo_path && unveil(repo_path, "r") != 0)
303 return got_error_from_errno2("unveil", repo_path);
305 if (unveil("/tmp", "rwc") != 0)
306 return got_error_from_errno2("unveil", "/tmp");
308 err = got_privsep_unveil_exec_helpers();
309 if (err != NULL)
310 return err;
312 if (unveil(NULL, NULL) != 0)
313 return got_error_from_errno("unveil");
315 return NULL;
318 static const struct got_error *
319 gw_strdup_string(char **s, char *str1, const char *str2)
321 if (str1 && str2)
322 return got_error_from_errno("strdup");
324 if (str1)
325 *s = strdup(str1);
326 else
327 *s = strdup(str2);
328 if (*s == NULL)
329 return got_error_from_errno("strdup");
330 return NULL;
333 static int
334 isbinary(const uint8_t *buf, size_t n)
336 size_t i;
338 for (i = 0; i < n; i++)
339 if (buf[i] == 0)
340 return 1;
341 return 0;
344 static const struct got_error *
345 gw_blame(struct gw_trans *gw_trans)
347 const struct got_error *error = NULL;
348 struct gw_header *header = NULL;
349 char *age = NULL;
350 enum kcgi_err kerr;
352 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
353 NULL) == -1)
354 return got_error_from_errno("pledge");
356 if ((header = gw_init_header()) == NULL)
357 return got_error_from_errno("malloc");
359 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
360 if (error)
361 goto done;
363 error = gw_get_header(gw_trans, header, 1);
364 if (error)
365 goto done;
367 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
368 "blame_header_wrapper", KATTR__MAX);
369 if (kerr != KCGI_OK)
370 goto done;
371 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
372 "blame_header", KATTR__MAX);
373 if (kerr != KCGI_OK)
374 goto done;
375 error = gw_get_time_str(&age, header->committer_time,
376 TM_LONG);
377 if (error)
378 goto done;
379 error = gw_gen_age_header(gw_trans, age ?age : "");
380 if (error)
381 goto done;
382 error = gw_gen_commit_msg_header(gw_trans, header->commit_msg);
383 if (error)
384 goto done;
385 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
386 if (kerr != KCGI_OK)
387 goto done;
388 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
389 "dotted_line", KATTR__MAX);
390 if (kerr != KCGI_OK)
391 goto done;
392 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
393 if (kerr != KCGI_OK)
394 goto done;
396 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
397 "blame", KATTR__MAX);
398 if (kerr != KCGI_OK)
399 goto done;
400 error = gw_output_file_blame(gw_trans);
401 if (error)
402 goto done;
403 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
404 done:
405 got_ref_list_free(&header->refs);
406 gw_free_headers(header);
407 if (error == NULL && kerr != KCGI_OK)
408 error = gw_kcgi_error(kerr);
409 return error;
412 static const struct got_error *
413 gw_blob(struct gw_trans *gw_trans)
415 const struct got_error *error = NULL;
416 struct gw_header *header = NULL;
418 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
419 NULL) == -1)
420 return got_error_from_errno("pledge");
422 if ((header = gw_init_header()) == NULL)
423 return got_error_from_errno("malloc");
425 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
426 if (error)
427 goto done;
429 error = gw_get_header(gw_trans, header, 1);
430 if (error)
431 goto done;
433 error = gw_output_blob_buf(gw_trans);
434 done:
435 got_ref_list_free(&header->refs);
436 gw_free_headers(header);
437 return error;
440 static const struct got_error *
441 gw_diff(struct gw_trans *gw_trans)
443 const struct got_error *error = NULL;
444 struct gw_header *header = NULL;
445 char *age = NULL;
446 enum kcgi_err kerr = KCGI_OK;
448 if (pledge("stdio rpath wpath cpath proc exec sendfd unveil",
449 NULL) == -1)
450 return got_error_from_errno("pledge");
452 if ((header = gw_init_header()) == NULL)
453 return got_error_from_errno("malloc");
455 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
456 if (error)
457 goto done;
459 error = gw_get_header(gw_trans, header, 1);
460 if (error)
461 goto done;
463 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
464 "diff_header_wrapper", KATTR__MAX);
465 if (kerr != KCGI_OK)
466 goto done;
467 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
468 "diff_header", KATTR__MAX);
469 if (kerr != KCGI_OK)
470 goto done;
471 error = gw_gen_diff_header(gw_trans, header->parent_id,
472 header->commit_id);
473 if (error)
474 goto done;
475 error = gw_gen_commit_header(gw_trans, header->commit_id,
476 header->refs_str);
477 if (error)
478 goto done;
479 error = gw_gen_tree_header(gw_trans, header->tree_id);
480 if (error)
481 goto done;
482 error = gw_gen_author_header(gw_trans, header->author);
483 if (error)
484 goto done;
485 error = gw_gen_committer_header(gw_trans, header->author);
486 if (error)
487 goto done;
488 error = gw_get_time_str(&age, header->committer_time,
489 TM_LONG);
490 if (error)
491 goto done;
492 error = gw_gen_age_header(gw_trans, age ?age : "");
493 if (error)
494 goto done;
495 error = gw_gen_commit_msg_header(gw_trans, header->commit_msg);
496 if (error)
497 goto done;
498 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
499 if (kerr != KCGI_OK)
500 goto done;
501 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
502 "dotted_line", KATTR__MAX);
503 if (kerr != KCGI_OK)
504 goto done;
505 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
506 if (kerr != KCGI_OK)
507 goto done;
509 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
510 "diff", KATTR__MAX);
511 if (kerr != KCGI_OK)
512 goto done;
513 error = gw_output_diff(gw_trans, header);
514 if (error)
515 goto done;
517 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
518 if (kerr != KCGI_OK)
519 goto done;
520 done:
521 got_ref_list_free(&header->refs);
522 gw_free_headers(header);
523 free(age);
524 if (error == NULL && kerr != KCGI_OK)
525 error = gw_kcgi_error(kerr);
526 return error;
529 static const struct got_error *
530 gw_index(struct gw_trans *gw_trans)
532 const struct got_error *error = NULL;
533 struct gw_dir *gw_dir = NULL;
534 char *href_next = NULL, *href_prev = NULL, *href_summary = NULL;
535 char *href_briefs = NULL, *href_commits = NULL, *href_tree = NULL;
536 unsigned int prev_disp = 0, next_disp = 1, dir_c = 0;
537 enum kcgi_err kerr;
539 if (pledge("stdio rpath proc exec sendfd unveil",
540 NULL) == -1) {
541 error = got_error_from_errno("pledge");
542 return error;
545 error = gw_apply_unveil(gw_trans->gw_conf->got_repos_path, NULL);
546 if (error)
547 return error;
549 error = gw_load_got_paths(gw_trans);
550 if (error)
551 return error;
553 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
554 "index_header", KATTR__MAX);
555 if (kerr != KCGI_OK)
556 return gw_kcgi_error(kerr);
557 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
558 "index_header_project", KATTR__MAX);
559 if (kerr != KCGI_OK)
560 return gw_kcgi_error(kerr);
561 kerr = khtml_puts(gw_trans->gw_html_req, "Project");
562 if (kerr != KCGI_OK)
563 return gw_kcgi_error(kerr);
564 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
565 if (kerr != KCGI_OK)
566 return gw_kcgi_error(kerr);
568 if (gw_trans->gw_conf->got_show_repo_description) {
569 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
570 "index_header_description", KATTR__MAX);
571 if (kerr != KCGI_OK)
572 return gw_kcgi_error(kerr);
573 kerr = khtml_puts(gw_trans->gw_html_req, "Description");
574 if (kerr != KCGI_OK)
575 return gw_kcgi_error(kerr);
576 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
577 if (kerr != KCGI_OK)
578 return gw_kcgi_error(kerr);
581 if (gw_trans->gw_conf->got_show_repo_owner) {
582 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
583 "index_header_owner", KATTR__MAX);
584 if (kerr != KCGI_OK)
585 return gw_kcgi_error(kerr);
586 kerr = khtml_puts(gw_trans->gw_html_req, "Owner");
587 if (kerr != KCGI_OK)
588 return gw_kcgi_error(kerr);
589 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
590 if (kerr != KCGI_OK)
591 return gw_kcgi_error(kerr);
594 if (gw_trans->gw_conf->got_show_repo_age) {
595 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
596 "index_header_age", KATTR__MAX);
597 if (kerr != KCGI_OK)
598 return gw_kcgi_error(kerr);
599 kerr = khtml_puts(gw_trans->gw_html_req, "Last Change");
600 if (kerr != KCGI_OK)
601 return gw_kcgi_error(kerr);
602 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
603 if (kerr != KCGI_OK)
604 return gw_kcgi_error(kerr);
607 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
608 if (kerr != KCGI_OK)
609 return gw_kcgi_error(kerr);
611 if (TAILQ_EMPTY(&gw_trans->gw_dirs)) {
612 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
613 "index_wrapper", KATTR__MAX);
614 if (kerr != KCGI_OK)
615 return gw_kcgi_error(kerr);
616 kerr = khtml_puts(gw_trans->gw_html_req,
617 "No repositories found in ");
618 if (kerr != KCGI_OK)
619 return gw_kcgi_error(kerr);
620 kerr = khtml_puts(gw_trans->gw_html_req,
621 gw_trans->gw_conf->got_repos_path);
622 if (kerr != KCGI_OK)
623 return gw_kcgi_error(kerr);
624 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
625 if (kerr != KCGI_OK)
626 return gw_kcgi_error(kerr);
627 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
628 "dotted_line", KATTR__MAX);
629 if (kerr != KCGI_OK)
630 return gw_kcgi_error(kerr);
631 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
632 if (kerr != KCGI_OK)
633 return gw_kcgi_error(kerr);
634 return error;
637 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry)
638 dir_c++;
640 TAILQ_FOREACH(gw_dir, &gw_trans->gw_dirs, entry) {
641 if (gw_trans->page > 0 && (gw_trans->page *
642 gw_trans->gw_conf->got_max_repos_display) > prev_disp) {
643 prev_disp++;
644 continue;
647 prev_disp++;
649 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
650 "index_wrapper", KATTR__MAX);
651 if (kerr != KCGI_OK)
652 return gw_kcgi_error(kerr);
654 if (asprintf(&href_summary, "?path=%s&action=summary",
655 gw_dir->name) == -1)
656 return got_error_from_errno("asprintf");
657 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
658 "index_project", KATTR__MAX);
659 if (kerr != KCGI_OK)
660 goto done;
661 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
662 href_summary, KATTR__MAX);
663 if (kerr != KCGI_OK)
664 goto done;
665 kerr = khtml_puts(gw_trans->gw_html_req, gw_dir->name);
666 if (kerr != KCGI_OK)
667 goto done;
668 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
669 if (kerr != KCGI_OK)
670 goto done;
671 if (gw_trans->gw_conf->got_show_repo_description) {
672 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
673 KATTR_ID, "index_project_description", KATTR__MAX);
674 if (kerr != KCGI_OK)
675 goto done;
676 kerr = khtml_puts(gw_trans->gw_html_req,
677 gw_dir->description ? gw_dir->description : "");
678 if (kerr != KCGI_OK)
679 goto done;
680 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
681 if (kerr != KCGI_OK)
682 goto done;
684 if (gw_trans->gw_conf->got_show_repo_owner) {
685 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
686 KATTR_ID, "index_project_owner", KATTR__MAX);
687 if (kerr != KCGI_OK)
688 goto done;
689 kerr = khtml_puts(gw_trans->gw_html_req,
690 gw_dir->owner ? gw_dir->owner : "");
691 if (kerr != KCGI_OK)
692 goto done;
693 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
694 if (kerr != KCGI_OK)
695 goto done;
697 if (gw_trans->gw_conf->got_show_repo_age) {
698 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
699 KATTR_ID, "index_project_age", KATTR__MAX);
700 if (kerr != KCGI_OK)
701 goto done;
702 kerr = khtml_puts(gw_trans->gw_html_req,
703 gw_dir->age ? gw_dir->age : "");
704 if (kerr != KCGI_OK)
705 goto done;
706 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
707 if (kerr != KCGI_OK)
708 goto done;
711 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
712 "navs_wrapper", KATTR__MAX);
713 if (kerr != KCGI_OK)
714 goto done;
715 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
716 "navs", KATTR__MAX);
717 if (kerr != KCGI_OK)
718 goto done;
720 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
721 href_summary, KATTR__MAX);
722 if (kerr != KCGI_OK)
723 goto done;
724 kerr = khtml_puts(gw_trans->gw_html_req, "summary");
725 if (kerr != KCGI_OK)
726 goto done;
727 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
728 if (kerr != KCGI_OK)
729 goto done;
731 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
732 if (kerr != KCGI_OK)
733 goto done;
735 if (asprintf(&href_briefs, "?path=%s&action=briefs",
736 gw_dir->name) == -1) {
737 error = got_error_from_errno("asprintf");
738 goto done;
740 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
741 href_briefs, KATTR__MAX);
742 if (kerr != KCGI_OK)
743 goto done;
744 kerr = khtml_puts(gw_trans->gw_html_req, "commit briefs");
745 if (kerr != KCGI_OK)
746 goto done;
747 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
748 if (kerr != KCGI_OK)
749 error = gw_kcgi_error(kerr);
751 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
752 if (kerr != KCGI_OK)
753 goto done;
755 if (asprintf(&href_commits, "?path=%s&action=commits",
756 gw_dir->name) == -1) {
757 error = got_error_from_errno("asprintf");
758 goto done;
760 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
761 href_commits, KATTR__MAX);
762 if (kerr != KCGI_OK)
763 goto done;
764 kerr = khtml_puts(gw_trans->gw_html_req, "commits");
765 if (kerr != KCGI_OK)
766 goto done;
767 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
768 if (kerr != KCGI_OK)
769 goto done;
771 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
772 if (kerr != KCGI_OK)
773 goto done;
775 if (asprintf(&href_tree, "?path=%s&action=tree",
776 gw_dir->name) == -1) {
777 error = got_error_from_errno("asprintf");
778 goto done;
780 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
781 href_tree, KATTR__MAX);
782 if (kerr != KCGI_OK)
783 goto done;
784 kerr = khtml_puts(gw_trans->gw_html_req, "tree");
785 if (kerr != KCGI_OK)
786 goto done;
788 kerr = khtml_closeelem(gw_trans->gw_html_req, 4);
789 if (kerr != KCGI_OK)
790 goto done;
791 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
792 "dotted_line", KATTR__MAX);
793 if (kerr != KCGI_OK)
794 goto done;
795 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
796 if (kerr != KCGI_OK)
797 goto done;
799 free(href_summary);
800 href_summary = NULL;
801 free(href_briefs);
802 href_briefs = NULL;
803 free(href_commits);
804 href_commits = NULL;
805 free(href_tree);
806 href_tree = NULL;
808 if (gw_trans->gw_conf->got_max_repos_display == 0)
809 continue;
811 if (next_disp == gw_trans->gw_conf->got_max_repos_display) {
812 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
813 KATTR_ID, "np_wrapper", KATTR__MAX);
814 if (kerr != KCGI_OK)
815 goto done;
816 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
817 KATTR_ID, "nav_prev", KATTR__MAX);
818 if (kerr != KCGI_OK)
819 goto done;
820 } else if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
821 (gw_trans->page > 0) &&
822 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
823 prev_disp == gw_trans->repos_total)) {
824 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
825 KATTR_ID, "np_wrapper", KATTR__MAX);
826 if (kerr != KCGI_OK)
827 goto done;
828 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
829 KATTR_ID, "nav_prev", KATTR__MAX);
830 if (kerr != KCGI_OK)
831 goto done;
834 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
835 (gw_trans->page > 0) &&
836 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
837 prev_disp == gw_trans->repos_total)) {
838 if (asprintf(&href_prev, "?page=%d",
839 gw_trans->page - 1) == -1) {
840 error = got_error_from_errno("asprintf");
841 goto done;
843 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
844 KATTR_HREF, href_prev, KATTR__MAX);
845 free(href_prev);
846 if (kerr != KCGI_OK)
847 goto done;
848 kerr = khtml_puts(gw_trans->gw_html_req, "Previous");
849 if (kerr != KCGI_OK)
850 goto done;
851 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
852 if (kerr != KCGI_OK)
853 goto done;
856 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
857 if (kerr != KCGI_OK)
858 return gw_kcgi_error(kerr);
860 if (gw_trans->gw_conf->got_max_repos_display > 0 &&
861 next_disp == gw_trans->gw_conf->got_max_repos_display &&
862 dir_c != (gw_trans->page + 1) *
863 gw_trans->gw_conf->got_max_repos_display) {
864 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
865 KATTR_ID, "nav_next", KATTR__MAX);
866 if (kerr != KCGI_OK)
867 goto done;
868 if (asprintf(&href_next, "?page=%d",
869 gw_trans->page + 1) == -1) {
870 error = got_error_from_errno("calloc");
871 goto done;
873 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
874 KATTR_HREF, href_next, KATTR__MAX);
875 free(href_next);
876 if (kerr != KCGI_OK)
877 goto done;
878 kerr = khtml_puts(gw_trans->gw_html_req, "Next");
879 if (kerr != KCGI_OK)
880 goto done;
881 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
882 if (kerr != KCGI_OK)
883 goto done;
884 next_disp = 0;
885 break;
888 if ((gw_trans->gw_conf->got_max_repos_display > 0) &&
889 (gw_trans->page > 0) &&
890 (next_disp == gw_trans->gw_conf->got_max_repos_display ||
891 prev_disp == gw_trans->repos_total)) {
892 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
893 if (kerr != KCGI_OK)
894 goto done;
896 next_disp++;
898 done:
899 free(href_summary);
900 free(href_briefs);
901 free(href_commits);
902 free(href_tree);
903 if (error == NULL && kerr != KCGI_OK)
904 error = gw_kcgi_error(kerr);
905 return error;
908 static const struct got_error *
909 gw_commits(struct gw_trans *gw_trans)
911 const struct got_error *error = NULL;
912 struct gw_header *header = NULL, *n_header = NULL;
913 char *age = NULL;
914 char *href_diff = NULL, *href_blob = NULL;
915 enum kcgi_err kerr = KCGI_OK;
917 if ((header = gw_init_header()) == NULL)
918 return got_error_from_errno("malloc");
920 if (pledge("stdio rpath proc exec sendfd unveil",
921 NULL) == -1) {
922 error = got_error_from_errno("pledge");
923 goto done;
926 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
927 if (error)
928 goto done;
930 error = gw_get_header(gw_trans, header,
931 gw_trans->gw_conf->got_max_commits_display);
932 if (error)
933 goto done;
935 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
936 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
937 "commits_line_wrapper", KATTR__MAX);
938 if (kerr != KCGI_OK)
939 goto done;
940 error = gw_gen_commit_header(gw_trans, n_header->commit_id,
941 n_header->refs_str);
942 if (error)
943 goto done;
944 error = gw_gen_author_header(gw_trans, n_header->author);
945 if (error)
946 goto done;
947 error = gw_gen_committer_header(gw_trans, n_header->author);
948 if (error)
949 goto done;
950 error = gw_get_time_str(&age, n_header->committer_time,
951 TM_LONG);
952 if (error)
953 goto done;
954 error = gw_gen_age_header(gw_trans, age ?age : "");
955 if (error)
956 goto done;
957 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
958 if (kerr != KCGI_OK)
959 goto done;
961 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
962 "dotted_line", KATTR__MAX);
963 if (kerr != KCGI_OK)
964 goto done;
965 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
966 if (kerr != KCGI_OK)
967 goto done;
969 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
970 "commit", KATTR__MAX);
971 if (kerr != KCGI_OK)
972 goto done;
973 kerr = khttp_puts(gw_trans->gw_req, n_header->commit_msg);
974 if (kerr != KCGI_OK)
975 goto done;
976 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
977 if (kerr != KCGI_OK)
978 goto done;
980 if (asprintf(&href_diff, "?path=%s&action=diff&commit=%s",
981 gw_trans->repo_name, n_header->commit_id) == -1) {
982 error = got_error_from_errno("asprintf");
983 goto done;
985 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
986 KATTR_ID, "navs_wrapper", KATTR__MAX);
987 if (kerr != KCGI_OK)
988 goto done;
989 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
990 KATTR_ID, "navs", KATTR__MAX);
991 if (kerr != KCGI_OK)
992 goto done;
993 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
994 KATTR_HREF, href_diff, KATTR__MAX);
995 if (kerr != KCGI_OK)
996 goto done;
997 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
998 if (kerr != KCGI_OK)
999 goto done;
1000 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1001 if (kerr != KCGI_OK)
1002 goto done;
1004 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
1005 if (kerr != KCGI_OK)
1006 goto done;
1008 if (asprintf(&href_blob, "?path=%s&action=tree&commit=%s",
1009 gw_trans->repo_name, n_header->commit_id) == -1) {
1010 error = got_error_from_errno("asprintf");
1011 goto done;
1013 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1014 KATTR_HREF, href_blob, KATTR__MAX);
1015 if (kerr != KCGI_OK)
1016 goto done;
1017 khtml_puts(gw_trans->gw_html_req, "tree");
1018 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1019 if (kerr != KCGI_OK)
1020 goto done;
1021 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1022 if (kerr != KCGI_OK)
1023 goto done;
1025 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1026 "solid_line", KATTR__MAX);
1027 if (kerr != KCGI_OK)
1028 goto done;
1029 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1030 if (kerr != KCGI_OK)
1031 goto done;
1033 free(age);
1034 age = NULL;
1036 done:
1037 got_ref_list_free(&header->refs);
1038 gw_free_headers(header);
1039 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
1040 gw_free_headers(n_header);
1041 free(age);
1042 free(href_diff);
1043 free(href_blob);
1044 if (error == NULL && kerr != KCGI_OK)
1045 error = gw_kcgi_error(kerr);
1046 return error;
1049 static const struct got_error *
1050 gw_briefs(struct gw_trans *gw_trans)
1052 const struct got_error *error = NULL;
1053 struct gw_header *header = NULL, *n_header = NULL;
1054 char *age = NULL, *age_html = NULL;
1055 char *href_diff = NULL, *href_blob = NULL;
1056 char *newline, *smallerthan;
1057 enum kcgi_err kerr = KCGI_OK;
1059 if ((header = gw_init_header()) == NULL)
1060 return got_error_from_errno("malloc");
1062 if (pledge("stdio rpath proc exec sendfd unveil",
1063 NULL) == -1) {
1064 error = got_error_from_errno("pledge");
1065 goto done;
1068 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1069 if (error)
1070 goto done;
1072 if (gw_trans->action == GW_SUMMARY)
1073 error = gw_get_header(gw_trans, header, D_MAXSLCOMMDISP);
1074 else
1075 error = gw_get_header(gw_trans, header,
1076 gw_trans->gw_conf->got_max_commits_display);
1077 if (error)
1078 goto done;
1080 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry) {
1081 error = gw_get_time_str(&age, n_header->committer_time,
1082 TM_DIFF);
1083 if (error)
1084 goto done;
1086 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1087 KATTR_ID, "briefs_wrapper", KATTR__MAX);
1088 if (kerr != KCGI_OK)
1089 goto done;
1091 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1092 KATTR_ID, "briefs_age", KATTR__MAX);
1093 if (kerr != KCGI_OK)
1094 goto done;
1095 if (asprintf(&age_html, "%s", age ? age : "") == -1) {
1096 error = got_error_from_errno("asprintf");
1097 goto done;
1099 kerr = khtml_puts(gw_trans->gw_html_req, age_html);
1100 if (kerr != KCGI_OK)
1101 goto done;
1102 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1103 if (kerr != KCGI_OK)
1104 goto done;
1106 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1107 KATTR_ID, "briefs_author", KATTR__MAX);
1108 if (kerr != KCGI_OK)
1109 goto done;
1110 smallerthan = strchr(n_header->author, '<');
1111 if (smallerthan)
1112 *smallerthan = '\0';
1113 kerr = khtml_puts(gw_trans->gw_html_req, n_header->author);
1114 if (kerr != KCGI_OK)
1115 goto done;
1116 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1117 if (kerr != KCGI_OK)
1118 goto done;
1120 if (asprintf(&href_diff, "?path=%s&action=diff&commit=%s",
1121 gw_trans->repo_name, n_header->commit_id) == -1) {
1122 error = got_error_from_errno("asprintf");
1123 goto done;
1125 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1126 KATTR_ID, "briefs_log", KATTR__MAX);
1127 if (kerr != KCGI_OK)
1128 goto done;
1129 newline = strchr(n_header->commit_msg, '\n');
1130 if (newline)
1131 *newline = '\0';
1132 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1133 KATTR_HREF, href_diff, KATTR__MAX);
1134 if (kerr != KCGI_OK)
1135 goto done;
1136 kerr = khtml_puts(gw_trans->gw_html_req, n_header->commit_msg);
1137 if (kerr != KCGI_OK)
1138 goto done;
1139 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1140 if (kerr != KCGI_OK)
1141 goto done;
1143 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1144 KATTR_ID, "navs_wrapper", KATTR__MAX);
1145 if (kerr != KCGI_OK)
1146 goto done;
1147 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1148 KATTR_ID, "navs", KATTR__MAX);
1149 if (kerr != KCGI_OK)
1150 goto done;
1151 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1152 KATTR_HREF, href_diff, KATTR__MAX);
1153 if (kerr != KCGI_OK)
1154 goto done;
1155 kerr = khtml_puts(gw_trans->gw_html_req, "diff");
1156 if (kerr != KCGI_OK)
1157 goto done;
1158 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1159 if (kerr != KCGI_OK)
1160 goto done;
1162 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
1163 if (kerr != KCGI_OK)
1164 goto done;
1166 if (asprintf(&href_blob, "?path=%s&action=tree&commit=%s",
1167 gw_trans->repo_name, n_header->commit_id) == -1) {
1168 error = got_error_from_errno("asprintf");
1169 goto done;
1171 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1172 KATTR_HREF, href_blob, KATTR__MAX);
1173 if (kerr != KCGI_OK)
1174 goto done;
1175 khtml_puts(gw_trans->gw_html_req, "tree");
1176 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1177 if (kerr != KCGI_OK)
1178 goto done;
1179 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1180 if (kerr != KCGI_OK)
1181 goto done;
1183 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1184 KATTR_ID, "dotted_line", KATTR__MAX);
1185 if (kerr != KCGI_OK)
1186 goto done;
1187 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1188 if (kerr != KCGI_OK)
1189 goto done;
1191 free(age);
1192 age = NULL;
1193 free(age_html);
1194 age_html = NULL;
1195 free(href_diff);
1196 href_diff = NULL;
1197 free(href_blob);
1198 href_blob = NULL;
1200 done:
1201 got_ref_list_free(&header->refs);
1202 gw_free_headers(header);
1203 TAILQ_FOREACH(n_header, &gw_trans->gw_headers, entry)
1204 gw_free_headers(n_header);
1205 free(age);
1206 free(age_html);
1207 free(href_diff);
1208 free(href_blob);
1209 if (error == NULL && kerr != KCGI_OK)
1210 error = gw_kcgi_error(kerr);
1211 return error;
1214 static const struct got_error *
1215 gw_summary(struct gw_trans *gw_trans)
1217 const struct got_error *error = NULL;
1218 char *age = NULL;
1219 enum kcgi_err kerr = KCGI_OK;
1221 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1222 return got_error_from_errno("pledge");
1224 /* unveil is applied with gw_briefs below */
1226 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1227 "summary_wrapper", KATTR__MAX);
1228 if (kerr != KCGI_OK)
1229 return gw_kcgi_error(kerr);
1231 if (gw_trans->gw_conf->got_show_repo_description &&
1232 gw_trans->gw_dir->description != NULL &&
1233 (strcmp(gw_trans->gw_dir->description, "") != 0)) {
1234 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1235 KATTR_ID, "description_title", KATTR__MAX);
1236 if (kerr != KCGI_OK)
1237 goto done;
1238 kerr = khtml_puts(gw_trans->gw_html_req, "Description: ");
1239 if (kerr != KCGI_OK)
1240 goto done;
1241 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1242 if (kerr != KCGI_OK)
1243 goto done;
1244 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1245 KATTR_ID, "description", KATTR__MAX);
1246 if (kerr != KCGI_OK)
1247 goto done;
1248 kerr = khtml_puts(gw_trans->gw_html_req,
1249 gw_trans->gw_dir->description);
1250 if (kerr != KCGI_OK)
1251 goto done;
1252 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1253 if (kerr != KCGI_OK)
1254 goto done;
1257 if (gw_trans->gw_conf->got_show_repo_owner &&
1258 gw_trans->gw_dir->owner != NULL &&
1259 (strcmp(gw_trans->gw_dir->owner, "") != 0)) {
1260 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1261 KATTR_ID, "repo_owner_title", KATTR__MAX);
1262 if (kerr != KCGI_OK)
1263 goto done;
1264 kerr = khtml_puts(gw_trans->gw_html_req, "Owner: ");
1265 if (kerr != KCGI_OK)
1266 goto done;
1267 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1268 if (kerr != KCGI_OK)
1269 goto done;
1270 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1271 KATTR_ID, "repo_owner", KATTR__MAX);
1272 if (kerr != KCGI_OK)
1273 goto done;
1274 kerr = khtml_puts(gw_trans->gw_html_req,
1275 gw_trans->gw_dir->owner);
1276 if (kerr != KCGI_OK)
1277 goto done;
1278 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1279 if (kerr != KCGI_OK)
1280 goto done;
1283 if (gw_trans->gw_conf->got_show_repo_age) {
1284 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
1285 "refs/heads", TM_LONG);
1286 if (error)
1287 goto done;
1288 if (age != NULL) {
1289 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1290 KATTR_ID, "last_change_title", KATTR__MAX);
1291 if (kerr != KCGI_OK)
1292 goto done;
1293 kerr = khtml_puts(gw_trans->gw_html_req,
1294 "Last Change: ");
1295 if (kerr != KCGI_OK)
1296 goto done;
1297 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1298 if (kerr != KCGI_OK)
1299 goto done;
1300 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1301 KATTR_ID, "last_change", KATTR__MAX);
1302 if (kerr != KCGI_OK)
1303 goto done;
1304 kerr = khtml_puts(gw_trans->gw_html_req, age);
1305 if (kerr != KCGI_OK)
1306 goto done;
1307 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1308 if (kerr != KCGI_OK)
1309 goto done;
1313 if (gw_trans->gw_conf->got_show_repo_cloneurl &&
1314 gw_trans->gw_dir->url != NULL &&
1315 (strcmp(gw_trans->gw_dir->url, "") != 0)) {
1316 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1317 KATTR_ID, "cloneurl_title", KATTR__MAX);
1318 if (kerr != KCGI_OK)
1319 goto done;
1320 kerr = khtml_puts(gw_trans->gw_html_req, "Clone URL: ");
1321 if (kerr != KCGI_OK)
1322 goto done;
1323 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1324 if (kerr != KCGI_OK)
1325 goto done;
1326 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1327 KATTR_ID, "cloneurl", KATTR__MAX);
1328 if (kerr != KCGI_OK)
1329 goto done;
1330 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->gw_dir->url);
1331 if (kerr != KCGI_OK)
1332 goto done;
1333 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1334 if (kerr != KCGI_OK)
1335 goto done;
1338 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1339 if (kerr != KCGI_OK)
1340 goto done;
1342 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1343 "briefs_title_wrapper", KATTR__MAX);
1344 if (kerr != KCGI_OK)
1345 goto done;
1346 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1347 "briefs_title", KATTR__MAX);
1348 if (kerr != KCGI_OK)
1349 goto done;
1350 kerr = khtml_puts(gw_trans->gw_html_req, "Commit Briefs");
1351 if (kerr != KCGI_OK)
1352 goto done;
1353 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1354 if (kerr != KCGI_OK)
1355 goto done;
1356 error = gw_briefs(gw_trans);
1357 if (error)
1358 goto done;
1360 error = gw_output_repo_tags(gw_trans, NULL, D_MAXSLCOMMDISP,
1361 TAGBRIEF);
1362 if (error)
1363 goto done;
1365 error = gw_output_repo_heads(gw_trans);
1366 done:
1367 free(age);
1368 if (error == NULL && kerr != KCGI_OK)
1369 error = gw_kcgi_error(kerr);
1370 return error;
1373 static const struct got_error *
1374 gw_tree(struct gw_trans *gw_trans)
1376 const struct got_error *error = NULL;
1377 struct gw_header *header = NULL;
1378 char *tree = NULL, *tree_html = NULL, *tree_html_disp = NULL;
1379 char *age = NULL, *age_html = NULL;
1380 enum kcgi_err kerr;
1382 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1383 return got_error_from_errno("pledge");
1385 if ((header = gw_init_header()) == NULL)
1386 return got_error_from_errno("malloc");
1388 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1389 if (error)
1390 goto done;
1392 error = gw_get_header(gw_trans, header, 1);
1393 if (error)
1394 goto done;
1396 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1397 "tree_header_wrapper", KATTR__MAX);
1398 if (kerr != KCGI_OK)
1399 goto done;
1400 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1401 "tree_header", KATTR__MAX);
1402 if (kerr != KCGI_OK)
1403 goto done;
1404 error = gw_gen_tree_header(gw_trans, header->tree_id);
1405 if (error)
1406 goto done;
1407 error = gw_get_time_str(&age, header->committer_time,
1408 TM_LONG);
1409 if (error)
1410 goto done;
1411 error = gw_gen_age_header(gw_trans, age ?age : "");
1412 if (error)
1413 goto done;
1414 error = gw_gen_commit_msg_header(gw_trans, header->commit_msg);
1415 if (error)
1416 goto done;
1417 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1418 if (kerr != KCGI_OK)
1419 goto done;
1420 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1421 "dotted_line", KATTR__MAX);
1422 if (kerr != KCGI_OK)
1423 goto done;
1424 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1425 if (kerr != KCGI_OK)
1426 goto done;
1428 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1429 "tree", KATTR__MAX);
1430 if (kerr != KCGI_OK)
1431 goto done;
1432 error = gw_output_repo_tree(gw_trans);
1433 if (error)
1434 goto done;
1436 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1437 done:
1438 got_ref_list_free(&header->refs);
1439 gw_free_headers(header);
1440 free(tree_html_disp);
1441 free(tree_html);
1442 free(tree);
1443 free(age);
1444 free(age_html);
1445 if (error == NULL && kerr != KCGI_OK)
1446 error = gw_kcgi_error(kerr);
1447 return error;
1450 static const struct got_error *
1451 gw_tag(struct gw_trans *gw_trans)
1453 const struct got_error *error = NULL;
1454 struct gw_header *header = NULL;
1455 enum kcgi_err kerr;
1457 if (pledge("stdio rpath proc exec sendfd unveil", NULL) == -1)
1458 return got_error_from_errno("pledge");
1460 if ((header = gw_init_header()) == NULL)
1461 return got_error_from_errno("malloc");
1463 error = gw_apply_unveil(gw_trans->gw_dir->path, NULL);
1464 if (error)
1465 goto done;
1467 error = gw_get_header(gw_trans, header, 1);
1468 if (error)
1469 goto done;
1471 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1472 "tag_header_wrapper", KATTR__MAX);
1473 if (kerr != KCGI_OK)
1474 goto done;
1475 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1476 "tag_header", KATTR__MAX);
1477 if (kerr != KCGI_OK)
1478 goto done;
1479 error = gw_gen_commit_header(gw_trans, header->commit_id,
1480 header->refs_str);
1481 if (error)
1482 goto done;
1483 error = gw_gen_commit_msg_header(gw_trans, header->commit_msg);
1484 if (error)
1485 goto done;
1486 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1487 if (kerr != KCGI_OK)
1488 goto done;
1489 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1490 "dotted_line", KATTR__MAX);
1491 if (kerr != KCGI_OK)
1492 goto done;
1493 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1494 if (kerr != KCGI_OK)
1495 goto done;
1497 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1498 "tree", KATTR__MAX);
1499 if (kerr != KCGI_OK)
1500 goto done;
1502 error = gw_output_repo_tags(gw_trans, header, 1, TAGFULL);
1503 if (error)
1504 goto done;
1506 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1507 done:
1508 got_ref_list_free(&header->refs);
1509 gw_free_headers(header);
1510 if (error == NULL && kerr != KCGI_OK)
1511 error = gw_kcgi_error(kerr);
1512 return error;
1515 static const struct got_error *
1516 gw_load_got_path(struct gw_trans *gw_trans, struct gw_dir *gw_dir)
1518 const struct got_error *error = NULL;
1519 DIR *dt;
1520 char *dir_test;
1521 int opened = 0;
1523 if (asprintf(&dir_test, "%s/%s/%s",
1524 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1525 GOTWEB_GIT_DIR) == -1)
1526 return got_error_from_errno("asprintf");
1528 dt = opendir(dir_test);
1529 if (dt == NULL) {
1530 free(dir_test);
1531 } else {
1532 error = gw_strdup_string(&gw_dir->path, dir_test, NULL);
1533 opened = 1;
1534 if (error)
1535 goto errored;
1536 goto done;
1539 if (asprintf(&dir_test, "%s/%s/%s",
1540 gw_trans->gw_conf->got_repos_path, gw_dir->name,
1541 GOTWEB_GOT_DIR) == -1)
1542 return got_error_from_errno("asprintf");
1544 dt = opendir(dir_test);
1545 if (dt == NULL)
1546 free(dir_test);
1547 else {
1548 opened = 1;
1549 error = got_error(GOT_ERR_NOT_GIT_REPO);
1550 goto errored;
1553 if (asprintf(&dir_test, "%s/%s",
1554 gw_trans->gw_conf->got_repos_path, gw_dir->name) == -1)
1555 return got_error_from_errno("asprintf");
1557 error = gw_strdup_string(&gw_dir->path, dir_test, NULL);
1558 if (error) {
1559 opened = 1;
1560 goto errored;
1562 done:
1563 error = gw_get_repo_description(&gw_dir->description, gw_trans,
1564 gw_dir->path);
1565 if (error)
1566 goto errored;
1567 error = gw_get_repo_owner(&gw_dir->owner, gw_trans, gw_dir->path);
1568 if (error)
1569 goto errored;
1570 error = gw_get_repo_age(&gw_dir->age, gw_trans, gw_dir->path,
1571 "refs/heads", TM_DIFF);
1572 if (error)
1573 goto errored;
1574 error = gw_get_clone_url(&gw_dir->url, gw_trans, gw_dir->path);
1575 errored:
1576 free(dir_test);
1577 if (opened)
1578 closedir(dt);
1579 return error;
1582 static const struct got_error *
1583 gw_load_got_paths(struct gw_trans *gw_trans)
1585 const struct got_error *error = NULL;
1586 DIR *d;
1587 struct dirent **sd_dent;
1588 struct gw_dir *gw_dir;
1589 struct stat st;
1590 unsigned int d_cnt, d_i;
1592 d = opendir(gw_trans->gw_conf->got_repos_path);
1593 if (d == NULL) {
1594 error = got_error_from_errno2("opendir",
1595 gw_trans->gw_conf->got_repos_path);
1596 return error;
1599 d_cnt = scandir(gw_trans->gw_conf->got_repos_path, &sd_dent, NULL,
1600 alphasort);
1601 if (d_cnt == -1) {
1602 error = got_error_from_errno2("scandir",
1603 gw_trans->gw_conf->got_repos_path);
1604 return error;
1607 for (d_i = 0; d_i < d_cnt; d_i++) {
1608 if (gw_trans->gw_conf->got_max_repos > 0 &&
1609 (d_i - 2) == gw_trans->gw_conf->got_max_repos)
1610 break; /* account for parent and self */
1612 if (strcmp(sd_dent[d_i]->d_name, ".") == 0 ||
1613 strcmp(sd_dent[d_i]->d_name, "..") == 0)
1614 continue;
1616 if ((gw_dir = gw_init_gw_dir(sd_dent[d_i]->d_name)) == NULL)
1617 return got_error_from_errno("gw_dir malloc");
1619 error = gw_load_got_path(gw_trans, gw_dir);
1620 if (error && error->code == GOT_ERR_NOT_GIT_REPO) {
1621 error = NULL;
1622 continue;
1624 else if (error)
1625 return error;
1627 if (lstat(gw_dir->path, &st) == 0 && S_ISDIR(st.st_mode) &&
1628 !got_path_dir_is_empty(gw_dir->path)) {
1629 TAILQ_INSERT_TAIL(&gw_trans->gw_dirs, gw_dir,
1630 entry);
1631 gw_trans->repos_total++;
1635 closedir(d);
1636 return error;
1639 static const struct got_error *
1640 gw_parse_querystring(struct gw_trans *gw_trans)
1642 const struct got_error *error = NULL;
1643 struct kpair *p;
1644 struct gw_query_action *action = NULL;
1645 unsigned int i;
1647 if (gw_trans->gw_req->fieldnmap[0]) {
1648 error = got_error_from_errno("bad parse");
1649 return error;
1650 } else if ((p = gw_trans->gw_req->fieldmap[KEY_PATH])) {
1651 /* define gw_trans->repo_path */
1652 if (asprintf(&gw_trans->repo_name, "%s", p->parsed.s) == -1)
1653 return got_error_from_errno("asprintf");
1655 if (asprintf(&gw_trans->repo_path, "%s/%s",
1656 gw_trans->gw_conf->got_repos_path, p->parsed.s) == -1)
1657 return got_error_from_errno("asprintf");
1659 /* get action and set function */
1660 if ((p = gw_trans->gw_req->fieldmap[KEY_ACTION]))
1661 for (i = 0; i < nitems(gw_query_funcs); i++) {
1662 action = &gw_query_funcs[i];
1663 if (action->func_name == NULL)
1664 continue;
1666 if (strcmp(action->func_name,
1667 p->parsed.s) == 0) {
1668 gw_trans->action = i;
1669 if (asprintf(&gw_trans->action_name,
1670 "%s", action->func_name) == -1)
1671 return
1672 got_error_from_errno(
1673 "asprintf");
1675 break;
1678 action = NULL;
1681 if ((p = gw_trans->gw_req->fieldmap[KEY_COMMIT_ID]))
1682 if (asprintf(&gw_trans->commit, "%s",
1683 p->parsed.s) == -1)
1684 return got_error_from_errno("asprintf");
1686 if ((p = gw_trans->gw_req->fieldmap[KEY_FILE]))
1687 if (asprintf(&gw_trans->repo_file, "%s",
1688 p->parsed.s) == -1)
1689 return got_error_from_errno("asprintf");
1691 if ((p = gw_trans->gw_req->fieldmap[KEY_FOLDER]))
1692 if (asprintf(&gw_trans->repo_folder, "%s",
1693 p->parsed.s) == -1)
1694 return got_error_from_errno("asprintf");
1696 if ((p = gw_trans->gw_req->fieldmap[KEY_HEADREF]))
1697 if (asprintf(&gw_trans->headref, "%s",
1698 p->parsed.s) == -1)
1699 return got_error_from_errno("asprintf");
1701 if (action == NULL) {
1702 error = got_error_from_errno("invalid action");
1703 return error;
1705 if ((gw_trans->gw_dir =
1706 gw_init_gw_dir(gw_trans->repo_name)) == NULL)
1707 return got_error_from_errno("gw_dir malloc");
1709 error = gw_load_got_path(gw_trans, gw_trans->gw_dir);
1710 if (error)
1711 return error;
1712 } else
1713 gw_trans->action = GW_INDEX;
1715 if ((p = gw_trans->gw_req->fieldmap[KEY_PAGE]))
1716 gw_trans->page = p->parsed.i;
1718 return error;
1721 static struct gw_dir *
1722 gw_init_gw_dir(char *dir)
1724 struct gw_dir *gw_dir;
1726 if ((gw_dir = malloc(sizeof(*gw_dir))) == NULL)
1727 return NULL;
1729 if (asprintf(&gw_dir->name, "%s", dir) == -1)
1730 return NULL;
1732 return gw_dir;
1735 static const struct got_error *
1736 gw_display_open(struct gw_trans *gw_trans, enum khttp code, enum kmime mime)
1738 enum kcgi_err kerr;
1740 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_ALLOW], "GET");
1741 if (kerr != KCGI_OK)
1742 return gw_kcgi_error(kerr);
1743 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_STATUS], "%s",
1744 khttps[code]);
1745 if (kerr != KCGI_OK)
1746 return gw_kcgi_error(kerr);
1747 kerr = khttp_head(gw_trans->gw_req, kresps[KRESP_CONTENT_TYPE], "%s",
1748 kmimetypes[mime]);
1749 if (kerr != KCGI_OK)
1750 return gw_kcgi_error(kerr);
1751 kerr = khttp_head(gw_trans->gw_req, "X-Content-Type-Options",
1752 "nosniff");
1753 if (kerr != KCGI_OK)
1754 return gw_kcgi_error(kerr);
1755 kerr = khttp_head(gw_trans->gw_req, "X-Frame-Options", "DENY");
1756 if (kerr != KCGI_OK)
1757 return gw_kcgi_error(kerr);
1758 kerr = khttp_head(gw_trans->gw_req, "X-XSS-Protection",
1759 "1; mode=block");
1760 if (kerr != KCGI_OK)
1761 return gw_kcgi_error(kerr);
1763 if (gw_trans->mime == KMIME_APP_OCTET_STREAM) {
1764 kerr = khttp_head(gw_trans->gw_req,
1765 kresps[KRESP_CONTENT_DISPOSITION],
1766 "attachment; filename=%s", gw_trans->repo_file);
1767 if (kerr != KCGI_OK)
1768 return gw_kcgi_error(kerr);
1771 kerr = khttp_body(gw_trans->gw_req);
1772 return gw_kcgi_error(kerr);
1775 static const struct got_error *
1776 gw_display_index(struct gw_trans *gw_trans)
1778 const struct got_error *error;
1779 enum kcgi_err kerr;
1781 error = gw_display_open(gw_trans, KHTTP_200, gw_trans->mime);
1782 if (error)
1783 return error;
1785 kerr = khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0);
1786 if (kerr != KCGI_OK)
1787 return gw_kcgi_error(kerr);
1789 if (gw_trans->action != GW_BLOB) {
1790 kerr = khttp_template(gw_trans->gw_req, gw_trans->gw_tmpl,
1791 gw_query_funcs[gw_trans->action].template);
1792 if (kerr != KCGI_OK) {
1793 khtml_close(gw_trans->gw_html_req);
1794 return gw_kcgi_error(kerr);
1798 return gw_kcgi_error(khtml_close(gw_trans->gw_html_req));
1801 static void
1802 gw_display_error(struct gw_trans *gw_trans, const struct got_error *err)
1804 if (gw_display_open(gw_trans, KHTTP_200, gw_trans->mime) != NULL)
1805 return;
1807 if (khtml_open(gw_trans->gw_html_req, gw_trans->gw_req, 0) != KCGI_OK)
1808 return;
1809 khtml_puts(gw_trans->gw_html_req, err->msg);
1810 khtml_close(gw_trans->gw_html_req);
1813 static int
1814 gw_template(size_t key, void *arg)
1816 const struct got_error *error = NULL;
1817 enum kcgi_err kerr;
1818 struct gw_trans *gw_trans = arg;
1819 char *img_src = NULL;
1821 switch (key) {
1822 case (TEMPL_HEAD):
1823 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_META,
1824 KATTR_CONTENT, "initial-scale=1.0, user-scalable=no",
1825 KATTR__MAX);
1826 if (kerr != KCGI_OK)
1827 return 0;
1828 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1829 if (kerr != KCGI_OK)
1830 return 0;
1831 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_META,
1832 KATTR_CHARSET, "utf-8",
1833 KATTR__MAX);
1834 if (kerr != KCGI_OK)
1835 return 0;
1836 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1837 if (kerr != KCGI_OK)
1838 return 0;
1839 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_META,
1840 KATTR_NAME, "msapplication-TileColor",
1841 KATTR_CONTENT, "#da532c", KATTR__MAX);
1842 if (kerr != KCGI_OK)
1843 return 0;
1844 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1845 if (kerr != KCGI_OK)
1846 return 0;
1847 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_META,
1848 KATTR_NAME, "theme-color",
1849 KATTR_CONTENT, "#ffffff", KATTR__MAX);
1850 if (kerr != KCGI_OK)
1851 return 0;
1852 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1853 if (kerr != KCGI_OK)
1854 return 0;
1855 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_LINK,
1856 KATTR_REL, "apple-touch-icon", KATTR_SIZES, "180x180",
1857 KATTR_HREF, "/apple-touch-icon.png", KATTR__MAX);
1858 if (kerr != KCGI_OK)
1859 return 0;
1860 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1861 if (kerr != KCGI_OK)
1862 return 0;
1863 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_LINK,
1864 KATTR_REL, "icon", KATTR_TYPE, "image/png", KATTR_SIZES,
1865 "32x32", KATTR_HREF, "/favicon-32x32.png", KATTR__MAX);
1866 if (kerr != KCGI_OK)
1867 return 0;
1868 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1869 if (kerr != KCGI_OK)
1870 return 0;
1871 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_LINK,
1872 KATTR_REL, "icon", KATTR_TYPE, "image/png", KATTR_SIZES,
1873 "16x16", KATTR_HREF, "/favicon-16x16.png", KATTR__MAX);
1874 if (kerr != KCGI_OK)
1875 return 0;
1876 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1877 if (kerr != KCGI_OK)
1878 return 0;
1879 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_LINK,
1880 KATTR_REL, "manifest", KATTR_HREF, "/site.webmanifest",
1881 KATTR__MAX);
1882 if (kerr != KCGI_OK)
1883 return 0;
1884 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1885 if (kerr != KCGI_OK)
1886 return 0;
1887 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_LINK,
1888 KATTR_REL, "mask-icon", KATTR_HREF,
1889 "/safari-pinned-tab.svg", KATTR__MAX);
1890 if (kerr != KCGI_OK)
1891 return 0;
1892 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1893 if (kerr != KCGI_OK)
1894 return 0;
1895 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_LINK,
1896 KATTR_REL, "stylesheet", KATTR_TYPE, "text/css",
1897 KATTR_HREF, "/gotweb.css", KATTR__MAX);
1898 if (kerr != KCGI_OK)
1899 return 0;
1900 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
1901 if (kerr != KCGI_OK)
1902 return 0;
1903 break;
1904 case(TEMPL_HEADER):
1905 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1906 KATTR_ID, "got_link", KATTR__MAX);
1907 if (kerr != KCGI_OK)
1908 return 0;
1909 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
1910 KATTR_HREF, gw_trans->gw_conf->got_logo_url,
1911 KATTR_TARGET, "_sotd", KATTR__MAX);
1912 if (kerr != KCGI_OK)
1913 return 0;
1914 if (asprintf(&img_src, "/%s",
1915 gw_trans->gw_conf->got_logo) == -1)
1916 return 0;
1917 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_IMG,
1918 KATTR_SRC, img_src, KATTR__MAX);
1919 if (kerr != KCGI_OK) {
1920 free(img_src);
1921 return 0;
1923 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
1924 if (kerr != KCGI_OK) {
1925 free(img_src);
1926 return 0;
1928 break;
1929 case (TEMPL_SITEPATH):
1930 error = gw_output_site_link(gw_trans);
1931 if (error)
1932 return 0;
1933 break;
1934 case(TEMPL_TITLE):
1935 if (gw_trans->gw_conf->got_site_name != NULL) {
1936 kerr = khtml_puts(gw_trans->gw_html_req,
1937 gw_trans->gw_conf->got_site_name);
1938 if (kerr != KCGI_OK)
1939 return 0;
1941 break;
1942 case (TEMPL_SEARCH):
1943 break;
1944 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
1945 "search", KATTR__MAX);
1946 if (kerr != KCGI_OK)
1947 return 0;
1948 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_FORM,
1949 KATTR_METHOD, "POST", KATTR__MAX);
1950 if (kerr != KCGI_OK)
1951 return 0;
1952 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_INPUT, KATTR_ID,
1953 "got-search", KATTR_NAME, "got-search", KATTR_SIZE, "15",
1954 KATTR_MAXLENGTH, "50", KATTR__MAX);
1955 if (kerr != KCGI_OK)
1956 return 0;
1957 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_BUTTON,
1958 KATTR__MAX);
1959 if (kerr != KCGI_OK)
1960 return 0;
1961 kerr = khtml_puts(gw_trans->gw_html_req, "Search");
1962 if (kerr != KCGI_OK)
1963 return 0;
1964 kerr = khtml_closeelem(gw_trans->gw_html_req, 4);
1965 if (kerr != KCGI_OK)
1966 return 0;
1967 break;
1968 case(TEMPL_SITEOWNER):
1969 if (gw_trans->gw_conf->got_site_owner != NULL &&
1970 gw_trans->gw_conf->got_show_site_owner) {
1971 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1972 KATTR_ID, "site_owner_wrapper", KATTR__MAX);
1973 if (kerr != KCGI_OK)
1974 return 0;
1975 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1976 KATTR_ID, "site_owner", KATTR__MAX);
1977 if (kerr != KCGI_OK)
1978 return 0;
1979 kerr = khtml_puts(gw_trans->gw_html_req,
1980 gw_trans->gw_conf->got_site_owner);
1981 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
1982 if (kerr != KCGI_OK)
1983 return 0;
1985 break;
1986 case(TEMPL_CONTENT):
1987 error = gw_query_funcs[gw_trans->action].func_main(gw_trans);
1988 if (error) {
1989 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
1990 KATTR_ID, "tmpl_err", KATTR__MAX);
1991 if (kerr != KCGI_OK)
1992 return 0;
1993 kerr = khttp_puts(gw_trans->gw_req, "Error: ");
1994 if (kerr != KCGI_OK)
1995 return 0;
1996 kerr = khttp_puts(gw_trans->gw_req, error->msg);
1997 if (kerr != KCGI_OK)
1998 return 0;
1999 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2000 if (kerr != KCGI_OK)
2001 return 0;
2003 break;
2004 default:
2005 return 0;
2007 return 1;
2010 static const struct got_error *
2011 gw_gen_commit_header(struct gw_trans *gw_trans, char *str1, char *str2)
2013 const struct got_error *error = NULL;
2014 char *ref_str = NULL;
2015 enum kcgi_err kerr = KCGI_OK;
2017 if (strcmp(str2, "") != 0) {
2018 if (asprintf(&ref_str, "(%s)", str2) == -1) {
2019 error = got_error_from_errno("asprintf");
2020 goto done;
2022 } else {
2023 error = gw_strdup_string(&ref_str, "", NULL);
2024 if (error)
2025 goto done;
2028 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2029 KATTR_ID, "header_commit_title", KATTR__MAX);
2030 if (kerr != KCGI_OK)
2031 goto done;
2032 kerr = khtml_puts(gw_trans->gw_html_req, "Commit: ");
2033 if (kerr != KCGI_OK)
2034 goto done;
2035 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2036 if (kerr != KCGI_OK)
2037 goto done;
2038 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2039 KATTR_ID, "header_commit", KATTR__MAX);
2040 if (kerr != KCGI_OK)
2041 goto done;
2042 kerr = khtml_puts(gw_trans->gw_html_req, str1);
2043 if (kerr != KCGI_OK)
2044 goto done;
2045 kerr = khtml_puts(gw_trans->gw_html_req, " ");
2046 if (kerr != KCGI_OK)
2047 goto done;
2048 kerr = khtml_puts(gw_trans->gw_html_req, ref_str);
2049 if (kerr != KCGI_OK)
2050 goto done;
2051 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2052 done:
2053 if (error == NULL && kerr != KCGI_OK)
2054 error = gw_kcgi_error(kerr);
2055 return error;
2058 static const struct got_error *
2059 gw_gen_diff_header(struct gw_trans *gw_trans, char *str1, char *str2)
2061 const struct got_error *error = NULL;
2062 enum kcgi_err kerr = KCGI_OK;
2064 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2065 KATTR_ID, "header_diff_title", KATTR__MAX);
2066 if (kerr != KCGI_OK)
2067 goto done;
2068 kerr = khtml_puts(gw_trans->gw_html_req, "Diff: ");
2069 if (kerr != KCGI_OK)
2070 goto done;
2071 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2072 if (kerr != KCGI_OK)
2073 goto done;
2074 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2075 KATTR_ID, "header_diff", KATTR__MAX);
2076 if (kerr != KCGI_OK)
2077 goto done;
2078 kerr = khtml_puts(gw_trans->gw_html_req, str1);
2079 if (kerr != KCGI_OK)
2080 goto done;
2081 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_BR, KATTR__MAX);
2082 if (kerr != KCGI_OK)
2083 goto done;
2084 kerr = khtml_puts(gw_trans->gw_html_req, str2);
2085 if (kerr != KCGI_OK)
2086 goto done;
2087 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2088 done:
2089 if (error == NULL && kerr != KCGI_OK)
2090 error = gw_kcgi_error(kerr);
2091 return error;
2094 static const struct got_error *
2095 gw_gen_age_header(struct gw_trans *gw_trans, const char *str)
2097 const struct got_error *error = NULL;
2098 enum kcgi_err kerr = KCGI_OK;
2100 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2101 KATTR_ID, "header_age_title", KATTR__MAX);
2102 if (kerr != KCGI_OK)
2103 goto done;
2104 kerr = khtml_puts(gw_trans->gw_html_req, "Date: ");
2105 if (kerr != KCGI_OK)
2106 goto done;
2107 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2108 if (kerr != KCGI_OK)
2109 goto done;
2110 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2111 KATTR_ID, "header_age", KATTR__MAX);
2112 if (kerr != KCGI_OK)
2113 goto done;
2114 kerr = khtml_puts(gw_trans->gw_html_req, str);
2115 if (kerr != KCGI_OK)
2116 goto done;
2117 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2118 done:
2119 if (error == NULL && kerr != KCGI_OK)
2120 error = gw_kcgi_error(kerr);
2121 return error;
2124 static const struct got_error *
2125 gw_gen_author_header(struct gw_trans *gw_trans, const char *str)
2127 const struct got_error *error = NULL;
2128 enum kcgi_err kerr;
2130 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2131 KATTR_ID, "header_author_title", KATTR__MAX);
2132 if (kerr != KCGI_OK)
2133 goto done;
2134 kerr = khtml_puts(gw_trans->gw_html_req, "Author: ");
2135 if (kerr != KCGI_OK)
2136 goto done;
2137 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2138 if (kerr != KCGI_OK)
2139 goto done;
2140 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2141 KATTR_ID, "header_author", KATTR__MAX);
2142 if (kerr != KCGI_OK)
2143 goto done;
2144 kerr = khtml_puts(gw_trans->gw_html_req, str);
2145 if (kerr != KCGI_OK)
2146 goto done;
2147 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2148 done:
2149 if (error == NULL && kerr != KCGI_OK)
2150 error = gw_kcgi_error(kerr);
2151 return error;
2154 static const struct got_error *
2155 gw_gen_committer_header(struct gw_trans *gw_trans, const char *str)
2157 const struct got_error *error = NULL;
2158 enum kcgi_err kerr;
2160 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2161 KATTR_ID, "header_committer_title", KATTR__MAX);
2162 if (kerr != KCGI_OK)
2163 goto done;
2164 kerr = khtml_puts(gw_trans->gw_html_req, "Committer: ");
2165 if (kerr != KCGI_OK)
2166 goto done;
2167 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2168 if (kerr != KCGI_OK)
2169 goto done;
2170 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2171 KATTR_ID, "header_committer", KATTR__MAX);
2172 if (kerr != KCGI_OK)
2173 goto done;
2174 kerr = khtml_puts(gw_trans->gw_html_req, str);
2175 if (kerr != KCGI_OK)
2176 goto done;
2177 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2178 done:
2179 if (error == NULL && kerr != KCGI_OK)
2180 error = gw_kcgi_error(kerr);
2181 return error;
2184 static const struct got_error *
2185 gw_gen_commit_msg_header(struct gw_trans *gw_trans, char *str)
2187 const struct got_error *error = NULL;
2188 enum kcgi_err kerr;
2190 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2191 KATTR_ID, "header_commit_msg_title", KATTR__MAX);
2192 if (kerr != KCGI_OK)
2193 goto done;
2194 kerr = khtml_puts(gw_trans->gw_html_req, "Message: ");
2195 if (kerr != KCGI_OK)
2196 goto done;
2197 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2198 if (kerr != KCGI_OK)
2199 goto done;
2200 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2201 KATTR_ID, "header_commit_msg", KATTR__MAX);
2202 if (kerr != KCGI_OK)
2203 goto done;
2204 kerr = khttp_puts(gw_trans->gw_req, str);
2205 if (kerr != KCGI_OK)
2206 goto done;
2207 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2208 done:
2209 if (error == NULL && kerr != KCGI_OK)
2210 error = gw_kcgi_error(kerr);
2211 return error;
2214 static const struct got_error *
2215 gw_gen_tree_header(struct gw_trans *gw_trans, char *str)
2217 const struct got_error *error = NULL;
2218 enum kcgi_err kerr;
2220 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2221 KATTR_ID, "header_tree_title", KATTR__MAX);
2222 if (kerr != KCGI_OK)
2223 goto done;
2224 kerr = khtml_puts(gw_trans->gw_html_req, "Tree: ");
2225 if (kerr != KCGI_OK)
2226 goto done;
2227 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2228 if (kerr != KCGI_OK)
2229 goto done;
2230 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2231 KATTR_ID, "header_tree", KATTR__MAX);
2232 if (kerr != KCGI_OK)
2233 goto done;
2234 kerr = khtml_puts(gw_trans->gw_html_req, str);
2235 if (kerr != KCGI_OK)
2236 goto done;
2237 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2238 done:
2239 if (error == NULL && kerr != KCGI_OK)
2240 error = gw_kcgi_error(kerr);
2241 return error;
2244 static const struct got_error *
2245 gw_get_repo_description(char **description, struct gw_trans *gw_trans,
2246 char *dir)
2248 const struct got_error *error = NULL;
2249 FILE *f = NULL;
2250 char *d_file = NULL;
2251 unsigned int len;
2252 size_t n;
2254 *description = NULL;
2255 if (gw_trans->gw_conf->got_show_repo_description == 0)
2256 return NULL;
2258 if (asprintf(&d_file, "%s/description", dir) == -1)
2259 return got_error_from_errno("asprintf");
2261 f = fopen(d_file, "r");
2262 if (f == NULL) {
2263 if (errno == ENOENT || errno == EACCES)
2264 return NULL;
2265 error = got_error_from_errno2("fopen", d_file);
2266 goto done;
2269 if (fseek(f, 0, SEEK_END) == -1) {
2270 error = got_ferror(f, GOT_ERR_IO);
2271 goto done;
2273 len = ftell(f);
2274 if (len == -1) {
2275 error = got_ferror(f, GOT_ERR_IO);
2276 goto done;
2278 if (fseek(f, 0, SEEK_SET) == -1) {
2279 error = got_ferror(f, GOT_ERR_IO);
2280 goto done;
2282 *description = calloc(len + 1, sizeof(**description));
2283 if (*description == NULL) {
2284 error = got_error_from_errno("calloc");
2285 goto done;
2288 n = fread(*description, 1, len, f);
2289 if (n == 0 && ferror(f))
2290 error = got_ferror(f, GOT_ERR_IO);
2291 done:
2292 if (f != NULL && fclose(f) == -1 && error == NULL)
2293 error = got_error_from_errno("fclose");
2294 free(d_file);
2295 return error;
2298 static const struct got_error *
2299 gw_get_time_str(char **repo_age, time_t committer_time, int ref_tm)
2301 struct tm tm;
2302 time_t diff_time;
2303 char *years = "years ago", *months = "months ago";
2304 char *weeks = "weeks ago", *days = "days ago", *hours = "hours ago";
2305 char *minutes = "minutes ago", *seconds = "seconds ago";
2306 char *now = "right now";
2307 char *s;
2308 char datebuf[29];
2310 *repo_age = NULL;
2312 switch (ref_tm) {
2313 case TM_DIFF:
2314 diff_time = time(NULL) - committer_time;
2315 if (diff_time > 60 * 60 * 24 * 365 * 2) {
2316 if (asprintf(repo_age, "%lld %s",
2317 (diff_time / 60 / 60 / 24 / 365), years) == -1)
2318 return got_error_from_errno("asprintf");
2319 } else if (diff_time > 60 * 60 * 24 * (365 / 12) * 2) {
2320 if (asprintf(repo_age, "%lld %s",
2321 (diff_time / 60 / 60 / 24 / (365 / 12)),
2322 months) == -1)
2323 return got_error_from_errno("asprintf");
2324 } else if (diff_time > 60 * 60 * 24 * 7 * 2) {
2325 if (asprintf(repo_age, "%lld %s",
2326 (diff_time / 60 / 60 / 24 / 7), weeks) == -1)
2327 return got_error_from_errno("asprintf");
2328 } else if (diff_time > 60 * 60 * 24 * 2) {
2329 if (asprintf(repo_age, "%lld %s",
2330 (diff_time / 60 / 60 / 24), days) == -1)
2331 return got_error_from_errno("asprintf");
2332 } else if (diff_time > 60 * 60 * 2) {
2333 if (asprintf(repo_age, "%lld %s",
2334 (diff_time / 60 / 60), hours) == -1)
2335 return got_error_from_errno("asprintf");
2336 } else if (diff_time > 60 * 2) {
2337 if (asprintf(repo_age, "%lld %s", (diff_time / 60),
2338 minutes) == -1)
2339 return got_error_from_errno("asprintf");
2340 } else if (diff_time > 2) {
2341 if (asprintf(repo_age, "%lld %s", diff_time,
2342 seconds) == -1)
2343 return got_error_from_errno("asprintf");
2344 } else {
2345 if (asprintf(repo_age, "%s", now) == -1)
2346 return got_error_from_errno("asprintf");
2348 break;
2349 case TM_LONG:
2350 if (gmtime_r(&committer_time, &tm) == NULL)
2351 return got_error_from_errno("gmtime_r");
2353 s = asctime_r(&tm, datebuf);
2354 if (s == NULL)
2355 return got_error_from_errno("asctime_r");
2357 if (asprintf(repo_age, "%s UTC", datebuf) == -1)
2358 return got_error_from_errno("asprintf");
2359 break;
2361 return NULL;
2364 static const struct got_error *
2365 gw_get_repo_age(char **repo_age, struct gw_trans *gw_trans, char *dir,
2366 char *repo_ref, int ref_tm)
2368 const struct got_error *error = NULL;
2369 struct got_object_id *id = NULL;
2370 struct got_repository *repo = NULL;
2371 struct got_commit_object *commit = NULL;
2372 struct got_reflist_head refs;
2373 struct got_reflist_entry *re;
2374 struct got_reference *head_ref;
2375 int is_head = 0;
2376 time_t committer_time = 0, cmp_time = 0;
2377 char *refname;
2379 *repo_age = NULL;
2380 SIMPLEQ_INIT(&refs);
2382 if (repo_ref == NULL)
2383 return NULL;
2385 if (strncmp(repo_ref, "refs/heads/", 11) == 0)
2386 is_head = 1;
2388 if (gw_trans->gw_conf->got_show_repo_age == 0)
2389 return NULL;
2391 error = got_repo_open(&repo, dir, NULL);
2392 if (error)
2393 goto done;
2395 if (is_head)
2396 error = got_ref_list(&refs, repo, "refs/heads",
2397 got_ref_cmp_by_name, NULL);
2398 else
2399 error = got_ref_list(&refs, repo, repo_ref,
2400 got_ref_cmp_by_name, NULL);
2401 if (error)
2402 goto done;
2404 SIMPLEQ_FOREACH(re, &refs, entry) {
2405 if (is_head) {
2406 error = gw_strdup_string(&refname, repo_ref, NULL);
2407 if (error)
2408 goto done;
2409 } else {
2410 error = gw_strdup_string(&refname, NULL,
2411 got_ref_get_name(re->ref));
2412 if (error)
2413 goto done;
2415 error = got_ref_open(&head_ref, repo, refname, 0);
2416 if (error)
2417 goto done;
2419 error = got_ref_resolve(&id, repo, head_ref);
2420 got_ref_close(head_ref);
2421 if (error)
2422 goto done;
2424 error = got_object_open_as_commit(&commit, repo, id);
2425 if (error)
2426 goto done;
2428 committer_time =
2429 got_object_commit_get_committer_time(commit);
2431 if (cmp_time < committer_time)
2432 cmp_time = committer_time;
2435 if (cmp_time != 0) {
2436 committer_time = cmp_time;
2437 error = gw_get_time_str(repo_age, committer_time, ref_tm);
2439 done:
2440 got_ref_list_free(&refs);
2441 free(id);
2442 return error;
2445 static const struct got_error *
2446 gw_output_diff(struct gw_trans *gw_trans, struct gw_header *header)
2448 const struct got_error *error;
2449 FILE *f = NULL;
2450 struct got_object_id *id1 = NULL, *id2 = NULL;
2451 char *label1 = NULL, *label2 = NULL, *line = NULL;
2452 int obj_type;
2453 size_t linesize = 0;
2454 ssize_t linelen;
2455 enum kcgi_err kerr = KCGI_OK;
2457 f = got_opentemp();
2458 if (f == NULL)
2459 return NULL;
2461 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
2462 if (error)
2463 goto done;
2465 if (strncmp(header->parent_id, "/dev/null", 9) != 0) {
2466 error = got_repo_match_object_id(&id1, &label1,
2467 header->parent_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2468 if (error)
2469 goto done;
2472 error = got_repo_match_object_id(&id2, &label2,
2473 header->commit_id, GOT_OBJ_TYPE_ANY, 1, header->repo);
2474 if (error)
2475 goto done;
2477 error = got_object_get_type(&obj_type, header->repo, id2);
2478 if (error)
2479 goto done;
2480 switch (obj_type) {
2481 case GOT_OBJ_TYPE_BLOB:
2482 error = got_diff_objects_as_blobs(id1, id2, NULL, NULL, 3, 0,
2483 header->repo, f);
2484 break;
2485 case GOT_OBJ_TYPE_TREE:
2486 error = got_diff_objects_as_trees(id1, id2, "", "", 3, 0,
2487 header->repo, f);
2488 break;
2489 case GOT_OBJ_TYPE_COMMIT:
2490 error = got_diff_objects_as_commits(id1, id2, 3, 0,
2491 header->repo, f);
2492 break;
2493 default:
2494 error = got_error(GOT_ERR_OBJ_TYPE);
2496 if (error)
2497 goto done;
2499 if (fseek(f, 0, SEEK_SET) == -1) {
2500 error = got_ferror(f, GOT_ERR_IO);
2501 goto done;
2504 while ((linelen = getline(&line, &linesize, f)) != -1) {
2505 error = gw_colordiff_line(gw_trans, line);
2506 if (error)
2507 goto done;
2508 /* XXX: KHTML_PRETTY breaks this */
2509 kerr = khtml_puts(gw_trans->gw_html_req, line);
2510 if (kerr != KCGI_OK)
2511 goto done;
2512 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2513 if (kerr != KCGI_OK)
2514 goto done;
2516 if (linelen == -1 && ferror(f))
2517 error = got_error_from_errno("getline");
2518 done:
2519 if (f && fclose(f) == -1 && error == NULL)
2520 error = got_error_from_errno("fclose");
2521 free(line);
2522 free(label1);
2523 free(label2);
2524 free(id1);
2525 free(id2);
2527 if (error == NULL && kerr != KCGI_OK)
2528 error = gw_kcgi_error(kerr);
2529 return error;
2532 static const struct got_error *
2533 gw_get_repo_owner(char **owner, struct gw_trans *gw_trans, char *dir)
2535 const struct got_error *error = NULL;
2536 struct got_repository *repo;
2537 const char *gitconfig_owner;
2539 *owner = NULL;
2541 if (gw_trans->gw_conf->got_show_repo_owner == 0)
2542 return NULL;
2544 error = got_repo_open(&repo, dir, NULL);
2545 if (error)
2546 return error;
2547 gitconfig_owner = got_repo_get_gitconfig_owner(repo);
2548 if (gitconfig_owner)
2549 error = gw_strdup_string(owner, NULL, gitconfig_owner);
2550 got_repo_close(repo);
2551 return error;
2554 static const struct got_error *
2555 gw_get_clone_url(char **url, struct gw_trans *gw_trans, char *dir)
2557 const struct got_error *error = NULL;
2558 FILE *f;
2559 char *d_file = NULL;
2560 unsigned int len;
2561 size_t n;
2563 *url = NULL;
2565 if (asprintf(&d_file, "%s/cloneurl", dir) == -1)
2566 return got_error_from_errno("asprintf");
2568 f = fopen(d_file, "r");
2569 if (f == NULL) {
2570 if (errno != ENOENT && errno != EACCES)
2571 error = got_error_from_errno2("fopen", d_file);
2572 goto done;
2575 if (fseek(f, 0, SEEK_END) == -1) {
2576 error = got_ferror(f, GOT_ERR_IO);
2577 goto done;
2579 len = ftell(f);
2580 if (len == -1) {
2581 error = got_ferror(f, GOT_ERR_IO);
2582 goto done;
2584 if (fseek(f, 0, SEEK_SET) == -1) {
2585 error = got_ferror(f, GOT_ERR_IO);
2586 goto done;
2589 *url = calloc(len + 1, sizeof(**url));
2590 if (*url == NULL) {
2591 error = got_error_from_errno("calloc");
2592 goto done;
2595 n = fread(*url, 1, len, f);
2596 if (n == 0 && ferror(f))
2597 error = got_ferror(f, GOT_ERR_IO);
2598 done:
2599 if (f && fclose(f) == -1 && error == NULL)
2600 error = got_error_from_errno("fclose");
2601 free(d_file);
2602 return NULL;
2605 static const struct got_error *
2606 gw_output_repo_tags(struct gw_trans *gw_trans, struct gw_header *header,
2607 int limit, int tag_type)
2609 const struct got_error *error = NULL;
2610 struct got_repository *repo = NULL;
2611 struct got_reflist_head refs;
2612 struct got_reflist_entry *re;
2613 char *age = NULL;
2614 char *id_str = NULL, *newline, *href_commits = NULL;
2615 char *tag_commit0 = NULL, *href_tag = NULL, *href_briefs = NULL;
2616 struct got_tag_object *tag = NULL;
2617 enum kcgi_err kerr = KCGI_OK;
2618 int summary_header_displayed = 0;
2620 SIMPLEQ_INIT(&refs);
2622 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
2623 if (error)
2624 goto done;
2626 error = got_ref_list(&refs, repo, "refs/tags", got_ref_cmp_tags, repo);
2627 if (error)
2628 goto done;
2630 SIMPLEQ_FOREACH(re, &refs, entry) {
2631 const char *refname;
2632 const char *tagger;
2633 const char *tag_commit;
2634 time_t tagger_time;
2635 struct got_object_id *id;
2636 struct got_commit_object *commit = NULL;
2638 refname = got_ref_get_name(re->ref);
2639 if (strncmp(refname, "refs/tags/", 10) != 0)
2640 continue;
2641 refname += 10;
2643 error = got_ref_resolve(&id, repo, re->ref);
2644 if (error)
2645 goto done;
2647 error = got_object_open_as_tag(&tag, repo, id);
2648 if (error) {
2649 if (error->code != GOT_ERR_OBJ_TYPE) {
2650 free(id);
2651 goto done;
2653 /* "lightweight" tag */
2654 error = got_object_open_as_commit(&commit, repo, id);
2655 if (error) {
2656 free(id);
2657 goto done;
2659 tagger = got_object_commit_get_committer(commit);
2660 tagger_time =
2661 got_object_commit_get_committer_time(commit);
2662 error = got_object_id_str(&id_str, id);
2663 free(id);
2664 if (error)
2665 goto done;
2666 } else {
2667 free(id);
2668 tagger = got_object_tag_get_tagger(tag);
2669 tagger_time = got_object_tag_get_tagger_time(tag);
2670 error = got_object_id_str(&id_str,
2671 got_object_tag_get_object_id(tag));
2672 if (error)
2673 goto done;
2675 if (error)
2676 goto done;
2678 if (tag_type == TAGFULL && strncmp(id_str, header->commit_id,
2679 strlen(id_str)) != 0)
2680 continue;
2682 if (commit) {
2683 error = got_object_commit_get_logmsg(&tag_commit0,
2684 commit);
2685 if (error)
2686 goto done;
2687 got_object_commit_close(commit);
2688 } else {
2689 tag_commit0 = strdup(got_object_tag_get_message(tag));
2690 if (tag_commit0 == NULL) {
2691 error = got_error_from_errno("strdup");
2692 goto done;
2696 tag_commit = tag_commit0;
2697 while (*tag_commit == '\n')
2698 tag_commit++;
2700 switch (tag_type) {
2701 case TAGBRIEF:
2702 newline = strchr(tag_commit, '\n');
2703 if (newline)
2704 *newline = '\0';
2706 if (summary_header_displayed == 0) {
2707 kerr = khtml_attr(gw_trans->gw_html_req,
2708 KELEM_DIV, KATTR_ID,
2709 "summary_tags_title_wrapper", KATTR__MAX);
2710 if (kerr != KCGI_OK)
2711 goto done;
2712 kerr = khtml_attr(gw_trans->gw_html_req,
2713 KELEM_DIV, KATTR_ID,
2714 "summary_tags_title", KATTR__MAX);
2715 if (kerr != KCGI_OK)
2716 goto done;
2717 kerr = khtml_puts(gw_trans->gw_html_req,
2718 "Tags");
2719 if (kerr != KCGI_OK)
2720 goto done;
2721 kerr = khtml_closeelem(gw_trans->gw_html_req,
2722 2);
2723 if (kerr != KCGI_OK)
2724 goto done;
2725 kerr = khtml_attr(gw_trans->gw_html_req,
2726 KELEM_DIV, KATTR_ID,
2727 "summary_tags_content", KATTR__MAX);
2728 if (kerr != KCGI_OK)
2729 goto done;
2730 summary_header_displayed = 1;
2733 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2734 KATTR_ID, "tags_wrapper", KATTR__MAX);
2735 if (kerr != KCGI_OK)
2736 goto done;
2737 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2738 KATTR_ID, "tags_age", KATTR__MAX);
2739 if (kerr != KCGI_OK)
2740 goto done;
2741 error = gw_get_time_str(&age, tagger_time, TM_DIFF);
2742 if (error)
2743 goto done;
2744 kerr = khtml_puts(gw_trans->gw_html_req,
2745 age ? age : "");
2746 if (kerr != KCGI_OK)
2747 goto done;
2748 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2749 if (kerr != KCGI_OK)
2750 goto done;
2751 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2752 KATTR_ID, "tags", KATTR__MAX);
2753 if (kerr != KCGI_OK)
2754 goto done;
2755 kerr = khtml_puts(gw_trans->gw_html_req, refname);
2756 if (kerr != KCGI_OK)
2757 goto done;
2758 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2759 if (kerr != KCGI_OK)
2760 goto done;
2761 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2762 KATTR_ID, "tags_name", KATTR__MAX);
2763 if (kerr != KCGI_OK)
2764 goto done;
2765 if (asprintf(&href_tag, "?path=%s&action=tag&commit=%s",
2766 gw_trans->repo_name, id_str) == -1) {
2767 error = got_error_from_errno("asprintf");
2768 goto done;
2770 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
2771 KATTR_HREF, href_tag, KATTR__MAX);
2772 if (kerr != KCGI_OK)
2773 goto done;
2774 kerr = khtml_puts(gw_trans->gw_html_req, tag_commit);
2775 if (kerr != KCGI_OK)
2776 goto done;
2777 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
2778 if (kerr != KCGI_OK)
2779 goto done;
2781 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2782 KATTR_ID, "navs_wrapper", KATTR__MAX);
2783 if (kerr != KCGI_OK)
2784 goto done;
2785 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2786 KATTR_ID, "navs", KATTR__MAX);
2787 if (kerr != KCGI_OK)
2788 goto done;
2790 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
2791 KATTR_HREF, href_tag, KATTR__MAX);
2792 if (kerr != KCGI_OK)
2793 goto done;
2794 kerr = khtml_puts(gw_trans->gw_html_req, "tag");
2795 if (kerr != KCGI_OK)
2796 goto done;
2797 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2798 if (kerr != KCGI_OK)
2799 goto done;
2801 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
2802 if (kerr != KCGI_OK)
2803 goto done;
2804 if (asprintf(&href_briefs,
2805 "?path=%s&action=briefs&commit=%s",
2806 gw_trans->repo_name, id_str) == -1) {
2807 error = got_error_from_errno("asprintf");
2808 goto done;
2810 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
2811 KATTR_HREF, href_briefs, KATTR__MAX);
2812 if (kerr != KCGI_OK)
2813 goto done;
2814 kerr = khtml_puts(gw_trans->gw_html_req,
2815 "commit briefs");
2816 if (kerr != KCGI_OK)
2817 goto done;
2818 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2819 if (kerr != KCGI_OK)
2820 goto done;
2822 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
2823 if (kerr != KCGI_OK)
2824 goto done;
2826 if (asprintf(&href_commits,
2827 "?path=%s&action=commits&commit=%s",
2828 gw_trans->repo_name, id_str) == -1) {
2829 error = got_error_from_errno("asprintf");
2830 goto done;
2832 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
2833 KATTR_HREF, href_commits, KATTR__MAX);
2834 if (kerr != KCGI_OK)
2835 goto done;
2836 kerr = khtml_puts(gw_trans->gw_html_req,
2837 "commits");
2838 if (kerr != KCGI_OK)
2839 goto done;
2840 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
2841 if (kerr != KCGI_OK)
2842 goto done;
2844 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2845 KATTR_ID, "dotted_line", KATTR__MAX);
2846 if (kerr != KCGI_OK)
2847 goto done;
2848 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
2849 if (kerr != KCGI_OK)
2850 goto done;
2851 break;
2852 case TAGFULL:
2853 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2854 KATTR_ID, "tag_info_date_title", KATTR__MAX);
2855 if (kerr != KCGI_OK)
2856 goto done;
2857 kerr = khtml_puts(gw_trans->gw_html_req, "Tag Date:");
2858 if (kerr != KCGI_OK)
2859 goto done;
2860 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2861 if (kerr != KCGI_OK)
2862 goto done;
2863 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2864 KATTR_ID, "tag_info_date", KATTR__MAX);
2865 if (kerr != KCGI_OK)
2866 goto done;
2867 error = gw_get_time_str(&age, tagger_time, TM_LONG);
2868 if (error)
2869 goto done;
2870 kerr = khtml_puts(gw_trans->gw_html_req,
2871 age ? age : "");
2872 if (kerr != KCGI_OK)
2873 goto done;
2874 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2875 if (kerr != KCGI_OK)
2876 goto done;
2878 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2879 KATTR_ID, "tag_info_tagger_title", KATTR__MAX);
2880 if (kerr != KCGI_OK)
2881 goto done;
2882 kerr = khtml_puts(gw_trans->gw_html_req, "Tagger:");
2883 if (kerr != KCGI_OK)
2884 goto done;
2885 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2886 if (kerr != KCGI_OK)
2887 goto done;
2888 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2889 KATTR_ID, "tag_info_date", KATTR__MAX);
2890 if (kerr != KCGI_OK)
2891 goto done;
2892 kerr = khtml_puts(gw_trans->gw_html_req, tagger);
2893 if (kerr != KCGI_OK)
2894 goto done;
2895 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2896 if (kerr != KCGI_OK)
2897 goto done;
2899 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
2900 KATTR_ID, "tag_info", KATTR__MAX);
2901 if (kerr != KCGI_OK)
2902 goto done;
2903 kerr = khttp_puts(gw_trans->gw_req, tag_commit);
2904 if (kerr != KCGI_OK)
2905 goto done;
2906 break;
2907 default:
2908 break;
2910 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
2911 if (kerr != KCGI_OK)
2912 goto done;
2914 if (limit && --limit == 0)
2915 break;
2917 if (tag)
2918 got_object_tag_close(tag);
2919 tag = NULL;
2920 free(id_str);
2921 id_str = NULL;
2922 free(age);
2923 age = NULL;
2924 free(tag_commit0);
2925 tag_commit0 = NULL;
2926 free(href_tag);
2927 href_tag = NULL;
2928 free(href_briefs);
2929 href_briefs = NULL;
2930 free(href_commits);
2931 href_commits = NULL;
2933 done:
2934 if (tag)
2935 got_object_tag_close(tag);
2936 free(id_str);
2937 free(age);
2938 free(tag_commit0);
2939 free(href_tag);
2940 free(href_briefs);
2941 free(href_commits);
2942 got_ref_list_free(&refs);
2943 if (repo)
2944 got_repo_close(repo);
2945 if (error == NULL && kerr != KCGI_OK)
2946 error = gw_kcgi_error(kerr);
2947 return error;
2950 static void
2951 gw_free_headers(struct gw_header *header)
2953 free(header->id);
2954 free(header->path);
2955 if (header->commit != NULL)
2956 got_object_commit_close(header->commit);
2957 if (header->repo)
2958 got_repo_close(header->repo);
2959 free(header->refs_str);
2960 free(header->commit_id);
2961 free(header->author);
2962 free(header->committer);
2963 free(header->parent_id);
2964 free(header->tree_id);
2965 free(header->commit_msg);
2968 static struct gw_header *
2969 gw_init_header()
2971 struct gw_header *header;
2973 header = malloc(sizeof(*header));
2974 if (header == NULL)
2975 return NULL;
2977 header->repo = NULL;
2978 header->commit = NULL;
2979 header->id = NULL;
2980 header->path = NULL;
2981 SIMPLEQ_INIT(&header->refs);
2983 return header;
2986 static const struct got_error *
2987 gw_get_commits(struct gw_trans * gw_trans, struct gw_header *header,
2988 int limit)
2990 const struct got_error *error = NULL;
2991 struct got_commit_graph *graph = NULL;
2993 error = got_commit_graph_open(&graph, header->path, 0);
2994 if (error)
2995 goto done;
2997 error = got_commit_graph_iter_start(graph, header->id, header->repo,
2998 NULL, NULL);
2999 if (error)
3000 goto done;
3002 for (;;) {
3003 error = got_commit_graph_iter_next(&header->id, graph,
3004 header->repo, NULL, NULL);
3005 if (error) {
3006 if (error->code == GOT_ERR_ITER_COMPLETED)
3007 error = NULL;
3008 goto done;
3010 if (header->id == NULL)
3011 goto done;
3013 error = got_object_open_as_commit(&header->commit, header->repo,
3014 header->id);
3015 if (error)
3016 goto done;
3018 error = gw_get_commit(gw_trans, header);
3019 if (limit > 1) {
3020 struct gw_header *n_header = NULL;
3021 if ((n_header = gw_init_header()) == NULL) {
3022 error = got_error_from_errno("malloc");
3023 goto done;
3026 error = gw_strdup_string(&n_header->refs_str,
3027 header->refs_str, NULL);
3028 if (error)
3029 goto done;
3030 error = gw_strdup_string(&n_header->commit_id,
3031 header->commit_id, NULL);
3032 if (error)
3033 goto done;
3034 error = gw_strdup_string(&n_header->parent_id,
3035 header->parent_id, NULL);
3036 if (error)
3037 goto done;
3038 error = gw_strdup_string(&n_header->tree_id,
3039 header->tree_id, NULL);
3040 if (error)
3041 goto done;
3042 error = gw_strdup_string(&n_header->author,
3043 header->author, NULL);
3044 if (error)
3045 goto done;
3046 error = gw_strdup_string(&n_header->committer,
3047 header->committer, NULL);
3048 if (error)
3049 goto done;
3050 error = gw_strdup_string(&n_header->commit_msg,
3051 header->commit_msg, NULL);
3052 if (error)
3053 goto done;
3054 n_header->committer_time = header->committer_time;
3055 TAILQ_INSERT_TAIL(&gw_trans->gw_headers, n_header,
3056 entry);
3058 if (error || (limit && --limit == 0))
3059 break;
3061 done:
3062 if (graph)
3063 got_commit_graph_close(graph);
3064 return error;
3067 static const struct got_error *
3068 gw_get_commit(struct gw_trans *gw_trans, struct gw_header *header)
3070 const struct got_error *error = NULL;
3071 struct got_reflist_entry *re;
3072 struct got_object_id *id2 = NULL;
3073 struct got_object_qid *parent_id;
3074 char *refs_str = NULL, *commit_msg = NULL, *commit_msg0;
3076 /*print commit*/
3077 SIMPLEQ_FOREACH(re, &header->refs, entry) {
3078 char *s;
3079 const char *name;
3080 struct got_tag_object *tag = NULL;
3081 int cmp;
3083 name = got_ref_get_name(re->ref);
3084 if (strcmp(name, GOT_REF_HEAD) == 0)
3085 continue;
3086 if (strncmp(name, "refs/", 5) == 0)
3087 name += 5;
3088 if (strncmp(name, "got/", 4) == 0)
3089 continue;
3090 if (strncmp(name, "heads/", 6) == 0)
3091 name += 6;
3092 if (strncmp(name, "remotes/", 8) == 0)
3093 name += 8;
3094 if (strncmp(name, "tags/", 5) == 0) {
3095 error = got_object_open_as_tag(&tag, header->repo,
3096 re->id);
3097 if (error) {
3098 if (error->code != GOT_ERR_OBJ_TYPE)
3099 continue;
3101 * Ref points at something other
3102 * than a tag.
3104 error = NULL;
3105 tag = NULL;
3108 cmp = got_object_id_cmp(tag ?
3109 got_object_tag_get_object_id(tag) : re->id, header->id);
3110 if (tag)
3111 got_object_tag_close(tag);
3112 if (cmp != 0)
3113 continue;
3114 s = refs_str;
3115 if (asprintf(&refs_str, "%s%s%s", s ? s : "",
3116 s ? ", " : "", name) == -1) {
3117 error = got_error_from_errno("asprintf");
3118 free(s);
3119 return error;
3121 error = gw_strdup_string(&header->refs_str, refs_str, NULL);
3122 free(s);
3123 if (error)
3124 return error;
3127 if (refs_str == NULL) {
3128 error = gw_strdup_string(&header->refs_str, "", NULL);
3129 if (error)
3130 return error;
3132 free(refs_str);
3134 error = got_object_id_str(&header->commit_id, header->id);
3135 if (error)
3136 return error;
3138 error = got_object_id_str(&header->tree_id,
3139 got_object_commit_get_tree_id(header->commit));
3140 if (error)
3141 return error;
3143 if (gw_trans->action == GW_DIFF) {
3144 parent_id = SIMPLEQ_FIRST(
3145 got_object_commit_get_parent_ids(header->commit));
3146 if (parent_id != NULL) {
3147 id2 = got_object_id_dup(parent_id->id);
3148 free (parent_id);
3149 error = got_object_id_str(&header->parent_id, id2);
3150 if (error)
3151 return error;
3152 free(id2);
3153 } else {
3154 error = gw_strdup_string(&header->parent_id,
3155 "/dev/null", NULL);
3156 if (error)
3157 return error;
3159 } else {
3160 error = gw_strdup_string(&header->parent_id, "", NULL);
3161 if (error)
3162 return error;
3165 header->committer_time =
3166 got_object_commit_get_committer_time(header->commit);
3168 error = gw_strdup_string(&header->author, NULL,
3169 got_object_commit_get_author(header->commit));
3170 if (error)
3171 return error;
3172 error = gw_strdup_string(&header->committer, NULL,
3173 got_object_commit_get_committer(header->commit));
3174 if (error)
3175 return error;
3176 error = got_object_commit_get_logmsg(&commit_msg0, header->commit);
3177 if (error)
3178 return error;
3180 commit_msg = commit_msg0;
3181 while (*commit_msg == '\n')
3182 commit_msg++;
3184 error = gw_strdup_string(&header->commit_msg, commit_msg, NULL);
3185 free(commit_msg0);
3186 return error;
3189 static const struct got_error *
3190 gw_get_header(struct gw_trans *gw_trans, struct gw_header *header, int limit)
3192 const struct got_error *error = NULL;
3193 char *in_repo_path = NULL;
3195 error = got_repo_open(&header->repo, gw_trans->repo_path, NULL);
3196 if (error)
3197 return error;
3199 if (gw_trans->commit == NULL) {
3200 struct got_reference *head_ref;
3201 error = got_ref_open(&head_ref, header->repo,
3202 gw_trans->headref, 0);
3203 if (error)
3204 return error;
3206 error = got_ref_resolve(&header->id, header->repo, head_ref);
3207 got_ref_close(head_ref);
3208 if (error)
3209 return error;
3211 error = got_object_open_as_commit(&header->commit,
3212 header->repo, header->id);
3213 } else {
3214 struct got_reference *ref;
3215 error = got_ref_open(&ref, header->repo, gw_trans->commit, 0);
3216 if (error == NULL) {
3217 int obj_type;
3218 error = got_ref_resolve(&header->id, header->repo, ref);
3219 got_ref_close(ref);
3220 if (error)
3221 return error;
3222 error = got_object_get_type(&obj_type, header->repo,
3223 header->id);
3224 if (error)
3225 return error;
3226 if (obj_type == GOT_OBJ_TYPE_TAG) {
3227 struct got_tag_object *tag;
3228 error = got_object_open_as_tag(&tag,
3229 header->repo, header->id);
3230 if (error)
3231 return error;
3232 if (got_object_tag_get_object_type(tag) !=
3233 GOT_OBJ_TYPE_COMMIT) {
3234 got_object_tag_close(tag);
3235 error = got_error(GOT_ERR_OBJ_TYPE);
3236 return error;
3238 free(header->id);
3239 header->id = got_object_id_dup(
3240 got_object_tag_get_object_id(tag));
3241 if (header->id == NULL)
3242 error = got_error_from_errno(
3243 "got_object_id_dup");
3244 got_object_tag_close(tag);
3245 if (error)
3246 return error;
3247 } else if (obj_type != GOT_OBJ_TYPE_COMMIT) {
3248 error = got_error(GOT_ERR_OBJ_TYPE);
3249 return error;
3251 error = got_object_open_as_commit(&header->commit,
3252 header->repo, header->id);
3253 if (error)
3254 return error;
3256 if (header->commit == NULL) {
3257 error = got_repo_match_object_id_prefix(&header->id,
3258 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
3259 header->repo);
3260 if (error)
3261 return error;
3263 error = got_repo_match_object_id_prefix(&header->id,
3264 gw_trans->commit, GOT_OBJ_TYPE_COMMIT,
3265 header->repo);
3268 error = got_repo_map_path(&in_repo_path, header->repo,
3269 gw_trans->repo_path, 1);
3270 if (error)
3271 return error;
3273 if (in_repo_path) {
3274 error = gw_strdup_string(&header->path, in_repo_path, NULL);
3275 if (error) {
3276 free(in_repo_path);
3277 return error;
3280 free(in_repo_path);
3282 error = got_ref_list(&header->refs, header->repo, NULL,
3283 got_ref_cmp_by_name, NULL);
3284 if (error)
3285 return error;
3287 error = gw_get_commits(gw_trans, header, limit);
3288 return error;
3291 struct blame_line {
3292 int annotated;
3293 char *id_str;
3294 char *committer;
3295 char datebuf[11]; /* YYYY-MM-DD + NUL */
3298 struct gw_blame_cb_args {
3299 struct blame_line *lines;
3300 int nlines;
3301 int nlines_prec;
3302 int lineno_cur;
3303 off_t *line_offsets;
3304 FILE *f;
3305 struct got_repository *repo;
3306 struct gw_trans *gw_trans;
3309 static const struct got_error *
3310 gw_blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
3312 const struct got_error *err = NULL;
3313 struct gw_blame_cb_args *a = arg;
3314 struct blame_line *bline;
3315 char *line = NULL;
3316 size_t linesize = 0;
3317 struct got_commit_object *commit = NULL;
3318 off_t offset;
3319 struct tm tm;
3320 time_t committer_time;
3321 enum kcgi_err kerr = KCGI_OK;
3323 if (nlines != a->nlines ||
3324 (lineno != -1 && lineno < 1) || lineno > a->nlines)
3325 return got_error(GOT_ERR_RANGE);
3327 if (lineno == -1)
3328 return NULL; /* no change in this commit */
3330 /* Annotate this line. */
3331 bline = &a->lines[lineno - 1];
3332 if (bline->annotated)
3333 return NULL;
3334 err = got_object_id_str(&bline->id_str, id);
3335 if (err)
3336 return err;
3338 err = got_object_open_as_commit(&commit, a->repo, id);
3339 if (err)
3340 goto done;
3342 err = gw_strdup_string(&bline->committer, NULL,
3343 got_object_commit_get_committer(commit));
3344 if (err)
3345 goto done;
3347 committer_time = got_object_commit_get_committer_time(commit);
3348 if (localtime_r(&committer_time, &tm) == NULL)
3349 return got_error_from_errno("localtime_r");
3350 if (strftime(bline->datebuf, sizeof(bline->datebuf), "%G-%m-%d",
3351 &tm) >= sizeof(bline->datebuf)) {
3352 err = got_error(GOT_ERR_NO_SPACE);
3353 goto done;
3355 bline->annotated = 1;
3357 /* Print lines annotated so far. */
3358 bline = &a->lines[a->lineno_cur - 1];
3359 if (!bline->annotated)
3360 goto done;
3362 offset = a->line_offsets[a->lineno_cur - 1];
3363 if (fseeko(a->f, offset, SEEK_SET) == -1) {
3364 err = got_error_from_errno("fseeko");
3365 goto done;
3368 while (bline->annotated) {
3369 char *smallerthan, *at, *nl, *committer;
3370 char *lineno = NULL, *href_diff = NULL, *href_link = NULL;
3371 size_t len;
3373 if (getline(&line, &linesize, a->f) == -1) {
3374 if (ferror(a->f))
3375 err = got_error_from_errno("getline");
3376 break;
3379 committer = bline->committer;
3380 smallerthan = strchr(committer, '<');
3381 if (smallerthan && smallerthan[1] != '\0')
3382 committer = smallerthan + 1;
3383 at = strchr(committer, '@');
3384 if (at)
3385 *at = '\0';
3386 len = strlen(committer);
3387 if (len >= 9)
3388 committer[8] = '\0';
3390 nl = strchr(line, '\n');
3391 if (nl)
3392 *nl = '\0';
3394 if (a->gw_trans->repo_folder == NULL) {
3395 err = gw_strdup_string(&a->gw_trans->repo_folder, "",
3396 NULL);
3397 if (err)
3398 goto err;
3400 if (a->gw_trans->repo_folder == NULL)
3401 goto err;
3403 kerr = khtml_attr(a->gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
3404 "blame_wrapper", KATTR__MAX);
3405 if (kerr != KCGI_OK)
3406 goto err;
3407 kerr = khtml_attr(a->gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
3408 "blame_number", KATTR__MAX);
3409 if (kerr != KCGI_OK)
3410 goto err;
3411 if (asprintf(&lineno, "%.*d", a->nlines_prec,
3412 a->lineno_cur) == -1)
3413 goto err;
3414 kerr = khtml_puts(a->gw_trans->gw_html_req, lineno);
3415 if (kerr != KCGI_OK)
3416 goto err;
3417 kerr = khtml_closeelem(a->gw_trans->gw_html_req, 1);
3418 if (kerr != KCGI_OK)
3419 goto err;
3421 kerr = khtml_attr(a->gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
3422 "blame_hash", KATTR__MAX);
3423 if (kerr != KCGI_OK)
3424 goto err;
3425 if (asprintf(&href_diff,
3426 "?path=%s&action=diff&commit=%s&file=%s&folder=%s",
3427 a->gw_trans->repo_name, bline->id_str,
3428 a->gw_trans->repo_file, a->gw_trans->repo_folder) == -1) {
3429 err = got_error_from_errno("asprintf");
3430 goto err;
3432 if (asprintf(&href_link, "%.8s", bline->id_str) == -1) {
3433 err = got_error_from_errno("asprintf");
3434 goto err;
3436 kerr = khtml_attr(a->gw_trans->gw_html_req, KELEM_A,
3437 KATTR_HREF, href_diff, KATTR__MAX);
3438 if (kerr != KCGI_OK)
3439 goto done;
3440 kerr = khtml_puts(a->gw_trans->gw_html_req, href_link);
3441 if (kerr != KCGI_OK)
3442 goto err;
3443 kerr = khtml_closeelem(a->gw_trans->gw_html_req, 2);
3444 if (kerr != KCGI_OK)
3445 goto err;
3447 kerr = khtml_attr(a->gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
3448 "blame_date", KATTR__MAX);
3449 if (kerr != KCGI_OK)
3450 goto err;
3451 kerr = khtml_puts(a->gw_trans->gw_html_req, bline->datebuf);
3452 if (kerr != KCGI_OK)
3453 goto err;
3454 kerr = khtml_closeelem(a->gw_trans->gw_html_req, 1);
3455 if (kerr != KCGI_OK)
3456 goto err;
3458 kerr = khtml_attr(a->gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
3459 "blame_author", KATTR__MAX);
3460 if (kerr != KCGI_OK)
3461 goto err;
3462 kerr = khtml_puts(a->gw_trans->gw_html_req, committer);
3463 if (kerr != KCGI_OK)
3464 goto err;
3465 kerr = khtml_closeelem(a->gw_trans->gw_html_req, 1);
3466 if (kerr != KCGI_OK)
3467 goto err;
3469 kerr = khtml_attr(a->gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
3470 "blame_code", KATTR__MAX);
3471 if (kerr != KCGI_OK)
3472 goto err;
3473 kerr = khtml_puts(a->gw_trans->gw_html_req, line);
3474 if (kerr != KCGI_OK)
3475 goto err;
3476 kerr = khtml_closeelem(a->gw_trans->gw_html_req, 1);
3477 if (kerr != KCGI_OK)
3478 goto err;
3480 kerr = khtml_closeelem(a->gw_trans->gw_html_req, 1);
3481 if (kerr != KCGI_OK)
3482 goto err;
3484 a->lineno_cur++;
3485 bline = &a->lines[a->lineno_cur - 1];
3486 err:
3487 free(lineno);
3488 free(href_diff);
3489 free(href_link);
3491 done:
3492 if (commit)
3493 got_object_commit_close(commit);
3494 free(line);
3495 if (err == NULL && kerr != KCGI_OK)
3496 err = gw_kcgi_error(kerr);
3497 return err;
3500 static const struct got_error *
3501 gw_output_file_blame(struct gw_trans *gw_trans)
3503 const struct got_error *error = NULL;
3504 struct got_repository *repo = NULL;
3505 struct got_object_id *obj_id = NULL;
3506 struct got_object_id *commit_id = NULL;
3507 struct got_blob_object *blob = NULL;
3508 char *path = NULL, *in_repo_path = NULL;
3509 struct gw_blame_cb_args bca;
3510 int i, obj_type;
3511 size_t filesize;
3513 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3514 if (error)
3515 return error;
3517 if (asprintf(&path, "%s%s%s",
3518 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3519 gw_trans->repo_folder ? "/" : "",
3520 gw_trans->repo_file) == -1) {
3521 error = got_error_from_errno("asprintf");
3522 goto done;
3525 error = got_repo_map_path(&in_repo_path, repo, path, 1);
3526 if (error)
3527 goto done;
3529 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
3530 GOT_OBJ_TYPE_COMMIT, 1, repo);
3531 if (error)
3532 goto done;
3534 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
3535 if (error)
3536 goto done;
3538 if (obj_id == NULL) {
3539 error = got_error(GOT_ERR_NO_OBJ);
3540 goto done;
3543 error = got_object_get_type(&obj_type, repo, obj_id);
3544 if (error)
3545 goto done;
3547 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3548 error = got_error(GOT_ERR_OBJ_TYPE);
3549 goto done;
3552 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
3553 if (error)
3554 goto done;
3556 bca.f = got_opentemp();
3557 if (bca.f == NULL) {
3558 error = got_error_from_errno("got_opentemp");
3559 goto done;
3561 error = got_object_blob_dump_to_file(&filesize, &bca.nlines,
3562 &bca.line_offsets, bca.f, blob);
3563 if (error || bca.nlines == 0)
3564 goto done;
3566 /* Don't include \n at EOF in the blame line count. */
3567 if (bca.line_offsets[bca.nlines - 1] == filesize)
3568 bca.nlines--;
3570 bca.lines = calloc(bca.nlines, sizeof(*bca.lines));
3571 if (bca.lines == NULL) {
3572 error = got_error_from_errno("calloc");
3573 goto done;
3575 bca.lineno_cur = 1;
3576 bca.nlines_prec = 0;
3577 i = bca.nlines;
3578 while (i > 0) {
3579 i /= 10;
3580 bca.nlines_prec++;
3582 bca.repo = repo;
3583 bca.gw_trans = gw_trans;
3585 error = got_blame(in_repo_path, commit_id, repo, gw_blame_cb, &bca,
3586 NULL, NULL);
3587 if (error)
3588 goto done;
3589 done:
3590 free(bca.line_offsets);
3591 free(in_repo_path);
3592 free(commit_id);
3593 free(obj_id);
3594 free(path);
3596 for (i = 0; i < bca.nlines; i++) {
3597 struct blame_line *bline = &bca.lines[i];
3598 free(bline->id_str);
3599 free(bline->committer);
3601 free(bca.lines);
3602 if (bca.f && fclose(bca.f) == EOF && error == NULL)
3603 error = got_error_from_errno("fclose");
3604 if (blob)
3605 got_object_blob_close(blob);
3606 if (repo)
3607 got_repo_close(repo);
3608 return error;
3611 static const struct got_error *
3612 gw_output_blob_buf(struct gw_trans *gw_trans)
3614 const struct got_error *error = NULL;
3615 struct got_repository *repo = NULL;
3616 struct got_object_id *obj_id = NULL;
3617 struct got_object_id *commit_id = NULL;
3618 struct got_blob_object *blob = NULL;
3619 char *path = NULL, *in_repo_path = NULL;
3620 int obj_type, set_mime = 0;
3621 size_t len, hdrlen;
3622 const uint8_t *buf;
3623 enum kcgi_err kerr = KCGI_OK;
3625 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3626 if (error)
3627 return error;
3629 if (asprintf(&path, "%s%s%s",
3630 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3631 gw_trans->repo_folder ? "/" : "",
3632 gw_trans->repo_file) == -1) {
3633 error = got_error_from_errno("asprintf");
3634 goto done;
3637 error = got_repo_map_path(&in_repo_path, repo, path, 1);
3638 if (error)
3639 goto done;
3641 error = got_repo_match_object_id(&commit_id, NULL, gw_trans->commit,
3642 GOT_OBJ_TYPE_COMMIT, 1, repo);
3643 if (error)
3644 goto done;
3646 error = got_object_id_by_path(&obj_id, repo, commit_id, in_repo_path);
3647 if (error)
3648 goto done;
3650 if (obj_id == NULL) {
3651 error = got_error(GOT_ERR_NO_OBJ);
3652 goto done;
3655 error = got_object_get_type(&obj_type, repo, obj_id);
3656 if (error)
3657 goto done;
3659 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3660 error = got_error(GOT_ERR_OBJ_TYPE);
3661 goto done;
3664 error = got_object_open_as_blob(&blob, repo, obj_id, 8192);
3665 if (error)
3666 goto done;
3668 hdrlen = got_object_blob_get_hdrlen(blob);
3669 do {
3670 error = got_object_blob_read_block(&len, blob);
3671 if (error)
3672 goto done;
3673 buf = got_object_blob_get_read_buf(blob);
3676 * Skip blob object header first time around,
3677 * which also contains a zero byte.
3679 buf += hdrlen;
3680 if (set_mime == 0) {
3681 if (isbinary(buf, len - hdrlen))
3682 gw_trans->mime = KMIME_APP_OCTET_STREAM;
3683 else
3684 gw_trans->mime = KMIME_TEXT_PLAIN;
3685 set_mime = 1;
3686 error = gw_display_index(gw_trans);
3687 if (error)
3688 goto done;
3690 khttp_write(gw_trans->gw_req, buf, len - hdrlen);
3691 hdrlen = 0;
3692 } while (len != 0);
3693 done:
3694 free(in_repo_path);
3695 free(commit_id);
3696 free(obj_id);
3697 free(path);
3698 if (blob)
3699 got_object_blob_close(blob);
3700 if (repo)
3701 got_repo_close(repo);
3702 if (error == NULL && kerr != KCGI_OK)
3703 error = gw_kcgi_error(kerr);
3704 return error;
3707 static const struct got_error *
3708 gw_output_repo_tree(struct gw_trans *gw_trans)
3710 const struct got_error *error = NULL;
3711 struct got_repository *repo = NULL;
3712 struct got_object_id *tree_id = NULL, *commit_id = NULL;
3713 struct got_tree_object *tree = NULL;
3714 char *path = NULL, *in_repo_path = NULL;
3715 char *id_str = NULL;
3716 char *build_folder = NULL;
3717 char *href_blob = NULL, *href_blame = NULL;
3718 const char *class = NULL;
3719 int nentries, i, class_flip = 0;
3720 enum kcgi_err kerr = KCGI_OK;
3722 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3723 if (error)
3724 goto done;
3726 if (gw_trans->repo_folder != NULL) {
3727 error = gw_strdup_string(&path, gw_trans->repo_folder, NULL);
3728 if (error)
3729 goto done;
3730 } else {
3731 error = got_repo_map_path(&in_repo_path, repo,
3732 gw_trans->repo_path, 1);
3733 if (error)
3734 goto done;
3735 free(path);
3736 path = in_repo_path;
3739 if (gw_trans->commit == NULL) {
3740 struct got_reference *head_ref;
3741 error = got_ref_open(&head_ref, repo, gw_trans->headref, 0);
3742 if (error)
3743 goto done;
3744 error = got_ref_resolve(&commit_id, repo, head_ref);
3745 if (error)
3746 goto done;
3747 got_ref_close(head_ref);
3749 } else {
3750 error = got_repo_match_object_id(&commit_id, NULL,
3751 gw_trans->commit, GOT_OBJ_TYPE_COMMIT, 1, repo);
3752 if (error)
3753 goto done;
3756 error = got_object_id_str(&gw_trans->commit, commit_id);
3757 if (error)
3758 goto done;
3760 error = got_object_id_by_path(&tree_id, repo, commit_id, path);
3761 if (error)
3762 goto done;
3764 error = got_object_open_as_tree(&tree, repo, tree_id);
3765 if (error)
3766 goto done;
3768 nentries = got_object_tree_get_nentries(tree);
3769 for (i = 0; i < nentries; i++) {
3770 struct got_tree_entry *te;
3771 const char *modestr = "";
3772 mode_t mode;
3774 te = got_object_tree_get_entry(tree, i);
3776 error = got_object_id_str(&id_str, got_tree_entry_get_id(te));
3777 if (error)
3778 goto done;
3780 mode = got_tree_entry_get_mode(te);
3781 if (got_object_tree_entry_is_submodule(te))
3782 modestr = "$";
3783 else if (S_ISLNK(mode))
3784 modestr = "@";
3785 else if (S_ISDIR(mode))
3786 modestr = "/";
3787 else if (mode & S_IXUSR)
3788 modestr = "*";
3790 if (class_flip == 0) {
3791 class = "back_lightgray";
3792 class_flip = 1;
3793 } else {
3794 class = "back_white";
3795 class_flip = 0;
3798 if (S_ISDIR(mode)) {
3799 if (asprintf(&build_folder, "%s/%s",
3800 gw_trans->repo_folder ? gw_trans->repo_folder : "",
3801 got_tree_entry_get_name(te)) == -1) {
3802 error = got_error_from_errno(
3803 "asprintf");
3804 goto done;
3806 if (asprintf(&href_blob,
3807 "?path=%s&action=%s&commit=%s&folder=%s",
3808 gw_trans->repo_name, gw_trans->action_name,
3809 gw_trans->commit, build_folder) == -1) {
3810 error = got_error_from_errno("asprintf");
3811 goto done;
3814 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3815 KATTR_ID, "tree_wrapper", KATTR__MAX);
3816 if (kerr != KCGI_OK)
3817 goto done;
3818 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3819 KATTR_ID, "tree_line", KATTR_CLASS, class,
3820 KATTR__MAX);
3821 if (kerr != KCGI_OK)
3822 goto done;
3823 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
3824 KATTR_HREF, href_blob, KATTR_CLASS,
3825 "diff_directory", KATTR__MAX);
3826 if (kerr != KCGI_OK)
3827 goto done;
3828 kerr = khtml_puts(gw_trans->gw_html_req,
3829 got_tree_entry_get_name(te));
3830 if (kerr != KCGI_OK)
3831 goto done;
3832 kerr = khtml_puts(gw_trans->gw_html_req, modestr);
3833 if (kerr != KCGI_OK)
3834 goto done;
3835 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
3836 if (kerr != KCGI_OK)
3837 goto done;
3838 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3839 KATTR_ID, "tree_line_blank", KATTR_CLASS, class,
3840 KATTR__MAX);
3841 if (kerr != KCGI_OK)
3842 goto done;
3843 kerr = khtml_entity(gw_trans->gw_html_req,
3844 KENTITY_nbsp);
3845 if (kerr != KCGI_OK)
3846 goto done;
3847 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
3848 if (kerr != KCGI_OK)
3849 goto done;
3850 } else {
3851 if (asprintf(&href_blob,
3852 "?path=%s&action=%s&commit=%s&file=%s&folder=%s",
3853 gw_trans->repo_name, "blob", gw_trans->commit,
3854 got_tree_entry_get_name(te),
3855 gw_trans->repo_folder ?
3856 gw_trans->repo_folder : "") == -1) {
3857 error = got_error_from_errno("asprintf");
3858 goto done;
3860 if (asprintf(&href_blame,
3861 "?path=%s&action=%s&commit=%s&file=%s&folder=%s",
3862 gw_trans->repo_name, "blame", gw_trans->commit,
3863 got_tree_entry_get_name(te),
3864 gw_trans->repo_folder ?
3865 gw_trans->repo_folder : "") == -1) {
3866 error = got_error_from_errno("asprintf");
3867 goto done;
3870 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3871 KATTR_ID, "tree_wrapper", KATTR__MAX);
3872 if (kerr != KCGI_OK)
3873 goto done;
3874 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3875 KATTR_ID, "tree_line", KATTR_CLASS, class,
3876 KATTR__MAX);
3877 if (kerr != KCGI_OK)
3878 goto done;
3879 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
3880 KATTR_HREF, href_blob, KATTR__MAX);
3881 if (kerr != KCGI_OK)
3882 goto done;
3883 kerr = khtml_puts(gw_trans->gw_html_req,
3884 got_tree_entry_get_name(te));
3885 if (kerr != KCGI_OK)
3886 goto done;
3887 kerr = khtml_puts(gw_trans->gw_html_req, modestr);
3888 if (kerr != KCGI_OK)
3889 goto done;
3890 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
3891 if (kerr != KCGI_OK)
3892 goto done;
3893 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3894 KATTR_ID, "tree_line_navs", KATTR_CLASS, class,
3895 KATTR__MAX);
3896 if (kerr != KCGI_OK)
3897 goto done;
3899 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
3900 KATTR_HREF, href_blob, KATTR__MAX);
3901 if (kerr != KCGI_OK)
3902 goto done;
3903 kerr = khtml_puts(gw_trans->gw_html_req, "blob");
3904 if (kerr != KCGI_OK)
3905 goto done;
3906 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
3907 if (kerr != KCGI_OK)
3908 goto done;
3910 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
3911 if (kerr != KCGI_OK)
3912 goto done;
3914 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A,
3915 KATTR_HREF, href_blame, KATTR__MAX);
3916 if (kerr != KCGI_OK)
3917 goto done;
3918 kerr = khtml_puts(gw_trans->gw_html_req, "blame");
3919 if (kerr != KCGI_OK)
3920 goto done;
3922 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
3923 if (kerr != KCGI_OK)
3924 goto done;
3926 free(id_str);
3927 id_str = NULL;
3928 free(href_blob);
3929 href_blob = NULL;
3930 free(build_folder);
3931 build_folder = NULL;
3933 done:
3934 if (tree)
3935 got_object_tree_close(tree);
3936 if (repo)
3937 got_repo_close(repo);
3938 free(id_str);
3939 free(href_blob);
3940 free(href_blame);
3941 free(in_repo_path);
3942 free(tree_id);
3943 free(build_folder);
3944 if (error == NULL && kerr != KCGI_OK)
3945 error = gw_kcgi_error(kerr);
3946 return error;
3949 static const struct got_error *
3950 gw_output_repo_heads(struct gw_trans *gw_trans)
3952 const struct got_error *error = NULL;
3953 struct got_repository *repo = NULL;
3954 struct got_reflist_head refs;
3955 struct got_reflist_entry *re;
3956 char *age = NULL, *href_summary = NULL, *href_briefs = NULL;
3957 char *href_commits = NULL;
3958 enum kcgi_err kerr = KCGI_OK;
3960 SIMPLEQ_INIT(&refs);
3962 error = got_repo_open(&repo, gw_trans->repo_path, NULL);
3963 if (error)
3964 goto done;
3966 error = got_ref_list(&refs, repo, "refs/heads", got_ref_cmp_by_name,
3967 NULL);
3968 if (error)
3969 goto done;
3971 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3972 KATTR_ID, "summary_heads_title_wrapper", KATTR__MAX);
3973 if (kerr != KCGI_OK)
3974 goto done;
3975 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3976 KATTR_ID, "summary_heads_title", KATTR__MAX);
3977 if (kerr != KCGI_OK)
3978 goto done;
3979 kerr = khtml_puts(gw_trans->gw_html_req, "Heads");
3980 if (kerr != KCGI_OK)
3981 goto done;
3982 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
3983 if (kerr != KCGI_OK)
3984 goto done;
3985 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
3986 KATTR_ID, "summary_heads_content", KATTR__MAX);
3987 if (kerr != KCGI_OK)
3988 goto done;
3990 SIMPLEQ_FOREACH(re, &refs, entry) {
3991 char *refname;
3993 error = gw_strdup_string(&refname, NULL,
3994 got_ref_get_name(re->ref));
3995 if (error)
3996 goto done;
3998 if (strncmp(refname, "refs/heads/", 11) != 0) {
3999 free(refname);
4000 continue;
4003 error = gw_get_repo_age(&age, gw_trans, gw_trans->gw_dir->path,
4004 refname, TM_DIFF);
4005 if (error)
4006 goto done;
4008 if (strncmp(refname, "refs/heads/", 11) == 0)
4009 refname += 11;
4011 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
4012 KATTR_ID, "heads_wrapper", KATTR__MAX);
4013 if (kerr != KCGI_OK)
4014 goto done;
4015 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
4016 KATTR_ID, "heads_age", KATTR__MAX);
4017 if (kerr != KCGI_OK)
4018 goto done;
4019 kerr = khtml_puts(gw_trans->gw_html_req, age ? age : "");
4020 if (kerr != KCGI_OK)
4021 goto done;
4022 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
4023 if (kerr != KCGI_OK)
4024 goto done;
4025 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
4026 KATTR_ID, "heads_space", KATTR__MAX);
4027 if (kerr != KCGI_OK)
4028 goto done;
4029 kerr = khtml_entity(gw_trans->gw_html_req, KENTITY_nbsp);
4030 if (kerr != KCGI_OK)
4031 goto done;
4032 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
4033 if (kerr != KCGI_OK)
4034 goto done;
4035 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV,
4036 KATTR_ID, "head", KATTR__MAX);
4037 if (kerr != KCGI_OK)
4038 goto done;
4039 if (asprintf(&href_summary,
4040 "?path=%s&action=summary&headref=%s",
4041 gw_trans->repo_name, refname) == -1) {
4042 error = got_error_from_errno("asprintf");
4043 goto done;
4045 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
4046 href_summary, KATTR__MAX);
4047 kerr = khtml_puts(gw_trans->gw_html_req, refname);
4048 if (kerr != KCGI_OK)
4049 goto done;
4050 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
4051 if (kerr != KCGI_OK)
4052 goto done;
4054 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
4055 "navs_wrapper", KATTR__MAX);
4056 if (kerr != KCGI_OK)
4057 goto done;
4058 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
4059 "navs", KATTR__MAX);
4060 if (kerr != KCGI_OK)
4061 goto done;
4063 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
4064 href_summary, KATTR__MAX);
4065 if (kerr != KCGI_OK)
4066 goto done;
4067 kerr = khtml_puts(gw_trans->gw_html_req, "summary");
4068 if (kerr != KCGI_OK)
4069 goto done;
4070 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
4071 if (kerr != KCGI_OK)
4072 goto done;
4074 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
4075 if (kerr != KCGI_OK)
4076 goto done;
4077 if (asprintf(&href_briefs, "?path=%s&action=briefs&headref=%s",
4078 gw_trans->repo_name, refname) == -1) {
4079 error = got_error_from_errno("asprintf");
4080 goto done;
4082 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
4083 href_briefs, KATTR__MAX);
4084 if (kerr != KCGI_OK)
4085 goto done;
4086 kerr = khtml_puts(gw_trans->gw_html_req, "commit briefs");
4087 if (kerr != KCGI_OK)
4088 goto done;
4089 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
4090 if (kerr != KCGI_OK)
4091 goto done;
4093 kerr = khtml_puts(gw_trans->gw_html_req, " | ");
4094 if (kerr != KCGI_OK)
4095 goto done;
4097 if (asprintf(&href_commits,
4098 "?path=%s&action=commits&headref=%s",
4099 gw_trans->repo_name, refname) == -1) {
4100 error = got_error_from_errno("asprintf");
4101 goto done;
4103 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
4104 href_commits, KATTR__MAX);
4105 if (kerr != KCGI_OK)
4106 goto done;
4107 kerr = khtml_puts(gw_trans->gw_html_req, "commits");
4108 if (kerr != KCGI_OK)
4109 goto done;
4110 kerr = khtml_closeelem(gw_trans->gw_html_req, 3);
4111 if (kerr != KCGI_OK)
4112 goto done;
4114 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
4115 "dotted_line", KATTR__MAX);
4116 if (kerr != KCGI_OK)
4117 goto done;
4118 kerr = khtml_closeelem(gw_trans->gw_html_req, 2);
4119 if (kerr != KCGI_OK)
4120 goto done;
4121 break;
4122 free(href_summary);
4123 href_summary = NULL;
4124 free(href_briefs);
4125 href_briefs = NULL;
4126 free(href_commits);
4127 href_commits = NULL;
4129 done:
4130 got_ref_list_free(&refs);
4131 free(href_summary);
4132 free(href_briefs);
4133 free(href_commits);
4134 if (repo)
4135 got_repo_close(repo);
4136 return error;
4139 static const struct got_error *
4140 gw_output_site_link(struct gw_trans *gw_trans)
4142 const struct got_error *error = NULL;
4143 char *href_summary = NULL;
4144 enum kcgi_err kerr = KCGI_OK;
4146 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
4147 "site_link", KATTR__MAX);
4148 if (kerr != KCGI_OK)
4149 goto done;
4150 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF, GOTWEB,
4151 KATTR__MAX);
4152 if (kerr != KCGI_OK)
4153 goto done;
4154 kerr = khtml_puts(gw_trans->gw_html_req,
4155 gw_trans->gw_conf->got_site_link);
4156 if (kerr != KCGI_OK)
4157 goto done;
4158 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
4159 if (kerr != KCGI_OK)
4160 goto done;
4162 if (gw_trans->repo_name != NULL) {
4163 kerr = khtml_puts(gw_trans->gw_html_req, " / ");
4164 if (kerr != KCGI_OK)
4165 goto done;
4166 if (asprintf(&href_summary, "?path=%s&action=summary",
4167 gw_trans->repo_name) == -1)
4168 goto done;
4169 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_A, KATTR_HREF,
4170 href_summary, KATTR__MAX);
4171 if (kerr != KCGI_OK)
4172 goto done;
4173 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->repo_name);
4174 if (kerr != KCGI_OK)
4175 goto done;
4176 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
4177 if (kerr != KCGI_OK)
4178 goto done;
4179 kerr = khtml_puts(gw_trans->gw_html_req, " / ");
4180 if (kerr != KCGI_OK)
4181 goto done;
4182 kerr = khtml_puts(gw_trans->gw_html_req, gw_trans->action_name);
4183 if (kerr != KCGI_OK)
4184 goto done;
4187 kerr = khtml_closeelem(gw_trans->gw_html_req, 1);
4188 if (kerr != KCGI_OK)
4189 goto done;
4190 done:
4191 free(href_summary);
4192 return error;
4195 static const struct got_error *
4196 gw_colordiff_line(struct gw_trans *gw_trans, char *buf)
4198 const struct got_error *error = NULL;
4199 char *color = NULL;
4200 enum kcgi_err kerr = KCGI_OK;
4202 if (strncmp(buf, "-", 1) == 0)
4203 color = "diff_minus";
4204 else if (strncmp(buf, "+", 1) == 0)
4205 color = "diff_plus";
4206 else if (strncmp(buf, "@@", 2) == 0)
4207 color = "diff_chunk_header";
4208 else if (strncmp(buf, "@@", 2) == 0)
4209 color = "diff_chunk_header";
4210 else if (strncmp(buf, "commit +", 8) == 0)
4211 color = "diff_meta";
4212 else if (strncmp(buf, "commit -", 8) == 0)
4213 color = "diff_meta";
4214 else if (strncmp(buf, "blob +", 6) == 0)
4215 color = "diff_meta";
4216 else if (strncmp(buf, "blob -", 6) == 0)
4217 color = "diff_meta";
4218 else if (strncmp(buf, "file +", 6) == 0)
4219 color = "diff_meta";
4220 else if (strncmp(buf, "file -", 6) == 0)
4221 color = "diff_meta";
4222 else if (strncmp(buf, "from:", 5) == 0)
4223 color = "diff_author";
4224 else if (strncmp(buf, "via:", 4) == 0)
4225 color = "diff_author";
4226 else if (strncmp(buf, "date:", 5) == 0)
4227 color = "diff_date";
4228 kerr = khtml_attr(gw_trans->gw_html_req, KELEM_DIV, KATTR_ID,
4229 "diff_line", KATTR_CLASS, color ? color : "", KATTR__MAX);
4230 if (error == NULL && kerr != KCGI_OK)
4231 error = gw_kcgi_error(kerr);
4232 return error;
4235 int
4236 main(int argc, char *argv[])
4238 const struct got_error *error = NULL;
4239 struct gw_trans *gw_trans;
4240 struct gw_dir *dir = NULL, *tdir;
4241 const char *page = "index";
4242 int gw_malloc = 1;
4243 enum kcgi_err kerr;
4245 if ((gw_trans = malloc(sizeof(struct gw_trans))) == NULL)
4246 errx(1, "malloc");
4248 if ((gw_trans->gw_req = malloc(sizeof(struct kreq))) == NULL)
4249 errx(1, "malloc");
4251 if ((gw_trans->gw_html_req = malloc(sizeof(struct khtmlreq))) == NULL)
4252 errx(1, "malloc");
4254 if ((gw_trans->gw_tmpl = malloc(sizeof(struct ktemplate))) == NULL)
4255 errx(1, "malloc");
4257 kerr = khttp_parse(gw_trans->gw_req, gw_keys, KEY__ZMAX, &page, 1, 0);
4258 if (kerr != KCGI_OK) {
4259 error = gw_kcgi_error(kerr);
4260 goto done;
4263 if ((gw_trans->gw_conf =
4264 malloc(sizeof(struct gotweb_conf))) == NULL) {
4265 gw_malloc = 0;
4266 error = got_error_from_errno("malloc");
4267 goto done;
4270 TAILQ_INIT(&gw_trans->gw_dirs);
4271 TAILQ_INIT(&gw_trans->gw_headers);
4273 gw_trans->page = 0;
4274 gw_trans->repos_total = 0;
4275 gw_trans->repo_path = NULL;
4276 gw_trans->commit = NULL;
4277 error = gw_strdup_string(&gw_trans->headref, GOT_REF_HEAD, NULL);
4278 if (error)
4279 goto done;
4280 gw_trans->mime = KMIME_TEXT_HTML;
4281 gw_trans->gw_tmpl->key = gw_templs;
4282 gw_trans->gw_tmpl->keysz = TEMPL__MAX;
4283 gw_trans->gw_tmpl->arg = gw_trans;
4284 gw_trans->gw_tmpl->cb = gw_template;
4285 error = parse_conf(GOTWEB_CONF, gw_trans->gw_conf);
4286 if (error)
4287 goto done;
4289 error = gw_parse_querystring(gw_trans);
4290 if (error)
4291 goto done;
4293 if (gw_trans->action == GW_BLOB)
4294 error = gw_blob(gw_trans);
4295 else
4296 error = gw_display_index(gw_trans);
4297 done:
4298 if (error) {
4299 gw_trans->mime = KMIME_TEXT_PLAIN;
4300 gw_trans->action = GW_ERR;
4301 gw_display_error(gw_trans, error);
4303 if (gw_malloc) {
4304 free(gw_trans->gw_conf->got_repos_path);
4305 free(gw_trans->gw_conf->got_site_name);
4306 free(gw_trans->gw_conf->got_site_owner);
4307 free(gw_trans->gw_conf->got_site_link);
4308 free(gw_trans->gw_conf->got_logo);
4309 free(gw_trans->gw_conf->got_logo_url);
4310 free(gw_trans->gw_conf);
4311 free(gw_trans->commit);
4312 free(gw_trans->repo_path);
4313 free(gw_trans->repo_name);
4314 free(gw_trans->repo_file);
4315 free(gw_trans->action_name);
4316 free(gw_trans->headref);
4318 TAILQ_FOREACH_SAFE(dir, &gw_trans->gw_dirs, entry, tdir) {
4319 free(dir->name);
4320 free(dir->description);
4321 free(dir->age);
4322 free(dir->url);
4323 free(dir->path);
4324 free(dir);
4329 khttp_free(gw_trans->gw_req);
4330 return 0;