/**
 * Copyright 2012, 2013, 2014 Nick Galbreath
 * nickg@client9.com
 * BSD License -- see COPYING.txt for details
 *
 * This is for testing against files in ../data/ *.txt
 * Reads from stdin or a list of files, and emits if a line
 * is a SQLi attack or not, and does basic statistics
 *
 */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "libinjection.h"
#include "libinjection_html5.h"
#include "libinjection_xss.h"

int urlcharmap(char ch);
size_t modp_url_decode(char *dest, const char *s, size_t len);
const char *h5_type_to_string(enum html5_type x);
void print_html5_token(h5_state_t *hs);

int urlcharmap(char ch) {
    switch (ch) {
    case '0':
        return 0;
    case '1':
        return 1;
    case '2':
        return 2;
    case '3':
        return 3;
    case '4':
        return 4;
    case '5':
        return 5;
    case '6':
        return 6;
    case '7':
        return 7;
    case '8':
        return 8;
    case '9':
        return 9;
    case 'a':
    case 'A':
        return 10;
    case 'b':
    case 'B':
        return 11;
    case 'c':
    case 'C':
        return 12;
    case 'd':
    case 'D':
        return 13;
    case 'e':
    case 'E':
        return 14;
    case 'f':
    case 'F':
        return 15;
    default:
        return 256;
    }
}

size_t modp_url_decode(char *dest, const char *s, size_t len) {
    const char *deststart = dest;

    size_t i = 0;
    int d = 0;
    while (i < len) {
        switch (s[i]) {
        case '+':
            *dest++ = ' ';
            i += 1;
            break;
        case '%':
            if (i + 2 < len) {
                d = (urlcharmap(s[i + 1]) << 4) | urlcharmap(s[i + 2]);
                if (d < 256) {
                    *dest = (char)d;
                    dest++;
                    i += 3; /* loop will increment one time */
                } else {
                    *dest++ = '%';
                    i += 1;
                }
            } else {
                *dest++ = '%';
                i += 1;
            }
            break;
        default:
            *dest++ = s[i];
            i += 1;
        }
    }
    *dest = '\0';
    return (size_t)(dest - deststart); /* compute "strlen" of dest */
}

const char *h5_type_to_string(enum html5_type x) {
    switch (x) {
    case DATA_TEXT:
        return "DATA_TEXT";
    case TAG_NAME_OPEN:
        return "TAG_NAME_OPEN";
    case TAG_NAME_CLOSE:
        return "TAG_NAME_CLOSE";
    case TAG_NAME_SELFCLOSE:
        return "TAG_NAME_SELFCLOSE";
    case TAG_DATA:
        return "TAG_DATA";
    case TAG_CLOSE:
        return "TAG_CLOSE";
    case ATTR_NAME:
        return "ATTR_NAME";
    case ATTR_VALUE:
        return "ATTR_VALUE";
    case TAG_COMMENT:
        return "TAG_COMMENT";
    case DOCTYPE:
        return "DOCTYPE";
    default:
        assert(0);
    }
}

void print_html5_token(
    h5_state_t *hs) { // cppcheck-suppress constParameterPointer
    char *tmp = (char *)malloc(hs->token_len + 1);
    if (tmp == NULL) {
        fprintf(stderr, "out of memory\n");
        exit(1);
    }
    memcpy(tmp, hs->token_start, hs->token_len);
    /* TODO.. encode to be printable */
    tmp[hs->token_len] = '\0';

    printf("%s,%d,%s\n", h5_type_to_string(hs->token_type), (int)hs->token_len,
           tmp);

    free(tmp);
}

int main(int argc, const char *argv[]) {
    size_t slen;
    h5_state_t hs;
    char *copy;
    int offset = 1;
    int flag = 0;
    int urldecode = 0;
    injection_result_t h5_result;
    injection_result_t xss_result;

    if (argc < 2) {
        fprintf(stderr, "need more args\n");
        return 1;
    }

    while (offset < argc) {
        if (strcmp(argv[offset], "-u") == 0) {
            offset += 1;
            urldecode = 1;

        } else if (strcmp(argv[offset], "-f") == 0) {
            offset += 1;
            flag = atoi(argv[offset]);
            offset += 1;
        } else {
            break;
        }
    }

    /* ATTENTION: argv is a C-string, null terminated.  We copy this
     * to it's own location, WITHOUT null byte.  This way, valgrind
     * can see if we run past the buffer.
     */

    slen = strlen(argv[offset]);
    copy = (char *)malloc(slen);
    if (copy == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    memcpy(copy, argv[offset], slen);
    if (urldecode) {
        slen = modp_url_decode(copy, copy, slen);
    }

    libinjection_h5_init(&hs, copy, slen, (enum html5_flags)flag);
    while ((h5_result = libinjection_h5_next(&hs)) ==
           LIBINJECTION_RESULT_TRUE) {
        print_html5_token(&hs);
    }

    /* Check for parser error */
    if (h5_result == LIBINJECTION_RESULT_ERROR) {
        fprintf(stderr, "error: HTML5 parser encountered an error\n");
        free(copy);
        return -1;
    }

    xss_result = libinjection_is_xss(copy, slen, flag);
    if (xss_result == LIBINJECTION_RESULT_ERROR) {
        fprintf(stderr, "error: XSS parser encountered an error\n");
        free(copy);
        return -1;
    } else if (xss_result == LIBINJECTION_RESULT_TRUE) {
        printf("is injection!\n");
    }

    free(copy);
    return 0;
}
