/* 
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2008, Eric des Courtis <eric.des.courtis@benbria.com>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Eric des Courtis <eric.des.courtis@benbria.com>
 * Copyright (C) Benbria. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * Eric des Courtis <eric.des.courtis@benbria.com>
 *
 *
 * http_req.c -- HTTP client implementation
 *
 */

#include "http_req.h"

/*
extern int dprintf(int, const char *, ...);
extern int isblank(int);
*/

/* NOTE: v = version, header = h, status = s, newline = n, phrase = p */
static accept_state_t state_start(char c, state_machine_t *sm);
static accept_state_t state_shp  (char c, state_machine_t *sm);
static accept_state_t state_n    (char c, state_machine_t *sm);
static accept_state_t state_vhp_A(char c, state_machine_t *sm);
static accept_state_t state_vhp_B(char c, state_machine_t *sm);
static accept_state_t state_vhp_C(char c, state_machine_t *sm);
static accept_state_t state_vhp_D(char c, state_machine_t *sm);
static accept_state_t state_vhp_E(char c, state_machine_t *sm);
static accept_state_t state_vhp_F(char c, state_machine_t *sm);
static accept_state_t state_vhp_G(char c, state_machine_t *sm);
static accept_state_t state_vhp_H(char c, state_machine_t *sm);
static accept_state_t state_hp_A (char c, state_machine_t *sm);
static accept_state_t state_hp_B (char c, state_machine_t *sm);
static accept_state_t state_p    (char c, state_machine_t *sm);
static accept_state_t state_error(char c, state_machine_t *sm);

static int read_all(int s, char *buf, size_t len);

#ifdef DEBUG
int main(int argc, char *argv[])
{
    http_response_t response;
    http_request_t request;
    int ret;
    int i;

    if(argc != 2) return EXIT_FAILURE;

    request.method = GET;
    request.version = DEFAULT_HTTP_VERSION;
    request.url = argv[1];
    request.header_len = 0;
    request.body_len = 0;

    ret = http_req(&request, &response);
    if(ret == ERROR) return EXIT_FAILURE;
    
    printf("Version     : %s\n", response.version);
    printf("Status Code : %d\n", response.status_code);
    printf("Phrase      : %s\n", response.phrase);
    
    for(i = 0; i < response.header_len; i++){
        printf(
            "Header      : key = [%s] value = [%s]\n", 
            response.headers[i].field_name, response.headers[i].value
        );
    }

    fflush(stdout);
    write(STDOUT_FILENO, response.body, response.body_len);
    printf("\n");

    free_http_response(&response);

    return EXIT_SUCCESS;
}
#endif

int http_req(http_request_t *req, http_response_t *res)
{
    uint32_t addr;
    int s;
    int ret;
    uint16_t port = 0;
    size_t len;
    struct sockaddr_in sck;
    struct hostent *hst;
    char *hostname;
    char *method;
    char *buf;
    int buf_len;
    ssize_t i;
    int j;
    int l;
    int m;
    int p;
    int q = 0;
    char f = HTTP_FALSE;
    char port_s[MAX_PORT_LEN];
    sighandler_t sig;
    struct timeval tv;

    tv.tv_sec  = 1;
    tv.tv_usec = 0;


    (void)memset(&sck, 0, sizeof(sck));

    sig = signal(SIGPIPE, SIG_IGN);
    if(sig == SIG_ERR){
        fprintf(stderr, "Cannot ignore SIGPIPE signal\n");
        return ERROR;
    }
 
    s = socket(PF_INET, SOCK_STREAM, 0);
    if(s == ERROR){
        perror("Could not create socket");
        return ERROR;
    }

    (void)setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    (void)setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

    len = strlen(req->url);
    hostname = (char *)malloc(len * sizeof(char) + 1);
    if(hostname == NULL){
        perror("Could not allocate memory for hostname");
        close(s);
        return ERROR;
    }
    
    EMPTY_STRING(hostname);

    l = strlen(req->url) + 1;
    m = strlen("http://");
    for(p = 0, j = m; j < l; j++){
        if(req->url[j] == ':'){
            strncpy(hostname, req->url + m, j - m);
            hostname[j - m] = '\0';
            f = HTTP_TRUE;
            p = j;
            continue;
        }

        if((req->url[j] == '/' || req->url[j] == '\0')  && f == HTTP_TRUE){
            if((j - p) < MAX_PORT_LEN){
                strncpy(port_s, req->url + p + 1, j - p);
                port_s[j - p] = '\0';
                port = (uint16_t)atoi(port_s);
            }else port = 0;
            q = j;
            break;
        }

        if((req->url[j] == '/' || req->url[j] == '\0') && f == HTTP_FALSE){
            strncpy(hostname, req->url + m, j - m);
            hostname[j - m] = '\0';
            port = DEFAULT_HTTP_PORT;
            q = j;
            break;
        }
    }

    if(port == 0 || hostname[0] == '\0'){
        fprintf(stderr, "Invalid URL\n");
        close(s);
        free(hostname);
        return ERROR;
    }

    l = strlen(hostname);
    for(j = 0; j < l; j++){
        if(hostname[j] == '/'){
            hostname[j] = '\0';
            break;
        }
    }

    hst = gethostbyname(hostname);
    if(hst == NULL){
        herror("Could not find host");
        close(s);
        free(hostname);
        return ERROR;
    }

    addr = *((uint32_t *)hst->h_addr_list[0]);
    
    sck.sin_family = AF_INET;
    sck.sin_port = htons(port);
    sck.sin_addr.s_addr = addr;

    ret = connect(s, (struct sockaddr *)&sck, sizeof(sck));
    if(ret == ERROR){
        perror("Could not connect to host");
        close(s);
        free(hostname);
        return ERROR;
    }
    
    switch(req->method){
    case GET:
        method = HTTP_GET_METHOD;
        break;
    case HEAD:
        method = HTTP_HEAD_METHOD;
        break;
    case POST:
        method = HTTP_POST_METHOD;
        break;
    case DELETE:
	method = HTTP_DELETE_METHOD;
	break;
    case PUT:
	method = HTTP_PUT_METHOD;
	break;
    default:
        method = HTTP_GET_METHOD;
    }
   
    if(req->url[q] == '/'){ 
        dprintf(s, "%s %s HTTP/%s\r\n", method, req->url + q, req->version);
    }else{
        dprintf(s, "%s /%s HTTP/%s\r\n", method, req->url + q, req->version);
    }
    
    if(port != DEFAULT_HTTP_PORT)
        dprintf(s, "Host: %s:%d\r\n", hostname, port);   
    else dprintf(s, "Host: %s\r\n", hostname);   
    dprintf(s, "Connection: close\r\n");
    dprintf(s, "Content-Length: %d\r\n", req->body_len); 

    for(i = 0; i < req->header_len; i++){
        dprintf(
            s, 
            "%s: %s\r\n", 
            req->headers[i].field_name,
            req->headers[i].value
        );
    }

    dprintf(s, "\r\n");

    
    if(req->body != NULL && req->body_len != 0){
        ret = write(s, req->body, req->body_len);
        if(ret == ERROR){
            perror("Could not write body to socket");
            free(hostname);
            return ERROR;
        }
    }

    buf = (char *)malloc(RECV_BUFF_SIZE * sizeof(char));
    if(buf == NULL){
        perror("Could not allocate memory for buffer");
        close(s);
        free(hostname);
        return ERROR;
    }

    buf_len = read_all(s, buf, RECV_BUFF_SIZE -1);
    if(buf_len == ERROR){
        perror("Could not read into buffer");
        free(hostname);
        return ERROR;
    }
    buf[buf_len] = '\0';
   
    close(s);
   
    (void)signal(SIGPIPE, sig);
    free(hostname);
    return http_parse_response(buf, buf_len, res);
}

int http_parse_response(char *buf, ssize_t buf_len, http_response_t *response)
{
    token_t token;
    state_machine_t sm;
    int pos;
    ssize_t size;
    char buff[STATUS_CODE_LEN];
    int old_pos;
    int nt;
    int i;
    int j;
    int f;

    INIT_STATE_MACHINE(&sm);
    
    sm.buf = buf;
    sm.buf_len = buf_len;

    pos = sm.pos;
    token = get_next_token(&sm);
    if(token != VERSION){
        fprintf(stderr, "ERROR %d-%d\n", VERSION, token);
        return ERROR;
    }
    
    size = sm.pos - pos;
    response->version = (char *)malloc((size + 1) * sizeof(char));
    if(response->version == NULL){
        perror("Cannot allocate memory for version number");
        return ERROR;
    }
    strncpy(response->version, sm.buf + pos, size);
    response->version[size] = '\0';

    pos = sm.pos;
    token = get_next_token(&sm);
    if(token != STATUS_CODE){
        fprintf(stderr, "ERROR %d-%d\n", STATUS_CODE, token);
        return ERROR;
    }

    size = sm.pos - pos;

    buff[STATUS_CODE_LEN - 1] = '\0';
    strncpy(buff, sm.buf + pos, STATUS_CODE_LEN - 1);

    response->status_code = atoi(buff);

    pos = sm.pos;
    token = get_next_token(&sm);
    if(token != PHRASE){
        fprintf(stderr, "ERROR %d-%d\n", PHRASE, token);
        return ERROR;
    }

    size = sm.pos - pos - 2;
    response->phrase = (char *)malloc((size + 1) * sizeof(char));
    if(response->phrase == NULL){
        perror("Cannot allocate memory for phrase");
        return ERROR;
    }
    strncpy(response->phrase, sm.buf + pos, size);
    response->phrase[size] = '\0';

    old_pos = sm.pos;
    nt = 0;
    f = HTTP_FALSE;
    do{
        token = get_next_token(&sm);
        switch(token){
        case PHRASE:
        case STATUS_CODE:
            f = HTTP_FALSE;
        case VERSION:
        case NEWLINE:
            break;
        case HEADER:
            if(f == HTTP_FALSE){
                nt++;
                f = HTTP_TRUE;
            }
            else f = HTTP_FALSE;
            break;
        case SYNTAX_ERROR:
            return ERROR; 
        }
    
        if(token != HEADER && token != PHRASE && token != STATUS_CODE) break;
    }while(token != SYNTAX_ERROR);

    if(nt != 0){ 
        response->headers = (http_header_t *)malloc(sizeof(http_header_t)*nt);
        if(response->headers == NULL){
            perror("Could not allocate memory for headers");
            return ERROR;
        }
    }else response->headers = NULL;

    response->header_len = nt;

    sm.pos = old_pos;
    for(i = 0; i < nt; i++){
        pos = sm.pos;
        sm.state = state_start;
        sm.stop = HTTP_FALSE;
        token = get_next_token(&sm);
        size = sm.pos - pos;
        size -= 2;

        response->headers[i].field_name = 
            (char *)malloc((size + 1) * sizeof(char));

        if(response->headers[i].field_name == NULL){
            perror("Could not allocate memory for header");
            return ERROR;
        }

        strncpy(response->headers[i].field_name, sm.buf + pos, size);
        response->headers[i].field_name[size] = '\0';

        pos = sm.pos;
        token = get_next_token(&sm);
        if(token == HEADER || token == STATUS_CODE){
            for(j = 0; 
                ((sm.buf + pos)[j] == '\r' 
                    && (sm.buf + pos)[j + 1] == '\n') == 0; 
                j++
            );
            size = j;
            sm.pos = j + 2;
        }else size = sm.pos - pos - 2;

        response->headers[i].value = 
            (char *)malloc((size + 1) * sizeof(char));

        if(response->headers[i].value == NULL){
            perror("Could not allocate memory for header");
            return ERROR;
        }
        strncpy(response->headers[i].value, sm.buf + pos, size);
        response->headers[i].value[size] = '\0';
        
    }

    pos = sm.pos;
    token = get_next_token(&sm);
    if(token != NEWLINE){
        fprintf(stderr, "ERROR %d-%d\n", NEWLINE, token);
        return ERROR;
    }

    response->body = (char *)malloc((buf_len - sm.pos + 1) * sizeof(char));
    if(response->body == NULL){
        perror("Could not allocate memory for body");
        return ERROR;
    }

    response->body_len = buf_len - sm.pos;
    response->body[response->body_len] = '\0';

    (void)memcpy(response->body, buf + sm.pos, response->body_len);

    return SUCCESS;
}

void free_http_response(http_response_t *response)
{
    free(response->version);
    free(response->phrase);
    if(response->headers != NULL) free(response->headers);
    if(response->body != NULL)    free(response->body);
}

token_t get_next_token(state_machine_t *sm)
{
    char c;
    accept_state_t accept;

    while(sm->stop != HTTP_TRUE){
        c = sm->buf[sm->pos];
        accept = sm->state(c, sm);
        switch(accept){
        case NOAS:
        case ASNR:
            sm->pos++;
        case ASWR:
            break;
        }

        switch(accept){
        case ASNR:
        case ASWR:
            sm->state = state_start;
            return sm->token;
        case NOAS:
            break;
        }
        
        if(sm->pos >= sm->buf_len){
            sm->stop = HTTP_TRUE;
            break;
        }
    }
    
    return SYNTAX_ERROR;
}

static accept_state_t state_start(char c, state_machine_t *sm)
{
    if(toupper(c) == 'H')               sm->state = state_vhp_A;
    else if(isdigit(c))                 sm->state = state_shp;
    else if(c == '\r' || c == '\n')     sm->state = state_n;
    else if(isprint(c))                 sm->state = state_hp_A;
    else                                sm->state = state_error;

    return NOAS;
}

static accept_state_t state_shp(char c, state_machine_t *sm)
{
    if(isdigit(c))                      sm->state = state_shp;
    else if(isblank(c)){
        sm->token = STATUS_CODE;
        return ASNR;
    }else if(c == ':')                  sm->state = state_hp_B;
    else if(c == '\r' || c == '\n')     sm->state = state_p;
    else if(isprint(c))                 sm->state = state_hp_A;
    else                                sm->state = state_error;
    
    return NOAS;
}

static accept_state_t state_n(char c, state_machine_t *sm)
{
    if(c == '\r' || c == '\n'){
        sm->token = NEWLINE;
        return ASNR;
    }else if(c == ':')                  sm->state = state_hp_B;
    else if(isprint(c))                 sm->state = state_hp_A;
    else                                sm->state = state_error;

    return NOAS;
}

static accept_state_t state_vhp_A(char c, state_machine_t *sm)
{
    if(toupper(c) == 'T')               sm->state = state_vhp_B;
    else if(isprint(c))                 sm->state = state_hp_A;
    else if(c == '\n' || c == '\r')     sm->state = state_p;
    else                                sm->state = state_error;

    return NOAS;
}

static accept_state_t state_vhp_B(char c, state_machine_t *sm)
{
    if(toupper(c) == 'T')               sm->state = state_vhp_C;
    else if(isprint(c))                 sm->state = state_hp_A;
    else if(c == '\n' || c == '\r')     sm->state = state_p;
    else                                sm->state = state_error;

    return NOAS;
}

static accept_state_t state_vhp_C(char c, state_machine_t *sm)
{
    if(toupper(c) == 'P')               sm->state = state_vhp_D;
    else if(isprint(c))                 sm->state = state_hp_A;
    else if(c == '\n' || c == '\r')     sm->state = state_p;
    else                                sm->state = state_error;
    
    return NOAS;
}

static accept_state_t state_vhp_D(char c, state_machine_t *sm)
{
    if(toupper(c) == '/')               sm->state = state_vhp_E;
    else if(isprint(c))                 sm->state = state_hp_A;
    else if(c == '\n' || c == '\r')     sm->state = state_p;
    else                                sm->state = state_error;
    
    return NOAS;
}

static accept_state_t state_vhp_E(char c, state_machine_t *sm)
{
    if(isdigit(c))                      sm->state = state_vhp_F;
    else if(isprint(c))                 sm->state = state_hp_A;
    else if(c == '\n' || c == '\r')     sm->state = state_p;
    else                                sm->state = state_error;
    
    return NOAS;
}

static accept_state_t state_vhp_F(char c, state_machine_t *sm)
{
    if(isdigit(c))                      sm->state = state_vhp_F;
    else if(c == '.')                   sm->state = state_vhp_G;
    else if(isprint(c))                 sm->state = state_hp_A;
    else if(c == '\n' || c == '\r')     sm->state = state_p;
    else                                sm->state = state_error;
    
    return NOAS;
}

static accept_state_t state_vhp_G(char c, state_machine_t *sm)
{
    if(isdigit(c))                      sm->state = state_vhp_H;
    else if(isprint(c))                 sm->state = state_hp_A;
    else if(c == '\n' || c == '\r')     sm->state = state_p;
    else                                sm->state = state_error;
    
    return NOAS;
}

static accept_state_t state_vhp_H(char c, state_machine_t *sm)
{
    if(isdigit(c))                      sm->state = state_vhp_H;
    else if(isblank(c)){
        sm->token = VERSION;
        return ASNR;

    }else if(isprint(c))                sm->state = state_hp_A;

    return NOAS;
}

static accept_state_t state_hp_A(char c, state_machine_t *sm)
{
    if(c == ':')                        sm->state = state_hp_B;
    else if(c == '\r' || c == '\n')     sm->state = state_p;
    else if(isprint(c))                 sm->state = state_hp_A;
    else                                sm->state = state_error;

    return NOAS;
}

static accept_state_t state_hp_B(char c, state_machine_t *sm)
{
    if(isblank(c)){
        sm->token = HEADER;
        return ASNR;
    } else if(c == '\r' || c == '\n')   sm->state = state_p;
    else if(c == ':')                   sm->state = state_hp_B;
    else if(isprint(c))                 sm->state = state_hp_A;
    else                                sm->state = state_error;

    return NOAS;
}

static accept_state_t state_p(char c, state_machine_t *sm)
{
    if(c == '\r' || c == '\n'){
        sm->token = PHRASE;
        return ASNR;
    }else if(c == ':')          sm->state = state_hp_B; 
    else if(isprint(c))         sm->state = state_hp_A;
    else                        sm->state = state_error;

    return NOAS;
}

static accept_state_t state_error(char c, state_machine_t *sm)
{
    c = 0;
    sm->token = SYNTAX_ERROR;

    return ASNR;
}

static int read_all(int s, char *buf, size_t len)
{
    size_t t;
    size_t r = ERROR;
 
    for(t = 0; t < len && r != 0;){
        r = read(s, buf + t, len - t);
        if((int)r == ERROR){
            return ERROR;
        }else{
            t += r;
        }
    }

    return (int)t;
}

