/*
 * 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_util.c 436 2006-11-11 08:52:58Z oda $
 */

#include <stdio.h>
#include "http_request.h"
#include "apr_lib.h"
#include "apr_strings.h"
#include "apr_time.h"
#include "apr_xlate.h"
#include "ctr_util.h"

static char *ctr_html_unescape_common(char *s, int istxt, apr_pool_t *pool);
static char *ctr_hdml_escape_sub(const char *s, apr_pool_t *pool, int spcflg);

char *ctr_trim(char *s)
{
    s = ctr_trim_head(s);
    ctr_trim_last(s);

    return s;
}

char *ctr_trim_head(char *s)
{
    for (; *s; s++) {
        if (!apr_isspace(*s))
            break;
    }

    return s;
}

char *ctr_trim_last(char *s)
{
    char *ep;

    for (ep = s + strlen(s); --ep >= s;) {
        if (!apr_isspace(*ep))
            break;
        *ep = '\0';
    }

    return s;
}

char *ctr_trim_newlines(char *s)
{
    s = ctr_trim_head_newlines(s);
    ctr_trim_last_newlines(s);

    return s;
}

char *ctr_trim_head_newlines(char *s)
{
    for (; *s; s++) {
        if (*s != '\r' && *s != '\n')
            break;
    }

    return s;
}

char *ctr_trim_last_newlines(char *s)
{
    char *ep;

    for (ep = s + strlen(s); --ep >= s;) {
        if (*ep != '\r' && *ep != '\n')
            break;
        *ep = '\0';
    }

    return s;
}

char *ctr_trim_head_newline(char *s)
{
    if (*s == '\r')
        s++;
    if (*s == '\n')
        s++;

    return s;
}

char *ctr_trim_last_newline(char *s)
{
    size_t len;
    char *ep;

    len = strlen(s);
    if (len == 0)
        return s;

    ep = s + len - 1;
    if (*ep == '\n')
        *ep-- = '\0';
    if (ep >= s && *ep == '\r')
        *ep = '\0';

    return s;
}

char *ctr_del_spaces(char *s)
{
    char *p, *q;

    p = q = s;
    while (*q) {
        if (!apr_isspace(*q))
            *p++ = *q;
        q++;
    }
    *p = '\0';

    return s;
}

char *ctr_unite_spaces(char *s)
{
    char *p, *q;
    int inspaces;

    p = q = s;
    inspaces = FALSE;
    while (*q) {
        if (!apr_isspace(*q)) {
            *p++ = *q;
            inspaces = FALSE;
        } else if (!inspaces) {
            *p++ = ' ';
            inspaces = TRUE;
        }
        q++;
    }
    *p = '\0';

    return s;
}

char *ctr_tolower_str(char *s)
{
    char *p;

    p = s;
    while (*p) {
        *p = apr_tolower(*p);
        p++;
    }

    return s;
}

int ctr_isspace_str(const char *s)
{
    if (!*s)
        return FALSE;

    while (*s) {
        if (!apr_isspace(*s))
            return FALSE;
        s++;
    }

    return TRUE;
}

int ctr_isdigit_str(const char *s)
{
    if (!*s)
        return FALSE;

    while (*s) {
        if (!apr_isdigit(*s))
            return FALSE;
        s++;
    }

    return TRUE;
}

int ctr_isdigit_strn(const char *s, apr_size_t len)
{
    if (!*s || len == 0)
        return FALSE;

    while (*s && len > 0) {
        if (!apr_isdigit(*s))
            return FALSE;
        s++;
        len--;
    }

    return TRUE;
}

int ctr_isxdigit_str(const char *s)
{
    if (!*s)
        return FALSE;

    while (*s) {
        if (!apr_isxdigit(*s))
            return FALSE;
        s++;
    }

    return TRUE;
}

int ctr_isxdigit_strn(const char *s, apr_size_t len)
{
    if (!*s || len == 0)
        return FALSE;

    while (*s && len > 0) {
        if (!apr_isxdigit(*s))
            return FALSE;
        s++;
        len--;
    }

    return TRUE;
}

int ctr_startwith(const char *msg, const char *word)
{
    return !strncmp(msg, word, strlen(word));
}

int ctr_casestartwith(const char *msg, const char *word)
{
    return !strncasecmp(msg, word, strlen(word));
}

int ctr_endwith(const char *msg, const char *word)
{
    apr_size_t mlen, wlen;

    if ((mlen = strlen(msg)) < (wlen = strlen(word)))
        return FALSE;

    return !strcmp(msg + (mlen - wlen), word);
}

int ctr_caseendwith(const char *msg, const char *word)
{
    apr_size_t mlen, wlen;

    if ((mlen = strlen(msg)) < (wlen = strlen(word)))
        return FALSE;

    return !strcasecmp(msg + (mlen - wlen), word);
}

void ctr_char_split(const char *src, const char **dst1,
                    const char **dst2, char sep, apr_pool_t *pool)
{
    char *p, *q;

    p = apr_pstrdup(pool, src);
    if ((q = strchr(p, sep)) != NULL) {
        *q++ = '\0';
        *dst1 = p;
        *dst2 = q;
    } else {
        *dst1 = NULL;
        *dst2 = NULL;
    }
}

char *ctr_uniqstr_get(apr_pool_t *pool)
{
    apr_time_t t;
    const static char exp[] =
        "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char buf[32];
    int i;

    t = apr_time_now();
    for (i = 0; t > sizeof(exp) - 2; i++) {
        buf[i] = exp[t % (sizeof(exp) - 1)];
        t /= sizeof(exp) - 1;
    }
    buf[i++] = exp[t];
    buf[i] = '\0';

    return apr_pstrdup(pool, buf);
}

char *ctr_uri_suffix_remove(const char *uri, const char **suffix,
                            apr_pool_t *pool)
{
    const char *p, *q;

    if (uri == NULL)
        return NULL;

    if ((p = strrchr(uri, '/')) != NULL)
        p++;
    else
        p = uri;

    q = strrchr(p, '.');
    if (q != NULL) {
        *suffix = q + 1;
        return apr_pstrndup(pool, uri, q - uri);
    } else {
        *suffix = "";
        return apr_pstrdup(pool, uri);
    }
}

apr_table_t *ctr_args_get(const request_rec *r)
{
    /*
     * XXX:
     * This function does not support multi-values in an argument.
     */

    const char *data = NULL;
    apr_table_t *argstbl;
    const char *tkn;
    char *key, *val;
    apr_status_t rv;

    if (r->method_number == M_GET)
        data = apr_pstrdup(r->pool, r->args);
    else if (r->method_number == M_POST) {
        const char *ctype;
        apr_bucket_brigade *bb;
        int eos;

        ctype = apr_table_get(r->headers_in, "Content-Type");
        if (ctype == NULL ||
            !ctr_startwith(ctype, "application/x-www-form-urlencoded"))
            return NULL;

        bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
        eos = FALSE;
        do {
            apr_bucket *b;

            rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
                                APR_BLOCK_READ, 8192);  /* XXX: 8192 */
            if (rv != APR_SUCCESS)
                return NULL;

            for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);
                 b = APR_BUCKET_NEXT(b)) {
                if (!APR_BUCKET_IS_METADATA(b)) {
                    const char *p;
                    apr_size_t len;

                    apr_bucket_read(b, &p, &len, APR_BLOCK_READ);
                    p = apr_pstrndup(r->pool, p, len);
                    if (data == NULL)
                        data = p;
                    else
                        data = apr_pstrcat(r->pool, data, p, NULL);
                } else if (APR_BUCKET_IS_EOS(b)) {
                    eos = TRUE;
                    break;
                }
            }
        } while (!eos);
    }

    if (data == NULL)
        return NULL;

    argstbl = apr_table_make(r->pool, 1);
    while (*data && (tkn = ap_getword(r->pool, &data, '&'))) {
        key = ap_getword(r->pool, &tkn, '=');
        val = ap_getword(r->pool, &tkn, '=');
        ap_unescape_url(key);
        ap_unescape_url(val);
        apr_table_setn(argstbl, key, val);
    }

    return argstbl;
}

char *ctr_uri_escape(const char *s, apr_pool_t *pool)
{
    const char *s2;
    unsigned char c;
    int i;
    char *p, *q;

    s2 = s;

    i = 0;
    while (*s) {
        c = (unsigned char)*s;

        if ((0x01 <= c && c <= 0x2c) ||
            (c == 0x2f) ||
            (0x3a <= c && c <= 0x40) ||
            (0x5b <= c && c <= 0x5e) ||
            (c == 0x60) ||
            (0x7b <= c))
            i += 2;
        s++;
    }

    s = s2;
    p = q = apr_palloc(pool, strlen(s) + i + 1);
    while (*s) {
        c = (unsigned char)*s;

        if ((0x01 <= c && c <= 0x2c) ||
            (c == 0x2f) ||
            (0x3a <= c && c <= 0x40) ||
            (0x5b <= c && c <= 0x5e) ||
            (c == 0x60) ||
            (0x7b <= c)) {
            sprintf(p, "%%%X", c);
            p += 3;
        } else
            *p++ = *s;
        s++;
    }
    *p = '\0';

    return q;
}

char *ctr_html_escape(const char *s, apr_pool_t *pool)
{
    const char *s2;
    int i;
    char *p, *q;

    s2 = s;

    i = 0;
    while (*s) {
        if (*s == '<' || *s == '>')
            i += 3;     /* 3 is length of "&lt" or "&gt" */
        else if (*s == '&')
            i += 4;     /* 4 is length of "&amp" */
        else if (*s == '"' || *s == (char)0xff)
            i += 5;     /* 5 is length of "&quot" or "&nbsp" */
        s++;
    }

    s = s2;
    p = q = apr_palloc(pool, strlen(s) + i + 1);
    while (*s) {
        if (*s == '<') {
            strncpy(p, "&lt;", 4);
            p += 4;
        } else if (*s == '>') {
            strncpy(p, "&gt;", 4);
            p += 4;
        }  else if (*s == '&') {
            strncpy(p, "&amp;", 5);
            p += 5;
        } else if (*s == '"') {
            strncpy(p, "&quot;", 6);
            p += 6;
        } else if (*s == (char)0xff) {
            strncpy(p, "&nbsp;", 6);
            p += 6;
        } else
            *p++ = *s;
        s++;
    }
    *p = '\0';

    return q;
}

static int ctr_html_unescape_sub(char *s, size_t len, char **q, int istxt,
                                 apr_pool_t *pool)
{
    char copy[6];
    uint16_t code;
    int i;

    if (strncasecmp(s, "&quot", len) == 0) {
        *(*q)++ = '"';
        return TRUE;
    } else if (strncasecmp(s, "&apos", len) == 0) {
        *(*q)++ = '\'';
        return TRUE;
    } else if (strncasecmp(s, "&amp", len) == 0) {
        *(*q)++ = '&';
        return TRUE;
    } else if (strncasecmp(s, "&lt", len) == 0) {
        *(*q)++ = '<';
        return TRUE;
    } else if (strncasecmp(s, "&gt", len) == 0) {
        *(*q)++ = '>';
        return TRUE;
    } else if (strncasecmp(s, "&nbsp", len) == 0) {
        if (istxt)
            *(*q)++ = (char)0xff;       /* mark of &nbsp; */
        else
            *(*q)++ = ' ';
        return TRUE;
    } else if (strncasecmp(s, "&copy", len) == 0) {
        *(*q)++ = '(';
        *(*q)++ = 'C';
        *(*q)++ = ')';
        return TRUE;
    } else if (strncasecmp(s, "&reg", len) == 0) {
        *(*q)++ = '(';
        *(*q)++ = 'R';
        *(*q)++ = ')';
        return TRUE;
    }

    if (strncmp(s, "&#", 2) == 0 && len <= 7) {
        if (len >= 4 && (s[2] == 'x' || s[2] == 'X') &&
            ctr_isxdigit_strn(s + 3, len - 3)) {
            /* unicode character reference */
            apr_xlate_t *xl;
            char inbuf[2], outbuf[4];
            apr_size_t inleft, outleft;

            s += 3;
            len -= 3;

            for (i = 0; i < 4 && len > 0; i++) {
                copy[i] = s[i];
                len--;
            }
            copy[i] = '\0';
            code = (uint16_t)apr_strtoi64(copy, NULL, 16);

            /*
             * translate a unicode character into shift_jis.
             */
            if (apr_xlate_open(&xl, "SHIFT_JIS", "UCS-2BE",
                               pool) != APR_SUCCESS)
                return FALSE;
            inbuf[0] = code >> 8;
            inbuf[1] = code & 0xff;
            inleft = 2;
            outleft = sizeof(outbuf);
            if (apr_xlate_conv_buffer(xl, inbuf, &inleft,
                                      outbuf, &outleft) != APR_SUCCESS ||
                inleft != 0) {
                apr_xlate_close(xl);
                return FALSE;
            }
            apr_xlate_close(xl);

            if (outbuf[0] != 0) {
                *(*q)++ = outbuf[0];
                *(*q)++ = outbuf[1];
            } else
                *(*q)++ = outbuf[1];
            return TRUE;
        } else if (len >= 3 && ctr_isdigit_strn(s + 2, len - 2)) {
            /* shift_jis character reference */
            s += 2;
            len -= 2;

            for (i = 0; i < 5 && len > 0; i++) {
                copy[i] = s[i];
                len--;
            }
            copy[i] = '\0';
            code = (uint16_t)apr_atoi64(copy);
            if (code > 0xff)
                *(*q)++ = code >> 8;
            *(*q)++ = code & 0xff;
            return TRUE;
        }
    }

    return FALSE;
}

static char *ctr_html_unescape_common(char *s, int istxt, apr_pool_t *pool)
{
    char *p, *q, *start;

    p = q = s;
    start = NULL;
    while (*p) {
        if (*p == '&') {
            if (start != NULL) {
                strncpy(q, start, p - start);
                q += p - start;
            }
            start = p;
        } else if (*p == ';' && start != NULL) {
            if (!ctr_html_unescape_sub(start, p - start, &q, istxt, pool)) {
                strncpy(q, start, p - start + 1);
                q += p - start + 1;
            }
            start = NULL;
        } else if (start == NULL)
            *q++ = *p;
        p++;
    }

    if (start != NULL) {
        strncpy(q, start, p - start);
        q += p - start;
    }
    *q = '\0';

    return s;
}

char *ctr_html_unescape(char *s, apr_pool_t *pool)
{
    return ctr_html_unescape_common(s, FALSE, pool);
}

char *ctr_html_text_unescape(char *s, apr_pool_t *pool)
{
    return ctr_html_unescape_common(s, TRUE, pool);
}

char *ctr_hdml_escape(const char *s, apr_pool_t *pool)
{
    return ctr_hdml_escape_sub(s, pool, TRUE);
}

char *ctr_hdml_text_escape(const char *s, apr_pool_t *pool)
{
    return ctr_hdml_escape_sub(s, pool, FALSE);
}

static char *ctr_hdml_escape_sub(const char *s, apr_pool_t *pool, int spcflg)
{
    char *p, *q;
    size_t len;

    s = p = ctr_html_escape(s, pool);

    len = strlen(p);
    while (*p) {
        if (*p == '$')
            len += 4;   /* 4 is length of "dol;" */
        else if (*p == ' ' && spcflg)
            len += 5;   /* 5 is length of "nbsp;" */
        p++;
    }

    q = p = apr_palloc(pool, len + 1);
    while (*s) {
        if (*s == '$') {
            strcpy(p, "&dol;");
            p += 5;
        } else if (*s == ' ' && spcflg) {
            strcpy(p, "&nbsp;");
            p += 6;
        } else
            *p++ = *s;
        s++;
    }
    *p = '\0';

    return q;
}

char *ctr_hdml_vars_escape(const char *s, apr_pool_t *pool)
{
    const char *s2;
    int i;
    char nc;
    char *p, *q;

    s2 = s;

    i = 0;
    nc = '\0';
    while (*s) {
        if (*s == '"' || *s == '&' || *s == '<' || *s == '>' || *s == '$' ||
            *s == '=' || *s == '+' || *s == '%' || *s == ';' || *s == ' ')
            i += 2;
        else if (*s == '\r' && (nc == '\0' || nc == '\r')) {
            i += 5;
            nc = '\r';
        } else if (*s == '\n' && (nc == '\0' || nc == '\n')) {
            i += 5;
            nc = '\n';
        }
        s++;
    }

    s = s2;
    q = p = apr_palloc(pool, strlen(s) + i + 1);
    while (*s) {
        if (*s == '"' || *s == '&' || *s == '<' || *s == '>' || *s == '$' ||
            *s == '=' || *s == '+' || *s == '%' || *s == ';' || *s == ' ') {
            sprintf(p, "%%%02X", *s);
            p += 3;
        } else if (*s == nc) {
            sprintf(p, "%%%02X%%%02X", '\r', '\n');
            p += 6;
        } else if (*s != '\r' && *s != '\n')
            *p++ = *s;
        s++;
    }
    *p = '\0';

    return q;
}
