/*
**  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_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;
}
