lighttpd 1.4.x https://www.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

773 lines
16 KiB

#include <sys/stat.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include "buffer.h"
#include "server.h"
#include "log.h"
#include "plugin.h"
#include "mod_cml.h"
#include "stream.h"
tnode_val *tnode_val_init() {
tnode_val *tv;
tv = calloc(1, sizeof(*tv));
assert(tv);
return tv;
}
int tnode_val_move(tnode_val *dst, tnode_val *src) {
switch(src->type) {
case T_NODE_VALUE_LONG:
dst->data.lon = src->data.lon;
break;
case T_NODE_VALUE_STRING:
dst->data.str = src->data.str;
src->data.str = NULL;
break;
default:
break;
}
dst->type = src->type;
src->type = UNSET;
return 0;
}
void tnode_val_free(tnode_val *tv) {
switch(tv->type) {
case T_NODE_VALUE_STRING:
buffer_free(tv->data.str);
break;
default:
break;
}
free(tv);
}
tnode_val_array *tnode_val_array_init() {
tnode_val_array *tva;
tva = calloc(1, sizeof(*tva));
assert(tva);
return tva;
}
int tnode_val_array_append(tnode_val_array *tva, tnode_val *val) {
if (tva->size == 0) {
tva->size = 4;
tva->ptr = malloc(tva->size * sizeof(tnode_val *));
} else if (tva->size == tva->used) {
tva->size += 4;
tva->ptr = realloc(tva->ptr, tva->size * sizeof(tnode_val *));
}
tva->ptr[tva->used++] = val;
return 0;
}
void tnode_val_array_free(tnode_val_array *tva) {
size_t i;
if (!tva) return;
for (i = 0; i < tva->used; i++) {
tnode_val_free(tva->ptr[i]);
}
free(tva->ptr);
free(tva);
}
tnode *tnode_init() {
tnode *t;
t = calloc(1, sizeof(*t));
assert(t);
return t;
}
void tnode_free(tnode *t ) {
if (IS_STRING(t)) {
buffer_free(t->value.data.str);
}
if (t->r) tnode_free(t->r);
if (t->l) tnode_free(t->l);
free(t);
}
int tnode_prepare_long(tnode *t) {
switch (t->value.type) {
case T_NODE_VALUE_STRING:
buffer_free(t->value.data.str);
break;
default:
break;
}
t->value.type = T_NODE_VALUE_LONG;
return 0;
}
int tnode_prepare_string(tnode *t) {
switch (t->value.type) {
case T_NODE_VALUE_STRING:
buffer_reset(t->value.data.str);
break;
default:
t->value.data.str = buffer_init();
break;
}
t->value.type = T_NODE_VALUE_STRING;
return 0;
}
int cache_trigger_parse(server *srv, connection *con, plugin_data *p, buffer *t /* trigger */, tnode *n) {
size_t i, pre = 0, post = 0;
int braces = 0;
int quotes = 0;
/*
* unix.time.now - file.mtime("head.html") > 30
*/
cache_trigger_functions f[] = {
{ "file.mtime", 1, f_file_mtime },
{ "unix.time.now", 0, f_unix_time_now },
{ "mysql.escape", 1, f_mysql_escape },
{ "mysql.connect", 4, f_mysql_connect },
{ "mysql.query", 1, f_mysql_query },
{ NULL, 0, NULL },
};
if (t->used == 0) {
log_error_write(srv, __FILE__, __LINE__, "s", "empty term");
return -1;
}
n->op = UNSET;
/* search for the highest op */
for (i = 0; i < t->used; i++) {
switch(t->ptr[i]) {
case '\\':
switch (t->ptr[i+1]) {
case '"': if (quotes) i++; break;
}
break;
case '"':
quotes = quotes ? 0 : 1;
break;
}
if (quotes) continue;
switch(t->ptr[i]) {
case '(': braces++; break;
case ')': braces--;
if (braces < 0) {
log_error_write(srv, __FILE__, __LINE__, "ss", "braces don't match:", t->ptr);
return -1;
}
break;
}
if (braces) continue;
switch(t->ptr[i]) {
case '-': if (MINUS >= n->op) { n->op = MINUS; pre = i - 1; post = i + 1; } break;
case '+': if (PLUS >= n->op) { n->op = PLUS; pre = i - 1; post = i + 1; } break;
case '*': if (TIMES >= n->op) { n->op = TIMES; pre = i - 1; post = i + 1; } break;
case '/': if (PART >= n->op) { n->op = PART; pre = i - 1; post = i + 1; } break;
case '>':
switch(t->ptr[i+1]) {
case '=':
if (GE > n->op) { n->op = GE; pre = i - 1; post = i + 2; }
i++;
break;
default:
if (GT > n->op) { n->op = GT; pre = i - 1; post = i + 1; }
break;
}
break;
case '<':
switch(t->ptr[i+1]) {
case '=':
if (LE > n->op) { n->op = LE; pre = i - 1; post = i + 2; }
i++;
break;
default:
if (LT > n->op) { n->op = LT; pre = i - 1; post = i + 1; }
break;
}
break;
case '!':
switch(t->ptr[i+1]) {
case '=':
if (NE > n->op) { n->op = NE; pre = i - 1; post = i + 2; }
i++;
break;
}
break;
case '&':
switch(t->ptr[i+1]) {
case '&':
if (AND > n->op) { n->op = AND; pre = i - 1; post = i + 2; }
i++;
break;
}
break;
case '|':
switch(t->ptr[i+1]) {
case '|':
if (OR > n->op) { n->op = OR; pre = i - 1; post = i + 2; }
i++;
break;
}
break;
case '=': if (EQ > n->op) { n->op = EQ; pre = i - 1; post = i + 1; } break;
}
}
if (braces != 0) {
log_error_write(srv, __FILE__, __LINE__, "ss", "braces don't match:", t->ptr);
return -1;
}
if (quotes != 0) {
log_error_write(srv, __FILE__, __LINE__, "ss", "quotes don't match:", t->ptr);
return -1;
}
if (n->op != UNSET) {
buffer *b;
b = buffer_init();
/* strip spaces */
for (i = pre; t->ptr[i] == ' ' || t->ptr[i] == '\t'; i--);
buffer_copy_string_len(b, t->ptr, i + 1);
if (n->l == NULL) n->l = tnode_init();
if (-1 == cache_trigger_parse(srv, con, p, b, n->l)) {
buffer_free(b);
return -1;
}
for (i = post; t->ptr[i] == ' ' || t->ptr[i] == '\t'; i++);
buffer_copy_string_len(b, t->ptr + i, t->used - i - 1);
if (n->r == NULL) n->r = tnode_init();
if (-1 == cache_trigger_parse(srv, con, p, b, n->r)) {
buffer_free(b);
return -1;
}
buffer_free(b);
} else {
char *br_open;
if (t->ptr[0] == '(' &&
t->ptr[t->used - 2] == ')') {
buffer *b;
b = buffer_init();
buffer_copy_string_len(b, t->ptr + 1, t->used - 3);
if (-1 == cache_trigger_parse(srv, con, p, b, n)) {
buffer_free(b);
return -1;
}
buffer_free(b);
return 0;
} else if (t->ptr[0] == '"' &&
t->ptr[t->used - 2] == '"') {
/* a string */
tnode_prepare_string(n);
buffer_copy_string_len(n->value.data.str, t->ptr + 1, t->used - 3);
return 0;
} else if (NULL != (br_open = strchr(t->ptr, '(')) &&
t->ptr[t->used - 2] == ')') {
/* no basic op, perhaps a function */
for (i = 0; f[i].name; i++) {
size_t slen;
slen = br_open - t->ptr;
if ((strlen(f[i].name) == slen) &&
(0 == strncmp(f[i].name, t->ptr, slen))) {
/* we know the function */
/* parse parameters */
if (0 != cache_parse_parameters(srv, con, p, br_open + 1, t->used - slen - 3, p->params)) {
fprintf(stderr, "%s.%d: parsing parameters failed\n",
__FILE__, __LINE__
);
return -1;
}
if (p->params->used != f[i].params) {
fprintf(stderr, "%s.%d: wrong param-count for %s: %d, expected %d\n",
__FILE__, __LINE__,
f[i].name,
p->params->used,
f[i].params
);
return -1;
}
if (0 != f[i].func(srv, con, p, n)) {
fprintf(stderr, "%s.%d: function %s failed\n", __FILE__, __LINE__, f[i].name);
return -1;
}
return 0;
}
}
} else {
char *err;
tnode_prepare_long(n);
VAL_LONG(n) = strtol(t->ptr, &err, 10);
if (*err != '\0') {
/* isn't a int */
fprintf(stderr, "%s.%d: can't evaluate: '%s', '%s'\n",
__FILE__, __LINE__,
t->ptr, err);
return -1;
}
}
}
return 0;
}
int cache_ops_long(tnode *res, tnode *l, tnode *r) {
if (!IS_LONG(l) || !IS_LONG(r)) {
return -1;
}
#define OP1(x, y) \
case x: \
VAL_LONG(res) = (VAL_LONG(l) y VAL_LONG(r)); \
break;
#define OP2(x, y) \
case x: \
VAL_LONG(res) = (VAL_LONG(l) y VAL_LONG(r)); \
break;
switch(res->op) {
OP1(MINUS, -);
OP1(PLUS, +);
OP1(TIMES, *);
OP1(PART, /);
OP2(GT, >);
OP2(LT, <);
OP2(EQ, ==);
OP2(NE, !=);
OP2(LE, >=);
OP2(GE, <=);
OP2(AND, &&);
OP2(OR, ||);
default:
return -1;
}
#undef OP1
#undef OP2
tnode_prepare_long(res);
res->value.type = T_NODE_VALUE_LONG;
res->op = UNSET;
return 0;
}
int cache_ops_string(tnode *res, tnode *l, tnode *r) {
if (!IS_STRING(l) || !IS_STRING(r)) {
return -1;
}
switch(res->op) {
case PLUS:
tnode_prepare_string(res);
buffer_copy_string_buffer(VAL_STRING(res), VAL_STRING(l));
buffer_append_string_buffer(VAL_STRING(res), VAL_STRING(r));
res->value.type = T_NODE_VALUE_STRING;
res->op = UNSET;
break;
default:
return -1;
}
return 0;
}
int cache_trigger_eval(server *srv, connection *con, plugin_data *p, tnode *t) {
if (t->op == UNSET) {
/* a value */
return 0;
}
if (t->l->op != UNSET) {
if (-1 == cache_trigger_eval(srv, con, p, t->l)) {
fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
return -1;
}
}
if (t->r->op != UNSET) {
if (-1 == cache_trigger_eval(srv, con, p, t->r)) {
fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
return -1;
}
}
/* left and right are simple datatypes now */
if (IS_LONG(t->l) && IS_LONG(t->r)) {
if (-1 == cache_ops_long(t, t->l, t->r)) {
fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
return -1;
}
} else if (IS_STRING(t->l) && IS_STRING(t->r)) {
if (-1 == cache_ops_string(t, t->l, t->r)) {
fprintf(stderr, "%s.%d: cache_ops_string failed\n", __FILE__, __LINE__);
return -1;
}
} else {
fprintf(stderr, "%s.%d: typemismatch\n",
__FILE__, __LINE__);
return -1;
}
return 0;
}
int cache_trigger(server *srv, connection *con, plugin_data *p) {
size_t i;
tnode *t;
t = tnode_init();
for (i = 0; i < p->trigger_if->used; i++) {
if (-1 == cache_trigger_parse(srv, con, p, p->trigger_if->ptr[i], t)) {
fprintf(stderr, "%s.%d: cache_trigger_parse failed\n", __FILE__, __LINE__);
return -1;
}
if (-1 == cache_trigger_eval(srv, con, p, t)) {
fprintf(stderr, "%s.%d: cache_trigger_eval failed\n", __FILE__, __LINE__);
return -1;
}
if (IS_LONG(t)) {
#if 0
fprintf(stderr, "eval: %s = %ld\n", p->trigger_if->ptr[i]->ptr, VAL_LONG(t));
#endif
if (VAL_LONG(t) != 0) {
tnode_free(t);
return 1;
}
} else if (IS_STRING(t)) {
#if 0
fprintf(stderr, "eval: %s = '%s'\n", p->trigger_if->ptr[i]->ptr, VAL_STRING(t)->ptr);
#endif
if (VAL_STRING(t)->used > 1) {
tnode_free(t);
return 1;
}
}
}
tnode_free(t);
return 0;
}
/**
* parse the cache-file
*
* if cache-file is broken, call handler
* if no handler is set, report 500
*
* known keywords
*
* - include
* - content-type
*
*/
int cache_parse(server *srv, connection *con, plugin_data *p, buffer *fn) {
stream cf;
char *line, *end;
int r;
if (0 != stream_open(&cf, fn)) {
log_error_write(srv, __FILE__, __LINE__, "s", strerror(errno));
return -1;
}
buffer_reset(srv->tmp_buf);
for (line = cf.start;
NULL != (end = (memchr(line, '\n', cf.size - (line - cf.start)))) && (end - cf.start < cf.size);
line = end + 1) {
int s_len, key_len;
char *value;
s_len = end - line;
if (*line == '#') continue;
if (s_len == 0) continue;
if (line[s_len-1] == '\\') {
/* backslash at the end of the line */
buffer_append_string_len(srv->tmp_buf, line, s_len - 1);
continue;
}
buffer_append_string_len(srv->tmp_buf, line, s_len);
if ((NULL == (value = strchr(srv->tmp_buf->ptr, ' '))) &&
(NULL == (value = strchr(srv->tmp_buf->ptr, '\t')))) {
log_error_write(srv, __FILE__, __LINE__, "sss", fn->ptr, "whitespace is missing: <key> <value>", srv->tmp_buf->ptr);
stream_close(&cf);
return -1;
}
key_len = value - srv->tmp_buf->ptr;
/* strip spaces */
for(; *value == ' '; value++);
switch(key_len) {
case 4:
if (0 == strncmp(srv->tmp_buf->ptr, "eval", key_len)) {
buffer *b;
b = buffer_array_append_get_buffer(p->eval);
buffer_copy_string(b, value);
} else {
log_error_write(srv, __FILE__, __LINE__, "db", key_len, srv->tmp_buf);
}
break;
case 10:
if (0 == strncmp(srv->tmp_buf->ptr, "trigger.if", key_len)) {
buffer *b;
b = buffer_array_append_get_buffer(p->trigger_if);
buffer_copy_string(b, value);
} else {
log_error_write(srv, __FILE__, __LINE__, "db", key_len, srv->tmp_buf);
}
break;
case 14:
if (0 == strncmp(srv->tmp_buf->ptr, "output.include", key_len)) {
struct stat st;
buffer *b;
b = buffer_array_append_get_buffer(p->output_include);
buffer_copy_string_buffer(b, p->basedir);
buffer_append_string(b, value);
if (-1 == stat(b->ptr, &st)) {
log_error_write(srv, __FILE__, __LINE__, "sbs", "output.include:", b, strerror(errno));
p->output_include->used--;
stream_close(&cf);
return -1;
}
} else {
log_error_write(srv, __FILE__, __LINE__, "db", key_len, srv->tmp_buf);
}
break;
case 15:
if (0 == strncmp(srv->tmp_buf->ptr, "trigger.handler", key_len)) {
/*
* if one of the trigger.if's is true, the trigger.handler is called
*
* the output of the trigger is sent to the client
*
*/
buffer *b;
b = p->trigger_handler;
buffer_copy_string_buffer(b, p->basedir);
buffer_append_string(b, value);
} else {
log_error_write(srv, __FILE__, __LINE__, "db", key_len, srv->tmp_buf);
}
break;
case 19:
if (0 == strncmp(srv->tmp_buf->ptr, "output.content-type", key_len)) {
response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), value, strlen(value));
} else {
log_error_write(srv, __FILE__, __LINE__, "db", key_len, srv->tmp_buf);
}
break;
default:
log_error_write(srv, __FILE__, __LINE__, "db", key_len, srv->tmp_buf);
break;
}
buffer_reset(srv->tmp_buf);
}
stream_close(&cf);
if (p->trigger_handler->used && (0 != (r = cache_trigger(srv, con, p)))) {
if (r == -1) return -1;
/* triggering */
/* rewrite filename */
buffer_copy_string_buffer(con->physical.path, p->trigger_handler);
chunkqueue_reset(con->write_queue);
return 1;
} else {
size_t i;
struct stat st;
for (i = 0; i < p->output_include->used; i++) {
buffer *b = p->output_include->ptr[i];
stat(b->ptr, &st);
chunkqueue_append_file(con->write_queue, b, 0, st.st_size);
}
con->file_finished = 1;
}
return 0;
}
int cache_parse_parameter(server *srv, connection *con, plugin_data *p, const char *param, size_t param_len, tnode_val *val) {
buffer *b;
tnode *t;
b = buffer_init();
buffer_copy_string_len(b, param, param_len);
t = tnode_init();
/* we got an expression */
if (-1 == cache_trigger_parse(srv, con, p, b, t)) {
fprintf(stderr, "%s.%d: cache_trigger_parse failed: %s\n", __FILE__, __LINE__, b->ptr);
return -1;
}
if (-1 == cache_trigger_eval(srv, con, p, t)) {
fprintf(stderr, "%s.%d: cache_trigger_eval failed\n", __FILE__, __LINE__);
return -1;
}
buffer_free(b);
/* t is our value */
tnode_val_move(val, &(t->value));
tnode_free(t);
return 0;
}
int cache_parse_parameters(server *srv, connection *con, plugin_data *p, const char *params, size_t param_len, tnode_val_array *res) {
/*
* scheme:
* <expression>[, <expression>]
*
*/
size_t i;
int quotes = 0;
const char *start;
tnode_val *tv;
start = params;
for (i = 0; i < param_len; i++) {
char c = params[i];
if (c == '\\' && params[i+1] == '"' && quotes) i++;
if (c == '"') quotes = quotes ? 0 : 1;
if (!quotes) {
if (c == ',') {
tv = tnode_val_init();
if (-1 == cache_parse_parameter(srv, con, p, start, params + (i-1) - start, tv)) {
fprintf(stderr, "%s.%d: cache_parse_parameter failed: %s\n", __FILE__, __LINE__, start);
return -1;
}
tnode_val_array_append(res, tv);
start = params + i + 1;
}
}
}
tv = tnode_val_init();
if (-1 == cache_parse_parameter(srv, con, p, start, params + param_len - start, tv)) {
fprintf(stderr, "%s.%d: cache_parse_parameter failed: %s\n", __FILE__, __LINE__, start);
return -1;
}
tnode_val_array_append(res, tv);
if (quotes != 0) {
fprintf(stderr, "%s.%d: quotes don't match: %s\n", __FILE__, __LINE__, params);
return -1;
}
return 0;
}