From 6e78c2c8dfb69684adee82ee28b38c137156bca9 Mon Sep 17 00:00:00 2001 From: Jan Kneschke Date: Mon, 8 Aug 2005 14:40:47 +0000 Subject: [PATCH] user defined variable, compute on parsing: string+string, int+int, array+array, var+=expression. "include" sub configuration file. (merged ([308], [309], [306], [305]) git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-merge-1.4.x@520 152afb58-edef-0310-8abb-c4023f1b3aa9 --- configure.in | 6 +- doc/configuration.txt | 22 ++- doc/lighttpd.conf | 18 ++ src/array.c | 34 ++++ src/array.h | 3 + src/buffer.c | 6 + src/buffer.h | 1 + src/configfile-glue.c | 2 +- src/configfile.c | 312 ++++++++++++++++++++++------------ src/configfile.h | 4 + src/configparser.y | 230 ++++++++++++++++++++++--- src/data_array.c | 11 +- src/data_config.c | 11 ++ src/data_count.c | 11 ++ src/data_fastcgi.c | 10 ++ src/data_integer.c | 10 ++ src/data_string.c | 10 ++ src/server.c | 5 + tests/Makefile.am | 3 + tests/condition.conf | 16 +- tests/core-condition.t | 8 +- tests/core-var-include.t | 50 ++++++ tests/docroot/www/Makefile.am | 2 +- tests/prepare.sh | 1 + tests/var-include-sub.conf | 24 +++ tests/var-include.conf | 41 +++++ 26 files changed, 693 insertions(+), 158 deletions(-) create mode 100755 tests/core-var-include.t create mode 100644 tests/var-include-sub.conf create mode 100644 tests/var-include.conf diff --git a/configure.in b/configure.in index 1fd786bf..59d059d1 100644 --- a/configure.in +++ b/configure.in @@ -96,7 +96,11 @@ AC_ARG_WITH(mysql, if test \! -x $withval; then echo "--with-mysql=path-to-mysql_config" fi - MYSQL_INCLUDE="`$withval --cflags | sed s/\'//g`" + if $withval | grep -- '--include' ; then + MYSQL_INCLUDE="`$withval --include | sed s/\'//g`" + else + MYSQL_INCLUDE="`$withval --cflags | sed s/\'//g`" + fi MYSQL_LIBS="`$withval --libs | sed s/\'//g`" AC_MSG_RESULT(yes) diff --git a/doc/configuration.txt b/doc/configuration.txt index 685466a7..017c0d11 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -26,13 +26,15 @@ Basic Syntax A BNF like notation: :: - option : NAME = VARIABLE + option : NAME = VALUE + merge : NAME += VALUE NAME : modulename.key - VARIABLE : ( | | | ) + VALUE : ( | | | | VALUE [ + VALUE ]*) : "text" : digit* : ( "enable" | "disable" ) - : "(" [ "=>" ] [, [ "=>" ] ]* ")" + : "(" [ "=>" ] [, [ "=>" ] ]* ")" + INCLUDE : "include" VALUE Example ------- @@ -50,7 +52,13 @@ Example # enable directory listings server.dir-listing = "enable" - + + # variables, computed when config is read. + var.mymodule = "foo" + server.modules += ( "mod_" + var.mymodule ) + + # include, relative to dirname of main config file + include "mime.types.conf" Conditional Configuration @@ -63,6 +71,12 @@ Most options can be configured conditionally by using the following syntax { ... + { + ... nesting: match only when parent match + } + } + | { + ... else if } where is one of one of the following: diff --git a/doc/lighttpd.conf b/doc/lighttpd.conf index f2898cac..02464396 100644 --- a/doc/lighttpd.conf +++ b/doc/lighttpd.conf @@ -283,3 +283,21 @@ $HTTP["url"] =~ "\.pdf$" { ## don't forget to add index.cml to server.indexfiles # cml.extension = ".cml" # cml.memcache-hosts = ( "127.0.0.1:11211" ) + +#### variable usage: +## variable name without "." is auto prefixed by "var." and becomes "var.bar" +#bar = 1 +#var.mystring = "foo" + +## integer add +#bar += 1 +## string concat, with integer cast as string, result: "www.foo1.com" +#server.name = "www." + mystring + var.bar + ".com" +## array merge +#index-file.names = (foo + ".php") + index-file.names +#index-file.names += (foo + ".php") + +#### include +#include /etc/lighttpd/lighttpd-inc.conf +## same as above if you run: "lighttpd -f /etc/lighttpd/lighttpd.conf" +#include "lighttpd-inc.conf" diff --git a/src/array.c b/src/array.c index 0a1f081d..ac6737ed 100644 --- a/src/array.c +++ b/src/array.c @@ -20,6 +20,26 @@ array *array_init(void) { return a; } +array *array_init_array(array *src) { + size_t i; + array *a = array_init(); + + a->used = src->used; + a->size = src->size; + a->next_power_of_2 = src->next_power_of_2; + a->unique_ndx = src->unique_ndx; + + a->data = malloc(sizeof(*src->data) * src->size); + for (i = 0; i < src->size; i++) { + if (src->data[i]) a->data[i] = src->data[i]->copy(src->data[i]); + else a->data[i] = NULL; + } + + a->sorted = malloc(sizeof(*src->sorted) * src->size); + memcpy(a->sorted, src->sorted, sizeof(*src->sorted) * src->size); + return a; +} + void array_free(array *a) { size_t i; if (!a) return; @@ -122,6 +142,20 @@ data_unset *array_get_unused_element(array *a, data_type_t t) { return ds; } +/* replace or insert data, return the old one with the same key */ +data_unset *array_replace(array *a, data_unset *du) { + int ndx; + if (-1 == (ndx = array_get_index(a, du->key->ptr, du->key->used, NULL))) { + array_insert_unique(a, du); + return NULL; + } + else { + data_unset *old = a->data[ndx]; + a->data[ndx] = du; + return old; + } +} + int array_insert_unique(array *a, data_unset *str) { int ndx = -1; int pos = 0; diff --git a/src/array.h b/src/array.h index 37f21812..b444ca98 100644 --- a/src/array.h +++ b/src/array.h @@ -14,6 +14,7 @@ typedef enum { TYPE_UNSET, TYPE_STRING, TYPE_COUNT, TYPE_ARRAY, TYPE_INTEGER, TY #define DATA_UNSET \ data_type_t type; \ buffer *key; \ + struct data_unset *(*copy)(struct data_unset *src); \ void (* free)(struct data_unset *p); \ void (* reset)(struct data_unset *p); \ int (*insert_dup)(struct data_unset *dst, struct data_unset *src); \ @@ -120,6 +121,7 @@ typedef struct { data_fastcgi *data_fastcgi_init(void); array *array_init(void); +array *array_init_array(array *a); void array_free(array *a); void array_reset(array *a); int array_insert_unique(array *a, data_unset *str); @@ -127,6 +129,7 @@ data_unset *array_pop(array *a); int array_print(array *a, int depth); data_unset *array_get_unused_element(array *a, data_type_t t); data_unset *array_get_element(array *a, const char *key); +data_unset *array_replace(array *a, data_unset *du); int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len); void array_print_indent(int depth); diff --git a/src/buffer.c b/src/buffer.c index 5c943edb..6d86560c 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -29,6 +29,12 @@ buffer* buffer_init(void) { return b; } +buffer *buffer_init_buffer(buffer *src) { + buffer *b = buffer_init(); + buffer_copy_string_buffer(b, src); + return b; +} + /** * free the buffer * diff --git a/src/buffer.h b/src/buffer.h index 4204595a..3e27c31e 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -39,6 +39,7 @@ void buffer_array_reset(buffer_array *b); buffer *buffer_array_append_get_buffer(buffer_array *b); buffer* buffer_init(void); +buffer* buffer_init_buffer(buffer *b); buffer* buffer_init_string(const char *str); void buffer_free(buffer *b); void buffer_reset(buffer *b); diff --git a/src/configfile-glue.c b/src/configfile-glue.c index ffad77e2..067ac05c 100644 --- a/src/configfile-glue.c +++ b/src/configfile-glue.c @@ -153,7 +153,7 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat buffer *l; server_socket *srv_sock = con->srv_socket; /* check parent first */ - if (dc->parent) { + if (dc->parent && dc->parent->context_ndx) { if (con->conf.log_condition_handling) { log_error_write(srv, __FILE__, __LINE__, "sb", "go parent", dc->parent->string); } diff --git a/src/configfile.c b/src/configfile.c index 099e517c..747e14df 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -355,7 +355,9 @@ typedef struct { int foo; int bar; - char *input; + buffer *file; + stream s; + const char *input; size_t offset; size_t size; @@ -367,6 +369,42 @@ typedef struct { int in_cond; } tokenizer_t; +static int tokenizer_open(server *srv, tokenizer_t *t, buffer *basedir, const char *fn) { + if (buffer_is_empty(basedir) && + (fn[0] == '/' || fn[0] == '\\') && + (fn[0] == '.' && (fn[1] == '/' || fn[1] == '\\'))) { + t->file = buffer_init_string(fn); + } else { + t->file = buffer_init_buffer(basedir); + buffer_append_string(t->file, fn); + } + + if (0 != stream_open(&(t->s), t->file)) { + log_error_write(srv, __FILE__, __LINE__, "sbss", + "opening configfile ", t->file, "failed:", strerror(errno)); + buffer_free(t->file); + return -1; + } + + t->input = t->s.start; + t->offset = 0; + t->size = t->s.size; + t->line = 1; + t->line_pos = 1; + + t->in_key = 1; + t->in_brace = 0; + t->in_cond = 0; + return 0; +} + +static int tokenizer_close(server *srv, tokenizer_t *t) { + UNUSED(srv); + + buffer_free(t->file); + return stream_close(&(t->s)); +} + static int config_skip_newline(tokenizer_t *t) { int skipped = 1; assert(t->input[t->offset] == '\r' || t->input[t->offset] == '\n'); @@ -394,7 +432,7 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * for (tid = 0; tid == 0 && t->offset < t->size && t->input[t->offset] ; ) { char c = t->input[t->offset]; - char *start = NULL; + const char *start = NULL; switch (c) { case '=': @@ -406,7 +444,8 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * tid = TK_ARRAY_ASSIGN; } else { - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, "use => for assignments in arrays"); return -1; @@ -425,11 +464,14 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * tid = TK_MATCH; } else { - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, "only =~ and == are allow in the condition"); return -1; } + t->in_key = 1; + t->in_cond = 0; } else if (t->in_key) { tid = TK_ASSIGN; @@ -437,9 +479,9 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * t->offset++; t->line_pos++; - t->in_key = 0; } else { - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, "unexpected equal-sign: ="); return -1; @@ -461,13 +503,17 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * tid = TK_NOMATCH; } else { - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, "only !~ and != are allow in the condition"); return -1; } + t->in_key = 1; + t->in_cond = 0; } else { - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, "unexpected exclamation-marks: !"); return -1; @@ -512,7 +558,7 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * } else { config_skip_newline(t); t->line_pos = 1; - t->line++; + t->line++; } break; case ',': @@ -556,7 +602,8 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * if (t->input[t->offset + i] == '\0') { /* ERROR */ - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, "missing closing quote"); @@ -593,6 +640,20 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * buffer_copy_string(token, "$"); break; + + case '+': + if (t->input[t->offset + 1] == '=') { + t->offset += 2; + buffer_copy_string(token, "+="); + tid = TK_APPEND; + } + else { + t->offset++; + tid = TK_PLUS; + buffer_copy_string(token, "+"); + } + break; + case '|': t->offset++; tid = TK_OR; @@ -603,8 +664,6 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * t->offset++; tid = TK_LCURLY; - t->in_key = 1; - t->in_cond = 0; buffer_copy_string(token, "{"); @@ -641,28 +700,7 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * break; default: - if (t->in_key) { - /* the key might consist of [-.0-9a-z] */ - for (i = 0; t->input[t->offset + i] && - (isalnum((unsigned char)t->input[t->offset + i]) || - t->input[t->offset + i] == '.' || - t->input[t->offset + i] == '-' - ); i++); - - if (i && t->input[t->offset + i]) { - tid = TK_LKEY; - buffer_copy_string_len(token, t->input + t->offset, i); - - t->offset += i; - t->line_pos += i; - } else { - /* ERROR */ - log_error_write(srv, __FILE__, __LINE__, "sdsds", - "line:", t->line, "pos:", t->line_pos, - "invalid character in lvalue"); - return -1; - } - } else if (t->in_cond) { + if (t->in_cond) { for (i = 0; t->input[t->offset + i] && (isalpha((unsigned char)t->input[t->offset + i]) ); i++); @@ -675,40 +713,60 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * t->line_pos += i; } else { /* ERROR */ - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, "invalid character in condition"); return -1; } - } else { - if (isdigit((unsigned char)c)) { - /* take all digits */ - for (i = 0; t->input[t->offset + i] && isdigit((unsigned char)t->input[t->offset + i]); i++); + } else if (isdigit((unsigned char)c)) { + /* take all digits */ + for (i = 0; t->input[t->offset + i] && isdigit((unsigned char)t->input[t->offset + i]); i++); + + /* was there it least a digit ? */ + if (i && t->input[t->offset + i]) { + tid = TK_INTEGER; - /* was there it least a digit ? */ - if (i && t->input[t->offset + i]) { - tid = TK_INTEGER; - - buffer_copy_string_len(token, t->input + t->offset, i); - - t->offset += i; - t->line_pos += i; - } else { - /* ERROR */ - log_error_write(srv, __FILE__, __LINE__, "sdsds", - "line:", t->line, "pos:", t->line_pos, - "unexpected EOF"); - - return -1; - } + buffer_copy_string_len(token, t->input + t->offset, i); + + t->offset += i; + t->line_pos += i; } else { /* ERROR */ - log_error_write(srv, __FILE__, __LINE__, "sdsds", + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, "line:", t->line, "pos:", t->line_pos, - "invalid value field"); + "unexpected EOF"); return -1; } + } else { + /* the key might consist of [-.0-9a-z] */ + for (i = 0; t->input[t->offset + i] && + (isalnum((unsigned char)t->input[t->offset + i]) || + t->input[t->offset + i] == '.' || + t->input[t->offset + i] == '-' + ); i++); + + if (i && t->input[t->offset + i]) { + buffer_copy_string_len(token, t->input + t->offset, i); + + if (strcmp(token->ptr, "include") == 0) { + tid = TK_INCLUDE; + } else { + tid = TK_LKEY; + } + + t->offset += i; + t->line_pos += i; + } else { + /* ERROR */ + log_error_write(srv, __FILE__, __LINE__, "sbsdsds", + "file:", t->file, + "line:", t->line, "pos:", t->line_pos, + "invalid character in variable name"); + return -1; + } } break; } @@ -716,6 +774,11 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * if (tid) { *token_id = tid; +#if 0 + log_error_write(srv, __FILE__, __LINE__, "sbsdsdb", + "file:", t->file, + "line:", t->line, "pos:", t->line_pos, token); +#endif return 1; } else if (t->offset < t->size) { @@ -726,40 +789,88 @@ static int config_tokenizer(server *srv, tokenizer_t *t, int *token_id, buffer * return 0; } -int config_read(server *srv, const char *fn) { - stream s; +int config_parse_file(server *srv, config_t *context, const char *fn) { tokenizer_t t; void *pParser; int token_id; - buffer *token; - config_t context; - data_config *dc; + buffer *token, *lasttoken; int ret; - buffer *bfn = buffer_init_string(fn); - - if (0 != stream_open(&s, bfn)) { - buffer_free(bfn); - - log_error_write(srv, __FILE__, __LINE__, "ssss", - "opening configfile ", fn, "failed:", strerror(errno)); + + if (tokenizer_open(srv, &t, context->basedir, fn) == -1) { return -1; } - buffer_free(bfn); + pParser = configparserAlloc( malloc ); + lasttoken = buffer_init(); + token = buffer_init(); + while((1 == (ret = config_tokenizer(srv, &t, &token_id, token))) && context->ok) { + buffer_copy_string_buffer(lasttoken, token); + configparser(pParser, token_id, token, context); + + token = buffer_init(); + } + if (ret != -1 && context->ok) { + /* add an EOL at EOF, better than say sorry */ + buffer_copy_string(token, "(EOL)"); + configparser(pParser, TK_EOL, token, context); + token = buffer_init_string("(END)"); + if (context->ok) { + configparser(pParser, 0, token, context); + token = buffer_init(); + } + } + configparserFree(pParser, free); + buffer_free(token); + + if (ret == -1) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "configfile parser failed:", lasttoken); + } + else if (context->ok == 0) { + log_error_write(srv, __FILE__, __LINE__, "sbsdsdsb", + "file:", t.file, + "line:", t.line, "pos:", t.line_pos, + "parser failed somehow near here:", lasttoken); + ret = -1; + } + buffer_free(lasttoken); - t.input = s.start; - t.offset = 0; - t.size = s.size; - t.line = 1; - t.line_pos = 1; - - t.in_key = 1; - t.in_brace = 0; - t.in_cond = 0; - - context.ok = 1; + tokenizer_close(srv, &t); + return ret == -1 ? -1 : 0; +} + +static void context_init(server *srv, config_t *context) { + context->srv = srv; + context->ok = 1; + context->configs_stack = array_init(); + context->basedir = buffer_init(); +} + +static void context_free(config_t *context) { + array_free(context->configs_stack); + buffer_free(context->basedir); +} + +int config_read(server *srv, const char *fn) { + config_t context; + data_config *dc; + int ret; + char *pos; + + context_init(srv, &context); context.all_configs = srv->config_context; - context.configs_stack = array_init(); + + pos = strrchr(fn, +#ifdef __WIN32 + '\\' +#else + '/' +#endif + ); + if (pos) { + buffer_copy_string_len(context.basedir, fn, pos - fn + 1); + fn = pos + 1; + } dc = data_config_init(); buffer_copy_string(dc->key, "global"); @@ -768,39 +879,18 @@ int config_read(server *srv, const char *fn) { dc->context_ndx = context.all_configs->used; array_insert_unique(context.all_configs, (data_unset *)dc); context.current = dc; - + /* default context */ srv->config = dc->value; - pParser = configparserAlloc( malloc ); - token = buffer_init(); - while((1 == (ret = config_tokenizer(srv, &t, &token_id, token))) && context.ok) { - configparser(pParser, token_id, token, &context); - - token = buffer_init(); + ret = config_parse_file(srv, &context, fn); + + assert(0 != ret || context.configs_stack->used == 0); + context_free(&context); + + if (0 != ret) { + return ret; } - configparser(pParser, 0, token, &context); - configparserFree(pParser, free ); - - buffer_free(token); - - stream_close(&s); - - if (ret == -1) { - log_error_write(srv, __FILE__, __LINE__, "s", - "configfile parser failed"); - return -1; - } - - if (context.ok == 0) { - log_error_write(srv, __FILE__, __LINE__, "sdsds", - "line:", t.line, "pos:", t.line_pos, - "parser failed somehow near here"); - return -1; - } - - assert(context.configs_stack->used == 0); - array_free(context.configs_stack); if (0 != config_insert(srv)) { return -1; diff --git a/src/configfile.h b/src/configfile.h index 5f87d181..f8dc9bb2 100644 --- a/src/configfile.h +++ b/src/configfile.h @@ -3,16 +3,20 @@ #include "array.h" #include "buffer.h" +#include "server.h" typedef struct { + server *srv; int ok; array *all_configs; array *configs_stack; /* to parse nested block */ data_config *current; /* current started with { */ + buffer *basedir; } config_t; void *configparserAlloc(void *(*mallocProc)(size_t)); void configparserFree(void *p, void (*freeProc)(void*)); void configparser(void *yyp, int yymajor, buffer *yyminor, config_t *ctx); +int config_parse_file(server *srv, config_t *context, const char *fn); #endif diff --git a/src/configparser.y b/src/configparser.y index d88cc240..08f496ba 100644 --- a/src/configparser.y +++ b/src/configparser.y @@ -6,6 +6,7 @@ %include { #include #include +#include #include "config.h" #include "configfile.h" #include "buffer.h" @@ -14,7 +15,9 @@ static void configparser_push(config_t *ctx, data_config *dc, int isnew) { if (isnew) { dc->context_ndx = ctx->all_configs->used; + assert(dc->context_ndx > ctx->current->context_ndx); array_insert_unique(ctx->all_configs, (data_unset *)dc); + dc->parent = ctx->current; } array_insert_unique(ctx->configs_stack, (data_unset *)ctx->current); ctx->current = dc; @@ -26,6 +29,85 @@ static data_config *configparser_pop(config_t *ctx) { return old; } +data_unset *configparser_get_variable(config_t *ctx, buffer *key) { + data_unset *ds, *result; + data_config *dc; + + result = NULL; +#if 0 + fprintf(stderr, "get var %s\n", key->ptr); +#endif + for (dc = ctx->current; dc && !result; dc = dc->parent) { +#if 0 + fprintf(stderr, "get var on block: %s\n", dc->key->ptr); +#endif + ds = array_get_element(dc->value, key->ptr); + if (NULL != ds) { + result = ds; + break; + } + } + if (NULL == result) { + fprintf(stderr, "Undefined config variable: %s\n", key->ptr); + ctx->ok = 0; + } + return result; +} + +/* op1 is to be eat/return by this function, op1->key is not cared + op2 is left untouch, unreferenced + */ +data_unset *configparser_merge_data(config_t *ctx, data_unset *op1, const data_unset *op2) { + /* type mismatch */ + if (op1->type != op2->type) { + if (op1->type == TYPE_STRING && op2->type == TYPE_INTEGER) { + data_string *ds = (data_string *)op1; + buffer_append_long(ds->value, ((data_integer*)op2)->value); + return op1; + } + else if (op1->type == TYPE_INTEGER && op2->type == TYPE_STRING) { + data_string *ds = data_string_init(); + buffer_append_long(ds->value, ((data_integer*)op1)->value); + buffer_append_string_buffer(ds->value, ((data_string*)op2)->value); + op1->free(op1); + return (data_unset *)ds; + } + else { + fprintf(stderr, "data type mismatch, cannot be merge\n"); + ctx->ok = 0; + op1->free(op1); + return NULL; + } + } + + switch (op1->type) { + case TYPE_STRING: + buffer_append_string_buffer(((data_string *)op1)->value, ((data_string *)op2)->value); + break; + case TYPE_INTEGER: + ((data_integer *)op1)->value += ((data_integer *)op2)->value; + break; + case TYPE_ARRAY: { + array *dst = ((data_array *)op1)->value; + array *src = ((data_array *)op2)->value; + data_unset *du; + size_t i; + + for (i = 0; i < src->used; i ++) { + du = (data_unset *)src->data[i]; + if (du) { + array_insert_unique(dst, du->copy(du)); + } + } + break; + default: + assert(0); + break; + } + } + return op1; +} + } %parse_failure { @@ -37,9 +119,12 @@ metalines ::= metalines metaline. metalines ::= . metaline ::= varline. metaline ::= condlines EOL. +metaline ::= include. metaline ::= EOL. %type value {data_unset *} +%type expression {data_unset *} +%type context_rvalue {data_unset *} %type aelement {data_unset *} %type aelements {array *} %type array {array *} @@ -48,7 +133,7 @@ metaline ::= EOL. %type cond {config_cond_t } %token_destructor { buffer_free($$); } -varline ::= key(A) ASSIGN value(B). { +varline ::= key(A) ASSIGN expression(B). { buffer_copy_string_buffer(B->key, A); if (NULL == array_get_element(ctx->current->value, B->key->ptr)) { array_insert_unique(ctx->current->value, B); @@ -61,11 +146,63 @@ varline ::= key(A) ASSIGN value(B). { buffer_free(A); } +varline ::= key(A) APPEND expression(B). { + array *vars = ctx->current->value; + data_unset *du; + + if (NULL == (du = configparser_get_variable(ctx, A))) { + fprintf(stderr, "Undefined config variable in conditional 1 %s: %s\n", + ctx->current->key->ptr, A->ptr); + ctx->ok = 0; + } else if (NULL != (du = array_get_element(vars, A->ptr))) { + /* exists in current block */ + du = configparser_merge_data(ctx, du, B); + buffer_copy_string_buffer(du->key, A); + array_replace(vars, du); + } else { + du = configparser_merge_data(ctx, du->copy(du), B); + buffer_copy_string_buffer(du->key, A); + array_insert_unique(ctx->current->value, du); + } + buffer_free(A); + A = NULL; + B->free(B); + B = NULL; +} + key(A) ::= LKEY(B). { + if (strchr(B->ptr, '.') == NULL) { + A = buffer_init_string("var."); + buffer_append_string_buffer(A, B); + } + else { + A = B; + B = NULL; + } +} + +expression(A) ::= expression(B) PLUS value(C). { + A = configparser_merge_data(ctx, B, C); + B = NULL; + C->free(C); + C = NULL; +} + +expression(A) ::= value(B). { A = B; B = NULL; } +value(A) ::= key(B). { + A = configparser_get_variable(ctx, B); + if (!A) { + /* make a dummy so it won't crash */ + A = (data_unset *)data_string_init(); + } + buffer_free(B); + B = NULL; +} + value(A) ::= STRING(B). { A = (data_unset *)data_string_init(); buffer_copy_string_buffer(((data_string *)(A))->value, B); @@ -110,11 +247,11 @@ aelements(A) ::= aelement(B). { array_insert_unique(A, B); } -aelement(A) ::= value(B). { +aelement(A) ::= expression(B). { A = B; B = NULL; } -aelement(A) ::= STRING(B) ARRAY_ASSIGN value(C). { +aelement(A) ::= STRING(B) ARRAY_ASSIGN expression(C). { buffer_copy_string_buffer(C->key, B); buffer_free(B); @@ -140,32 +277,49 @@ condlines(A) ::= condline(B). { } condline(A) ::= context LCURLY metalines RCURLY. { - data_config *parent, *cur; + data_config *cur; cur = ctx->current; configparser_pop(ctx); - parent = ctx->current; - assert(cur && parent); + assert(cur && ctx->current); - if (0 != parent->context_ndx) { /* not global */ - assert(cur->context_ndx > parent->context_ndx); - cur->parent = parent; - } A = cur; } -context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) STRING(D). { +context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) expression(D). { data_config *dc; - buffer *b; - + buffer *b, *rvalue; + + if (ctx->ok && D->type != TYPE_STRING) { + fprintf(stderr, "rvalue must be string"); + ctx->ok = 0; + } + b = buffer_init(); buffer_copy_string_buffer(b, ctx->current->key); buffer_append_string(b, "/"); buffer_append_string_buffer(b, B); buffer_append_string_buffer(b, C); - buffer_append_string_buffer(b, D); - buffer_append_long(b, E); + switch(E) { + case CONFIG_COND_NE: + buffer_append_string_len(b, CONST_STR_LEN("!=")); + break; + case CONFIG_COND_EQ: + buffer_append_string_len(b, CONST_STR_LEN("==")); + break; + case CONFIG_COND_NOMATCH: + buffer_append_string_len(b, CONST_STR_LEN("!~")); + break; + case CONFIG_COND_MATCH: + buffer_append_string_len(b, CONST_STR_LEN("=~")); + break; + default: + buffer_append_string_len(b, CONST_STR_LEN("??")); + break; + } + rvalue = ((data_string*)D)->value; + buffer_append_string_buffer(b, rvalue); if (NULL != (dc = (data_config *)array_get_element(ctx->all_configs, b->ptr))) { configparser_push(ctx, dc, 0); @@ -180,7 +334,7 @@ context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) STRING(D). switch(E) { case CONFIG_COND_NE: case CONFIG_COND_EQ: - dc->string = buffer_init_string(D->ptr); + dc->string = buffer_init_buffer(rvalue); break; case CONFIG_COND_NOMATCH: case CONFIG_COND_MATCH: { @@ -189,16 +343,22 @@ context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) STRING(D). int erroff; if (NULL == (dc->regex = - pcre_compile(D->ptr, 0, &errptr, &erroff, NULL))) { - dc->string = buffer_init_string(errptr); - dc->cond = CONFIG_COND_UNSET; - - ctx->ok = 0; - } else if (NULL == (dc->regex_study = pcre_study(dc->regex, 0, &errptr)) && + pcre_compile(rvalue->ptr, 0, &errptr, &erroff, NULL))) { + dc->string = buffer_init_string(errptr); + dc->cond = CONFIG_COND_UNSET; + + fprintf(stderr, "parsing regex failed: %s -> %s at offset %d\n", + rvalue->ptr, errptr, erroff); + + ctx->ok = 0; + } else if (NULL == (dc->regex_study = + pcre_study(dc->regex, 0, &errptr)) && errptr != NULL) { fprintf(stderr, "studying regex failed: %s -> %s\n", - D->ptr, errptr); - ctx->ok = 0; + rvalue->ptr, errptr); + ctx->ok = 0; + } else { + dc->string = buffer_init_buffer(rvalue); } #else fprintf(stderr, "regex conditionals are not allowed as pcre-support" \ @@ -218,10 +378,10 @@ context ::= DOLLAR SRVVARNAME(B) LBRACKET STRING(C) RBRACKET cond(E) STRING(D). configparser_push(ctx, dc, 1); } + buffer_free(b); - buffer_free(B); - buffer_free(C); - buffer_free(D); + D->free(D); + D = NULL; } cond(A) ::= EQ. { A = CONFIG_COND_EQ; @@ -235,3 +395,19 @@ cond(A) ::= NE. { cond(A) ::= NOMATCH. { A = CONFIG_COND_NOMATCH; } + +include ::= INCLUDE expression(A). { + if (ctx->ok) { + if (A->type != TYPE_STRING) { + fprintf(stderr, "file must be string"); + ctx->ok = 0; + } else { + buffer *file = ((data_string*)A)->value; + if (0 != config_parse_file(ctx->srv, ctx, file->ptr)) { + ctx->ok = 0; + } + } + A->free(A); + } + A = NULL; +} diff --git a/src/data_array.c b/src/data_array.c index 6368f227..b916f467 100644 --- a/src/data_array.c +++ b/src/data_array.c @@ -4,6 +4,15 @@ #include "array.h" +static data_unset *data_array_copy(data_unset *s) { + data_array *src = (data_array *)s; + data_array *ds = data_array_init(); + + ds->key = buffer_init_buffer(src->key); + ds->value = array_init_array(src->value); + return (data_unset *)ds; +} + static void data_array_free(data_unset *d) { data_array *ds = (data_array *)d; @@ -39,7 +48,6 @@ static void data_array_print(data_unset *d, int depth) { fprintf(stderr, "}"); } - data_array *data_array_init(void) { data_array *ds; @@ -48,6 +56,7 @@ data_array *data_array_init(void) { ds->key = buffer_init(); ds->value = array_init(); + ds->copy = data_array_copy; ds->free = data_array_free; ds->reset = data_array_reset; ds->insert_dup = data_array_insert_dup; diff --git a/src/data_config.c b/src/data_config.c index 0ed85686..788c86c6 100644 --- a/src/data_config.c +++ b/src/data_config.c @@ -4,6 +4,16 @@ #include "array.h" +static data_unset *data_config_copy(data_unset *s) { + data_config *src = (data_config *)s; + data_config *ds = data_config_init(); + + ds->key = buffer_init_buffer(src->key); + ds->comp_key = buffer_init_buffer(src->comp_key); + ds->value = array_init_array(src->value); + return (data_unset *)ds; +} + static void data_config_free(data_unset *d) { data_config *ds = (data_config *)d; @@ -58,6 +68,7 @@ data_config *data_config_init(void) { ds->comp_key = buffer_init(); ds->value = array_init(); + ds->copy = data_config_copy; ds->free = data_config_free; ds->reset = data_config_reset; ds->insert_dup = data_config_insert_dup; diff --git a/src/data_count.c b/src/data_count.c index 20305b25..5287a009 100644 --- a/src/data_count.c +++ b/src/data_count.c @@ -4,6 +4,15 @@ #include "array.h" +static data_unset *data_count_copy(data_unset *s) { + data_count *src = (data_count *)s; + data_count *ds = data_count_init(); + + ds->key = buffer_init_buffer(src->key); + ds->count = src->count; + return (data_unset *)ds; +} + static void data_count_free(data_unset *d) { data_count *ds = (data_count *)d; @@ -47,6 +56,8 @@ data_count *data_count_init(void) { ds->key = buffer_init(); ds->count = 1; + ds->copy = data_count_copy; + ds->copy = data_count_copy; ds->free = data_count_free; ds->reset = data_count_reset; ds->insert_dup = data_count_insert_dup; diff --git a/src/data_fastcgi.c b/src/data_fastcgi.c index 617b611f..d9f24bb0 100644 --- a/src/data_fastcgi.c +++ b/src/data_fastcgi.c @@ -5,6 +5,15 @@ #include "array.h" #include "fastcgi.h" +static data_unset *data_fastcgi_copy(data_unset *s) { + data_fastcgi *src = (data_fastcgi *)s; + data_fastcgi *ds = data_fastcgi_init(); + + ds->key = buffer_init_buffer(src->key); + ds->host = buffer_init_buffer(src->host); + return (data_unset *)ds; +} + static void data_fastcgi_free(data_unset *d) { data_fastcgi *ds = (data_fastcgi *)d; @@ -48,6 +57,7 @@ data_fastcgi *data_fastcgi_init(void) { ds->port = 0; ds->is_disabled = 0; + ds->copy = data_fastcgi_copy; ds->free = data_fastcgi_free; ds->reset = data_fastcgi_reset; ds->insert_dup = data_fastcgi_insert_dup; diff --git a/src/data_integer.c b/src/data_integer.c index ea627cc6..b938ff70 100644 --- a/src/data_integer.c +++ b/src/data_integer.c @@ -4,6 +4,15 @@ #include "array.h" +static data_unset *data_integer_copy(data_unset *s) { + data_integer *src = (data_integer *)s; + data_integer *ds = data_integer_init(); + + ds->key = buffer_init_buffer(src->key); + ds->value = src->value; + return (data_unset *)ds; +} + static void data_integer_free(data_unset *d) { data_integer *ds = (data_integer *)d; @@ -44,6 +53,7 @@ data_integer *data_integer_init(void) { ds->key = buffer_init(); ds->value = 0; + ds->copy = data_integer_copy; ds->free = data_integer_free; ds->reset = data_integer_reset; ds->insert_dup = data_integer_insert_dup; diff --git a/src/data_string.c b/src/data_string.c index f8226923..b35b437b 100644 --- a/src/data_string.c +++ b/src/data_string.c @@ -5,6 +5,15 @@ #include "array.h" +static data_unset *data_string_copy(data_unset *s) { + data_string *src = (data_string *)s; + data_string *ds = data_string_init(); + + ds->key = buffer_init_buffer(src->key); + ds->value = buffer_init_buffer(src->value); + return (data_unset *)ds; +} + static void data_string_free(data_unset *d) { data_string *ds = (data_string *)d; @@ -74,6 +83,7 @@ data_string *data_string_init(void) { ds->key = buffer_init(); ds->value = buffer_init(); + ds->copy = data_string_copy; ds->free = data_string_free; ds->reset = data_string_reset; ds->insert_dup = data_string_insert_dup; diff --git a/src/server.c b/src/server.c index 606a8ef2..65afd6dd 100644 --- a/src/server.c +++ b/src/server.c @@ -629,6 +629,11 @@ int main (int argc, char **argv) { for (i = 0; srv->config && i < srv->config->used; i++) { data_unset *du = srv->config->data[i]; + /* all var.* is known as user defined variable */ + if (strncmp(du->key->ptr, "var.", sizeof("var.") - 1) == 0) { + continue; + } + if (NULL == array_get_element(srv->config_touched, du->key->ptr)) { log_error_write(srv, __FILE__, __LINE__, "sbs", "WARNING: unknown config-key:", diff --git a/tests/Makefile.am b/tests/Makefile.am index da190ae0..e3f387ac 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -22,6 +22,9 @@ CONFS=fastcgi-10.conf \ fastcgi-13.conf \ bug-06.conf \ bug-12.conf \ + core-var-include.t \ + var-include.conf \ + var-include-sub.conf \ condition.conf \ core-condition.t \ core-request.t \ diff --git a/tests/condition.conf b/tests/condition.conf index f5947cf6..7e34e493 100644 --- a/tests/condition.conf +++ b/tests/condition.conf @@ -16,7 +16,7 @@ server.tag = "Apache 1.3.29" server.modules = ( - "mod_access", + "mod_redirect", "mod_accesslog" ) ######################## MODULE CONFIG ############################ @@ -26,23 +26,23 @@ accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" mimetype.assign = ( ".html" => "text/html" ) -# ban first, unban later -url.access-deny = ( "index.html" ) +url.redirect = ("^" => "/default") $HTTP["host"] == "www.example.org" { server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" server.name = "www.example.org" + url.redirect = ("^" => "/match_1") } | $HTTP["host"] == "test1.example.org" { server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" server.name = "test1.example.org" - url.access-deny = ( "nothing" ) + url.redirect = ("^" => "/match_2") } # comments | $HTTP["host"] == "test2.example.org" { server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" server.name = "test2.example.org" - url.access-deny = ( "nothing" ) + url.redirect = ("^" => "/match_3") } # comments @@ -50,10 +50,10 @@ $HTTP["host"] == "www.example.org" { | $HTTP["host"] == "test3.example.org" { server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" server.name = "test3.example.org" - # comments - url.access-deny = ( "nothing" ) + url.redirect = ("^" => "/match_4") + # comments $HTTP["url"] == "/index.html" { - url.access-deny = ( "index.html" ) + url.redirect = ("^" => "/match_5") } } diff --git a/tests/core-condition.t b/tests/core-condition.t index 933f566c..1fc00b5c 100755 --- a/tests/core-condition.t +++ b/tests/core-condition.t @@ -22,7 +22,7 @@ GET /index.html HTTP/1.0 Host: www.example.org EOF ); -$t->{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ); +$t->{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => "/match_1" } ); ok($tf->handle_http($t) == 0, 'config deny'); $t->{REQUEST} = ( <{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ); +$t->{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => "/match_2" } ); ok($tf->handle_http($t) == 0, '2nd child of chaining'); $t->{REQUEST} = ( <{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ); +$t->{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => "/match_3" } ); ok($tf->handle_http($t) == 0, '3rd child of chaining'); $t->{REQUEST} = ( <{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ); +$t->{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => "/match_5" } ); ok($tf->handle_http($t) == 0, 'nesting'); ok($tf->stop_proc == 0, "Stopping lighttpd"); diff --git a/tests/core-var-include.t b/tests/core-var-include.t new file mode 100755 index 00000000..2dc17e77 --- /dev/null +++ b/tests/core-var-include.t @@ -0,0 +1,50 @@ +#! /usr/bin/perl -w +BEGIN { + # add current source dir to the include-path + # we need this for make distcheck + (my $srcdir = $0) =~ s#/[^/]+$#/#; + unshift @INC, $srcdir; +} + +use strict; +use IO::Socket; +use Test::More tests => 16; +use LightyTest; + +my $tf = LightyTest->new(); +my $t; + +$tf->{CONFIGFILE} = 'var-include.conf'; + +ok($tf->start_proc == 0, "Starting lighttpd") or die(); + +$t->{REQUEST} = ( "GET /index.html HTTP/1.0\r\nHost: www.example.org\r\n" ); +$t->{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => "/redirect" } ); +ok($tf->handle_http($t) == 0, 'basic test'); + +my $myvar = "good"; +my $server_name = "test.example.org"; +my $mystr = "string"; +$mystr .= "_append"; +my $tests = { + "include" => "/good_include", + "concat" => "/good_" . "concat", + "servername1" => "/good_" . $server_name, + "servername2" => $server_name . "/good_", + "servername3" => "/good_" . $server_name . "/", + "var.myvar" => "/good_var_myvar" . $myvar, + "myvar" => "/good_myvar" . $myvar, + "number1" => "/good_number" . "1", + "number2" => "1" . "/good_number", + "array_append" => "/good_array_append", + "string_append" => "/good_" . $mystr, + "number_append" => "/good_" . "2" +}; +foreach my $test (keys %{ $tests }) { + my $expect = $tests->{$test}; + $t->{REQUEST} = ( "GET /$test HTTP/1.0\r\nHost: $server_name\r\n" ); + $t->{RESPONSE} = ( { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 301, 'Location' => $expect } ); + ok($tf->handle_http == 0, $test); +} + +ok($tf->stop_proc == 0, "Stopping lighttpd"); diff --git a/tests/docroot/www/Makefile.am b/tests/docroot/www/Makefile.am index 3ac0106e..0b9af0b8 100644 --- a/tests/docroot/www/Makefile.am +++ b/tests/docroot/www/Makefile.am @@ -1,4 +1,4 @@ EXTRA_DIST=cgi.php cgi.pl dummydir index.html index.txt phpinfo.php \ - phpself.php redirect.php cgi-pathinfo.pl phphost.php \ + phpself.php redirect.php cgi-pathinfo.pl phphost.php pathinfo.php \ nph-status.pl prefix.fcgi SUBDIRS=go indexfile expire diff --git a/tests/prepare.sh b/tests/prepare.sh index a12bddf7..ba86cf69 100755 --- a/tests/prepare.sh +++ b/tests/prepare.sh @@ -32,6 +32,7 @@ cp $srcdir/docroot/123/*.txt \ $srcdir/docroot/123/*.php \ $srcdir/docroot/123/*.bla $tmpdir/servers/123.example.org/pages/ cp $srcdir/lighttpd.user $tmpdir/ +cp $srcdir/var-include-sub.conf /tmp/ printf "%-40s" "preparing infrastructure" diff --git a/tests/var-include-sub.conf b/tests/var-include-sub.conf new file mode 100644 index 00000000..4ec85d35 --- /dev/null +++ b/tests/var-include-sub.conf @@ -0,0 +1,24 @@ +# file to be included +$HTTP["host"] =~ "^" + server.name + "$" { + url.redirect = ( + "^/include$" => "/good_include", + "^/concat$" => "/good_" + "concat", + "^/servername1$" => "/good_" + server.name, + "^/servername2$" => server.name + "/good_", + "^/servername3$" => "/good_" + server.name + "/", + "^/var.myvar$" => "/good_var_myvar" + var.myvar, + "^/myvar$" => "/good_myvar" + myvar, + "^/number1$" => "/good_number" + one, + "^/number2$" => one + "/good_number", + ) + # without var prefix + mystr = "string" + mystr += "_append" + # from parent + one += 1 + url.redirect += ( + "^/array_append$" => "/good_array_append", + "^/string_append$" => "/good_" + mystr, + "^/number_append$" => "/good_" + one, + ) +} diff --git a/tests/var-include.conf b/tests/var-include.conf new file mode 100644 index 00000000..ab8d22c6 --- /dev/null +++ b/tests/var-include.conf @@ -0,0 +1,41 @@ + +debug.log-request-handling = "enable" +debug.log-condition-handling = "enable" + +server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" +server.pid-file = "/tmp/lighttpd/lighttpd.pid" + +## bind to port (default: 80) +server.port = 2048 + +## bind to localhost (default: all interfaces) +server.bind = "localhost" +server.errorlog = "/tmp/lighttpd/logs/lighttpd.error.log" +server.name = "www.example.org" +server.tag = "Apache 1.3.29" + + +server.modules = ( "mod_redirect", + "mod_accesslog" ) + +######################## MODULE CONFIG ############################ + + +accesslog.filename = "/tmp/lighttpd/logs/lighttpd.access.log" + +mimetype.assign = ( ".html" => "text/html" ) + +url.redirect = ("^" => "/default") + +$HTTP["host"] == "www.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "www.example.org" + url.redirect = ("^" => "/redirect") +} +$HTTP["host"] == "test.example.org" { + server.document-root = "/tmp/lighttpd/servers/www.example.org/pages/" + server.name = "test.example.org" + var.myvar = "good" + var.one = 1 + include "var-include-sub.conf" +}