/*
 * 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: mod_cthree.c 441 2006-11-13 04:55:48Z oda $
 */

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "ap_config.h"
#include "apr_lib.h"
#include "apr_strings.h"
#include "ctr_browsers.h"
#include "ctr_fetch.h"
#include "ctr_hdml_support.h"
#include "ctr_html_conv.h"
#include "ctr_img_support.h"
#include "ctr_parser.h"
#include "ctr_util.h"
#include "ctr_xml.h"

#define CTR_VERSION             "0.3-BETA1"

#define CTR_CONTROL_HEADER      "X-C3-Control"

#define CTR_BRWXML_URI          \
    "http://c3.mode2.co.jp/mod_cthree/xmlfiles/0.3/browsers.xml"

typedef enum _ctr_imgmax_carrier ctr_imgmax_carrier_t;
typedef struct _ctr_imgmax ctr_imgmax_t;
typedef struct _ctr_cfg ctr_cfg_t;
typedef struct _ctr_srvcfg ctr_srvcfg_t;
typedef struct _ctr_inpctx ctr_inpctx_t;
typedef struct _ctr_outctx ctr_outctx_t;
typedef struct _ctr_emojixml_info ctr_emojixml_info_t;

enum _ctr_imgmax_carrier {
    MC_NONE, MC_DOCOMO, MC_KDDI, MC_SOFTBANK, MC_WILLCOM
};

struct _ctr_imgmax {
    ctr_imgmax_carrier_t carrier;
    const char *name;
    const char *group;
    const char *size;
};

struct _ctr_cfg {
    const char *dir;
    int enabled;
    int enabled_set;
    int cachectl;
    int cachectl_set;
    const char *unknown_ua;
    int conv_unknown;
    int conv_unknown_set;
    int hdml_wap20;
    int hdml_wap20_set;
    int exp_subcards;
    int exp_subcards_set;
    int conv_text;
    int conv_text_set;
    int ihtml_conv;
    int ihtml_conv_set;
    int log_unknown;
    int log_unknown_set;
    int log_invhtml;
    int log_invhtml_set;
    int log_convedtype;
    int log_convedtype_set;
    const char *emojixml_path;
    int emojixml_path_set;
    apr_hash_t *emojihash;
    apr_array_header_t *imgmaxes;
    ctr_cfg_t *next;
};

struct _ctr_srvcfg {
    const char *server;
    const char *img_cachedir;
    int img_cachedir_set;
};

struct _ctr_inpctx {
    const char *conved;
    size_t left;
};

struct _ctr_outctx {
    char *data;
    apr_size_t datalen;
};

struct _ctr_emojixml_info {
    apr_hash_t *emojihash;
    enum {
        EX_NEUTRAL, EX_EMOJIS, EX_EMOJI, EX_IHTML, EX_AUXHTML, EX_SHTML,
        EX_VHTML, EX_HDML, EX_WHTML
    } stat;
    apr_array_header_t *unknowns;
    ctr_emoji_info_t *curinfo;
    apr_pool_t *pool;
};

static void ctr_register_hooks(apr_pool_t *p);
static void *ctr_cfg_create(apr_pool_t *p, char *d);
static void *ctr_cfg_merge(apr_pool_t *pool, void *base, void *sub);
static void *ctr_srvcfg_create(apr_pool_t *pool, server_rec *server);
static void *ctr_srvcfg_merge(apr_pool_t *pool, void *basev, void *subv);
static const char *ctr_brwxml_path_set(cmd_parms *parms, void *dummy,
                                       const char *arg);
static const char *ctr_brwxml_autodl_set(cmd_parms *parms, void *dummy,
                                         int flg);
static const char *ctr_brwxml_uri_set(cmd_parms *parms, void *dummy,
                                      const char *arg);
static const char *ctr_img_cachedir_set(cmd_parms *parms, void *dummy,
                                        const char *arg);
static const char *ctr_enabled_set(cmd_parms *parms, void *mconf, int flg);
static const char *ctr_cachectl_set(cmd_parms *parms, void *mconf, int flg);
static const char *ctr_conv_unknown_set(cmd_parms *parms, void *mconf,
                                        int flg);
static const char *ctr_hdml_wap20_set(cmd_parms *parms, void *mconf, int flg);
static const char *ctr_exp_subcards_set(cmd_parms *parms,
                                        void *mconf, int flg);
static const char *ctr_conv_text_set(cmd_parms *parms, void *mconf, int flg);
static const char *ctr_ihtml_conv_set(cmd_parms *parms, void *mconf, int flg);
static const char *ctr_log_unknown_set(cmd_parms *parms, void *mconf, int flg);
static const char *ctr_log_invhtml_set(cmd_parms *parms, void *mconf, int flg);
static const char *ctr_log_convedtype_set(cmd_parms *parms, void *mconf,
                                          int flg);
static const char *ctr_emojixml_path_set(cmd_parms *parms,
                                         void *mconf, const char *arg);
static const char *ctr_img_maxsize_set(cmd_parms *parms, void *mconf,
                                       const char *arg1, const char *arg2);
static void ctr_child_init(apr_pool_t *pool, server_rec *server);
static int ctr_pre_config(apr_pool_t *pool, apr_pool_t *plog,
                          apr_pool_t *ptemp);
static int ctr_post_config(apr_pool_t *pool, apr_pool_t *plog,
                           apr_pool_t *ptemp, server_rec *server);
static int ctr_post_read_request(request_rec *r);
static int ctr_type_checker(request_rec *r);
static int ctr_fixups(request_rec *r);
static int ctr_handler(request_rec *r);
static void ctr_insert_filter(request_rec *r);
static int ctr_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
                            ap_input_mode_t mode, apr_read_type_e block,
                            apr_off_t readbytes);
static apr_status_t ctr_output_filter(ap_filter_t *f, apr_bucket_brigade *bb);
static const char *ctr_brwxml_download(const char *uri, apr_file_t **fd,
                                       apr_pool_t *pool);
static apr_status_t ctr_brwxml_local_copy(apr_file_t *fd, const char *local,
                                          apr_pool_t *pool);
static int ctr_emojixml_read(ctr_cfg_t *cfg, const server_rec *server,
                             const request_rec *r, apr_pool_t *pool);
static const char *ctr_emojixml_start_element(void *udata, const char *name,
                                              apr_table_t *attrs);
static const char *ctr_emojixml_end_element(void *udata, const char *name);
static ctr_cfg_t *ctr_resctl_merge(const ctr_cfg_t *cfg, const request_rec *r);

static const command_rec ctr_cfg_cmds[] =
{
    AP_INIT_TAKE1("C3BrowsersXmlPath", ctr_brwxml_path_set, NULL, RSRC_CONF,
                  "File path of browsers xml (default is "
                  "browsers.xml under " DEFAULT_EXP_SYSCONFDIR ")"),
    AP_INIT_FLAG("C3BrowsersXmlAutoDl", ctr_brwxml_autodl_set, NULL, RSRC_CONF,
                 "Enable auto download of browsers xml (default in On)"),
    AP_INIT_TAKE1("C3BrowsersXmlUri", ctr_brwxml_uri_set, NULL, RSRC_CONF,
                  "URI of auto download of browsers xml"
                  "(default is " CTR_BRWXML_URI ")"),
    AP_INIT_TAKE1("C3ImageCacheDir", ctr_img_cachedir_set, NULL, RSRC_CONF,
                  "Directory used for image cache files "
                  "(default is system temporary directory)"),
    AP_INIT_FLAG("C3Enable", ctr_enabled_set, NULL, OR_ALL,
                 "Enable mod_cthree (default is Off)"),
    AP_INIT_FLAG("C3Engine", ctr_enabled_set, NULL, OR_ALL,
                 "Enable mod_cthree (default is Off)"),
    AP_INIT_FLAG("C3CacheControl", ctr_cachectl_set, NULL, OR_ALL,
                 "Add or remove HTTP headers to prevent browsers from "
                 "caching pages (default is On)"),
    AP_INIT_TAKE1("C3ConvUnknownAs", ap_set_string_slot,
                  (void *)APR_OFFSETOF(ctr_cfg_t, unknown_ua), OR_ALL,
                  "If User-Agent is uknown, convert pages as if it were "
                  "(default is NULL)"),
    AP_INIT_FLAG("C3ConvUnknown", ctr_conv_unknown_set, NULL, OR_ALL,
                 "If User-Agent is uknown, provide contents for full browser "
                 "(default is Off)"),
    AP_INIT_FLAG("C3HdmlForWap20", ctr_hdml_wap20_set, NULL, OR_ALL,
                 "Provide HDML contents for WAP 2.0 browser (default is Off)"),
    AP_INIT_FLAG("C3HdmlExportSubCards", ctr_exp_subcards_set, NULL, OR_ALL,
                 "Export HDML sub cards to decrease content length "
                 "(default is Off)"),
    AP_INIT_FLAG("C3ConvText", ctr_conv_text_set, NULL, OR_ALL,
                 "Convert text/html contents (default is On)"),
    AP_INIT_FLAG("C3ConvIHtml", ctr_ihtml_conv_set, NULL, OR_ALL,
                 "Convert contents from IHTML to IHTML (default is On)"),
    AP_INIT_FLAG("C3LogUnknown", ctr_log_unknown_set, NULL, OR_ALL,
                 "Output to error log if User-Agent is Unknown "
                 "(default is On)"),
    AP_INIT_FLAG("C3LogInvalidHtml", ctr_log_invhtml_set, NULL, OR_ALL,
                 "Output to error log if original HTML is invalid "
                 "(default is On)"),
    AP_INIT_FLAG("C3LogConvedType", ctr_log_convedtype_set, NULL, OR_ALL,
                 "Output to error log the type of HTML "
                 "which is the result of conversion (default is Off)"),
    AP_INIT_TAKE1("C3EmojiXmlPath", ctr_emojixml_path_set, NULL, OR_ALL,
                  "File path of emoji.xml (default is "
                  "emoji.xml under " DEFAULT_EXP_SYSCONFDIR ")"),
    AP_INIT_TAKE12("C3ImageMaxSize", ctr_img_maxsize_set, NULL, OR_ALL,
                   "Maximum size of a converted image"),
    { NULL }
};

module AP_MODULE_DECLARE_DATA cthree_module = {
    STANDARD20_MODULE_STUFF,
    ctr_cfg_create,             /* create per-dir    config structures */
    ctr_cfg_merge,              /* merge  per-dir    config structures */
    ctr_srvcfg_create,          /* create per-server config structures */
    ctr_srvcfg_merge,           /* merge  per-server config structures */
    ctr_cfg_cmds,               /* table of config file commands       */
    ctr_register_hooks          /* register hooks                      */
};

static ctr_cfg_t *ctr_cfg_list;
static const char *brwxml_path;
static const char *brwxml_uri;
static int brwxml_autodl;
apr_hash_t *brwhash;

static void ctr_register_hooks(apr_pool_t *p)
{
    ap_hook_pre_config(ctr_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(ctr_post_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(ctr_child_init, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_read_request(ctr_post_read_request, NULL, NULL,
                              APR_HOOK_MIDDLE);
    ap_hook_type_checker(ctr_type_checker, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_fixups(ctr_fixups, NULL, NULL, APR_HOOK_FIRST);
    ap_hook_handler(ctr_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_insert_filter(ctr_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
    ap_register_input_filter("cthree-in", ctr_input_filter, NULL,
                             AP_FTYPE_RESOURCE);
    ap_register_output_filter("cthree-out", ctr_output_filter, NULL,
                              AP_FTYPE_RESOURCE);
}

static void *ctr_cfg_create(apr_pool_t *pool, char *d)
{
    ctr_cfg_t *cfg = apr_palloc(pool, sizeof(*cfg));
    char *s;

    if (d != NULL)
        cfg->dir = apr_pstrdup(pool, d);
    else
        cfg->dir = NULL;
    cfg->enabled = FALSE;
    cfg->enabled_set = FALSE;
    cfg->cachectl = TRUE;
    cfg->cachectl_set = FALSE;
    cfg->unknown_ua = NULL;
    cfg->conv_unknown = FALSE;
    cfg->conv_unknown_set = FALSE;
    cfg->hdml_wap20 = FALSE;
    cfg->hdml_wap20_set = FALSE;
    cfg->exp_subcards = FALSE;
    cfg->exp_subcards_set = FALSE;
    cfg->conv_text = TRUE;
    cfg->conv_text_set = FALSE;
    cfg->ihtml_conv = TRUE;
    cfg->ihtml_conv_set = FALSE;
    cfg->log_unknown = TRUE;
    cfg->log_unknown_set = FALSE;
    cfg->log_invhtml = TRUE;
    cfg->log_invhtml_set = FALSE;
    cfg->log_convedtype = FALSE;
    cfg->log_convedtype_set = FALSE;
    if (apr_filepath_merge(&s, DEFAULT_EXP_SYSCONFDIR,
                           "emoji.xml", 0, pool) == APR_SUCCESS)
        cfg->emojixml_path = s;
    else
        cfg->emojixml_path = NULL;
    cfg->emojixml_path_set = FALSE;
    cfg->emojihash = NULL;
    cfg->imgmaxes = apr_array_make(pool, 4, sizeof(ctr_imgmax_t));
    cfg->next = ctr_cfg_list;
    ctr_cfg_list = cfg;

    return cfg;
}

static void *ctr_cfg_merge(apr_pool_t *pool, void *basev, void *subv)
{
    ctr_cfg_t *base, *sub, *new;

    base = (ctr_cfg_t *)basev;
    sub = (ctr_cfg_t *)subv;

    new = apr_palloc(pool, sizeof(*new));
    new->dir = sub->dir;
    new->enabled = sub->enabled_set ? sub->enabled : base->enabled;
    new->enabled_set = TRUE;
    new->cachectl = sub->cachectl_set ? sub->cachectl : base->cachectl;
    new->cachectl_set = TRUE;
    new->unknown_ua = sub->unknown_ua ? sub->unknown_ua : base->unknown_ua;
    new->conv_unknown =
        sub->conv_unknown_set ? sub->conv_unknown : base->conv_unknown;
    new->conv_unknown_set = TRUE;
    new->hdml_wap20 = sub->hdml_wap20_set ? sub->hdml_wap20 : base->hdml_wap20;
    new->hdml_wap20_set = TRUE;
    new->exp_subcards =
        sub->exp_subcards_set ? sub->exp_subcards : base->exp_subcards;
    new->exp_subcards_set = TRUE;
    new->conv_text = sub->conv_text_set ? sub->conv_text : base->conv_text;
    new->conv_text_set = TRUE;
    new->ihtml_conv = sub->ihtml_conv_set ? sub->ihtml_conv : base->ihtml_conv;
    new->ihtml_conv_set = TRUE;
    new->log_unknown =
        sub->log_unknown_set ? sub->log_unknown : base->log_unknown;
    new->log_unknown_set = TRUE;
    new->log_invhtml =
        sub->log_invhtml_set ? sub->log_invhtml : base->log_invhtml;
    new->log_invhtml_set = TRUE;
    new->log_convedtype =
        sub->log_convedtype_set ? sub->log_convedtype : base->log_convedtype;
    new->log_convedtype_set = TRUE;
    new->emojixml_path =
        sub->emojixml_path_set ? sub->emojixml_path : base->emojixml_path;
    new->emojixml_path_set = TRUE;
    new->emojihash = sub->emojixml_path_set ? sub->emojihash : base->emojihash;
    new->imgmaxes = apr_array_append(pool, base->imgmaxes, sub->imgmaxes);
    new->next = ctr_cfg_list;
    ctr_cfg_list = new;

    return new;
}

static void *ctr_srvcfg_create(apr_pool_t *pool, server_rec *server)
{
    ctr_srvcfg_t *cfg = apr_palloc(pool, sizeof(*cfg));

    cfg->server = server->server_hostname;
    if (apr_temp_dir_get(&cfg->img_cachedir, pool) != APR_SUCCESS)
        cfg->img_cachedir = NULL;
    cfg->img_cachedir_set = FALSE;

    return cfg;
}

static void *ctr_srvcfg_merge(apr_pool_t *pool, void *basev, void *subv)
{
    ctr_srvcfg_t *base, *sub, *new;

    base = (ctr_srvcfg_t *)basev;
    sub = (ctr_srvcfg_t *)subv;
    new = apr_palloc(pool, sizeof(*new));
    new->server = sub->server;
    new->img_cachedir =
        sub->img_cachedir_set ? sub->img_cachedir : base->img_cachedir;
    new->img_cachedir_set = TRUE;

    return new;
}

static const char *ctr_brwxml_path_set(cmd_parms *parms, void *dummy,
                                       const char *arg)
{
    const char *errmsg;

    if ((errmsg = ap_check_cmd_context(parms, GLOBAL_ONLY)) != NULL)
        return errmsg;

    brwxml_path = ap_server_root_relative(parms->pool, arg);
    return NULL;
}

static const char *ctr_brwxml_autodl_set(cmd_parms *parms, void *dummy,
                                         int flg)
{
    const char *errmsg;

    if ((errmsg = ap_check_cmd_context(parms, GLOBAL_ONLY)) != NULL)
        return errmsg;

    brwxml_autodl = flg;
    return NULL;
}

static const char *ctr_brwxml_uri_set(cmd_parms *parms, void *dummy,
                                      const char *arg)
{
    const char *errmsg;

    if ((errmsg = ap_check_cmd_context(parms, GLOBAL_ONLY)) != NULL)
        return errmsg;

    brwxml_uri = arg;
    return NULL;
}

static const char *ctr_img_cachedir_set(cmd_parms *parms, void *dummy,
                                        const char *arg)
{
    ctr_srvcfg_t *srvcfg;
    apr_dir_t *dirp;
    char *buf;
    apr_status_t rv;

    srvcfg = ap_get_module_config(parms->server->module_config,
                                  &cthree_module);

    if (arg == NULL) {
        return
            "C3ImgCacheDir: failed to get default temporary directory, "
            "please specify it";
    }

    if ((rv =apr_dir_open(&dirp, arg, parms->pool)) != APR_SUCCESS) {
        buf = apr_palloc(parms->pool, 256);
        return apr_pstrcat(parms->pool, "C3ImgCacheDir: ",
                           apr_strerror(rv, buf, 256), NULL);
    }
    apr_dir_close(dirp);

    srvcfg->img_cachedir = arg;
    srvcfg->img_cachedir_set = TRUE;
    return NULL;
}

static const char *ctr_enabled_set(cmd_parms *parms, void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->enabled = flg;
    cfg->enabled_set = TRUE;
    return NULL;
}

static const char *ctr_cachectl_set(cmd_parms *parms, void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->cachectl = flg;
    cfg->cachectl_set = TRUE;
    return NULL;
}

static const char *ctr_conv_unknown_set(cmd_parms *parms, void *mconf,
                                        int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->conv_unknown = flg;
    cfg->conv_unknown_set = TRUE;
    return NULL;
}

static const char *ctr_hdml_wap20_set(cmd_parms *parms, void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->hdml_wap20 = flg;
    cfg->hdml_wap20_set = TRUE;
    return NULL;
}

static const char *ctr_exp_subcards_set(cmd_parms *parms,
                                        void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->exp_subcards = flg;
    cfg->exp_subcards_set = TRUE;
    return NULL;
}

static const char *ctr_conv_text_set(cmd_parms *parms, void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->conv_text = flg;
    cfg->conv_text_set = TRUE;
    return NULL;
}

static const char *ctr_ihtml_conv_set(cmd_parms *parms, void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->ihtml_conv = flg;
    cfg->ihtml_conv_set = TRUE;
    return NULL;
}

static const char *ctr_log_unknown_set(cmd_parms *parms, void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->log_unknown = flg;
    cfg->log_unknown_set = TRUE;
    return NULL;
}

static const char *ctr_log_invhtml_set(cmd_parms *parms, void *mconf, int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->log_invhtml = flg;
    cfg->log_invhtml_set = TRUE;
    return NULL;
}

static const char *ctr_log_convedtype_set(cmd_parms *parms, void *mconf,
                                          int flg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->log_convedtype = flg;
    cfg->log_convedtype_set = TRUE;
    return NULL;
}

static const char *ctr_emojixml_path_set(cmd_parms *parms,
                                         void *mconf, const char *arg)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;

    cfg->emojixml_path = arg;
    cfg->emojixml_path_set = TRUE;
    return NULL;
}

static const char *ctr_img_maxsize_set(cmd_parms *parms, void *mconf,
                                       const char *arg1, const char *arg2)
{
    ctr_cfg_t *cfg = (ctr_cfg_t *)mconf;
    ctr_imgmax_carrier_t carrier = MC_NONE;
    char *name = NULL;
    char *group = NULL;
    unsigned idx;
    ctr_imgmax_t *im;

    if (arg2 == NULL)
        arg2 = arg1;
    else {
        char *cond, *tkn, *last = NULL;

        cond = apr_pstrdup(parms->pool, arg1);
        for (tkn = apr_strtok(cond, ",", &last);
             tkn != NULL; tkn = apr_strtok(NULL, ",", &last)) {
            char *key, *val, *last2 = NULL;

            key = apr_strtok(tkn, "=", &last2);
            val = apr_strtok(NULL, "=", &last2);
            if (key == NULL || val == NULL)
                goto errout1;
            key = ctr_trim(key);
            val = ctr_trim(val);

            if (strcmp(key, "carrier") == 0) {
                if (strcmp(val, "docomo") == 0)
                    carrier = MC_DOCOMO;
                else if (strcmp(val, "kddi") == 0)
                    carrier = MC_KDDI;
                else if (strcmp(val, "softbank") == 0 ||
                         strcmp(val, "vodafone") == 0)
                    carrier = MC_SOFTBANK;
                else if (strcmp(val, "willcom") == 0)
                    carrier = MC_WILLCOM;
                else
                    goto errout1;
            } else if (strcmp(key, "name") == 0)
                name = val;
            else if (strcmp(key, "group") == 0)
                group = val;
            else
                goto errout1;
        }
    }

    idx = strlen(arg2);
    idx--;

    /*
     * arg2 is image size.  It must be one of "NNN%", "NNN", "NNNk", "NNNK",
     * "NNNm", "NNNM", "NNNg", "NNNG".  N is a digit character.  k or K means
     * kilo-bytes, m or M does mega-bytes, and g or G does giga-bytes.
     */
    if (!apr_isdigit(arg2[idx])) {
        char *s;
        long v, v2;

        s = apr_pstrndup(parms->pool, arg2, idx);
        if ((v = v2 = (long)apr_atoi64(s)) <= 0)
            goto errout2;

        if (arg2[idx] == '%') {
            if (v > 100) {
                /* must be between 1% and 100% */
                goto errout2;
            }
        } else {
            if (arg2[idx] == 'k' || arg2[idx] == 'K')
                v2 = v * 1024;
            else if (arg2[idx] == 'm' || arg2[idx] == 'M')
                v2 = v * 1024 * 1024;
            else if (arg2[idx] == 'g' || arg2[idx] == 'G')
                v2 = v * 1024 * 1024 * 1024;
            else
                goto errout2;

            if (v2 < v)
                goto errout2;

            arg2 = apr_ltoa(parms->pool, v2);
        }
    }

    im = apr_array_push(cfg->imgmaxes);
    im->carrier = carrier;
    im->name = name;
    im->group = group;
    im->size = arg2;
    return NULL;

errout1:
    return "C3ImageMaxSize: invalid first parameter";
errout2:
    return "C3ImageMaxSize: invalid size specified";
}

static void ctr_child_init(apr_pool_t *pool, server_rec *server)
{
    ctr_img_child_init(pool, server);
}

static int ctr_pre_config(apr_pool_t *pool, apr_pool_t *plog,
                          apr_pool_t *ptemp)
{
    char *s;

    if (apr_filepath_merge(&s, DEFAULT_EXP_SYSCONFDIR,
                           "browsers.xml", 0, pool) != APR_SUCCESS)
        brwxml_path = NULL;
    else
        brwxml_path = ap_server_root_relative(pool, s);

    brwxml_autodl = TRUE;
    brwxml_uri = CTR_BRWXML_URI;

    return OK;
}

static int ctr_post_config(apr_pool_t *pool, apr_pool_t *plog,
                           apr_pool_t *ptemp, server_rec *server)
{
    static const char *udata_key = "__mod_cthree_fstflg";
    void *udata;
    ctr_cfg_t *cfg;
    int errflg;

    /*
     * When this function is called for the first time, there is nothing to
     * do, because Apache restarts just after returning from this function and
     * this function is called again after the restart.
     */
    apr_pool_userdata_get(&udata, udata_key, server->process->pool);
    if (udata == NULL) {
        apr_pool_userdata_set("1", udata_key, apr_pool_cleanup_null,
                              server->process->pool);
        return OK;
    }

    ap_add_version_component(pool, "mod_cthree/" CTR_VERSION);

    /*
     * Read browsers xml and intialize browser information.
     */

    brwhash = NULL;

    if (brwxml_autodl && brwxml_uri != NULL) {
        /*
         * Trying to download browsers xml from remote server.
         */
        apr_file_t *fd;
        ctr_brwxml_data_t brwdata;
        const char *errmsg;

        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server,
                     "mod_cthree: trying to read "
                     "remote browsers xml [%s] ...", brwxml_uri);

        if ((errmsg = ctr_brwxml_download(brwxml_uri, &fd, ptemp)) != NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                         "mod_cthree: failed to download browsers xml: %s: %s",
                         brwxml_uri, errmsg);
            goto try_local;
        }

        if ((errmsg = ctr_brwxml_read(&brwdata, fd, pool)) != NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                         "mod_cthree: downloaded browsers xml: %s", errmsg);
            apr_file_close(fd);
            goto try_local;
        }

        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server,
                     "mod_cthree: done "
                     "(docomo %d, kddi %d, softbank %d, willcom %d)",
                     brwdata.docomo_cnt, brwdata.kddi_cnt,
                     brwdata.softbank_cnt, brwdata.willcom_cnt);

        if (brwxml_path != NULL) {
            /*
             * Copy downloaded browsers xml to local one.
             */
            if (ctr_brwxml_local_copy(fd, brwxml_path, ptemp) != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                             "mod_cthree: failed "
                             "to copy downloaded browsers xml to local one");
            }
        }

        brwhash = brwdata.brwhash;
        apr_file_close(fd);
    }

    try_local:
    if (brwhash == NULL && brwxml_path != NULL) {
        /*
         * Trying to read local browsers xml.
         */
        apr_file_t *fd;
        ctr_brwxml_data_t brwdata;
        const char *errmsg;

        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server,
                     "mod_cthree: trying to read "
                     "local browsers xml [%s] ...", brwxml_path);

        if (apr_file_open(&fd, brwxml_path, APR_READ,
                          APR_OS_DEFAULT, pool) != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                         "mod_cthree: failed to open local browsers xml: %s",
                         brwxml_path);
        } else {
            if ((errmsg = ctr_brwxml_read(&brwdata, fd, pool)) != NULL) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                             "mod_cthree: local browsers xml: %s", errmsg);
            } else {
                ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, server,
                             "mod_cthree: done "
                             "(docomo %d, kddi %d, softbank %d, willcom %d)",
                             brwdata.docomo_cnt, brwdata.kddi_cnt,
                             brwdata.softbank_cnt, brwdata.willcom_cnt);
                brwhash = brwdata.brwhash;
            }

            apr_file_close(fd);
        }
    }

    if (brwhash == NULL) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                     "mod_cthree: disable browser info support");
    }

    /*
     * Read emoji xml and intialize emoji information.
     */

    for (cfg = ctr_cfg_list; cfg != NULL; cfg = cfg->next) {
        if (cfg->emojixml_path == NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                         "mod_cthree: failed to get emoji xml path");
            errflg = TRUE;
        } else {
            ctr_cfg_t *cfg2;

            errflg = FALSE;

            cfg->emojixml_path = ap_server_root_relative(pool,
                                                         cfg->emojixml_path);

            /*
             * Check if the same emoji xml has already been parsed.  If so, we
             * has not to parse again, reuse the result.
             */
            for (cfg2 = ctr_cfg_list; cfg2 != cfg; cfg2 = cfg2->next) {
                if (!strcmp(cfg->emojixml_path, cfg2->emojixml_path)) {
                    cfg->emojihash = cfg2->emojihash;
                    break;
                }
            }
            if (cfg2 != cfg)
                continue;

            if (!ctr_emojixml_read(cfg, server, NULL, pool))
                errflg = TRUE;
        }
        if (errflg) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                         "mod_cthree: disable emoji conversion");
        }
    }

    return OK;
}

static int ctr_post_read_request(request_rec *r)
{
    apr_pool_t *pool = r->pool;
    char *newargs, *newuri;

    /*
     * Manage mod_cthree specific parameters.
     */
    ctr_img_post_read_request(r);
    newargs = ctr_hdml_params_manage(r->args, r);

    if (r->path_info) {
        newuri = apr_pstrcat(pool, r->uri, r->path_info,
                             newargs != NULL ? "?" : NULL, newargs, NULL);
    } else {
        newuri = apr_pstrcat(pool, r->uri,
                             newargs != NULL ? "?" : NULL, newargs, NULL);
    }

    /*
     * Make the request object not to have mod_cthree specific parameters
     * any more.
     */
    ap_parse_uri(r, newuri);

    /*
     * This is needed not to log mod_cthree specific parameters.
     */
    r->the_request = apr_pstrcat(pool, r->method, " ", r->unparsed_uri, " ",
                                 r->protocol, NULL);

    return OK;
}

static int ctr_type_checker(request_rec *r)
{
    ctr_cfg_t *cfg = ap_get_module_config(r->per_dir_config, &cthree_module);

    /*
     * If mod_cthree is not enabled, there is nothing to do.
     */
    if (!cfg->enabled)
        return DECLINED;

    return ctr_export_type_checker(r);
}

#if 0
static int ctr_header_print_cb(void *rec, const char *key, const char *val)
{
    request_rec *r = (request_rec *)rec;

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                  "mod_cthree: HEADER: [%s] -> [%s]", key, val);
    return TRUE;
}
#endif

static int ctr_fixups(request_rec *r)
{
    apr_pool_t *pool = r->pool;
    ctr_cfg_t *cfg;
    ctr_browser_t *brw;
    char *s;
    ctr_pictype_t *pt;

    cfg = ap_get_module_config(r->per_dir_config, &cthree_module);

    /*
     * If mod_cthree is not enabled, there is nothing to do.
     */
    if (!cfg->enabled)
        return DECLINED;

#if 0
    apr_table_do(ctr_header_print_cb, r, r->headers_in, NULL);
#endif

    /*
     * Set browser information into the request as HTTP headers.
     */
    brw = ctr_browser_lookup(brwhash, NULL, r, cfg->log_unknown);
    if (brw->carrier == CR_UNKNOWN && cfg->unknown_ua != NULL)
        brw = ctr_browser_lookup(brwhash, cfg->unknown_ua, r, FALSE);
    apr_table_setn(r->headers_in, "X-C3-Browser-Carrier",
                   apr_psprintf(pool, "%d", brw->carrier));
    apr_table_setn(r->headers_in, "X-C3-Browser-Name", brw->name);
    apr_table_setn(r->headers_in, "X-C3-Browser-Group", brw->group);
    apr_table_setn(r->headers_in, "X-C3-Browser-CharWidth",
                   apr_psprintf(pool, "%d", brw->charwidth));
    apr_table_setn(r->headers_in, "X-C3-Browser-CharHeight",
                   apr_psprintf(pool, "%d", brw->charheight));
    apr_table_setn(r->headers_in, "X-C3-Browser-PixWidth",
                   apr_psprintf(pool, "%d", brw->pixwidth));
    apr_table_setn(r->headers_in, "X-C3-Browser-PixHeight",
                   apr_psprintf(pool, "%d", brw->pixheight));
    apr_table_setn(r->headers_in, "X-C3-Browser-WPixWidth",
                   apr_psprintf(pool, "%d", brw->wpixwidth));
    apr_table_setn(r->headers_in, "X-C3-Browser-WPixHeight",
                   apr_psprintf(pool, "%d", brw->wpixheight));
    apr_table_setn(r->headers_in, "X-C3-Browser-Colors",
                   apr_psprintf(pool, "%d", brw->colors));
    s = "";
    for (pt = brw->pictypes; pt != NULL; pt = pt->next) {
        if (*s == '\0')
            s = apr_itoa(pool, pt->type);
        else
            s = apr_pstrcat(pool, s, ",", apr_itoa(pool, pt->type), NULL);
    }
    apr_table_setn(r->headers_in, "X-C3-Browser-PicTypes", s);
    apr_table_setn(r->headers_in, "X-C3-Browser-CacheSize",
                   apr_psprintf(pool, "%d", brw->cachesize));
    apr_table_setn(r->headers_in, "X-C3-Browser-IsCacheIncBoth",
                   apr_psprintf(pool, "%d", brw->iscacheincboth));
    if (brw->carrier == CR_DOCOMO) {
        if (brw->spec.docomo.htmlver != -1.0) {
            apr_table_setn(r->headers_in, "X-C3-Browser-Docomo-HtmlVer",
                           apr_psprintf(pool, "%.1f",
                                        brw->spec.docomo.htmlver));
        } else
            apr_table_setn(r->headers_in, "X-C3-Browser-Docomo-HtmlVer", "");
        if (brw->spec.docomo.xhtmlver != -1.0) {
            apr_table_setn(r->headers_in, "X-C3-Browser-Docomo-XHtmlVer",
                          apr_psprintf(pool, "%.1f",
                                       brw->spec.docomo.xhtmlver));
        } else
            apr_table_setn(r->headers_in, "X-C3-Browser-Docomo-XHtmlVer", "");
        apr_table_setn(r->headers_in, "X-C3-Browser-Docomo-IsFoma",
                       apr_psprintf(pool, "%d", brw->spec.docomo.isfoma));
    } else if (brw->carrier == CR_KDDI) {
        if (brw->spec.kddi.realname != NULL) {
            apr_table_setn(r->headers_in, "X-C3-Browser-Kddi-RealName",
                           brw->spec.kddi.realname);
        } else
            apr_table_setn(r->headers_in, "X-C3-Browser-Kddi-RealName", "");
        apr_table_setn(r->headers_in, "X-C3-Browser-Kddi-IsWap20",
                       apr_psprintf(pool, "%d", brw->spec.kddi.iswap20));
        apr_table_setn(r->headers_in, "X-C3-Browser-Kddi-IsTuka",
                       apr_psprintf(pool, "%d", brw->spec.kddi.istuka));
    } else if (brw->carrier == CR_SOFTBANK) {
        apr_table_setn(r->headers_in, "X-C3-Browser-Softbank-IsGetOnly",
                       apr_psprintf(pool, "%d", brw->spec.softbank.isgetonly));
        apr_table_setn(r->headers_in, "X-C3-Browser-Softbank-IsPacket",
                       apr_psprintf(pool, "%d", brw->spec.softbank.ispacket));
        apr_table_setn(r->headers_in, "X-C3-Browser-Softbank-IsXhtml",
                       apr_psprintf(pool, "%d", brw->spec.softbank.isxhtml));
    }

    return OK;
}

static int ctr_handler(request_rec *r)
{
    static int (*handlers[])(request_rec *)  = {
        ctr_entry_handler, ctr_chkon_handler, ctr_chkoff_handler,
        ctr_radio_handler, ctr_choice_handler, NULL
    };
    ctr_cfg_t *cfg = ap_get_module_config(r->per_dir_config, &cthree_module);
    int (**h)(request_rec *);
    int rv;

    /*
     * If mod_cthree is not enabled, there is nothing to do.
     */
    if (!cfg->enabled)
        return DECLINED;

    h = handlers;
    while (*h) {
        if ((rv = (*h)(r)) != DECLINED)
            return rv;
        h++;
    }

    return DECLINED;
}

static void ctr_insert_filter(request_rec *r)
{
    ctr_cfg_t *cfg = ap_get_module_config(r->per_dir_config, &cthree_module);

    ap_add_input_filter("cthree-in", NULL, r, r->connection);
    if (cfg->enabled)
        ap_add_output_filter("cthree-out", NULL, r, r->connection);
}

static int ctr_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
                            ap_input_mode_t mode, apr_read_type_e block,
                            apr_off_t nbytes)
{
    request_rec *r = f->r;
    conn_rec *c = r->connection;
    apr_pool_t *pool = r->pool;
    const char *ctype;
    ctr_inpctx_t *ctx = f->ctx;
    apr_bucket *b;
    apr_status_t rv;

    ctype = apr_table_get(r->headers_in, "Content-Type");

    if (mode != AP_MODE_READBYTES || r->method_number != M_POST ||
        ctype == NULL ||
        !ctr_startwith(ctype, "application/x-www-form-urlencoded")) {
        /*
         * We does not need to do conversion.  Simply return the buckets that
         * next filter does.
         */
        return ap_get_brigade(f->next, bb, mode, block, nbytes);
    }

    if (!ctx) {
        apr_bucket_brigade *newbb = apr_brigade_create(pool, c->bucket_alloc);
        const char *raw = NULL;
        int eos = FALSE;

        f->ctx = ctx = apr_palloc(pool, sizeof(*ctx));
        ctx->conved = NULL;
        ctx->left = 0;

        /*
         * Loop until we get a EOS bucket.  mod_cthree needs all data to
         * convert it.  So, pre-fetch all data even if the caller requests a
         * part of it.
         */
        for (;;) {
            if ((rv = ap_get_brigade(f->next, newbb, AP_MODE_READBYTES,
                                     block, 8192)) != APR_SUCCESS)
                return rv;

            for (;;) {
                if (APR_BRIGADE_EMPTY(newbb))
                    break;

                b = APR_BRIGADE_FIRST(newbb);

                if (!APR_BUCKET_IS_METADATA(b)) {
                    const char *p;
                    apr_size_t len;

                    apr_bucket_read(b, &p, &len, block);
                    p = apr_pstrndup(pool, p, len);
                    if (raw == NULL)
                        raw = p;
                    else
                        raw = apr_pstrcat(pool, raw, p, NULL);
                } else if (APR_BUCKET_IS_EOS(b)) {
                    apr_bucket_delete(b);
                    eos = TRUE;
                    break;
                }

                apr_bucket_delete(b);
            }

            if (eos)
                break;
        }

        if (raw != NULL) {
            ctx->conved = ctr_hdml_params_manage(raw, r);
            if (ctx->conved != NULL)
                ctx->left = strlen(ctx->conved);
        }
    }

    if (ctx->left != 0) {
        /*
         * If there is data that has been converted, create a bucket from it
         * and return it.  Futher more, if all conveted data is returned.
         */
        if (ctx->left >= nbytes) {
            b = apr_bucket_pool_create(ctx->conved, nbytes, pool,
                                       c->bucket_alloc);
            ctx->conved += nbytes;
            ctx->left -= nbytes;
        } else {
            b = apr_bucket_pool_create(ctx->conved, ctx->left, pool,
                                       c->bucket_alloc);
            ctx->conved += ctx->left;
            ctx->left = 0;
        }
        APR_BRIGADE_INSERT_TAIL(bb, b);
    }

    if (ctx->left == 0) {
        /*
         * Return EOS bucket when there is no data.
         */
        b = apr_bucket_eos_create(c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, b);
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    request_rec *r = f->r;
    conn_rec *conn = r->connection;
    apr_pool_t *pool = r->pool;
    const char *s;
    ctr_cfg_t *cfg;
    ctr_browser_t *brw;
    apr_bucket_brigade *newbb;
    ctr_outctx_t *ctx = f->ctx;
    int eos = FALSE;
    apr_bucket *b;
    const char *buf;
    apr_size_t len;
    char *tdata;
    apr_status_t rv;

    if ((s = apr_table_get(r->headers_out, CTR_CONTROL_HEADER)) != NULL) {
        apr_table_setn(r->notes, CTR_CONTROL_HEADER, s);
        apr_table_unset(r->headers_out, CTR_CONTROL_HEADER);
    }

    cfg = ap_get_module_config(r->per_dir_config, &cthree_module);
    cfg = ctr_resctl_merge(cfg, r);

    /*
     * If mod_cthree is not enabled, there is nothing to do.
     */
    if (!cfg->enabled) {
        ap_pass_brigade(f->next, bb);
        return APR_SUCCESS;
    }

    brw = ctr_browser_lookup(brwhash, NULL, r, FALSE);
    if (brw->carrier == CR_UNKNOWN && cfg->unknown_ua != NULL)
        brw = ctr_browser_lookup(brwhash, cfg->unknown_ua, r, FALSE);

    /*
     * mod_cthree does no conversion if one of the following conditions is
     * matched.
     *
     * 1) Content-Type is unknown.
     * 2) Content-Type is not "text/html", "application/xhtml+xml"  and
     *    "image/xxx".
     * 3) C3ConvText is set to Off and Cotent-Type is text/html.
     * 4) User-Agent is unkown and C3ConvUnknownAs is not set or C3ConvUnknown
     *    is set to Off (default).
     * 5) C3ConvIHTML is set to Off and the browser is a DoCoMo's one.
     */
    if (r->content_type == NULL ||
        (!ctr_startwith(r->content_type, "text/html") &&
         !ctr_startwith(r->content_type, "application/xhtml+xml") &&
         !ctr_startwith(r->content_type, "image/")) ||
        (!cfg->conv_text && ctr_startwith(r->content_type, "text/html")) ||
        (brw->carrier == CR_UNKNOWN && !cfg->conv_unknown) ||
        (!cfg->ihtml_conv && brw->carrier == CR_DOCOMO)) {
        newbb = bb;
        goto out;
    }

    if (!ctx) {
        f->ctx = ctx = apr_palloc(pool, sizeof(*ctx));
        ctx->data = NULL;
        ctx->datalen = 0;
    }

    for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);
         b = APR_BUCKET_NEXT(b)) {
        if (!APR_BUCKET_IS_METADATA(b)) {
            /*
             * Get a pointer to data in a bucket.
             */
            if ((rv = apr_bucket_read(b, &buf, &len,
                                      APR_BLOCK_READ)) != APR_SUCCESS) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "mod_cthree: unable to read data from bucket");
                return rv;
            }

            if (ctx->datalen == 0) {
                tdata = apr_palloc(pool, len + 1);
                memcpy(tdata, buf, len);
            } else {
                tdata = apr_palloc(pool, ctx->datalen + len + 1);
                memcpy(tdata, ctx->data, ctx->datalen);
                memcpy(tdata + ctx->datalen, buf, len);
            }
            ctx->data = tdata;
            ctx->datalen += len;
            *(ctx->data + ctx->datalen) = '\0';
        } else if (APR_BUCKET_IS_EOS(b)) {
            /*
             * Found eos (End Of Stream) bucket.  Stop reading.
             */
            eos = TRUE;
            break;
        } else {
            /* ignore bucket */
        }
    }

    if (!eos) {
        /*
         * If data continues further, we cannot convert it yet.  Just return
         * here and we don't pass brigates to next filter until receiving an
         * eos bucket.
         */
        return APR_SUCCESS;
    }

    newbb = apr_brigade_create(pool, conn->bucket_alloc);

    if (ctx->datalen == 0)
        goto out2;

    if (ctr_startwith(r->content_type, "text/html") ||
        ctr_startwith(r->content_type, "application/xhtml+xml")) {
        /* i-mode html or xhtml conversion */
        CTR_CLASS_NAME(page) *page;
        int flg;

        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                      "mod_cthree: converting a html page");

        if (cfg->emojihash == NULL &&
            !ctr_emojixml_read(cfg, r->server, r, pool)) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_cthree: disable emoji conversion");
        }

        /*
         * Parse the input data, and construct an object tree.
         */
        flg = 0;
        if (cfg->log_invhtml)
            flg |= PF_WARN_OUT;
        page = ctr_page_new(pool, "", -1, -1, -1, -1);
        if ((rv = ctr_html_parse(page, ctx->data, flg, r)) != APR_SUCCESS)
            return rv;
        ctx->data = NULL;
        ctx->datalen = 0;

        /*
         * Covert the object tree to a backet.
         */
        flg = 0;
        if (cfg->hdml_wap20)
            flg |= CF_HDML_WAP20;
        if (cfg->exp_subcards)
            flg |= CF_HDML_EXPSUBCARDS;
        if (cfg->conv_unknown)
            flg |= CF_CONV_UNKNOWN;
        if (cfg->log_convedtype)
            flg |= CF_LOG_CONVEDTYPE;
        if ((b = ctr_page_convert(page, flg, brw, cfg->emojihash, r)) != NULL)
            APR_BRIGADE_INSERT_TAIL(newbb, b);
    } else {
        /* image conversion */
        ctr_srvcfg_t *srvcfg;
        ctr_imgmax_t *ims = (ctr_imgmax_t *)cfg->imgmaxes->elts;
        ctr_imgmax_t *matched = NULL;
        int i;

        srvcfg = ap_get_module_config(r->server->module_config,
                                      &cthree_module);

        for (i = cfg->imgmaxes->nelts - 1; i >= 0; i--) {
            ctr_imgmax_t *im = &ims[i];

            if ((im->carrier == MC_NONE ||
                 (im->carrier == MC_DOCOMO && brw->carrier == CR_DOCOMO) ||
                 (im->carrier == MC_KDDI && brw->carrier == CR_KDDI) ||
                 (im->carrier == MC_SOFTBANK && brw->carrier == CR_SOFTBANK) ||
                 (im->carrier == MC_WILLCOM && brw->carrier == CR_WILLCOM)) &&
                (im->name == NULL || strcmp(im->name, brw->name) == 0) &&
                (im->group == NULL || strcmp(im->group, brw->group) == 0)) {
                if (im->carrier != MC_NONE &&
                    im->name != NULL && im->group != NULL) {
                    matched = im;
                    break;
                }

                if (matched == NULL)
                    matched = im;
                else {
                    if (im->name != NULL) {
                        if (matched->name == NULL) {
                            matched = im;
                            continue;
                        }
                    } else if (matched->name != NULL)
                        continue;

                    if (im->group != NULL) {
                        if (matched->group == NULL) {
                            matched = im;
                            continue;
                        }
                    } else if (matched->group != NULL)
                        continue;

                    if (im->carrier != MC_NONE) {
                        if (matched->carrier == MC_NONE) {
                            matched = im;
                            continue;
                        }
                    } else if (matched->carrier != MC_NONE)
                        continue;
                }
            }
        }

        if (matched == NULL) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                          "mod_cthree: imgmax: not matched");
        } else {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                          "mod_cthree: imgmax: matched [%s]", matched->size);
        }

        if ((b = ctr_img_convert(ctx->data, ctx->datalen, brw,
                                 matched ? matched->size : NULL,
                                 srvcfg->img_cachedir, r)) != NULL)
            APR_BRIGADE_INSERT_TAIL(newbb, b);
    }

out2:
    b = apr_bucket_eos_create(conn->bucket_alloc);
    APR_BRIGADE_INSERT_TAIL(newbb, b);

    /*
     * Remove Content-Length header to re-calculate the length of the page
     * converted by mod_cthree if possible.
     */
    apr_table_unset(r->headers_out, "Content-Length");

out:
    if (cfg->cachectl) {
        apr_table_unset(r->headers_out, "Last-Modified");
        apr_table_unset(r->headers_out, "ETag");
        apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
    }

    ap_pass_brigade(f->next, newbb);

    return APR_SUCCESS;
}

static const char *ctr_brwxml_download(const char *uri, apr_file_t **fd,
                                       apr_pool_t *pool)
{
    const char *tmpdir;
    char *tmpfile;
    const char *errmsg;
    apr_off_t offset;

    if (apr_temp_dir_get(&tmpdir, pool) != APR_SUCCESS)
        return "failed to get temporary directory";

    if (apr_filepath_merge(&tmpfile, tmpdir,
                           "browsers.xml.XXXXXX", 0, pool) != APR_SUCCESS)
        return "failed to get temporary browsers xml path";

    if (apr_file_mktemp(fd, tmpfile, 0, pool) != APR_SUCCESS)
        return "failed to make temporary file";

    if ((errmsg = ctr_http_fetch(uri, "C3AutoDownload/" CTR_VERSION,
                                 *fd, pool)) != NULL) {
        apr_file_close(*fd);
        return errmsg;
    }

    offset = 0;
    if (apr_file_seek(*fd, APR_SET, &offset) != APR_SUCCESS) {
        apr_file_close(*fd);
        return "failed to set file position of temporary browsers xml";
    }

    return NULL;
}

static apr_status_t ctr_brwxml_local_copy(apr_file_t *rfd, const char *local,
                                          apr_pool_t *pool)
{
    apr_off_t offset;
    apr_finfo_t finfo;
    int backuped;
    const char *localbk;
    apr_file_t *lfd;
    char buf[8192];
    apr_size_t bufsz;
    apr_status_t rv;

    offset = 0;
    if ((rv = apr_file_seek(rfd, APR_SET, &offset)) != APR_SUCCESS)
        return rv;

    /*
     * Backup the old browsers xml.
     */
    localbk = apr_pstrcat(pool, local, ".bk", NULL);
    if ((rv = apr_stat(&finfo, local, APR_FINFO_MIN, pool)) != APR_SUCCESS)
            backuped = FALSE;
    else {
        if ((rv = apr_file_rename(local, localbk, pool)) != APR_SUCCESS)
            return rv;
        backuped = TRUE;
    }

    if ((rv = apr_file_open(&lfd, local,
                            APR_WRITE | APR_CREATE |
                            APR_TRUNCATE | APR_BUFFERED,
                            APR_OS_DEFAULT, pool)) != APR_SUCCESS) {
        if (backuped)
            apr_file_rename(localbk, local, pool);
        return rv;
    }

    for (;;) {
        bufsz = sizeof(buf);
        if ((rv = apr_file_read(rfd, buf, &bufsz)) != APR_SUCCESS)
            break;
        if ((rv = apr_file_write(lfd, buf, &bufsz)) != APR_SUCCESS) {
            apr_file_close(lfd);
            if (backuped)
                apr_file_rename(localbk, local, pool);
            return rv;
        }
    }

    apr_file_close(lfd);

    if (rv == APR_SUCCESS || rv == APR_EOF) {
        if (backuped)
            apr_file_remove(localbk, pool);
        return APR_SUCCESS;
    } else {
        if (backuped)
            apr_file_rename(localbk, local, pool);
        return rv;
    }
}

static int ctr_emojixml_read(ctr_cfg_t *cfg, const server_rec *server,
                             const request_rec *r, apr_pool_t *pool)
{
    apr_file_t *fd;
    ctr_emojixml_info_t *exinfo;
    ctr_xml_handler_t handler;
    const char *errmsg;
    apr_status_t rv;

    if ((rv = apr_file_open(&fd, cfg->emojixml_path, APR_READ,
                            APR_OS_DEFAULT, pool)) != APR_SUCCESS) {
        if (r == NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, rv, server,
                         "mod_cthree: failed to open emoji xml: %s",
                         cfg->emojixml_path);
        } else {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                          "mod_cthree: failed to open emoji xml: %s",
                          cfg->emojixml_path);
        }
        return FALSE;
    }

    exinfo = apr_palloc(pool, sizeof(*exinfo));
    exinfo->emojihash = apr_hash_make(pool);
    exinfo->stat = EX_NEUTRAL;
    CTR_XML_UNKNOWN_INIT(exinfo->unknowns, pool);
    exinfo->curinfo = NULL;
    exinfo->pool = pool;

    handler.start_element = ctr_emojixml_start_element;
    handler.end_element = ctr_emojixml_end_element;
    handler.characters = NULL;

    errmsg = ctr_xml_file_parse(&handler, fd, exinfo, pool);
    apr_file_close(fd);

    if (errmsg != NULL) {
        if (r == NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, server,
                         "mod_cthree: %s: %s", cfg->emojixml_path, errmsg);
        } else {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "mod_cthree: %s: %s", cfg->emojixml_path, errmsg);
        }
        return FALSE;
    }

    cfg->emojihash = exinfo->emojihash;
    return TRUE;
}

static const char *ctr_emojixml_start_element(void *udata, const char *name,
                                              apr_table_t *attrs)
{
    ctr_emojixml_info_t *exinfo = udata;
    apr_pool_t *pool = exinfo->pool;
    const char *id;
    ctr_emoji_info_t *info;
    enum {
        ET_IHTML = 1, ET_AUXHTML, ET_SHTML, ET_VHTML, ET_HDML, ET_WHTML
    } ttype;

    /*
     * If we are now in unknown mode, push the tag name into a unknown list,
     * and return here.
     */
    if (!CTR_XML_UNKNOWN_EMPTY(exinfo->unknowns)) {
        CTR_XML_UNKNOWN_PUSH(exinfo->unknowns, name);
        return NULL;
    }

    if (strcmp(name, "emojis") == 0) {
        if (exinfo->stat == EX_NEUTRAL)
            exinfo->stat = EX_EMOJIS;
        else
            goto errout;
    } else if (strcmp(name, "emoji") == 0) {
        if (exinfo->stat == EX_EMOJIS) {
            if ((id = apr_table_get(attrs, "id")) == NULL)
                return apr_psprintf(pool, "no id in <%s>", name);

            info = apr_palloc(exinfo->pool, sizeof(*info));
            info->ihtml = NULL;
            info->auxhtml = NULL;
            info->shtml = NULL;
            info->hdml = NULL;
            info->whtml = NULL;
            CTR_HASH_SET(exinfo->emojihash, id, info);

            exinfo->curinfo = info;
            exinfo->stat = EX_EMOJI;
        } else
            goto errout;
    } else if ((strcmp(name, "ihtml") == 0 && (ttype = ET_IHTML)) ||
               (strcmp(name, "auxhtml") == 0 && (ttype = ET_AUXHTML)) ||
               (strcmp(name, "shtml") == 0 && (ttype = ET_SHTML)) ||
               (strcmp(name, "vhtml") == 0 && (ttype = ET_VHTML)) ||
               (strcmp(name, "hdml") == 0 && (ttype = ET_HDML)) ||
               (strcmp(name, "whtml") == 0 && (ttype = ET_WHTML))) {
        if (exinfo->stat == EX_EMOJI) {
            if ((id = apr_table_get(attrs, "id")) == NULL)
                return apr_psprintf(pool, "no id in <%s>", name);

            if (ttype == ET_IHTML) {
                exinfo->curinfo->ihtml = id;
                exinfo->stat = EX_IHTML;
            } else if (ttype == ET_AUXHTML) {
                exinfo->curinfo->auxhtml = id;
                exinfo->stat = EX_AUXHTML;
            } else if (ttype == ET_SHTML) {
                exinfo->curinfo->shtml = id;
                exinfo->stat = EX_SHTML;
            } else if (ttype == ET_VHTML) {
                exinfo->curinfo->shtml = id;
                exinfo->stat = EX_VHTML;
            } else if (ttype == ET_HDML) {
                exinfo->curinfo->hdml = id;
                exinfo->stat = EX_HDML;
            } else if (ttype == ET_WHTML) {
                exinfo->curinfo->whtml = id;
                exinfo->stat = EX_WHTML;
            }
        } else
            goto errout;
    } else {
        /*
         * This tag may be a new one that is unknown for us.  Push the tag name
         * into a unknown list, and go into unknown mode.
         */
        CTR_XML_UNKNOWN_PUSH(exinfo->unknowns, name);
    }

    return NULL;

errout:
    return apr_psprintf(pool, "found <%s> at invalid place", name);
}

static const char *ctr_emojixml_end_element(void *udata, const char *name)
{
    ctr_emojixml_info_t *exinfo = udata;
    apr_pool_t *pool = exinfo->pool;

    /*
     * If we are now in unknown mode, pop the tag name from a unknown list, and
     * return here.
     */
    if (!CTR_XML_UNKNOWN_EMPTY(exinfo->unknowns)) {
        if (strcmp(CTR_XML_UNKNOWN_LAST(exinfo->unknowns), name) == 0)
            CTR_XML_UNKNOWN_POP(exinfo->unknowns);
        return NULL;
    }

    if (strcmp(name, "emojis") == 0) {
        if (exinfo->stat == EX_EMOJIS)
            exinfo->stat = EX_NEUTRAL;
        else
            goto errout;
    } else if (strcmp(name, "emoji") == 0) {
        if (exinfo->stat == EX_EMOJI) {
            exinfo->curinfo = NULL;
            exinfo->stat = EX_EMOJIS;
        } else
            goto errout;
    } else if (strcmp(name, "ihtml") == 0) {
        if (exinfo->stat == EX_IHTML)
            exinfo->stat = EX_EMOJI;
        else
            goto errout;
    } else if (strcmp(name, "auxhtml") == 0) {
        if (exinfo->stat == EX_AUXHTML)
            exinfo->stat = EX_EMOJI;
        else
            goto errout;
    } else if (strcmp(name, "shtml") == 0) {
        if (exinfo->stat == EX_SHTML)
            exinfo->stat = EX_EMOJI;
        else
            goto errout;
    } else if (strcmp(name, "vhtml") == 0) {
        if (exinfo->stat == EX_VHTML)
            exinfo->stat = EX_EMOJI;
        else
            goto errout;
    } else if (strcmp(name, "hdml") == 0) {
        if (exinfo->stat == EX_HDML)
            exinfo->stat = EX_EMOJI;
        else
            goto errout;
    } else if (strcmp(name, "whtml") == 0) {
        if (exinfo->stat == EX_WHTML)
            exinfo->stat = EX_EMOJI;
        else
            goto errout;
    }

    return NULL;

errout:
    return apr_psprintf(pool, "found </%s> at invalid place", name);
}

static ctr_cfg_t *ctr_resctl_merge(const ctr_cfg_t *cfg, const request_rec *r)
{
    ctr_cfg_t *new;
    const char *s;
    char *data, *tkn, *last;

    new = apr_palloc(r->pool, sizeof(*r));
    *new = *cfg;

    if ((s = apr_table_get(r->notes, CTR_CONTROL_HEADER)) == NULL)
        return new;

    data = apr_pstrdup(r->pool, s);

    for (tkn = apr_strtok(data, ",", &last); tkn != NULL;
         tkn = apr_strtok(NULL, ",", &last)) {
        char *key, *val, *last2;

        key = apr_strtok(tkn, "=", &last2);
        val = apr_strtok(NULL, "=", &last2);

        if (val == NULL)
            continue;

        key = ctr_trim(key);
        val = ctr_trim(val);

        if (!strcasecmp(key, "enable")) {
            if (!strcasecmp(val, "on") || !strcasecmp(val, "off"))
                new->enabled = strcasecmp(val, "off");
        } else if (!strcasecmp(key, "convtext")) {
            if (!strcasecmp(val, "on") || !strcasecmp(val, "off"))
                new->conv_text = strcasecmp(val, "off");
        }
    }

    return new;
}
