2 * Copyright (c) 2016, 2019, 2020-2022 Tracey Emery <tracey@traceyemery.net>
3 * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
4 * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
5 * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
6 * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include "got_compat.h"
23 #include <netinet/in.h>
24 #include <sys/queue.h>
26 #include <sys/types.h>
40 #include "got_error.h"
41 #include "got_object.h"
42 #include "got_reference.h"
43 #include "got_repository.h"
45 #include "got_cancel.h"
46 #include "got_worktree.h"
48 #include "got_commit_graph.h"
49 #include "got_blame.h"
50 #include "got_privsep.h"
56 static const struct querystring_keys querystring_keys
[] = {
61 { "headref", HEADREF
},
62 { "index_page", INDEX_PAGE
},
66 static const struct action_keys action_keys
[] = {
69 { "blobraw", BLOBRAW
},
71 { "commits", COMMITS
},
76 { "summary", SUMMARY
},
83 static const struct got_error
*gotweb_init_querystring(struct querystring
**);
84 static const struct got_error
*gotweb_parse_querystring(struct querystring
*,
86 static const struct got_error
*gotweb_assign_querystring(struct querystring
*,
88 static int gotweb_render_index(struct template *);
89 static const struct got_error
*gotweb_load_got_path(struct repo_dir
**,
90 const char *, struct request
*);
91 static const struct got_error
*gotweb_load_file(char **, const char *,
93 static const struct got_error
*gotweb_get_repo_description(char **,
94 struct server
*, const char *, int);
95 static const struct got_error
*gotweb_get_clone_url(char **, struct server
*,
98 static void gotweb_free_querystring(struct querystring
*);
99 static void gotweb_free_repo_dir(struct repo_dir
*);
101 struct server
*gotweb_get_server(const char *);
104 gotweb_reply(struct request
*c
, int status
, const char *ctype
,
105 struct gotweb_url
*location
)
109 if (status
!= 200 && tp_writef(c
->tp
, "Status: %d\r\n", status
) == -1)
113 if (tp_writes(c
->tp
, "Location: ") == -1 ||
114 gotweb_render_url(c
, location
) == -1 ||
115 tp_writes(c
->tp
, "\r\n") == -1)
119 csp
= "Content-Security-Policy: default-src 'self'; "
120 "script-src 'none'; object-src 'none';\r\n";
121 if (tp_writes(c
->tp
, csp
) == -1)
124 if (ctype
&& tp_writef(c
->tp
, "Content-Type: %s\r\n", ctype
) == -1)
127 return tp_writes(c
->tp
, "\r\n");
131 gotweb_reply_file(struct request
*c
, const char *ctype
, const char *file
,
136 r
= tp_writef(c
->tp
, "Content-Disposition: attachment; "
137 "filename=%s%s\r\n", file
, suffix
? suffix
: "");
140 return gotweb_reply(c
, 200, ctype
, NULL
);
144 gotweb_process_request(struct request
*c
)
146 const struct got_error
*error
= NULL
;
147 struct server
*srv
= NULL
;
148 struct querystring
*qs
= NULL
;
149 struct repo_dir
*repo_dir
= NULL
;
150 struct repo_commit
*commit
;
151 const char *rss_ctype
= "application/rss+xml;charset=utf-8";
156 /* init the transport */
157 error
= gotweb_init_transport(&c
->t
);
159 log_warnx("%s: %s", __func__
, error
->msg
);
162 /* get the gotwebd server */
163 srv
= gotweb_get_server(c
->server_name
);
165 log_warnx("%s: error server is NULL", __func__
);
169 /* parse our querystring */
170 error
= gotweb_init_querystring(&qs
);
172 log_warnx("%s: %s", __func__
, error
->msg
);
176 error
= gotweb_parse_querystring(qs
, c
->querystring
);
178 log_warnx("%s: %s", __func__
, error
->msg
);
182 /* Log the request. */
183 if (gotwebd_env
->gotwebd_verbose
> 0) {
184 char *server_name
= NULL
;
185 char *querystring
= NULL
;
186 char *document_uri
= NULL
;
188 if (c
->server_name
[0] &&
189 stravis(&server_name
, c
->server_name
, VIS_SAFE
) == -1) {
193 if (c
->querystring
[0] &&
194 stravis(&querystring
, c
->querystring
, VIS_SAFE
) == -1) {
198 if (c
->document_uri
[0] &&
199 stravis(&document_uri
, c
->document_uri
, VIS_SAFE
) == -1) {
204 log_info("processing request: server='%s' query='%s' "
206 server_name
? server_name
: "",
207 querystring
? querystring
: "",
208 document_uri
? document_uri
: "");
215 * certain actions require a commit id in the querystring. this stops
216 * bad actors from exploiting this by manually manipulating the
220 if (qs
->action
== BLAME
|| qs
->action
== BLOB
||
221 qs
->action
== BLOBRAW
|| qs
->action
== DIFF
||
222 qs
->action
== PATCH
) {
223 if (qs
->commit
== NULL
) {
224 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
229 if (qs
->action
!= INDEX
) {
230 if (qs
->path
== NULL
) {
231 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
235 error
= gotweb_load_got_path(&repo_dir
, qs
->path
, c
);
236 c
->t
->repo_dir
= repo_dir
;
241 if (qs
->action
== BLOBRAW
|| qs
->action
== BLOB
) {
242 if (qs
->folder
== NULL
|| qs
->file
== NULL
) {
243 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
247 error
= got_get_repo_commits(c
, 1);
251 error
= got_open_blob_for_output(&c
->t
->blob
, &c
->t
->fd
,
252 &binary
, c
, qs
->folder
, qs
->file
, qs
->commit
);
257 switch (qs
->action
) {
259 if (qs
->folder
== NULL
|| qs
->file
== NULL
) {
260 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
263 error
= got_get_repo_commits(c
, 1);
265 log_warnx("%s: %s", __func__
, error
->msg
);
268 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
270 gotweb_render_page(c
->tp
, gotweb_render_blame
);
274 struct gotweb_url url
= {
278 .commit
= qs
->commit
,
279 .folder
= qs
->folder
,
283 gotweb_reply(c
, 302, NULL
, &url
);
287 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
289 gotweb_render_page(c
->tp
, gotweb_render_blob
);
293 r
= gotweb_reply_file(c
, "application/octet-stream",
296 r
= gotweb_reply(c
, 200, "text/plain", NULL
);
299 if (template_flush(c
->tp
) == -1)
303 error
= got_object_blob_read_block(&len
, c
->t
->blob
);
308 buf
= got_object_blob_get_read_buf(c
->t
->blob
);
309 if (fcgi_write(c
, buf
, len
) == -1)
314 error
= got_get_repo_commits(c
, srv
->max_commits_display
);
317 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
319 gotweb_render_page(c
->tp
, gotweb_render_briefs
);
322 error
= got_get_repo_commits(c
, srv
->max_commits_display
);
324 log_warnx("%s: %s", __func__
, error
->msg
);
327 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
329 gotweb_render_page(c
->tp
, gotweb_render_commits
);
332 error
= got_get_repo_commits(c
, 1);
334 log_warnx("%s: %s", __func__
, error
->msg
);
337 error
= got_open_diff_for_output(&c
->t
->fp
, c
);
339 log_warnx("%s: %s", __func__
, error
->msg
);
342 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
344 gotweb_render_page(c
->tp
, gotweb_render_diff
);
347 c
->t
->nrepos
= scandir(srv
->repos_path
, &c
->t
->repos
, NULL
,
349 if (c
->t
->nrepos
== -1) {
351 error
= got_error_from_errno2("scandir",
355 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
357 gotweb_render_page(c
->tp
, gotweb_render_index
);
360 error
= got_get_repo_commits(c
, 1);
362 log_warnx("%s: %s", __func__
, error
->msg
);
365 error
= got_open_diff_for_output(&c
->t
->fp
, c
);
367 log_warnx("%s: %s", __func__
, error
->msg
);
370 if (gotweb_reply(c
, 200, "text/plain", NULL
) == -1)
372 gotweb_render_patch(c
->tp
);
375 error
= got_get_repo_tags(c
, D_MAXSLCOMMDISP
);
378 if (gotweb_reply_file(c
, rss_ctype
, repo_dir
->name
, ".rss")
381 gotweb_render_rss(c
->tp
);
384 error
= got_ref_list(&c
->t
->refs
, c
->t
->repo
, "refs/heads",
385 got_ref_cmp_by_name
, NULL
);
387 log_warnx("%s: got_ref_list: %s", __func__
,
391 error
= got_get_repo_commits(c
, srv
->summary_commits_display
);
395 error
= got_get_repo_tags(c
, srv
->summary_tags_display
);
397 log_warnx("%s: got_get_repo_tags: %s", __func__
,
401 qs
->action
= SUMMARY
;
402 commit
= TAILQ_FIRST(&c
->t
->repo_commits
);
403 if (commit
&& qs
->commit
== NULL
) {
404 qs
->commit
= strdup(commit
->commit_id
);
405 if (qs
->commit
== NULL
) {
406 error
= got_error_from_errno("strdup");
407 log_warn("%s: strdup", __func__
);
411 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
413 gotweb_render_page(c
->tp
, gotweb_render_summary
);
416 error
= got_get_repo_tags(c
, 1);
418 log_warnx("%s: %s", __func__
, error
->msg
);
421 if (TAILQ_EMPTY(&c
->t
->repo_tags
)) {
422 error
= got_error_msg(GOT_ERR_BAD_OBJ_ID
,
426 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
428 gotweb_render_page(c
->tp
, gotweb_render_tag
);
431 error
= got_get_repo_tags(c
, srv
->max_commits_display
);
433 log_warnx("%s: %s", __func__
, error
->msg
);
436 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
438 gotweb_render_page(c
->tp
, gotweb_render_tags
);
441 error
= got_get_repo_commits(c
, 1);
443 log_warnx("%s: %s", __func__
, error
->msg
);
446 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
448 gotweb_render_page(c
->tp
, gotweb_render_tree
);
452 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
457 if (gotweb_reply(c
, 400, "text/html", NULL
) == -1)
459 gotweb_render_page(c
->tp
, gotweb_render_error
);
463 gotweb_get_server(const char *server_name
)
467 /* check against the server name first */
468 if (*server_name
!= '\0')
469 TAILQ_FOREACH(srv
, &gotwebd_env
->servers
, entry
)
470 if (strcmp(srv
->name
, server_name
) == 0)
473 /* otherwise, use the first server */
474 return TAILQ_FIRST(&gotwebd_env
->servers
);
477 const struct got_error
*
478 gotweb_init_transport(struct transport
**t
)
480 const struct got_error
*error
= NULL
;
482 *t
= calloc(1, sizeof(**t
));
484 return got_error_from_errno2(__func__
, "calloc");
486 TAILQ_INIT(&(*t
)->repo_commits
);
487 TAILQ_INIT(&(*t
)->repo_tags
);
488 TAILQ_INIT(&(*t
)->refs
);
495 static const struct got_error
*
496 gotweb_init_querystring(struct querystring
**qs
)
498 const struct got_error
*error
= NULL
;
500 *qs
= calloc(1, sizeof(**qs
));
502 return got_error_from_errno2(__func__
, "calloc");
504 (*qs
)->headref
= strdup("HEAD");
505 if ((*qs
)->headref
== NULL
) {
508 return got_error_from_errno2(__func__
, "strdup");
511 (*qs
)->action
= INDEX
;
516 static const struct got_error
*
517 gotweb_parse_querystring(struct querystring
*qs
, char *qst
)
519 const struct got_error
*error
= NULL
;
520 char *tok1
= NULL
, *tok1_pair
= NULL
, *tok1_end
= NULL
;
521 char *tok2
= NULL
, *tok2_pair
= NULL
, *tok2_end
= NULL
;
528 return got_error_from_errno2(__func__
, "strdup");
533 while (tok1_pair
!= NULL
) {
534 strsep(&tok1_end
, "&");
536 tok2
= strdup(tok1_pair
);
539 return got_error_from_errno2(__func__
, "strdup");
545 while (tok2_pair
!= NULL
) {
546 strsep(&tok2_end
, "=");
548 error
= gotweb_assign_querystring(qs
, tok2_pair
,
553 tok2_pair
= tok2_end
;
556 tok1_pair
= tok1_end
;
567 * Adapted from usr.sbin/httpd/httpd.c url_decode.
569 static const struct got_error
*
570 gotweb_urldecode(char *url
)
582 /* Encoding character is followed by two hex chars */
583 if (!isxdigit((unsigned char)p
[1]) ||
584 !isxdigit((unsigned char)p
[2]) ||
585 (p
[1] == '0' && p
[2] == '0'))
586 return got_error(GOT_ERR_BAD_QUERYSTRING
);
592 * We don't have to validate "hex" because it is
593 * guaranteed to include two hex chars followed by nul.
595 x
= strtoul(hex
, NULL
, 16);
611 static const struct got_error
*
612 gotweb_assign_querystring(struct querystring
*qs
, char *key
, char *value
)
614 const struct got_error
*error
= NULL
;
618 error
= gotweb_urldecode(value
);
622 for (el_cnt
= 0; el_cnt
< nitems(querystring_keys
); el_cnt
++) {
623 if (strcmp(key
, querystring_keys
[el_cnt
].name
) != 0)
626 switch (querystring_keys
[el_cnt
].element
) {
628 for (a_cnt
= 0; a_cnt
< nitems(action_keys
); a_cnt
++) {
629 if (strcmp(value
, action_keys
[a_cnt
].name
) != 0)
631 qs
->action
= action_keys
[a_cnt
].action
;
638 qs
->commit
= strdup(value
);
639 if (qs
->commit
== NULL
) {
640 error
= got_error_from_errno2(__func__
,
646 qs
->file
= strdup(value
);
647 if (qs
->file
== NULL
) {
648 error
= got_error_from_errno2(__func__
,
654 qs
->folder
= strdup(value
);
655 if (qs
->folder
== NULL
) {
656 error
= got_error_from_errno2(__func__
,
663 qs
->headref
= strdup(value
);
664 if (qs
->headref
== NULL
) {
665 error
= got_error_from_errno2(__func__
,
673 qs
->index_page
= strtonum(value
, INT64_MIN
,
676 error
= got_error_from_errno3(__func__
,
680 if (qs
->index_page
< 0)
684 qs
->path
= strdup(value
);
685 if (qs
->path
== NULL
) {
686 error
= got_error_from_errno2(__func__
,
701 gotweb_free_repo_tag(struct repo_tag
*rt
)
706 free(rt
->tag_commit
);
707 free(rt
->commit_msg
);
714 gotweb_free_repo_commit(struct repo_commit
*rc
)
724 free(rc
->commit_msg
);
730 gotweb_free_querystring(struct querystring
*qs
)
743 gotweb_free_repo_dir(struct repo_dir
*repo_dir
)
745 if (repo_dir
!= NULL
) {
746 free(repo_dir
->name
);
747 free(repo_dir
->owner
);
748 free(repo_dir
->description
);
750 free(repo_dir
->path
);
756 gotweb_free_transport(struct transport
*t
)
758 const struct got_error
*err
;
759 struct repo_commit
*rc
= NULL
, *trc
= NULL
;
760 struct repo_tag
*rt
= NULL
, *trt
= NULL
;
763 got_ref_list_free(&t
->refs
);
764 TAILQ_FOREACH_SAFE(rc
, &t
->repo_commits
, entry
, trc
) {
765 TAILQ_REMOVE(&t
->repo_commits
, rc
, entry
);
766 gotweb_free_repo_commit(rc
);
768 TAILQ_FOREACH_SAFE(rt
, &t
->repo_tags
, entry
, trt
) {
769 TAILQ_REMOVE(&t
->repo_tags
, rt
, entry
);
770 gotweb_free_repo_tag(rt
);
772 gotweb_free_repo_dir(t
->repo_dir
);
773 gotweb_free_querystring(t
->qs
);
775 free(t
->tags_more_id
);
777 got_object_blob_close(t
->blob
);
779 err
= got_gotweb_closefile(t
->fp
);
781 log_warnx("%s: got_gotweb_closefile failure: %s",
784 if (t
->fd
!= -1 && close(t
->fd
) == -1)
785 log_warn("%s: close", __func__
);
787 for (i
= 0; i
< t
->nrepos
; ++i
)
792 got_repo_close(t
->repo
);
797 gotweb_index_navs(struct request
*c
, struct gotweb_url
*prev
, int *have_prev
,
798 struct gotweb_url
*next
, int *have_next
)
800 struct transport
*t
= c
->t
;
801 struct querystring
*qs
= t
->qs
;
802 struct server
*srv
= c
->srv
;
804 *have_prev
= *have_next
= 0;
806 if (qs
->index_page
> 0) {
808 *prev
= (struct gotweb_url
){
810 .index_page
= qs
->index_page
- 1,
813 if (t
->next_disp
== srv
->max_repos_display
&&
814 t
->repos_total
!= (qs
->index_page
+ 1) *
815 srv
->max_repos_display
) {
817 *next
= (struct gotweb_url
){
819 .index_page
= qs
->index_page
+ 1,
825 gotweb_render_index(struct template *tp
)
827 const struct got_error
*error
= NULL
;
828 struct request
*c
= tp
->tp_arg
;
829 struct server
*srv
= c
->srv
;
830 struct transport
*t
= c
->t
;
831 struct querystring
*qs
= t
->qs
;
832 struct repo_dir
*repo_dir
= NULL
;
833 struct dirent
**sd_dent
= t
->repos
;
834 unsigned int d_i
, d_disp
= 0;
835 unsigned int d_skipped
= 0;
838 if (gotweb_render_repo_table_hdr(c
->tp
) == -1)
841 for (d_i
= 0; d_i
< t
->nrepos
; d_i
++) {
842 if (strcmp(sd_dent
[d_i
]->d_name
, ".") == 0 ||
843 strcmp(sd_dent
[d_i
]->d_name
, "..") == 0) {
848 error
= got_path_dirent_type(&type
, srv
->repos_path
,
852 if (type
!= DT_DIR
) {
857 if (qs
->index_page
> 0 && (qs
->index_page
*
858 srv
->max_repos_display
) > t
->prev_disp
) {
863 error
= gotweb_load_got_path(&repo_dir
, sd_dent
[d_i
]->d_name
,
866 if (error
->code
!= GOT_ERR_NOT_GIT_REPO
)
867 log_warnx("%s: %s: %s", __func__
,
868 sd_dent
[d_i
]->d_name
, error
->msg
);
869 gotweb_free_repo_dir(repo_dir
);
878 r
= gotweb_render_repo_fragment(c
->tp
, repo_dir
);
879 gotweb_free_repo_dir(repo_dir
);
881 got_repo_close(t
->repo
);
887 if (d_disp
== srv
->max_repos_display
)
890 t
->repos_total
= t
->nrepos
- d_skipped
;
892 if (srv
->max_repos_display
== 0 ||
893 t
->repos_total
<= srv
->max_repos_display
)
896 if (gotweb_render_navs(c
->tp
) == -1)
903 should_urlencode(int c
)
905 if (c
<= ' ' || c
>= 127)
929 /* needed because the URLs are embedded into the HTML */
938 gotweb_urlencode(const char *str
)
946 for (s
= str
; *s
; ++s
) {
948 if (should_urlencode(*s
))
952 escaped
= calloc(1, len
+ 1);
957 for (s
= str
; *s
; ++s
) {
958 if (should_urlencode(*s
)) {
959 a
= (*s
& 0xF0) >> 4;
963 escaped
[i
++] = a
<= 9 ? ('0' + a
) : ('7' + a
);
964 escaped
[i
++] = b
<= 9 ? ('0' + b
) : ('7' + b
);
973 gotweb_action_name(int action
)
1010 gotweb_render_url(struct request
*c
, struct gotweb_url
*url
)
1012 const char *sep
= "?", *action
;
1016 action
= gotweb_action_name(url
->action
);
1017 if (action
!= NULL
) {
1018 if (tp_writef(c
->tp
, "?action=%s", action
) == -1)
1024 if (tp_writef(c
->tp
, "%scommit=%s", sep
, url
->commit
) == -1)
1030 tmp
= gotweb_urlencode(url
->file
);
1033 r
= tp_writef(c
->tp
, "%sfile=%s", sep
, tmp
);
1041 tmp
= gotweb_urlencode(url
->folder
);
1044 r
= tp_writef(c
->tp
, "%sfolder=%s", sep
, tmp
);
1052 tmp
= gotweb_urlencode(url
->headref
);
1055 r
= tp_writef(c
->tp
, "%sheadref=%s", sep
, url
->headref
);
1062 if (url
->index_page
!= -1) {
1063 if (tp_writef(c
->tp
, "%sindex_page=%d", sep
,
1064 url
->index_page
) == -1)
1070 tmp
= gotweb_urlencode(url
->path
);
1073 r
= tp_writef(c
->tp
, "%spath=%s", sep
, tmp
);
1084 gotweb_render_absolute_url(struct request
*c
, struct gotweb_url
*url
)
1086 struct template *tp
= c
->tp
;
1087 const char *proto
= c
->https
? "https" : "http";
1089 if (tp_writes(tp
, proto
) == -1 ||
1090 tp_writes(tp
, "://") == -1 ||
1091 tp_htmlescape(tp
, c
->server_name
) == -1 ||
1092 tp_htmlescape(tp
, c
->document_uri
) == -1)
1095 return gotweb_render_url(c
, url
);
1098 static const struct got_error
*
1099 gotweb_load_got_path(struct repo_dir
**rp
, const char *dir
,
1102 const struct got_error
*error
= NULL
;
1103 struct server
*srv
= c
->srv
;
1104 struct transport
*t
= c
->t
;
1105 struct repo_dir
*repo_dir
;
1109 *rp
= calloc(1, sizeof(**rp
));
1111 return got_error_from_errno("calloc");
1114 if (asprintf(&dir_test
, "%s/%s/%s", srv
->repos_path
, dir
,
1115 GOTWEB_GIT_DIR
) == -1)
1116 return got_error_from_errno("asprintf");
1118 dt
= opendir(dir_test
);
1121 if (asprintf(&dir_test
, "%s/%s", srv
->repos_path
, dir
) == -1)
1122 return got_error_from_errno("asprintf");
1123 dt
= opendir(dir_test
);
1126 if (asprintf(&dir_test
, "%s/%s%s", srv
->repos_path
,
1127 dir
, GOTWEB_GIT_DIR
) == -1)
1128 return got_error_from_errno("asprintf");
1129 dt
= opendir(dir_test
);
1132 return got_error_path(dir
,
1133 GOT_ERR_NOT_GIT_REPO
);
1138 repo_dir
->path
= dir_test
;
1141 repo_dir
->name
= strdup(repo_dir
->path
+ strlen(srv
->repos_path
) + 1);
1142 if (repo_dir
->name
== NULL
) {
1143 error
= got_error_from_errno("strdup");
1147 if (srv
->respect_exportok
&&
1148 faccessat(dirfd(dt
), "git-daemon-export-ok", F_OK
, 0) == -1) {
1149 error
= got_error_path(repo_dir
->name
, GOT_ERR_NOT_GIT_REPO
);
1153 error
= got_repo_open(&t
->repo
, repo_dir
->path
, NULL
,
1154 gotwebd_env
->pack_fds
);
1157 error
= gotweb_get_repo_description(&repo_dir
->description
, srv
,
1158 repo_dir
->path
, dirfd(dt
));
1161 if (srv
->show_repo_owner
) {
1162 error
= gotweb_load_file(&repo_dir
->owner
, repo_dir
->path
,
1163 "owner", dirfd(dt
));
1166 if (repo_dir
->owner
== NULL
) {
1167 error
= got_get_repo_owner(&repo_dir
->owner
, c
);
1172 if (srv
->show_repo_age
) {
1173 error
= got_get_repo_age(&repo_dir
->age
, c
, NULL
);
1177 error
= gotweb_get_clone_url(&repo_dir
->url
, srv
, repo_dir
->path
,
1181 if (dt
!= NULL
&& closedir(dt
) == EOF
&& error
== NULL
)
1182 error
= got_error_from_errno("closedir");
1183 if (error
&& t
->repo
) {
1184 got_repo_close(t
->repo
);
1190 static const struct got_error
*
1191 gotweb_load_file(char **str
, const char *dir
, const char *file
, int dirfd
)
1193 const struct got_error
*error
= NULL
;
1200 fd
= openat(dirfd
, file
, O_RDONLY
);
1202 if (errno
== ENOENT
|| errno
== EACCES
)
1204 return got_error_from_errno_fmt("openat %s/%s", dir
, file
);
1207 if (fstat(fd
, &sb
) == -1) {
1208 error
= got_error_from_errno_fmt("fstat %s/%s", dir
, file
);
1213 if (len
> GOTWEBD_MAXDESCRSZ
- 1)
1214 len
= GOTWEBD_MAXDESCRSZ
- 1;
1216 *str
= calloc(len
+ 1, 1);
1218 error
= got_error_from_errno("calloc");
1222 if (read(fd
, *str
, len
) == -1)
1223 error
= got_error_from_errno("read");
1225 if (fd
!= -1 && close(fd
) == -1 && error
== NULL
)
1226 error
= got_error_from_errno("close");
1230 static const struct got_error
*
1231 gotweb_get_repo_description(char **description
, struct server
*srv
,
1232 const char *dirpath
, int dir
)
1234 *description
= NULL
;
1235 if (srv
->show_repo_description
== 0)
1238 return gotweb_load_file(description
, dirpath
, "description", dir
);
1241 static const struct got_error
*
1242 gotweb_get_clone_url(char **url
, struct server
*srv
, const char *dirpath
,
1246 if (srv
->show_repo_cloneurl
== 0)
1249 return gotweb_load_file(url
, dirpath
, "cloneurl", dir
);
1253 gotweb_render_age(struct template *tp
, time_t committer_time
)
1255 struct request
*c
= tp
->tp_arg
;
1256 long long diff_time
;
1257 const char *years
= "years ago", *months
= "months ago";
1258 const char *weeks
= "weeks ago", *days
= "days ago";
1259 const char *hours
= "hours ago", *minutes
= "minutes ago";
1260 const char *seconds
= "seconds ago", *now
= "right now";
1262 diff_time
= time(NULL
) - committer_time
;
1263 if (diff_time
> 60 * 60 * 24 * 365 * 2) {
1264 if (tp_writef(c
->tp
, "%lld %s",
1265 (diff_time
/ 60 / 60 / 24 / 365), years
) == -1)
1267 } else if (diff_time
> 60 * 60 * 24 * (365 / 12) * 2) {
1268 if (tp_writef(c
->tp
, "%lld %s",
1269 (diff_time
/ 60 / 60 / 24 / (365 / 12)),
1272 } else if (diff_time
> 60 * 60 * 24 * 7 * 2) {
1273 if (tp_writef(c
->tp
, "%lld %s",
1274 (diff_time
/ 60 / 60 / 24 / 7), weeks
) == -1)
1276 } else if (diff_time
> 60 * 60 * 24 * 2) {
1277 if (tp_writef(c
->tp
, "%lld %s",
1278 (diff_time
/ 60 / 60 / 24), days
) == -1)
1280 } else if (diff_time
> 60 * 60 * 2) {
1281 if (tp_writef(c
->tp
, "%lld %s",
1282 (diff_time
/ 60 / 60), hours
) == -1)
1284 } else if (diff_time
> 60 * 2) {
1285 if (tp_writef(c
->tp
, "%lld %s", (diff_time
/ 60),
1288 } else if (diff_time
> 2) {
1289 if (tp_writef(c
->tp
, "%lld %s", diff_time
,
1293 if (tp_writes(tp
, now
) == -1)