/*
 * Handler for HTTP protocol in proxy gateway server:
 *   int http_proxy_host_parse ( char *url, char **host, char **path );
 *   int http_proxy_handler ( scb, string *content, char *host, char *path );
 *
 * Author:	David Jones
 * Date:	19-FEB-1998
 * Revised:	28-mar-1998		Use <DNETCGI> instead of <DNETRAW>
 */
#include <stdio.h>
#include <stdlib.h>
#include "../base_code/tclient_tcp.h"
#include "../base_code/session.h"
#include "../base_code/http_header.h"
#include "../base_code/tserver_decnet.h"
#include "../base_code/tmemory.h"

int http_proxy_abort ( session_ctx, char *, char * );
int http_finish_request ( session_ctx, char * );
int tlog_putlog ( int, char *, ... );
int http_log_level;			/* trace level for diagnostics. */
char *proxy_localhosts[256];
int proxy_target_time_limit;

#ifdef DATA_CAPTURE
static int request_number = 0;
#include <pthread.h>
static pthread_mutex_t request_modify = PTHREAD_MUTEX_INITIALIZER;
#endif

#define NET_WRITE ts_decnet_write
#define NET_READ ts_decnet_read

#define TC_WRITE_SIZE 2048

struct outstream {
    int valid;
    int used;
    int bufsize;
    tc_socket cnx;
    char *buffer;
};

static int put_flush ( struct outstream *s )
{
    int status, i, length, size;

    if ( s->valid == 0 ) return 0;
    for ( i = 0; i < s->used; i+=length ) {
	size = s->used - i;
	if ( size > TC_WRITE_SIZE ) size = TC_WRITE_SIZE;
	status = tc_write ( s->cnx, &s->buffer[i], size, &length );
	if ( (status&1) == 0 ) {
	    s->valid = 0;
	    return status;
	}
    }
    s->used = 0;
    return 1;
}

static int put_string ( struct outstream *s, char *data, int count )
{
    int status, i;
    if ( s->valid == 0 ) return 0;
    while ( (s->used+count) > s->bufsize ) {
	i = s->bufsize - s->used;
	if ( i > 0 ) {
	    if ( i > count ) i = count;
	    tu_strncpy ( &s->buffer[s->used], data, i );
	    count = count - i;
	    data = &data[i];
	    s->used += i;
	}
	status = put_flush ( s );
	if ( (status&1) == 0 ) return status;
    }
	
    if ( count > 0 ) {
	tu_strncpy ( &s->buffer[s->used], data, count );
	s->used += count;
    }
    return 1;
}
/***************************************************************************/
/* Parse routine checks syntax of url and returns pointers to host
 * string and path.
 *
 * Arguments:
 *    url	String to be parsed, assumed to be of form '//host[:n]/path...'
 *    host	Returns pointer to string containing host[:n] portion of url
 *    path	Returns pointer to path, may be address within url argument.
 *
 * Return value:
 *   -1		Allocation failure on host string.
 *    0		Syntax error, invalid format for url argument.
 *    1		Success.
 */
int http_proxy_host_parse ( char *url, 		/* url - 'HTTP:' */
	char **host, char **path )
{
    int i;
    if ( url[0] != '/' ) return 0;
    if ( url[1] != '/' ) return 0;
    *host = &url[2];
    for ( i = 2; url[i]; i++ ) if ( url[i] == '/' ) break;
    *host = tm_malloc ( i );
    if ( !*host ) return -1;
    tu_strnzcpy ( *host, &url[2], i-2 );

    *path = (url[i]) ? &url[i] : "/";
    return 1;		/* success */
}
/***************************************************************************/
/* Top-level handler for HTTP protocol requests.  A single request is
 * responded to.
 *
 * Arguments:
 *    scb	Session control block, holds context for processing of request.
 *    content	Descriptor that points to content supplied with request,
 *		length set to -1 if no content supplied.
 *    host	Host name and port (optional) of desired target server for
 *		HTTP request.
 *    url	Relative-path portion of request to send to target host.
 *
 * Return value:
 *   odd	Success.
 *   even	Error code returned by network I/O.
 */
int http_proxy_handler (  session_ctx scb, 
	string *content, char *host, char *url )
{
    char errmsg[256], temp[4096], first_line[64], *delim;
    int status, port_num, length, i, url_len, first_send, fl_len, total_received;
    struct outstream s;
    FILE *hdr_file, *data_file;
#ifdef DATA_CAPTURE
    int cur_reqnum;
    char fname[100];
#endif
    /*
     * See if host matches local host.  String must include port number.
     */
    tu_strnzcpy ( temp, host, sizeof(temp) -1 );
    tu_strupcase ( temp, temp );
    if ( http_log_level > 1 ) tlog_putlog ( 2,
	"!AZ - target: '!AZ', local: '!AZ'!/",  scb->log_prefix, temp, 
	proxy_localhosts[0] );
    for ( i = 0; proxy_localhosts[i][0]; i++ ) {
	if ( tu_strncmp ( proxy_localhosts[i], temp, sizeof(temp) ) == 0 ) {
	    /*
	     * Only do local re-directs for GET and HEAD requests.
	     */
	    if ( (tu_strncmp ( scb->request[0].s, "GET", 4 ) != 0) &&
		 (tu_strncmp ( scb->request[0].s, "HEAD", 5 ) != 0) ) break;
	    /*
	     * Target host is the proxy server, exit with local redirect since
	     * server can more efficiently handle it directly.
	     */
	    status = NET_WRITE ( scb->cnx, "<DNETCGI>", 9 );
	    status = NET_WRITE ( scb->cnx, "Location: ", 10  );
	    status = NET_WRITE ( scb->cnx, url, tu_strlen(url) );
	    status = NET_WRITE ( scb->cnx, "\r\n\r\n", 4 );
	    status = http_finish_request ( scb, "</DNETCGI>" );
	    return status;
	}
    }
    /*
     * Parse port number out of host string and connect to remote.
     */
    tu_strnzcpy ( temp, host, sizeof(temp)-1 );
    delim = tu_strstr ( temp, ":" );
    if ( delim ) {
	port_num = atoi ( delim+1 );
	*delim = '\0';
    } else port_num = 80;

    status = tc_open_tcp_socket ( temp, port_num, 0, (char **) 0,
	&s.cnx, errmsg );
    if ( (status&1) == 0 ) {	/* connect fail */
	status = http_proxy_abort ( scb, "502 connect error", errmsg );
	return status;
    }
    s.valid = 1;		/* initialize outstream structure */
    s.used = 0;
    s.bufsize = sizeof(temp);
    s.buffer = temp;
    if ( proxy_target_time_limit > 0 ) tc_set_time_limit ( s.cnx,
	proxy_target_time_limit );
    hdr_file = data_file = (FILE *) 0;
#if DATA_CAPTURE
    /*
     * Allocate request number and open file to save request.
     */
    pthread_mutex_lock ( &request_modify );
    cur_reqnum = request_number++;
    pthread_mutex_unlock ( &request_modify );
    sprintf ( fname, "req%9d.hdr", cur_reqnum );
    hdr_file = fopen ( fname, "w" );
    if ( hdr_file ) fprintf ( hdr_file, "host\n>%s %s HTTP/1.0\n", 
		scb->request[0].s, url );
#endif
    /*
     * contruct HTTP request and send reqeuest: method path protocolNL
     */
    status = put_string ( &s, scb->request[0].s, scb->request[0].l );
    if ( s.valid ) status = put_string ( &s, " ", 1 );
    if ( s.valid ) status = put_string ( &s, url, tu_strlen ( url ) );
    if ( s.valid && (scb->request[2].l > 0) ) {
	status = put_string ( &s, " ", 1 );
	if ( s.valid ) status = put_string 
	    ( &s, scb->request[2].s, scb->request[2].l );
    }
    if ( s.valid ) status = put_string ( &s, "\r\n", 2 );
    if ( !s.valid ) {
	status = http_proxy_abort ( scb, "502 send error", 
		"Error sending request" );
	status  = tc_close ( s.cnx );
	if ( hdr_file ) fclose ( hdr_file );
	return status;
    }
    /*
     * Send headers, exclude keep-alive and connection headers.
     * (Should also exclude public, proxy-authenticate, upgrade,
     * transfer-encoding)
     */
    for ( i = 3; scb->request[i].l > 0; i++ ) {
	if ( hdr_file ) fprintf ( hdr_file, ">%s\n", scb->request[i].s );
	if ( scb->req_atom[i] == http_std_atoms.connection ) continue;

	status = put_string ( &s, scb->request[i].s, scb->request[i].l );
	if ( s.valid ) status = put_string ( &s, "\r\n", 2 );
	if ( !s.valid ) {
	    status = http_proxy_abort ( scb, "502 send error", 
		"Error sending request header" );
	    status  = tc_close ( s.cnx );
	    if ( hdr_file ) fclose ( hdr_file );
	    return status;
	}
    }
    status = put_string ( &s, "\r\n", 2 );	/* Blank line */
    if ( hdr_file ) fprintf ( hdr_file, ">\n" );
    if ( s.valid ) status = put_flush ( &s );
    if ( !s.valid ) {
	    status = http_proxy_abort ( scb, "502 send error", 
		"Error sending request terminator" );
	    status  = tc_close ( s.cnx );
	    if ( hdr_file ) fclose ( hdr_file );
	    return status;
    }
    /*
     * Now include content.
     */
    if ( content->l > 0 ) {
#ifdef DATA_CAPTURE
	sprintf ( fname, "req%09d.content", cur_reqnum );
	data_file = fopen ( fname, "wb" );
	if ( data_file ) {
	    fwrite ( content->s, 1, content->l, data_file );
	    fclose ( data_file );
	}
#endif
	status = put_string ( &s, content->s, content->l );
        if ( s.valid ) status = put_flush ( &s );
        if ( !s.valid ) {
	    status = http_proxy_abort ( scb, "502 send error", 
		"Error sending request content to target" );
	    status  = tc_close ( s.cnx );
	    if ( hdr_file ) fclose ( hdr_file );
	    return status;
	}
    }
    /*
     * Place in raw mode and return response.  Ideally, we should read and
     * interpret the headers.
     */
#ifdef DATA_CAPTURE
	sprintf ( fname, "req%09d.content", cur_reqnum );
	data_file = fopen ( fname, "wb" );
#endif
    status = NET_WRITE ( scb->cnx, "<DNETCGI>", 9 );
    fl_len = 0;
    total_received = 0;
    for ( first_send=1; (status&1) != 0; ) {
	/*
	 * CGI mode has a limitation that CGI header are read into 1K
	 * buffer.  Initially do 1K transfers until well passed header area.
	 */
	status = tc_read ( s.cnx, temp, 
	    total_received < sizeof(temp) ? 1000 : sizeof(temp), &length );
	if ( (status&1) == 0 ) {
	    break;
	}
	if ( data_file ) {
	    fwrite ( temp, 1, length, data_file );
	}
	total_received += length;
	if ( first_send ) {
	    /*
	     * Accumulate enough chars to determine if first line back
	     * is http status line and redo to CGI status.
	     */
	    int i;
	    for ( i = 0; i < length; i++ ) {
		if ( temp[i] == ' ' || temp[i] == '\t' ) break;
		if ( fl_len+i >= sizeof(first_line) ) break;
		first_line[fl_len+i] = temp[i];
		fl_len++;
	    }
	    if ( i >= length ) continue;	/* first white not seen */
	    first_line[fl_len++] = '\0';
	    status = NET_WRITE ( scb->cnx, "Status: ", 8 );
	    if ( (status&1) == 0 ) break;
	    first_send = 0;
	    /*
	     * Send remainder of buffer.
	     */
	    status = NET_WRITE ( scb->cnx, &temp[i], length-i );
	}
	else status = NET_WRITE ( scb->cnx, temp, length );
    }
    status = http_finish_request ( scb, "</DNETCGI>" );
    status  = tc_close ( s.cnx );
    if ( data_file ) fclose ( data_file );
    if ( hdr_file ) fclose ( hdr_file );
    return 0;
}
