/*
 * This module is an MST server script for the DECthread HTTP_SERVER.  It
 * provides support for extended directory browsing.  Options are read
 * from a separate configuration file at startup.
 *
 * Use the following rules in the configuration file to load this module and 
 * delcare it a converter script for file directories.
 *
 *    ThreadPool ds_pool stack=162000 q_flag=1 limit=5
 *    Service dirserv pool=ds_pool dynamic=(dirserv,http_dirserv_mst)\
 *	info=www_system:http_dirserv.conf
 *    presentation text/file-directory %dirserv:/dirserv
 *
 *
 *  Author:	David Jones
 *  Date:	5-SEP-1995
 *  Revised:	9-DEC-1995	Return valid HTTP response  on dir open failure.
 *  Revised:	4-APR-1996	Preserve search arguments in redirects.
 *  Revsied:	14-FEB-1998	Hack to work around EFS handling in C RTL.
 *  Revised:	5-MAY-1999	add EFS fixup.
 *  Revised:	12-APR-2000	Initial language support hack.
 */ 
#include "pthread_1c_np.h"
#include <time.h>
#ifdef VMS
#include <stat.h>
#else
#include <sys/stat.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "dirserv_options.h"	/* dir_opt typdef */
#ifdef PTHREAD_USE_D4
#define CONDITIONAL_YIELD if ( !http_reentrant_c_rtl ) pthread_yield();
#else
#define CONDITIONAL_YIELD if ( !http_reentrant_c_rtl ) sched_yield();
#endif
#include <stdlib.h>
#include "tutil.h"
#include "file_access.h"
#include "mst_share.h"	/* mst support routines */

struct stream_ctx {
    void *ff;			/* Input file */
    int used, filled;
    char buffer[4096];
};
typedef struct stream_ctx *stream;
int htds_read_rules(), htds_get_rules();
/********************************************************************/
/* Init routine for use when this MST is a dynamically loaded shareable image.
 */
int dirserv_init ( mst_linkage vector, char *info, char *errmsg )
{
    int status;
    status = mstshr_init ( vector, info, errmsg );
    if ( (status&1) == 0 ) return status;
    tf_initialize("");
    /*
     * Read config file name, specified via info argument.
     */
    status = htds_read_rules ( info );
    sprintf ( errmsg, "Status of reading '%s' is %d", info, status );
    return status;
}

/**************************************************************************/

/*
 * Thread-based DECnet script server for use with HTTP server.  Provide
 * extended directory listing.  Note that this is a scriptserver program,
 * not a WWWEXEC-based script.
 *
 * The program reads a configuration file to determine how to present
 * the directory.  The file format is that same as for the HTTP server
 * but the rule set is different:
 *
 *     Include file-name		# Include specified file.
 *     Welcome file-name [lang-list]	# add directory index to search list.
 *     DirAccess [ON|OFF|SELECTIVE [control-file [OVERRIDE]]]
 *					# controls file listing.
 *     DirReadme [OFF|TOP|BOTTOM] [readme-file]
 *					# controls inclusion of README files.
 *     DirShowDate [ON|OFF]		# Include last modify date.
 *     DirShowSize [ON|OFF]		# Include file size.
 *     DirShowBytes [ON|OFF]		# Report size in bytes rather than Kb.
 *     DirShowHidden [ON|OFF]		# Supress listing ".ext" files.
 *
 * The name of the configuration file is supplied by the HTTP server 
 * configuration (see below).
 *
 * Command-line format:
 *
 *	$ dirserv err-file
 *
 * args:
 *    err-file	    Name of log file to create, connections and query strings
 *		    are written to the log file.
 *
 * Logical names:
 *    WWW_DIRSERV_OBJECT	If defined as an exec mode logical, indicates
 *				that server should become a persitent network
 *				object using the equivalence name as taskname.
 *				If not defined, program will translate sys$net
 *				and service a single request.
 *
 *    WWW_DIRSERV_ACCESS	List of node/username pairs that are allowed
 *				to access this object.  If missing, no access
 *				checks are performed.
 *
 * Environment variables:
 *
 * HTTP server configuration:
 *     To configure this program as the server's directory presentation
 *     script, add a presentation rule with the following form:
 *
 *		presentation text/file-directory node::"0=WWWDIR"config-file
 *
 *  Author:	David Jones
 *  Date:	 1-DEC-1994
 *  Revised:    16-DEC-1994		Support HEAD command (req. by spec).
 *  Revised:    17-DEC-1994		Added remote user check.
 *  Revised:	21-FEB-1995		Fix SYSNAM report and cleanly close
 *					connections.
 *  Revised:    16-MAR-1995		Fix bug introduced by 21-feb fix.
 *  Revised:	31-MAR-1995		Work around Netscape bug.
 */



#define RESPONSE_LINE_LIMIT 100
typedef struct { int l; char *s; } string;
struct out_stream { void *ctx; int used; char buf[1024]; };
/*
 * Global (program-wide) variables.
 */
static int min_available;
static int send_module(), send_catalogue();
static char conf_file[256];
static int list_files ( struct out_stream *out, void *dirf, dir_opt *opt,
	char *dirbuf, int bufsize, char *full_name, char *tail );

struct language_spec {
    char code[20];
    float qual;
};
/**************************************************************************/
/* Utility routine to buffer output, accumulating small text fragments
 * into larger chunks for efficient I/O
 */

static int put_text ( struct out_stream *stream, char *ctlstr, ... )
{
    int used, status, i, count, lo;
    char *buf, *text;
    va_list arg;
    used = stream->used;
    buf = &stream->buf[used];
    va_start ( arg, ctlstr );
    for (i = 0; ctlstr[i]; i++ ) {
	if ( (ctlstr[i] == '%') && 
	    ((ctlstr[i+1] == 's') || (ctlstr[i+1] == 't')) ) {
	    /* Replace %s in ctlstr with next argument */
	    i++;
	    if ( ctlstr[i] == 's' ) 
		for (text = va_arg(arg,char *); *text; used++) {
		    if ( used >= sizeof(stream->buf) ) {
	        	status = mst_write ( stream->ctx, stream->buf, used, &lo );
	        	if ( (status&1) == 0 ) return status;
	        	used = 0;
	        	buf = stream->buf;
		    }
	            *buf++ = *text++;
		}
	    else for (text = va_arg(arg,char *); *text; used++) {
		    if ( *text == '\n' ) {
		        if ( used >= sizeof(stream->buf) ) {
	        	    status = mst_write ( stream->ctx, 
					stream->buf, used, &lo );
	        	    if ( (status&1) == 0 ) return status;
	        	    used = 0;
	        	    buf = stream->buf;
		        }
			*buf++ = '\r';
			used++;
		    }
		    if ( used >= sizeof(stream->buf) ) {
	        	status = mst_write ( stream->ctx, stream->buf, used, &lo );
	        	if ( (status&1) == 0 ) return status;
	        	used = 0;
	        	buf = stream->buf;
		    }
	            *buf++ = *text++;
		}
	} else {
	    /* Add ctlstr character to output stream. */
	    if ( used >= sizeof(stream->buf) ) {
	        status = mst_write ( stream->ctx, stream->buf, used, &lo );
	        if ( (status&1) == 0 ) return status;
	        used = 0;
	        buf = stream->buf;
	    }
	    *buf++ = ctlstr[i]; used++;
	}
    }
    stream->used = used;
    return 1;
}
/*************************************************************************/
/* Routine to handle parsing of request headers for accept-language.
 * the hdr string is destroyed.
 */
static int parse_language_value ( char *value, int vlen,
	struct language_spec *lang, int lang_size, int *lang_count )
{
    int i, pos, state;
    struct language_spec *cur;
    /*
     * Header value is comma-separated list.
     */
    cur = lang;
    if ( *lang_count >= lang_size ) return 1;	/* no room */
    for ( state = i = pos = 0; i <= vlen; i++ ) {
	if ( http_log_level > 19 ) tlog_putlog ( 20,
		"   valparse value[!SL] = '!AF', state: !SL !SL/!SL!/", 
		i, 1, &value[i], state, *lang_count, lang_size );
	if ( state == 0 ) {
	    if ( (i==vlen) || (value[i] == ',') ) {
		cur->code[pos] = '\0';
		cur++;
		pos = 0;
		cur->code[pos] = '\0';
		cur->qual = 1.0;
		*lang_count = (*lang_count) + 1;
    		if ( *lang_count >= lang_size ) return 1;   /* no more room */
	    } else if (value[i] == ';') {
		state = 1;
		*lang_count = (*lang_count) + 1;
	    } else if ( pos < (sizeof(lang->code)-1) ) {
		cur->code[pos++] = value[i];
	    }
	} else if ( state == 1 ) {
	    if ( (i==vlen) || (value[i] == ',') ) {
		cur->code[pos] = '\0';
     		if ( *lang_count >= lang_size ) return 1;   /* no more room */
		cur++;
		pos = 0;
		cur->code[pos] = '\0';
		cur->qual = 1.0;
		state = 0;
	    }
	}
    }
    return 1;
}
static int parse_language_header ( char *hdr, int hdrlen,
	struct language_spec *lang, int lang_size, int *lang_count )
{
    int i, vlen, j, status;
    char *value;
    /*
     * Find label delimiter and upcase string.
     */
    value = (char *) 0;
    for ( i = 0; i < hdrlen; i++ ) if ( hdr[i] == ':' ) {
	hdr[i] = '\0';
	value = &hdr[i+1];
	vlen = hdrlen - i - 1;
	tu_strupcase ( hdr, hdr );
	if ( tu_strncmp ( hdr, "ACCEPT-LANGUAGE", 16 ) == 0 ) {
	    /*
	     *  parse the string and append to list.
	     */
	    if ( http_log_level > 8 ) tlog_putlog ( 9,
	   	"Accept-language header val: '!AF'!/",
		hdrlen-i-1, value );
	    while ( (*value == ' ') || (*value == '\t') ) {
		value++;	/* skip leading whitespace */
		vlen--; if ( vlen <=  0 ) return 1;	/* no value !!! */
	    }
	    status = parse_language_value ( value, vlen, 
			lang, lang_size, lang_count );
	    return status;

	} else {
	     return 1;		/* not a header we are interested in */
	}
    }
    return 1;
}

static int match_language ( 
	struct language_spec *accepted_lang, 	/* what client will accept */
	int al_count, 
	char *lang_list, 			/* what we can provide */
	int lang_count )
{
    int i, j, off, length;
    /*
     * Output loop iterates over languages client says it understood.
     */
    if ( http_log_level > 8 ) tlog_putlog ( 9,
	"Match_languge counts: !SL !SL/!/", al_count, lang_count );
    for ( i = 0; i < al_count; i++ ) {
	/*
	 * Inner loop tests client language against each language covvered by
	 * file.
	 */
	off = 0;
	for ( j = 0; j < lang_count; j++ ) {
	    if ( http_log_level > 8 ) tlog_putlog ( 9,
		"accept[!SL] = '!AZ' provide[!SL] = '!AZ'!/", i,
		accepted_lang[i].code, j, &lang_list[off] );
	    length = tu_strlen ( &lang_list[off] );
	    if ( tu_strmatchwild ( accepted_lang[i].code, &lang_list[off] )
		== 0 ) return 1;	/* we have a match */
	    off = off + length + 1;
	}
    }
    return 0;	/* nothing matched */
}
/*************************************************************************/
/* Main routine for handling htds server connection.  This function is called 
 * as the thread init routine for MST server threads.
 */
int dirserv ( mst_link_t ctx, 	  	/* MST connection context */
	char *service, 			/* Service name */
	char *info, 			/* optional argument with exec rule */
	int ndx,			/* Thread index */
	int available )			/* # of contexts left on  free list */
{
    int length, status, i, tf_len, j, lo;
    int header_only, accepted_lang_count;
    string prologue[4];
    string request[128];
    char *full_name, *tail, prolog_buf[1104];

    char errmsg[256], dir_file[256], dirbuf[4096];
    char taskname[20], cre_date[32];
    char *bp, *opcode, *method, *url, *protocol, *module, log_prefix[40];
    char *sa, search_arg[256];
    struct language_spec accepted_lang[20];
    struct out_stream outbound, *out;
    dir_welcome *def;
    void *dirf;
    dir_opt opt;
    /*
     * Make prefix string for log entries so we can follow interleaved
     * entries in log file.
     */
    tu_strcpy ( log_prefix, "Service" );
    tu_strnzcpy ( &log_prefix[7], service, sizeof(log_prefix)-18 );
    i = tu_strlen ( service ) + 7; log_prefix[i++] = '/';
    tu_strint ( ndx, &log_prefix[i] );
    if ( http_log_level > 0 ) tlog_putlog ( 1, 
	"!AZ connected, info: '!AZ', pool remaining: !SL!/", log_prefix,
	info, available );
    /*                   0      1        2        3
     * Read prologue (module, method, protocol, ident) sent by HTTP server.
     */
    for ( i = 0, bp = prolog_buf; i < 4; i++ ) {
	status = mst_read ( ctx, bp, 255, &length );
	if ( (status&1) == 1 ) {
	    prologue[i].l = length;
	    prologue[i].s = bp;
	    bp[length++] = '\0';	/* safety first, terminate string */
	    bp = &bp[length];
	} else {
	    tlog_putlog ( 0, "!AZ, Error reading prologue: !SL!/", log_prefix,
			status );
	    return status;
	}
    }
    htds_get_rules ( (void *) 0, &opt );	/* Load global values */
    /*
     * If welcome list specified language restrictions, get the language list.
     */
    accepted_lang_count = 0;
    if ( opt.check_language ) {
        status = mst_write ( ctx, "<DNETHDR>", 9, &lo );
	if ( (status&1) == 0 ) return status;
	do {
	    status = mst_read ( ctx, dirbuf, sizeof(dirbuf), &length );
	    if ( (status&1) == 0 ) return status;
	    status = parse_language_header ( dirbuf, length,
		accepted_lang, 20, &accepted_lang_count );
	} while ( length > 0 );
	if ( (status&1) == 0 ) return status;	/* I/O error */
    }
    /*
     * Fetch original URL so we can put it in page title.
     */
    status = mst_write ( ctx, "<DNETRQURL>", 11, &lo );
    if ( (status&1) == 1 ) status = 
	mst_read ( ctx, dirbuf, sizeof(dirbuf)-1, &length );
    if ( (status&1) == 0 ) return status;
    dirbuf[length] = '\0';
    /*
     * Fetch search argument if present.
     */
    search_arg[0] = '\0';
    sa = tu_strstr ( dirbuf, "?" );
    if ( sa ) {
	*sa = '\0';
	status = mst_write ( ctx, "<DNETARG2>", 10, &lo );
	if ( (status&1) == 1 ) status = mst_read ( ctx, search_arg,
		sizeof(search_arg)-1, &lo );
	if ( (status&1) == 1 ) search_arg[lo] = '\0';
	else return status;
    }
    /*
     * Make pointers to portion of buffer with translated path and ending
     * location so we can easily append.
     */
    full_name = prologue[3].s;
    tail = &full_name[prologue[3].l];
    /*
     * Initialize structure for buffering output fragments into larger
     * messages (much more efficient) and put connection in CGI mode.
     */
    outbound.ctx = ctx;
    outbound.used = 0;
    out = &outbound;
    mst_write ( ctx, "<DNETCGI>", 9, &lo );	/* separate message */
    /*
     * Check method, must be GET or HEAD.  Set header_only flag if HEAD.
     */
    header_only = 0;
    if ( tu_strncmp ( prologue[1].s, "GET", 4 ) != 0 ) {
	if ( tu_strncmp ( prologue[1].s, "HEAD", 5 ) != 0 ) {
	    put_text ( out, "Status: 404 unsupported method\r\n%s",
	       "Content-type: text/plain\r\n\r\nError, unsupported method\r\n");
	    if ( outbound.used > 0 ) mst_write ( ctx, out->buf, out->used, &lo );
	    mst_write ( ctx, "</DNETCGI>", 10, &lo );
	    status = mst_close ( ctx );
	    return 0;
	}
	header_only = 1;
    }
    /*
     * Search list of welcome files for first existent one and redirect.
     */
    for ( def = opt.welcome_list; def; def = def->next ) {
	void *wf;
	if ( def->lang_count > 0 ) {
	    /*
	     * screen request against the current request language.
	     */
	    if ( !match_language ( accepted_lang, accepted_lang_count,
		def->lang_list, def->lang_count ) ) continue;
	}
	tu_strcpy ( tail, def->fname );
	if ( http_log_level > 3 ) 
		tlog_putlog(4,"Searching for '!AZ'!/", full_name );
	wf = tf_open ( full_name, "r", errmsg );
	if ( wf ) {
	    /* Found file, redirect using original path client requested */
	    tf_close ( wf );
	    put_text ( out, "Location: %s%s%s\r\n\r\n", dirbuf, tail,
		search_arg );
	    if ( outbound.used > 0 ) mst_write ( ctx, out->buf, out->used, &lo );
	    mst_write ( ctx, "</DNETCGI>", 10, &lo );
	    status = mst_close ( ctx );
	    return 1;
	}
    }
    /*
     * No welcome file, check restrictions on browsing:
     *    0 - no restrictions.
     *   -1 - Never browse.
     *    1 - Allow browse if control-file present (selective)
     *    2 - Allow browse if control-file present and read control file
     *        as additional config file rules (selective override).
     */
    if ( opt.access ) {
	void *cf;
	/* Look for control file. (selective) add*/
	tu_strnzcpy ( tail, opt.control_file, 80 );
	
	cf = tf_open ( full_name, "r", errmsg );
	if ( !cf ) opt.access = -2;
	else {
	    if ( opt.access > 1 ) htds_get_rules ( cf, &opt );
	    else tf_close ( cf );
	}

	if ( opt.access < 0 ) {
	    /*
	     * Browsing not allowed on this directory.
	     */
	    put_text ( out, "status: 403 Not browsable\r\n" );
	    put_text ( out, "content-type: text/plain\r\n\r\n" );
	    put_text (out, opt.access == -2 ?
		 "Directory not browsable (no %s file present).\r\n" :
		 "Directory browse disabled.\r\n", tail );
	    if ( out->used > 0 )
		status = mst_write ( ctx, out->buf, out->used, &lo );
	    mst_write ( ctx, "</DNETCGI>", 10, &lo );
	    status = mst_close ( ctx );
	    return 1;
	}
    }
    /*
     * Open ident passed to us in prologue in special 'd' mode that
     * performs directory scan.  (reads return multiple filenames
     * separated by nulls)
     */
    *tail = '\0';
    dirf = tf_open ( full_name, "d", errmsg );
    if ( !dirf ) {
	put_text ( out, 
		"Status: 400 bad request\r\ncontent-type: text/plain\r\n\r\n");
	put_text ( out, "Error(400): " );
	put_text ( out, errmsg );
	put_text ( out, "\r\n" );
	if ( out->used > 0 )
		status = mst_write ( ctx, out->buf, out->used, &lo );
	mst_write ( ctx, "</DNETCGI>", 10, &lo );
	status = mst_close ( ctx );
	return 1;
    }
    put_text ( out, 
	"status: 200 Directory listing follows\r\ncontent-type: text/html\r\n\r\n" );
    if ( out->used > 0 ) {
	status = mst_write ( ctx, out->buf, out->used, &lo );
	outbound.used = 0;
    }
    if ( header_only ) {
	/* don't return any data */
	mst_write ( ctx, "</DNETCGI>", 10, &lo );
	tf_close ( dirf );
	status = mst_close ( ctx );
	if ( (status&1) == 0 )
        	tlog_putlog ( 0, "!AZ, status of close !SL!/", log_prefix, status );
	return status;
    }
    /*
     * Generate header section of HTML output.
     */
    put_text ( out, "<HTML><HEAD><TITLE>Directory %s</TITLE></HEAD>\r\n",
		dirbuf);
    put_text ( out, "<BODY><DL>" );
    /*
     * Body of document is list.  Check for readme with TOP option.
     */
    if ( opt.readme == 1 ) {
	void *rf;
	tu_strcpy ( tail, opt.readme_file );
	rf = tf_open ( full_name, "r", errmsg );
	if ( rf ) {
	    put_text ( out, "<DT>Description</DT>\r\n<DD><PRE>" );
	    while ( 0 < (length=tf_read(rf,dirbuf,sizeof(dirbuf)-1) ) ) {
		dirbuf[length] = '\0';
		status = put_text ( out, "%t", dirbuf );
		if ( (status&1) == 0 ) return status;
	    }
	    tf_close ( rf );
	    put_text ( out, "</PRE></DD>\r\n" );
	}
    }
    /*
     * Read directory and convert to HTML.
     */
    list_files ( out, dirf, &opt, dirbuf, sizeof(dirbuf), full_name, tail );
    put_text(out, "</DL><HR></BODY></HTML>\r\n");
    /*
     * Body of document is list.  Check for readme with BOTTOM option.
     */
    if ( opt.readme == 2 ) {
	void *rf;
	tu_strcpy ( tail, opt.readme_file );
	rf = tf_open ( full_name, "r", errmsg );
	if ( rf ) {
	    put_text ( out, "<DT>Description</DT>\r\n<DD><PRE>" );
	    while ( 0 < (length=tf_read(rf,dirbuf,sizeof(dirbuf)-1) ) ) {
		dirbuf[length] = '\0';
		status = put_text ( out, "%t", dirbuf );
		if ( (status&1) == 0 ) return status;
	    }
	    tf_close ( rf );
	    put_text ( out, "</PRE></DD>\r\n" );
	}
    }
    /*
     * Flush remaining buffer and mark end of output.
     */
    if ( outbound.used > 0 ) mst_write ( ctx, out->buf, out->used, &lo );
    mst_write ( ctx, "</DNETCGI>", 10, &lo );
    tf_close ( dirf );
    status = mst_close ( ctx );
    if ( (status&1) == 0 )
        tlog_putlog ( 0, "!AZ, status of close !SL!/", log_prefix, status );
    return status;
}
/***********************************************************************/
/* In 7.2 C runtime does not handle ODS-5 extended filenames properly
 * when filename is in unix syntax.  Check input specification for
 * this condition and convert to VMS syntax if needed, placing
 * re-written specification in work.  Return value is either
 * original string pointer or work.
 *
 * We assume any EFS specifications properly escape the extended characters
 * with circumflexes.  Note that slashes are not valid in filename
 * so its presence indicates a unix syntax specification.
 */
#ifdef ODS5_FIXUP
char *efs_fixup ( char *ident, char *work, int work_size )
{
    int i, j, slash_count, last_ddelim;
    char c, *newspec;
    /*
     * scan for escapes and characters that need escapes.
     */
    slash_count = 0;
    for ( i = 0; c = ident[i]; i++ ) {
	if ( c == '/' ) slash_count++;
	if ( c == '^' ) break;
    }
    if ( !c || (slash_count==0) ) return ident;	/* valid for CRTL use */
    /*
     * do fixup.  Break ident into elements.
     */
    j = 0;			/* output length */
    newspec = work;
    work_size = work_size - 2;
    if ( ident[0] == '/' ) {	/* first token is 'device' */
	last_ddelim = 0;	/* directory delimiter position */
    } else {
	newspec[j++] = '[';
	newspec[j++] = '.';	/* sub-directory */
        newspec[j++] = ident[0];
	last_ddelim = 1;
    }
    for ( i = 1; c = ident[i]; i++ ) {
	if ( c == '/' ) {
	    if ( last_ddelim == 0 ) {
	        newspec[j++] = ':';
	        newspec[j++] = '[';
	    } else {
	        newspec[j++] = '.';
	    }
	    if ( ((j-last_ddelim) == 2) && (newspec[j-1] == '.') ) {
		/* ignore /./ sequences */
		j = j - 1;
	    } else if ( ((j-last_ddelim) == 3) &&
		(tu_strncmp ( &newspec[j-1], "..", 2 ) == 0) ) {
		/*
		 * Replace /../ with -
		 */
		j = j - 1;
		newspec[j-1] = '-';
	    }
	    last_ddelim = j-1;
        } else {
	    newspec[j++] = c;
	}
	/*
	 * Give up if we overflow the buffer.
	 */
	if ( j >= (work_size) ) {
	    tlog_putlog ( 0, "efs_fixup buffer overflow converting !AZ!/",
		ident );
	    return ident;
	}
    }
    newspec[j] = '\0';
    /*
     * final fixup.
     */
    if ( newspec[last_ddelim] == '.' ) newspec[last_ddelim] = ']';
    else {
	for ( j = last_ddelim; j > 0; --j ) newspec[j] = newspec[j-1];
	newspec++;
    }
    if  ( http_log_level > 4 ) tlog_putlog ( 5, 
	"Converted '!AZ' to EFS '!AZ'!/", ident, newspec );
    return newspec;
}
#else
/*
 * Do no fixup.
 */
#define efs_fixup(ident,ctx,l) ident
#endif
/*************************************************************************/
/* Return values: 0 error, 1 - normal, 2 - normal dir.
 */
static int format_optional ( char *fname, dir_opt *opt, char info[64] )
{
#ifdef ODS5_FIXUP
    char nambuf[512];
#else
    char nambuf[64];
#endif
#ifdef VMS
    stat_t statblk;
#else
    struct stat statblk;
#endif
    int st_sts, k, used, size, cdate, mdate, uic;
    char number[20], *fspec;
    /*
     * Extract header information about file.
     */
    fspec = efs_fixup ( fname, nambuf, sizeof(nambuf) );
    LOCK_C_RTL
    st_sts = stat ( fspec, &statblk );
    UNLOCK_C_RTL

    if ( st_sts == 0 ) {
	uic = statblk.st_uid;
    } else {
	strcpy(info, "{stat error}" );
	return 0;
    }
    /*
     * If status OK, add optional data if any.
     */
    used = 0;
    if ( opt->showsize ) {
	/*
	 * Format size, either as full byte or kilobytes.
	 */
	size = statblk.st_size;
	info[used++] = ','; info[used++] = ' ';
	if ( opt->showbytes ) {
	    tu_strcpy ( &info[used], tu_strint(size,number) );
	    used += tu_strlen ( number );
	    tu_strcpy ( &info[used], " bytes" );
	    used += 6;
	} else {
	    tu_strcpy ( &info[used], tu_strint((size+999)/1000,number) );
	    used += tu_strlen ( number );
	    tu_strcpy ( &info[used], " Kb" );
	    used += 3;
	}
    }
    if ( opt->showdate ) {
	/*
	 * Fetch time, ctime() is non-reentrant, so get lock.
	 */
        cdate = statblk.st_ctime;
        mdate = statblk.st_mtime;
	info[used++] = ','; info[used++] = ' ';

	pthread_lock_global_np();
	tu_strncpy ( &info[used], ctime ( (time_t *) &mdate ), 63-used );
	pthread_unlock_global_np();

	/* Trim trailing '\n' added by ctime() */
	for ( ; info[used]; used++ ) if ( info[used] == '\n' ) {
	    info[used] = '\0';
	    break;
	}
    }

    if ( statblk.st_mode & S_IFDIR ) return 2; else return 1;
}

#ifdef VMS
/*************************************************************************/
/* VMS version, ODS-2 keeps directories sorted.
 */
static int list_files ( struct out_stream *out, void *dirf, dir_opt *opt,
	char dirbuf[4096], int bufsize, char *full_name, char *tail ) 
{
    int need_stat, i, j, k, length;
    char opt_info[64];
    /*
     * Make summary flag to indicate whether a stat() call is needed,
     * only incur the disk I/O if needed.
     */
    need_stat = opt->showsize || opt->showdate || opt->showowner || 
		opt->showgroup || opt->showprot;
    /*
     * We assume we are inside DL list, start new topic (files)
     */
    put_text ( out, "<DT>Files</DT>\r\n<DD><PRE>");
    /*
     * Scan directory.
     */
    while ( 0 < (length = tf_read(dirf, dirbuf, bufsize)) ) {
	/*
	 * tf_read returns multiple names in each buffer, process each.
	 */
	for ( i = j = 0; i < length; i++ ) if ( dirbuf[i] == '\0' ) {
	    void *fp;
	    /*
	     * dirbuf[j] is start of filename, skip immediately if
	     * hidden.
	     */
	    if ( (dirbuf[j] == '.') && !opt->showhidden ) {
		j = i + 1;
		continue;
	    }
	    /*
	     * Construct complete filename
	     */
	    opt_info[0] = '\0';
	    tu_strcpy ( tail, &dirbuf[j] );
	    if ( ((i-j) > 4) && (tu_strncmp(&tail[i-j-4],".DIR", 5) == 0) ) {
		/*
		 *  We are a directory, convert '.DIR' to '/' and make
		 * hypertext refernece.
		 */
		tail[i-j-4] = '\0';
	        put_text(out, "<A HREF=\"%s/\">%s/</A>", tail, tail );
	    } else {
	        /*
	         * Make Anchor with relative hypertext reference.
	         */
		int st_sts, size;
		unsigned int uic, cdate, mdate;

	        put_text(out, "<A HREF=\"%s\">%s</A>", tail, tail );
		/*
		 * Only get stat info if we have to.
		 */
		if ( need_stat ) {
		    format_optional ( full_name, opt, opt_info );
		}
	    }
 	    put_text(out,"%s\r\n", opt_info);
	    j = i+1;		/* Advance to next name in buffer */
	}
    }
    /* Close up HTML list structures */
    put_text(out, "</PRE></DD>\r\n" );
    return 1;
}
#else
/*************************************************************************/
static void qsort_names ( short *list, char *data, int size )
{
    char *lowest;
    int i,j, hbound, lbound, pivot, start, finish, *top, stack[40];
    short tmp;

    top = &stack[40];
    *--top = 0;		/* initial lbound */
    *--top = size-1;	/* initial hbound */
    while ( top < &stack[40] ) {
	finish = *top++; start = *top++;
	if ( (finish-start) > 3 ) {	
	    /* avoid n**2  on already sorted arrays */
	    tmp = list[(start+finish)/2];
	    list[(start+finish)/2] = list[start];
	    list[start] = tmp;
	}
	pivot = list[start];
	if ( finish > start + 1 ) {
	    /*
	     * More that 2 elements in partition, split into 2.
	     */
	    lbound = start; hbound = finish;
	    while ( lbound < hbound ) {
		if ( tu_strncmp(&data[pivot],&data[list[lbound+1]],256) > 0 ) {
		    lbound++;
		} else {
		    do {
			if ( tu_strncmp(&data[pivot],
				&data[list[hbound]],256) > 0 ) break;
			--hbound;
		    } while ( lbound < hbound );
		    if ( lbound >= hbound ) break;
		    tmp = list[hbound];   		/* swap */
		    list[hbound] = list[lbound+1];
		    hbound--;
		    list[lbound+1] = tmp;
		}
	    }
	    /* push larger partition onto stack first */
	    if ( lbound+lbound < (start+finish) ) {  /* high goes first */
		*--top = lbound+1;  *--top = finish;
		*--top = start;  *--top = lbound;
	    } else if ( lbound != finish ) {	/* low goes first */
		*--top = start;  *--top = lbound;
		*--top = lbound+1;  *--top = finish;
	    } else {
		/* Pivot is at max. */
		list[start] = list[finish];
		list[finish] = pivot;
		*--top = start;  *--top = finish-1;
	    }
	} else {
	    /* Only 2 elements in partition */
	    if ( tu_strncmp ( &data[pivot], &data[list[finish]],256 ) > 0 ) {
		list[start] = list[finish];
		list[finish] = pivot;
	    }
	}
    }
}
/*************************************************************************/
/* Unix version, sort entries.
 */
static int list_files ( struct out_stream *out, void *dirf, dir_opt *opt,
	char dirbuf[4096], int bufsize, char *full_name, char *tail ) 
{
    int status, start, i, j, k, length;
    char number[20], opt_info[64];
    union {
	short ptr[1000];	/* Offset to data */
	char data[20000];
    } dlist;
    /*
     * Scan up to 16K of data into buffer.
     */
    length = tf_read(dirf, dlist.data, sizeof(dlist.data)-sizeof(short)*2);
    if ( length <= 0 ) {
	put_text ( out, "Error reading directory\r\n");
	return 1;
    }
    /*
     * Use unused portion of dlist to hold offsets to the individual records.
     * ptr[k..j-1] has offsets.
     */
    k = j = (length+sizeof(short)-1)/sizeof(short);
    dlist.ptr[j] = 0;
    for ( start = i = 0; i < length; i++ ) if ( dlist.data[i] == '\0' ) {
	dlist.ptr[j] = start;
	/* Only include file in list if non-null and not hidden */
	if ( dlist.data[start] ) {
	    if ( (dlist.data[start] != '.') || opt->showhidden ) j++;
	}
	start = i + 1;
	if ( j >= 8192 ) break;
    }
    /*
     * sort the list.
     */
    qsort_names ( &dlist.ptr[k], dlist.data, j-k );
    /*
     * We assume we are inside DL list, start new topic (files)
     */
    put_text ( out, "<DT>Files (%s):</DT>\r\n<DD><PRE>", 
	tu_strint ( j-k, number) );
    /*
     * Scan directory.
     */
    for ( i = k; i < j; i++ ) {
	/*
	 * Contstruct complete filename and stat file.
	 */
	tu_strcpy ( tail, &dlist.data[dlist.ptr[i]]);
	status = format_optional (  full_name, opt, opt_info );
	if ( status == 2 ) {
	     /* directory */
	     int l;
	     l = tu_strlen ( tail );
#ifdef VMS
	     if ( l > 4 ) tail[l-4] = '\0';	/* trim .dir */
#endif
	     put_text(out, "<A HREF=\"%s/\">%s/</A>\r\n", tail, tail );
	} else {
	     put_text(out, "<A HREF=\"%s\">%s</A>%s\r\n", tail, tail, opt_info );
	}
    }
    /* Close up HTML list structures */
    put_text(out, "</PRE></DD>\r\n" );
    return 1;
}
#endif
