/* mohawk - small HTTP server
**
** Copyright © 2010 by Baptiste Daroussin <bapt@FreeBSD.org>,
** Copyright © 2010 by Freddy Dissaux <freddy.dsx@free.fr>,
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/
%option nounput
%option noinput

%{
#include <err.h>
#include <errno.h>
#include <glob.h>
#include <limits.h>
#if defined(__FreeBSD__)
#include <osreldate.h>
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__OpenBSD__)
#include "y.tab.h"
#else
#include "parse.h"
#endif
#include "include.h"

const char* words(const char *);
unsigned int parse_number(const char *, unsigned int, unsigned int);
int yylex(void);

#if (defined(__FreeBSD__) && __FreeBSD_version < 1000033)
extern int yylineno;
int yylineno = 1;
#endif
const char *
words(const char *s)
{
	char *p;
	p = strrchr(s, '"');
	*p = '\0';
	return s + 1;
}

unsigned int
parse_number(const char *buf, unsigned int min, unsigned int max)
{
	char *ep;
	unsigned int ival;
	long lval;

	errno = 0;
	lval = strtol(buf, &ep, 10);
	if (buf[0] == '\0' || *ep != '\0')
		errx(1, "parse_number '%s': '%s'\n", buf, ep);
	if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) || (lval > INT_MAX || lval < INT_MIN))
		errx(1, "parse_number '%s': out of range\n", buf);
	ival = (unsigned int)lval;
	if (ival < min || ival > max)
		errx(1, "parse_number '%s': %d out of range %d %d\n", buf, ival, min, max);
	return ival;
}
%}

/* Based on http://www.esiee.fr/~najmanl/compil/Flex/flex_2.html#SEC12 */
/* the "incl" state is used for picking up the name
 * of an include file
 */
%x incl

%{
struct mohawk_include {
	int yylineno;
#if defined (__OpenBSD__)
	int glob_idx;
#else
	size_t glob_idx;
#endif
	const char *config_filename;
	YY_BUFFER_STATE yy_buffer_state;
	glob_t g;
};

static struct mohawk_include includes[MAX_INCLUDE_DEPTH];
const char *config_filename;
static int include_idx = 0;
%}

%%
restrict_froms      errx(1, "restrict_froms is deprecated. Use grant_access instead");
auth_blacklistd     return AUTH_BLACKLISTD;
auth_blocklistd     return AUTH_BLACKLISTD;
auth_path           return AUTH_PATH;
auth_patterns       return AUTH_PATTERNS;
authentication      return AUTHENTICATION;
blacklist_patterns  return BLACKLIST_PATTERNS;
blocklist_patterns  return BLACKLIST_PATTERNS;
cgi_env             return CGI_ENV;
cgi_map             return CGI_MAP;
cgi_patterns        return CGI_PATTERNS;
charset             return CHARSET;
chroot              return CHROOT;
clone               return CLONE;
debug               return MOHAWK_DEBUG;
default             return DEFAULT;
dirlist             return DIRLIST;
dirlist_css_url     return DIRLIST_CSS_URL;
email_admin         return EMAIL_ADMIN;
external_symlinks   return EXTERNAL_SYMLINKS;
hostname_in_rootdir return HOSTNAME_IN_ROOTDIR;
include             BEGIN(incl);
index_names         return INDEX_NAMES;
listen              return LISTEN;
location            return LOCATION;
maxage              return MAXAGE;
mime_type           return MIME_TYPE;
mohawk_name         return MOHAWK_NAME;
no_auth_patterns    return NO_AUTH_PATTERNS;
no_cgi_maps         return NO_CGI_MAPS;
no_log_patterns     return NO_LOG_PATTERNS;
no_unix_socket_patterns return NO_UNIX_SOCKET_PATTERNS;
pidfile             return PIDFILE;
port                return PORT;
reset               return RESET;
grant_access        return GRANT_ACCESS;
rootdir             return ROOTDIR; 
status_url          return STATUS_URL;
syslog_facility     return SYSLOG_FACILITY;
unix_socket_path    return UNIX_SOCKET_PATH;
unix_socket_patterns return UNIX_SOCKET_PATTERNS;
user                return USER;
vhost               return VHOST;
x_forwarded_for     return X_FORWARDED_FOR;
cgi_expose_mohawk_version return CGI_EXPOSE_MOHAWK_VERSION;
\{                  return OBRACE;
\}                  return EBRACE;
on|off              yylval.state  =! strcmp(yytext, "on"); return STATE;
\"[^\"]*\"          yylval.string = strdup(words(yytext)); return WORDS;
[0-9]+              yylval.number = parse_number(yytext, 0, INT_MAX); return NUMBER;
[[:graph:]]+        yylval.string = strdup(yytext); return WORD;
[ \t]+              ;
#.*$                /* ignore this token */

<incl>[ \t]*        /* eat the whitespace */
<incl>[^ \t\n]+ {   /* got the include file name */
	if (include_idx >= MAX_INCLUDE_DEPTH)
		errx(1, "Includes nested too deeply");

	includes[include_idx].yy_buffer_state = YY_CURRENT_BUFFER;
	includes[include_idx].yylineno = yylineno;
	include_idx++;
	if (index(yytext, '*') || index(yytext, '?')) {
		if (glob(yytext, GLOB_ERR, NULL, &includes[include_idx].g))
			errx(1, "Can't glob '%s'", yytext);

		config_filename = includes[include_idx].g.gl_pathv[0];
	}
	else {
		includes[include_idx].config_filename = strdup(yytext);
		config_filename = includes[include_idx].config_filename;
	}
	if ((yyin = fopen(config_filename, "r")) == NULL)
		errx(1, "Can't fopen '%s'", config_filename);

	yylineno = 1;
	yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));

	BEGIN(INITIAL);
}

<<EOF>> {
	if (include_idx == 0) {/* End of main config file */
		for (; include_idx < MAX_INCLUDE_DEPTH; include_idx++) {
			if (includes[include_idx].g.gl_pathv != NULL)
				globfree(&includes[include_idx].g);
		}
		yyterminate();
	}
	else {
		if (includes[include_idx].g.gl_pathc == 0 || includes[include_idx].glob_idx == includes[include_idx].g.gl_pathc - 1) {
			/* no glob or end of simple include */
			include_idx--,
			yylineno = includes[include_idx].yylineno;
			config_filename = includes[include_idx].config_filename;
			yy_switch_to_buffer(includes[include_idx].yy_buffer_state);
		}
		else {
			includes[include_idx].glob_idx++;
			config_filename = includes[include_idx].g.gl_pathv[includes[include_idx].glob_idx];
			if ((yyin = fopen(config_filename, "r")) == NULL)
				errx(1, "Can't fopen '%s'", config_filename);

			yylineno = 1;
			yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));

			BEGIN(INITIAL);
		}
	}
}
\n                  yylineno++;
%%

/* vim: set sw=4 sts=4 ts=4 : */
