/*
 * This script manages a web-based respository for files.  The user
 * is presented with a page listing the files in the repository and
 * a form at the bottom for submitting new files.  
 *
 * The PATH_TRANSLATED holds the directory path of a directory that
 * contains the respository files as well as a special repository.index
 * file (the script will take no action on directories missing this file).
 *
 * The repository.index file consists of multiple records where each record
 * corresponds to 1 data file in the respository and is split into the
 * following tab-delimited fields:
 *    RF-name		Name of respository entry.  The name must begin
 *			with "RF" and be followed by 5 decimal digits.
 *    ftype		Filename extension.  The actual filename stored
 *			will be RF-name.ftype (e.g. RF00001.jpeg).  The
 *			period is implied.
 *    creator		Authenticated username of creator or "?" if unknown.
 *    description	Display name of repository file, defaults to
 *			original name of file.
 *
 * The last record is special and has the format "HInnnnn", where nnnnn is
 * a decimal number denoting the highest index used so far in an entry name.
 *    
 *
 * Author:	David Jones
 * Date:	30-MAR-2001
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unixio.h>
#include <ctype.h>
#include <stat.h>
#include <time.h>
#include <errno.h>
#include <descrip.h>			/* VMS string descriptors */
#include <lckdef.h>			/* VMS lock manager */
int SYS$ENQW(), SYS$DEQ();

#include "cgilib.h"
#include "cgilib_form.h"

union rf_index_data {			/* Repository defintion */
    struct {
        char *name;		/* File name, must start with RF */
        char *type;
	char *creator;
	char *description;
    } fld;
    char *elem[4];
};
struct rf_instance {
    struct rf_instance *next;
    union rf_index_data iline;		/* Index line */
    int stat_status;
    struct stat finfo;
    char storage[1];			/* variable-sized */
};
struct rf_index {
    int size;
    int high_id;
    int update;
    int dirty;				/* True if file contents changed */
    struct { int status, id; } lksb;	/* VMS lock status block */
    FILE *f;
    char *directory;
    char *file_spec;			/* Scratch region for file names */
    struct rf_instance *list;
    struct rf_instance *last;
};
#define CPF cgi_printf
/**************************************************************************/
/* Use VMS lock manager to get exclusive access to repository index file.
 * Only group global locks are used, therefore all processes must be
 * on same node and
 */
/**************************************************************************/
/* The load_index function reads the repository.index file and builds
 * a table of entries.  Directory argument must include final slash.
 */
int load_index ( const char *directory, int update, struct rf_index *ndx )
{
    char line[32768];
    struct rf_instance *last_ri;
    static char resource_name[32];
    static $DESCRIPTOR(res_name_dx, resource_name);
    int hash, i, x, status;
    /*
     * construct filename to open.
     */
    ndx->directory = malloc ( strlen(directory) + 1 );
    ndx->file_spec = malloc ( strlen(directory) + 128 );
    if ( !ndx->file_spec ) return 0;
    strcpy ( ndx->directory, directory );
    sprintf ( ndx->file_spec, "%s%s", directory, "repository.index" );
    ndx->update = update;
    ndx->dirty = 0;
    ndx->size = 0;
    ndx->high_id = -1;
    ndx->list = (struct rf_instance *) 0;
    ndx->last = (struct rf_instance *) 0;
    /*
     * Hash directory name (case blind) to produce lock name.  Name collisions
     * only affect performance.
     */
    for ( hash=i=0; directory[i]; i++ ) {
	x = (unsigned) (_toupper(directory[i]));
	hash = 0x03fffffff & (x*577 ^ hash*3);
    }
    sprintf ( resource_name, "WEB_REPOSITORY_%x", hash );
    res_name_dx.dsc$w_length = strlen ( resource_name );
    /*
     * Lock file and open.
     */
    status = SYS$ENQW ( 0, LCK$K_EXMODE, &ndx->lksb,  0, &res_name_dx,
	0, 0, 0, 0, 0, 0, 0 );
printf ( "Status of lock acquire (%s), %d, %d\n", resource_name,
	status, ndx->lksb.status );
    if ( (status&1) == 1 ) status = ndx->lksb.status;
    if ( (status&1) == 0 ) return status;

    ndx->f = fopen ( ndx->file_spec, update ? "r+" : "r" );
printf ( "load_index file %s open: %x\n", ndx->file_spec, ndx->f );
    if ( !ndx->f ) {
	SYS$DEQ ( ndx->lksb.id, 0, 0, 0 );
	return 0;
    }
    /*
     * Read each line.
     */
    last_ri = (struct rf_instance *) 0;
    while ( fgets ( line, sizeof(line)-1, ndx->f ) ) {
	int length, i, j;
	struct rf_instance *cur;
	char *str;
	/*
	 * Determine length of line and allocate an instance block.
	 */
	for ( length = 0; line[length] && (line[length] != '\n'); length++ );
	line[length] = '\0';
printf ( "raw iline read: '%s'\n", line );
	cur = malloc ( sizeof(struct rf_instance) + length );
	if ( !cur ) return 0;
	strcpy ( cur->storage, line );
	cur->next = (struct rf_instance *) 0;
	cur->iline.elem[0] = cur->storage;
	cur->iline.elem[1] = "";
	cur->iline.elem[2] = "";
	cur->iline.elem[3] = "";
	cur->stat_status = 0;			/* file status unknown */
	/*
	 * Break up line into tab-delimited strings.
	 */
	str = cur->storage;
	for ( i = j = 0; i < length; i++ ) if ( str[i] == '\t' ) {
	    j++;
	    str[i] = '\0';		/* terminate previous string */
	    cur->iline.elem[j] = &str[i+1];
	    if ( j >= 3 ) break;
	}
	/*
	 * Verify that name is valid form (RFnnnnn) and track highest
	 */
	j = 0;
	if ( ((str[0] == 'R') && (str[1] == 'F')) ||
	     ((str[0] == 'H') && (str[1] == 'I')) ) {
	    for ( i = 2; str[i]; i++ ) {
		if ( !isdigit(str[i]) ) { j = 0; break; };
		j = j * 10 + (str[i]-'0');
		if ( j > 99999 ) { j = 0; break; };
	    }
	    if ( str[0] == 'H' ) {
		/* force new high ID, then ignore the line. */
		if ( j > ndx->high_id ) ndx->high_id = j;
		j = 0;
		break;		/* read no more records */
	    }
	}
	    
	if ( j > 0 ) {
	    if ( j > ndx->high_id ) ndx->high_id = j;
	    /*
	     * Name valid, add to list.
	     */
	    if ( last_ri ) last_ri->next = cur;
	    else ndx->list = cur;
	    last_ri = cur;
	    ndx->size++;
	} else {
	    /* Ignore the line. */
	    free ( cur );
	}
	    
    }
    ndx->last = last_ri;
    /*
     * Release lock if only reading.
     */
    if ( !ndx->update ) {
	fclose ( ndx->f );
	ndx->f = (FILE *) 0;		/* indicates file closed */
	SYS$DEQ ( ndx->lksb.id, 0, 0, 0 );
    }

    return 1;
}
/*
 * Rundown loaded index.
 */
int unload_index ( struct rf_index *ndx ) 
{
    struct rf_instance *cur, *next;
    int status;
    /*
     * Re-write file if in update mode and list was changed.
     */
printf("unloading index in %s, update flag: %d ptr: %x %x\n", ndx->directory,
ndx->update, ndx->list, ndx->last );
    if ( ndx->update && ndx->f && ndx->dirty ) {
	/*
	 * Determine file organization of the repository index file, only
         * streamlf file work properly with overwrites
	 */
	struct stat sbuf;

	fstat ( fileno(ndx->f), &sbuf );
	if ( sbuf.st_fab_rfm != 5 ) {
	    /*  File wrong type, recreate it. */
	    char obsname[540], *semi;
	    fgetname ( ndx->f, ndx->file_spec );
	    semi = strchr ( ndx->file_spec, ';' );
	    if ( semi ) *semi = '\0';
	    sprintf ( obsname, "%s-obsolete", ndx->file_spec );
printf  ( "File format wrong, renameing to '%s'\n", obsname );
	    fclose ( ndx->f );
	    rename ( ndx->file_spec, obsname );
	    ndx->f = fopen ( ndx->file_spec, "w" );
	}	
	fseek ( ndx->f, 0, SEEK_SET );		/* beginning of file */
	for ( cur = ndx->list; cur; cur = cur->next ) {
printf("Current instance: %x, file: %s\n", cur, cur->iline.elem[0] );
	    status = fprintf ( ndx->f, "%s\t%s\t%s\t%s\n",
		cur->iline.elem[0], cur->iline.elem[1], cur->iline.elem[2],
		cur->iline.elem[3] );
if ( status < 0 ) { int i;
   printf ( "File write error: %s\n", strerror (errno) );
   for(i=0;i<4;i++) printf ( " elem[%d] = '%s'\n", i, cur->iline.elem[i] );
}
	}
	/*
	 * Add final line to track high ID.
	 */
printf("adding final line, high: %d\n", ndx->high_id );
	if ( ndx->high_id > 0 ) fprintf ( ndx->f, "HI%05d\t\t\t\n",
		ndx->high_id );
    }
    /*
     * Close and unlock file, release storage used for filenames.
     */
    if ( ndx->f ) {
	fclose ( ndx->f );
        SYS$DEQ ( ndx->lksb.id, 0, 0, 0 );
    }
    if ( ndx->file_spec ) free ( ndx->file_spec );
    if ( ndx->directory ) free ( ndx->directory );
    /*
     * Deallocate instance list.
     */
    for ( cur = ndx->list; cur; cur = next ) {
	next = cur->next;
	free ( cur );
    }
    return 1;
}
/**************************************************************************/
/* Routines to construct HTML tags.
 */
static int tag_work_len = 0;
static char tag_work[8192];

static int tag_open ( const char *intro, const char *tag )
{
    if ( intro ) {
	tag_work_len = strlen ( intro );
	if ( tag_work_len > 0 ) strcpy ( tag_work, intro );
    } else {
	tag_work_len = 0;
    }
    tag_work[tag_work_len++] = '<';
    strcpy ( &tag_work[tag_work_len], tag );
    tag_work_len += strlen ( tag );
    return 1;
}

static int tag_close ( )
{
    if ( tag_work_len <= 0 ) return 0;
    tag_work[tag_work_len++] = '>';
    tag_work[tag_work_len] = '\0';
    tag_work_len = 0;
    return cgi_printf ( "%s", tag_work );
}
static int tag_attr (const char *attr_name, const char *fmt, ... )
{
    va_list arg_list;
    int status, i, j;
    char c, attr_work[8192];
    static struct {
	const char c, *ent;
    } ent_table[] = { { '&', "&amp;"}, { '>', "&gt;" },
	{ '<', "&lt;" }, { '"', "&quot;" }, { '\0', "" }
    };
    /*
     * Start the attribute and lead with opening quote.
     */
    tag_work[tag_work_len++] = ' ';
    strcpy ( &tag_work[tag_work_len], attr_name );
    tag_work_len += strlen ( attr_name );
    tag_work[tag_work_len++] = '=';
    tag_work[tag_work_len++] = '"';
    /*
     * Format string,
     */
    if ( strchr ( fmt, '%' ) ) {
	va_start ( arg_list,fmt );
        status = vsprintf ( &tag_work[tag_work_len], fmt, arg_list );
    } else {
	/*
	 * No formatting escapes, copy direct.
	 */
	strcpy ( &tag_work[tag_work_len], fmt );
    }
    /*
     * Convert special characters, get string length as side effect.
     */
    for ( i = tag_work_len; tag_work[i]; i++ ) {
	c = tag_work[i];
	for ( j = 0; ent_table[j].c; j++ ) if ( c == ent_table[j].c ) {
	    strcpy ( attr_work, &tag_work[i+1] );
	    strcpy ( &tag_work[i], ent_table[j].ent );
	    strcat ( tag_work, attr_work );
	    i += (strlen ( ent_table[j].ent ) - 1);
	    break;
	}
    }
    tag_work_len = i;
    /*
     * Close the attribute with final quote
     */
    tag_work[tag_work_len++] = '"';
    return 1;
}
/**************************************************************************/
/* Format file index entries as an HTML table.  Table with have 5 fields.
 */			
int index_to_html ( const char *script_name, const char *virt_dir, 
	struct rf_index *rep, int sort_index )
{
    int status;
    struct rf_instance *cur;
    char *cur_user;
    FILE *desc_file;

    /*
     * Check for existence of respository.description file and include
     * if found.
     */
    sprintf ( rep->file_spec, "%srepository.description", rep->directory );
    desc_file = fopen ( rep->file_spec, "r" );
    if ( desc_file ) {
	char desc_rec[4080];
	while ( fgets  ( desc_rec, sizeof(desc_rec), desc_file ) ) {
	    if ( (CPF ( "%s", desc_rec )&1) == 0 ) break;
	}
	fclose ( desc_file );
    }
    cur_user = cgi_info ( "REMOTE_USER" );
    if ( !cur_user ) cur_user = "????";
    if ( !rep->list ) {
	return 1;
	CPF ( "The repository currently has no files<BR>\n" );
    }
    /*
     * Sort entries on specified index number.
     */
    /*
     * Start table.
     */
    tag_open ( "", "FORM" ); 
	tag_attr("action", "%s%s", script_name, virt_dir );
	tag_attr("method","post"); tag_close();
    tag_open ( "\n", "TABLE" );
	tag_attr("COL","5"), tag_attr("BORDER","1"); tag_close();
    CPF ( "<TR>%s%s%s%s</TR>\n",
		"<TH>Identifer</TH>", "<TH>type</TH>", "<TH>creator</TH>",
		"<TH>Description</TH><TH>Date</TH>" );
    /*
     * process each table entry.
     */
    for ( cur = rep->list; cur; cur = cur->next ) {
	/*
	 * Lookup data on each entry.
	 */
	if ( cur->stat_status == 0 ) {
	    sprintf ( rep->file_spec, "%s%s.%s", rep->directory,
		cur->iline.fld.name, cur->iline.fld.type );
	    status = stat ( rep->file_spec, &cur->finfo );
	    if ( status < 0 ) {
		cur->stat_status = -1;
		strcpy ( rep->file_spec, strerror ( errno ) );
	    }
	    else cur->stat_status = 1;
	}
	/*
	 * Format data into HTML.  First table column is option list.
	 */
	tag_open("", "TR"); tag_attr("ALIGN","LEFT"); tag_close();
	if ( !cur->iline.fld.creator || !cur->iline.fld.creator[0] ||
		(strcmp(cur->iline.fld.creator, cur_user) == 0) ) {
	    /*
	     * Give owner option to update field, unowned fields updatable
	     * by anyone.
	     */
	    tag_open("<TD>", "select" );
	        tag_attr("name","%s", cur->iline.fld.name ); tag_close();
	    CPF ( "<option selected>%s<option>delete</select></TD>\n",
		cur->iline.fld.name);
	} else {
	    CPF ( "<TD>%s</TD>", cur->iline.fld.name );
	}
	CPF ( 
	    "<TD>%s</TD><TD>%s</TD><TD><A HREF=\"%s%s.%s\">%s</A></TD>",
	    cur->iline.fld.type, 
	    cur->iline.fld.creator, virt_dir,
	    cur->iline.fld.name, cur->iline.fld.type, 
	    cur->iline.fld.description );
	if ( (CPF ( "<TD>%s</TD></TR>\n", cur->stat_status > 0 ?
		ctime ( &cur->finfo.st_ctime ) : rep->file_spec )&1) == 0 )
	    return 0;;
    }
    CPF ( "</TABLE><BR>\n" );
    tag_open("</TABLE><BR>\n", "input");
	tag_attr("type","SUBMIT"); tag_attr("value"," Update "); tag_close();
    CPF ( "</FORM>\n" );
    return 1;
}
/**************************************************************************/
/* Convert various newline formats for stream text files to cannonical form.
 * For crlf lines, file data will be shortened.  Return value is record count.
 *  CRLF --> LF
 *  CR --> LF
 *  LF --> LF
 */
static int cannonicalize_newlines ( char *content, int *content_length )
{
    int length, i, j, state, count;
    char c;

    length = *content_length;
    state = 0;
    count = 0;
    for ( j = i = 0; i < length; i++ ) {
	c = content[i];
	switch ( state ) {
	    case 0:
		if ( c == '\r' ) {
		    c = '\n';
		    state = 1;
		}
		if ( i != j ) content[j] = c;
		if ( c == '\n' ) count++;
		j++;
		break;

	    case 1:		/* CR seen ignore following LF */
		if ( c == '\n' ) {
		    state = 0;
		} else {
		    if ( c == '\r' ) c = '\n';
		    else state = 0;

		    if ( i != j ) content[j] = c;
		    if ( c == '\n' ) count++;
		    j++;
		}
		break;

	    default:
		break;
	}
    }
    *content_length = j;
    return count;
}
/**************************************************************************/
/* Add new file to repository, output HTML acknowlegement.
 */
int upload_file ( const char *path, const char *vpath, struct rf_index *rep )
{
    char *fdata, *fname, *fctype;
    int fd_len, fn_len, ct_len, desc_len, status;
    struct rf_instance *cur;
    FILE *df;

    status = CPF ( 
	"<HEAD><TITLE>Repository upload result</TITLE></HEAD><BODY>\n" );
    
    status = cgi_form_field_lookup ( "FDATA", &fdata, &fd_len );
    if ( (status&1) == 0 ) {
	/*
	 * No supplied field data.
	 */
	CPF ( "Upload filed, error in field lookup: %d\n", status );
	return status;

    }
    /*
     * Get attributes included in MIME-header.
     */
    status = cgi_form_field_lookup ( "FDATA.FILENAME", &fname, &fn_len );
    if ( (status&1) == 0 ) { fname = ""; fn_len = 0; }
    else fname[fn_len] = '\0';

    status = cgi_form_field_lookup ( "FDATA.CONTENT-TYPE", &fctype,
  	&ct_len );
    if ( (status&1) == 0 ) { fctype = ""; ct_len = 0; }
    else fctype[ct_len] = '\0';

    CPF ( "Content-type supplied by browser: '%s' (exists=%d)<BR>\n",
	fctype, status );
    if ( strncmp ( fctype, "TEXT/", 5 ) == 0 ) {
	int count;
	count = cannonicalize_newlines ( fdata, &fd_len );
	printf ( "caonnicalize result: %d, new fd_len: %d\n", count,fd_len);
	CPF ( "Text file has %d record%s<BR>\n", count, count==1 ? "" : "s" );
    }
    /*
     * Read the respository and prepare for update.
     */
    status = load_index ( path, 1, rep );
    if ( (status&1) == 0 ) {
	CPF ( "Error reading %srepository.index\n", path );
	return status;
    }
    if ( rep->high_id < 0 ) rep->high_id = 0;
    /*
     * Allocate message ID and generate filename, append new instance to
     * tail of list.
     */
    rep->high_id++;
    cur = malloc ( sizeof(struct rf_instance) + 256 );
    cur->next = (struct rf_instance *) 0;
    cur->iline.fld.name = cur->storage;
    sprintf ( cur->storage, "RF%05d", rep->high_id );
    cur->iline.fld.type = "DAT";
    if ( fn_len > 0 ) {
	int i;
	for ( i = (fn_len-1); i >= 0; --i ) {
	    if ( fname[i] == '/' ) break;
	    if ( fname[i] == '.' ) {
		cur->iline.fld.type = &fname[i+1];
		break;
	    }
	}
    }
    /*
     * Determine creator of file and description.  Fall back to original
     * filename
     */
    cur->iline.fld.creator = cgi_info ( "REMOTE_USER" );
    if ( !cur->iline.fld.creator ) cur->iline.fld.creator = "????";
    cur->iline.fld.description = "";
    status = cgi_form_field_lookup ( "DESCRIPTION", 
	&cur->iline.fld.description, &desc_len );
    if ( (status&1) == 0 ) cur->iline.fld.description = "New file";
    else if ( desc_len == 0 ) status = 0;

    if ( ((status&1) == 0) && (fn_len > 0) ) {
	int i;
	cur->iline.fld.description = fname;
	for ( i = (fn_len-1); i >= 0; --i ) {
	    if ( fname[i] == '/' ) {
		cur->iline.fld.description = &fname[i+1];
	    }
	}
    }
    cur->stat_status = 0;
    if ( rep->list ) rep->last->next = cur; else rep->list = cur;
    rep->last = cur;
    rep->dirty = 1;
    /*
     * Create file.
     */
    sprintf ( rep->file_spec, "%s%s.%s", rep->directory,
	cur->iline.fld.name, cur->iline.fld.type );
    df = fopen ( rep->file_spec, "w" );
    if ( df ) {
	int seg, i, count;
	status = 1;
	for ( i = 0; i < fd_len; i += count ) {
	    seg = 32000;
	    if ( seg > (fd_len-i) ) seg = fd_len - i;
	    count = fwrite ( &fdata[i], 1, seg, df );
	    if ( count < 0 ) { status = 0; break; }
	}
	fclose ( df );
    } else status = 0;

    if ( (status&1) == 0 ) {
	CPF ( "Error writing file\n" );
    } else {
	CPF ( "Submitted file has been stored in repository (%s)\n",
		rep->file_spec );
	/* index_to_html ( cgi_info("SCRIPT_NAME"), vpath, rep, 0 ); */
    }
    status = unload_index ( rep );
    tag_open ( "<HR>", "A" );
    tag_attr ( "HREF", "%s%s", cgi_info("SCRIPT_NAME"), vpath );
    tag_close();
    CPF ( "Go Back</A><BR>", "/A" );
    return status;
}
/**************************************************************************/
/* Update repository entries based on form contents.
 */
int update_repository ( const char *path, char *cur_user,
	const char *vpath, struct rf_index *rep )
{
    char *fname, *fctype;
    int vlen, modified, fd_len, fn_len, ct_len, desc_len, status;
    struct rf_instance *cur, *prev, *next;
    char *value;
    FILE *df;
    /*
     * Read the respository and prepare for update.
     */
    status = CPF ( 
	"<HEAD><TITLE>Repository update result</TITLE></HEAD><BODY>\n" );
    status = load_index ( path, 1, rep );
    if ( (status&1) == 0 ) {
	CPF ( "Error reading %srepository.index\n", path );
	return status;
    }
    if ( rep->high_id < 0 ) rep->high_id = 0;
    /*
     * Scan list and check for deletions.
     */
    prev = (struct rf_instance *) 0;
    modified = 0;
    for ( cur = rep->list; cur; cur = next ) {
	/*
	 * Every entry has a option in the form by the same name.
	 */
	next = cur->next;
	if ( !cur->iline.fld.creator || 
		(strcmp(cur_user,cur->iline.fld.creator)==0) ) {
	    status = cgi_form_field_lookup ( cur->iline.fld.name, 
		&value, &vlen );
	    if ( (status&1) == 1 ) {
		if ( strncmp(value,"delete",7) == 0 ) {
		    /*
		     * User wants this entry deleted.
		     */
		    modified = 1;	/* flag that update happended */
		    if ( prev ) prev->next = next;
		    else rep->list = next;
		    rep->dirty = 1;
		    /*
		     * Actually delete the file, note that load function
		     * garanteed file name begins with RF.
		     */
		    sprintf ( rep->file_spec, "%s%s.%s", rep->directory,
			cur->iline.fld.name, cur->iline.fld.type );
		    if ( delete ( rep->file_spec ) < 0 ) {
			CPF ( "Error deleting entry %s: %s<BR>\n",
				cur->iline.fld.name, strerror(errno) );
		    } else {
		       CPF ( "Deleted entry %s!<BR>\n", cur->iline.fld.name );
		    }

		} else if ( strncmp ( value, "edit", 5) == 0 ) {
		}
	    }
	}
	prev = cur;
    }
    if ( (status&1) == 0 ) {
	CPF ( "Error updating repository\n" );
    } else if ( modified ) {
	CPF ( "Edits to repository have been submitted\n");
	/* index_to_html ( cgi_info("SCRIPT_NAME"), vpath, rep, 0 ); */
    } else {
	CPF ( "No changes made to repository<BR>\n");
    }
    if ( modified ) {
	/*
	 * Re-write the updated file.
	 */
	status = unload_index ( rep );
	if ( (status&1) == 0 ) {
	    CPF ( "Error re-writing index (%d)<BR>\n", status );
	}
    }
    tag_open ( "<HR>", "A" );
    tag_attr ( "HREF", "%s%s", cgi_info("SCRIPT_NAME"), vpath );
    tag_close();
    CPF ( "Go Back</A><BR>", "/A" );
    
    return status;
}
/**************************************************************************/
int main ( int argc, char **argv )
{
    int status;
    char *request_method, *path, *apath, *script_name;
    struct rf_index rep;
    /*
     * Initialize CGIlib and get request method to tell use how to behave.
     */
    status = cgi_init ( argc, argv );
    if ( (status&1) == 0 ) return status;
    request_method = cgi_info ( "REQUEST_METHOD" );
    if ( !request_method ) {
	CPF ( "%s\n%s\n\n%s\n", "Content-type: text/plain",
	    "Status: 500 internal error", "cgi_info call failure" );
	return 44;
    }
    /*
     * Get path-translated and construct directory name.
     */
    apath = cgi_info ( "PATH_INFO" );
    path = cgi_info ( "PATH_TRANSLATED" );
    script_name = cgi_info ( "SCRIPT_NAME" );
    if ( !script_name || !path || !apath ) {
	CPF ( "%s\n%s\n\n%s\n", "Content-type: text/plain",
	    "Status: 500 internal error", "cgi_info call failure" );
	return 44;
    }
printf ( "Request method: %s\n", request_method );
    /*
     * Take action based upon method.
     */
    if ( strcmp ( request_method, "GET" ) == 0 ) {
	/*
	 * Redirect to directory if last character not a slash.
	 */
	int last_off;
	last_off = strlen ( apath ) - 1;
	if ( last_off >= 0 ) if ( apath[last_off] != '/' ) {
	    CPF ( "Location: *://*:*%s%s/\n\n\n", script_name, apath );
	    return 1;
	}
	CPF ( "Content-type: text/html\n\n%s\n", "<HTML>" );
	/*
	 * Read the respository.
	 */
	status = load_index ( path, 0, &rep );
	if ( (status&1) == 1 ) {
	    /*
	     * Load repository index and return directory of contents.
	     */
	    CPF ( 
		"<HEAD><TITLE>Repository listing</TITLE></HEAD><BODY>\n" );
	    CPF ( "<H2>File repository, directory %s</H2><P>\n",
		apath );

	    status = index_to_html ( script_name, apath, &rep, 0 );

	    unload_index ( &rep );
	    /*
	     * Append form for upload of additional files.
	     */
	    tag_open( "<HR><H2>File upload</H2>\n", "FORM");
	        tag_attr("action", "%s%s", script_name, apath );
	        tag_attr("method", "post"); 
	        tag_attr("enctype", "multipart/form-data");
	        if ( tag_close ( ) < 0 ) return 0;
	    tag_open( "\nFile to upload: ", "input");
	        tag_attr("type","file"); tag_attr("size","40");
	        tag_attr("name","fdata");
	        if ( tag_close ( ) < 0 ) return 0;
	    tag_open ( "<BR>\nDescription: ", "input" );
		tag_attr("type","text"); tag_attr("size","40");
		tag_attr("name", "DESCRIPTION");
	        if ( tag_close ( ) < 0 ) return 0;
	    tag_open ( "<BR>\n", "input" );
		tag_attr("type","submit"); tag_attr("value", " Upload ");
		if ( tag_close ( ) < 0 ) return 0;
	    CPF ( "</FORM>\n" );
	} else {
	    /*
	     * Load error on repository file.
	     */
	    CPF ( "<HEAD><TITLE>load error</TITLE><BODY>\n" );
	    CPF ( "Error loading repository.index in %s\n",
		apath );
	}

    } else if ( strcmp ( request_method, "POST" ) == 0 ) {
	/*
	 * Parse the form data and get the file
	 */
	char errmsg[256];
	CPF ( "Content-type: text/html\n\n%s\n", "<HTML>" );

	status = cgi_parse_form_data ( errmsg );
	if ( (status&1) == 0 ) {
	    CPF ( "<BODY>Error parsing posted data: '%s' (%d)\n", 
		errmsg, status );
	} else {
	    /*
	     * See file data supplied.
	     */
	    char *fdata, *cur_user;
	    int fd_len;

	    if ( 1&cgi_form_field_lookup ( "FDATA", &fdata, &fd_len ) ) {
		/*
		 * Add a new file.
		 */
printf ("Calling upload sub-function\n");
		status = upload_file ( path, apath, &rep );
	    } else {
		/*
		 * Edit existing entries.
		 */
		cur_user = cgi_info ( "REMOTE_USER" );
		if ( !cur_user ) cur_user = "????";
printf ("Calling update sub-function\n");
		status = update_repository ( path, cur_user, apath, &rep );
	    }
	}

    } else if ( strcmp ( request_method, "HEAD" ) == 0 ) {
	/*
	 * Head request sends null response.
	 */
	CPF ( "Content-type: text/html\n\n%s\n", "<HTML>" );
    } else {
	/*
	 * Unknown method.
	 */
	CPF ( "%s\n%s\n\n%s\n", "Status: 501 Unknown method",
		"Content-type: text/plain", "Unknown method in request");
    }
    CPF ( "</BODY></HTML>\n" );
    return 1;
}
