#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "httpd.h"

/* ---------------------------------------------------------------------- */

void
read_request(struct REQUEST *req, int pipelined)
{
    int             rc;
    char            *h;

 restart:
    rc = read(req->fd, req->hreq + req->hdata, MAX_HEADER - req->hdata);
    switch (rc) {
    case -1:
	if (errno == EAGAIN) {
	    if (pipelined)
		break; /* check if there is already a full request */
	    else
		return;
	}
	if (errno == EINTR)
	    goto restart;
	xperror(LOG_INFO,"read",req->peerhost);
	/* fall through */
    case 0:
	req->state = STATE_CLOSE;
	return;
    default:
	req->hdata += rc;
	req->hreq[req->hdata] = 0;
    }

    /* check if this looks like a http request after
       the first few bytes... */
    if (req->hdata < 5)
	return;
    if (strncmp(req->hreq,"GET ",4)  != 0  &&
	strncmp(req->hreq,"PUT ",4)  != 0  &&
	strncmp(req->hreq,"HEAD ",5) != 0  &&
	strncmp(req->hreq,"POST ",5) != 0) {
	mkerror(req,400,0);
	return;
    }
    
    /* header complete ?? */
    if (NULL != (h = strstr(req->hreq,"\r\n\r\n")) ||
	NULL != (h = strstr(req->hreq,"\n\n"))) {
	if (*h == '\r') {
	    h += 4;
	    *(h-2) = 0;
	} else {
	    h += 2;
	    *(h-1) = 0;
	}
	req->lreq  = h - req->hreq;
	req->state = STATE_PARSE_HEADER;
	return;
    }

    if (req->hdata == MAX_HEADER) {
	/* oops: buffer full, but found no complete request ... */
	mkerror(req,400,0);
	return;
    }
    return;
}

/* ---------------------------------------------------------------------- */

static int
unhex(unsigned char c)
{
    if (c < '@')
	return c - '0';
    return (c & 0x0f) + 9;
}

static void
unquote(unsigned char *dst, unsigned char *src)
{
    int i,j,q,n=strlen(src);

    q=0;
    for (i=0, j=0; i<n; i++, j++) {
	if (src[i] == '?')
	    q = 1;
	if (q && src[i] == '+') {
	    dst[j] = ' ';
	} else if (src[i] == '%') {
	    dst[j] = (unhex(src[i+1]) << 4) | unhex(src[i+2]);
	    i += 2;
	} else {
	    dst[j] = src[i];
	}
    }
    dst[j] = 0;
}

void
parse_request(struct REQUEST *req)
{
    char filename[2048], proto[5], *h;
    int  port;
    
    if (debug > 2)
	fprintf(stderr,"%s\n",req->hreq);

    /* parse request. Hehe, scanf is powerfull :-) */
    if (4 != sscanf(req->hreq, "%4[A-Z] %255[^ \t\r\n] HTTP/%d.%d",
		    req->type,filename,&(req->major),&(req->minor))) {
	mkerror(req,400,0);
	return;
    }
    if (filename[0] == '/') {
	strcpy(req->uri,filename);
    } else {
	port = 0;
	*proto = 0;
	if (4 != sscanf(filename,"%4[a-zA-Z]://%64[a-zA-Z0-9.-]:%d%255[^ \t\r\n]",
			proto,req->hostname,&port,req->uri) &&
	    3 != sscanf(filename,"%4[a-zA-Z]://%64[a-zA-Z0-9.-]%255[^ \t\r\n]",
			proto,req->hostname,req->uri)) {
	    mkerror(req,400,0);
	    return;
	}
	if (*proto != 0 && 0 != strcasecmp(proto,"http")) {
	    mkerror(req,400,0);
	    return;
	}
    }

    unquote(req->path,req->uri);    
    if (debug)
	fprintf(stderr,"%03d: %s \"%s\" HTTP/%d.%d\n",
		req->fd, req->type, req->path, req->major, req->minor);

    if (0 != strcmp(req->type,"GET") &&
	0 != strcmp(req->type,"HEAD")) {
	mkerror(req,501,0);
	return;
    }

    if (0 == strcmp(req->type,"HEAD")) {
	req->head_only = 1;
    }

    /* checks */
    if (req->path[0] != '/') {
	mkerror(req,400,0);
	return;
    }

    /* parse header lines */
    req->keep_alive = req->minor;
    for (h = req->hreq; h - req->hreq < req->lreq;) {
	h = strchr(h,'\n');
	if (NULL == h)
	    break;
	h++;

	if (0 == strncasecmp(h,"Connection: ",12)) {
	    req->keep_alive = (0 == strncasecmp(h+12,"Keep-Alive",10));

	} else if (0 == strncasecmp(h,"Host: ",6)) {
	    sscanf(h+6,"%64[a-zA-Z0-9.-]",req->hostname);
	}
    }

    /* make sure we have a hostname */
    if (req->hostname[0] == '\0' || canonicalhost)
	strncpy(req->hostname,server_host,64);

    /* lowercase hostname */
    for (h = req->hostname; *h != 0; h++) {
	if (*h < 'A' || *h > 'Z')
	    continue;
	*h += 32;
    }

    /* handle request (more to come) */
    buildpage(req);
    return;
}
