2 * Copyright (c) 2019, 2020 Tracey Emery <tracey@openbsd.org>
3 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
4 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
5 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
6 * Copyright (c) 2001 Markus Friedl. All rights reserved.
7 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
8 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 #include <sys/types.h>
25 #include <sys/queue.h>
35 #include "got_error.h"
38 TAILQ_HEAD
(files
, file
) files
= TAILQ_HEAD_INITIALIZER
(files
);
40 TAILQ_ENTRY
(file
) entry
;
49 static const struct got_error
* pushfile
(struct file
**, const char *);
53 int yyerror(const char *, ...
)
54 __attribute__
((__format__
(printf
, 1, 2)))
55 __attribute__
((__nonnull__
(1)));
56 int kw_cmp
(const void *, const void *);
63 TAILQ_HEAD
(symhead
, sym
) symhead
= TAILQ_HEAD_INITIALIZER
(symhead
);
65 TAILQ_ENTRY
(sym
) entry
;
72 int symset
(const char *, const char *, int);
73 char *symget
(const char *);
75 const struct got_error
* gerror
= NULL
;
76 struct gotweb_config
*gw_conf
;
88 %token GOT_WWW_PATH GOT_MAX_REPOS GOT_SITE_NAME GOT_SITE_OWNER GOT_SITE_LINK
89 %token GOT_LOGO GOT_LOGO_URL GOT_SHOW_REPO_OWNER GOT_SHOW_REPO_AGE
90 %token GOT_SHOW_REPO_DESCRIPTION GOT_MAX_REPOS_DISPLAY GOT_REPOS_PATH
91 %token GOT_MAX_COMMITS_DISPLAY ERROR GOT_SHOW_SITE_OWNER
92 %token GOT_SHOW_REPO_CLONEURL
93 %token
<v.
string> STRING
94 %token
<v.number
> NUMBER
95 %type
<v.number
> boolean
104 if
(strcasecmp
($1, "true") == 0 ||
105 strcasecmp
($1, "on") == 0 ||
106 strcasecmp
($1, "yes") == 0)
108 else if
(strcasecmp
($1, "false") == 0 ||
109 strcasecmp
($1, "off") == 0 ||
110 strcasecmp
($1, "no") == 0)
113 yyerror("invalid boolean value '%s'", $1);
120 main
: GOT_REPOS_PATH STRING
{
121 gw_conf
->got_repos_path
= $2;
123 | GOT_WWW_PATH STRING
{
124 gw_conf
->got_www_path
= $2;
126 | GOT_MAX_REPOS NUMBER
{
128 gw_conf
->got_max_repos
= $2;
130 | GOT_SITE_NAME STRING
{
131 gw_conf
->got_site_name
= $2;
133 | GOT_SITE_OWNER STRING
{
134 gw_conf
->got_site_owner
= $2;
136 | GOT_SITE_LINK STRING
{
137 gw_conf
->got_site_link
= $2;
140 gw_conf
->got_logo
= $2;
142 | GOT_LOGO_URL STRING
{
143 gw_conf
->got_logo_url
= $2;
145 | GOT_SHOW_SITE_OWNER boolean
{
146 gw_conf
->got_show_site_owner
= $2;
148 | GOT_SHOW_REPO_OWNER boolean
{
149 gw_conf
->got_show_repo_owner
= $2;
151 | GOT_SHOW_REPO_AGE boolean
{
152 gw_conf
->got_show_repo_age
= $2;
154 | GOT_SHOW_REPO_DESCRIPTION boolean
{
155 gw_conf
->got_show_repo_description
= $2;
157 | GOT_SHOW_REPO_CLONEURL boolean
{
158 gw_conf
->got_show_repo_cloneurl
= $2;
160 | GOT_MAX_REPOS_DISPLAY NUMBER
{
162 gw_conf
->got_max_repos_display
= $2;
164 | GOT_MAX_COMMITS_DISPLAY NUMBER
{
166 gw_conf
->got_max_commits_display
= $2;
177 yyerror(const char *fmt
, ...
)
184 if
(vasprintf
(&msg
, fmt
, ap
) == -1) {
185 gerror
= got_error_from_errno
("vasprintf");
189 if
(asprintf
(&err
, "%s:%d: %s", file
->name
, yylval.lineno
, msg
) == -1) {
190 gerror
= got_error_from_errno
("asprintf");
193 gerror
= got_error_msg
(GOT_ERR_PARSE_CONFIG
, err
);
199 kw_cmp
(const void *k
, const void *e
)
201 return
(strcmp
(k
, ((const struct keywords
*)e
)->k_name
));
207 /* This has to be sorted always. */
208 static const struct keywords keywords
[] = {
209 { "got_logo", GOT_LOGO
},
210 { "got_logo_url", GOT_LOGO_URL
},
211 { "got_max_commits_display", GOT_MAX_COMMITS_DISPLAY
},
212 { "got_max_repos", GOT_MAX_REPOS
},
213 { "got_max_repos_display", GOT_MAX_REPOS_DISPLAY
},
214 { "got_repos_path", GOT_REPOS_PATH
},
215 { "got_show_repo_age", GOT_SHOW_REPO_AGE
},
216 { "got_show_repo_cloneurl", GOT_SHOW_REPO_CLONEURL
},
217 { "got_show_repo_description", GOT_SHOW_REPO_DESCRIPTION
},
218 { "got_show_repo_owner", GOT_SHOW_REPO_OWNER
},
219 { "got_show_site_owner", GOT_SHOW_SITE_OWNER
},
220 { "got_site_link", GOT_SITE_LINK
},
221 { "got_site_name", GOT_SITE_NAME
},
222 { "got_site_owner", GOT_SITE_OWNER
},
223 { "got_www_path", GOT_WWW_PATH
},
225 const struct keywords
*p
;
227 p
= bsearch
(s
, keywords
, sizeof
(keywords
)/sizeof
(keywords
[0]),
228 sizeof
(keywords
[0]), kw_cmp
);
236 #define START_EXPAND 1
237 #define DONE_EXPAND 2
239 static int expanding
;
247 if
(file
->ungetpos
> 0)
248 c
= file
->ungetbuf
[--file
->ungetpos
];
250 c
= getc
(file
->stream
);
252 if
(c
== START_EXPAND
)
254 else if
(c
== DONE_EXPAND
)
268 if
((c
= igetc
()) == EOF
) {
269 yyerror("reached end of file while parsing "
271 if
(file
== topfile || popfile
() == EOF
)
278 while
((c
= igetc
()) == '\\') {
284 yylval.lineno
= file
->lineno
;
290 * Fake EOL when hit EOF for the first time. This gets line
291 * count right if last line in included file is syntactically
292 * invalid and has no newline.
294 if
(file
->eof_reached
== 0) {
295 file
->eof_reached
= 1;
299 if
(file
== topfile || popfile
() == EOF
)
313 if
(file
->ungetpos
>= file
->ungetsize
) {
314 void *p
= reallocarray
(file
->ungetbuf
, file
->ungetsize
, 2);
316 err
(1, "%s", __func__
);
318 file
->ungetsize
*= 2;
320 file
->ungetbuf
[file
->ungetpos
++] = c
;
328 /* Skip to either EOF or the first real EOL. */
344 unsigned char buf
[8096];
345 unsigned char *p
, *val
;
351 while
((c
= lgetc
(0)) == ' ' || c
== '\t')
354 yylval.lineno
= file
->lineno
;
356 while
((c
= lgetc
(0)) != '\n' && c
!= EOF
)
358 if
(c
== '$' && !expanding
) {
360 if
((c
= lgetc
(0)) == EOF
)
363 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
364 yyerror("string too long");
367 if
(isalnum
(c
) || c
== '_') {
377 yyerror("macro '%s' not defined", buf
);
380 p
= val
+ strlen
(val
) - 1;
381 lungetc
(DONE_EXPAND
);
386 lungetc
(START_EXPAND
);
395 if
((c
= lgetc
(quotec
)) == EOF
)
400 } else if
(c
== '\\') {
401 if
((next
= lgetc
(quotec
)) == EOF
)
403 if
(next
== quotec || c
== ' ' || c
== '\t')
405 else if
(next
== '\n') {
410 } else if
(c
== quotec
) {
413 } else if
(c
== '\0') {
414 yyerror("syntax error");
417 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
418 yyerror("string too long");
423 yylval.v.
string = strdup
(buf
);
424 if
(yylval.v.
string == NULL
)
425 err
(1, "%s", __func__
);
429 #define allowed_to_end_number(x) \
430 (isspace
(x
) || x
== ')' || x
==',' || x
== '/' || x
== '}' || x
== '=')
432 if
(c
== '-' || isdigit
(c
)) {
435 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
436 yyerror("string too long");
439 } while
((c
= lgetc
(0)) != EOF
&& isdigit
(c
));
441 if
(p
== buf
+ 1 && buf
[0] == '-')
443 if
(c
== EOF || allowed_to_end_number
(c
)) {
444 const char *errstr
= NULL
;
447 yylval.v.number
= strtonum
(buf
, LLONG_MIN
,
450 yyerror("\"%s\" invalid number: %s",
465 #define allowed_in_string(x) \
466 (isalnum
(x
) ||
(ispunct
(x
) && x
!= '(' && x
!= ')' && \
467 x
!= '{' && x
!= '}' && \
468 x
!= '!' && x
!= '=' && x
!= '#' && \
471 if
(isalnum
(c
) || c
== ':' || c
== '_') {
474 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
475 yyerror("string too long");
478 } while
((c
= lgetc
(0)) != EOF
&& (allowed_in_string
(c
)));
481 if
((token
= lookup
(buf
)) == STRING
)
482 if
((yylval.v.
string = strdup
(buf
)) == NULL
)
483 err
(1, "%s", __func__
);
487 yylval.lineno
= file
->lineno
;
495 static const struct got_error
*
496 pushfile
(struct file
**nfile
, const char *name
)
498 const struct got_error
* error = NULL
;
500 if
(((*nfile
) = calloc
(1, sizeof
(struct file
))) == NULL
)
501 return got_error_from_errno2
(__func__
, "calloc");
502 if
(((*nfile
)->name
= strdup
(name
)) == NULL
) {
504 return got_error_from_errno2
(__func__
, "strdup");
506 if
(((*nfile
)->stream
= fopen
((*nfile
)->name
, "r")) == NULL
) {
508 if
(asprintf
(&msg
, "%s", (*nfile
)->name
) == -1)
509 return got_error_from_errno
("asprintf");
510 error = got_error_msg
(GOT_ERR_NO_CONFIG_FILE
, msg
);
511 free
((*nfile
)->name
);
516 (*nfile
)->lineno
= TAILQ_EMPTY
(&files
) ?
1 : 0;
517 (*nfile
)->ungetsize
= 16;
518 (*nfile
)->ungetbuf
= malloc
((*nfile
)->ungetsize
);
519 if
((*nfile
)->ungetbuf
== NULL
) {
520 fclose
((*nfile
)->stream
);
521 free
((*nfile
)->name
);
523 return got_error_from_errno2
(__func__
, "malloc");
525 TAILQ_INSERT_TAIL
(&files
, (*nfile
), entry
);
532 struct file
*prev
= NULL
;
534 TAILQ_REMOVE
(&files
, file
, entry
);
535 fclose
(file
->stream
);
537 free
(file
->ungetbuf
);
540 return
(file ?
0 : EOF
);
543 const struct got_error
*
544 parse_gotweb_config
(struct gotweb_config
**gconf
, const char *filename
)
546 gw_conf
= malloc
(sizeof
(struct gotweb_config
));
547 if
(gw_conf
== NULL
) {
548 gerror
= got_error_from_errno
("malloc");
551 gw_conf
->got_repos_path
= strdup
(D_GOTPATH
);
552 if
(gw_conf
->got_repos_path
== NULL
) {
553 gerror
= got_error_from_errno
("strdup");
556 gw_conf
->got_www_path
= strdup
(D_GOTWWW
);
557 if
(gw_conf
->got_www_path
== NULL
) {
558 gerror
= got_error_from_errno
("strdup");
561 gw_conf
->got_site_name
= strdup
(D_SITENAME
);
562 if
(gw_conf
->got_site_name
== NULL
) {
563 gerror
= got_error_from_errno
("strdup");
566 gw_conf
->got_site_owner
= strdup
(D_SITEOWNER
);
567 if
(gw_conf
->got_site_owner
== NULL
) {
568 gerror
= got_error_from_errno
("strdup");
571 gw_conf
->got_site_link
= strdup
(D_SITELINK
);
572 if
(gw_conf
->got_site_link
== NULL
) {
573 gerror
= got_error_from_errno
("strdup");
576 gw_conf
->got_logo
= strdup
(D_GOTLOGO
);
577 if
(gw_conf
->got_logo
== NULL
) {
578 gerror
= got_error_from_errno
("strdup");
581 gw_conf
->got_logo_url
= strdup
(D_GOTURL
);
582 if
(gw_conf
->got_logo_url
== NULL
) {
583 gerror
= got_error_from_errno
("strdup");
586 gw_conf
->got_show_site_owner
= D_SHOWSOWNER
;
587 gw_conf
->got_show_repo_owner
= D_SHOWROWNER
;
588 gw_conf
->got_show_repo_age
= D_SHOWAGE
;
589 gw_conf
->got_show_repo_description
= D_SHOWDESC
;
590 gw_conf
->got_show_repo_cloneurl
= D_SHOWURL
;
591 gw_conf
->got_max_repos
= D_MAXREPO
;
592 gw_conf
->got_max_repos_display
= D_MAXREPODISP
;
593 gw_conf
->got_max_commits_display
= D_MAXCOMMITDISP
;
596 * We don't require that the gotweb config file exists
597 * So reset gerror if it doesn't exist and goto done.
599 gerror
= pushfile
(&file
, filename
);
600 if
(gerror
&& gerror
->code
== GOT_ERR_NO_CONFIG_FILE
) {
615 symset
(const char *nam
, const char *val
, int persist
)
619 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
620 if
(strcmp
(nam
, sym
->nam
) == 0)
625 if
(sym
->persist
== 1)
630 TAILQ_REMOVE
(&symhead
, sym
, entry
);
634 if
((sym
= calloc
(1, sizeof
(*sym
))) == NULL
)
637 sym
->nam
= strdup
(nam
);
638 if
(sym
->nam
== NULL
) {
642 sym
->val
= strdup
(val
);
643 if
(sym
->val
== NULL
) {
649 sym
->persist
= persist
;
650 TAILQ_INSERT_TAIL
(&symhead
, sym
, entry
);
655 cmdline_symset
(char *s
)
661 if
((val
= strrchr
(s
, '=')) == NULL
)
664 len
= strlen
(s
) - strlen
(val
) + 1;
665 if
((sym
= malloc
(len
)) == NULL
)
666 errx
(1, "cmdline_symset: malloc");
668 strlcpy
(sym
, s
, len
);
670 ret
= symset
(sym
, val
+ 1, 1);
677 symget
(const char *nam
)
681 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
682 if
(strcmp
(nam
, sym
->nam
) == 0) {