/* Copyright 2005-2006 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 "etl_template.h" #include "common.h" #include "lexer.h" #include #ifndef APR_ARRAY_IDX #define APR_ARRAY_IDX(ary,i,type) (((type *)(ary)->elts)[i]) #endif #ifndef APR_FPROT_OS_DEFAULT #define APR_FPROT_OS_DEFAULT APR_OS_DEFAULT #endif #define CHK_ERR(expr) do { \ apr_status_t apr__err = (expr); \ if (apr__err) \ return apr__err; \ } while (0) etl_template_variable_t * etl_template_make_int (int i, apr_pool_t *pool) { etl_template_variable_t *v = apr_pcalloc (pool, sizeof (*v)); v->type = ETL_TEMPLATE_SCALAR; v->contents.s = apr_psprintf (pool, "%d", i); return v; } etl_template_variable_t * etl_template_make_str (const char *str, apr_pool_t *pool) { etl_template_variable_t *v = apr_pcalloc (pool, sizeof (*v)); v->type = ETL_TEMPLATE_SCALAR; v->contents.s = apr_pstrdup (pool, str); return v; } etl_template_variable_t * etl_template_make_hash (apr_pool_t *pool) { etl_template_variable_t *h = apr_pcalloc (pool, sizeof (*h)); h->type = ETL_TEMPLATE_HASH; h->contents.h = apr_hash_make (pool); return h; } etl_template_variable_t * etl_template_make_array (int nitems, apr_pool_t *pool) { etl_template_variable_t *a = apr_pcalloc (pool, sizeof (*a)); a->type = ETL_TEMPLATE_ARRAY; a->contents.a = apr_array_make (pool, nitems, sizeof (etl_template_variable_t *)); return a; } apr_status_t etl_template_parse (etl_template_t **tmpl, apr_bucket_brigade *in, apr_pool_t *pool) { etl_template_t *t = apr_pcalloc (pool, sizeof (*t)); apr_status_t apr_err = APR_EGENERAL; etl_parser_state_t state; apr_pool_t *subpool; etl_lexer_t *lexer; void *parser; int tok; apr_pool_create (&subpool, pool); lexer = etl_lexer_create (in, subpool); parser = etl_parser_alloc (malloc); state.error = NULL; state.tmpl = NULL; state.pool = pool; while ((apr_err = etl_lexer_scan (&tok, lexer, subpool)) == APR_SUCCESS && tok != ETL_TOK_EOI) { char *data = NULL; if (tok == ETL_TOK_TEXT || tok == ETL_TOK_VAR) { data = etl_lexer_text (lexer, pool); } if (tok != ETL_TOK_EOI) { etl_parser_parse (parser, tok, data, &state); } } etl_parser_parse (parser, 0, NULL, &state); etl_parser_free (parser, free); apr_pool_destroy (subpool); /* XXX do something with state.error if there is one */ if (state.tmpl == NULL) return APR_EINVAL; *tmpl = state.tmpl; return apr_err; } apr_status_t etl_template_parse_file (etl_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 = etl_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 (etl_template_variable_t *var, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { if (var) { switch (var->type) { case ETL_TEMPLATE_SCALAR: CHK_ERR (apr_brigade_printf (out, NULL, NULL, "%s", var->contents.s)); break; case ETL_TEMPLATE_HASH: return APR_EINVAL; case ETL_TEMPLATE_ARRAY: return APR_EINVAL; default: abort (); } } else return APR_EINVAL; return APR_SUCCESS; } static apr_status_t html_filter (apr_bucket_brigade *out, const char *contents) { int i, copied; for (i = 0; contents[i]; ++i) { unsigned char c = (unsigned char) contents[i]; if (c != '<' && c != '>' && c != '&' && c != '"') continue; if (i - copied) CHK_ERR (apr_brigade_write (out, NULL, NULL, contents + copied, i - copied)); switch (c) { case '<': CHK_ERR (apr_brigade_printf (out, NULL, NULL, "<")); break; case '>': CHK_ERR (apr_brigade_printf (out, NULL, NULL, ">")); break; case '&': CHK_ERR (apr_brigade_printf (out, NULL, NULL, "&")); break; case '"': CHK_ERR (apr_brigade_printf (out, NULL, NULL, """)); break; default: abort (); } copied = i + 1; } if (i - copied) CHK_ERR (apr_brigade_write (out, NULL, NULL, contents + copied, i - copied)); return APR_SUCCESS; } static apr_status_t xml_filter (apr_bucket_brigade *out, const char *contents) { return html_filter (out, contents); } static const char valid_uri_chars[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static apr_status_t url_filter (apr_bucket_brigade *out, const char *contents) { int i, copied; for (i = 0; contents[i]; ++i) { unsigned char c = (unsigned char) contents[i]; if (valid_uri_chars[c]) continue; if (i - copied) CHK_ERR (apr_brigade_write (out, NULL, NULL, contents + copied, i - copied)); CHK_ERR (apr_brigade_printf (out, NULL, NULL, "%%%02X", c)); copied = i + 1; } if (i - copied) CHK_ERR (apr_brigade_write (out, NULL, NULL, contents + copied, i - copied)); return APR_SUCCESS; } typedef apr_status_t (*filter_func_t) (apr_bucket_brigade *out, const char *contents); static filter_func_t filters[] = { html_filter, xml_filter, url_filter }; static apr_status_t for_loop (etl_template_node_t *itr, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool); static apr_status_t execute_node (etl_template_node_t *n, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { etl_template_node_t *itr = n; apr_pool_t *subpool; apr_pool_create (&subpool, pool); while (itr) { int invert_sense = 0; switch (itr->type) { case ETL_NODE_PRINT: { char *varname = NULL, *keyname = NULL; etl_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 == ETL_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 ETL_NODE_FILTER: { etl_template_filter_type_t filt = itr->contents.flt->filter; const char *varname = itr->contents.flt->var; etl_template_variable_t *var; var = apr_hash_get (environment, varname, APR_HASH_KEY_STRING); if (!var || var->type != ETL_TEMPLATE_SCALAR) return APR_EINVAL; if (filt < 0 || filt > (sizeof (filters) / sizeof (filters[0]))) return APR_EINVAL; else CHK_ERR (filters[filt](out, var->contents.s)); } break; case ETL_NODE_LITERAL: CHK_ERR (apr_brigade_printf (out, NULL, NULL, "%s", itr->contents.l.data)); break; case ETL_NODE_UNLESS: invert_sense = 1; /* FALLTHROUGH */ case ETL_NODE_IF: { etl_template_variable_t *var; etl_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 == ETL_TEMPLATE_SCALAR && atoi(var->contents.s) != 0) { if (yes) CHK_ERR (execute_node (yes, environment, out, subpool)); } else if (no) CHK_ERR (execute_node (no, environment, out, subpool)); } break; case ETL_NODE_FOR: CHK_ERR (for_loop(itr, environment, out, subpool)); break; default: abort(); } itr = itr->next; apr_pool_clear (subpool); } apr_pool_destroy (subpool); return APR_SUCCESS; } static apr_status_t for_loop (etl_template_node_t *itr, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { apr_status_t apr_err = APR_SUCCESS; etl_template_variable_t *var = apr_hash_get ( environment, itr->contents.f->arr, APR_HASH_KEY_STRING ); if (var) { switch (var->type) { case ETL_TEMPLATE_ARRAY: { int i; for (i = 0; i < var->contents.a->nelts; ++i) { etl_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, etl_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 ETL_TEMPLATE_HASH: { etl_template_variable_t key, *oldkey = NULL, *oldval = NULL; char *keyvar, *valvar, *tmp; apr_hash_index_t *hi; key.type = ETL_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 etl_template_execute (etl_template_t *t, apr_hash_t *environment, apr_bucket_brigade *out, apr_pool_t *pool) { return execute_node (t->tree, environment, out, pool); }