/*         		    					     HTServer.c
**	MINI SERVER
**
**	(c) COPRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**
**  Authors:
**	HFN		Henrik Frystyk Nielsen (frystyk@w3.org)
**
**  History:
**	Nov 24 95	First version
*/

#include "WWWLib.h"			      /* Global Library Include file */
#include "WWWApp.h"
#include "WWWMIME.h"
#include "WWWHTTP.h"
#include "WWWFile.h"
#include "WWWTrans.h"

#include "HTBInit.h"
#include "HTInit.h"

#include "HTServer.h"					 /* Implemented here */

#ifndef W3C_VERSION
#define W3C_VERSION 		"unspecified"
#endif

#define APP_NAME		"W3CMiniServer"
#define APP_VERSION		W3C_VERSION

/* Default page for "-help" command line option */
#define HELP	"http://www.w3.org/pub/WWW/MiniServ/User/CommandLine.html"

#define DEFAULT_OUTPUT_FILE	"web.out"
#define DEFAULT_RULE_FILE	"web.conf"
#define DEFAULT_LOG_FILE       	"web.log"

#define DEFAULT_PORT		80

#define DEFAULT_TIMEOUT		120		       /* timeout in seconds */

#define DEFAULT_FORMAT		WWW_SOURCE

#define DEFAULT_BACKLOG		HT_BACKLOG

#define DEFAULT_ACCEPT		-1

#if defined(__svr4__)
#define CATCH_SIG
#endif

typedef enum _MSFlags {
    MS_PREEMPTIVE=0x1
} MSFlags;

typedef struct _MiniServ {
    HTRequest *		request;
    HTParentAnchor *	anchor;
    HTList *		active;			  /* List of active requests */
    SOCKET		port;
    struct timeval *	tv;				/* Timeout on socket */
    HTList *		converters;		     /* Content type streams */
    HTList *		encoders;		  /* Transfer coding streams */
    int			backlog;		       /* Backlog for listen */
    int			accept;		/* Total accepted connections served */
    char *		cwd;				  /* Current dir URL */
    char *		rules;
    char *		logfile;
    MSFlags		flags;
} MiniServ;


/* ------------------------------------------------------------------------- */

/*	Standard (non-error) Output
**	---------------------------
*/
PUBLIC int OutputData(const char  * fmt, ...)
{
    int ret;
    va_list pArgs;
    va_start(pArgs, fmt);
    ret = vfprintf(stdout, fmt, pArgs);
    va_end(pArgs);
    return ret;
}

/* ------------------------------------------------------------------------- */

/*	Create a Mini Server Object
**	---------------------------
*/
PRIVATE MiniServ * MiniServ_new (void)
{
    MiniServ * me;
    if ((me = (MiniServ *) HT_CALLOC(1, sizeof(MiniServ))) == NULL ||
	(me->tv = (struct timeval*) HT_CALLOC(1, sizeof(struct timeval))) == NULL)
	HT_OUTOFMEM("MiniServ_new");
    me->tv->tv_sec = DEFAULT_TIMEOUT;
    me->cwd = HTGetCurrentDirectoryURL();

    /* Bind the MiniServ object together with the Request Object */
    me->request = HTRequest_new();
    me->active = HTList_new();
    HTList_addObject(me->active, me->request);
    me->port = DEFAULT_PORT;
    me->backlog = DEFAULT_BACKLOG;
    me->accept = DEFAULT_ACCEPT;

    /* Set default values in request object */
    HTRequest_setGnHd(me->request, HT_G_DATE);
    HTRequest_setOutputFormat(me->request, DEFAULT_FORMAT);
    HTRequest_setContext(me->request, me);
    return me;
}

/*	Delete a Mini Server Object
**	---------------------------
*/
PRIVATE BOOL MiniServ_delete (MiniServ * me)
{
    if (me) {
	HTRequest_delete(me->request);
	if (me->logfile) HTLog_close();
	HTList_delete(me->active);
	HTList_delete(me->converters);
	HTList_delete(me->encoders);
	HT_FREE(me->cwd);
	HT_FREE(me->tv);
	HT_FREE(me);
	return YES;
    }
    return NO;
}

PRIVATE void Cleanup (MiniServ * me, int status)
{
    MiniServ_delete(me);
    HTEventTerminate();
    HTLibTerminate();
#ifdef VMS
    exit(status ? status : 1);
#else
    exit(status ? status : 0);
#endif
}


#ifdef CATCH_SIG
#include <signal.h>
/*								    SetSignal
**  This function sets up signal handlers. This might not be necessary to
**  call if the application has its own handlers.
*/
PRIVATE void SetSignal (void)
{
    /* On some systems (SYSV) it is necessary to catch the SIGPIPE signal
    ** when attemting to connect to a remote host where you normally should
    ** get `connection refused' back
    */
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
	if (WWWTRACE) HTTrace("HTSignal.... Can't catch SIGPIPE\n");
    } else {
	if (WWWTRACE) HTTrace("HTSignal.... Ignoring SIGPIPE\n");
    }
}
#endif /* CATCH_SIG */

PRIVATE void VersionInfo (void)
{
    OutputData("\n\nW3C Reference Software\n\n");
    OutputData("\tW3C W3C Mini Server (%s) version %s.\n",
	     APP_NAME, APP_VERSION);
    OutputData("\tW3C Reference Library version %s.\n\n",HTLib_version());
    OutputData("Please send feedback to <libwww@w3.org>\n");
}

/*	timeout_handler
**	---------------
**	This function is registered to handle timeout in select eventloop
*/
PRIVATE int timeout_handler (HTRequest * request)
{
    HTTrace("Request timeout... We don't know how to handle this yet :-(\n");
    return HT_OK;
}

/*	server_handler
**	---------------
**	Is called if we get a read notification on server port
*/
PRIVATE int server_handler (SOCKET sockfd, HTRequest * request, SockOps ops)
{
    MiniServ * ms = (MiniServ *) HTRequest_context(request);
    HTNet * net = HTRequest_net(request);
    HTNet * accepted = NULL;

    if (WWWTRACE) HTTrace("Accepting... socket %d\n", sockfd);
    if (HTDoAccept(net, &accepted) == HT_OK) {
	
	/* Count down the number of accepts we have left */
	if (ms->accept > 0) ms->accept--;

	/* Create a new request object and start the server */
	if (ops == FD_READ) {
	    HTRequest * server = HTRequest_dup(request);
	    HTList_addObject(ms->active, server);
	    HTNet_newServer(server, accepted, "http");
	    return HT_OK;
	}
    }
    return HT_ERROR;
}

/*	terminate_handler
**	-----------------
**	Is called whenever a request has terminated
*/
PRIVATE int terminate_handler (HTRequest * request, void * param, int status)
{
    MiniServ * ms = (MiniServ *) HTRequest_context(request);
    if (WWWTRACE) HTTrace("SERVER...... %p ended with status %d\n",
			   request, status);
    HTRequest_delete(request);
    if (!ms->accept) {
	HTTrace("SERVER...... Done - stopping\n");
	Cleanup(ms, 0);
    }
    return HT_OK;
}

/* ------------------------------------------------------------------------- */
/*				  MAIN PROGRAM				     */
/* ------------------------------------------------------------------------- */

int main (int argc, char ** argv)
{    
    int		arg;
    MiniServ *	ms = MiniServ_new();

    /* Starts Mac GUSI socket library */
#ifdef GUSI				   /* Starts Mac GUSI socket library */
    GUSISetup(GUSIwithSIOUXSockets);
    GUSISetup(GUSIwithInternetSockets);
#endif

    /* Set up the application's event loop. We use the 
       example event loop that comes with the Library.  */
    HTEventInit();

    /* Initiate W3C Reference Library */
    HTLibInit(APP_NAME, APP_VERSION);

    /* Initialize the standard set of MIME header parsers */
    HTMIMEInit();

    /* Register the transport protocol for our listen tool */
    HTTransport_add("buffered_tcp", HT_TP_SINGLE, HTReader_new, HTBufferWriter_new);
    HTTransport_add("local", HT_TP_SINGLE, HTReader_new, HTWriter_new);

    /* Initialize the protocol modules */
    HTProtocol_add("http", "buffered_tcp", NO, HTLoadHTTP, HTServHTTP);
    HTProtocol_add("file", "local", NO, HTLoadFile, NULL);

    /* Initialize set of Content Type Converters */
    ms->converters = HTList_new();
    HTConversion_add(ms->converters, "message/x-rfc822-head",
		     "*/*", HTMIMEHeader, 1.0, 0.0, 0.0);
    HTConversion_add(ms->converters, "message/rfc822",
		     "*/*", HTMIMEConvert, 1.0, 0.0, 0.0);
    HTConversion_add(ms->converters, "multipart/*",
		     "*/*", HTBoundary,  1.0, 0.0, 0.0);
    HTFormat_setConversion(ms->converters);

    /* Set up encoders and decoders */
    ms->encoders = HTList_new();
    HTEncoderInit(ms->encoders);
    HTFormat_setTransferCoding(ms->encoders); 

    /* Initialize bindings between file suffixes and media types */
    HTFileInit();

    /* Get any proxy or gateway environment variables */
    HTProxy_getEnvVar();

    /* Scan command Line for parameters */
    for (arg=1; arg<argc ; arg++) {
	if (*argv[arg] == '-') {

	    /* -? or -help: show the command line help page */
	    if (!strcmp(argv[arg],"-?") || !strcmp(argv[arg],"-help")) {
		ms->anchor = (HTParentAnchor *) HTAnchor_findAddress(HELP);

	    /* log file */
	    } else if (!strcmp(argv[arg], "-l")) {
		ms->logfile = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : DEFAULT_LOG_FILE;

	    /* rule file */
	    } else if (!strcmp(argv[arg], "-r")) {
		ms->rules = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : DEFAULT_RULE_FILE;

	    /* preemptive or non-preemptive access */
	    } else if (!strcmp(argv[arg], "-single")) {
		HTRequest_setPreemptive(ms->request, YES);
		ms->flags |= MS_PREEMPTIVE;

	    /* timeout -- Change the default request timeout */
	    } else if (!strcmp(argv[arg], "-timeout")) {
		int timeout = (arg+1 < argc && *argv[arg+1] != '-') ?
		    atoi(argv[++arg]) : DEFAULT_TIMEOUT;
		if (timeout > 0) ms->tv->tv_sec = timeout;

	    /* server port */
	    } else if (!strncmp(argv[arg], "-p", 2)) { 
		ms->port = (arg+1 < argc && *argv[arg+1] != '-') ?
		    atoi(argv[++arg]) : DEFAULT_PORT;

	    /* backlog for listen() */
	    } else if (!strcmp(argv[arg], "-backlog")) { 
		ms->backlog = (arg+1 < argc && *argv[arg+1] != '-') ?
		    atoi(argv[++arg]) : DEFAULT_BACKLOG;

	    /* Total number of accepted connections to be served */
	    } else if (!strcmp(argv[arg], "-accept")) { 
		ms->accept = (arg+1 < argc && *argv[arg+1] != '-') ?
		    atoi(argv[++arg]) : DEFAULT_ACCEPT;

	    /* directory listings */
	    } else if (!strncmp(argv[arg], "-d", 2)) {
		char *p = argv[arg]+2;
		for(;*p;p++) {
		    switch (argv[arg][2]) {
		      case 'r':HTFile_setDirReadme(HT_DIR_README_NONE); break;
		      case 't':HTFile_setDirReadme(HT_DIR_README_TOP); break;
		      case 'b':HTFile_setDirReadme(HT_DIR_README_BOTTOM);break;
		      case 'n':HTFile_setDirAccess(HT_DIR_FORBID); break;
		      case 's':HTFile_setDirAccess(HT_DIR_SELECTIVE); break;
		      case 'y':HTFile_setDirAccess(HT_DIR_OK); break;
		      default:
			if (WWWTRACE)
			    HTTrace("Bad parameter (%s) in -d option\n",
				     argv[arg]);
		    }
		}

	    /* Print version and exit */
	    } else if (!strcmp(argv[arg], "-version")) { 
		VersionInfo();
		Cleanup(ms, 0);
		
#ifdef WWWTRACE
	    /* trace flags */
	    } else if (!strncmp(argv[arg], "-v", 2)) {
		HTSetTraceMessageMask(argv[arg]+2);
#endif
	    } else {
		if (WWWTRACE) HTTrace("Bad Argument (%s)\n",argv[arg]);
	    }
        } else {
	    if (WWWTRACE) HTTrace("Bad Argument (%s)\n",argv[arg]);
	}
    }
	
#ifdef CATCH_SIG
    SetSignal();
#endif

    /* Register our User Prompts etc in the Alert Manager */
    HTAlert_add(HTError_response, HT_A_MESSAGE);
    HTError_setShow(HT_ERR_SHOW_INFO+HT_ERR_SHOW_FIRST);

    /* Rule file specified? */
    if (ms->rules) {
	HTList * list = HTList_new();
	HTRequest * rr = HTRequest_new();
	char * rules = HTParse(ms->rules, ms->cwd, PARSE_ALL);
	HTParentAnchor * ra = (HTParentAnchor *) HTAnchor_findAddress(rules);
	HTRequest_setPreemptive(rr, YES);
	HTConversion_add(list, "application/x-www-rules", "*/*", HTRules,
			 1.0, 0.0, 0.0);
	HTRequest_setConversion(rr, list, YES);
	if (HTLoadAnchor((HTAnchor *) ra, rr) != YES)
	    if (WWWTRACE) HTTrace("Can't access rules\n");
	HTConversion_deleteAll(list);
	HTRequest_delete(rr);
	HT_FREE(rules);
    }

    /* Register our filters for the Net manager */
    HTBeforeInit();
    HTNetCall_addAfter(HTLogFilter, NULL, HT_ALL);
    HTNetCall_addAfter(terminate_handler, NULL, HT_ALL);
    
    /* Set timeout on sockets */
    HTEventrg_registerTimeout(ms->tv, ms->request, timeout_handler, NO);

    /* Set max number of sockets we want open simultanously */
    HTNet_setMaxSocket(32);

    /* Log file specifed? */
    if (ms->logfile) HTLog_open(ms->logfile, YES, YES);

    /* Set up a server to listen on this port */
    if (ms->port >= 0) {

	/* Create a Net object and bind it to our server request object */
	HTNet * net = HTNet_new(ms->port, ms->request);

	/* Start listening on the socket */
 	if (net && HTDoListen(net, ms->port, INVSOC, ms->backlog) == HT_OK) {

	    /* Register a callback function for handling a request */
	    HTEventrg_register(HTNet_socket(net),ms->request,(SockOps) FD_READ,
			       server_handler, HT_PRIORITY_MAX);

        } else {
	    if (WWWTRACE) HTTrace("Can't listen on port %d\n",ms->port);
	    Cleanup(ms, -1);
	}
    }

    /* Go into the event loop... */
    HTEventrg_loop(ms->request);

    /* Only gets here if event loop fails */
    Cleanup(ms, 0);
    return 0;
}
