/*
 * Main code for formating and transmiting a file to a WWW client.
 *
 * Author:	David Jones
 * Revised:	26-MAY-1994		Updated HTTP_GET_DOC_CACHE call.
 * Revised:	24-JUN-1994		Introduce session control block.
 * Revised:	22-JUL-1994		Explicitly close file on HEAD command.
 * Revised:	14-AUG-1994		validate file ownership.
 * Revised:	24-AUG-1994		Re-work presentation action.
 * Revised:	29-AUG-1994		Include file count in dir_to_html.
 * Revised:	12-OCT-1994		Include Transfer encoding.
 * Revised:	 4-NOV-1994		Replace check for index.html with
 *					search list.
 * Revised:     16-NOV-1994		Support DirAccess directive.
 * Revised:	22-NOV-1994		Support fallback check for directories.
 * Revised:	17-DEC-1994		Add support for if-modified-since.
 * Revised:     26-DEC-1994		Include last-modified header on 304
 *					response for Netscape's sake.
 * Revised:	14-JAN-1995		Add hack for content-encoding.
 * Revised:	15-FEB-1995		Change name to script_execute
 * Revised:	27-MAY-1995		Inhibit caching of 302 redirect.
 * Revised:	29-MAY-1995		Utilize new "rd" mode in tf_open().
 * Revised:	3-JUN-1995		Disallow /../ elements in path.
 * Revised:	12-JUN-1995		Don't return 304 status if request
 *					is to no-cache port.
 * Revised:	26-JUL-1995		Support multi-homed redirects.
 * Revised:	12-JAN-1995		Fix bug is cache size check.
 * Revised:     19-MAR-1996		Add content-length to 304 response.
 * Revised:	8-OCT-1996		Update to use http_header module.
 * REvised:	25-OCT-1996		Fix problem with negative sizes.
 * Revised:	9-AUG-1997		Re-do caching.
 * Revised:	2-OCT-1997		Reset cache entry on 302 redirects.
 * Revised:	12-OCT-1997		Fix bugs in nocache port logic,
 *					initialize tfc.hdr when needed.
 * Revised:	12-FEB-1998		Hack for SSL redirects. (http_scheme_fixup)
 * Revised:	2-FEB-1999		Don't treat null length file as error.
 * Revised:	29-APR-1999		Support http_error_page.
 * Revised:	27-JUL-1999		Pre-test cache_allowed flag before
 *					calling http_check_cache_access.
 * Revised:	28-JUL-1999		Change format of http_send_response_header.
 * Revised:	13-AUG-1999		Check for pragma: no-cache.
 * Revised:	17-SEP-1999		Check for /-/ and disallow.
 * Revised:	27-AUG-2000		Enhanced expires header support.
 * Revised:	16-DEC-2001		Add log prefix to log line.
 */
#include <stdio.h>
#include "ctype_np.h"
#include "session.h"
#include "http_header.h"
#include "file_access.h"
#include "file_cache.h"
#include "tserver_tcp.h"
#include "errorpage.h"			/* alternate error report */

extern char http_server_version[];
int http_log_level, tlog_putlog(int, char *, ...);
char *http_search_script;
char **http_index_filename;		/* List of filenames */
int http_dir_access;			/* 0-all, 1-restricted, 2-none */
int http_crlf_newline;			/* 0-LF newline, 1- CRLF newline */
char *http_dir_access_file;		/* Directory access filename */
int http_send_error(), http_add_response(), http_send_response_header();
int http_check_protection(), http_script_execute();
char *http_url_suffix();
static int dir_to_html ( void *ctx, void *of, char *buffer, int bufsize,
	int *data_bytes );

/****************************************************************************/
/* This utility routine handles paired operation of writing a buffer to the
 * the network and caching the buffer.  If the cache put fails or the
 * network operation fails, the cache context is rundown.
 */
static int send_and_cache ( void *cnx, tfc_ctxptr tfc, char *buffer, int used )
{
    int status;
    status = ts_tcp_write ( cnx, buffer, used );
    if ( tfc->mode == 2 ) {
	if ( (status&1) == 1 ) tfc_put ( tfc, buffer, used );
	else tfc_rundown_context ( tfc, 1 );	/* delete entry */
    }
    return status;
}
/****************************************************************************/
/* The following utility routine handles constructing a location header
 * line given a host (local_address) and path.  The current thread's local
 * port is checked and appended to the host string if needed.
 *
 * The port_attr field in the access struct points to a string
 * that directs an alternate formatting option for URLs on a particular
 * port.  Format of attributes string is :
 *
 *	[+][:]scheme
 *
 * where:
 *	nnn	Port number to match (e.g. 443).
 *	+	Optional flag character.  If present, keepalives are allowed
 *		(default is to inhibit keep-alive on connection).
 *	:	Option flag character.  If present, port number is included
 *		in URL (default is to assume port number implied).  This
 *		flag must appear after the plus flag if it is present.
 *	scheme	Scheme name to use at startup of URL (e.g. http).
 *
 */
static void location_url ( session_ctx scb, char *path, char *result )
{
    unsigned int rem_addr;
    int local_port, remote_port, default_scheme_port, i, j;
    char port_str[8], *port_attr;
    /*
     * Scan port attributes string and set flags for options present.
     * scheme name starts at offset j.
     */
    port_attr = scb->acc->port_attr;
    j = 0;
    if ( port_attr[j] == '+' ) j++;	/* keepalive allowed flag */
    if ( port_attr[j] == ':' ) {
	default_scheme_port = 0;	/* Port number not default for scheme */
	j++;				/* skip flag character */
    } else {
	default_scheme_port = 1;
    }
    /*
     * Start with Location: header and get port number client connected to
     * (local_port).
     */
    tu_strcpy ( result, "Location: " );
    tu_strcpy ( &result[10], &port_attr[j] );
    i = 10 + tu_strlen ( &port_attr[j] );
    /*
     * Append puncutation and host string.
     */
    tu_strcpy ( &result[i], "://" );
    i += 3;
    tu_strcpy ( &result[i], scb->acc->local_address );
    i += tu_strlen ( scb->acc->local_address );
    if ( !default_scheme_port ) {
	result[i++] = ':';
        ts_tcp_info ( &local_port, &remote_port, (unsigned int *) &rem_addr );
	tu_strint ( local_port, &result[i] );
	i += tu_strlen ( &result[i] );
    }
    /*
     * End buffer with path string.
     */
    tu_strcpy ( &result[i], path );
    if ( http_log_level > 2 ) {
	tlog_putlog ( 3, "!AZ send_doc3 relocation: '!AZ', fixup: '!AZ'!/", 
		scb->log_prefix, result, port_attr );
    }
}
/****************************************************************************/
int http_send_document (
	session_ctx scb,			/* Session control block */
	char *req_ident,			/* Filename in request */
	char *ident, 				/* Translated File spec. */
	char *arg,				/*  search arguments on URL */
	string *iobuf )				/* I/O buffer */
{
    int text_mode, header_only, i, status, pl, length, bufsize, enc_len;
    int cache_status;	/* 0-none, 1-readable, 2-writing, 3-invalid */
    int http_match_suffix();
    int http_get_presentation(), http_check_cache_access();
    unsigned int since_time;
    struct tfc_context tfc;
    void *of, *o2f;				/* tf_* file pointer */
    char *presentation;
    char *rep, *encoding, *suffix, *stsmsg, hdrline[64], *buffer, fixup[340];
    /*
     * Check access protection.
     */
    if ( scb->acc->prot_file[0] ) {
	i = scb->rsphdr->l;		/* original length */
        status = http_check_protection  ( scb, ident, iobuf );
	if ( http_log_level > 2 ) {
	    tlog_putlog ( 3, 
		"!AZ Protection check status: !SL, protfail: '!AZ'!/", 
		scb->log_prefix, status, http_error_page.protfail.type ?
		   http_error_page.protfail.ident : "*" ); 
	}
	if ( (status == 0) && http_error_page.protfail.type ) {
	    /*
	     * Override normal 'protected' response.  Disable the protection
	     * check for the recursive call.  Reset the response header.
	     */
	    if ( tu_strncmp ( &scb->rsphdr->s[i], "401 ", 4 ) == 0 ) {
		/* Unauthorized status, pass through. */
		status = http_send_response_header ( scb );
		if ( (status&1) == 1 ) status = ts_tcp_write ( scb->cnx,
			"Additional authorization required\r\n", 35 );
	    } else {
		/*
		 * Generate standard error response.
		 */
		char ecode[8];
		scb->rsphdr->l = i;	/* restore length, keep error code */
		tu_strnzcpy ( ecode, &scb->rsphdr->s[i], 4 );
		rep = scb->acc->prot_file;
		scb->acc->prot_file = "";
		status = http_process_error ( scb, &http_error_page.protfail,
			ecode, req_ident, fixup, sizeof(fixup), arg, iobuf );
		scb->acc->prot_file = rep;
	    }
	    return status;
        } else if ( status == 0 ) {
	    /*
	     * Send error response to client, since this type of failure
	     * is 'normal', don't  echo request headers in response.
	     */
	    status = http_send_response_header ( scb );
	    if ( (status&1) == 1 ) status = ts_tcp_write ( scb->cnx,
		"File _protected_ against access\r\n", 33 );
	    return status;
	}
    }
    if ( scb->acc->cache_allowed ) {
	http_check_cache_access ( scb->acc );
	if ( scb->acc->cache_allowed ) {
	    /*
	     * Check for headers that disable cache.
	     */
	    i = http_extract_header ( http_std_atoms.pragma, scb, fixup,
		sizeof(fixup)-1, &length );
	    if ( i > 0 ) {
		fixup[length] = '\0';
		tu_strupcase ( fixup, fixup );
		if ( tu_strncmp ( fixup, "NO-CACHE", 9 ) == 0 ) {
		    scb->acc->cache_allowed = 0;
	    	    if ( http_log_level > 1 ) tlog_putlog ( 2, 
			"!AZ Disallowing cache due to pragma: no-cache!/",
			scb->log_prefix );
		}
	    }
	}
    }
    /*
     * Check for parent directory navigation and disallow.
     */
    if ( buffer = tu_strstr ( ident, "/.." ) ) {
	if ( !buffer[3] || (buffer[3] == '/') ) {
	    status = http_send_error ( scb, "403 illegal filename",
		"Illegal filename construct (/../)" );
	    return status;
	}
    }
    if ( buffer = tu_strstr ( ident, "/-" ) ) {
	/*
	 * Don't allow /-/ or /--/  or /-.xxx/ but allow /-.dat /-test/pl.dat
	 */
	int i, j, state;
	state = 0;
	for ( i = 2; buffer[i]; i++ ) {
	    if ( buffer[i] == '/' ) {
		if ( state < 2 ) {
		    status = http_send_error ( scb, "403 illegal filename",
			"Illegal filename construct (/-/)" );
		    return status;
		}
		break;
	    }
	    switch ( state ) {
	      case 0:
		if ( buffer[i] == '.' ) state = 1;
		else if ( buffer[i] != '-' ) state = 2;
	 	break;
	      case 1:
	      case 2:
		break;
	    }
	}
    }
    /*
     * Negotiate representation and encoding values based on client's
     * Capabilities.  All clients handle text/plain and text/html.
     */
    bufsize = iobuf->l; buffer = iobuf->s;
    if ( scb->request[2].l == 0 ) {
	/* 
	 * No protocol, assume HTTP/0.9 with and implicit Accept: * / *
	 */
	tu_strcpy ( buffer, "*,*/*" ); length = 5;
    } else {
	/*
	 * Default to text/html and text/plain and append any additional
	 * accepted representations specified in request header.
	 */
	tu_strcpy ( buffer, "text/html,text/plain," );
	length = 21;
	http_extract_header ( http_std_atoms.accept, scb, 
		&buffer[21], bufsize-22, &length );
	length = (length > 0 ) ? (length+21) : 20;
        buffer[length] = '\0';
    }
    suffix = http_url_suffix ( ident );
    encoding = "8bit"; enc_len = 4;
    if ( *suffix == '/' ) {
	/*
	 * Requesting directory.  Use special representation so that
	 * people can write their own scripts to handle it.
	 */
	rep = "text/file-directory";
     } else {
	/*
	 * Find representation of suffix that client supports.
	 */
	rep = "text/plain";
        status = http_match_suffix ( suffix, buffer, &rep, &encoding );
	if ( (status == 0) && (*suffix == '.') ) {
	    status = http_match_suffix ( "*.*", buffer, &rep, &encoding );
	    if ( (status == 1) && (0 == tu_strncmp(encoding,"*",2)) ) {
		status = http_match_suffix ( suffix, "*/*", &rep, &encoding );
	    }
	}
	if ( status == 0 ) {
	    status = http_match_suffix ( "*", buffer, &rep, &encoding );
	    if ( (status == 1) && (0 == tu_strncmp(encoding,"*",2)) ) {
		status = http_match_suffix ( suffix, "*/*", &rep, &encoding );
	    }
	}
	if ( status == 0 ) if ( tu_strncmp("HEAD",scb->request[0].s,5) == 0 ) {
	    /*
	     * For HEAD requests, accept it anyway.
	     */
	    status= http_match_suffix ( suffix, "*/*", &rep, &encoding );
	}
        if ( status == 0 ) {
	    status = http_send_error ( scb,"406 Unsupported format",
		"Client does not accept data type" );
	    return status;
	}
    }
    /*
     * Get length of encoding string, breaking on slash (part after slash
     * is content-encoding).
     */
    for ( enc_len = 0; 
	encoding[enc_len] && (encoding[enc_len]!='/'); enc_len++ );
    /*
     * Check for defined presentation and searches.
     */
    if ( http_get_presentation ( rep, &presentation ) ) {
	/*
	 * Dynamic presentation defined for this content-type, construct
	 * exec-like ident for script_execute: url*rep*script
	 */
	tu_strnzcpy(fixup,ident,256);
	pl = tu_strlen(fixup);
	fixup[pl++] = '*';
	tu_strnzcpy(&fixup[pl],rep,sizeof(fixup)-pl-1);
	pl = tu_strlen(fixup);
	fixup[pl++] = '*';
	tu_strnzcpy(&fixup[pl],presentation,sizeof(fixup)-pl-1);
	status = http_script_execute ( scb, "CONVERT", fixup, arg, iobuf );
	return status;
    }
    if ( *arg ) {	/* Search argument present */
	if ( !http_search_script ) {
	    status = http_send_error ( scb, "500 disabled",
		"Search capability not enabled" );
	} else {
	    /* Construct exec-like argument */
	    tu_strnzcpy(fixup,ident,256);
	    pl = tu_strlen(fixup);
	    fixup[pl++] = '*'; fixup[pl++] = '*';
	    tu_strnzcpy(&fixup[pl],http_search_script,sizeof(fixup)-pl-1);
	    status = http_script_execute ( scb, "SEARCH", fixup, arg, iobuf );
	}
	return status;
    }
    /*
     * Scan for if-modified-since.
     */
    length = 0;
    since_time = 0;
    header_only = (0 == tu_strncmp("HEAD",scb->request[0].s,5));
    http_extract_header ( http_std_atoms.if_modified_since, scb, buffer,
	bufsize-1, &length );
    if ( length > 0 ) {
	/*
	 * Convert time to ctime for comparison purposes.
	 */
	buffer[length] = '\0';
	since_time = tf_decode_time ( buffer );
	if ( http_log_level > 1 ) 
	   tlog_putlog ( 2, "!AZ Detected if-modified since: '!AZ' (!UL)!/", 
		scb->log_prefix, buffer, since_time );
    }
    /*
     * Try to open file.
     */
    cache_status = tfc.mode = 0;		/* default to no caching */
    text_mode = tu_strncmp ( encoding, "binary", 6 );  /* non-zero if not equal*/
    if ( text_mode == 0 ) {
	/* Open file for binary access */
	stsmsg = "200 Sending data";
	if ( scb->acc->cache_allowed ) {
	    cache_status = tfc_cached_open ( 0, ident, &of, buffer, &tfc );
	    if ( cache_status == 1 ) stsmsg = "200 Sending cached data";
	    else if ( cache_status == 2 ) {
		/* New entry or overwriting stale entry. */
		stsmsg = "200 Sending data and Caching";
		if ( scb->acc->retention > 0 ) {
		    /*
		     * Override the expiration date.
		     */
		    if ( http_log_level > 1 ) tlog_putlog ( 2, 
			"!AZ Overriding cache refresh, new is !SL!/", 
			scb->log_prefix, scb->acc->retention );
		    status = tfc_update_expiration (&tfc, scb->acc->retention);
		}
	    }
	} else {
	    /* Caching disabled, use regular tf_open call */
	    of = tf_open ( ident, "rb", buffer );
	}
    } else if ( *suffix == '/' ) {
	/*
	 * Open file for special directory listing.
	 */
	rep = "text/html";
	stsmsg = "200 Sending directory listing";
	of = tf_open ( ident, "d", buffer );
	if ( of ) {
	    char index_fname[256];
	    int length, mfd;
	    /*
	     * See if index file from list exists in same directory.
	     */
	    tu_strnzcpy ( index_fname, ident, 255 );
	    length = tu_strlen ( index_fname );
	    for ( o2f = (void *) 0, i = 0; http_index_filename[i]; i++ ) {
	        tu_strnzcpy ( &index_fname[length], http_index_filename[i],
			255-length );
	        o2f = tf_open ( index_fname, "r", buffer );
		if ( o2f ) break;
	    }
	    mfd = 0;
#ifdef VMS
	    if ( !o2f ) {
		/* May be top level, retry with mfd */
		tu_strnzcpy ( &index_fname[length], "000000/", 255-length );
		length = tu_strlen ( index_fname );
		for ( i = 0; !o2f && http_index_filename[i]; i++ ) {
		     tu_strnzcpy ( &index_fname[length], 
			http_index_filename[i], 255-length );
	             o2f = tf_open ( index_fname, "r", buffer );
		}
		if ( o2f ) mfd = 1;	/* using master file directory */
	    }
#endif
	    if ( o2f ) {
		/*
		 * Found welcome file, variable i has index for filename found.
		 */
		if ( scb->acc->local_address && (scb->request[2].l > 0) ) {
		    /*
		     * Full HTTP/1.0 request, send redirect.
		     */
		    int local_port, remote_port, rem_addr;
		    stsmsg = buffer;
		    tu_strcpy ( stsmsg, "302 Found index file\r\n" );
		    location_url ( scb, req_ident,  &stsmsg[22] );
		    if ( mfd ) {
		        tu_strcpy ( &stsmsg[tu_strlen(stsmsg)], "000000/" );
		    }
		    tu_strcpy ( &stsmsg[tu_strlen(stsmsg)], 
				http_index_filename[i] );
		    tf_close ( o2f );
		    scb->acc->cache_allowed = 0;
		} else {
		    /*
		     * Replace directory lookup with real file.
		     */
		    stsmsg = "200 Sending index file";
		    tf_close ( of );
		    of = o2f;
		    text_mode = 1;
		    suffix = ".HTML"; rep = "text/html"; 
		    encoding = "8bit"; enc_len = 4;
		}
	    } else if ( http_dir_access ) {
		/*
		 * No welcome file and browsing restricted.
		 */
		if ( http_dir_access == 1 ) {
	            tu_strnzcpy ( &index_fname[length-7], http_dir_access_file,
			255-(length-7) );
	            o2f = tf_open ( index_fname, "r", buffer );
		}
		if ( o2f ) {
		    tf_close ( o2f );	/* Access allowed, cleanup */
		} else {
		    stsmsg = "403 Directory browse disabled";
		    tf_close ( of );
		    status = http_send_error ( scb,
			"403 Directory browse disabled",
			"Directory not browsable.", buffer );
		    return status;
		}
	    }
	}
    } else {
	stsmsg = "200 Sending document";
	if ( scb->acc->cache_allowed ) {
	    cache_status = tfc_cached_open ( 1, ident, &of, buffer, &tfc );
	    if ( cache_status == 1 ) stsmsg = "200 Sending cached data";
	    else if ( cache_status == 2 ) {
		stsmsg = "200 Sending data and caching";
		if ( scb->acc->retention > 0 ) {
		    /*
		     * Override the expiration date.
		     */
		    if ( http_log_level > 1 ) tlog_putlog ( 2, 
			"!AZ Overriding cache refresh, new is !SL!/", 
			scb->log_prefix, scb->acc->retention );
		    status = tfc_update_expiration (&tfc, scb->acc->retention);
	        }
	    }
	} else {
	    /* Caching disabled */
	    of = tf_open ( ident, "rd", buffer );
	}
        /*
         * Check if open was on a directory (only if dir_access allows though).
         */
        if ( of && scb->acc->local_address && (http_dir_access < 2) && 
		tf_isdir ( of ) ) {
	    /*
	     * Build redirect.
	     */
	    int local_port, remote_port, rem_addr;
	    stsmsg = buffer;
	    tu_strcpy ( stsmsg, "302 Found directory file\r\n" );
	    location_url ( scb, req_ident, &stsmsg[26] );

	    if ( *suffix != '/' ) 
			tu_strcpy ( &stsmsg[tu_strlen(stsmsg)], "/" );
	    scb->acc->cache_allowed = 0;	/* inhibit caching */
	    if ( cache_status ) {
		/*
		 * Abort or clear cache entry.
		 */
	        tfc_rundown_context ( &tfc, 1 );	/* abort */
		cache_status = 0;
	    }
	    suffix = "/"; rep = "text/html";
	}
    }
    /*
     * Check ownership of file if UIC specified, file must either be owned by
     * same UIC or a resource identifier.
     */
    if ( (of || cache_status) && scb->acc->uic && (*suffix != '/') ) {
	/*
	 * If we aren't cached, load tfc uic field for test.
	 */
	int status;
	if ( !cache_status ) {
	    status = tf_header_info ( of, &tfc.hdr.size, 
		&tfc.hdr.uic, &tfc.hdr.cdate, &tfc.hdr.mdate );
	} else status = 0;

	if ( (status < 0)  || ( !(tfc.hdr.uic&0x80000000) && 
		(tfc.hdr.uic != scb->acc->uic) ) ) {
	    if ( of ) tf_close ( of );
	    tfc_rundown_context ( &tfc, 1 );	/* abort */
	    cache_status = 0;
	    of = (void *) 0;
	    tu_strcpy ( buffer, "not owner." );
	}
    }
    if ( of || (cache_status==1) ) {
	/*
	 * File opened and cleared for takeoff, format header line.
	 */
	http_add_response ( scb->rsphdr, stsmsg, 0 );
        http_add_response (  scb->rsphdr, "MIME-version: 1.0", 0 );
	tu_strcpy ( hdrline, "Server: OSU/" );
	tu_strcpy ( &hdrline[12], http_server_version );
	http_add_response ( scb->rsphdr, hdrline, 0 );
	tu_strcpy ( hdrline, "Content-type: " );
	tu_strcpy ( &hdrline[14], rep );
	http_add_response ( scb->rsphdr, hdrline, 0 );
	tu_strcpy ( hdrline, "Content-transfer-encoding: " );
	tu_strnzcpy ( &hdrline[27], encoding, enc_len );
	http_add_response ( scb->rsphdr, hdrline, 0 );
	if ( encoding[enc_len] == '/' ) {
	    tu_strcpy ( hdrline, "Content-encoding: " );
	    tu_strcpy ( &hdrline[18], &encoding[enc_len+1] );
	    http_add_response ( scb->rsphdr, hdrline, 0 );
	}
	if ( *suffix != '/' ) {
	    /*
	     * Not a directory, add last-modified time and content-length.
	     */
	    int size;
	    if ( !cache_status ) {
		/*
		 * load tfc.hdr fields from openned file since cache not read.
		 */
		tf_header_info ( of, &tfc.hdr.size, 
		    &tfc.hdr.uic, &tfc.hdr.cdate, &tfc.hdr.mdate );
	    }
	    if ( !text_mode || (cache_status == 1)) {
 		    if ( scb->keepalive_count < scb->keepalive_limit ) {
			http_add_response ( scb->rsphdr, 
				"Connection: Keep-Alive", 0 );
		        scb->keepalive_pending = 1;
		    }
		    tu_strcpy ( hdrline, "Content-length: " );
		    tu_strint ( tfc.hdr.size, &hdrline[16] );
		    http_add_response ( scb->rsphdr, hdrline, 0 );
 	    }
	    tu_strcpy ( hdrline, "Last-Modified: " );
	    tf_format_time ( tfc.hdr.mdate, &hdrline[15] );
	    http_add_response ( scb->rsphdr, hdrline, 0 );
	    /*
	     * Check if translation step encounted an expires option and
	     * add expires tag.  Expiration time to send determined by etype:
	     *   1 - Create date of file plus exp_offset.
	     *   2 - Modify date of file plus exp_offset.
	     *   3 - Expiration date from file header.
             */
	    if ( scb->acc->fopt.etype > 0 ) {
		unsigned int edate;
		int vstatus, reset_flag;
		/* 
		 * Compute expriation date based on etype code.
		 */
		if ( scb->acc->fopt.etype == 1 ) edate = tfc.hdr.cdate +
			scb->acc->fopt.exp_offset;
		else if ( scb->acc->fopt.etype == 2 ) edate = tfc.hdr.mdate +
			scb->acc->fopt.exp_offset;
		else edate = tfc.hdr.edate;
		/* 
		 * Validate exp. time against cache entry.  Verify function
		 * returns 0 cache expiration time is after edate, resetting
		 * if reset_flag true.
		 */
		reset_flag = (scb->acc->retention > 0) ? 0 : 1;
		vstatus = tfc_verify_expiration ( &tfc, edate, 1 );
		if ( (vstatus == 0) && (scb->acc->retention > 0) ) {
		    /* let http_session.c send the expires header */
		    if ( http_log_level > 1 ) tlog_putlog ( 2,
			"!AZ Punting send of expires header!/", scb->log_prefix );
		    scb->acc->fopt.etype = 0;	
		} else {
		    if ( http_log_level > 1 ) tlog_putlog ( 2,
		        "!AZ Sending expires header, type: !SL offset: !SL v: !SL !SL!/",
			scb->log_prefix, scb->acc->fopt.etype, scb->acc->fopt.
			exp_offset, vstatus, reset_flag );
		    tu_strcpy ( hdrline, "Expires: " );
		    tf_format_time ( edate, &hdrline[9] );
		    http_add_response ( scb->rsphdr, hdrline, 0 );
		}
	    }
	    /*
	     * If request has if-modified-header, check time and reset
	     * response buffer if file not updated since then and
	     * not using no-cache port.
	     */
	    if ( since_time && (since_time >= tfc.hdr.mdate) &&
			scb->acc->cache_allowed ) {
		    char *s;
		    for ( size = 0, s = scb->rsphdr->s; 
			s[size] && (s[size] != ' '); size++ );
		    scb->rsphdr->l = size+1;	/* Reset size to HTTP/1.0 */
		    http_add_response ( scb->rsphdr,
			"304 Document not modified", 0  );
		    http_add_response ( scb->rsphdr, hdrline, 0 );
 		    if ( (scb->keepalive_count < scb->keepalive_limit) && 
 		    		!scb->keepalive_pending ) {
			http_add_response ( scb->rsphdr, 
				"Connection: Keep-Alive", 0 );
			http_add_response ( scb->rsphdr, 
				"Content-length: 0", 0 );
		        scb->keepalive_pending = 1;
		    }
 		    header_only = 1;	/* skip sending data when 304 status */
	    }
	}
	if ( header_only ) {
	    status = http_send_response_header ( scb );
	    if ( cache_status == 2 ) {		/* Delete created cache entry */
		tfc_rundown_context ( &tfc, 1 );
		cache_status = 0;
	    }
	} else {  /* if ( ((status&1) == 1) && !header_only ) { */
	    int size, used;
	    /*
	     * Send data, either binary, text, or directory.
	     */
	    used = 0;
	    if ( *suffix == '/' ) {
		/*
		 * Get and format directory lines (only if not re-directed).
		 */
	        status = http_send_response_header ( scb );
		if ( (status&1)  &&  (tu_strncmp ( stsmsg, "302", 3 ) != 0) )
		    status = dir_to_html ( scb->cnx, of, buffer, bufsize, 
			&scb->data_bytes );
		    used = scb->data_bytes;
	    } else if ( cache_status == 1 ) {
		/*
		 * returned cached entry.
		 */
		int length;
	        status = http_send_response_header ( scb );
		while ( tfc_get ( &tfc,  (void **) &buffer, &length) ) {
		    status = ts_tcp_write ( scb->cnx, buffer, length );
		    if ( (status&1) == 0 ) break;
		    scb->data_bytes += length;
		}

	    } else if ( text_mode ) {
		/*
		 * Get text lines and transmit, buffer multiple lines / write.
		 * Defer sending content header until after first buffer
		 * read.
		 */
		tf_set_crlf_newline ( of, http_crlf_newline );
		size = tf_getline(of, &buffer[used], bufsize-used, 2000 );
#ifdef DISALLOW_NULLFILE
		if ( size > 0 ) {
#else
		if ( size >= 0 ) {
#endif
		    /* only add to counters if positive (non-error) */
		    scb->data_bytes += size;
		    used += size;
		} else {
		    /* reset response header with 500 error message */
    		    if ( scb->request[2].l > 0 ) scb->rsphdr->l = 9;
		    else scb->rsphdr->l = 0;
		    if ( cache_status ) tfc_rundown_context ( &tfc, 1 );
		    http_add_response ( scb->rsphdr,
			"500 Error reading specified document", 1 );
		}
		if ( tf_at_eof ( of ) ) {
		    /*
		     * Entire file contents fit in the buffer, append 
		     * content-length to header.
		     */
		    if ( scb->keepalive_count < scb->keepalive_limit ) {
			http_add_response ( scb->rsphdr, 
				"Connection: Keep-Alive", 0 );
		        scb->keepalive_pending = 1;
		    }

		    tu_strcpy ( hdrline, "Content-length: " );
		    tu_strint ( size, &hdrline[16] );
		    http_add_response ( scb->rsphdr, hdrline, 0 );
		    size = 0;
		}
		/*
		 * Send response header and rollback cache state if error
		 * in write.
		 */
	        status = http_send_response_header ( scb );
		if ( (status&1) == 0 && tfc.mode == 2 )
			tfc_rundown_context ( &tfc, 1 );
		/*
		 * Send file data to client, saving in cache while doing so.
		 */
		while ( (status&1) && (size > 0) ) {
		    if ( bufsize - used < 100 ) {
			status = send_and_cache(scb->cnx, &tfc, buffer, used);
			used = 0;
		        if ( (status&1) == 0 ) break;
		    }
		    size = tf_getline(of, &buffer[used], bufsize-used, 2000 );
#ifdef DEBUG
printf("getline returned size: %d, used is: %d of %d\n", size, used, bufsize );
#endif
		    scb->data_bytes += size;
		    used += size;
		}
		/*
		 * Flush remaining data.
		 */
		if ( used > 0 ) {
		    status = send_and_cache(scb->cnx, &tfc, buffer, used);
		}
	    } else {
		/*
		 * Read binary data.
		 */
	        status = http_send_response_header ( scb );
		if ( (status&1) == 0 && tfc.mode == 2 )
			tfc_rundown_context ( &tfc, 1 );
		for ( ; 0 < ( size = tf_read(of,buffer,bufsize) );
			scb->data_bytes += size ) {
		    status = send_and_cache ( scb->cnx, &tfc, buffer, size );
		    if ( (status&1) == 0 ) break;
		}
	    }
	}
	/*
	 * Close to deallocate file access structure.
	 */
	if ( of ) tf_close ( of );
	if ( tfc.mode ) tfc_rundown_context ( &tfc, 0 );
    } else {
	/* Open error, tfc_cached_open puts formatted error in buffer array */
	if ( http_log_level > 2 ) {
	    tlog_putlog ( 3, "!AZ Open error on file '!AZ', openfail: '!AZ'!/", 
		scb->log_prefix, ident, http_error_page.openfail.type ?
			http_error_page.openfail.ident : "*" ); 
	}
	if ( http_error_page.openfail.type ) {
	    if ( tu_strncmp ( ident, 
		http_error_page.openfail.munged_ident, 512 ) != 0 ) {
		/*
		 * Use alternate report page.
		 */
		status = http_process_error ( scb,
		    &http_error_page.openfail, "404 ", req_ident, 
		    fixup, sizeof(fixup), arg, iobuf );
		return status;
	    }
	}
	status = http_send_error ( scb,
		"404 error opening file in request", buffer );
    }
    return status;
}
/****************************************************************************/
/*  Read directory information and format into HTML document.
 */
static int dir_to_html ( void *ctx, void *of, char *buffer, int bufsize,
	int *data_bytes )
{
    int length, i, count, fmtlen, tot_len, status;
    struct tu_textbuf buf;
    char element[400];
    /*
     * Initialize textbuf structure to use buffer and add header text.
     */
    buf.l = 0;
    buf.s = buffer;
    buf.size = bufsize;

    tu_add_text ( &buf, 
	"<HTML><HEAD><TITLE>Directory listing</TITLE></HEAD>", 80 );
    tu_add_text ( &buf, "<BODY>Files:<DIR>\r\n", 80 );
    /*
     * Format list item for every directory entry.
     */
    count = 0;
    while ( (tot_len = tf_read(of, element, sizeof(element)) ) > 0 ) {
	for ( i = 0; i < tot_len; i += length + 1 ) {
	    /*
	     * Make sure we have enough room to construct element.
	     */
	    fmtlen = length = tu_strlen ( &element[i] );
#ifdef VMS
	    if ( length > 4 ) {
		/* Convert directory files to / syntax */
		if ( tu_strncmp ( ".DIR", &element[i+length-4], 5 ) == 0 ) {
		    tu_strcpy ( &element[i+length-4], "/" );
		    fmtlen = length - 3;
		}
	    }
#endif
	    if ( buf.l + (2*fmtlen) + 22 > bufsize ) {
	        if ( buf.l > 0 ) status = ts_tcp_write ( ctx, buf.s, buf.l );
	        else status = 1;
	        if ( (status &1) == 0 ) return status;	/* error */
	        *data_bytes += buf.l;			/* Update stats */
	        buf.l = 0;
	    }
	    /*
	     * Append element to buffer.
	     */
	    count++;
	    tu_add_text ( &buf, "<LI><A HREF=\"", 14 );
	    tu_add_text ( &buf, &element[i], fmtlen );
	    tu_add_text ( &buf, "\">", 2 );
	    tu_add_text ( &buf, &element[i], fmtlen );
	    tu_add_text ( &buf, "</A>\r\n", 6 );
	}
    }
    /*
     * Add trailer and flush.
     */
    if ( buf.l + 80 > bufsize ) {
	if ( buf.l > 0 ) status = ts_tcp_write ( ctx, buf.s, buf.l );
	else status = 1;
	if ( (status &1) == 0 ) return status;	/* error */
	*data_bytes += buf.l;			/* Update stats */
	buf.l = 0;
    }
    tu_strint ( count, element );
    tu_add_text ( &buf,"\r\n</DIR>\r\nNumber of files in directory: ", 40 );
    tu_add_text ( &buf, element, tu_strlen(element) );
    tu_add_text ( &buf, "\r\n</BODY></HTML>\r\n",19  );
    status = ts_tcp_write ( ctx, buf.s, buf.l );
    *data_bytes += buf.l;

    return status;
}
