#ifdef WIN32
#include <windows.h>
#endif /* WIN32 */

#include "HT.h"
#include "HTChunk.h"
#include "HTParse.h"
#include "tcp.h"
#ifndef WIN32
#include "netinet/tcp.h"
#endif /* WIN32 */
#include "../Client/c_def.h"

/* debug_level is defined by module that calls http_method */
extern int debug_level;

static void get_host_port(char *, char **, int *);
#ifdef EXPLICIT_BIND
#ifndef WIN32
typedef int	SOCKET;
#endif /* WIN32 */
static int assign_port_number(SOCKET s);
#endif /* EXPLICIT_BIND */
static THREAD http_info *http = NULL;

PRIVATE int HTTPCleanup (http_info * http);

#ifdef WIN32
#define PRINT_ERR_LINENO	fprintf(p_stderr, "runtime error in %s line %d neterr = %d\n", __FILE__, __LINE__, WSAGetLastError())
#else
#define PRINT_ERR_LINENO	fprintf(p_stderr, "runtime error in %s line %d neterr = %d\n", __FILE__, __LINE__, errno);
#endif /* WIN32 */

int HTLoadHTTP (HTRequest *request)
{
    int status = -1;
    int retval = 0;

    if (http == NULL) {

        /* create http_info */
        if ((http = (http_info *) calloc(1, sizeof(http_info))) == NULL) {
            fprintf(p_stderr, "HTOps:HTLoadHTTP: http_info: out of memory error");
	    PRINT_ERR_LINENO;
	    return -1;
	}
        http->isoc = HTInputSocket_new(-1);
	if (http->isoc == NULL) {
	    fprintf(p_stderr, "HTOps:HTLoadHTTP: socketnew: out of memory error");
	    PRINT_ERR_LINENO;
	    return -1;
	}
    }

    http->connect = 0;
    http->isoc->input_file_number = http->sockfd = -1;
    http->isoc->input_pointer = http->isoc->input_limit = 
	http->isoc->input_buffer;

    http->request = request;
    status = HTTPDoConnect(http);
    if (status < 0) {
        /* fprintf(p_stderr, "HTOps:HTLoadHTTP: Error in HTDoConnect\n"); */
        retval = HTTPCleanup(http);
        return status;
    }
    status = HTTPSendRequest(http);
    if (status < 0) {
        /* fprintf(p_stderr, "HTOps:HTLoadHTTP: Error in HTTPSendRequest\n"); */
        retval = HTTPCleanup(http);
        return status;
    }
    status = HTTPGetReply(http);
    if (status < 0) {
        /* fprintf(p_stderr, "HTOps:HTLoadHTTP: Error in HTTPGetReply\n"); */
        retval = HTTPCleanup(http);
        return status;
    }
    retval = HTTPCleanup(http);
    return status;
}
    /* the following 5 declarations moved/added here 9-98 to 
	   eliminate extra gethostbyname() calls */

    int HostNameResolvedd = 0;
    struct hostent *hostelement;
    char *host_port;
    char *host;
    int port;

int HTTPDoConnect ( 
    http_info * http
)
{
    THREAD static int conn_num = 0;	/* connection attempt # */
    SockA sock_addr;                            /* SockA is defined in tcp.h */
    int status;
    int tcpnodelay;
#ifdef EXPLICIT_BIND
    int numtries;
#endif /* EXPLICIT_BIND */

    if (debug_level >= 10) {
        fprintf(p_stderr, "HTTPDoConnect: enter\n");
    }

    if (http->request->keep_alive > 0) {
        if (http->connect > 0) {
            return 1;
        }
    } 

    /* the following code changed/rearranged 9-98 to eliminate extra
	   gethostbyname() calls */

    if (!HostNameResolvedd) {
      host_port = HTParse(http->request->url, "", PARSE_HOST);
      if (debug_level >= 10) {
          fprintf(p_stderr, "HTDoConnect: host_port = %s\n", host_port);
      }
      get_host_port(host_port, &host, &port);
      /* free(host_port);  */

#ifndef WIN32    
      if ((hostelement = gethostbyname(host)) == NULL) {
#else
      if ((hostelement = gethostinfo(host)) == NULL) {
#endif /* WIN32 */
	fprintf(p_stderr, "HostByName.. Can't find internet node name `%s'.\n", host);
	PRINT_ERR_LINENO;
	return -1;		
      }
      HostNameResolvedd = 1;
    }
    /* end of changes 9-98 */

    memset((void *) &sock_addr, '\0', sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    if (port > 0) {
        sock_addr.sin_port = htons(port);
    } else {
	sock_addr.sin_port = htons((short) 80);
    }

    if (debug_level >= 10) {
        fprintf(p_stderr, "HTDoConnect: host = %s port = %d\n", host,
		ntohs(sock_addr.sin_port));
    }

    if ((http->sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    {
        fprintf(p_stderr, "No socket");
	PRINT_ERR_LINENO;
        return -1;
    }
    http->isoc->input_file_number = http->sockfd;
    tcpnodelay = 1;
    if (setsockopt(http->sockfd, IPPROTO_TCP, TCP_NODELAY,
	    (char *) &tcpnodelay, sizeof(tcpnodelay)) < 0) {
        fprintf(p_stderr, "Can't set sockopt TCP_NODELAY");
	PRINT_ERR_LINENO;
        return -1;
    }

#ifdef EXPLICIT_BIND
    /* explicitly bind the socket to a port */
    if ((numtries = assign_port_number(http->sockfd)) < 0) {
	printf(" %5d* ", conn_num);		/* note no \n */
	return -1;
    } else {
	if (numtries > 10)
	    printf("Numtries = %d\n", numtries);
    }
#endif /* EXPLICIT_BIND */



    memcpy(&sock_addr.sin_addr, 
	hostelement->h_addr,
	hostelement->h_length
    );

    conn_num++;
    status = connect(http->sockfd, (struct sockaddr *) &sock_addr,
	sizeof(sock_addr));

    if (debug_level >= 10) {
        fprintf(p_stderr, "HTDoConnect: leave; status = %d\n", status);
    }

    if (status < 0)
	PRINT_ERR_LINENO;
    return status;
}

#define CONN_COMMAND "Connection: Keep-Alive\r\n"
#define CONN_COMMAND_LEN sizeof(CONN_COMMAND)
#define ACCEPT_COMMAND "Accept: */* HTTP/1.0\r\n\r\n"
#define ACCEPT_COMMAND_LEN sizeof(ACCEPT_COMMAND)

int HTTPSendRequest (
    http_info * http
)
{
    int status = 0;
    char *path;
    HTChunk *command;

    char *method;
    char buf[256], *bufp = buf;

    if (http->request->keep_alive > 0) {
        http->connect += 1;
    }    

#define NEW 1
#ifdef NEW
    if (http->request->method != METHOD_INVALID) {
	method = HTMethod_name(http->request->method);
    }
    else
	method = "GET";
    path = strchr(http->request->url, '/')+1;
    path = strchr(path, '/')+1;
    path = strchr(path, '/')+1;
    bufp += sprintf(bufp, "%s /%s HTTP/1.0\r\n%s%s",
                        method,
                        path,
                        (http->request->keep_alive ? CONN_COMMAND : ""),
                        ACCEPT_COMMAND );

    /* Now, we are ready for sending the request */
    if ((status = NETWRITE(http->sockfd, buf, bufp-buf))<0) {
	if (TRACE) fprintf(p_stderr, "HTTP Tx..... Error sending command\n");
    }
#else
    command = HTChunkCreate(2048);		/* The whole command */
    if (http->request->method != METHOD_INVALID) {
	HTChunkPuts(command, HTMethod_name(http->request->method));
	HTChunkPutc(command, ' ');
    }
    else
	HTChunkPuts(command, "GET ");

    HTChunkPuts(command, "/");
    path = HTParse(http->request->url, "", PARSE_PATH);
    HTChunkPuts(command, path);
    HTChunkPuts(command, " HTTP/1.0");
    HTChunkPutc(command, CR);			     /* CR LF, as in rfc 977 */
    HTChunkPutc(command, LF);
    if (http->request->keep_alive > 0) {
        HTChunkPuts(command, CONN_COMMAND);
    }
    HTChunkPuts(command, ACCEPT_COMMAND);
    
    HTChunkTerminate(command);
    
    /* Now, we are ready for sending the request */
    if ((status = NETWRITE(http->sockfd, command->data, command->size-1))<0) {
	PRINT_ERR_LINENO;
	if (TRACE) fprintf(p_stderr, "HTTP Tx..... Error sending command\n");
    }
    HTChunkFree(command);
#endif /* NEW */

    return status;
}

#define VERSION_LENGTH 10
#define LINE_LENGTH 100

int HTTPGetReply (
    http_info * http
)
{
    int expect_size = 0;
    int server_status = 0;
    int ret_count = 0;
    int len = 0;
    int content_length = 0;
    int got_content_length = 0;
    int count = 0;
    int prev_count;
    char server_version[VERSION_LENGTH];
    char *status_line = NULL;
    char *block = NULL;
    char valname[LINE_LENGTH];
    char *valchar = valname;
    int	stat_len;
    int rv;

    if (debug_level >= 10) {
        fprintf(p_stderr, "HTTPGetReply: enter\n");
        fprintf(p_stderr, "HTTPGetReply: access_speed = %d validate = %d\n", 
            http->request->access_speed, http->request->validate);
    }

    status_line = HTInputSocket_getStatusLine(http->isoc);
    if (status_line == NULL){
	PRINT_ERR_LINENO;
	goto error;
    }
    if (debug_level >= 10) {
	fprintf(p_stderr, "HTTPGetReply: status line: %s\n", status_line);
    }
    ret_count =  sscanf(status_line, "HTTP/%s %d", server_version,
        &server_status);
    if(ret_count != 2) {
	fprintf(p_stderr, "HTTPGetReply: bad status line: %s\n", status_line);
    }
    free(status_line);
    if (ret_count == 2) {
        if (debug_level >= 10) {
            fprintf(p_stderr, "HTTPGetReply: version %s status %d\n",
                server_version, server_status);
        }
        switch (server_status/100) {
            case 2:
                break;
            case 3: case 4: case 5:
            default:
		fprintf(p_stderr, "HTTPGetReply: Status %d for URL \"%s\"\n",
		    server_status, http->request->url);
                goto error;
	        break;
        }
    } else {
	PRINT_ERR_LINENO;
        goto error;
    }

    /* get header lines until empty line */
    while(1) {
        status_line = HTInputSocket_getLine(http->isoc);
        if (status_line == NULL){
	    PRINT_ERR_LINENO;
            goto error;
        }
        if (debug_level >= 10) {
            fprintf(p_stderr, "HTTPGetReply: line: %s\n", status_line);
        }
        if (sscanf(status_line, "Content-length: %d", &content_length)
               > 0) { 
	    got_content_length = 1;
            if (debug_level >= 10) {
                fprintf(p_stderr, "HTTPGetReply: content_length: %d\n",
                    content_length);
            }
        }
        if (*status_line == '\0'){
            if (debug_level >= 10) {
                fprintf(p_stderr, "HTTPGetReply: empty line\n");
            }
	    free(status_line);
            break;
        }
	free(status_line);
    }

    if (http->request->validate) {
	/* get the expected file size & file name from the first line of the file */
        status_line = HTInputSocket_getLine(http->isoc);
        if (status_line == NULL){
	    PRINT_ERR_LINENO;
            goto error;
        }
        if (debug_level >= 10) {
            fprintf(p_stderr, "HTTPGetReply: size line: %s\n", status_line);
        }
        ret_count =  sscanf(status_line, "%d %s", &expect_size, valname);
	stat_len = strlen(status_line)+2;
	free(status_line);
        if (ret_count != 2) {
	    PRINT_ERR_LINENO;
    	    goto error;
        }
        if (debug_level >= 10) {
            fprintf(p_stderr, "HTTPGetReply: validate url = %s\n", valname);
            fprintf(p_stderr, "HTTPGetReply: expect_size %d\n", expect_size);
        }
    }

#define CHUNKSIZE 4096
/* assumes that the maximum block read (INPUT_BUFFER_SIZE) 
 * is <= the verification interval (CHUNKSIZE)
 */
    prev_count = CHUNKSIZE-1;
    while (1) {
        len = INPUT_BUFFER_SIZE;
        block = HTInputSocket_getBlock(http->isoc, &len);
        count += len;
        if (debug_level >= 10) {
            fprintf(p_stderr, "HTTPGetReply: got %d bytes\n", len);
        }
        if (block == NULL) {
            break;
        }
	if (http->request->validate &&
	    prev_count+len >= CHUNKSIZE) {

	    if (*valchar++ == block[(CHUNKSIZE-1)-prev_count]) {
							/* Check if match validates */
		if (*valchar == '\0') {			/* check for end of string */
		    valchar = valname;			/* if so, start over again */
		}
	    } else {					/* block does not match url */
		if (debug_level >= 10) {
		     fprintf(p_stderr, "HTTPGetReply: mismatch '%c' (%d) != '%c' (%d)\n",
			    (int)(*valchar),(int)(*valchar),
			    (int)(block[0]), (int)(block[0]));
		}
		PRINT_ERR_LINENO;
		goto error;				/* a fetch error */
	    }
	    prev_count -= CHUNKSIZE;
	}
	prev_count += len;
#define CLOSE_ON_COUNT 1
#ifdef CLOSE_ON_COUNT
	if (got_content_length) {
#else
        if (http->request->keep_alive > 0) {
#endif /* CLOSE_ON_COUNT */
	    if (count+stat_len == content_length) {
                break;
            }
        }
    }

    if (debug_level >= 10) {
        fprintf(p_stderr, "HTTPGetReply: count %d\n", count);
    }

    count += stat_len;
    if (http->request->validate && (count != expect_size)) {
	PRINT_ERR_LINENO;
        goto error;
    }
    rv = 1;
    goto exit;

error:
    rv = -1;
exit:
    return rv;
}


PRIVATE int HTTPCleanup (http_info * http)
{
    int status = 0;

    if (http->request->keep_alive > 0){
        if (debug_level >= 10) {
            fprintf(p_stderr, "HTTPCleanup: connect = %d keep_alive = %d\n", 
                http->connect, http->request->keep_alive);
        }
        if (http->connect < http->request->keep_alive) {
            return 1;
        } else {
            http->connect = 0;
        }
    }
    if ((status = NETCLOSE(http->sockfd)) < 0){
	PRINT_ERR_LINENO;
        fprintf(p_stderr, "HTTPCleanup: close error\n");
    }
    return status;
}

PRIVATE char * method_names[(int)MAX_METHODS + 1] =
{
    "INVALID-METHOD",
    "GET",
    "HEAD",
    "POST",
    "PUT",
    "DELETE",
    "CHECKOUT",
    "CHECKIN",
    "SHOWMETHOD",
    "LINK",
    "UNLINK",
    NULL
};

PUBLIC HTMethod HTMethod_enum ARGS1(char *, name)
{
    if (name) {
        int i;
        for (i=1; i < (int)MAX_METHODS; i++)
            if (!strcmp(name, method_names[i]))
                return (HTMethod)i;
    }
    return METHOD_INVALID;
}
 
 
/*      Get method name
**      ---------------
*/
PUBLIC char * HTMethod_name ARGS1(HTMethod, method)
{
    if ((int)method > (int)METHOD_INVALID  &&
        (int)method < (int)MAX_METHODS)
        return method_names[(int)method];
    else
        return method_names[(int)METHOD_INVALID];
}
/*
 * host_port is of the form host:port or just host
 */
static void
get_host_port(
    char *host_port,
    char **host,
    int *port
)
{
    char *p;
 
    *host = (char *) strdup(host_port);
    p = strchr(*host, ':');
    if (p == NULL) {
        *port = -1;
        return;
    } else {
        *p = '\0';
        p++;
        *port = atoi(p);
    }
}

#ifdef EXPLICIT_BIND
#define MINPORT 5001L	    /* lowest port number to assign */
#define MAXPORT 65000L	    /* highest port number to assign */

/* Assign a port number to an unbounded socket
   Each child gets its own unique range of port number via % operation

   Return -1 for failure, otherwise the number of extra sockets checked
 */
static int assign_port_number(SOCKET s) {
#ifdef WIN32
SOCKADDR_IN sin; 
THREAD static long port_req = -1;
#else
struct sockaddr_in sin;
static long port_req = -1;
#endif /* WIN32 */
u_short port;
int i;
extern int children;			/* # of children on this system */

    sin.sin_family = AF_INET; 
    sin.sin_addr.s_addr = 0; 
    if (port_req == -1)
	port_req = Child_num;		/* initialize */

    for (i = 0; i <= (MAXPORT-MINPORT) % children; i++) { 
	port = (u_short)((port_req % (1+MAXPORT-MINPORT)) + MINPORT);
	port_req += children;
	sin.sin_port = htons(port); 
#ifdef WIN32
	if (bind(s, (LPSOCKADDR)&sin, sizeof (sin)) == 0) { 
#else
	if (bind(s, (struct sockaddr_in *)&sin, sizeof (sin)) == 0) { 
#endif /* WIN32 */
	    /* port assignment worked */ 
	    return i;
	} 
#ifdef WIN32
	if ( GetLastError() != WSAEADDRINUSE) { 
#else
	if (errno = EADDRINUSE) {
#endif /* WIN32 */
	    /* fail from cause other than lack of a port */ 
	    return -1;
	} 
	/* this seems to be unused? raj */
	/* numskips++; */
    } 
    /* fail--all port numbers are in use */ 
    return -1;
} 
#endif /* EXPLICIT_BIND */