/* ** Licensed to the Apache Software Foundation (ASF) under one or more ** contributor license agreements. See the NOTICE file distributed with ** this work for additional information regarding copyright ownership. ** The ASF licenses this file to You 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 "apreq_error.h" #include "apreq_parser.h" #include "apreq_util.h" #include "apr_strings.h" #include "apr_xml.h" #include "apr_hash.h" #define PARSER_STATUS_CHECK(PREFIX) do { \ if (ctx->status == PREFIX##_ERROR) \ return APREQ_ERROR_GENERAL; \ else if (ctx->status == PREFIX##_COMPLETE) \ return APR_SUCCESS; \ else if (bb == NULL) \ return APR_INCOMPLETE; \ } while (0); APREQ_DECLARE(apreq_parser_t *) apreq_parser_make(apr_pool_t *pool, apr_bucket_alloc_t *ba, const char *content_type, apreq_parser_function_t pfn, apr_size_t brigade_limit, const char *temp_dir, apreq_hook_t *hook, void *ctx) { apreq_parser_t *p = apr_palloc(pool, sizeof *p); p->content_type = content_type; p->parser = pfn; p->hook = hook; p->pool = pool; p->bucket_alloc = ba; p->brigade_limit = brigade_limit; p->temp_dir = temp_dir; p->ctx = ctx; return p; } APREQ_DECLARE(apreq_hook_t *) apreq_hook_make(apr_pool_t *pool, apreq_hook_function_t hook, apreq_hook_t *next, void *ctx) { apreq_hook_t *h = apr_palloc(pool, sizeof *h); h->hook = hook; h->next = next; h->pool = pool; h->ctx = ctx; return h; } /*XXX this may need to check the parser's state before modifying the hook list */ APREQ_DECLARE(apr_status_t) apreq_parser_add_hook(apreq_parser_t *p, apreq_hook_t *h) { apreq_hook_t *last = h; while (last->next) last = last->next; last->next = p->hook; p->hook = h; return APR_SUCCESS; } static int default_parsers_lock = 0; static apr_hash_t *default_parsers = NULL; static apr_pool_t *default_parser_pool = NULL; static apr_status_t apreq_parsers_cleanup(void *data) { default_parsers_lock = 0; default_parsers = NULL; default_parser_pool = NULL; return APR_SUCCESS; } APREQ_DECLARE(apr_status_t) apreq_pre_initialize(apr_pool_t *pool) { apr_status_t status; if (default_parser_pool != NULL) return APR_SUCCESS; if (default_parsers_lock) return APREQ_ERROR_GENERAL; status = apr_pool_create(&default_parser_pool, pool); if (status != APR_SUCCESS) return status; apr_pool_tag(default_parser_pool, "apreq_default_parser"); apr_pool_cleanup_register(default_parser_pool, NULL, apreq_parsers_cleanup, apr_pool_cleanup_null); default_parsers = apr_hash_make(default_parser_pool); apreq_register_parser("application/x-www-form-urlencoded", apreq_parse_urlencoded); apreq_register_parser("multipart/form-data", apreq_parse_multipart); apreq_register_parser("multipart/related", apreq_parse_multipart); return APR_SUCCESS; } APREQ_DECLARE(apr_status_t) apreq_post_initialize(apr_pool_t *pool) { (void)pool; if (default_parser_pool == NULL) return APREQ_ERROR_GENERAL; default_parsers_lock = 1; return APR_SUCCESS; } APREQ_DECLARE(apr_status_t) apreq_initialize(apr_pool_t *pool) { apr_status_t s = apreq_pre_initialize(pool); if (s != APR_SUCCESS) return s; return apreq_post_initialize(pool); } APREQ_DECLARE(apr_status_t) apreq_register_parser(const char *enctype, apreq_parser_function_t pfn) { apreq_parser_function_t *f = NULL; if (default_parsers == NULL) return APR_EINIT; if (enctype == NULL) return APR_EINVAL; if (default_parsers_lock) return APREQ_ERROR_GENERAL; if (pfn != NULL) { f = apr_palloc(default_parser_pool, sizeof *f); *f = pfn; } apr_hash_set(default_parsers, apr_pstrdup(default_parser_pool, enctype), APR_HASH_KEY_STRING, f); return APR_SUCCESS; } APREQ_DECLARE(apreq_parser_function_t)apreq_parser(const char *enctype) { apreq_parser_function_t *f; apr_size_t tlen = 0; if (enctype == NULL || default_parsers_lock == 0) return NULL; while (enctype[tlen] && enctype[tlen] != ';') ++tlen; f = apr_hash_get(default_parsers, enctype, tlen); if (f != NULL) return *f; else return NULL; } APREQ_DECLARE_HOOK(apreq_hook_disable_uploads) { return (bb == NULL) ? APR_SUCCESS : APREQ_ERROR_GENERAL; } APREQ_DECLARE_HOOK(apreq_hook_discard_brigade) { apr_status_t s = APR_SUCCESS; if (hook->next) s = apreq_hook_run(hook->next, param, bb); if (bb != NULL) apr_brigade_cleanup(bb); return s; } /* generic parser */ struct gen_ctx { apreq_param_t *param; enum { GEN_INCOMPLETE, GEN_COMPLETE, GEN_ERROR } status; }; APREQ_DECLARE_PARSER(apreq_parse_generic) { struct gen_ctx *ctx = parser->ctx; apr_pool_t *pool = parser->pool; apr_status_t s = APR_SUCCESS; apr_bucket *e = APR_BRIGADE_LAST(bb); unsigned saw_eos = 0; if (ctx == NULL) { parser->ctx = ctx = apr_palloc(pool, sizeof *ctx); ctx->status = GEN_INCOMPLETE; ctx->param = apreq_param_make(pool, "_dummy_", strlen("_dummy_"), "", 0); ctx->param->upload = apr_brigade_create(pool, parser->bucket_alloc); ctx->param->info = apr_table_make(pool, APREQ_DEFAULT_NELTS); } PARSER_STATUS_CHECK(GEN); while (e != APR_BRIGADE_SENTINEL(bb)) { if (APR_BUCKET_IS_EOS(e)) { saw_eos = 1; break; } e = APR_BUCKET_PREV(e); } if (parser->hook != NULL) { s = apreq_hook_run(parser->hook, ctx->param, bb); if (s != APR_SUCCESS) { ctx->status = GEN_ERROR; return s; } } apreq_brigade_setaside(bb, pool); s = apreq_brigade_concat(pool, parser->temp_dir, parser->brigade_limit, ctx->param->upload, bb); if (s != APR_SUCCESS) { ctx->status = GEN_ERROR; return s; } if (saw_eos) { ctx->status = GEN_COMPLETE; return APR_SUCCESS; } else return APR_INCOMPLETE; } struct xml_ctx { apr_xml_doc *doc; apr_xml_parser *xml_parser; enum { XML_INCOMPLETE, XML_COMPLETE, XML_ERROR } status; }; APREQ_DECLARE_HOOK(apreq_hook_apr_xml_parser) { apr_pool_t *pool = hook->pool; struct xml_ctx *ctx = hook->ctx; apr_status_t s = APR_SUCCESS; apr_bucket *e; if (ctx == NULL) { hook->ctx = ctx = apr_palloc(pool, sizeof *ctx); ctx->doc = NULL; ctx->xml_parser = apr_xml_parser_create(pool); ctx->status = XML_INCOMPLETE; } PARSER_STATUS_CHECK(XML); for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { const char *data; apr_size_t dlen; if (APR_BUCKET_IS_EOS(e)) { s = apr_xml_parser_done(ctx->xml_parser, &ctx->doc); if (s == APR_SUCCESS) { ctx->status = XML_COMPLETE; if (hook->next) s = apreq_hook_run(hook->next, param, bb); } else { ctx->status = XML_ERROR; } return s; } else if (APR_BUCKET_IS_METADATA(e)) { continue; } s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); if (s != APR_SUCCESS) { ctx->status = XML_ERROR; return s; } s = apr_xml_parser_feed(ctx->xml_parser, data, dlen); if (s != APR_SUCCESS) { ctx->status = XML_ERROR; return s; } } if (hook->next) return apreq_hook_run(hook->next, param, bb); return APR_SUCCESS; } APREQ_DECLARE_HOOK(apreq_hook_find_param) { apreq_hook_find_param_ctx_t *ctx = hook->ctx; int is_final = (bb == NULL) || APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)); apr_status_t s = (hook->next == NULL) ? APR_SUCCESS : apreq_hook_run(hook->next, param, bb); if (is_final && s == APR_SUCCESS && ctx->param == NULL && strcasecmp(ctx->name, param->v.name) == 0) { ctx->param = param; ctx->prev->next = hook->next; } return s; }