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>
39 #include "got_error.h"
40 #include "got_object.h"
41 #include "got_reference.h"
42 #include "got_repository.h"
44 #include "got_cancel.h"
45 #include "got_worktree.h"
47 #include "got_commit_graph.h"
48 #include "got_blame.h"
49 #include "got_privsep.h"
54 static const struct querystring_keys querystring_keys
[] = {
59 { "headref", HEADREF
},
60 { "index_page", INDEX_PAGE
},
65 static const struct action_keys action_keys
[] = {
68 { "blobraw", BLOBRAW
},
70 { "commits", COMMITS
},
75 { "summary", SUMMARY
},
82 static const struct got_error
*gotweb_init_querystring(struct querystring
**);
83 static const struct got_error
*gotweb_parse_querystring(struct querystring
**,
85 static const struct got_error
*gotweb_assign_querystring(struct querystring
**,
87 static int gotweb_render_index(struct template *);
88 static const struct got_error
*gotweb_init_repo_dir(struct repo_dir
**,
90 static const struct got_error
*gotweb_load_got_path(struct request
*c
,
92 static const struct got_error
*gotweb_get_repo_description(char **,
93 struct server
*, const char *, int);
94 static const struct got_error
*gotweb_get_clone_url(char **, struct server
*,
97 static void gotweb_free_querystring(struct querystring
*);
98 static void gotweb_free_repo_dir(struct repo_dir
*);
100 struct server
*gotweb_get_server(const char *);
103 gotweb_reply(struct request
*c
, int status
, const char *ctype
,
104 struct gotweb_url
*location
)
108 if (status
!= 200 && tp_writef(c
->tp
, "Status: %d\r\n", status
) == -1)
112 if (tp_writes(c
->tp
, "Location: ") == -1 ||
113 gotweb_render_url(c
, location
) == -1 ||
114 tp_writes(c
->tp
, "\r\n") == -1)
118 csp
= "Content-Security-Policy: default-src 'self'; "
119 "script-src 'none'; object-src 'none';\r\n";
120 if (tp_writes(c
->tp
, csp
) == -1)
123 if (ctype
&& tp_writef(c
->tp
, "Content-Type: %s\r\n", ctype
) == -1)
126 return tp_writes(c
->tp
, "\r\n");
130 gotweb_reply_file(struct request
*c
, const char *ctype
, const char *file
,
135 r
= tp_writef(c
->tp
, "Content-Disposition: attachment; "
136 "filename=%s%s\r\n", file
, suffix
? suffix
: "");
139 return gotweb_reply(c
, 200, ctype
, NULL
);
143 gotweb_process_request(struct request
*c
)
145 const struct got_error
*error
= NULL
;
146 struct server
*srv
= NULL
;
147 struct querystring
*qs
= NULL
;
148 struct repo_dir
*repo_dir
= NULL
;
149 const char *rss_ctype
= "application/rss+xml;charset=utf-8";
154 /* init the transport */
155 error
= gotweb_init_transport(&c
->t
);
157 log_warnx("%s: %s", __func__
, error
->msg
);
160 /* don't process any further if client disconnected */
161 if (c
->sock
->client_status
== CLIENT_DISCONNECT
)
163 /* get the gotwebd server */
164 srv
= gotweb_get_server(c
->server_name
);
166 log_warnx("%s: error server is NULL", __func__
);
170 /* parse our querystring */
171 error
= gotweb_init_querystring(&qs
);
173 log_warnx("%s: %s", __func__
, error
->msg
);
177 error
= gotweb_parse_querystring(&qs
, c
->querystring
);
179 log_warnx("%s: %s", __func__
, error
->msg
);
184 * certain actions require a commit id in the querystring. this stops
185 * bad actors from exploiting this by manually manipulating the
189 if (qs
->action
== BLAME
|| qs
->action
== BLOB
||
190 qs
->action
== BLOBRAW
|| qs
->action
== DIFF
||
191 qs
->action
== PATCH
) {
192 if (qs
->commit
== NULL
) {
193 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
198 if (qs
->action
!= INDEX
) {
199 error
= gotweb_init_repo_dir(&repo_dir
, qs
->path
);
202 error
= gotweb_load_got_path(c
, repo_dir
);
203 c
->t
->repo_dir
= repo_dir
;
204 if (error
&& error
->code
!= GOT_ERR_LONELY_PACKIDX
)
208 if (qs
->action
== BLOBRAW
|| qs
->action
== BLOB
) {
209 error
= got_get_repo_commits(c
, 1);
213 error
= got_open_blob_for_output(&c
->t
->blob
, &c
->t
->fd
,
219 switch (qs
->action
) {
221 error
= got_get_repo_commits(c
, 1);
223 log_warnx("%s: %s", __func__
, error
->msg
);
226 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
228 gotweb_render_page(c
->tp
, gotweb_render_blame
);
232 struct gotweb_url url
= {
237 .commit
= qs
->commit
,
238 .folder
= qs
->folder
,
242 gotweb_reply(c
, 302, NULL
, &url
);
246 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
248 gotweb_render_page(c
->tp
, gotweb_render_blob
);
252 r
= gotweb_reply_file(c
, "application/octet-stream",
255 r
= gotweb_reply(c
, 200, "text/plain", NULL
);
258 if (template_flush(c
->tp
) == -1)
262 error
= got_object_blob_read_block(&len
, c
->t
->blob
);
267 buf
= got_object_blob_get_read_buf(c
->t
->blob
);
268 if (fcgi_write(c
, buf
, len
) == -1)
273 error
= got_get_repo_commits(c
, srv
->max_commits_display
);
276 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
278 gotweb_render_page(c
->tp
, gotweb_render_briefs
);
281 error
= got_get_repo_commits(c
, srv
->max_commits_display
);
283 log_warnx("%s: %s", __func__
, error
->msg
);
286 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
288 gotweb_render_page(c
->tp
, gotweb_render_commits
);
291 error
= got_get_repo_commits(c
, 1);
293 log_warnx("%s: %s", __func__
, error
->msg
);
296 error
= got_open_diff_for_output(&c
->t
->fp
, c
);
298 log_warnx("%s: %s", __func__
, error
->msg
);
301 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
303 gotweb_render_page(c
->tp
, gotweb_render_diff
);
306 c
->t
->nrepos
= scandir(srv
->repos_path
, &c
->t
->repos
, NULL
,
308 if (c
->t
->nrepos
== -1) {
310 error
= got_error_from_errno2("scandir",
314 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
316 gotweb_render_page(c
->tp
, gotweb_render_index
);
319 error
= got_get_repo_commits(c
, 1);
321 log_warnx("%s: %s", __func__
, error
->msg
);
324 error
= got_open_diff_for_output(&c
->t
->fp
, c
);
326 log_warnx("%s: %s", __func__
, error
->msg
);
329 if (gotweb_reply(c
, 200, "text/plain", NULL
) == -1)
331 gotweb_render_patch(c
->tp
);
334 error
= got_get_repo_tags(c
, D_MAXSLCOMMDISP
);
337 if (gotweb_reply_file(c
, rss_ctype
, repo_dir
->name
, ".rss")
340 gotweb_render_rss(c
->tp
);
343 error
= got_ref_list(&c
->t
->refs
, c
->t
->repo
, "refs/heads",
344 got_ref_cmp_by_name
, NULL
);
346 log_warnx("%s: got_ref_list: %s", __func__
,
350 error
= got_get_repo_commits(c
, D_MAXSLCOMMDISP
);
354 error
= got_get_repo_tags(c
, D_MAXSLCOMMDISP
);
356 log_warnx("%s: got_get_repo_tags: %s", __func__
,
360 qs
->action
= SUMMARY
;
361 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
363 gotweb_render_page(c
->tp
, gotweb_render_summary
);
366 error
= got_get_repo_tags(c
, 1);
368 log_warnx("%s: %s", __func__
, error
->msg
);
371 if (c
->t
->tag_count
== 0) {
372 error
= got_error_msg(GOT_ERR_BAD_OBJ_ID
,
376 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
378 gotweb_render_page(c
->tp
, gotweb_render_tag
);
381 error
= got_get_repo_tags(c
, srv
->max_commits_display
);
383 log_warnx("%s: %s", __func__
, error
->msg
);
386 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
388 gotweb_render_page(c
->tp
, gotweb_render_tags
);
391 error
= got_get_repo_commits(c
, 1);
393 log_warnx("%s: %s", __func__
, error
->msg
);
396 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
398 gotweb_render_page(c
->tp
, gotweb_render_tree
);
402 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
407 if (gotweb_reply(c
, 400, "text/html", NULL
) == -1)
409 gotweb_render_page(c
->tp
, gotweb_render_error
);
413 gotweb_get_server(const char *server_name
)
417 /* check against the server name first */
418 if (*server_name
!= '\0')
419 TAILQ_FOREACH(srv
, &gotwebd_env
->servers
, entry
)
420 if (strcmp(srv
->name
, server_name
) == 0)
423 /* otherwise, use the first server */
424 return TAILQ_FIRST(&gotwebd_env
->servers
);
427 const struct got_error
*
428 gotweb_init_transport(struct transport
**t
)
430 const struct got_error
*error
= NULL
;
432 *t
= calloc(1, sizeof(**t
));
434 return got_error_from_errno2(__func__
, "calloc");
436 TAILQ_INIT(&(*t
)->repo_commits
);
437 TAILQ_INIT(&(*t
)->repo_tags
);
438 TAILQ_INIT(&(*t
)->refs
);
445 static const struct got_error
*
446 gotweb_init_querystring(struct querystring
**qs
)
448 const struct got_error
*error
= NULL
;
450 *qs
= calloc(1, sizeof(**qs
));
452 return got_error_from_errno2(__func__
, "calloc");
454 (*qs
)->headref
= strdup("HEAD");
455 if ((*qs
)->headref
== NULL
) {
458 return got_error_from_errno2(__func__
, "strdup");
461 (*qs
)->action
= INDEX
;
462 (*qs
)->commit
= NULL
;
464 (*qs
)->folder
= NULL
;
465 (*qs
)->index_page
= 0;
471 static const struct got_error
*
472 gotweb_parse_querystring(struct querystring
**qs
, char *qst
)
474 const struct got_error
*error
= NULL
;
475 char *tok1
= NULL
, *tok1_pair
= NULL
, *tok1_end
= NULL
;
476 char *tok2
= NULL
, *tok2_pair
= NULL
, *tok2_end
= NULL
;
483 return got_error_from_errno2(__func__
, "strdup");
488 while (tok1_pair
!= NULL
) {
489 strsep(&tok1_end
, "&");
491 tok2
= strdup(tok1_pair
);
494 return got_error_from_errno2(__func__
, "strdup");
500 while (tok2_pair
!= NULL
) {
501 strsep(&tok2_end
, "=");
503 error
= gotweb_assign_querystring(qs
, tok2_pair
,
508 tok2_pair
= tok2_end
;
511 tok1_pair
= tok1_end
;
522 * Adapted from usr.sbin/httpd/httpd.c url_decode.
524 static const struct got_error
*
525 gotweb_urldecode(char *url
)
537 /* Encoding character is followed by two hex chars */
538 if (!isxdigit((unsigned char)p
[1]) ||
539 !isxdigit((unsigned char)p
[2]) ||
540 (p
[1] == '0' && p
[2] == '0'))
541 return got_error(GOT_ERR_BAD_QUERYSTRING
);
547 * We don't have to validate "hex" because it is
548 * guaranteed to include two hex chars followed by nul.
550 x
= strtoul(hex
, NULL
, 16);
566 static const struct got_error
*
567 gotweb_assign_querystring(struct querystring
**qs
, char *key
, char *value
)
569 const struct got_error
*error
= NULL
;
573 error
= gotweb_urldecode(value
);
577 for (el_cnt
= 0; el_cnt
< nitems(querystring_keys
); el_cnt
++) {
578 if (strcmp(key
, querystring_keys
[el_cnt
].name
) != 0)
581 switch (querystring_keys
[el_cnt
].element
) {
583 for (a_cnt
= 0; a_cnt
< ACTIONS__MAX
; a_cnt
++) {
584 if (strcmp(value
, action_keys
[a_cnt
].name
) != 0)
586 else if (strcmp(value
,
587 action_keys
[a_cnt
].name
) == 0){
589 action_keys
[a_cnt
].action
;
597 (*qs
)->commit
= strdup(value
);
598 if ((*qs
)->commit
== NULL
) {
599 error
= got_error_from_errno2(__func__
,
605 (*qs
)->file
= strdup(value
);
606 if ((*qs
)->file
== NULL
) {
607 error
= got_error_from_errno2(__func__
,
613 (*qs
)->folder
= strdup(value
);
614 if ((*qs
)->folder
== NULL
) {
615 error
= got_error_from_errno2(__func__
,
621 free((*qs
)->headref
);
622 (*qs
)->headref
= strdup(value
);
623 if ((*qs
)->headref
== NULL
) {
624 error
= got_error_from_errno2(__func__
,
632 (*qs
)->index_page
= strtonum(value
, INT64_MIN
,
635 error
= got_error_from_errno3(__func__
,
639 if ((*qs
)->index_page
< 0)
640 (*qs
)->index_page
= 0;
643 (*qs
)->path
= strdup(value
);
644 if ((*qs
)->path
== NULL
) {
645 error
= got_error_from_errno2(__func__
,
653 (*qs
)->page
= strtonum(value
, INT64_MIN
,
656 error
= got_error_from_errno3(__func__
,
673 gotweb_free_repo_tag(struct repo_tag
*rt
)
678 free(rt
->tag_commit
);
679 free(rt
->commit_msg
);
686 gotweb_free_repo_commit(struct repo_commit
*rc
)
696 free(rc
->commit_msg
);
702 gotweb_free_querystring(struct querystring
*qs
)
715 gotweb_free_repo_dir(struct repo_dir
*repo_dir
)
717 if (repo_dir
!= NULL
) {
718 free(repo_dir
->name
);
719 free(repo_dir
->owner
);
720 free(repo_dir
->description
);
722 free(repo_dir
->path
);
728 gotweb_free_transport(struct transport
*t
)
730 const struct got_error
*err
;
731 struct repo_commit
*rc
= NULL
, *trc
= NULL
;
732 struct repo_tag
*rt
= NULL
, *trt
= NULL
;
735 got_ref_list_free(&t
->refs
);
736 TAILQ_FOREACH_SAFE(rc
, &t
->repo_commits
, entry
, trc
) {
737 TAILQ_REMOVE(&t
->repo_commits
, rc
, entry
);
738 gotweb_free_repo_commit(rc
);
740 TAILQ_FOREACH_SAFE(rt
, &t
->repo_tags
, entry
, trt
) {
741 TAILQ_REMOVE(&t
->repo_tags
, rt
, entry
);
742 gotweb_free_repo_tag(rt
);
744 gotweb_free_repo_dir(t
->repo_dir
);
745 gotweb_free_querystring(t
->qs
);
750 got_object_blob_close(t
->blob
);
752 err
= got_gotweb_closefile(t
->fp
);
754 log_warnx("%s: got_gotweb_closefile failure: %s",
757 if (t
->fd
!= -1 && close(t
->fd
) == -1)
758 log_warn("%s: close", __func__
);
760 for (i
= 0; i
< t
->nrepos
; ++i
)
765 got_repo_close(t
->repo
);
770 gotweb_get_navs(struct request
*c
, struct gotweb_url
*prev
, int *have_prev
,
771 struct gotweb_url
*next
, int *have_next
)
773 struct transport
*t
= c
->t
;
774 struct querystring
*qs
= t
->qs
;
775 struct server
*srv
= c
->srv
;
777 *have_prev
= *have_next
= 0;
781 if (qs
->index_page
> 0) {
783 *prev
= (struct gotweb_url
){
785 .index_page
= qs
->index_page
- 1,
789 if (t
->next_disp
== srv
->max_repos_display
&&
790 t
->repos_total
!= (qs
->index_page
+ 1) *
791 srv
->max_repos_display
) {
793 *next
= (struct gotweb_url
){
795 .index_page
= qs
->index_page
+ 1,
801 if (t
->prev_id
&& qs
->commit
!= NULL
&&
802 strcmp(qs
->commit
, t
->prev_id
) != 0) {
804 *prev
= (struct gotweb_url
){
807 .page
= qs
->page
- 1,
809 .commit
= t
->prev_id
,
810 .headref
= qs
->headref
,
815 *next
= (struct gotweb_url
){
818 .page
= qs
->page
+ 1,
820 .commit
= t
->next_id
,
821 .headref
= qs
->headref
,
829 gotweb_render_index(struct template *tp
)
831 const struct got_error
*error
= NULL
;
832 struct request
*c
= tp
->tp_arg
;
833 struct server
*srv
= c
->srv
;
834 struct transport
*t
= c
->t
;
835 struct querystring
*qs
= t
->qs
;
836 struct repo_dir
*repo_dir
= NULL
;
837 struct dirent
**sd_dent
= t
->repos
;
838 unsigned int d_i
, d_disp
= 0;
839 unsigned int d_skipped
= 0;
842 if (gotweb_render_repo_table_hdr(c
->tp
) == -1)
845 for (d_i
= 0; d_i
< t
->nrepos
; d_i
++) {
846 if (srv
->max_repos
> 0 && t
->prev_disp
== srv
->max_repos
)
849 if (strcmp(sd_dent
[d_i
]->d_name
, ".") == 0 ||
850 strcmp(sd_dent
[d_i
]->d_name
, "..") == 0) {
855 error
= got_path_dirent_type(&type
, srv
->repos_path
,
859 if (type
!= DT_DIR
) {
864 if (qs
->index_page
> 0 && (qs
->index_page
*
865 srv
->max_repos_display
) > t
->prev_disp
) {
870 error
= gotweb_init_repo_dir(&repo_dir
, sd_dent
[d_i
]->d_name
);
874 error
= gotweb_load_got_path(c
, repo_dir
);
875 if (error
&& error
->code
!= GOT_ERR_LONELY_PACKIDX
) {
876 if (error
->code
!= GOT_ERR_NOT_GIT_REPO
)
877 log_warnx("%s: %s: %s", __func__
,
878 sd_dent
[d_i
]->d_name
, error
->msg
);
879 gotweb_free_repo_dir(repo_dir
);
888 r
= gotweb_render_repo_fragment(c
->tp
, repo_dir
);
889 gotweb_free_repo_dir(repo_dir
);
891 got_repo_close(t
->repo
);
897 if (d_disp
== srv
->max_repos_display
)
900 t
->repos_total
= t
->nrepos
- d_skipped
;
902 if (srv
->max_repos_display
== 0)
904 if (srv
->max_repos
> 0 && srv
->max_repos
< srv
->max_repos_display
)
906 if (t
->repos_total
<= srv
->max_repos
||
907 t
->repos_total
<= srv
->max_repos_display
)
910 if (gotweb_render_navs(c
->tp
) == -1)
917 should_urlencode(int c
)
919 if (c
<= ' ' || c
>= 127)
943 /* needed because the URLs are embedded into the HTML */
952 gotweb_urlencode(const char *str
)
960 for (s
= str
; *s
; ++s
) {
962 if (should_urlencode(*s
))
966 escaped
= calloc(1, len
+ 1);
971 for (s
= str
; *s
; ++s
) {
972 if (should_urlencode(*s
)) {
973 a
= (*s
& 0xF0) >> 4;
977 escaped
[i
++] = a
<= 9 ? ('0' + a
) : ('7' + a
);
978 escaped
[i
++] = b
<= 9 ? ('0' + b
) : ('7' + b
);
987 gotweb_action_name(int action
)
1024 gotweb_render_url(struct request
*c
, struct gotweb_url
*url
)
1026 const char *sep
= "?", *action
;
1030 action
= gotweb_action_name(url
->action
);
1031 if (action
!= NULL
) {
1032 if (tp_writef(c
->tp
, "?action=%s", action
) == -1)
1038 if (tp_writef(c
->tp
, "%scommit=%s", sep
, url
->commit
) == -1)
1044 if (tp_writef(c
->tp
, "%sprevid=%s", sep
, url
->previd
) == -1)
1050 if (tp_writef(c
->tp
, "%sprevset=%s", sep
, url
->prevset
) == -1)
1056 tmp
= gotweb_urlencode(url
->file
);
1059 r
= tp_writef(c
->tp
, "%sfile=%s", sep
, tmp
);
1067 tmp
= gotweb_urlencode(url
->folder
);
1070 r
= tp_writef(c
->tp
, "%sfolder=%s", sep
, tmp
);
1078 tmp
= gotweb_urlencode(url
->headref
);
1081 r
= tp_writef(c
->tp
, "%sheadref=%s", sep
, url
->headref
);
1088 if (url
->index_page
!= -1) {
1089 if (tp_writef(c
->tp
, "%sindex_page=%d", sep
,
1090 url
->index_page
) == -1)
1096 tmp
= gotweb_urlencode(url
->path
);
1099 r
= tp_writef(c
->tp
, "%spath=%s", sep
, tmp
);
1106 if (url
->page
!= -1) {
1107 if (tp_writef(c
->tp
, "%spage=%d", sep
, url
->page
) == -1)
1116 gotweb_render_absolute_url(struct request
*c
, struct gotweb_url
*url
)
1118 struct template *tp
= c
->tp
;
1119 const char *proto
= c
->https
? "https" : "http";
1121 if (tp_writes(tp
, proto
) == -1 ||
1122 tp_writes(tp
, "://") == -1 ||
1123 tp_htmlescape(tp
, c
->server_name
) == -1 ||
1124 tp_htmlescape(tp
, c
->document_uri
) == -1)
1127 return gotweb_render_url(c
, url
);
1130 static const struct got_error
*
1131 gotweb_load_got_path(struct request
*c
, struct repo_dir
*repo_dir
)
1133 const struct got_error
*error
= NULL
;
1134 struct socket
*sock
= c
->sock
;
1135 struct server
*srv
= c
->srv
;
1136 struct transport
*t
= c
->t
;
1140 if (asprintf(&dir_test
, "%s/%s/%s", srv
->repos_path
, repo_dir
->name
,
1141 GOTWEB_GIT_DIR
) == -1)
1142 return got_error_from_errno("asprintf");
1144 dt
= opendir(dir_test
);
1147 if (asprintf(&dir_test
, "%s/%s", srv
->repos_path
,
1148 repo_dir
->name
) == -1)
1149 return got_error_from_errno("asprintf");
1150 dt
= opendir(dir_test
);
1153 return got_error_path(repo_dir
->name
,
1154 GOT_ERR_NOT_GIT_REPO
);
1158 repo_dir
->path
= dir_test
;
1161 if (srv
->respect_exportok
&&
1162 faccessat(dirfd(dt
), "git-daemon-export-ok", F_OK
, 0) == -1) {
1163 error
= got_error_path(repo_dir
->name
, GOT_ERR_NOT_GIT_REPO
);
1167 error
= got_repo_open(&t
->repo
, repo_dir
->path
, NULL
, sock
->pack_fds
);
1170 error
= gotweb_get_repo_description(&repo_dir
->description
, srv
,
1171 repo_dir
->path
, dirfd(dt
));
1174 error
= got_get_repo_owner(&repo_dir
->owner
, c
);
1177 if (srv
->show_repo_age
) {
1178 error
= got_get_repo_age(&repo_dir
->age
, c
, NULL
);
1182 error
= gotweb_get_clone_url(&repo_dir
->url
, srv
, repo_dir
->path
,
1186 if (dt
!= NULL
&& closedir(dt
) == EOF
&& error
== NULL
)
1187 error
= got_error_from_errno("closedir");
1188 if (error
&& t
->repo
) {
1189 got_repo_close(t
->repo
);
1195 static const struct got_error
*
1196 gotweb_init_repo_dir(struct repo_dir
**repo_dir
, const char *dir
)
1198 const struct got_error
*error
;
1200 *repo_dir
= calloc(1, sizeof(**repo_dir
));
1201 if (*repo_dir
== NULL
)
1202 return got_error_from_errno("calloc");
1204 if (asprintf(&(*repo_dir
)->name
, "%s", dir
) == -1) {
1205 error
= got_error_from_errno("asprintf");
1210 (*repo_dir
)->owner
= NULL
;
1211 (*repo_dir
)->description
= NULL
;
1212 (*repo_dir
)->url
= NULL
;
1213 (*repo_dir
)->path
= NULL
;
1218 static const struct got_error
*
1219 gotweb_get_repo_description(char **description
, struct server
*srv
,
1220 const char *dirpath
, int dir
)
1222 const struct got_error
*error
= NULL
;
1227 *description
= NULL
;
1228 if (srv
->show_repo_description
== 0)
1231 fd
= openat(dir
, "description", O_RDONLY
);
1233 if (errno
!= ENOENT
&& errno
!= EACCES
) {
1234 error
= got_error_from_errno_fmt("openat %s/%s",
1235 dirpath
, "description");
1240 if (fstat(fd
, &sb
) == -1) {
1241 error
= got_error_from_errno_fmt("fstat %s/%s",
1242 dirpath
, "description");
1247 if (len
> GOTWEBD_MAXDESCRSZ
- 1)
1248 len
= GOTWEBD_MAXDESCRSZ
- 1;
1250 *description
= calloc(len
+ 1, sizeof(**description
));
1251 if (*description
== NULL
) {
1252 error
= got_error_from_errno("calloc");
1256 if (read(fd
, *description
, len
) == -1)
1257 error
= got_error_from_errno("read");
1259 if (fd
!= -1 && close(fd
) == -1 && error
== NULL
)
1260 error
= got_error_from_errno("close");
1264 static const struct got_error
*
1265 gotweb_get_clone_url(char **url
, struct server
*srv
, const char *dirpath
,
1268 const struct got_error
*error
= NULL
;
1274 if (srv
->show_repo_cloneurl
== 0)
1277 fd
= openat(dir
, "cloneurl", O_RDONLY
);
1279 if (errno
!= ENOENT
&& errno
!= EACCES
) {
1280 error
= got_error_from_errno_fmt("openat %s/%s",
1281 dirpath
, "cloneurl");
1286 if (fstat(fd
, &sb
) == -1) {
1287 error
= got_error_from_errno_fmt("fstat %s/%s",
1288 dirpath
, "cloneurl");
1293 if (len
> GOTWEBD_MAXCLONEURLSZ
- 1)
1294 len
= GOTWEBD_MAXCLONEURLSZ
- 1;
1296 *url
= calloc(len
+ 1, sizeof(**url
));
1298 error
= got_error_from_errno("calloc");
1302 if (read(fd
, *url
, len
) == -1)
1303 error
= got_error_from_errno("read");
1305 if (fd
!= -1 && close(fd
) == -1 && error
== NULL
)
1306 error
= got_error_from_errno("close");
1311 gotweb_render_age(struct template *tp
, time_t committer_time
)
1313 struct request
*c
= tp
->tp_arg
;
1314 long long diff_time
;
1315 const char *years
= "years ago", *months
= "months ago";
1316 const char *weeks
= "weeks ago", *days
= "days ago";
1317 const char *hours
= "hours ago", *minutes
= "minutes ago";
1318 const char *seconds
= "seconds ago", *now
= "right now";
1320 diff_time
= time(NULL
) - committer_time
;
1321 if (diff_time
> 60 * 60 * 24 * 365 * 2) {
1322 if (tp_writef(c
->tp
, "%lld %s",
1323 (diff_time
/ 60 / 60 / 24 / 365), years
) == -1)
1325 } else if (diff_time
> 60 * 60 * 24 * (365 / 12) * 2) {
1326 if (tp_writef(c
->tp
, "%lld %s",
1327 (diff_time
/ 60 / 60 / 24 / (365 / 12)),
1330 } else if (diff_time
> 60 * 60 * 24 * 7 * 2) {
1331 if (tp_writef(c
->tp
, "%lld %s",
1332 (diff_time
/ 60 / 60 / 24 / 7), weeks
) == -1)
1334 } else if (diff_time
> 60 * 60 * 24 * 2) {
1335 if (tp_writef(c
->tp
, "%lld %s",
1336 (diff_time
/ 60 / 60 / 24), days
) == -1)
1338 } else if (diff_time
> 60 * 60 * 2) {
1339 if (tp_writef(c
->tp
, "%lld %s",
1340 (diff_time
/ 60 / 60), hours
) == -1)
1342 } else if (diff_time
> 60 * 2) {
1343 if (tp_writef(c
->tp
, "%lld %s", (diff_time
/ 60),
1346 } else if (diff_time
> 2) {
1347 if (tp_writef(c
->tp
, "%lld %s", diff_time
,
1351 if (tp_writes(tp
, now
) == -1)