/*
 * Portions taken from libapreq 1.33.
 * Copyright 2007 The Apache Software Foundation.
 * Used under the Apache License v2.0.
 * http://search.cpan.org/~stas/libapreq-1.33/
 */

#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#include "parser.h"

static
void
req_plustospace(char* str)
{
    register int x;
    for(x=0;str[x];x++)
        if(str[x] == '+')
            str[x] = ' ';
}

static
char
x2c(char* what)
{
    register char digit;

    digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
    digit *= 16;
    digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));
    return digit;
}

static
unsigned int
utf8_convert(char* str)
{
    long x = 0;
    int i = 0;
    while (i < 4 ) {
        if ( isxdigit(str[i]) != 0 ) {
            if( isdigit(str[i]) != 0 ) {
                x = x * 16 + str[i] - '0';
            }
            else {
                str[i] = tolower( str[i] );
                x = x * 16 + str[i] - 'a' + 10;
            }
        }
        else {
            return 0;
        }
        i++;
    }
    if(i < 3)
        return 0;
    return (x);
}

static
int
unescape_url_u(char* url)
{
    register int x, y, badesc, badpath;

    badesc = 0;
    badpath = 0;
    for (x = 0, y = 0; url[y]; ++x, ++y) {
        if (url[y] != '%'){
            url[x] = url[y];
        }
        else {
            if(url[y + 1] == 'u' || url[y + 1] == 'U'){
                unsigned int c = utf8_convert(&url[y + 2]);
                y += 5;
                if(c < 0x80){
                    url[x] = c;
                }
                else if(c < 0x800) {
                    url[x] = 0xc0 | (c >> 6);
                    url[++x] = 0x80 | (c & 0x3f);
                }
                else if(c < 0x10000){
                    url[x] = (0xe0 | (c >> 12));
                    url[++x] = (0x80 | ((c >> 6) & 0x3f));
                    url[++x] = (0x80 | (c & 0x3f));
                }
                else if(c < 0x200000){
                    url[x] = 0xf0 | (c >> 18);
                    url[++x] = 0x80 | ((c >> 12) & 0x3f);
                    url[++x] = 0x80 | ((c >> 6) & 0x3f);
                    url[++x] = 0x80 | (c & 0x3f);
                }
                else if(c < 0x4000000){
                    url[x] = 0xf8 | (c >> 24);
                    url[++x] = 0x80 | ((c >> 18) & 0x3f);
                    url[++x] = 0x80 | ((c >> 12) & 0x3f);
                    url[++x] = 0x80 | ((c >> 6) & 0x3f);
                    url[++x] = 0x80 | (c & 0x3f);
                }
                else if(c < 0x8000000){
                    url[x] = 0xfe | (c >> 30);
                    url[++x] = 0x80 | ((c >> 24) & 0x3f);
                    url[++x] = 0x80 | ((c >> 18) & 0x3f);
                    url[++x] = 0x80 | ((c >> 12) & 0x3f);
                    url[++x] = 0x80 | ((c >> 6) & 0x3f);
                    url[++x] = 0x80 | (c & 0x3f);
                }
            }
            else {
                if (!isxdigit(url[y + 1]) || !isxdigit(url[y + 2])) {
                    badesc = 1;
                    url[x] = '%';
                }
                else {
                    url[x] = x2c(&url[y + 1]);
                    y += 2;
                    if (url[x] == '/' || url[x] == '\0')
                        badpath = 1;
                }
            }
        }
    }
    url[x] = '\0';
    if (badesc)
        return 0;
    else if (badpath)
        return 0;
    else
        return 1;
}

static
char*
_strndup(char* str, size_t len)
{
    char *dup = (char*) malloc(len+1);
    if (dup) {
        strncpy(dup, str, len);
        dup[len] = '\0';
    }
    return dup;
}


static
char*
urlword(char** line)
{
    char* res = 0;
    char* pos = *line;
    char ch;

    while ( (ch = *pos) != '\0' && ch != ';' && ch != '&') {
        ++pos;
    }

    res = _strndup(*line, pos - *line);

    while (ch == ';' || ch == '&') {
        ++pos;
        ch = *pos;
    }

    *line = pos;

    return res;
}

char*
getword(char** line, char stop)
{
    char* pos = *line;
    int len;
    char* res;

    while ((*pos != stop) && *pos) {
        ++pos;
    }

    len = pos - *line;
    res = (char*)malloc(len + 1);
    memcpy(res, *line, len);
    res[len] = 0;

    if (stop) {
        while (*pos == stop) {
            ++pos;
        }
    }
    *line = pos;

    return res;
}

SV*
_split_to_parms(char* data)
{
    char* val;
    HV* hash = 0;

    while (*data && (val = urlword(&data))) {
        char* val_orig = val;
        char* key = getword(&val, '=');

        req_plustospace((char*)key);
        unescape_url_u((char*)key);
        req_plustospace((char*)val);
        unescape_url_u((char*)val);

        if (!hash) {
            hash = newHV();
        }

        int klen = strlen(key);
        SV* newval = newSVpv(val, 0);

        if (hv_exists(hash, key, klen)) {
            /* this param already exists */

            SV** entry = hv_fetch(hash, key, klen, 0);
            if (!entry) {
                return 0;
            }

            if (SvROK(*entry) && SvTYPE(SvRV(*entry)) == SVt_PVAV) {
                /* already an arrayref, just push to the end */
                av_push((AV*) SvRV(*entry), newval);
            }
            else {
                /* just a scalar; wrap the new and old values in an arrayref */
                SV* values[2] = { *entry, newval };
                AV* array = av_make(2, values);      /* this copies the SVs... */
                SvREFCNT_dec(newval);                /* ... so destroy the original. */
                SV* aref = newRV_noinc((SV*) array); /* create an array ref... */
                hv_store(hash, key, klen, aref, 0);  /* ... and stash it in the hash */
            }
        }
        else {
            /* no existing param, pop this one in */
            hv_store(hash, key, klen, newval, 0);
        }

        free(key);
        free(val_orig);
    }

    return hash ? newRV_noinc((SV*) hash) : 0;
}