/*******************************************************************************
**
** server.c
**
** This file is part of the ABYSS Web server project.
**
** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
*******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef ABYSS_WIN32
#include <io.h>
#else
/* Check this
#include <sys/io.h>
*/
#endif  /* ABYSS_WIN32 */
#include <fcntl.h>

#include "mallocvar.h"

#include "xmlrpc-c/abyss.h"

#define BOUNDARY    "##123456789###BOUNDARY"

typedef int (*TQSortProc)(const void *, const void *);

static int
cmpfilenames(const TFileInfo **f1,const TFileInfo **f2) {
    if (((*f1)->attrib & A_SUBDIR) && !((*f2)->attrib & A_SUBDIR))
        return (-1);
    if (!((*f1)->attrib & A_SUBDIR) && ((*f2)->attrib & A_SUBDIR))
        return 1;

    return strcmp((*f1)->name,(*f2)->name);
}

static int
cmpfiledates(const TFileInfo **f1,const TFileInfo **f2) {
    if (((*f1)->attrib & A_SUBDIR) && !((*f2)->attrib & A_SUBDIR))
        return (-1);
    if (!((*f1)->attrib & A_SUBDIR) && ((*f2)->attrib & A_SUBDIR))
        return 1;

    return ((*f1)->time_write-(*f2)->time_write);
}



static abyss_bool
ServerDirectoryHandler(TSession *r,char *z,TDate *dirdate) {
    TFileInfo fileinfo,*fi;
    TFileFind findhandle;
    char *p,z1[26],z2[20],z3[9],u,*z4;
    TList list;
    int16_t i;
    uint32_t k;
    abyss_bool text=FALSE;
    abyss_bool ascending=TRUE;
    uint16_t sort=1;    /* 1=by name, 2=by date */
    struct tm ftm;
    TPool pool;
    TDate date;

    if (r->query) {
        if (strcmp(r->query,"plain")==0)
            text=TRUE;
        else if (strcmp(r->query,"name-up")==0)
        {
            sort=1;
            ascending=TRUE;
        }
        else if (strcmp(r->query,"name-down")==0)
        {
            sort=1;
            ascending=FALSE;
        }
        else if (strcmp(r->query,"date-up")==0)
        {
            sort=2;
            ascending=TRUE;
        }
        else if (strcmp(r->query,"date-down")==0)
        {
            sort=2;
            ascending=FALSE;
        }
        else 
        {
            ResponseStatus(r,400);
            return TRUE;
        };
    }
    if (DateCompare(&r->date,dirdate)<0)
        dirdate=&r->date;

    p=RequestHeaderValue(r,"If-Modified-Since");
    if (p) {
        if (DateDecode(p,&date)) {
            if (DateCompare(dirdate,&date)<=0)
            {
                ResponseStatus(r,304);
                ResponseWrite(r);
                return TRUE;
            };
        }
    }
    if (!FileFindFirst(&findhandle,z,&fileinfo))
    {
        ResponseStatusErrno(r);
        return TRUE;
    };

    ListInit(&list);

    if (!PoolCreate(&pool,1024))
    {
        ResponseStatus(r,500);
        return TRUE;
    };

    do
    {
        /* Files which names start with a dot are ignored */
        /* This includes implicitly the ./ and ../ */
        if (*fileinfo.name=='.') {
            if (strcmp(fileinfo.name,"..")==0)
            {
                if (strcmp(r->uri,"/")==0)
                    continue;
            }
            else
                continue;
        }
        fi=(TFileInfo *)PoolAlloc(&pool,sizeof(fileinfo));
        if (fi)
        {
            memcpy(fi,&fileinfo,sizeof(fileinfo));
            if (ListAdd(&list,fi))
                continue;
        };

        ResponseStatus(r,500);
        FileFindClose(&findhandle);
        ListFree(&list);
        PoolFree(&pool);
        return TRUE;

    } while (FileFindNext(&findhandle,&fileinfo));

    FileFindClose(&findhandle);

    /* Send something to the user to show that we are still alive */
    ResponseStatus(r,200);
    ResponseContentType(r,(text?"text/plain":"text/html"));

    if (DateToString(dirdate,z))
        ResponseAddField(r,"Last-Modified",z);
    
    ResponseChunked(r);
    ResponseWrite(r);

    if (r->method!=m_head)
    {
        if (text)
        {
            sprintf(z,"Index of %s" CRLF,r->uri);
            i=strlen(z)-2;
            p=z+i+2;

            while (i>0)
            {
                *(p++)='-';
                i--;
            };

            *p='\0';
            strcat(z,CRLF CRLF
                   "Name                      Size      "
                   "Date-Time             Type" CRLF
                   "------------------------------------"
                   "--------------------------------------------"CRLF);
        }
        else
        {
            sprintf(z,"<HTML><HEAD><TITLE>Index of %s</TITLE></HEAD><BODY>"
                    "<H1>Index of %s</H1><PRE>",r->uri,r->uri);
            strcat(z,"Name                      Size      "
                   "Date-Time             Type<HR WIDTH=100%>"CRLF);
        };

        HTTPWrite(r,z,strlen(z));

        /* Sort the files */
            qsort(list.item,list.size,sizeof(void *),
                  (TQSortProc)(sort==1?cmpfilenames:cmpfiledates));
        
        /* Write the listing */
        if (ascending)
            i=0;
        else
            i=list.size-1;

        while ((i<list.size) && (i>=0))
        {
            fi=list.item[i];

            if (ascending)
                i++;
            else
                i--;
            
            strcpy(z,fi->name);

            k=strlen(z);

            if (fi->attrib & A_SUBDIR)
            {
                z[k++]='/';
                z[k]='\0';
            };

            if (k>24)
            {
                z[10]='\0';
                strcpy(z1,z);
                strcat(z1,"...");
                strcat(z1,z+k-11);
                k=24;
                p=z1+24;
            }
            else
            {
                strcpy(z1,z);
                
                k++;
                p=z1+k;
                while (k<25)
                    z1[k++]=' ';

                z1[25]='\0';
            };

            ftm=*(gmtime(&fi->time_write));
            sprintf(z2,"%02u/%02u/%04u %02u:%02u:%02u",ftm.tm_mday,ftm.tm_mon,
                ftm.tm_year+1900,ftm.tm_hour,ftm.tm_min,ftm.tm_sec);

            if (fi->attrib & A_SUBDIR)
            {
                strcpy(z3,"   --  ");
                z4="Directory";
            }
            else
            {
                if (fi->size<9999)
                    u='b';
                else 
                {
                    fi->size/=1024;
                    if (fi->size<9999)
                        u='K';
                    else
                    {
                        fi->size/=1024;
                        if (fi->size<9999)
                            u='M';
                        else
                            u='G';
                    };
                };
                
                sprintf(z3, "%5llu %c", fi->size, u);
                
                if (strcmp(fi->name,"..")==0)
                    z4="";
                else
                    z4=MIMETypeFromFileName(fi->name);

                if (!z4)
                    z4="Unknown";
            };

            if (text)
                sprintf(z,"%s%s %s    %s   %s"CRLF,
                    z1,p,z3,z2,z4);
            else
                sprintf(z,"<A HREF=\"%s%s\">%s</A>%s %s    %s   %s"CRLF,
                    fi->name,(fi->attrib & A_SUBDIR?"/":""),z1,p,z3,z2,z4);

            HTTPWrite(r,z,strlen(z));
        };
        
        /* Write the tail of the file */
        if (text)
            strcpy(z,SERVER_PLAIN_INFO);
        else
            strcpy(z,"</PRE>" SERVER_HTML_INFO "</BODY></HTML>" CRLF CRLF);

        HTTPWrite(r,z,strlen(z));
    };
    
    HTTPWriteEnd(r);
    /* Free memory and exit */
    ListFree(&list);
    PoolFree(&pool);

    return TRUE;
}

static abyss_bool
ServerFileHandler(TSession *r,char *z,TDate *filedate) {
    char *mediatype;
    TFile file;
    uint64_t filesize,start,end;
    uint16_t i;
    TDate date;
    char *p;

    mediatype=MIMETypeGuessFromFile(z);

    if (!FileOpen(&file,z,O_BINARY | O_RDONLY))
    {
        ResponseStatusErrno(r);
        return TRUE;
    };

    if (DateCompare(&r->date,filedate)<0)
        filedate=&r->date;

    p=RequestHeaderValue(r,"if-modified-since");
    if (p) {
        if (DateDecode(p,&date)) {
            if (DateCompare(filedate,&date)<=0) {
                ResponseStatus(r,304);
                ResponseWrite(r);
                return TRUE;
            }
            else
                r->ranges.size=0;
        }
    }
    filesize=FileSize(&file);

    switch (r->ranges.size)
    {
    case 0:
        ResponseStatus(r,200);
        break;

    case 1:
        if (!RangeDecode((char *)(r->ranges.item[0]),filesize,&start,&end))
        {
            ListFree(&(r->ranges));
            ResponseStatus(r,200);
            break;
        }
        
        sprintf(z, "bytes %llu-%llu/%llu", start, end, filesize);

        ResponseAddField(r,"Content-range",z);
        ResponseContentLength(r,end-start+1);
        ResponseStatus(r,206);
        break;

    default:
        ResponseContentType(r,"multipart/ranges; boundary=" BOUNDARY);
        ResponseStatus(r,206);
        break;
    };
    
    if (r->ranges.size==0)
    {
        ResponseContentLength(r,filesize);
        ResponseContentType(r,mediatype);
    };
    
    if (DateToString(filedate,z))
        ResponseAddField(r,"Last-Modified",z);

    ResponseWrite(r);

    if (r->method!=m_head)
    {
        if (r->ranges.size==0)
            ConnWriteFromFile(r->conn,&file,0,filesize-1,z,4096,0);
        else if (r->ranges.size==1)
            ConnWriteFromFile(r->conn,&file,start,end,z,4096,0);
        else
            for (i=0;i<=r->ranges.size;i++)
            {
                ConnWrite(r->conn,"--",2);
                ConnWrite(r->conn,BOUNDARY,strlen(BOUNDARY));
                ConnWrite(r->conn,CRLF,2);

                if (i<r->ranges.size)
                    if (RangeDecode((char *)(r->ranges.item[i]),
                                    filesize,
                                    &start,&end))
                    {
                        /* Entity header, not response header */
                        sprintf(z,"Content-type: %s" CRLF
                                "Content-range: bytes %llu-%llu/%llu" CRLF
                                "Content-length: %llu" CRLF
                                CRLF, mediatype, start, end,
                                filesize, end-start+1);

                        ConnWrite(r->conn,z,strlen(z));

                        ConnWriteFromFile(r->conn,&file,start,end,z,4096,0);
                    };
            };
    };

    FileClose(&file);

    return TRUE;
}

static abyss_bool
ServerDefaultHandlerFunc(TSession *r) {
    char *p,z[4096];
    TFileStat fs;
    uint16_t i;
    abyss_bool endingslash=FALSE;
    TDate objdate;

    if (!RequestValidURIPath(r))
    {
        ResponseStatus(r,400);
        return TRUE;
    };

    /* Must check for * (asterisk uri) in the future */
    if (r->method==m_options)
    {
        ResponseAddField(r,"Allow","GET, HEAD");
        ResponseContentLength(r,0);
        ResponseStatus(r,200);
        return TRUE;
    };

    if ((r->method!=m_get) && (r->method!=m_head))
    {
        ResponseAddField(r,"Allow","GET, HEAD");
        ResponseStatus(r,405);
        return TRUE;
    };

    strcpy(z,r->server->filespath);
    strcat(z,r->uri);

    p=z+strlen(z)-1;
    if (*p=='/')
    {
        endingslash=TRUE;
        *p='\0';
    };

#ifdef ABYSS_WIN32
    p=z;
    while (*p)
    {
        if ((*p)=='/')
            *p='\\';

        p++;
    };
#endif  /* ABYSS_WIN32 */

    if (!FileStat(z,&fs))
    {
        ResponseStatusErrno(r);
        return TRUE;
    };

    if (fs.st_mode & S_IFDIR)
    {
        /* Redirect to the same directory but with the ending slash
        ** to avoid problems with some browsers (IE for examples) when
        ** they generate relative urls */
        if (!endingslash)
        {
            strcpy(z,r->uri);
            p=z+strlen(z);
            *p='/';
            *(p+1)='\0';
            ResponseAddField(r,"Location",z);
            ResponseStatus(r,302);
            ResponseWrite(r);
            return TRUE;
        };

#ifdef ABYSS_WIN32
        *p='\\';
#else
        *p='/';
#endif  /* ABYSS_WIN32 */
        p++;

        i=r->server->defaultfilenames.size;
        while (i-->0) {
            *p='\0';        
            strcat(z,(char *)(r->server->defaultfilenames.item[i]));
            if (FileStat(z,&fs)) {
                if (!(fs.st_mode & S_IFDIR)) {
                    if (DateFromLocal(&objdate,fs.st_mtime))
                        return ServerFileHandler(r,z,&objdate);
                    else
                        return ServerFileHandler(r,z,NULL);
                }
            }
        };

        *(p-1)='\0';
        
        if (!FileStat(z,&fs))
        {
            ResponseStatusErrno(r);
            return TRUE;
        };

        if (DateFromLocal(&objdate,fs.st_mtime))
            return ServerDirectoryHandler(r,z,&objdate);
        else
            return ServerDirectoryHandler(r,z,NULL);

    }
    else
        if (DateFromLocal(&objdate,fs.st_mtime))
            return ServerFileHandler(r,z,&objdate);
        else
            return ServerFileHandler(r,z,NULL);
}



abyss_bool
ServerCreate(TServer *    const srvP,
             const char * const name,
             uint16_t     const port,
             const char * const filespath,
             const char * const logfilename) {

    abyss_bool success;

    if (name)
        srvP->name = strdup(name);
    else
        srvP->name = "unnamed";

    srvP->port = port;

    srvP->defaulthandler = ServerDefaultHandlerFunc;

    if (filespath)
        srvP->filespath = strdup(filespath);
    else
        srvP->filespath = strdup(DEFAULT_DOCS);

    srvP->keepalivetimeout = 15;
    srvP->keepalivemaxconn = 30;
    srvP->timeout = 15;
    srvP->advertise = TRUE;
#ifdef _UNIX
    srvP->pidfile = srvP->uid = srvP->gid = -1;
#endif  /* _UNIX */

    ListInit(&srvP->handlers);
    ListInitAutoFree(&srvP->defaultfilenames);

    if (logfilename)
        success = LogOpen(srvP, logfilename);
    else {
        srvP->logfile = -1;
        success = TRUE;
    }
    return success;
}



static void
terminateHandlers(TList * const handlersP) {
/*----------------------------------------------------------------------------
   Terminate all handlers in the list '*handlersP'.

   I.e. call each handler's terminate function.
-----------------------------------------------------------------------------*/
    if (handlersP->item) {
        unsigned int i;
        for (i = handlersP->size; i > 0; --i) {
            URIHandler2 * const handlerP = handlersP->item[i-1];
            if (handlerP->term)
                handlerP->term(handlerP);
        }
    }
}



void
ServerFree(TServer * const srvP) {

    free(srvP->name);

    free(srvP->filespath);
    
    terminateHandlers(&srvP->handlers);

    ListFree(&srvP->handlers);

    ListInitAutoFree(&srvP->defaultfilenames);

    LogClose(srvP);
}



static void
ServerFunc(TConn * c) {

    TSession r;
    uint32_t ka;
    TList handlers = c->server->handlers;

    ka=c->server->keepalivemaxconn;

    while (ka--) {
        RequestInit(&r,c);

        /* Wait to read until timeout */
        if (!ConnRead(c,c->server->keepalivetimeout))
            break;

        if (RequestRead(&r)) {
            /* Check if it is the last keepalive */
            if (ka==1)
                r.keepalive=FALSE;

            r.cankeepalive=r.keepalive;

            if (r.status==0) {
                if (r.versionmajor>=2)
                    ResponseStatus(&r,505);
                else if (!RequestValidURI(&r))
                    ResponseStatus(&r,400);
                else {
                    abyss_bool handled;
                    int i;

                    for (i = c->server->handlers.size-1, handled = FALSE;
                         i >= 0 && !handled;
                         --i) {
                        URIHandler2 * const handlerP = handlers.item[i];
                        
                        if (handlerP->handleReq2)
                            handlerP->handleReq2(handlerP, &r, &handled);
                        else if (handlerP->handleReq1)
                            handled = handlerP->handleReq1(&r);
                    }

                    if (!handled)
                        ((URIHandler)(c->server->defaulthandler))(&r);
                }
            }
        }
            
        HTTPWriteEnd(&r);

        if (!r.done)
            ResponseError(&r);

        SessionLog(&r);

        if (!(r.keepalive && r.cankeepalive))
            break;

        /**************** Must adjust the read buffer *****************/
        ConnReadInit(c);        
    };

    RequestFree(&r);
    SocketClose(&(c->socket));
}

void ServerInit(TServer *srv)
{
    /********* Must check errors from these functions *************/
    if (!SocketInit())
        TraceExit("Can't initialize TCP sockets\n");;

    if (!SocketCreate(&srv->listensock))
        TraceExit("Can't create a socket\n");;

    if (!SocketBind(&srv->listensock,NULL,srv->port))
        TraceExit("Can't bind\n");

    if (!SocketListen(&srv->listensock,MAX_CONN))
        TraceExit("Can't listen\n");
}

/* With pthread configuration, our connections run as threads of a
   single address space, so we manage a pool of connection
   descriptors.  With fork configuration, our connections run as
   separate processes with their own memory, so we don't have the
   pool.
*/

static abyss_bool const usingPthreadsForConnections = 
#ifdef _THREAD
TRUE;
#else
FALSE;
#endif



static void 
ServerRunThreaded(TServer *srv)
{
    uint32_t i;
    TSocket s,ns;
    TIPAddr peerIpAddr;
    TConn *c;

    /* Connection array from Heap. Small systems might not
     * have the "stack_size" required to have the array of
     * connections right on it */
    MALLOCARRAY_NOFAIL(c, MAX_CONN);

    for (i=0;i<MAX_CONN;i++)
        c[i].inUse = FALSE;

    s=srv->listensock;

    while( 1 )
    {
        /* collect all threads resources for closed connections */
        for (i=0;i<MAX_CONN;i++)
        {
            if( c[i].inUse && ( c[i].connected == FALSE ) )
            {
                ConnClose( &c[i] );
                c[i].inUse = FALSE;
            }
        }
        
        for (i=0; i<MAX_CONN && c[i].inUse; ++i);

        if (i==MAX_CONN)
        {
            /* Every connection descriptor was in use. */
            ThreadWait(2000);
            continue;
        };

        if (SocketAccept(&s,&ns,&peerIpAddr))
        {
            abyss_bool success;
            c[i].inUse = TRUE;
            success = ConnCreate2(&c[i], srv, ns, peerIpAddr, &ServerFunc, 
                                  ABYSS_BACKGROUND);
            if (success)
            {
                ConnProcess(&c[i]);
            }
            else
            {
                SocketClose(&ns);
                c[i].inUse = FALSE;
            }
        }
        else
            TraceMsg("Socket Error=%d\n", SocketError());
    }
    /* We never get here, but it's conceptually possible for someone to 
       terminate a server normally, so... 
    */
    free( c );
}



static void 
ServerRunForked(TServer *srv)
{
    TSocket s,ns;
    TConn c;
    TIPAddr ip;

    s=srv->listensock;

    while (1)
    {
        if (SocketAccept(&s,&ns,&ip))
        {
            abyss_bool success;
            success = ConnCreate2(&c, 
                                  srv, ns, ip, ServerFunc, ABYSS_BACKGROUND);

                /* ConnCreate2() forks.  Child does not return. */
            if (success)
                ConnProcess(&c);

            SocketClose(&ns); /* Close parent's copy of socket */
        }
        else
            TraceMsg("Socket Error=%d\n", SocketError());
    };
}



void 
ServerRun(TServer * const serverP) {
    if (usingPthreadsForConnections)
        ServerRunThreaded(serverP);
    else
        ServerRunForked(serverP);
}



/* ServerRunOnce() supplied by Brian Quinlan of ActiveState. */

/* Bryan Henderson found this to be completely wrong on 2001.11.29
   and changed it so it does the same thing as ServerRun(), but only
   once.

   The biggest problem it had was that when it forked the child (via
   ConnCreate(), both the parent and the child read the socket and
   processed the request!
*/


static void
closeParentSocketCopy(TSocket * socketP) {
/*----------------------------------------------------------------------------
   If we're doing forked connections, close the indicated socket because it
   is the parent's copy and the parent doesn't need it.  If we're doing
   threaded connections, then there's no such thing as a parent's copy, so
   nothing to close.
-----------------------------------------------------------------------------*/
#ifndef _THREAD
    SocketClose(socketP);
#endif
}


void ServerRunOnce2(TServer *           const srv,
                    enum abyss_foreback const foregroundBackground)
{
      TConn connection;
      TSocket listenSocket;
      TSocket connectedSocket;
      TIPAddr remoteAddr;
      abyss_bool succeeded;

      srv->keepalivemaxconn = 1;

      connection.connected = FALSE;
      connection.inUse = FALSE;

      listenSocket = srv->listensock;
      
      succeeded = SocketAccept(&listenSocket, &connectedSocket, &remoteAddr);
      if (succeeded) {
          abyss_bool success;
          success = ConnCreate2(&connection, 
                                srv, connectedSocket, remoteAddr, 
                                &ServerFunc,
                                foregroundBackground);
          if (success)
              ConnProcess(&connection);
          closeParentSocketCopy(&connectedSocket);
      } else
          TraceMsg("Socket Error=%d\n", SocketError());
}

void ServerRunOnce(TServer *srv)
{
    ServerRunOnce2(srv, ABYSS_BACKGROUND);
}



void
ServerAddHandler2(TServer *     const srvP,
                  URIHandler2 * const handlerArgP,
                  abyss_bool *  const successP) {

    URIHandler2 * handlerP;

    MALLOCVAR(handlerP);
    if (handlerP == NULL)
        *successP = FALSE;
    else {
        *handlerP = *handlerArgP;

        if (handlerP->init == NULL)
            *successP = TRUE;
        else
            handlerP->init(handlerP, successP);

        if (*successP) {
            *successP = ListAdd(&srvP->handlers, handlerP);

            if (!*successP) {
                if (handlerP->term)
                    handlerP->term(handlerP);
            }
        }
        if (!*successP)
            free(handlerP);
    }
}



static void
destroyHandler(URIHandler2 * const handlerP) {

    free(handlerP);
}



static URIHandler2 *
createHandler(URIHandler const function) {

    URIHandler2 * handlerP;

    MALLOCVAR(handlerP);
    if (handlerP != NULL) {
        handlerP->init       = NULL;
        handlerP->term       = destroyHandler;
        handlerP->userdata   = NULL;
        handlerP->handleReq2 = NULL;
        handlerP->handleReq1 = function;
    }
    return handlerP;
}



abyss_bool
ServerAddHandler(TServer *  const srvP,
                 URIHandler const function) {

    URIHandler2 * handlerP;
    abyss_bool success;

    handlerP = createHandler(function);

    if (handlerP == NULL)
        success = FALSE;
    else {
        success = ListAdd(&srvP->handlers, handlerP);

        if (!success)
            free(handlerP);
    }
    return success;
}



void
ServerDefaultHandler(TServer *  const srvP,
                     URIHandler const handler) {

    srvP->defaulthandler = handler;
}

abyss_bool LogOpen(TServer *srv, const char *filename) {

    if (FileOpenCreate(&(srv->logfile),filename,O_WRONLY | O_APPEND)) {
        if (MutexCreate(&(srv->logmutex)))
            return TRUE;
        else {
            FileClose(&(srv->logfile));
            srv->logfile=(-1);
        }
    }

    TraceMsg("Can't open log file '%s'",filename);
    return FALSE;
}

void LogWrite(TServer *srv,char *c)
{
    if ((srv->logfile)==(-1))
        return;

    if (!MutexLock(&(srv->logmutex)))
        return;

    FileWrite(&(srv->logfile),c,strlen(c));
    FileWrite(&(srv->logfile),LBR,strlen(LBR));

    MutexUnlock(&(srv->logmutex));
}

void LogClose(TServer *srv)
{
    if ((srv->logfile)==(-1))
        return;

    FileClose(&(srv->logfile));
    MutexFree(&(srv->logmutex));
}

abyss_bool SessionLog(TSession *s)
{
    char z[1024];
    uint32_t n;

    if (s->requestline == NULL)
        return FALSE;

    if (strlen(s->requestline)>1024-26-50)
        s->requestline[1024-26-50]='\0';

    n=sprintf(z,"%d.%d.%d.%d - %s - [",IPB1(s->conn->peerip),IPB2(s->conn->peerip),IPB3(s->conn->peerip),IPB4(s->conn->peerip),(s->user?s->user:""));

    DateToLogString(&s->date,z+n);

    sprintf(z+n+26,"] \"%s\" %d %d",s->requestline,s->status,s->conn->outbytes);

    LogWrite(s->server,z);
    return TRUE;
}
