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"
55 static const struct querystring_keys querystring_keys
[] = {
60 { "headref", HEADREF
},
61 { "index_page", INDEX_PAGE
},
66 static const struct action_keys action_keys
[] = {
69 { "blobraw", BLOBRAW
},
71 { "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(uint8_t *, uint8_t *);
103 gotweb_reply(struct request
*c
, int status
, const char *ctype
,
104 struct gotweb_url
*location
)
108 if (status
!= 200 && fcgi_printf(c
, "Status: %d\r\n", status
) == -1)
112 if (fcgi_puts(c
->tp
, "Location: ") == -1 ||
113 gotweb_render_url(c
, location
) == -1 ||
114 fcgi_puts(c
->tp
, "\r\n") == -1)
118 csp
= "Content-Security-Policy: default-src 'self'; "
119 "script-src 'none'; object-src 'none';\r\n";
120 if (fcgi_puts(c
->tp
, csp
) == -1)
123 if (ctype
&& fcgi_printf(c
, "Content-Type: %s\r\n", ctype
) == -1)
126 return fcgi_puts(c
->tp
, "\r\n");
130 gotweb_reply_file(struct request
*c
, const char *ctype
, const char *file
,
135 r
= fcgi_printf(c
, "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
;
150 /* init the transport */
151 error
= gotweb_init_transport(&c
->t
);
153 log_warnx("%s: %s", __func__
, error
->msg
);
156 /* don't process any further if client disconnected */
157 if (c
->sock
->client_status
== CLIENT_DISCONNECT
)
159 /* get the gotwebd server */
160 srv
= gotweb_get_server(c
->server_name
, c
->http_host
);
162 log_warnx("%s: error server is NULL", __func__
);
166 /* parse our querystring */
167 error
= gotweb_init_querystring(&qs
);
169 log_warnx("%s: %s", __func__
, error
->msg
);
173 error
= gotweb_parse_querystring(&qs
, c
->querystring
);
175 log_warnx("%s: %s", __func__
, error
->msg
);
180 * certain actions require a commit id in the querystring. this stops
181 * bad actors from exploiting this by manually manipulating the
185 if (qs
->action
== BLAME
|| qs
->action
== BLOB
||
186 qs
->action
== BLOBRAW
|| qs
->action
== DIFF
) {
187 if (qs
->commit
== NULL
) {
188 error
= got_error(GOT_ERR_QUERYSTRING
);
193 if (qs
->action
!= INDEX
) {
194 error
= gotweb_init_repo_dir(&repo_dir
, qs
->path
);
197 error
= gotweb_load_got_path(c
, repo_dir
);
198 c
->t
->repo_dir
= repo_dir
;
199 if (error
&& error
->code
!= GOT_ERR_LONELY_PACKIDX
)
203 if (qs
->action
== BLOBRAW
) {
208 error
= got_get_repo_commits(c
, 1);
212 error
= got_open_blob_for_output(&c
->t
->blob
, &c
->t
->fd
,
218 r
= gotweb_reply_file(c
, "application/octet-stream",
221 r
= gotweb_reply(c
, 200, "text/plain", NULL
);
226 error
= got_object_blob_read_block(&len
, c
->t
->blob
);
231 buf
= got_object_blob_get_read_buf(c
->t
->blob
);
232 if (fcgi_gen_binary_response(c
, buf
, len
) == -1)
239 if (qs
->action
== BLOB
) {
241 struct gotweb_url url
= {
246 .commit
= qs
->commit
,
247 .folder
= qs
->folder
,
251 error
= got_get_repo_commits(c
, 1);
255 error
= got_open_blob_for_output(&c
->t
->blob
, &c
->t
->fd
,
260 gotweb_reply(c
, 302, NULL
, &url
);
265 if (qs
->action
== RSS
) {
266 const char *ctype
= "application/rss+xml;charset=utf-8";
268 if (gotweb_reply_file(c
, ctype
, repo_dir
->name
, ".rss") == -1)
271 error
= got_get_repo_tags(c
, D_MAXSLCOMMDISP
);
273 log_warnx("%s: %s", __func__
, error
->msg
);
276 gotweb_render_rss(c
->tp
);
282 error
= got_get_repo_commits(c
, 1);
284 log_warnx("%s: %s", __func__
, error
->msg
);
287 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
289 gotweb_render_page(c
->tp
, gotweb_render_blame
);
292 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
294 gotweb_render_page(c
->tp
, gotweb_render_blob
);
297 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
299 gotweb_render_page(c
->tp
, gotweb_render_briefs
);
302 error
= got_get_repo_commits(c
, srv
->max_commits_display
);
304 log_warnx("%s: %s", __func__
, error
->msg
);
307 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
309 gotweb_render_page(c
->tp
, gotweb_render_commits
);
312 error
= got_get_repo_commits(c
, 1);
314 log_warnx("%s: %s", __func__
, error
->msg
);
317 error
= got_open_diff_for_output(&c
->t
->fp
, &c
->t
->fd
, c
);
319 log_warnx("%s: %s", __func__
, error
->msg
);
322 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
324 gotweb_render_page(c
->tp
, gotweb_render_diff
);
327 c
->t
->nrepos
= scandir(srv
->repos_path
, &c
->t
->repos
, NULL
,
329 if (c
->t
->nrepos
== -1) {
331 error
= got_error_from_errno2("scandir",
335 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
337 gotweb_render_page(c
->tp
, gotweb_render_index
);
340 error
= got_ref_list(&c
->t
->refs
, c
->t
->repo
, "refs/heads",
341 got_ref_cmp_by_name
, NULL
);
343 log_warnx("%s: got_ref_list: %s", __func__
,
348 error
= got_get_repo_tags(c
, D_MAXSLCOMMDISP
);
350 log_warnx("%s: got_get_repo_tags: %s", __func__
,
354 qs
->action
= SUMMARY
;
355 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
357 gotweb_render_page(c
->tp
, gotweb_render_summary
);
360 error
= got_get_repo_tags(c
, 1);
362 log_warnx("%s: %s", __func__
, error
->msg
);
365 if (c
->t
->tag_count
== 0) {
366 error
= got_error_msg(GOT_ERR_BAD_OBJ_ID
,
370 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
372 gotweb_render_page(c
->tp
, gotweb_render_tag
);
375 error
= got_get_repo_tags(c
, srv
->max_commits_display
);
377 log_warnx("%s: %s", __func__
, error
->msg
);
380 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
382 gotweb_render_page(c
->tp
, gotweb_render_tags
);
385 error
= got_get_repo_commits(c
, 1);
387 log_warnx("%s: %s", __func__
, error
->msg
);
390 if (gotweb_reply(c
, 200, "text/html", NULL
) == -1)
392 gotweb_render_page(c
->tp
, gotweb_render_tree
);
396 error
= got_error(GOT_ERR_BAD_QUERYSTRING
);
401 if (gotweb_reply(c
, 400, "text/html", NULL
) == -1)
403 gotweb_render_page(c
->tp
, gotweb_render_error
);
407 gotweb_get_server(uint8_t *server_name
, uint8_t *subdomain
)
409 struct server
*srv
= NULL
;
411 /* check against the server name first */
412 if (strlen(server_name
) > 0)
413 TAILQ_FOREACH(srv
, &gotwebd_env
->servers
, entry
)
414 if (strcmp(srv
->name
, server_name
) == 0)
417 /* check against subdomain second */
418 if (strlen(subdomain
) > 0)
419 TAILQ_FOREACH(srv
, &gotwebd_env
->servers
, entry
)
420 if (strcmp(srv
->name
, subdomain
) == 0)
423 /* if those fail, send first server */
424 TAILQ_FOREACH(srv
, &gotwebd_env
->servers
, entry
)
431 const struct got_error
*
432 gotweb_init_transport(struct transport
**t
)
434 const struct got_error
*error
= NULL
;
436 *t
= calloc(1, sizeof(**t
));
438 return got_error_from_errno2("%s: calloc", __func__
);
440 TAILQ_INIT(&(*t
)->repo_commits
);
441 TAILQ_INIT(&(*t
)->repo_tags
);
442 TAILQ_INIT(&(*t
)->refs
);
445 (*t
)->repo_dir
= NULL
;
447 (*t
)->next_id
= NULL
;
448 (*t
)->prev_id
= NULL
;
457 static const struct got_error
*
458 gotweb_init_querystring(struct querystring
**qs
)
460 const struct got_error
*error
= NULL
;
462 *qs
= calloc(1, sizeof(**qs
));
464 return got_error_from_errno2("%s: calloc", __func__
);
466 (*qs
)->headref
= strdup("HEAD");
467 if ((*qs
)->headref
== NULL
) {
470 return got_error_from_errno2("%s: strdup", __func__
);
473 (*qs
)->action
= INDEX
;
474 (*qs
)->commit
= NULL
;
476 (*qs
)->folder
= NULL
;
477 (*qs
)->index_page
= 0;
483 static const struct got_error
*
484 gotweb_parse_querystring(struct querystring
**qs
, char *qst
)
486 const struct got_error
*error
= NULL
;
487 char *tok1
= NULL
, *tok1_pair
= NULL
, *tok1_end
= NULL
;
488 char *tok2
= NULL
, *tok2_pair
= NULL
, *tok2_end
= NULL
;
495 return got_error_from_errno2("%s: strdup", __func__
);
500 while (tok1_pair
!= NULL
) {
501 strsep(&tok1_end
, "&");
503 tok2
= strdup(tok1_pair
);
506 return got_error_from_errno2("%s: strdup", __func__
);
512 while (tok2_pair
!= NULL
) {
513 strsep(&tok2_end
, "=");
515 error
= gotweb_assign_querystring(qs
, tok2_pair
,
520 tok2_pair
= tok2_end
;
523 tok1_pair
= tok1_end
;
534 * Adapted from usr.sbin/httpd/httpd.c url_decode.
536 static const struct got_error
*
537 gotweb_urldecode(char *url
)
549 /* Encoding character is followed by two hex chars */
550 if (!isxdigit((unsigned char)p
[1]) ||
551 !isxdigit((unsigned char)p
[2]) ||
552 (p
[1] == '0' && p
[2] == '0'))
553 return got_error(GOT_ERR_BAD_QUERYSTRING
);
559 * We don't have to validate "hex" because it is
560 * guaranteed to include two hex chars followed by nul.
562 x
= strtoul(hex
, NULL
, 16);
578 static const struct got_error
*
579 gotweb_assign_querystring(struct querystring
**qs
, char *key
, char *value
)
581 const struct got_error
*error
= NULL
;
585 error
= gotweb_urldecode(value
);
589 for (el_cnt
= 0; el_cnt
< QSELEM__MAX
; el_cnt
++) {
590 if (strcmp(key
, querystring_keys
[el_cnt
].name
) != 0)
593 switch (querystring_keys
[el_cnt
].element
) {
595 for (a_cnt
= 0; a_cnt
< ACTIONS__MAX
; a_cnt
++) {
596 if (strcmp(value
, action_keys
[a_cnt
].name
) != 0)
598 else if (strcmp(value
,
599 action_keys
[a_cnt
].name
) == 0){
601 action_keys
[a_cnt
].action
;
609 (*qs
)->commit
= strdup(value
);
610 if ((*qs
)->commit
== NULL
) {
611 error
= got_error_from_errno2("%s: strdup",
617 (*qs
)->file
= strdup(value
);
618 if ((*qs
)->file
== NULL
) {
619 error
= got_error_from_errno2("%s: strdup",
625 (*qs
)->folder
= strdup(value
);
626 if ((*qs
)->folder
== NULL
) {
627 error
= got_error_from_errno2("%s: strdup",
633 free((*qs
)->headref
);
634 (*qs
)->headref
= strdup(value
);
635 if ((*qs
)->headref
== NULL
) {
636 error
= got_error_from_errno2("%s: strdup",
642 if (strlen(value
) == 0)
644 (*qs
)->index_page
= strtonum(value
, INT64_MIN
,
647 error
= got_error_from_errno3("%s: strtonum %s",
651 if ((*qs
)->index_page
< 0)
652 (*qs
)->index_page
= 0;
655 (*qs
)->path
= strdup(value
);
656 if ((*qs
)->path
== NULL
) {
657 error
= got_error_from_errno2("%s: strdup",
663 if (strlen(value
) == 0)
665 (*qs
)->page
= strtonum(value
, INT64_MIN
,
668 error
= got_error_from_errno3("%s: strtonum %s",
684 gotweb_free_repo_tag(struct repo_tag
*rt
)
689 free(rt
->tag_commit
);
690 free(rt
->commit_msg
);
697 gotweb_free_repo_commit(struct repo_commit
*rc
)
707 free(rc
->commit_msg
);
713 gotweb_free_querystring(struct querystring
*qs
)
726 gotweb_free_repo_dir(struct repo_dir
*repo_dir
)
728 if (repo_dir
!= NULL
) {
729 free(repo_dir
->name
);
730 free(repo_dir
->owner
);
731 free(repo_dir
->description
);
733 free(repo_dir
->path
);
739 gotweb_free_transport(struct transport
*t
)
741 const struct got_error
*err
;
742 struct repo_commit
*rc
= NULL
, *trc
= NULL
;
743 struct repo_tag
*rt
= NULL
, *trt
= NULL
;
746 got_ref_list_free(&t
->refs
);
747 TAILQ_FOREACH_SAFE(rc
, &t
->repo_commits
, entry
, trc
) {
748 TAILQ_REMOVE(&t
->repo_commits
, rc
, entry
);
749 gotweb_free_repo_commit(rc
);
751 TAILQ_FOREACH_SAFE(rt
, &t
->repo_tags
, entry
, trt
) {
752 TAILQ_REMOVE(&t
->repo_tags
, rt
, entry
);
753 gotweb_free_repo_tag(rt
);
755 gotweb_free_repo_dir(t
->repo_dir
);
756 gotweb_free_querystring(t
->qs
);
761 got_object_blob_close(t
->blob
);
763 err
= got_gotweb_flushfile(t
->fp
, t
->fd
);
765 log_warnx("%s: got_gotweb_flushfile failure: %s",
772 for (i
= 0; i
< t
->nrepos
; ++i
)
780 gotweb_get_navs(struct request
*c
, struct gotweb_url
*prev
, int *have_prev
,
781 struct gotweb_url
*next
, int *have_next
)
783 struct transport
*t
= c
->t
;
784 struct querystring
*qs
= t
->qs
;
785 struct server
*srv
= c
->srv
;
787 *have_prev
= *have_next
= 0;
791 if (qs
->index_page
> 0) {
793 *prev
= (struct gotweb_url
){
795 .index_page
= qs
->index_page
- 1,
799 if (t
->next_disp
== srv
->max_repos_display
&&
800 t
->repos_total
!= (qs
->index_page
+ 1) *
801 srv
->max_repos_display
) {
803 *next
= (struct gotweb_url
){
805 .index_page
= qs
->index_page
+ 1,
811 if (t
->prev_id
&& qs
->commit
!= NULL
&&
812 strcmp(qs
->commit
, t
->prev_id
) != 0) {
814 *prev
= (struct gotweb_url
){
817 .page
= qs
->page
- 1,
819 .commit
= t
->prev_id
,
820 .headref
= qs
->headref
,
825 *next
= (struct gotweb_url
){
828 .page
= qs
->page
+ 1,
830 .commit
= t
->next_id
,
831 .headref
= qs
->headref
,
839 gotweb_render_index(struct template *tp
)
841 const struct got_error
*error
= NULL
;
842 struct request
*c
= tp
->tp_arg
;
843 struct server
*srv
= c
->srv
;
844 struct transport
*t
= c
->t
;
845 struct querystring
*qs
= t
->qs
;
846 struct repo_dir
*repo_dir
= NULL
;
847 struct dirent
**sd_dent
= t
->repos
;
848 unsigned int d_i
, d_disp
= 0;
849 unsigned int d_skipped
= 0;
852 if (gotweb_render_repo_table_hdr(c
->tp
) == -1)
855 for (d_i
= 0; d_i
< t
->nrepos
; d_i
++) {
856 if (srv
->max_repos
> 0 && t
->prev_disp
== srv
->max_repos
)
859 if (strcmp(sd_dent
[d_i
]->d_name
, ".") == 0 ||
860 strcmp(sd_dent
[d_i
]->d_name
, "..") == 0) {
865 error
= got_path_dirent_type(&type
, srv
->repos_path
,
869 if (type
!= DT_DIR
) {
874 if (qs
->index_page
> 0 && (qs
->index_page
*
875 srv
->max_repos_display
) > t
->prev_disp
) {
880 error
= gotweb_init_repo_dir(&repo_dir
, sd_dent
[d_i
]->d_name
);
884 error
= gotweb_load_got_path(c
, repo_dir
);
885 if (error
&& error
->code
== GOT_ERR_LONELY_PACKIDX
) {
886 if (error
->code
!= GOT_ERR_NOT_GIT_REPO
)
887 log_warnx("%s: %s: %s", __func__
,
888 sd_dent
[d_i
]->d_name
, error
->msg
);
889 gotweb_free_repo_dir(repo_dir
);
898 r
= gotweb_render_repo_fragment(c
->tp
, repo_dir
);
899 gotweb_free_repo_dir(repo_dir
);
904 if (d_disp
== srv
->max_repos_display
)
907 t
->repos_total
= t
->nrepos
- d_skipped
;
909 if (srv
->max_repos_display
== 0)
911 if (srv
->max_repos
> 0 && srv
->max_repos
< srv
->max_repos_display
)
913 if (t
->repos_total
<= srv
->max_repos
||
914 t
->repos_total
<= srv
->max_repos_display
)
917 if (gotweb_render_navs(c
->tp
) == -1)
924 should_urlencode(int c
)
926 if (c
<= ' ' || c
>= 127)
950 /* needed because the URLs are embedded into the HTML */
959 gotweb_urlencode(const char *str
)
967 for (s
= str
; *s
; ++s
) {
969 if (should_urlencode(*s
))
973 escaped
= calloc(1, len
+ 1);
978 for (s
= str
; *s
; ++s
) {
979 if (should_urlencode(*s
)) {
980 a
= (*s
& 0xF0) >> 4;
984 escaped
[i
++] = a
<= 9 ? ('0' + a
) : ('7' + a
);
985 escaped
[i
++] = b
<= 9 ? ('0' + b
) : ('7' + b
);
994 gotweb_action_name(int action
)
1029 gotweb_render_url(struct request
*c
, struct gotweb_url
*url
)
1031 const char *sep
= "?", *action
;
1035 action
= gotweb_action_name(url
->action
);
1036 if (action
!= NULL
) {
1037 if (fcgi_printf(c
, "?action=%s", action
) == -1)
1043 if (fcgi_printf(c
, "%scommit=%s", sep
, url
->commit
) == -1)
1049 if (fcgi_printf(c
, "%sprevid=%s", sep
, url
->previd
) == -1)
1055 if (fcgi_printf(c
, "%sprevset=%s", sep
, url
->prevset
) == -1)
1061 tmp
= gotweb_urlencode(url
->file
);
1064 r
= fcgi_printf(c
, "%sfile=%s", sep
, tmp
);
1072 tmp
= gotweb_urlencode(url
->folder
);
1075 r
= fcgi_printf(c
, "%sfolder=%s", sep
, tmp
);
1083 tmp
= gotweb_urlencode(url
->headref
);
1086 r
= fcgi_printf(c
, "%sheadref=%s", sep
, url
->headref
);
1093 if (url
->index_page
!= -1) {
1094 if (fcgi_printf(c
, "%sindex_page=%d", sep
,
1095 url
->index_page
) == -1)
1101 tmp
= gotweb_urlencode(url
->path
);
1104 r
= fcgi_printf(c
, "%spath=%s", sep
, tmp
);
1111 if (url
->page
!= -1) {
1112 if (fcgi_printf(c
, "%spage=%d", sep
, url
->page
) == -1)
1121 gotweb_render_absolute_url(struct request
*c
, struct gotweb_url
*url
)
1123 struct template *tp
= c
->tp
;
1124 const char *proto
= c
->https
? "https" : "http";
1126 if (fcgi_puts(tp
, proto
) == -1 ||
1127 fcgi_puts(tp
, "://") == -1 ||
1128 tp_htmlescape(tp
, c
->server_name
) == -1 ||
1129 tp_htmlescape(tp
, c
->document_uri
) == -1)
1132 return gotweb_render_url(c
, url
);
1135 static struct got_repository
*
1136 find_cached_repo(struct server
*srv
, const char *path
)
1140 for (i
= 0; i
< srv
->ncached_repos
; i
++) {
1141 if (strcmp(srv
->cached_repos
[i
].path
, path
) == 0)
1142 return srv
->cached_repos
[i
].repo
;
1148 static const struct got_error
*
1149 cache_repo(struct got_repository
**new, struct server
*srv
,
1150 struct repo_dir
*repo_dir
, struct socket
*sock
)
1152 const struct got_error
*error
= NULL
;
1153 struct got_repository
*repo
;
1154 struct cached_repo
*cr
;
1157 if (srv
->ncached_repos
>= GOTWEBD_REPO_CACHESIZE
) {
1158 cr
= &srv
->cached_repos
[srv
->ncached_repos
- 1];
1159 error
= got_repo_close(cr
->repo
);
1160 memset(cr
, 0, sizeof(*cr
));
1161 srv
->ncached_repos
--;
1164 memmove(&srv
->cached_repos
[1], &srv
->cached_repos
[0],
1165 srv
->ncached_repos
* sizeof(srv
->cached_repos
[0]));
1166 cr
= &srv
->cached_repos
[0];
1169 cr
= &srv
->cached_repos
[srv
->ncached_repos
];
1172 error
= got_repo_open(&repo
, repo_dir
->path
, NULL
, sock
->pack_fds
);
1175 memmove(&srv
->cached_repos
[0], &srv
->cached_repos
[1],
1176 srv
->ncached_repos
* sizeof(srv
->cached_repos
[0]));
1181 if (strlcpy(cr
->path
, repo_dir
->path
, sizeof(cr
->path
))
1182 >= sizeof(cr
->path
)) {
1184 memmove(&srv
->cached_repos
[0], &srv
->cached_repos
[1],
1185 srv
->ncached_repos
* sizeof(srv
->cached_repos
[0]));
1187 return got_error(GOT_ERR_NO_SPACE
);
1191 srv
->ncached_repos
++;
1196 static const struct got_error
*
1197 gotweb_load_got_path(struct request
*c
, struct repo_dir
*repo_dir
)
1199 const struct got_error
*error
= NULL
;
1200 struct socket
*sock
= c
->sock
;
1201 struct server
*srv
= c
->srv
;
1202 struct transport
*t
= c
->t
;
1203 struct got_repository
*repo
= NULL
;
1207 if (asprintf(&dir_test
, "%s/%s/%s", srv
->repos_path
, repo_dir
->name
,
1208 GOTWEB_GIT_DIR
) == -1)
1209 return got_error_from_errno("asprintf");
1211 dt
= opendir(dir_test
);
1215 repo_dir
->path
= dir_test
;
1220 if (asprintf(&dir_test
, "%s/%s", srv
->repos_path
,
1221 repo_dir
->name
) == -1)
1222 return got_error_from_errno("asprintf");
1224 dt
= opendir(dir_test
);
1226 error
= got_error_path(repo_dir
->name
, GOT_ERR_NOT_GIT_REPO
);
1229 repo_dir
->path
= dir_test
;
1234 if (srv
->respect_exportok
&&
1235 faccessat(dirfd(dt
), "git-daemon-export-ok", F_OK
, 0) == -1) {
1236 error
= got_error_path(repo_dir
->name
, GOT_ERR_NOT_GIT_REPO
);
1240 repo
= find_cached_repo(srv
, repo_dir
->path
);
1242 error
= cache_repo(&repo
, srv
, repo_dir
, sock
);
1247 error
= gotweb_get_repo_description(&repo_dir
->description
, srv
,
1248 repo_dir
->path
, dirfd(dt
));
1251 error
= got_get_repo_owner(&repo_dir
->owner
, c
);
1254 error
= got_get_repo_age(&repo_dir
->age
, c
, NULL
);
1257 error
= gotweb_get_clone_url(&repo_dir
->url
, srv
, repo_dir
->path
,
1261 if (dt
!= NULL
&& closedir(dt
) == EOF
&& error
== NULL
)
1262 error
= got_error_from_errno("closedir");
1266 static const struct got_error
*
1267 gotweb_init_repo_dir(struct repo_dir
**repo_dir
, const char *dir
)
1269 const struct got_error
*error
;
1271 *repo_dir
= calloc(1, sizeof(**repo_dir
));
1272 if (*repo_dir
== NULL
)
1273 return got_error_from_errno("calloc");
1275 if (asprintf(&(*repo_dir
)->name
, "%s", dir
) == -1) {
1276 error
= got_error_from_errno("asprintf");
1281 (*repo_dir
)->owner
= NULL
;
1282 (*repo_dir
)->description
= NULL
;
1283 (*repo_dir
)->url
= NULL
;
1284 (*repo_dir
)->path
= NULL
;
1289 static const struct got_error
*
1290 gotweb_get_repo_description(char **description
, struct server
*srv
,
1291 const char *dirpath
, int dir
)
1293 const struct got_error
*error
= NULL
;
1298 *description
= NULL
;
1299 if (srv
->show_repo_description
== 0)
1302 fd
= openat(dir
, "description", O_RDONLY
);
1304 if (errno
!= ENOENT
&& errno
!= EACCES
) {
1305 error
= got_error_from_errno_fmt("openat %s/%s",
1306 dirpath
, "description");
1311 if (fstat(fd
, &sb
) == -1) {
1312 error
= got_error_from_errno_fmt("fstat %s/%s",
1313 dirpath
, "description");
1318 if (len
> GOTWEBD_MAXDESCRSZ
- 1)
1319 len
= GOTWEBD_MAXDESCRSZ
- 1;
1321 *description
= calloc(len
+ 1, sizeof(**description
));
1322 if (*description
== NULL
) {
1323 error
= got_error_from_errno("calloc");
1327 if (read(fd
, *description
, len
) == -1)
1328 error
= got_error_from_errno("read");
1330 if (fd
!= -1 && close(fd
) == -1 && error
== NULL
)
1331 error
= got_error_from_errno("close");
1335 static const struct got_error
*
1336 gotweb_get_clone_url(char **url
, struct server
*srv
, const char *dirpath
,
1339 const struct got_error
*error
= NULL
;
1345 if (srv
->show_repo_cloneurl
== 0)
1348 fd
= openat(dir
, "cloneurl", O_RDONLY
);
1350 if (errno
!= ENOENT
&& errno
!= EACCES
) {
1351 error
= got_error_from_errno_fmt("openat %s/%s",
1352 dirpath
, "cloneurl");
1357 if (fstat(fd
, &sb
) == -1) {
1358 error
= got_error_from_errno_fmt("fstat %s/%s",
1359 dirpath
, "cloneurl");
1364 if (len
> GOTWEBD_MAXCLONEURLSZ
- 1)
1365 len
= GOTWEBD_MAXCLONEURLSZ
- 1;
1367 *url
= calloc(len
+ 1, sizeof(**url
));
1369 error
= got_error_from_errno("calloc");
1373 if (read(fd
, *url
, len
) == -1)
1374 error
= got_error_from_errno("read");
1376 if (fd
!= -1 && close(fd
) == -1 && error
== NULL
)
1377 error
= got_error_from_errno("close");
1382 gotweb_render_age(struct template *tp
, time_t committer_time
, int ref_tm
)
1384 struct request
*c
= tp
->tp_arg
;
1386 long long diff_time
;
1387 const char *years
= "years ago", *months
= "months ago";
1388 const char *weeks
= "weeks ago", *days
= "days ago";
1389 const char *hours
= "hours ago", *minutes
= "minutes ago";
1390 const char *seconds
= "seconds ago", *now
= "right now";
1397 diff_time
= time(NULL
) - committer_time
;
1398 if (diff_time
> 60 * 60 * 24 * 365 * 2) {
1399 if (fcgi_printf(c
, "%lld %s",
1400 (diff_time
/ 60 / 60 / 24 / 365), years
) == -1)
1402 } else if (diff_time
> 60 * 60 * 24 * (365 / 12) * 2) {
1403 if (fcgi_printf(c
, "%lld %s",
1404 (diff_time
/ 60 / 60 / 24 / (365 / 12)),
1407 } else if (diff_time
> 60 * 60 * 24 * 7 * 2) {
1408 if (fcgi_printf(c
, "%lld %s",
1409 (diff_time
/ 60 / 60 / 24 / 7), weeks
) == -1)
1411 } else if (diff_time
> 60 * 60 * 24 * 2) {
1412 if (fcgi_printf(c
, "%lld %s",
1413 (diff_time
/ 60 / 60 / 24), days
) == -1)
1415 } else if (diff_time
> 60 * 60 * 2) {
1416 if (fcgi_printf(c
, "%lld %s",
1417 (diff_time
/ 60 / 60), hours
) == -1)
1419 } else if (diff_time
> 60 * 2) {
1420 if (fcgi_printf(c
, "%lld %s", (diff_time
/ 60),
1423 } else if (diff_time
> 2) {
1424 if (fcgi_printf(c
, "%lld %s", diff_time
,
1428 if (fcgi_puts(tp
, now
) == -1)
1433 if (gmtime_r(&committer_time
, &tm
) == NULL
)
1436 s
= asctime_r(&tm
, datebuf
);
1440 if (fcgi_puts(tp
, datebuf
) == -1 ||
1441 fcgi_puts(tp
, " UTC") == -1)
1445 if (gmtime_r(&committer_time
, &tm
) == NULL
)
1448 r
= strftime(datebuf
, sizeof(datebuf
),
1449 "%a, %d %b %Y %H:%M:%S GMT", &tm
);
1453 if (fcgi_puts(tp
, datebuf
) == -1)