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>
26 #include <sys/queue.h>
36 #include "got_error.h"
39 TAILQ_HEAD
(files
, file
) files
= TAILQ_HEAD_INITIALIZER
(files
);
41 TAILQ_ENTRY
(file
) entry
;
50 static const struct got_error
* pushfile
(struct file
**, const char *);
54 int yyerror(const char *, ...
)
55 __attribute__
((__format__
(printf
, 1, 2)))
56 __attribute__
((__nonnull__
(1)));
57 int kw_cmp
(const void *, const void *);
64 TAILQ_HEAD
(symhead
, sym
) symhead
= TAILQ_HEAD_INITIALIZER
(symhead
);
66 TAILQ_ENTRY
(sym
) entry
;
73 int symset
(const char *, const char *, int);
74 int cmdline_symset
(char *);
75 char *symget
(const char *);
77 const struct got_error
* gerror
= NULL
;
78 struct gotweb_config
*gw_conf
;
90 %token GOT_WWW_PATH GOT_MAX_REPOS GOT_SITE_NAME GOT_SITE_OWNER GOT_SITE_LINK
91 %token GOT_LOGO GOT_LOGO_URL GOT_SHOW_REPO_OWNER GOT_SHOW_REPO_AGE
92 %token GOT_SHOW_REPO_DESCRIPTION GOT_MAX_REPOS_DISPLAY GOT_REPOS_PATH
93 %token GOT_MAX_COMMITS_DISPLAY ERROR GOT_SHOW_SITE_OWNER
94 %token GOT_SHOW_REPO_CLONEURL
95 %token
<v.
string> STRING
96 %token
<v.number
> NUMBER
97 %type
<v.number
> boolean
100 grammar
: /* empty */
106 if
(strcasecmp
($1, "true") == 0 ||
107 strcasecmp
($1, "on") == 0 ||
108 strcasecmp
($1, "yes") == 0)
110 else if
(strcasecmp
($1, "false") == 0 ||
111 strcasecmp
($1, "off") == 0 ||
112 strcasecmp
($1, "no") == 0)
115 yyerror("invalid boolean value '%s'", $1);
122 main
: GOT_REPOS_PATH STRING
{
123 gw_conf
->got_repos_path
= $2;
125 | GOT_WWW_PATH STRING
{
126 gw_conf
->got_www_path
= $2;
128 | GOT_MAX_REPOS NUMBER
{
130 gw_conf
->got_max_repos
= $2;
132 | GOT_SITE_NAME STRING
{
133 gw_conf
->got_site_name
= $2;
135 | GOT_SITE_OWNER STRING
{
136 gw_conf
->got_site_owner
= $2;
138 | GOT_SITE_LINK STRING
{
139 gw_conf
->got_site_link
= $2;
142 gw_conf
->got_logo
= $2;
144 | GOT_LOGO_URL STRING
{
145 gw_conf
->got_logo_url
= $2;
147 | GOT_SHOW_SITE_OWNER boolean
{
148 gw_conf
->got_show_site_owner
= $2;
150 | GOT_SHOW_REPO_OWNER boolean
{
151 gw_conf
->got_show_repo_owner
= $2;
153 | GOT_SHOW_REPO_AGE boolean
{
154 gw_conf
->got_show_repo_age
= $2;
156 | GOT_SHOW_REPO_DESCRIPTION boolean
{
157 gw_conf
->got_show_repo_description
= $2;
159 | GOT_SHOW_REPO_CLONEURL boolean
{
160 gw_conf
->got_show_repo_cloneurl
= $2;
162 | GOT_MAX_REPOS_DISPLAY NUMBER
{
164 gw_conf
->got_max_repos_display
= $2;
166 | GOT_MAX_COMMITS_DISPLAY NUMBER
{
168 gw_conf
->got_max_commits_display
= $2;
179 yyerror(const char *fmt
, ...
)
186 if
(vasprintf
(&msg
, fmt
, ap
) == -1) {
187 gerror
= got_error_from_errno
("vasprintf");
191 if
(asprintf
(&err
, "%s:%d: %s", file
->name
, yylval.lineno
, msg
) == -1) {
192 gerror
= got_error_from_errno
("asprintf");
195 gerror
= got_error_msg
(GOT_ERR_PARSE_CONFIG
, err
);
201 kw_cmp
(const void *k
, const void *e
)
203 return
(strcmp
(k
, ((const struct keywords
*)e
)->k_name
));
209 /* This has to be sorted always. */
210 static const struct keywords keywords
[] = {
211 { "got_logo", GOT_LOGO
},
212 { "got_logo_url", GOT_LOGO_URL
},
213 { "got_max_commits_display", GOT_MAX_COMMITS_DISPLAY
},
214 { "got_max_repos", GOT_MAX_REPOS
},
215 { "got_max_repos_display", GOT_MAX_REPOS_DISPLAY
},
216 { "got_repos_path", GOT_REPOS_PATH
},
217 { "got_show_repo_age", GOT_SHOW_REPO_AGE
},
218 { "got_show_repo_cloneurl", GOT_SHOW_REPO_CLONEURL
},
219 { "got_show_repo_description", GOT_SHOW_REPO_DESCRIPTION
},
220 { "got_show_repo_owner", GOT_SHOW_REPO_OWNER
},
221 { "got_show_site_owner", GOT_SHOW_SITE_OWNER
},
222 { "got_site_link", GOT_SITE_LINK
},
223 { "got_site_name", GOT_SITE_NAME
},
224 { "got_site_owner", GOT_SITE_OWNER
},
225 { "got_www_path", GOT_WWW_PATH
},
227 const struct keywords
*p
;
229 p
= bsearch
(s
, keywords
, sizeof
(keywords
)/sizeof
(keywords
[0]),
230 sizeof
(keywords
[0]), kw_cmp
);
238 #define START_EXPAND 1
239 #define DONE_EXPAND 2
241 static int expanding
;
249 if
(file
->ungetpos
> 0)
250 c
= file
->ungetbuf
[--file
->ungetpos
];
252 c
= getc
(file
->stream
);
254 if
(c
== START_EXPAND
)
256 else if
(c
== DONE_EXPAND
)
270 if
((c
= igetc
()) == EOF
) {
271 yyerror("reached end of file while parsing "
273 if
(file
== topfile || popfile
() == EOF
)
280 while
((c
= igetc
()) == '\\') {
286 yylval.lineno
= file
->lineno
;
292 * Fake EOL when hit EOF for the first time. This gets line
293 * count right if last line in included file is syntactically
294 * invalid and has no newline.
296 if
(file
->eof_reached
== 0) {
297 file
->eof_reached
= 1;
301 if
(file
== topfile || popfile
() == EOF
)
315 if
(file
->ungetpos
>= file
->ungetsize
) {
316 void *p
= reallocarray
(file
->ungetbuf
, file
->ungetsize
, 2);
318 err
(1, "%s", __func__
);
320 file
->ungetsize
*= 2;
322 file
->ungetbuf
[file
->ungetpos
++] = c
;
330 /* Skip to either EOF or the first real EOL. */
353 while
((c
= lgetc
(0)) == ' ' || c
== '\t')
356 yylval.lineno
= file
->lineno
;
358 while
((c
= lgetc
(0)) != '\n' && c
!= EOF
)
360 if
(c
== '$' && !expanding
) {
362 if
((c
= lgetc
(0)) == EOF
)
365 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
366 yyerror("string too long");
369 if
(isalnum
(c
) || c
== '_') {
379 yyerror("macro '%s' not defined", buf
);
382 p
= val
+ strlen
(val
) - 1;
383 lungetc
(DONE_EXPAND
);
385 lungetc
((unsigned char)*p
);
388 lungetc
(START_EXPAND
);
397 if
((c
= lgetc
(quotec
)) == EOF
)
402 } else if
(c
== '\\') {
403 if
((next
= lgetc
(quotec
)) == EOF
)
405 if
(next
== quotec || c
== ' ' || c
== '\t')
407 else if
(next
== '\n') {
412 } else if
(c
== quotec
) {
415 } else if
(c
== '\0') {
416 yyerror("syntax error");
419 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
420 yyerror("string too long");
425 yylval.v.
string = strdup
(buf
);
426 if
(yylval.v.
string == NULL
)
427 err
(1, "%s", __func__
);
431 #define allowed_to_end_number(x) \
432 (isspace
(x
) || x
== ')' || x
==',' || x
== '/' || x
== '}' || x
== '=')
434 if
(c
== '-' || isdigit
(c
)) {
437 if
((size_t)(p
-buf
) >= sizeof
(buf
)) {
438 yyerror("string too long");
441 } while
((c
= lgetc
(0)) != EOF
&& isdigit
(c
));
443 if
(p
== buf
+ 1 && buf
[0] == '-')
445 if
(c
== EOF || allowed_to_end_number
(c
)) {
446 const char *errstr
= NULL
;
449 yylval.v.number
= strtonum
(buf
, LLONG_MIN
,
452 yyerror("\"%s\" invalid number: %s",
460 lungetc
((unsigned char)*--p
);
461 c
= (unsigned char)*--p
;
467 #define allowed_in_string(x) \
468 (isalnum
(x
) ||
(ispunct
(x
) && x
!= '(' && x
!= ')' && \
469 x
!= '{' && x
!= '}' && \
470 x
!= '!' && x
!= '=' && x
!= '#' && \
473 if
(isalnum
(c
) || c
== ':' || c
== '_') {
476 if
((size_t)(p
-buf
) >= sizeof
(buf
)) {
477 yyerror("string too long");
480 } while
((c
= lgetc
(0)) != EOF
&& (allowed_in_string
(c
)));
483 if
((token
= lookup
(buf
)) == STRING
)
484 if
((yylval.v.
string = strdup
(buf
)) == NULL
)
485 err
(1, "%s", __func__
);
489 yylval.lineno
= file
->lineno
;
497 static const struct got_error
*
498 pushfile
(struct file
**nfile
, const char *name
)
500 const struct got_error
* error = NULL
;
502 if
(((*nfile
) = calloc
(1, sizeof
(struct file
))) == NULL
)
503 return got_error_from_errno2
(__func__
, "calloc");
504 if
(((*nfile
)->name
= strdup
(name
)) == NULL
) {
506 return got_error_from_errno2
(__func__
, "strdup");
508 if
(((*nfile
)->stream
= fopen
((*nfile
)->name
, "re")) == NULL
) {
510 if
(asprintf
(&msg
, "%s", (*nfile
)->name
) == -1)
511 return got_error_from_errno
("asprintf");
512 error = got_error_msg
(GOT_ERR_NO_CONFIG_FILE
, msg
);
513 free
((*nfile
)->name
);
518 (*nfile
)->lineno
= TAILQ_EMPTY
(&files
) ?
1 : 0;
519 (*nfile
)->ungetsize
= 16;
520 (*nfile
)->ungetbuf
= malloc
((*nfile
)->ungetsize
);
521 if
((*nfile
)->ungetbuf
== NULL
) {
522 fclose
((*nfile
)->stream
);
523 free
((*nfile
)->name
);
525 return got_error_from_errno2
(__func__
, "malloc");
527 TAILQ_INSERT_TAIL
(&files
, (*nfile
), entry
);
534 struct file
*prev
= NULL
;
536 TAILQ_REMOVE
(&files
, file
, entry
);
537 fclose
(file
->stream
);
539 free
(file
->ungetbuf
);
542 return
(file ?
0 : EOF
);
545 const struct got_error
*
546 parse_gotweb_config
(struct gotweb_config
**gconf
, const char *filename
)
548 gw_conf
= malloc
(sizeof
(struct gotweb_config
));
549 if
(gw_conf
== NULL
) {
550 gerror
= got_error_from_errno
("malloc");
553 gw_conf
->got_repos_path
= strdup
(D_GOTPATH
);
554 if
(gw_conf
->got_repos_path
== NULL
) {
555 gerror
= got_error_from_errno
("strdup");
558 gw_conf
->got_www_path
= strdup
(D_GOTWWW
);
559 if
(gw_conf
->got_www_path
== NULL
) {
560 gerror
= got_error_from_errno
("strdup");
563 gw_conf
->got_site_name
= strdup
(D_SITENAME
);
564 if
(gw_conf
->got_site_name
== NULL
) {
565 gerror
= got_error_from_errno
("strdup");
568 gw_conf
->got_site_owner
= strdup
(D_SITEOWNER
);
569 if
(gw_conf
->got_site_owner
== NULL
) {
570 gerror
= got_error_from_errno
("strdup");
573 gw_conf
->got_site_link
= strdup
(D_SITELINK
);
574 if
(gw_conf
->got_site_link
== NULL
) {
575 gerror
= got_error_from_errno
("strdup");
578 gw_conf
->got_logo
= strdup
(D_GOTLOGO
);
579 if
(gw_conf
->got_logo
== NULL
) {
580 gerror
= got_error_from_errno
("strdup");
583 gw_conf
->got_logo_url
= strdup
(D_GOTURL
);
584 if
(gw_conf
->got_logo_url
== NULL
) {
585 gerror
= got_error_from_errno
("strdup");
588 gw_conf
->got_show_site_owner
= D_SHOWSOWNER
;
589 gw_conf
->got_show_repo_owner
= D_SHOWROWNER
;
590 gw_conf
->got_show_repo_age
= D_SHOWAGE
;
591 gw_conf
->got_show_repo_description
= D_SHOWDESC
;
592 gw_conf
->got_show_repo_cloneurl
= D_SHOWURL
;
593 gw_conf
->got_max_repos
= D_MAXREPO
;
594 gw_conf
->got_max_repos_display
= D_MAXREPODISP
;
595 gw_conf
->got_max_commits_display
= D_MAXCOMMITDISP
;
598 * We don't require that the gotweb config file exists
599 * So reset gerror if it doesn't exist and goto done.
601 gerror
= pushfile
(&file
, filename
);
602 if
(gerror
&& gerror
->code
== GOT_ERR_NO_CONFIG_FILE
) {
617 symset
(const char *nam
, const char *val
, int persist
)
621 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
622 if
(strcmp
(nam
, sym
->nam
) == 0)
627 if
(sym
->persist
== 1)
632 TAILQ_REMOVE
(&symhead
, sym
, entry
);
636 if
((sym
= calloc
(1, sizeof
(*sym
))) == NULL
)
639 sym
->nam
= strdup
(nam
);
640 if
(sym
->nam
== NULL
) {
644 sym
->val
= strdup
(val
);
645 if
(sym
->val
== NULL
) {
651 sym
->persist
= persist
;
652 TAILQ_INSERT_TAIL
(&symhead
, sym
, entry
);
657 cmdline_symset
(char *s
)
663 if
((val
= strrchr
(s
, '=')) == NULL
)
666 len
= strlen
(s
) - strlen
(val
) + 1;
667 if
((sym
= malloc
(len
)) == NULL
)
668 errx
(1, "cmdline_symset: malloc");
670 strlcpy
(sym
, s
, len
);
672 ret
= symset
(sym
, val
+ 1, 1);
679 symget
(const char *nam
)
683 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
684 if
(strcmp
(nam
, sym
->nam
) == 0) {