/*
 * Copyright (c) 2006 Kazuaki ODA.
 * Copyright (c) 2006 Mode2 inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the company nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: ctr_xml.c 436 2006-11-11 08:52:58Z oda $
 */

#include "expat.h"
#include "apr_strings.h"
#include "apr_xlate.h"
#include "ctr_util.h"
#include "ctr_xml.h"

#define CTR_XML_BUFSZ   8192

typedef struct _ctr_xml_parser ctr_xml_parser_t;

struct _ctr_xml_parser {
    apr_pool_t *pool;
    XML_Parser raw_parser;
    const ctr_xml_handler_t *handler;
    const char *errmsg;
    void *udata;
};

static ctr_xml_parser_t *ctr_xml_parser_create(apr_pool_t *pool,
                                               const ctr_xml_handler_t *hndl,
                                               void *udata);
static apr_status_t ctr_xml_parser_cleanup(void *ctx);
static void ctr_xml_start_handler(void *udata, const char *name,
                                  const char **attrs);
static void ctr_xml_end_handler(void *udata, const char *name);
static void ctr_xml_cdata_handler(void *udata, const char *data, int len);
static int ctr_xml_unknown_encoding_handler(void *data, const XML_Char *name,
                                            XML_Encoding *info);
static int ctr_sjis_unicode_convert(void *data, const char *s);
static int ctr_eucjp_unicode_convert(void *data, const char *s);
static const char *ctr_utf8tosjis(const char *s, apr_pool_t *pool);

const char *ctr_xml_file_parse(const ctr_xml_handler_t *hndl, apr_file_t *fd,
                               void *udata, apr_pool_t *pool)
{
    ctr_xml_parser_t *parser;
    char *buf;
    apr_size_t buflen;
    int error;
    int line;
    char *errs;
    apr_status_t rv;

    parser = ctr_xml_parser_create(pool, hndl, udata);
    if (parser == NULL)
        return "insufficient mememory to create a parser";

    buf = apr_palloc(parser->pool, CTR_XML_BUFSZ);

    buflen = CTR_XML_BUFSZ;
    rv = apr_file_read(fd, buf, &buflen);
    while (rv == APR_SUCCESS) {
        if (XML_Parse(parser->raw_parser, buf, buflen, 0) == 0) {
            error = XML_GetErrorCode(parser->raw_parser);
            line = (int)XML_GetCurrentLineNumber(parser->raw_parser);
            errs = apr_psprintf(pool, "line %d: %s", line,
                                XML_ErrorString(error));
            return errs;
        }
        buflen = CTR_XML_BUFSZ;
        rv = apr_file_read(fd, buf, &buflen);
    }

    if (rv != APR_EOF) {
        errs = apr_palloc(pool, 128);   /* XXX: 128 */
        apr_strerror(rv, errs, 128);
        return errs;
    }

    if (XML_Parse(parser->raw_parser, "", 0, 1) == 0) {
        error = XML_GetErrorCode(parser->raw_parser);
        line = (int)XML_GetCurrentLineNumber(parser->raw_parser);
        errs = apr_psprintf(pool, "line %d: %s", line, XML_ErrorString(error));
        return errs;
    }

    apr_pool_cleanup_run(parser->pool, parser, ctr_xml_parser_cleanup);

    if (parser->errmsg)
        return parser->errmsg;
    else
        return NULL;
}

const char *ctr_xml_str_parse(const ctr_xml_handler_t *hndl, const char *s,
                              void *udata, apr_pool_t *pool)
{
    ctr_xml_parser_t *parser;
    int error;
    int line;
    char *errs;

    parser = ctr_xml_parser_create(pool, hndl, udata);
    if (parser == NULL)
        return "insufficient mememory to create a parser";

    if (XML_Parse(parser->raw_parser, s, strlen(s), 0) == 0) {
        error = XML_GetErrorCode(parser->raw_parser);
        line = (int)XML_GetCurrentLineNumber(parser->raw_parser);
        errs = apr_psprintf(pool, "line %d: %s", line,
                            XML_ErrorString(error));
        return errs;
    }

    if (XML_Parse(parser->raw_parser, "", 0, 1) == 0) {
        error = XML_GetErrorCode(parser->raw_parser);
        line = (int)XML_GetCurrentLineNumber(parser->raw_parser);
        errs = apr_psprintf(pool, "line %d: %s", line, XML_ErrorString(error));
        return errs;
    }

    apr_pool_cleanup_run(parser->pool, parser, ctr_xml_parser_cleanup);

    if (parser->errmsg)
        return parser->errmsg;
    else
        return NULL;
}

static ctr_xml_parser_t *ctr_xml_parser_create(apr_pool_t *pool,
                                               const ctr_xml_handler_t *hndl,
                                               void *udata)
{
    ctr_xml_parser_t *parser;

    parser = apr_palloc(pool, sizeof(*parser));
    parser->pool = pool;
    parser->raw_parser = XML_ParserCreate(NULL);
    if (parser->raw_parser == NULL) {
        (*apr_pool_abort_get(pool))(APR_ENOMEM);
        return NULL;
    }
    parser->handler = hndl;
    parser->errmsg = NULL;
    parser->udata = udata;

    apr_pool_cleanup_register(pool, parser, ctr_xml_parser_cleanup,
                              apr_pool_cleanup_null);

    XML_SetElementHandler(parser->raw_parser, ctr_xml_start_handler,
                          ctr_xml_end_handler);
    XML_SetCharacterDataHandler(parser->raw_parser, ctr_xml_cdata_handler);
    XML_SetUnknownEncodingHandler(parser->raw_parser,
                                  ctr_xml_unknown_encoding_handler,
                                  parser->pool);
    XML_SetUserData(parser->raw_parser, parser);

    return parser;
}

static apr_status_t ctr_xml_parser_cleanup(void *ctx)
{
    ctr_xml_parser_t *parser = ctx;

    XML_ParserFree(parser->raw_parser);

    return APR_SUCCESS;
}

static void ctr_xml_start_handler(void *udata, const char *name,
                                  const char **attrs)
{
    ctr_xml_parser_t *parser = udata;
    apr_pool_t *pool = parser->pool;
    apr_table_t *attrtbl;
    const char *attrname, *attrval;
    const char *errmsg;
    int line;

    if (parser->errmsg != NULL || parser->handler->start_element == NULL)
        return;

    attrtbl = apr_table_make(pool, 1);
    while (*attrs) {
        attrname = ctr_utf8tosjis(*attrs++, pool);
        attrval = ctr_utf8tosjis(*attrs++, pool);
        apr_table_setn(attrtbl, attrname, attrval);
    }

    errmsg = parser->handler->start_element(parser->udata,
                                            ctr_utf8tosjis(name, pool),
                                            attrtbl);
    if (errmsg != NULL) {
        line = (int)XML_GetCurrentLineNumber(parser->raw_parser);
        parser->errmsg = apr_psprintf(pool, "line %d: %s", line, errmsg);
    }
}

static void ctr_xml_end_handler(void *udata, const char *name)
{
    ctr_xml_parser_t *parser = udata;
    apr_pool_t *pool = parser->pool;
    const char *errmsg;
    int line;

    if (parser->errmsg != NULL || parser->handler->end_element == NULL)
        return;

    errmsg = parser->handler->end_element(parser->udata,
                                          ctr_utf8tosjis(name, pool));
    if (errmsg != NULL) {
        line = (int)XML_GetCurrentLineNumber(parser->raw_parser);
        parser->errmsg = apr_psprintf(pool, "line %d: %s", line, errmsg);
    }
}

static void ctr_xml_cdata_handler(void *udata, const char *data, int len)
{
    ctr_xml_parser_t *parser = udata;
    apr_pool_t *pool = parser->pool;
    char *s;
    const char *errmsg;
    int line;

    if (parser->errmsg != NULL || parser->handler->characters == NULL)
        return;

    s = apr_pstrndup(pool, data, len);
    errmsg = parser->handler->characters(parser->udata,
                                         ctr_utf8tosjis(s, pool));
    if (errmsg != NULL) {
        line = (int)XML_GetCurrentLineNumber(parser->raw_parser);
        parser->errmsg = apr_psprintf(pool, "line %d: %s", line, errmsg);
    }
}

static int ctr_xml_unknown_encoding_handler(void *data, const XML_Char *name,
                                            XML_Encoding *info)
{
    int i;

    if (strcasecmp(name, "shift_jis") == 0 ||
        strcasecmp(name, "windows-31j") == 0) {
        for (i = 0x00; i <= 0x7f; i++)
            info->map[i] = i;
        info->map[0x80] = -1;
        for (i = 0x81; i <= 0x84; i++)
            info->map[i] = -2;
        for (i = 0x85; i <= 0x86; i++)
            info->map[i] = -1;
        for (i = 0x87; i <= 0x9f; i++)
            info->map[i] = -2;
        info->map[0xa0] = -1;
        for (i = 0xa1; i <= 0xdf; i++)
            info->map[i] = 0xfec0 + i;
        for (i = 0xe0; i <= 0xea; i++)
            info->map[i] = -2;
        for (i = 0xeb; i <= 0xec; i++)
            info->map[i] = -1;
        for (i = 0xed; i <= 0xee; i++)
            info->map[i] = -2;
        for (i = 0xef; i <= 0xf9; i++)
            info->map[i] = -1;
        for (i = 0xfa; i <= 0xfc; i++)
            info->map[i] = -2;
        for (i = 0xfd; i <= 0xff; i++)
            info->map[i] = -1;
        info->data = data;
        info->convert = ctr_sjis_unicode_convert;
        info->release = NULL;
        return 1;
    } else if (strcasecmp(name, "euc-jp") == 0) {
        for (i = 0x00; i <= 0x7f; i++)
            info->map[i] = i;
        for (i = 0x80; i <= 0x8d; i++)
            info->map[i] = -1;
        info->map[0x8e] = -2;
        info->map[0x8f] = -3;
        for (i = 0x90; i <= 0xa0; i++)
            info->map[i] = -1;
        for (i = 0xa1; i <= 0xa8; i++)
            info->map[i] = -2;
        for (i = 0xa9; i <= 0xaf; i++)
            info->map[i] = -1;
        for (i = 0xb0; i <= 0xf4; i++)
            info->map[i] = -2;
        for (i = 0xf5; i <= 0xff; i++)
            info->map[i] = -1;
        info->data = data;
        info->convert = ctr_eucjp_unicode_convert;
        info->release = NULL;
        return 1;
    } else
        return 0;
}

static int ctr_sjis_unicode_convert(void *data, const char *s)
{
    apr_pool_t *pool = data;
    apr_xlate_t *xl;
    char buf[2];
    apr_size_t inleft, outleft;

    if (apr_xlate_open(&xl, "UCS-2BE", "SHIFT_JIS", pool) != APR_SUCCESS)
        return -1;      /* XXX */

    inleft = 2;
    outleft = sizeof(buf);
    if (apr_xlate_conv_buffer(xl, s, &inleft, buf, &outleft) != APR_SUCCESS ||
        inleft != 0) {
        apr_xlate_close(xl);
        return -1;
    }

    apr_xlate_close(xl);
    return (unsigned char)buf[0] << 8 | (unsigned char)buf[1];
}

static int ctr_eucjp_unicode_convert(void *data, const char *s)
{
    apr_pool_t *pool = data;
    apr_xlate_t *xl;
    char buf[2];
    apr_size_t inleft, outleft;

    if (apr_xlate_open(&xl, "UCS-2BE", "EUC-JP", pool) != APR_SUCCESS)
        return -1;      /* XXX */

    if ((unsigned char)*s == 0x8f)
        inleft = 3;
    else
        inleft = 2;
    outleft = sizeof(buf);
    if (apr_xlate_conv_buffer(xl, s, &inleft, buf, &outleft) != APR_SUCCESS ||
        inleft != 0) {
        apr_xlate_close(xl);
        return -1;
    }

    apr_xlate_close(xl);
    return (unsigned char)buf[0] << 8 | (unsigned char)buf[1];
}

static const char *ctr_utf8tosjis(const char *s, apr_pool_t *pool)
{
    apr_xlate_t *xl;
    char *buf;
    apr_size_t inleft, outleft;

    if (apr_xlate_open(&xl, "SHIFT_JIS", "UTF-8", pool) != APR_SUCCESS)
        return apr_pstrdup(pool, s);

    inleft = strlen(s);
    buf = apr_pcalloc(pool, inleft + 1);
    outleft = inleft + 1;
    if (apr_xlate_conv_buffer(xl, s, &inleft, buf, &outleft) != APR_SUCCESS ||
        inleft != 0) {
        apr_xlate_close(xl);
        return apr_pstrdup(pool, s);
    }

    apr_xlate_close(xl);
    return buf;
}
