/* Copyright 2006 Paul Querna * * 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 "proton.h" #include "private/context.h" #include "private/etl_util.h" #include "private/error_convert.h" #include "private/apr_compat.h" #include "private/request.h" #include #include #include #include #include "apr_buckets.h" #include "apr_file_info.h" #include "apr_tables.h" #include "apr_strings.h" #include "apr_dso.h" #include "etl_data.h" #include "pcre.h" static void conf_init(pwt_ctxt_t *pwt) { pwt->m_conf.root = NULL; pwt->m_conf.global_plugins = 0; pwt->m_conf.plugin_path = NULL; } static etl_error_t* stream_write_cb(void *baton, const char *buffer, size_t *len) { apr_bucket_brigade *bb = baton; apr_status_t apr_err = apr_brigade_write(bb, NULL, NULL, buffer, *len); if (apr_err) { return etl_error_create(ETL_EIO, "Error writing to brigade"); } return ETL_SUCCESS; } static void stream_destroy_cb(void *baton) { etl_stream_destroy(baton); } pwt_error_t* pwt_init(pwt_ctxt_t **pwt) { int utf8support = 0; apr_bucket_brigade *bb; apr_bucket_alloc_t *ba; /* Setup inital structure */ pwt_ctxt_t *m_pwt = calloc(1, sizeof(pwt_ctxt_t)); pcre_config(PCRE_CONFIG_UTF8, &utf8support); if (!utf8support) { return pwt_error_create(APR_EGENERAL, "PCRE must be configured with UTF-8 Support."); } /* XXXX: Not threadsafe. fix or document. */ apr_initialize(); apr_pool_create(&m_pwt->m_pool, NULL); ba = apr_bucket_alloc_create(m_pwt->m_pool); bb = apr_brigade_create(m_pwt->m_pool, ba); m_pwt->out = etl_stream_create(NULL, stream_write_cb, stream_destroy_cb, bb); *pwt = m_pwt; /* set output variables */ m_pwt->m_outheaders = apr_table_make(m_pwt->m_pool, 1); conf_init(m_pwt); return PWT_SUCCESS; } void pwt_destroy(pwt_ctxt_t **pwt) { pwt_ctxt_t *m_pwt = *pwt; /* XXXX: all other cleanups */ apr_pool_destroy(m_pwt->m_pool); free(m_pwt); *pwt = NULL; /* XXXX: not threadsafe. fix or document*/ apr_terminate(); } void pwt_root_set(pwt_ctxt_t *pwt, const char *path) { pwt->m_conf.root = apr_pstrdup(pwt->m_pool, path); } const char* pwt_root_get(pwt_ctxt_t *pwt) { return pwt->m_conf.root; } #if XXX_REFACTOR_ME static int get_first_hash_entry(apr_hash_t *env, apr_pool_t *p, void *key, void *value, apr_ssize_t *len) { apr_hash_index_t *hi = NULL; hi = apr_hash_first(p, env); if (!hi) { return 0; } apr_hash_this(hi, key, len, value); return 1; } #endif typedef struct load_routes_baton { pwt_error_t *err; pwt_ctxt_t *pwt; pwt_route_t *route_last; pwt_route_t *route_this; } load_routes_baton; static void etlv_load_routes_cb(const char *key, etl_variable_t *var, void *baton) { load_routes_baton *lrb = (load_routes_baton *)baton; pwt_ctxt_t *pwt = lrb->pwt; const char* modname; const char* regex; if (lrb->err != PWT_SUCCESS) { return; } if (etl_variable_type(var) != ETL_VARIABLE_STRING) { lrb->err = pwt_error_createf(APR_EINVAL, "value of 'proton.routes.%s' " "is of invalid type.", key); return; } modname = etl_variable_content_str(var); lrb->route_this = apr_palloc(pwt->m_pool, sizeof(pwt_route_t)); lrb->route_this->path = apr_pstrdup(pwt->m_pool, key); if (modname[0] == '^') { const char *errmsg; int erroffset; /** XXXXXX: do pcre magic */ modname++; regex = apr_pstrcat(pwt->m_pool, key, "?(/.+)?(/.*)", NULL); lrb->route_this->match = pcre_compile(regex, PCRE_UTF8, &errmsg, &erroffset, NULL); if (!lrb->route_this->match) { lrb->err = pwt_error_createf(APR_BADARG, "unable to compile PCRE('%s'): %s, " "at offset %d.", regex, errmsg, erroffset); return; } } else { lrb->route_this->match = NULL; } lrb->route_this->module = apr_pstrdup(pwt->m_pool, modname); if (!lrb->route_last) { pwt->m_conf.routes = lrb->route_this; } else { lrb->route_last->next = lrb->route_this; } lrb->route_last = lrb->route_this; } static void dump_routes(pwt_ctxt_t *pwt) { pwt_route_t *route = pwt->m_conf.routes; while (route) { fprintf(stderr, "%s -> %s\n", route->path, route->module); route = route->next; }; } static pwt_error_t* pwt_load_routes(pwt_ctxt_t *pwt) { const etl_variable_t* var = NULL; var = pwt_variable_get(pwt->m_env, "proton.routes"); if (var) { load_routes_baton lrb; lrb.pwt = pwt; lrb.err = PWT_SUCCESS; lrb.route_this = NULL; lrb.route_last = NULL; uint32_t arraysize; uint32_t i; if (etl_variable_type(var) != ETL_VARIABLE_ARRAY) { return pwt_error_create(APR_EINVAL, "'proton.routes' is not a array type."); } arraysize = etl_variable_array_nelts(var); for (i = 0; i < arraysize; i++) { const etl_variable_t *avar = etl_variable_array_idx(var, i); /** XXX: Error handling for broken config? */ etl_variable_hash_traverse(avar, etlv_load_routes_cb, &lrb); if (lrb.err != PWT_SUCCESS) { return lrb.err; } } } else { return pwt_error_create(APR_EINVAL, "'proton.routes' not configured."); } dump_routes(pwt); return PWT_SUCCESS; } static pwt_error_t* pwt_load_module(pwt_ctxt_t *pwt, const char *path_name, apr_pool_t *p) { apr_dso_handle_t *handle; apr_dso_handle_sym_t symbol; apr_status_t rv; char *module_name, *t; pwt_module_t *sym_reg; fprintf(stderr, "Loading module : %s\n", path_name); rv = apr_dso_load(&handle, path_name, pwt->m_pool); if (rv) { return pwt_error_create(rv, "error loading module"); } /* figure out the module name */ module_name = apr_pstrdup(p, basename(path_name)); if ((t = strrchr(module_name, '.'))) { *t = '\0'; } /* load the module info */ t = apr_psprintf(p, "%s_module", module_name); rv = apr_dso_sym(&symbol, handle, t); if (rv) { return pwt_error_create(rv, "not a proton module"); } fprintf(stderr, "Loaded module : %s\n", module_name); sym_reg = (pwt_module_t*) symbol; sym_reg->handlers = apr_array_make(pwt->m_pool, 1, sizeof(pwt_module_handler_t)); sym_reg->register_func(sym_reg, pwt); apr_hash_set(pwt->m_modules, module_name, APR_HASH_KEY_STRING, sym_reg); return PWT_SUCCESS; } static pwt_error_t* pwt_load_all_modules(pwt_ctxt_t *pwt) { apr_dir_t *dir; apr_finfo_t info; apr_status_t rv; pwt_error_t *err = PWT_SUCCESS; apr_pool_t *t_pool; char *full_path; rv = apr_dir_open(&dir, pwt->m_conf.plugin_path, pwt->m_pool); if (rv) { return pwt_error_create(rv, "Could not open plugin directory"); } pwt->m_modules = apr_hash_make(pwt->m_pool); apr_pool_create(&t_pool, pwt->m_pool); for (rv = apr_dir_read(&info, APR_FINFO_TYPE | APR_FINFO_NAME, dir); rv == APR_SUCCESS; rv = apr_dir_read(&info, APR_FINFO_TYPE | APR_FINFO_NAME, dir) ) { if (strncmp(info.name, ".", 1) == 0) { continue; } if (strstr(info.name, ".so")) { rv = apr_filepath_merge(&full_path, pwt->m_conf.plugin_path, info.name, 0, t_pool); if (rv) { continue; } err = pwt_load_module(pwt, full_path, t_pool); } } apr_pool_destroy(t_pool); apr_dir_close(dir); return err; } void pwt_register_handler(pwt_module_t *pmod, pwt_ctxt_t *pwt, const char *match, pwt_handler_cb func_cp, void *baton) { pwt_module_handler_t *handler = apr_pcalloc(pwt->m_pool, sizeof(pwt_module_handler_t)); *(pwt_module_handler_t**) apr_array_push(pmod->handlers) = handler; handler->match = match; handler->handler = func_cp; handler->baton = baton; } void pwt_register_handler_default(pwt_module_t *pmod, pwt_ctxt_t *pwt, pwt_handler_cb func_cp) { pwt_register_handler(pmod, pwt, "D", func_cp, NULL); } char* pwt_root_relative(pwt_ctxt_t *pwt, const char *path, apr_pool_t *p) { char *newpath = NULL; apr_status_t rv; rv = apr_filepath_merge(&newpath, pwt_root_get(pwt), path, APR_FILEPATH_TRUENAME, p); /** XXXX: Why accept all these return codes?? */ if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv) || APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_ENOTDIR(rv))) { return newpath; } else { return NULL; } } static pwt_error_t* pwt_import_conf(pwt_ctxt_t *pwt) { const char *str; str = pwt_variable_get_str(pwt->m_env, "proton.conf.plugin_path", NULL); if (str) { /* load plugins. */ pwt->m_conf.plugin_path = pwt_root_relative(pwt, str, pwt->m_pool); PWT_ERR(pwt_load_routes(pwt)); PWT_ERR(pwt_load_all_modules(pwt)); } else { return pwt_error_create(APR_EINVAL, "'proton.conf.plugin_path' not found " "in 'proton.ddl'"); } str = pwt_variable_get_str(pwt->m_env, "proton.conf.template_path", NULL); if (str) { pwt->m_conf.template_path = pwt_root_relative(pwt, str, pwt->m_pool); } return PWT_SUCCESS; } pwt_error_t* pwt_startup(pwt_ctxt_t *pwt) { const char *inc_path; etl_array_t *search_paths; apr_pool_t *t_pool = NULL; etl_resolver_t *resolver; apr_pool_create(&t_pool, pwt->m_pool); search_paths = etl_array_create(1); inc_path = strdup(pwt_root_relative(pwt, "./conf", t_pool)); if (!inc_path) { return pwt_error_create(APR_EGENERAL, "Merging Filepath for Core Config"); } etl_array_push(search_paths, (void*)inc_path); resolver = etl_resolver_file_create(search_paths); PWT_E_ERR(etl_ddl_parse_file(&pwt->m_env, resolver, pwt_root_relative(pwt, "./conf/proton.ddl", t_pool))); PWT_ERR(pwt_import_conf(pwt)); apr_pool_destroy(t_pool); etl_resolver_destroy(resolver); return PWT_SUCCESS; } pwt_error_t* pwt_execute(pwt_ctxt_t *pwt) { pwt_error_t *rv; pwt_req_t *req; pwt_req_init(&req, pwt); rv = pwt_req_exec(req); pwt_req_destroy(&req); return PWT_SUCCESS; } const char* pwt_route_match_path(pwt_ctxt_t *pwt, const char *path, int length) { int ovector[30]; pwt_route_t *route = pwt->m_conf.routes; while (route) { if (pcre_exec(route->match, NULL, path, length, 0, 0, ovector, 30) >= 0) { return route->module; } route = route->next; } return NULL; } const char* pwt_route_find_path(pwt_ctxt_t *pwt, const char *path) { pwt_route_t *route = pwt->m_conf.routes; while (route) { if (strcmp(route->path, path) == 0) { return route->module; } route = route->next; } return NULL; }