/* 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.
 */

/*
 * Written by Ian Holsman
 *
 */

#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_hash.h"
#include "apr_optional.h"

#define APR_WANT_STRFUNC
#include "apr_want.h"

#include "ap_config.h"
#include "mod_log_config.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_connection.h"
#include "http_protocol.h"

module AP_MODULE_DECLARE_DATA valid_utf8_module;

static const char utf8_filter_name[] = "UTF8VALIDATOR";


typedef struct utf8_ctx {
    apr_bucket_brigade *bb;
} utf8_ctx;

static char* validate_buffer( apr_pool_t *p,  const char* inbuf, apr_size_t* length)
{
    apr_size_t i=0;
    apr_size_t j=0;
    apr_size_t k=0;
    apr_size_t len=*length;

    char *buffer = apr_palloc( p, len+1);
    /* step 1. remove the encodings */
    while (i < len ) {
        if (inbuf[i] == '%' ) {
            if ((i < len -2 ) && ishexnumber( inbuf[i+1]) && ishexnumber( inbuf[i+2])) {
                int num;
                if (isnumber( inbuf[i+1] ) ) {
                    num = inbuf[i+1]-'0';
                }
                else {
                    num = 10 + inbuf[i+1]-'A';
                }
                num *= 16;
                if (isnumber( inbuf[i+2] ) ) {
                    num += inbuf[i+2]-'0';
                }
                else {
                    num += 10 + inbuf[i+2]-'A';
                }
                buffer[j++] = num;
                i += 3;
            }
            else {
                buffer[j++]=inbuf[i++];
            }

        }
        else {
            buffer[j++]=inbuf[i++];
        }
    }
    *length=j;
    buffer[j]=0;
    /* step 2. check for validity */
    while (k<j) {
        int ch1 = buffer[k];
        int ch2 = buffer[k+1];

        if (ch1  >= '\xf5' )  {
            return NULL;
        }
        if ( ch1 == '\xc0' || ch1  == '\xc1' ) {
            if (ch2 != 0 ) {
                return NULL;
            }
        }
        if (  ch1 == '\xe0' ) {
            if (ch2 < '\xa0' ) {
                return NULL;
            }
        }
        if ( ch1 == '\xf0' ) {
            if (ch2 < '\x90' ) {
                return NULL;
            }
        }
        k++;
    }

    return (char*)buffer;

}

static apr_status_t utf8_in_filter(ap_filter_t *f,
                                    apr_bucket_brigade *bb,
                                    ap_input_mode_t mode,
                                    apr_read_type_e block,
                                    apr_off_t readbytes) {
    apr_status_t rv;
    utf8_ctx *ctx = f->ctx;

    if (!ctx) {
        ctx = apr_pcalloc( f->c->pool, sizeof(*ctx));
        ctx->bb = apr_brigade_create( f->c->pool, f->c->bucket_alloc );
    }
    rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
    if ( rv != APR_SUCCESS ) {
        return rv;
    }

    while ( !APR_BRIGADE_EMPTY(ctx->bb) ) {
        apr_size_t length;
        char *buffer;
        const char *data;
        apr_bucket *cpy;
        int i ;
        apr_bucket *b;
        
        b = APR_BRIGADE_FIRST(ctx->bb );
        APR_BUCKET_REMOVE(b);
        if ( APR_BUCKET_IS_EOS(b) ) {
            APR_BRIGADE_INSERT_TAIL( bb, b);
            break;
        }
        length = b->length;

        rv = apr_bucket_read( b, &data, &length, APR_BLOCK_READ);
        if ( rv != APR_SUCCESS ) {
            return rv;
        }

        buffer = validate_buffer(f->c->pool, data, &length);
        if ( !buffer ) {
            ap_log_cerror( APLOG_MARK, APLOG_ERR, rv,f->c, "Unable to validate:%*s", b->length, data);
            return APR_EGENERAL;
        }
        ap_log_cerror( APLOG_MARK, APLOG_DEBUG, rv,f->c, "LEN:%d %*s", length, length, buffer );
        /* we throw away our buffer, as fixing up the content length and messing with the data makes this a much harder problem */
        cpy = apr_bucket_pool_create( data , b->length, f->c->pool, f->c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, cpy);

    }

    return APR_SUCCESS;


    }

static int utf8_pre_conn(conn_rec *c, void *csd) {
    ap_add_input_filter(utf8_filter_name, NULL, NULL, c);

    return OK;
}

static void register_hooks(apr_pool_t *p)
{

    ap_hook_pre_connection(utf8_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);

    ap_register_input_filter(utf8_filter_name, utf8_in_filter, NULL,
                             AP_FTYPE_NETWORK - 1);

}

module AP_MODULE_DECLARE_DATA valid_utf8_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,                       /* create per-dir config */
    NULL,                       /* merge per-dir config */
    NULL,                       /* server config */
    NULL,                       /* merge server config */
    NULL,                       /* command apr_table_t */
    register_hooks              /* register hooks */
};
