/* Copyright 2005 Garrett Rooney. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "apr_template.h" #include #ifndef APR_ARRAY_PUSH #define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary))) #endif #ifndef APR_ARRAY_IDX #define APR_ARRAY_IDX(ary,i,type) (((type *)(ary)->elts)[i]) #endif #define CHK_ERR(expr) do { \ apr_status_t apr__err = (expr); \ if (apr__err) \ return apr__err; \ } while (0) apr_template_variable_t * apr_template_make_int (int i, apr_pool_t *pool) { apr_template_variable_t *v = apr_pcalloc (pool, sizeof (*v)); v->type = APR_TEMPLATE_SCALAR; v->contents.s = apr_psprintf (pool, "%d", i); return v; } apr_template_variable_t * apr_template_make_str (const char *str, apr_pool_t *pool) { apr_template_variable_t *v = apr_pcalloc (pool, sizeof (*v)); v->type = APR_TEMPLATE_SCALAR; v->contents.s = apr_pstrdup (pool, str); return v; } apr_template_variable_t * apr_template_make_hash (apr_pool_t *pool) { apr_template_variable_t *h = apr_pcalloc (pool, sizeof (*h)); h->type = APR_TEMPLATE_HASH; h->contents.h = apr_hash_make (pool); return h; } apr_template_variable_t * apr_template_make_array (int nitems, apr_pool_t *pool) { apr_template_variable_t *a = apr_pcalloc (pool, sizeof (*a)); a->type = APR_TEMPLATE_ARRAY; a->contents.a = apr_array_make (pool, nitems, sizeof (apr_template_variable_t *)); return a; } typedef enum { IF = 1, FOR, PRINT, UNLESS, LITERAL } template_node_type_t; typedef struct template_node_t template_node_t; typedef struct { char *var; template_node_t *yes_task; template_node_t *no_task; } if_contents_t; typedef struct { char *var; char *arr; template_node_t *task; } for_contents_t; typedef struct { char *var; } print_contents_t; typedef struct { char *data; } literal_contents_t; struct template_node_t { template_node_type_t type; /* note: contents with a size == sizeof (void *) are stored inline, * others get stored as a pointer so we can minimize the size * of the contents union. */ union { if_contents_t *i; for_contents_t *f; print_contents_t p; literal_contents_t l; } contents; template_node_t *next; }; struct apr_template_t { template_node_t *tree; }; static template_node_t * make_literal_node (const char *str, apr_pool_t *pool) { template_node_t *t = apr_palloc (pool, sizeof (*t)); t->type = LITERAL; t->contents.l.data = apr_pstrdup (pool, str); t->next = NULL; return t; } static template_node_t * make_print_node (const char *var, apr_pool_t *pool) { template_node_t *t = apr_palloc (pool, sizeof (*t)); t->type = PRINT; t->contents.p.var = apr_pstrdup (pool, var); t->next = NULL; return t; } static template_node_t * make_if_node (const char *var, template_node_t *yes, template_node_t *no, apr_pool_t *pool) { template_node_t *t = apr_palloc (pool, sizeof (*t) + sizeof (if_contents_t)); t->type = IF; t->contents.i = (void *) (((char *) t) + sizeof (*t)); t->contents.i->var = apr_pstrdup (pool, var); t->contents.i->yes_task = yes; t->contents.i->no_task = no; t->next = NULL; return t; } static template_node_t * make_for_node (char *var, char *arr, template_node_t *task, apr_pool_t *pool) { template_node_t *t = apr_palloc (pool, sizeof (*t) + sizeof (for_contents_t)); t->type = FOR; t->contents.f = (void *) (((char *) t) + sizeof (*t)); t->contents.f->arr = apr_pstrdup (pool, arr); t->contents.f->var = apr_pstrdup (pool, var); t->contents.f->task = task; t->next = NULL; return t; } typedef struct { char buffer[1024]; int nbytes; int location; apr_bucket_brigade *in; apr_bucket_brigade *tb; } lexer_t; static lexer_t * lexer_create (apr_bucket_brigade *in, apr_pool_t *pool) { lexer_t *l = apr_pcalloc (pool, sizeof (*l)); l->in = in; l->tb = apr_brigade_create (pool, in->bucket_alloc); return l; } typedef enum { TEXT = 1, DIRECTIVE } token_type_t; typedef struct { token_type_t type; union { char *text; apr_array_header_t *args; } data; } token_t; static apr_status_t get_next_token (token_t **token, lexer_t *lexer, apr_pool_t *pool) { int started_at; *token = NULL; if (lexer->location >= lexer->nbytes || lexer->buffer[lexer->location] == 0) { CHK_ERR (apr_brigade_split_line (lexer->tb, lexer->in, APR_BLOCK_READ, sizeof (lexer->buffer))); lexer->nbytes = sizeof (lexer->buffer); apr_brigade_flatten (lexer->tb, lexer->buffer, &(lexer->nbytes)); lexer->buffer[lexer->nbytes] = 0; lexer->location = 0; apr_brigade_cleanup (lexer->tb); } if (lexer->nbytes == 0) return APR_EOF; started_at = lexer->location; for (; lexer->location < lexer->nbytes; ++lexer->location) { char c = lexer->buffer[lexer->location]; if (c == '[' || c == '\n') { if (c == '\n' && lexer->location == started_at) { token_t *t = apr_pcalloc (pool, sizeof (*t)); t->type = TEXT; t->data.text = apr_pstrdup (pool, "\n"); *token = t; ++lexer->location; break; } else if (lexer->location > started_at) { token_t *t = apr_pcalloc (pool, sizeof (*t)); t->type = TEXT; { /* lets just pretend i know why this works... */ int nalloc = lexer->location - started_at + 1; if (c == '\n') nalloc += 1; t->data.text = apr_pcalloc (pool, nalloc); memcpy (t->data.text, lexer->buffer + started_at, nalloc - 1); } *token = t; if (c == '\n') ++lexer->location; break; } else if (c == '[') { if (lexer->location != lexer->nbytes && lexer->buffer[lexer->location + 1] == '%') { char *end = strstr (&lexer->buffer[lexer->location + 1], "%]"); if (end) { token_t *t = apr_pcalloc (pool, sizeof (*t)); char *tmp = NULL, *str; t->type = DIRECTIVE; t->data.args = apr_array_make (pool, 5, sizeof (char *)); *end = 0; str = apr_strtok (&lexer->buffer[lexer->location + 3], " ", &tmp); APR_ARRAY_PUSH (t->data.args, char *) = apr_pstrdup (pool, str); while ((str = apr_strtok (NULL, " ", &tmp)) != NULL) { APR_ARRAY_PUSH (t->data.args, char *) = apr_pstrdup (pool, str); } *token = t; lexer->location = (end - lexer->buffer) + 2; break; } else return APR_EINVAL; } } } } assert (*token != NULL); return APR_SUCCESS; } static apr_status_t parse_if (template_node_t **node, template_node_t **itr, int recursion_lvl, lexer_t *lexer, token_t *tok, int invert_sense, apr_pool_t *pool); static apr_status_t parse_loop (template_node_t **node, int recursion_lvl, lexer_t *lexer, apr_pool_t *pool) { template_node_t *itr = NULL; apr_status_t apr_err; apr_pool_t *subpool; token_t *tok; apr_pool_create (&subpool, pool); for (apr_err = get_next_token (&tok, lexer, subpool); apr_err == APR_SUCCESS; apr_err = get_next_token (&tok, lexer, subpool)) { assert (tok); switch (tok->type) { case TEXT: if (itr) { itr->next = make_literal_node (tok->data.text, pool); itr = itr->next; } else { *node = make_literal_node (tok->data.text, pool); itr = *node; } break; case DIRECTIVE: { char *dir = APR_ARRAY_IDX (tok->data.args, 0, char *); if (strcmp (dir, "print") == 0 && tok->data.args->nelts == 2) { if (itr) { itr->next = make_print_node ( APR_ARRAY_IDX (tok->data.args, 1, char *), pool ); itr = itr->next; } else { *node = make_print_node ( APR_ARRAY_IDX (tok->data.args, 1, char *), pool ); itr = *node; } } else if (strcmp (dir, "if") == 0 && tok->data.args->nelts == 2) { CHK_ERR(parse_if (node, &itr, recursion_lvl, lexer, tok, 0, pool)); } else if (strcmp (dir, "unless") == 0 && tok->data.args->nelts == 2) { CHK_ERR(parse_if (node, &itr, recursion_lvl, lexer, tok, 1, pool)); } else if (strcmp (dir, "for") == 0 && tok->data.args->nelts == 4 && strcmp (APR_ARRAY_IDX (tok->data.args, 2, char *), "in") == 0) { template_node_t *task = NULL; CHK_ERR (parse_loop (&task, recursion_lvl + 1, lexer, pool)); if (itr) { itr->next = make_for_node ( APR_ARRAY_IDX (tok->data.args, 1, char *), APR_ARRAY_IDX (tok->data.args, 3, char *), task, pool ); itr = itr->next; } else { *node = make_for_node ( APR_ARRAY_IDX (tok->data.args, 1, char *), APR_ARRAY_IDX (tok->data.args, 3, char *), task, pool ); itr = *node; } } else if (strcmp (dir, "end") == 0 && tok->data.args->nelts == 1) { if (recursion_lvl > 0) apr_err = APR_SUCCESS; else apr_err = APR_EINVAL; goto cleanup; } else { apr_err = APR_EINVAL; break; } } break; default: abort (); } apr_pool_clear (subpool); } cleanup: apr_pool_destroy (subpool); return apr_err; } static apr_status_t parse_if (template_node_t **node, template_node_t **itr, int recursion_lvl, lexer_t *lexer, token_t *tok, int invert_sense, apr_pool_t *pool) { template_node_t *yes_task = NULL; CHK_ERR (parse_loop (&yes_task, recursion_lvl + 1, lexer, pool)); /* XXX did we get an end or an else? if an else, we need * to pull in the other half of the if statement. */ if (*itr) { (*itr)->next = make_if_node (APR_ARRAY_IDX (tok->data.args, 1, char *), yes_task, NULL, pool); *itr = (*itr)->next; } else { *node = make_if_node (APR_ARRAY_IDX (tok->data.args, 1, char *), yes_task, NULL, pool); *itr = *node; } if (invert_sense) (*itr)->type = UNLESS; return APR_SUCCESS; } apr_status_t apr_template_parse (apr_template_t **tmpl, apr_bucket_brigade *in, apr_pool_t *pool) { apr_template_t *t = apr_pcalloc (pool, sizeof (*t)); apr_status_t apr_err; apr_pool_t *subpool; lexer_t *lexer; apr_pool_create (&subpool, pool); lexer = lexer_create (in, pool); apr_err = parse_loop (&t->tree, 0, lexer, pool); apr_pool_destroy (subpool); if (! apr_err || APR_STATUS_IS_EOF (apr_err)) { *tmpl = t; apr_err = APR_SUCCESS; } else *tmpl = NULL; return apr_err; } apr_status_t apr_template_parse_file (apr_template_t **tmpl, const char *filename, apr_pool_t *pool) { apr_bucket_alloc_t *alloc; apr_bucket_brigade *in; apr_status_t apr_err; apr_pool_t *subpool; apr_finfo_t finfo; apr_file_t *file; apr_pool_create (&subpool, pool); alloc = apr_bucket_alloc_create (subpool); CHK_ERR (apr_file_open (&file, filename, APR_READ, APR_FPROT_OS_DEFAULT, subpool)); CHK_ERR (apr_file_info_get (&finfo, APR_FINFO_SIZE, file)); in = apr_brigade_create (subpool, alloc); APR_BRIGADE_INSERT_HEAD (in, apr_bucket_file_create (file, 0, finfo.size, subpool, alloc)); apr_err = apr_template_parse (tmpl, in, pool); apr_bucket_alloc_destroy (alloc); apr_pool_destroy (subpool); return apr_err; } static void var_key_split (const char *input, char **varname, char **keyname, apr_pool_t *pool) { char *tmp; *varname = apr_strtok (apr_pstrdup (pool, input), ".", &tmp); *keyname = apr_strtok (NULL, ".", &tmp); } static apr_status_t print_var (apr_template_variable_t *var, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { if (var) { switch (var->type) { case APR_TEMPLATE_SCALAR: apr_brigade_printf (out, NULL, "%s", var->contents.s); break; case APR_TEMPLATE_HASH: return APR_EINVAL; case APR_TEMPLATE_ARRAY: return APR_EINVAL; default: abort (); } } else return APR_EINVAL; return APR_SUCCESS; } static apr_status_t for_loop (template_node_t *itr, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool); static apr_status_t execute_node (template_node_t *n, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { template_node_t *itr = n; apr_pool_t *subpool; apr_pool_create (&subpool, pool); while (itr) { int invert_sense = 0; switch (itr->type) { case PRINT: { char *varname = NULL, *keyname = NULL; apr_template_variable_t *var; /* XXX this whole key split/hash get stuff should handle nested * hashes... */ var_key_split (itr->contents.p.var, &varname, &keyname, subpool); var = apr_hash_get (environment, varname, APR_HASH_KEY_STRING); if (keyname) { if (var && var->type == APR_TEMPLATE_HASH) var = apr_hash_get (var->contents.h, keyname, APR_HASH_KEY_STRING); else return APR_EINVAL; } CHK_ERR (print_var (var, environment, out, subpool)); } break; case LITERAL: apr_brigade_printf (out, NULL, "%s", itr->contents.l.data); break; case UNLESS: invert_sense = 1; /* FALLTHROUGH */ case IF: { apr_template_variable_t *var; template_node_t *yes, *no; if (invert_sense) { yes = itr->contents.i->no_task; no = itr->contents.i->yes_task; } else { yes = itr->contents.i->yes_task; no = itr->contents.i->no_task; } var = apr_hash_get ( environment, itr->contents.i->var, APR_HASH_KEY_STRING ); if (var && var->type == APR_TEMPLATE_SCALAR && atoi(var->contents.s) != 0) { if (yes) execute_node (yes, environment, out, pool); } else if (no) execute_node (no, environment, out, pool); } break; case FOR: CHK_ERR (for_loop(itr, environment, out, pool)); break; default: abort(); } itr = itr->next; apr_pool_clear (subpool); } apr_pool_destroy (subpool); return APR_SUCCESS; } static apr_status_t for_loop (template_node_t *itr, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { apr_status_t apr_err = APR_SUCCESS; apr_template_variable_t *var = apr_hash_get ( environment, itr->contents.f->arr, APR_HASH_KEY_STRING ); if (var) { switch (var->type) { case APR_TEMPLATE_ARRAY: { int i; for (i = 0; i < var->contents.a->nelts; ++i) { apr_template_variable_t *old = apr_hash_get ( environment, itr->contents.f->var, APR_HASH_KEY_STRING ); apr_hash_set (environment, itr->contents.f->var, APR_HASH_KEY_STRING, APR_ARRAY_IDX(var->contents.a, i, apr_template_variable_t *)); apr_err = execute_node (itr->contents.f->task, environment, out, pool); apr_hash_set (environment, itr->contents.f->var, APR_HASH_KEY_STRING, old); if (apr_err) return apr_err; } } break; case APR_TEMPLATE_HASH: { apr_template_variable_t key, *oldkey = NULL, *oldval = NULL; char *keyvar, *valvar, *tmp; apr_hash_index_t *hi; key.type = APR_TEMPLATE_SCALAR; keyvar = apr_strtok (apr_pstrdup (pool, itr->contents.f->var), ",", &tmp); valvar = apr_strtok (NULL, ",", &tmp); if (keyvar) oldkey = apr_hash_get (environment, keyvar, APR_HASH_KEY_STRING); if (valvar) oldval = apr_hash_get (environment, valvar, APR_HASH_KEY_STRING); for (hi = apr_hash_first (pool, var->contents.h); hi; hi = apr_hash_next (hi)) { const void *k; void *v; apr_hash_this (hi, &k, NULL, &v); key.contents.s = (char *) k; if (keyvar) apr_hash_set (environment, keyvar, APR_HASH_KEY_STRING, &key); if (valvar) apr_hash_set (environment, valvar, APR_HASH_KEY_STRING, v); CHK_ERR (execute_node (itr->contents.f->task, environment, out, pool)); } if (keyvar) apr_hash_set (environment, keyvar, APR_HASH_KEY_STRING, oldkey); if (valvar) apr_hash_set (environment, valvar, APR_HASH_KEY_STRING, oldval); } break; default: return APR_EINVAL; } } else return APR_EINVAL; return apr_err; } apr_status_t apr_template_execute (apr_template_t *t, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { return execute_node (t->tree, environment, out, pool); }