3547 lines
		
	
	
		
			96 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3547 lines
		
	
	
		
			96 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
    http.c -- GoAhead HTTP engine
 | 
						|
 | 
						|
    This module implements an embedded HTTP/1.1 web server. It supports
 | 
						|
    loadable URL handlers that define the nature of URL processing performed.
 | 
						|
 | 
						|
    Copyright (c) All Rights Reserved. See details at the end of the file.
 | 
						|
 */
 | 
						|
 | 
						|
/********************************* Includes ***********************************/
 | 
						|
 | 
						|
#include    "goahead.h"
 | 
						|
 | 
						|
/********************************* Defines ************************************/
 | 
						|
 | 
						|
#define WEBS_TIMEOUT (ME_GOAHEAD_LIMIT_TIMEOUT * 1000)
 | 
						|
#define PARSE_TIMEOUT (ME_GOAHEAD_LIMIT_PARSE_TIMEOUT * 1000)
 | 
						|
#define CHUNK_LOW               128     /* Low water mark for chunking */
 | 
						|
 | 
						|
#define TOKEN_HEADER_KEY        0x1     /* Validate token as a header key */
 | 
						|
#define TOKEN_HEADER_VALUE      0x2     /* Validate token as a header value */
 | 
						|
#define TOKEN_URI_VALUE         0x4     /* Validate token as a URI value */
 | 
						|
#define TOKEN_NUMBER            0x8     /* Validate token as a number */
 | 
						|
#define TOKEN_WORD              0x10    /* Validate token as single word with no spaces */
 | 
						|
#define TOKEN_LINE              0x20    /* Validate token as line with no newlines */
 | 
						|
 | 
						|
/************************************ Locals **********************************/
 | 
						|
 | 
						|
static int          websBackground;             /* Run as a daemon */
 | 
						|
static int          websDebug;                  /* Run in debug mode and defeat timeouts */
 | 
						|
static int          defaultHttpPort;            /* Default port number for http */
 | 
						|
static int          defaultSslPort;             /* Default port number for https */
 | 
						|
static int          listens[WEBS_MAX_LISTEN];   /* Listen endpoints */;
 | 
						|
static int          listenMax;                  /* Max entry in listens */
 | 
						|
static Webs         **webs;                     /* Open connection list head */
 | 
						|
static WebsHash     websMime;                   /* Set of mime types */
 | 
						|
static int          websMax;                    /* List size */
 | 
						|
static char         websHost[ME_MAX_IP];        /* Host name for the server */
 | 
						|
static char         websIpAddr[ME_MAX_IP];      /* IP address for the server */
 | 
						|
static char         *websHostUrl = NULL;        /* URL to access server */
 | 
						|
static char         *websIpAddrUrl = NULL;      /* URL to access server */
 | 
						|
 | 
						|
#define WEBS_ENCODE_HTML    0x1                 /* Bit setting in charMatch[] */
 | 
						|
#define WEBS_ENCODE_URI     0x4                 /* Encode URI characters */
 | 
						|
 | 
						|
/*
 | 
						|
    Character escape/descape matching codes. Generated by mprEncodeGenerate in appweb.
 | 
						|
*/
 | 
						|
static uchar charMatch[256] = {
 | 
						|
	0x00,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x7e,0x3c,0x3c,0x7c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x7c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x00,0x7f,0x28,0x2a,0x3c,0x2b,0x43,0x02,0x02,0x02,0x28,0x28,0x00,0x00,0x28,
 | 
						|
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x2a,0x3f,0x28,0x3f,0x2a,
 | 
						|
	0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 | 
						|
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0x7e,0x3a,0x3e,0x00,
 | 
						|
	0x3e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 | 
						|
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x3e,0x3e,0x02,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,
 | 
						|
	0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c,0x3c
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
    Add entries to the MimeList as required for your content
 | 
						|
 */
 | 
						|
static WebsMime websMimeList[] = {
 | 
						|
    { "application/java", ".class" },
 | 
						|
    { "application/java", ".jar" },
 | 
						|
    { "text/html", ".asp" },
 | 
						|
    { "text/html", ".htm" },
 | 
						|
    { "text/html", ".html" },
 | 
						|
    { "text/xml", ".xml" },
 | 
						|
    { "image/gif", ".gif" },
 | 
						|
    { "image/jpeg", ".jpg" },
 | 
						|
    { "image/png", ".png" },
 | 
						|
    { "image/svg+xml", ".svg" },
 | 
						|
    { "image/vnd.microsoft.icon", ".ico" },
 | 
						|
    { "text/css", ".css" },
 | 
						|
    { "text/plain", ".txt" },
 | 
						|
    { "application/x-javascript", ".js" },
 | 
						|
    { "application/x-shockwave-flash", ".swf" },
 | 
						|
    { "application/json", ".json" },
 | 
						|
 | 
						|
    { "application/binary", ".exe" },
 | 
						|
    { "application/compress", ".z" },
 | 
						|
    { "application/gzip", ".gz" },
 | 
						|
    { "application/octet-stream", ".bin" },
 | 
						|
    { "application/oda", ".oda" },
 | 
						|
    { "application/pdf", ".pdf" },
 | 
						|
    { "application/postscript", ".ai" },
 | 
						|
    { "application/postscript", ".eps" },
 | 
						|
    { "application/postscript", ".ps" },
 | 
						|
    { "application/rtf", ".rtf" },
 | 
						|
    { "application/x-bcpio", ".bcpio" },
 | 
						|
    { "application/x-cpio", ".cpio" },
 | 
						|
    { "application/x-csh", ".csh" },
 | 
						|
    { "application/x-dvi", ".dvi" },
 | 
						|
    { "application/x-gtar", ".gtar" },
 | 
						|
    { "application/x-hdf", ".hdf" },
 | 
						|
    { "application/x-latex", ".latex" },
 | 
						|
    { "application/x-mif", ".mif" },
 | 
						|
    { "application/x-netcdf", ".nc" },
 | 
						|
    { "application/x-netcdf", ".cdf" },
 | 
						|
    { "application/x-ns-proxy-autoconfig", ".pac" },
 | 
						|
    { "application/x-patch", ".patch" },
 | 
						|
    { "application/x-sh", ".sh" },
 | 
						|
    { "application/x-shar", ".shar" },
 | 
						|
    { "application/x-sv4cpio", ".sv4cpio" },
 | 
						|
    { "application/x-sv4crc", ".sv4crc" },
 | 
						|
    { "application/x-tar", ".tar" },
 | 
						|
    { "application/x-tgz", ".tgz" },
 | 
						|
    { "application/x-tcl", ".tcl" },
 | 
						|
    { "application/x-tex", ".tex" },
 | 
						|
    { "application/x-texinfo", ".texinfo" },
 | 
						|
    { "application/x-texinfo", ".texi" },
 | 
						|
    { "application/x-troff", ".t" },
 | 
						|
    { "application/x-troff", ".tr" },
 | 
						|
    { "application/x-troff", ".roff" },
 | 
						|
    { "application/x-troff-man", ".man" },
 | 
						|
    { "application/x-troff-me", ".me" },
 | 
						|
    { "application/x-troff-ms", ".ms" },
 | 
						|
    { "application/x-ustar", ".ustar" },
 | 
						|
    { "application/x-wais-source", ".src" },
 | 
						|
    { "application/zip", ".zip" },
 | 
						|
    { "audio/basic", ".au snd" },
 | 
						|
    { "audio/x-aiff", ".aif" },
 | 
						|
    { "audio/x-aiff", ".aiff" },
 | 
						|
    { "audio/x-aiff", ".aifc" },
 | 
						|
    { "audio/x-wav", ".wav" },
 | 
						|
    { "audio/x-wav", ".ram" },
 | 
						|
    { "image/ief", ".ief" },
 | 
						|
    { "image/jpeg", ".jpeg" },
 | 
						|
    { "image/jpeg", ".jpe" },
 | 
						|
    { "image/tiff", ".tiff" },
 | 
						|
    { "image/tiff", ".tif" },
 | 
						|
    { "image/x-cmu-raster", ".ras" },
 | 
						|
    { "image/x-portable-anymap", ".pnm" },
 | 
						|
    { "image/x-portable-bitmap", ".pbm" },
 | 
						|
    { "image/x-portable-graymap", ".pgm" },
 | 
						|
    { "image/x-portable-pixmap", ".ppm" },
 | 
						|
    { "image/x-rgb", ".rgb" },
 | 
						|
    { "image/x-xbitmap", ".xbm" },
 | 
						|
    { "image/x-xpixmap", ".xpm" },
 | 
						|
    { "image/x-xwindowdump", ".xwd" },
 | 
						|
    { "text/html", ".cfm" },
 | 
						|
    { "text/html", ".shtm" },
 | 
						|
    { "text/html", ".shtml" },
 | 
						|
    { "text/richtext", ".rtx" },
 | 
						|
    { "text/tab-separated-values", ".tsv" },
 | 
						|
    { "text/x-setext", ".etx" },
 | 
						|
    { "video/mpeg", ".mpeg" },
 | 
						|
    { "video/mpeg", ".mpg" },
 | 
						|
    { "video/mpeg", ".mpe" },
 | 
						|
    { "video/quicktime", ".qt" },
 | 
						|
    { "video/quicktime", ".mov" },
 | 
						|
    { "video/mp4", ".mp4" },
 | 
						|
    { "video/x-msvideo", ".avi" },
 | 
						|
    { "video/x-sgi-movie", ".movie" },
 | 
						|
    { NULL, NULL},
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
    Standard HTTP error codes
 | 
						|
 */
 | 
						|
static WebsError websErrors[] = {
 | 
						|
    { 200, "OK" },
 | 
						|
    { 201, "Created" },
 | 
						|
    { 204, "No Content" },
 | 
						|
    { 205, "Reset Content" },
 | 
						|
    { 206, "Partial Content" },
 | 
						|
    { 301, "Redirect" },
 | 
						|
    { 302, "Redirect" },
 | 
						|
    { 304, "Not Modified" },
 | 
						|
    { 400, "Bad Request" },
 | 
						|
    { 401, "Unauthorized" },
 | 
						|
    { 402, "Payment required" },
 | 
						|
    { 403, "Forbidden" },
 | 
						|
    { 404, "Not Found" },
 | 
						|
    { 405, "Access Denied" },
 | 
						|
    { 406, "Not Acceptable" },
 | 
						|
    { 408, "Request Timeout" },
 | 
						|
    { 413, "Request too large" },
 | 
						|
    { 500, "Internal Server Error" },
 | 
						|
    { 501, "Not Implemented" },
 | 
						|
    { 503, "Service Unavailable" },
 | 
						|
    { 0, NULL }
 | 
						|
};
 | 
						|
 | 
						|
#if ME_GOAHEAD_ACCESS_LOG && !ME_ROM
 | 
						|
static char     accessLog[64] = "access.log";       /* Log filename */
 | 
						|
static int      accessFd;                           /* Log file handle */
 | 
						|
#endif
 | 
						|
 | 
						|
static WebsHash sessions = -1;
 | 
						|
static int      sessionCount = 0;
 | 
						|
static int      pruneId;                            /* Callback ID */
 | 
						|
 | 
						|
/**************************** Forward Declarations ****************************/
 | 
						|
 | 
						|
static void     checkTimeout(void *arg, int id);
 | 
						|
static bool     filterChunkData(Webs *wp);
 | 
						|
static int      getTimeSinceMark(Webs *wp);
 | 
						|
static char     *getToken(Webs *wp, char *delim, int validation);
 | 
						|
static void     parseFirstLine(Webs *wp);
 | 
						|
static void     parseHeaders(Webs *wp);
 | 
						|
static bool     processContent(Webs *wp);
 | 
						|
static bool     parseIncoming(Webs *wp);
 | 
						|
static void     pruneSessions(void);
 | 
						|
static void     freeSession(WebsSession *sp);
 | 
						|
static void     freeSessions(void);
 | 
						|
static void     readEvent(Webs *wp);
 | 
						|
static void     reuseConn(Webs *wp);
 | 
						|
static void     setFileLimits(void);
 | 
						|
static int      setLocalHost(void);
 | 
						|
static void     socketEvent(int sid, int mask, void *data);
 | 
						|
static void     writeEvent(Webs *wp);
 | 
						|
static char     *validateToken(char *token, char *endToken, int validation);
 | 
						|
 | 
						|
#if ME_GOAHEAD_ACCESS_LOG
 | 
						|
static void     logRequest(Webs *wp, int code);
 | 
						|
#endif
 | 
						|
 | 
						|
/*********************************** Code *************************************/
 | 
						|
 | 
						|
PUBLIC int websOpen(cchar *documents, cchar *routeFile)
 | 
						|
{
 | 
						|
    WebsMime    *mt;
 | 
						|
 | 
						|
    webs = NULL;
 | 
						|
    websMax = 0;
 | 
						|
 | 
						|
    websOsOpen();
 | 
						|
    websRuntimeOpen();
 | 
						|
    websTimeOpen();
 | 
						|
    websFsOpen();
 | 
						|
    logOpen();
 | 
						|
    setFileLimits();
 | 
						|
    socketOpen();
 | 
						|
    if (setLocalHost() < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
#if ME_COM_SSL
 | 
						|
    if (sslOpen() < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    if ((sessions = hashCreate(-1)) < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    if (!websDebug) {
 | 
						|
        pruneId = websStartEvent(WEBS_SESSION_PRUNE, (WebsEventProc) pruneSessions, 0);
 | 
						|
    }
 | 
						|
    if (documents) {
 | 
						|
        websSetDocuments(documents);
 | 
						|
    }
 | 
						|
    if (websOpenRoute() < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
#if ME_GOAHEAD_CGI
 | 
						|
    websCgiOpen();
 | 
						|
#endif
 | 
						|
    websOptionsOpen();
 | 
						|
    websActionOpen();
 | 
						|
    websFileOpen();
 | 
						|
#if ME_GOAHEAD_UPLOAD
 | 
						|
    websUploadOpen();
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_JAVASCRIPT
 | 
						|
    websJstOpen();
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_AUTH
 | 
						|
    if (websOpenAuth(0) < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    if (routeFile && websLoad(routeFile) < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    /*
 | 
						|
        Create a mime type lookup table for quickly determining the content type
 | 
						|
     */
 | 
						|
    websMime = hashCreate(WEBS_HASH_INIT * 4);
 | 
						|
    assert(websMime >= 0);
 | 
						|
    for (mt = websMimeList; mt->type; mt++) {
 | 
						|
        hashEnter(websMime, mt->ext, valueString(mt->type, 0), 0);
 | 
						|
    }
 | 
						|
 | 
						|
#if ME_GOAHEAD_ACCESS_LOG && !ME_ROM
 | 
						|
    if ((accessFd = open(accessLog, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0666)) < 0) {
 | 
						|
        error("Cannot open access log %s", accessLog);
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    /* Some platforms don't implement O_APPEND (VXWORKS) */
 | 
						|
    lseek(accessFd, 0, SEEK_END);
 | 
						|
#endif
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websClose(void)
 | 
						|
{
 | 
						|
    Webs    *wp;
 | 
						|
    int     i;
 | 
						|
 | 
						|
    websCloseRoute();
 | 
						|
#if ME_GOAHEAD_AUTH
 | 
						|
    websCloseAuth();
 | 
						|
#endif
 | 
						|
    if (pruneId >= 0) {
 | 
						|
        websStopEvent(pruneId);
 | 
						|
        pruneId = -1;
 | 
						|
    }
 | 
						|
    if (sessions >= 0) {
 | 
						|
        freeSessions();
 | 
						|
    }
 | 
						|
    for (i = 0; i < listenMax; i++) {
 | 
						|
        if (listens[i] >= 0) {
 | 
						|
            socketCloseConnection(listens[i]);
 | 
						|
            listens[i] = -1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    listenMax = 0;
 | 
						|
    for (i = websMax; webs && i >= 0; i--) {
 | 
						|
        if ((wp = webs[i]) == NULL) {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
        if (wp->sid >= 0) {
 | 
						|
            socketCloseConnection(wp->sid);
 | 
						|
            wp->sid = -1;
 | 
						|
        }
 | 
						|
        websFree(wp);
 | 
						|
    }
 | 
						|
    wfree(websHostUrl);
 | 
						|
    wfree(websIpAddrUrl);
 | 
						|
    websIpAddrUrl = websHostUrl = NULL;
 | 
						|
 | 
						|
#if ME_COM_SSL
 | 
						|
    sslClose();
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_ACCESS_LOG
 | 
						|
    if (accessFd >= 0) {
 | 
						|
        close(accessFd);
 | 
						|
        accessFd = -1;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    websFsClose();
 | 
						|
    hashFree(websMime);
 | 
						|
    socketClose();
 | 
						|
    logClose();
 | 
						|
    websTimeClose();
 | 
						|
    websRuntimeClose();
 | 
						|
    websOsClose();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void initWebs(Webs *wp, int flags, int reuse)
 | 
						|
{
 | 
						|
    WebsBuf     rxbuf;
 | 
						|
    WebsTime    timestamp;
 | 
						|
    void        *ssl;
 | 
						|
    char        ipaddr[ME_MAX_IP], ifaddr[ME_MAX_IP];
 | 
						|
    int         wid, sid, timeout, listenSid;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    if (reuse) {
 | 
						|
        rxbuf = wp->rxbuf;
 | 
						|
        wid = wp->wid;
 | 
						|
        sid = wp->sid;
 | 
						|
        timeout = wp->timeout;
 | 
						|
        ssl = wp->ssl;
 | 
						|
        listenSid = wp->listenSid;
 | 
						|
        scopy(ipaddr, sizeof(ipaddr), wp->ipaddr);
 | 
						|
        scopy(ifaddr, sizeof(ifaddr), wp->ifaddr);
 | 
						|
        timestamp = wp->timestamp;
 | 
						|
    } else {
 | 
						|
        wid = sid = -1;
 | 
						|
        timeout = -1;
 | 
						|
        ssl = 0;
 | 
						|
        listenSid = -1;
 | 
						|
        timestamp = 0;
 | 
						|
    }
 | 
						|
    memset(wp, 0, sizeof(Webs));
 | 
						|
    wp->flags = flags;
 | 
						|
    wp->state = WEBS_BEGIN;
 | 
						|
    wp->wid = wid;
 | 
						|
    wp->sid = sid;
 | 
						|
    wp->timeout = timeout;
 | 
						|
    wp->docfd = -1;
 | 
						|
    wp->txLen = -1;
 | 
						|
    wp->rxLen = -1;
 | 
						|
    wp->responseCookies = hashCreate(7);
 | 
						|
    wp->code = HTTP_CODE_OK;
 | 
						|
    wp->ssl = ssl;
 | 
						|
    wp->listenSid = listenSid;
 | 
						|
    wp->timestamp = timestamp;
 | 
						|
#if !ME_ROM
 | 
						|
    wp->putfd = -1;
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_CGI
 | 
						|
    wp->cgifd = -1;
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_UPLOAD
 | 
						|
    wp->files = -1;
 | 
						|
    wp->upfd = -1;
 | 
						|
#endif
 | 
						|
    if (reuse) {
 | 
						|
        scopy(wp->ipaddr, sizeof(wp->ipaddr), ipaddr);
 | 
						|
        scopy(wp->ifaddr, sizeof(wp->ifaddr), ifaddr);
 | 
						|
    } else {
 | 
						|
        wp->timeout = -1;
 | 
						|
    }
 | 
						|
    wp->vars = hashCreate(WEBS_HASH_INIT);
 | 
						|
    /*
 | 
						|
        Ring queues can never be totally full and are short one byte. Better to do even I/O and allocate
 | 
						|
        a little more memory than required. The chunkbuf has extra room to fit chunk headers and trailers.
 | 
						|
     */
 | 
						|
    assert(ME_GOAHEAD_LIMIT_BUFFER >= 1024);
 | 
						|
    bufCreate(&wp->output, ME_GOAHEAD_LIMIT_BUFFER + 1, ME_GOAHEAD_LIMIT_BUFFER + 1);
 | 
						|
    bufCreate(&wp->chunkbuf, ME_GOAHEAD_LIMIT_BUFFER + 1, ME_GOAHEAD_LIMIT_BUFFER * 2);
 | 
						|
    bufCreate(&wp->input, ME_GOAHEAD_LIMIT_BUFFER + 1, ME_GOAHEAD_LIMIT_PUT + 1);
 | 
						|
    if (reuse) {
 | 
						|
        wp->rxbuf = rxbuf;
 | 
						|
    } else {
 | 
						|
        bufCreate(&wp->rxbuf, ME_GOAHEAD_LIMIT_HEADERS, ME_GOAHEAD_LIMIT_HEADERS + ME_GOAHEAD_LIMIT_PUT);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void termWebs(Webs *wp, int reuse)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    /*
 | 
						|
        Some of this is done elsewhere, but keep this here for when a shutdown is done and there are open connections.
 | 
						|
     */
 | 
						|
    bufFree(&wp->input);
 | 
						|
    bufFree(&wp->output);
 | 
						|
    bufFree(&wp->chunkbuf);
 | 
						|
    if (!reuse) {
 | 
						|
        bufFree(&wp->rxbuf);
 | 
						|
        if (wp->sid >= 0) {
 | 
						|
#if ME_COM_SSL
 | 
						|
            sslFree(wp);
 | 
						|
#endif
 | 
						|
            socketDeleteHandler(wp->sid);
 | 
						|
            socketCloseConnection(wp->sid);
 | 
						|
            wp->sid = -1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
#if !ME_ROM
 | 
						|
    if (wp->putfd >= 0) {
 | 
						|
        close(wp->putfd);
 | 
						|
        wp->putfd = -1;
 | 
						|
        assert(wp->putname && wp->filename);
 | 
						|
        if (rename(wp->putname, wp->filename) < 0) {
 | 
						|
            error("Cannot rename PUT file from %s to %s", wp->putname, wp->filename);
 | 
						|
        }
 | 
						|
    }
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_CGI
 | 
						|
    if (wp->cgifd >= 0) {
 | 
						|
        close(wp->cgifd);
 | 
						|
        wp->cgifd = -1;
 | 
						|
    }
 | 
						|
    if (wp->cgiStdin) {
 | 
						|
        unlink(wp->cgiStdin);
 | 
						|
        wfree(wp->cgiStdin);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_UPLOAD
 | 
						|
    wfree(wp->clientFilename);
 | 
						|
#endif
 | 
						|
    websPageClose(wp);
 | 
						|
    if (wp->timeout >= 0 && !reuse) {
 | 
						|
        websCancelTimeout(wp);
 | 
						|
    }
 | 
						|
    wfree(wp->authDetails);
 | 
						|
    wfree(wp->authResponse);
 | 
						|
    wfree(wp->authType);
 | 
						|
    wfree(wp->contentType);
 | 
						|
    wfree(wp->cookie);
 | 
						|
    wfree(wp->decodedQuery);
 | 
						|
    wfree(wp->digest);
 | 
						|
    wfree(wp->ext);
 | 
						|
    wfree(wp->filename);
 | 
						|
    wfree(wp->host);
 | 
						|
    wfree(wp->method);
 | 
						|
    wfree(wp->password);
 | 
						|
    wfree(wp->path);
 | 
						|
    wfree(wp->protoVersion);
 | 
						|
    wfree(wp->putname);
 | 
						|
    wfree(wp->query);
 | 
						|
    wfree(wp->realm);
 | 
						|
    wfree(wp->referrer);
 | 
						|
    wfree(wp->url);
 | 
						|
    wfree(wp->userAgent);
 | 
						|
    wfree(wp->username);
 | 
						|
#if ME_GOAHEAD_UPLOAD
 | 
						|
    wfree(wp->boundary);
 | 
						|
    wfree(wp->uploadTmp);
 | 
						|
    wfree(wp->uploadVar);
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_DIGEST
 | 
						|
    wfree(wp->cnonce);
 | 
						|
    wfree(wp->digestUri);
 | 
						|
    wfree(wp->opaque);
 | 
						|
    wfree(wp->nc);
 | 
						|
    wfree(wp->nonce);
 | 
						|
    wfree(wp->qop);
 | 
						|
#endif
 | 
						|
    hashFree(wp->vars);
 | 
						|
    hashFree(wp->responseCookies);
 | 
						|
 | 
						|
#if ME_GOAHEAD_UPLOAD
 | 
						|
    if (wp->files >= 0) {
 | 
						|
        websFreeUpload(wp);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websAlloc(int sid)
 | 
						|
{
 | 
						|
    Webs    *wp;
 | 
						|
    int     wid;
 | 
						|
 | 
						|
    if ((wid = wallocObject(&webs, &websMax, sizeof(Webs))) < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    wp = webs[wid];
 | 
						|
    assert(wp);
 | 
						|
    initWebs(wp, 0, 0);
 | 
						|
    wp->wid = wid;
 | 
						|
    wp->sid = sid;
 | 
						|
    wp->timestamp = time(0);
 | 
						|
    return wid;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void reuseConn(Webs *wp)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    bufCompact(&wp->rxbuf);
 | 
						|
    if (bufLen(&wp->rxbuf)) {
 | 
						|
        socketReservice(wp->sid);
 | 
						|
    }
 | 
						|
    termWebs(wp, 1);
 | 
						|
    initWebs(wp, wp->flags & (WEBS_KEEP_ALIVE | WEBS_SECURE | WEBS_HTTP11), 1);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websFree(Webs *wp)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    termWebs(wp, 0);
 | 
						|
    websMax = wfreeHandle(&webs, wp->wid);
 | 
						|
    wfree(wp);
 | 
						|
    assert(websMax >= 0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Called when the request is complete. Note: it may not have fully drained from the tx buffer.
 | 
						|
 */
 | 
						|
PUBLIC void websDone(Webs *wp)
 | 
						|
{
 | 
						|
    WebsSocket  *sp;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    if (wp->finalized) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    assert(WEBS_BEGIN <= wp->state && wp->state <= WEBS_COMPLETE);
 | 
						|
#if DEPRECATED || 1
 | 
						|
    wp->flags |= WEBS_FINALIZED;
 | 
						|
#endif
 | 
						|
    wp->finalized = 1;
 | 
						|
 | 
						|
    /*
 | 
						|
        Once running, it is the handlers responsibility to conclude the request.
 | 
						|
     */
 | 
						|
    if (wp->connError && wp->state < WEBS_RUNNING) {
 | 
						|
        wp->state = WEBS_COMPLETE;
 | 
						|
    }
 | 
						|
    if (wp->state < WEBS_COMPLETE) {
 | 
						|
        /*
 | 
						|
            Initiate flush. If not all flushed, wait for output to drain via a socket event.
 | 
						|
         */
 | 
						|
        if (websFlush(wp, 0) == 0) {
 | 
						|
            sp = socketPtr(wp->sid);
 | 
						|
            socketCreateHandler(wp->sid, sp->handlerMask | SOCKET_WRITABLE, socketEvent, wp);
 | 
						|
        }
 | 
						|
    }
 | 
						|
#if ME_GOAHEAD_ACCESS_LOG
 | 
						|
    logRequest(wp, wp->code);
 | 
						|
#endif
 | 
						|
    if (!(wp->flags & WEBS_RESPONSE_TRACED)) {
 | 
						|
        trace(3 | WEBS_RAW_MSG, "Request complete: code %d", wp->code);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int complete(Webs *wp, int reuse)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(wp->state == WEBS_BEGIN || wp->state == WEBS_COMPLETE);
 | 
						|
 | 
						|
    if (reuse && wp->flags & WEBS_KEEP_ALIVE && wp->rxRemaining == 0) {
 | 
						|
        reuseConn(wp);
 | 
						|
        socketCreateHandler(wp->sid, SOCKET_READABLE, socketEvent, wp);
 | 
						|
        trace(5, "Keep connection alive");
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
    trace(5, "Close connection");
 | 
						|
    wp->state = WEBS_BEGIN;
 | 
						|
    wp->flags |= WEBS_CLOSED;
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websListen(cchar *endpoint)
 | 
						|
{
 | 
						|
    WebsSocket  *sp;
 | 
						|
    char        *ip, *ipaddr;
 | 
						|
    int         port, secure, sid;
 | 
						|
 | 
						|
    assert(endpoint && *endpoint);
 | 
						|
 | 
						|
    if (listenMax >= WEBS_MAX_LISTEN) {
 | 
						|
        error("Too many listen endpoints");
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    socketParseAddress(endpoint, &ip, &port, &secure, 80);
 | 
						|
    if ((sid = socketListen(ip, port, websAccept, 0)) < 0) {
 | 
						|
        error("Unable to open socket on port %d.", port);
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    sp = socketPtr(sid);
 | 
						|
    sp->secure = secure;
 | 
						|
    if (sp->secure) {
 | 
						|
        if (!defaultSslPort) {
 | 
						|
            defaultSslPort = port;
 | 
						|
        }
 | 
						|
    } else if (!defaultHttpPort) {
 | 
						|
        defaultHttpPort = port;
 | 
						|
    }
 | 
						|
    listens[listenMax++] = sid;
 | 
						|
    if (ip) {
 | 
						|
        ipaddr = smatch(ip, "::") ? "[::]" : ip;
 | 
						|
    } else {
 | 
						|
        ipaddr = "*";
 | 
						|
    }
 | 
						|
    logmsg(2, "Started %s://%s:%d", secure ? "https" : "http", ipaddr, port);
 | 
						|
 | 
						|
    if (!websHostUrl) {
 | 
						|
        if (port == 80) {
 | 
						|
            websHostUrl = sclone(ip ? ip : websIpAddr);
 | 
						|
        } else {
 | 
						|
            websHostUrl = sfmt("%s:%d", ip ? ip : websIpAddr, port);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if (!websIpAddrUrl) {
 | 
						|
        if (port == 80) {
 | 
						|
            websIpAddrUrl = sclone(websIpAddr);
 | 
						|
        } else {
 | 
						|
            websIpAddrUrl = sfmt("%s:%d", websIpAddr, port);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    wfree(ip);
 | 
						|
    return sid;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Accept a new connection from ipaddr:port
 | 
						|
 */
 | 
						|
PUBLIC int websAccept(int sid, cchar *ipaddr, int port, int listenSid)
 | 
						|
{
 | 
						|
    Webs        *wp;
 | 
						|
    WebsSocket  *lp;
 | 
						|
    struct sockaddr_storage ifAddr;
 | 
						|
    int         wid, len;
 | 
						|
 | 
						|
    assert(sid >= 0);
 | 
						|
    assert(ipaddr && *ipaddr);
 | 
						|
    assert(listenSid >= 0);
 | 
						|
    assert(port >= 0);
 | 
						|
 | 
						|
    /*
 | 
						|
        Allocate a new handle for this accepted connection. This will allocate a Webs structure in the webs[] list
 | 
						|
     */
 | 
						|
    if ((wid = websAlloc(sid)) < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    wp = webs[wid];
 | 
						|
    assert(wp);
 | 
						|
    wp->listenSid = listenSid;
 | 
						|
    strncpy(wp->ipaddr, ipaddr, min(sizeof(wp->ipaddr) - 1, strlen(ipaddr)));
 | 
						|
 | 
						|
    /*
 | 
						|
        Get the ip address of the interface that accept the connection.
 | 
						|
     */
 | 
						|
    len = sizeof(ifAddr);
 | 
						|
    if (getsockname(socketPtr(sid)->sock, (struct sockaddr*) &ifAddr, (Socklen*) &len) < 0) {
 | 
						|
        error("Cannot get sockname");
 | 
						|
        websFree(wp);
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    socketAddress((struct sockaddr*) &ifAddr, (int) len, wp->ifaddr, sizeof(wp->ifaddr), NULL);
 | 
						|
 | 
						|
#if ME_GOAHEAD_LEGACY
 | 
						|
    /*
 | 
						|
        Check if this is a request from a browser on this system. This is useful to know for permitting administrative
 | 
						|
        operations only for local access
 | 
						|
     */
 | 
						|
    if (strcmp(wp->ipaddr, "127.0.0.1") == 0 || strcmp(wp->ipaddr, websIpAddr) == 0 ||
 | 
						|
            strcmp(wp->ipaddr, websHost) == 0) {
 | 
						|
        wp->flags |= WEBS_LOCAL;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    /*
 | 
						|
        Arrange for socketEvent to be called when read data is available
 | 
						|
     */
 | 
						|
    lp = socketPtr(listenSid);
 | 
						|
    trace(4, "New connection from %s:%d to %s:%d", ipaddr, port, wp->ifaddr, lp->port);
 | 
						|
 | 
						|
#if ME_COM_SSL
 | 
						|
    if (lp->secure) {
 | 
						|
        wp->flags |= WEBS_SECURE;
 | 
						|
        trace(4, "Upgrade connection to TLS");
 | 
						|
        if (sslUpgrade(wp) < 0) {
 | 
						|
            error("Cannot upgrade to TLS");
 | 
						|
            websFree(wp);
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    assert(wp->timeout == -1);
 | 
						|
    wp->timeout = websStartEvent(PARSE_TIMEOUT, checkTimeout, (void*) wp);
 | 
						|
    socketEvent(sid, SOCKET_READABLE, wp);
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    The webs socket handler. Called in response to I/O. We just pass control to the relevant read or write handler. A
 | 
						|
    pointer to the webs structure is passed as a (void*) in wptr.
 | 
						|
 */
 | 
						|
static void socketEvent(int sid, int mask, void *wptr)
 | 
						|
{
 | 
						|
    Webs    *wp;
 | 
						|
 | 
						|
    wp = (Webs*) wptr;
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    if (! websValid(wp)) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    if (mask & SOCKET_READABLE) {
 | 
						|
        readEvent(wp);
 | 
						|
    }
 | 
						|
    if (mask & SOCKET_WRITABLE) {
 | 
						|
        writeEvent(wp);
 | 
						|
    }
 | 
						|
    if (wp->flags & WEBS_CLOSED) {
 | 
						|
        websFree(wp);
 | 
						|
        /* WARNING: wp not valid here */
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Read from a connection. Return the number of bytes read if successful. This may be less than the requested "len" and
 | 
						|
    may be zero. Return -1 for errors or EOF. Distinguish between error and EOF via socketEof().
 | 
						|
 */
 | 
						|
static ssize websRead(Webs *wp, char *buf, ssize len)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    assert(buf);
 | 
						|
    assert(len > 0);
 | 
						|
#if ME_COM_SSL
 | 
						|
    if (wp->flags & WEBS_SECURE) {
 | 
						|
        return sslRead(wp, buf, len);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    return socketRead(wp->sid, buf, len);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    The webs read handler. This is the primary read event loop. It uses a state machine to track progress while parsing
 | 
						|
    the HTTP request.  Note: we never block as the socket is always in non-blocking mode.
 | 
						|
 */
 | 
						|
static void readEvent(Webs *wp)
 | 
						|
{
 | 
						|
    WebsBuf     *rxbuf;
 | 
						|
    WebsSocket  *sp;
 | 
						|
    ssize       nbytes;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    if (!websValid(wp)) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    websNoteRequestActivity(wp);
 | 
						|
    rxbuf = &wp->rxbuf;
 | 
						|
 | 
						|
    if (bufRoom(rxbuf) < (ME_GOAHEAD_LIMIT_BUFFER + 1)) {
 | 
						|
        if (!bufGrow(rxbuf, ME_GOAHEAD_LIMIT_BUFFER + 1)) {
 | 
						|
            websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Cannot grow rxbuf");
 | 
						|
            websPump(wp);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if ((nbytes = websRead(wp, (char*) rxbuf->endp, ME_GOAHEAD_LIMIT_BUFFER)) > 0) {
 | 
						|
        wp->lastRead = nbytes;
 | 
						|
        bufAdjustEnd(rxbuf, nbytes);
 | 
						|
        bufAddNull(rxbuf);
 | 
						|
    }
 | 
						|
    if (nbytes > 0 || wp->state > WEBS_BEGIN) {
 | 
						|
        websPump(wp);
 | 
						|
    }
 | 
						|
    if (wp->flags & WEBS_CLOSED) {
 | 
						|
        return;
 | 
						|
    } else if (nbytes < 0 && socketEof(wp->sid)) {
 | 
						|
        /* EOF or error. Allow running requests to continue. */
 | 
						|
        if (wp->state < WEBS_READY) {
 | 
						|
            if (wp->state > WEBS_BEGIN) {
 | 
						|
                websError(wp, HTTP_CODE_COMMS_ERROR | WEBS_CLOSE, "Read error: connection lost");
 | 
						|
                websPump(wp);
 | 
						|
            } else {
 | 
						|
                complete(wp, 0);
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            socketDeleteHandler(wp->sid);
 | 
						|
        }
 | 
						|
    } else if (wp->state < WEBS_READY) {
 | 
						|
        sp = socketPtr(wp->sid);
 | 
						|
        socketCreateHandler(wp->sid, sp->handlerMask | SOCKET_READABLE, socketEvent, wp);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websPump(Webs *wp)
 | 
						|
{
 | 
						|
    bool    canProceed;
 | 
						|
 | 
						|
    for (canProceed = 1; canProceed; ) {
 | 
						|
        switch (wp->state) {
 | 
						|
        case WEBS_BEGIN:
 | 
						|
            canProceed = parseIncoming(wp);
 | 
						|
            break;
 | 
						|
        case WEBS_CONTENT:
 | 
						|
            canProceed = processContent(wp);
 | 
						|
            break;
 | 
						|
        case WEBS_READY:
 | 
						|
            if (!websRunRequest(wp)) {
 | 
						|
                /* Reroute if the handler re-wrote the request */
 | 
						|
                websRouteRequest(wp);
 | 
						|
                wp->state = WEBS_READY;
 | 
						|
                canProceed = 1;
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            canProceed = (wp->state != WEBS_RUNNING);
 | 
						|
            break;
 | 
						|
        case WEBS_RUNNING:
 | 
						|
            /* Nothing to do until websDone is called */
 | 
						|
            return;
 | 
						|
        case WEBS_COMPLETE:
 | 
						|
            canProceed = complete(wp, 1);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static bool parseIncoming(Webs *wp)
 | 
						|
{
 | 
						|
    WebsBuf     *rxbuf;
 | 
						|
    char        *end, c;
 | 
						|
 | 
						|
    rxbuf = &wp->rxbuf;
 | 
						|
    while (*rxbuf->servp == '\r' || *rxbuf->servp == '\n') {
 | 
						|
        if (bufGetc(rxbuf) < 0) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if ((end = strstr((char*) wp->rxbuf.servp, "\r\n\r\n")) == 0) {
 | 
						|
        if (bufLen(&wp->rxbuf) >= ME_GOAHEAD_LIMIT_HEADER) {
 | 
						|
            websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Header too large");
 | 
						|
            return 1;
 | 
						|
        }
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    trace(3 | WEBS_RAW_MSG, "\n<<< Request\n");
 | 
						|
    c = *end;
 | 
						|
    *end = '\0';
 | 
						|
    trace(3 | WEBS_RAW_MSG, "%s\n", wp->rxbuf.servp);
 | 
						|
    *end = c;
 | 
						|
 | 
						|
    /*
 | 
						|
        Parse the first line of the Http header
 | 
						|
     */
 | 
						|
    parseFirstLine(wp);
 | 
						|
    if (wp->state == WEBS_COMPLETE) {
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
    parseHeaders(wp);
 | 
						|
    if (wp->state == WEBS_COMPLETE) {
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
    wp->state = (wp->rxChunkState || wp->rxLen > 0) ? WEBS_CONTENT : WEBS_READY;
 | 
						|
 | 
						|
    websRouteRequest(wp);
 | 
						|
 | 
						|
    if (wp->state == WEBS_COMPLETE) {
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
#if ME_GOAHEAD_CGI
 | 
						|
    if (wp->route && wp->route->handler && wp->route->handler->service == cgiHandler) {
 | 
						|
        if (smatch(wp->method, "POST")) {
 | 
						|
            wp->cgiStdin = websGetCgiCommName();
 | 
						|
            if ((wp->cgifd = open(wp->cgiStdin, O_CREAT | O_WRONLY | O_BINARY | O_TRUNC, 0666)) < 0) {
 | 
						|
                websError(wp, HTTP_CODE_NOT_FOUND | WEBS_CLOSE, "Cannot open CGI file");
 | 
						|
                return 1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
#endif
 | 
						|
#if !ME_ROM
 | 
						|
    if (smatch(wp->method, "PUT")) {
 | 
						|
        WebsStat    sbuf;
 | 
						|
        wp->code = (stat(wp->filename, &sbuf) == 0 && sbuf.st_mode & S_IFDIR) ? HTTP_CODE_NO_CONTENT : HTTP_CODE_CREATED;
 | 
						|
        wfree(wp->putname);
 | 
						|
        wp->putname = websTempFile(ME_GOAHEAD_PUT_DIR, "put");
 | 
						|
        if ((wp->putfd = open(wp->putname, O_BINARY | O_WRONLY | O_CREAT | O_BINARY, 0644)) < 0) {
 | 
						|
            error("Cannot create PUT filename %s", wp->putname);
 | 
						|
            websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Cannot create the put URI");
 | 
						|
            wfree(wp->putname);
 | 
						|
            return 1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Parse the first line of a HTTP request
 | 
						|
 */
 | 
						|
static void parseFirstLine(Webs *wp)
 | 
						|
{
 | 
						|
    char    *op, *protoVer, *url, *host, *query, *path, *port, *ext, *buf;
 | 
						|
    int     listenPort;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    /*
 | 
						|
        Determine the request type: GET, HEAD or POST
 | 
						|
     */
 | 
						|
    op = getToken(wp, NULL, TOKEN_WORD);
 | 
						|
    if (op == NULL || *op == '\0') {
 | 
						|
        websError(wp, HTTP_CODE_NOT_FOUND | WEBS_CLOSE, "Bad HTTP request");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    wp->method = supper(sclone(op));
 | 
						|
 | 
						|
    url = getToken(wp, NULL, TOKEN_URI_VALUE);
 | 
						|
    if (url == NULL || *url == '\0') {
 | 
						|
        websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE, "Bad HTTP request");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    if (strlen(url) > ME_GOAHEAD_LIMIT_URI) {
 | 
						|
        websError(wp, HTTP_CODE_REQUEST_URL_TOO_LARGE | WEBS_CLOSE, "URI too big");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    protoVer = getToken(wp, "\r\n", TOKEN_WORD);
 | 
						|
    if (protoVer == NULL || *protoVer == '\0') {
 | 
						|
        websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE, "Bad HTTP request");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    if (websGetLogLevel() == 2) {
 | 
						|
        trace(2, "%s %s %s", wp->method, url, protoVer);
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
        Parse the URL and store all the various URL components. websUrlParse returns an allocated buffer in buf which we
 | 
						|
        must free. We support both proxied and non-proxied requests. Proxied requests will have http://host/ at the
 | 
						|
        start of the URL. Non-proxied will just be local path names.
 | 
						|
     */
 | 
						|
    host = path = port = query = ext = NULL;
 | 
						|
    if (websUrlParse(url, &buf, NULL, &host, &port, &path, NULL, NULL, &query) < 0) {
 | 
						|
        error("Cannot parse URL: %s", url);
 | 
						|
        websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE | WEBS_NOLOG, "Bad URL");
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    //  Decode the path and save. This includes the extension.
 | 
						|
    if ((wp->path = websValidateUriPath(path)) == 0) {
 | 
						|
        websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE | WEBS_NOLOG, "Bad URL");
 | 
						|
        wfree(buf);
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    wp->url = sclone(url);
 | 
						|
    if ((ext = strrchr(wp->path, '.')) != NULL) {
 | 
						|
        wp->ext = sclone(slower(ext));
 | 
						|
    }
 | 
						|
    wp->filename = sfmt("%s%s", websGetDocuments(), wp->path);
 | 
						|
    wp->query = sclone(query);
 | 
						|
    wp->host = sclone(host);
 | 
						|
    wp->protocol = wp->flags & WEBS_SECURE ? "https" : "http";
 | 
						|
    if (smatch(protoVer, "HTTP/1.1")) {
 | 
						|
        wp->flags |= WEBS_KEEP_ALIVE | WEBS_HTTP11;
 | 
						|
    } else if (smatch(protoVer, "HTTP/1.0")) {
 | 
						|
        wp->flags &= ~(WEBS_HTTP11);
 | 
						|
    } else {
 | 
						|
        protoVer = "HTTP/1.1";
 | 
						|
        websError(wp, WEBS_CLOSE | HTTP_CODE_NOT_ACCEPTABLE, "Unsupported HTTP protocol");
 | 
						|
    }
 | 
						|
    wp->protoVersion = sclone(protoVer);
 | 
						|
    if ((listenPort = socketGetPort(wp->listenSid)) >= 0) {
 | 
						|
        wp->port = listenPort;
 | 
						|
    } else {
 | 
						|
        wp->port = atoi(port);
 | 
						|
    }
 | 
						|
    wfree(buf);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Parse a full request
 | 
						|
 */
 | 
						|
static void parseHeaders(Webs *wp)
 | 
						|
{
 | 
						|
    cchar   *prior;
 | 
						|
    char    *combined, *upperKey, *cp, *key, *value, *tok;
 | 
						|
    int     count;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    /*
 | 
						|
        Parse the header and create the Http header keyword variables
 | 
						|
        We rewrite the header as we go for non-local requests.  NOTE: this
 | 
						|
        modifies the header string directly and tokenizes each line with '\0'.
 | 
						|
    */
 | 
						|
    for (count = 0; wp->rxbuf.servp[0] != '\r'; count++) {
 | 
						|
        if (count >= ME_GOAHEAD_LIMIT_NUM_HEADERS) {
 | 
						|
            websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Too many headers");
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        key = getToken(wp, ":", TOKEN_HEADER_KEY);
 | 
						|
        if (key == NULL || *key == '\0' || bufLen(&wp->rxbuf) == 0) {
 | 
						|
            websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE, "Bad header format");
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        value = getToken(wp, "\r\n", TOKEN_HEADER_VALUE);
 | 
						|
        if (value == NULL || bufLen(&wp->rxbuf) == 0 || wp->rxbuf.servp[0] == '\0') {
 | 
						|
            websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE, "Bad header format");
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        slower(key);
 | 
						|
 | 
						|
        /*
 | 
						|
            Create a header variable for each line in the header
 | 
						|
         */
 | 
						|
        upperKey = sfmt("HTTP_%s", key);
 | 
						|
        for (cp = upperKey; *cp; cp++) {
 | 
						|
            if (*cp == '-') {
 | 
						|
                *cp = '_';
 | 
						|
            }
 | 
						|
        }
 | 
						|
        supper(upperKey);
 | 
						|
        if ((prior = websGetVar(wp, upperKey, 0)) != 0) {
 | 
						|
            combined = sfmt("%s, %s", prior, value);
 | 
						|
            websSetVar(wp, upperKey, combined);
 | 
						|
            wfree(combined);
 | 
						|
        } else {
 | 
						|
            websSetVar(wp, upperKey, value);
 | 
						|
        }
 | 
						|
        wfree(upperKey);
 | 
						|
 | 
						|
        /*
 | 
						|
            Track the requesting agent (browser) type
 | 
						|
         */
 | 
						|
        if (strcmp(key, "user-agent") == 0) {
 | 
						|
            wfree(wp->userAgent);
 | 
						|
            wp->userAgent = sclone(value);
 | 
						|
 | 
						|
        } else if (scaselesscmp(key, "authorization") == 0) {
 | 
						|
            wfree(wp->authType);
 | 
						|
            wp->authType = sclone(value);
 | 
						|
            ssplit(wp->authType, " \t", &tok);
 | 
						|
            wfree(wp->authDetails);
 | 
						|
            wp->authDetails = sclone(tok);
 | 
						|
            slower(wp->authType);
 | 
						|
 | 
						|
        } else if (strcmp(key, "connection") == 0) {
 | 
						|
            slower(value);
 | 
						|
            if (strcmp(value, "keep-alive") == 0) {
 | 
						|
                wp->flags |= WEBS_KEEP_ALIVE;
 | 
						|
            } else if (strcmp(value, "close") == 0) {
 | 
						|
                wp->flags &= ~WEBS_KEEP_ALIVE;
 | 
						|
            }
 | 
						|
 | 
						|
        } else if (strcmp(key, "content-length") == 0) {
 | 
						|
            if ((wp->rxLen = atoi(value)) < 0) {
 | 
						|
                websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Invalid content length");
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            if (smatch(wp->method, "PUT")) {
 | 
						|
                if (wp->rxLen > ME_GOAHEAD_LIMIT_PUT) {
 | 
						|
                    websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Too big");
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                if (wp->rxLen > ME_GOAHEAD_LIMIT_POST) { // open source code.
 | 
						|
                    websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Too big");
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (!smatch(wp->method, "HEAD")) {
 | 
						|
                wp->rxRemaining = wp->rxLen;
 | 
						|
            }
 | 
						|
 | 
						|
        } else if (strcmp(key, "content-type") == 0) {
 | 
						|
            wfree(wp->contentType);
 | 
						|
            wp->contentType = sclone(value);
 | 
						|
            if (strstr(value, "application/x-www-form-urlencoded")) {
 | 
						|
                wp->flags |= WEBS_FORM;
 | 
						|
            } else if (strstr(value, "application/json")) {
 | 
						|
                wp->flags |= WEBS_JSON;
 | 
						|
            } else if (strstr(value, "multipart/form-data")) {
 | 
						|
                wp->flags |= WEBS_UPLOAD;
 | 
						|
            }
 | 
						|
 | 
						|
        } else if (strcmp(key, "cookie") == 0) {
 | 
						|
            /* Should be only one cookie header really with semicolon delimmited key/value pairs */
 | 
						|
            wp->flags |= WEBS_COOKIE;
 | 
						|
            if (wp->cookie) {
 | 
						|
                char *prior = wp->cookie;
 | 
						|
                wp->cookie = sfmt("%s; %s", prior, value);
 | 
						|
                wfree(prior);
 | 
						|
            } else {
 | 
						|
                wp->cookie = sclone(value);
 | 
						|
            }
 | 
						|
 | 
						|
        } else if (strcmp(key, "host") == 0) {
 | 
						|
            if ((int) strspn(value, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.[]:")
 | 
						|
                    < (int) slen(value)) {
 | 
						|
                websError(wp, WEBS_CLOSE | HTTP_CODE_BAD_REQUEST, "Bad host header");
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            wfree(wp->host);
 | 
						|
            wp->host = sclone(value);
 | 
						|
 | 
						|
        } else if (strcmp(key, "if-modified-since") == 0) {
 | 
						|
            if ((cp = strchr(value, ';')) != NULL) {
 | 
						|
                *cp = '\0';
 | 
						|
            }
 | 
						|
            websParseDateTime(&wp->since, value, 0);
 | 
						|
 | 
						|
        /*
 | 
						|
            Yes Veronica, the HTTP spec does misspell Referrer
 | 
						|
         */
 | 
						|
        } else if (strcmp(key, "referer") == 0) {
 | 
						|
            wfree(wp->referrer);
 | 
						|
            wp->referrer = sclone(value);
 | 
						|
 | 
						|
        } else if (strcmp(key, "transfer-encoding") == 0) {
 | 
						|
            if (scaselesscmp(value, "chunked") == 0) {
 | 
						|
                wp->rxChunkState = WEBS_CHUNK_START;
 | 
						|
                wp->rxRemaining = MAXINT;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if (!wp->rxChunkState) {
 | 
						|
        /*
 | 
						|
            Step over "\r\n" after headers.
 | 
						|
            Don't do this if chunked so that chunking can parse a single chunk delimiter of "\r\nSIZE ...\r\n"
 | 
						|
         */
 | 
						|
        if (bufLen(&wp->rxbuf) < 2 || wp->rxbuf.servp[0] != '\r' || wp->rxbuf.servp[1] != '\n') {
 | 
						|
            websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE, "Bad header termination");
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        wp->rxbuf.servp += 2;
 | 
						|
    }
 | 
						|
    wp->eof = (wp->rxRemaining == 0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static bool processContent(Webs *wp)
 | 
						|
{
 | 
						|
    bool    canProceed;
 | 
						|
 | 
						|
    if (!wp->eof) {
 | 
						|
        canProceed = filterChunkData(wp);
 | 
						|
        if (!canProceed || wp->finalized) {
 | 
						|
            return canProceed;
 | 
						|
        }
 | 
						|
#if ME_GOAHEAD_UPLOAD
 | 
						|
        if (wp->flags & WEBS_UPLOAD) {
 | 
						|
            canProceed = websProcessUploadData(wp);
 | 
						|
            if (!canProceed || wp->finalized) {
 | 
						|
                return canProceed;
 | 
						|
            }
 | 
						|
        }
 | 
						|
#endif
 | 
						|
#if !ME_ROM
 | 
						|
        if (wp->putfd >= 0) {
 | 
						|
            canProceed = websProcessPutData(wp);
 | 
						|
            if (!canProceed || wp->finalized) {
 | 
						|
                return canProceed;
 | 
						|
            }
 | 
						|
        }
 | 
						|
#endif
 | 
						|
#if ME_GOAHEAD_CGI
 | 
						|
        if (wp->cgifd >= 0) {
 | 
						|
            canProceed = websProcessCgiData(wp);
 | 
						|
            if (!canProceed || wp->finalized) {
 | 
						|
                return canProceed;
 | 
						|
            }
 | 
						|
        }
 | 
						|
#endif
 | 
						|
    }
 | 
						|
    if (wp->eof) {
 | 
						|
        wp->state = WEBS_READY;
 | 
						|
        /*
 | 
						|
            Prevent reading content from the next request
 | 
						|
            The handler may not have been created if all the content was read in the initial read. No matter.
 | 
						|
         */
 | 
						|
        socketDeleteHandler(wp->sid);
 | 
						|
        canProceed = 1;
 | 
						|
    }
 | 
						|
    return canProceed;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Always called after data is consumed from the input buffer
 | 
						|
 */
 | 
						|
PUBLIC void websConsumeInput(Webs *wp, ssize nbytes)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    assert(nbytes >= 0);
 | 
						|
 | 
						|
    assert(bufLen(&wp->input) >= nbytes);
 | 
						|
    if (nbytes <= 0) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    bufAdjustStart(&wp->input, nbytes);
 | 
						|
    if (bufLen(&wp->input) == 0) {
 | 
						|
        bufReset(&wp->input);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static bool filterChunkData(Webs *wp)
 | 
						|
{
 | 
						|
    WebsBuf     *rxbuf;
 | 
						|
    ssize       chunkSize;
 | 
						|
    char        *start, *cp;
 | 
						|
    ssize       len, nbytes;
 | 
						|
    int         bad;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(wp->rxbuf.buf);
 | 
						|
    rxbuf = &wp->rxbuf;
 | 
						|
 | 
						|
    while (bufLen(rxbuf) > 0) {
 | 
						|
        switch (wp->rxChunkState) {
 | 
						|
        case WEBS_CHUNK_UNCHUNKED:
 | 
						|
            len = min(wp->rxRemaining, bufLen(rxbuf));
 | 
						|
            bufPutBlk(&wp->input, rxbuf->servp, len);
 | 
						|
            bufAddNull(&wp->input);
 | 
						|
            bufAdjustStart(rxbuf, len);
 | 
						|
            bufCompact(rxbuf);
 | 
						|
            wp->rxRemaining -= len;
 | 
						|
            if (wp->rxRemaining <= 0) {
 | 
						|
                wp->eof = 1;
 | 
						|
            }
 | 
						|
            assert(wp->rxRemaining >= 0);
 | 
						|
            return 1;
 | 
						|
 | 
						|
        case WEBS_CHUNK_START:
 | 
						|
            /*
 | 
						|
                Expect: "\r\nSIZE.*\r\n"
 | 
						|
             */
 | 
						|
            if (bufLen(rxbuf) < 5) {
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            start = rxbuf->servp;
 | 
						|
            bad = (start[0] != '\r' || start[1] != '\n');
 | 
						|
            for (cp = &start[2]; cp < rxbuf->endp && *cp != '\n'; cp++) {}
 | 
						|
            if (*cp != '\n' && (cp - start) < 80) {
 | 
						|
                /* Insufficient data */
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            bad += (cp[-1] != '\r' || cp[0] != '\n');
 | 
						|
            if (bad) {
 | 
						|
                websError(wp, WEBS_CLOSE | HTTP_CODE_BAD_REQUEST, "Bad chunk specification");
 | 
						|
                return 1;
 | 
						|
            }
 | 
						|
            chunkSize = hextoi(&start[2]);
 | 
						|
            if (!isxdigit((uchar) start[2]) || chunkSize < 0) {
 | 
						|
                websError(wp, WEBS_CLOSE | HTTP_CODE_BAD_REQUEST, "Bad chunk specification");
 | 
						|
                return 1;
 | 
						|
            }
 | 
						|
            if (chunkSize == 0) {
 | 
						|
                /* On the last chunk, consume the final "\r\n" */
 | 
						|
                if ((cp + 2) >= rxbuf->endp) {
 | 
						|
                    /* Insufficient data */
 | 
						|
                    return 0;
 | 
						|
                }
 | 
						|
                cp += 2;
 | 
						|
                bad += (cp[-1] != '\r' || cp[0] != '\n');
 | 
						|
                if (bad) {
 | 
						|
                    websError(wp, WEBS_CLOSE | HTTP_CODE_BAD_REQUEST, "Bad final chunk specification");
 | 
						|
                    return 1;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            bufAdjustStart(rxbuf, cp - start + 1);
 | 
						|
            wp->rxChunkSize = chunkSize;
 | 
						|
            wp->rxRemaining = chunkSize;
 | 
						|
            if (chunkSize == 0) {
 | 
						|
#if ME_GOAHEAD_LEGACY
 | 
						|
                wfree(wp->query);
 | 
						|
                wp->query = sclone(bufStart(&wp->input));
 | 
						|
#endif
 | 
						|
                wp->eof = 1;
 | 
						|
                return 1;
 | 
						|
            }
 | 
						|
            trace(7, "chunkFilter: start incoming chunk of %d bytes", chunkSize);
 | 
						|
            wp->rxChunkState = WEBS_CHUNK_DATA;
 | 
						|
            break;
 | 
						|
 | 
						|
        case WEBS_CHUNK_DATA:
 | 
						|
            len = min(bufLen(rxbuf), wp->rxRemaining);
 | 
						|
            nbytes = min(bufRoom(&wp->input), len);
 | 
						|
            if (len > 0 && (nbytes = bufPutBlk(&wp->input, rxbuf->servp, nbytes)) == 0) {
 | 
						|
                websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Too big");
 | 
						|
                return 1;
 | 
						|
            }
 | 
						|
            bufAddNull(&wp->input);
 | 
						|
            bufAdjustStart(rxbuf, nbytes);
 | 
						|
            wp->rxRemaining -= nbytes;
 | 
						|
            if (wp->rxRemaining <= 0) {
 | 
						|
                wp->rxChunkState = WEBS_CHUNK_START;
 | 
						|
                bufCompact(rxbuf);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Basic event loop. SocketReady returns true when a socket is ready for service. SocketSelect will block until an
 | 
						|
    event occurs. SocketProcess will actually do the servicing.
 | 
						|
 */
 | 
						|
PUBLIC void websServiceEvents(int *finished)
 | 
						|
{
 | 
						|
    int     delay, nextEvent;
 | 
						|
 | 
						|
    if (finished) {
 | 
						|
        *finished = 0;
 | 
						|
    }
 | 
						|
    // ===================== added by xiao ===================== //
 | 
						|
    // delay = 0; // open source code
 | 
						|
    #define TIME_OUT_MS 1000
 | 
						|
    delay = TIME_OUT_MS; // modifed by xiao, time out 1000 ms
 | 
						|
    // ===================== added by xiao end ===================== //
 | 
						|
    while (!finished || !*finished) {
 | 
						|
        if (socketSelect(-1, delay)) {
 | 
						|
            socketProcess();
 | 
						|
        }
 | 
						|
#if ME_GOAHEAD_CGI
 | 
						|
        delay = websCgiPoll();
 | 
						|
#else
 | 
						|
        delay = MAXINT;
 | 
						|
#endif
 | 
						|
        nextEvent = websRunEvents();
 | 
						|
        delay = min(delay, nextEvent);
 | 
						|
    // ===================== added by xiao ===================== //
 | 
						|
        delay = (delay <= 0 || delay >= TIME_OUT_MS) ? TIME_OUT_MS : delay;
 | 
						|
    // ===================== added by xiao end ===================== //
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    NOTE: the vars variable is modified
 | 
						|
 */
 | 
						|
static void addFormVars(Webs *wp, char *vars)
 | 
						|
{
 | 
						|
    WebsKey     *sp;
 | 
						|
    cchar       *prior;
 | 
						|
    char        *keyword, *value, *tok;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(vars);
 | 
						|
 | 
						|
    keyword = stok(vars, "&", &tok);
 | 
						|
    while (keyword != NULL) {
 | 
						|
        if ((value = strchr(keyword, '=')) != NULL) {
 | 
						|
            *value++ = '\0';
 | 
						|
            websDecodeUrl(keyword, keyword, strlen(keyword));
 | 
						|
            websDecodeUrl(value, value, strlen(value));
 | 
						|
        } else {
 | 
						|
            value = "";
 | 
						|
        }
 | 
						|
        if (*keyword) {
 | 
						|
            /*
 | 
						|
                If keyword has already been set, append the new value to what has been stored.
 | 
						|
             */
 | 
						|
            if ((prior = websGetVar(wp, keyword, NULL)) != 0) {
 | 
						|
                sp = websSetVarFmt(wp, keyword, "%s %s", prior, value);
 | 
						|
            } else {
 | 
						|
                sp = websSetVar(wp, keyword, value);
 | 
						|
            }
 | 
						|
            /* Flag as untrusted keyword by setting arg to 1. This is used by CGI to prefix this keyword */
 | 
						|
            sp->arg = 1;
 | 
						|
        }
 | 
						|
        keyword = stok(NULL, "&", &tok);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Set the variable (CGI) environment for this request. Create variables for all standard CGI variables. Also decode
 | 
						|
    the query string and create a variable for each name=value pair.
 | 
						|
 */
 | 
						|
PUBLIC void websSetEnv(Webs *wp)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    websSetVar(wp, "AUTH_TYPE", wp->authType);
 | 
						|
    websSetVarFmt(wp, "CONTENT_LENGTH", "%d", wp->rxLen);
 | 
						|
    websSetVar(wp, "CONTENT_TYPE", wp->contentType);
 | 
						|
    if (wp->route && wp->route->dir) {
 | 
						|
        websSetVar(wp, "DOCUMENT_ROOT", wp->route->dir);
 | 
						|
    }
 | 
						|
    websSetVar(wp, "GATEWAY_INTERFACE", "CGI/1.1");
 | 
						|
    websSetVar(wp, "PATH_INFO", wp->path);
 | 
						|
    websSetVar(wp, "PATH_TRANSLATED", wp->filename);
 | 
						|
    websSetVar(wp, "QUERY_STRING", wp->query);
 | 
						|
    websSetVar(wp, "REMOTE_ADDR", wp->ipaddr);
 | 
						|
    websSetVar(wp, "REMOTE_USER", wp->username);
 | 
						|
    websSetVar(wp, "REMOTE_HOST", wp->ipaddr);
 | 
						|
    websSetVar(wp, "REQUEST_METHOD", wp->method);
 | 
						|
    websSetVar(wp, "REQUEST_TRANSPORT", wp->protocol);
 | 
						|
    websSetVar(wp, "REQUEST_URI", wp->path);
 | 
						|
    websSetVar(wp, "SERVER_ADDR", wp->ifaddr);
 | 
						|
    websSetVar(wp, "SERVER_HOST", websHost);
 | 
						|
    websSetVar(wp, "SERVER_NAME", websHost);
 | 
						|
    websSetVarFmt(wp, "SERVER_PORT", "%d", wp->port);
 | 
						|
    websSetVar(wp, "SERVER_PROTOCOL", wp->protoVersion);
 | 
						|
    websSetVar(wp, "SERVER_URL", websHostUrl);
 | 
						|
    websSetVarFmt(wp, "SERVER_SOFTWARE", "GoAhead/%s", ME_VERSION);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetFormVars(Webs *wp)
 | 
						|
{
 | 
						|
    char    *data;
 | 
						|
 | 
						|
    if (wp->rxLen > 0 && bufLen(&wp->input) > 0) {
 | 
						|
        if (wp->flags & WEBS_FORM) {
 | 
						|
            data = sclone(wp->input.servp);
 | 
						|
            addFormVars(wp, data);
 | 
						|
            wfree(data);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetQueryVars(Webs *wp)
 | 
						|
{
 | 
						|
    /*
 | 
						|
        Decode and create an environment query variable for each query keyword. We split into pairs at each '&', then
 | 
						|
        split pairs at the '='.  Note: we rely on wp->decodedQuery preserving the decoded values in the symbol table.
 | 
						|
     */
 | 
						|
    if (wp->query && *wp->query) {
 | 
						|
        wfree(wp->decodedQuery);
 | 
						|
        wp->decodedQuery = sclone(wp->query);
 | 
						|
        addFormVars(wp, wp->decodedQuery);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Define a webs (CGI) variable for this connection. Also create in relevant scripting engines. Note: the incoming
 | 
						|
    value may be volatile.
 | 
						|
 */
 | 
						|
PUBLIC WebsKey *websSetVarFmt(Webs *wp, cchar *var, cchar *fmt, ...)
 | 
						|
{
 | 
						|
    WebsValue   v;
 | 
						|
    va_list     args;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(var && *var);
 | 
						|
 | 
						|
    if (fmt) {
 | 
						|
        va_start(args, fmt);
 | 
						|
        v = valueString(sfmtv(fmt, args), 0);
 | 
						|
        v.allocated = 1;
 | 
						|
        va_end(args);
 | 
						|
    } else {
 | 
						|
        v = valueString("", 0);
 | 
						|
    }
 | 
						|
    return hashEnter(wp->vars, var, v, 0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC WebsKey *websSetVar(Webs *wp, cchar *var, cchar *value)
 | 
						|
{
 | 
						|
    WebsValue   v;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(var && *var);
 | 
						|
 | 
						|
    if (value) {
 | 
						|
        v = valueString(value, VALUE_ALLOCATE);
 | 
						|
    } else {
 | 
						|
        v = valueString("", 0);
 | 
						|
    }
 | 
						|
    return hashEnter(wp->vars, var, v, 0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Return TRUE if a webs variable exists for this connection.
 | 
						|
 */
 | 
						|
PUBLIC bool websTestVar(Webs *wp, cchar *var)
 | 
						|
{
 | 
						|
    WebsKey       *sp;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(var && *var);
 | 
						|
 | 
						|
    if (var == NULL || *var == '\0') {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    if ((sp = hashLookup(wp->vars, var)) == NULL) {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Get a webs variable but return a default value if string not found.  Note, defaultGetValue can be NULL to permit
 | 
						|
    testing existence.
 | 
						|
 */
 | 
						|
PUBLIC cchar *websGetVar(Webs *wp, cchar *var, cchar *defaultGetValue)
 | 
						|
{
 | 
						|
    WebsKey   *sp;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(var && *var);
 | 
						|
 | 
						|
    if ((sp = hashLookup(wp->vars, var)) != NULL) {
 | 
						|
        assert(sp->content.type == string);
 | 
						|
        if (sp->content.value.string) {
 | 
						|
            return sp->content.value.string;
 | 
						|
        } else {
 | 
						|
            return "";
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return defaultGetValue;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Return TRUE if a webs variable is set to a given value
 | 
						|
 */
 | 
						|
PUBLIC int websCompareVar(Webs *wp, cchar *var, cchar *value)
 | 
						|
{
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(var && *var);
 | 
						|
 | 
						|
    if (strcmp(value, websGetVar(wp, var, " __UNDEF__ ")) == 0) {
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Cancel the request timeout. Note may be called multiple times.
 | 
						|
 */
 | 
						|
PUBLIC void websCancelTimeout(Webs *wp)
 | 
						|
{
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    if (wp->timeout >= 0) {
 | 
						|
        websStopEvent(wp->timeout);
 | 
						|
        wp->timeout = -1;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Output a HTTP response back to the browser. If redirect is set to a URL, the browser will be sent to this location.
 | 
						|
 */
 | 
						|
PUBLIC void websResponse(Webs *wp, int code, cchar *message)
 | 
						|
{
 | 
						|
    ssize   len;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    websSetStatus(wp, code);
 | 
						|
 | 
						|
    if (!smatch(wp->method, "HEAD") && message && *message) {
 | 
						|
        len = slen(message);
 | 
						|
        websWriteHeaders(wp, len + 2, 0);
 | 
						|
        websWriteEndHeaders(wp);
 | 
						|
        websWriteBlock(wp, message, len);
 | 
						|
        websWriteBlock(wp, "\r\n", 2);
 | 
						|
    } else {
 | 
						|
        websWriteHeaders(wp, 0, 0);
 | 
						|
        websWriteEndHeaders(wp);
 | 
						|
    }
 | 
						|
    websDone(wp);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static char *makeUri(cchar *scheme, cchar *host, int port, cchar *path)
 | 
						|
{
 | 
						|
    if (port <= 0) {
 | 
						|
        port = smatch(scheme, "https") ? defaultSslPort : defaultHttpPort;
 | 
						|
    }
 | 
						|
    if (port == 80 || port == 443) {
 | 
						|
        return sfmt("%s://%s%s", scheme, host, path);
 | 
						|
    }
 | 
						|
    return sfmt("%s://%s:%d%s", scheme, host, port, path);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Redirect the user to another webs page
 | 
						|
 */
 | 
						|
PUBLIC void websRedirect(Webs *wp, cchar *uri)
 | 
						|
{
 | 
						|
    char    *encoded, *message, *location, *scheme, *host, *pstr;
 | 
						|
    bool    secure, fullyQualified;
 | 
						|
    ssize   len;
 | 
						|
    int     originalPort, port;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(uri);
 | 
						|
    message = location = NULL;
 | 
						|
    originalPort = port = 0;
 | 
						|
 | 
						|
    //  This has risk of host header injection.
 | 
						|
    //  If enabled, must also free host and MUST sanitize wp->host which comes from the host header.
 | 
						|
    //  host = sclone(wp->host ? wp->host : websHostUrl);
 | 
						|
 | 
						|
    host = websHostUrl;
 | 
						|
    pstr = strchr(host, ']');
 | 
						|
    pstr = pstr ? pstr : host;
 | 
						|
    if ((pstr = strchr(pstr, ':')) != 0) {
 | 
						|
        *pstr++ = '\0';
 | 
						|
        originalPort = atoi(pstr);
 | 
						|
    }
 | 
						|
    if (smatch(uri, "http://") || smatch(uri, "https://")) {
 | 
						|
        /* Protocol switch with existing Uri */
 | 
						|
        scheme = sncmp(uri, "https", 5) == 0 ? "https" : "http";
 | 
						|
        uri = location = makeUri(scheme, host, 0, wp->url);
 | 
						|
    }
 | 
						|
    secure = strstr(uri, "https://") != 0;
 | 
						|
    fullyQualified = strstr(uri, "http://") || strstr(uri, "https://");
 | 
						|
    if (!fullyQualified) {
 | 
						|
        port = originalPort;
 | 
						|
        if (wp->flags & WEBS_SECURE) {
 | 
						|
            secure = 1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    scheme = secure ? "https" : "http";
 | 
						|
    if (port <= 0) {
 | 
						|
        port = secure ? defaultSslPort : defaultHttpPort;
 | 
						|
    }
 | 
						|
    if (strstr(uri, "https:///")) {
 | 
						|
        /* Short-hand for redirect to https */
 | 
						|
        uri = location = makeUri(scheme, host, port, &uri[8]);
 | 
						|
 | 
						|
    } else if (strstr(uri, "http:///")) {
 | 
						|
        uri = location = makeUri(scheme, host, port, &uri[7]);
 | 
						|
 | 
						|
    } else if (!fullyQualified) {
 | 
						|
        uri = location = makeUri(scheme, host, port, uri);
 | 
						|
    }
 | 
						|
    encoded = websEscapeUri(uri);
 | 
						|
    message = sfmt("<html><head></head><body>\r\n\
 | 
						|
        This document has moved to a new <a href=\"%s\">location</a>.\r\n\
 | 
						|
        Please update your documents to reflect the new location.\r\n\
 | 
						|
        </body></html>\r\n", encoded);
 | 
						|
    len = slen(message);
 | 
						|
    websSetStatus(wp, HTTP_CODE_MOVED_TEMPORARILY);
 | 
						|
    websWriteHeaders(wp, len + 2, uri);
 | 
						|
    websWriteEndHeaders(wp);
 | 
						|
    websWriteBlock(wp, message, len);
 | 
						|
    websWriteBlock(wp, "\r\n", 2);
 | 
						|
    websDone(wp);
 | 
						|
    wfree(message);
 | 
						|
    wfree(location);
 | 
						|
    wfree(encoded);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websRedirectByStatus(Webs *wp, int status)
 | 
						|
{
 | 
						|
    WebsKey     *key;
 | 
						|
    char        code[16], *uri;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(status >= 0);
 | 
						|
 | 
						|
    if (wp->route && wp->route->redirects >= 0) {
 | 
						|
        itosbuf(code, sizeof(code), status, 10);
 | 
						|
        if ((key = hashLookup(wp->route->redirects, code)) != 0) {
 | 
						|
            uri = key->content.value.string;
 | 
						|
        } else {
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
        websRedirect(wp, uri);
 | 
						|
    } else {
 | 
						|
        if (status == HTTP_CODE_UNAUTHORIZED) {
 | 
						|
            websError(wp, status, "Access Denied. User not logged in.");
 | 
						|
        } else {
 | 
						|
            websError(wp, status, 0);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Escape HTML to escape defined characters (prevent cross-site scripting)
 | 
						|
    Returns an allocated string.
 | 
						|
 */
 | 
						|
PUBLIC char *websEscapeHtml(cchar *html)
 | 
						|
{
 | 
						|
    cchar   *ip;
 | 
						|
    char    *result, *op;
 | 
						|
    int     len;
 | 
						|
 | 
						|
    if (!html) {
 | 
						|
        return sclone("");
 | 
						|
    }
 | 
						|
    for (len = 1, ip = html; *ip; ip++, len++) {
 | 
						|
        if (charMatch[(int) (uchar) *ip] & WEBS_ENCODE_HTML) {
 | 
						|
            len += 5;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if ((result = walloc(len)) == 0) {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    /*
 | 
						|
        Leave room for the biggest expansion
 | 
						|
     */
 | 
						|
    op = result;
 | 
						|
    while (*html != '\0') {
 | 
						|
        if (charMatch[(uchar) *html] & WEBS_ENCODE_HTML) {
 | 
						|
            if (*html == '&') {
 | 
						|
                strcpy(op, "&");
 | 
						|
                op += 5;
 | 
						|
            } else if (*html == '<') {
 | 
						|
                strcpy(op, "<");
 | 
						|
                op += 4;
 | 
						|
            } else if (*html == '>') {
 | 
						|
                strcpy(op, ">");
 | 
						|
                op += 4;
 | 
						|
            } else if (*html == '#') {
 | 
						|
                strcpy(op, "#");
 | 
						|
                op += 5;
 | 
						|
            } else if (*html == '(') {
 | 
						|
                strcpy(op, "(");
 | 
						|
                op += 5;
 | 
						|
            } else if (*html == ')') {
 | 
						|
                strcpy(op, ")");
 | 
						|
                op += 5;
 | 
						|
            } else if (*html == '"') {
 | 
						|
                strcpy(op, """);
 | 
						|
                op += 6;
 | 
						|
            } else if (*html == '\'') {
 | 
						|
                strcpy(op, "'");
 | 
						|
                op += 5;
 | 
						|
            } else {
 | 
						|
                assert(0);
 | 
						|
            }
 | 
						|
            html++;
 | 
						|
        } else {
 | 
						|
            *op++ = *html++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    assert(op < &result[len]);
 | 
						|
    *op = '\0';
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
    Uri encode by encoding special characters with hex equivalents. Return an allocated string.
 | 
						|
 */
 | 
						|
PUBLIC char *websEscapeUri(cchar *uri)
 | 
						|
{
 | 
						|
    static cchar    hexTable[] = "0123456789ABCDEF";
 | 
						|
    uchar           c;
 | 
						|
    cchar           *ip;
 | 
						|
    char            *result, *op;
 | 
						|
    int             len;
 | 
						|
 | 
						|
    assert(uri);
 | 
						|
 | 
						|
    if (!uri) {
 | 
						|
        return sclone("");
 | 
						|
    }
 | 
						|
    for (len = 1, ip = uri; *ip; ip++, len++) {
 | 
						|
        if (charMatch[(uchar) *ip] & WEBS_ENCODE_URI) {
 | 
						|
            len += 2;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if ((result = walloc(len)) == 0) {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    op = result;
 | 
						|
 | 
						|
    while ((c = (uchar) (*uri++)) != 0) {
 | 
						|
        if (charMatch[c] & WEBS_ENCODE_URI) {
 | 
						|
            *op++ = '%';
 | 
						|
            *op++ = hexTable[c >> 4];
 | 
						|
            *op++ = hexTable[c & 0xf];
 | 
						|
        } else {
 | 
						|
            *op++ = c;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    assert(op < &result[len]);
 | 
						|
    *op = '\0';
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websWriteHeader(Webs *wp, cchar *key, cchar *fmt, ...)
 | 
						|
{
 | 
						|
    va_list     vargs;
 | 
						|
    char        *buf;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    if (!(wp->flags & WEBS_RESPONSE_TRACED)) {
 | 
						|
        wp->flags |= WEBS_RESPONSE_TRACED;
 | 
						|
        trace(3 | WEBS_RAW_MSG, "\n>>> Response\n");
 | 
						|
    }
 | 
						|
    if (key) {
 | 
						|
        if (websWriteBlock(wp, key, strlen(key)) < 0) {
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
        if (websWriteBlock(wp, ": ", 2) < 0) {
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
        trace(3 | WEBS_RAW_MSG, "%s: ", key);
 | 
						|
    }
 | 
						|
    if (fmt) {
 | 
						|
        va_start(vargs, fmt);
 | 
						|
        if ((buf = sfmtv(fmt, vargs)) == 0) {
 | 
						|
            error("websWrite lost data, buffer overflow");
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
        va_end(vargs);
 | 
						|
        trace(3 | WEBS_RAW_MSG, "%s", buf);
 | 
						|
        if (websWriteBlock(wp, buf, strlen(buf)) < 0) {
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
        wfree(buf);
 | 
						|
        if (websWriteBlock(wp, "\r\n", 2) != 2) {
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    trace(3 | WEBS_RAW_MSG, "\r\n");
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetStatus(Webs *wp, int code)
 | 
						|
{
 | 
						|
    wp->code = (code & WEBS_CODE_MASK);
 | 
						|
    if (code & WEBS_CLOSE) {
 | 
						|
        wp->flags &= ~WEBS_KEEP_ALIVE;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Write a set of headers. Does not write the trailing blank line so callers can add more headers.
 | 
						|
    Set length to -1 if unknown and transfer-chunk-encoding will be employed.
 | 
						|
 */
 | 
						|
PUBLIC void websWriteHeaders(Webs *wp, ssize length, cchar *location)
 | 
						|
{
 | 
						|
    WebsKey     *cookie, *key, *next;
 | 
						|
    char        *date, *protoVersion;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    if (!(wp->flags & WEBS_HEADERS_CREATED)) {
 | 
						|
        protoVersion = wp->protoVersion;
 | 
						|
        if (!protoVersion) {
 | 
						|
            protoVersion = "HTTP/1.0";
 | 
						|
            wp->flags &= ~WEBS_KEEP_ALIVE;
 | 
						|
        }
 | 
						|
        websWriteHeader(wp, NULL, "%s %d %s", protoVersion, wp->code, websErrorMsg(wp->code));
 | 
						|
#if !ME_GOAHEAD_STEALTH
 | 
						|
        websWriteHeader(wp, "Server", "GoAhead-http");
 | 
						|
#endif
 | 
						|
        if ((date = websGetDateString(NULL)) != NULL) {
 | 
						|
            websWriteHeader(wp, "Date", "%s", date);
 | 
						|
            wfree(date);
 | 
						|
        }
 | 
						|
        if (wp->authResponse) {
 | 
						|
            websWriteHeader(wp, "WWW-Authenticate", "%s", wp->authResponse);
 | 
						|
        }
 | 
						|
        if (length >= 0) {
 | 
						|
            if (smatch(wp->method, "HEAD")) {
 | 
						|
                websWriteHeader(wp, "Content-Length", "%d", (int) length);
 | 
						|
            } else if (!((100 <= wp->code && wp->code <= 199) || wp->code == 204 || wp->code == 304)) {
 | 
						|
                /* Server must not emit a content length header for 1XX, 204 and 304 status */
 | 
						|
                websWriteHeader(wp, "Content-Length", "%d", (int) length);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        wp->txLen = length;
 | 
						|
        if (wp->txLen < 0) {
 | 
						|
            websWriteHeader(wp, "Transfer-Encoding", "chunked");
 | 
						|
        }
 | 
						|
        if (wp->flags & WEBS_KEEP_ALIVE) {
 | 
						|
            websWriteHeader(wp, "Connection", "keep-alive");
 | 
						|
        } else {
 | 
						|
            websWriteHeader(wp, "Connection", "close");
 | 
						|
        }
 | 
						|
        if (location) {
 | 
						|
            websWriteHeader(wp, "Location", "%s", location);
 | 
						|
        } else if ((key = hashLookup(websMime, wp->ext)) != 0) {
 | 
						|
            websWriteHeader(wp, "Content-Type", "%s", key->content.value.string);
 | 
						|
        }
 | 
						|
        for (cookie = hashFirst(wp->responseCookies); cookie; cookie = next) {
 | 
						|
            websWriteHeader(wp, "Set-Cookie", "%s", cookie->content.value.string);
 | 
						|
            websWriteHeader(wp, "Cache-Control", "%s", "no-cache=\"set-cookie\"");
 | 
						|
            next = hashNext(wp->responseCookies, cookie);
 | 
						|
        }
 | 
						|
#if defined(ME_GOAHEAD_CLIENT_CACHE)
 | 
						|
        if (wp->ext) {
 | 
						|
            char *etok = sfmt("%s,", &wp->ext[1]);
 | 
						|
            if (strstr(ME_GOAHEAD_CLIENT_CACHE ",", etok)) {
 | 
						|
                websWriteHeader(wp, "Cache-Control", "public, max-age=%d", ME_GOAHEAD_CLIENT_CACHE_LIFESPAN);
 | 
						|
            }
 | 
						|
            wfree(etok);
 | 
						|
        }
 | 
						|
#endif
 | 
						|
#ifdef ME_GOAHEAD_XFRAME_HEADER
 | 
						|
        if (*ME_GOAHEAD_XFRAME_HEADER) {
 | 
						|
            websWriteHeader(wp, "X-Frame-Options", "%s", ME_GOAHEAD_XFRAME_HEADER);
 | 
						|
        }
 | 
						|
#endif
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websWriteEndHeaders(Webs *wp)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    /*
 | 
						|
        By omitting the "\r\n" delimiter after the headers, chunks can emit "\r\nSize\r\n" as a single chunk delimiter
 | 
						|
     */
 | 
						|
    if (wp->txLen >= 0) {
 | 
						|
        websWriteBlock(wp, "\r\n", 2);
 | 
						|
    }
 | 
						|
    wp->flags |= WEBS_HEADERS_CREATED;
 | 
						|
    if (wp->txLen < 0) {
 | 
						|
        wp->flags |= WEBS_CHUNKING;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetTxLength(Webs *wp, ssize length)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
    wp->txLen = length;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Do formatted output to the browser. This is the public Javascript and form write procedure.
 | 
						|
 */
 | 
						|
PUBLIC ssize websWrite(Webs *wp, cchar *fmt, ...)
 | 
						|
{
 | 
						|
    va_list     vargs;
 | 
						|
    char        *buf;
 | 
						|
    ssize       rc;
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(fmt && *fmt);
 | 
						|
 | 
						|
    va_start(vargs, fmt);
 | 
						|
 | 
						|
    buf = NULL;
 | 
						|
    rc = 0;
 | 
						|
    if ((buf = sfmtv(fmt, vargs)) == 0) {
 | 
						|
        error("websWrite lost data, buffer overflow");
 | 
						|
    }
 | 
						|
    va_end(vargs);
 | 
						|
    assert(buf);
 | 
						|
    if (buf) {
 | 
						|
        rc = websWriteBlock(wp, buf, strlen(buf));
 | 
						|
        wfree(buf);
 | 
						|
    }
 | 
						|
    return rc;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Non-blocking write to socket.
 | 
						|
    Returns number of bytes written. Returns -1 on errors. May return short.
 | 
						|
 */
 | 
						|
PUBLIC ssize websWriteSocket(Webs *wp, cchar *buf, ssize size)
 | 
						|
{
 | 
						|
    ssize   written;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(buf);
 | 
						|
    assert(size >= 0);
 | 
						|
 | 
						|
    if (wp->flags & WEBS_CLOSED) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
#if ME_COM_SSL
 | 
						|
    if (wp->flags & WEBS_SECURE) {
 | 
						|
        if ((written = sslWrite(wp, (void*) buf, size)) < 0) {
 | 
						|
            return written;
 | 
						|
        }
 | 
						|
    } else
 | 
						|
#endif
 | 
						|
    if ((written = socketWrite(wp->sid, (void*) buf, size)) < 0) {
 | 
						|
        return written;
 | 
						|
    }
 | 
						|
    wp->written += written;
 | 
						|
    websNoteRequestActivity(wp);
 | 
						|
    return written;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Write some output using transfer chunk encoding if required.
 | 
						|
    Returns true if all the data was written. Otherwise return zero.
 | 
						|
 */
 | 
						|
static bool flushChunkData(Webs *wp)
 | 
						|
{
 | 
						|
    ssize   len, written, room;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    while (bufLen(&wp->chunkbuf) > 0) {
 | 
						|
        /*
 | 
						|
            Stop if there is not room for a reasonable size chunk.
 | 
						|
            Subtract 16 to allow for the final trailer.
 | 
						|
         */
 | 
						|
        if ((room = bufRoom(&wp->output) - 16) <= CHUNK_LOW) {
 | 
						|
            bufGrow(&wp->output, CHUNK_LOW - room + 1);
 | 
						|
            if ((room = bufRoom(&wp->output) - 16) <= CHUNK_LOW) {
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        switch (wp->txChunkState) {
 | 
						|
        default:
 | 
						|
        case WEBS_CHUNK_START:
 | 
						|
            /* Select the chunk size so that both the prefix and data will fit */
 | 
						|
            wp->txChunkLen = min(bufLen(&wp->chunkbuf), room - 16);
 | 
						|
            fmt(wp->txChunkPrefix, sizeof(wp->txChunkPrefix), "\r\n%x\r\n", wp->txChunkLen);
 | 
						|
            wp->txChunkPrefixLen = slen(wp->txChunkPrefix);
 | 
						|
            wp->txChunkPrefixNext = wp->txChunkPrefix;
 | 
						|
            wp->txChunkState = WEBS_CHUNK_HEADER;
 | 
						|
            break;
 | 
						|
 | 
						|
        case WEBS_CHUNK_HEADER:
 | 
						|
            if ((written = bufPutBlk(&wp->output, wp->txChunkPrefixNext, wp->txChunkPrefixLen)) < 0) {
 | 
						|
                return 0;
 | 
						|
            } else {
 | 
						|
                wp->txChunkPrefixNext += written;
 | 
						|
                wp->txChunkPrefixLen -= written;
 | 
						|
                if (wp->txChunkPrefixLen <= 0) {
 | 
						|
                    wp->txChunkState = WEBS_CHUNK_DATA;
 | 
						|
                } else {
 | 
						|
                    return 0;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
 | 
						|
        case WEBS_CHUNK_DATA:
 | 
						|
            if (wp->txChunkLen > 0) {
 | 
						|
                len = min(room, wp->txChunkLen);
 | 
						|
                if ((written = bufPutBlk(&wp->output, wp->chunkbuf.servp, len)) != len) {
 | 
						|
                    assert(0);
 | 
						|
                    return -1;
 | 
						|
                }
 | 
						|
                bufAdjustStart(&wp->chunkbuf, written);
 | 
						|
                wp->txChunkLen -= written;
 | 
						|
                if (wp->txChunkLen <= 0) {
 | 
						|
                    wp->txChunkState = WEBS_CHUNK_START;
 | 
						|
                    bufCompact(&wp->chunkbuf);
 | 
						|
                }
 | 
						|
                bufAddNull(&wp->output);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return bufLen(&wp->chunkbuf) == 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Initiate flushing output buffer. Returns true if all data is written to the socket and the buffer is empty.
 | 
						|
    Returns <  0 for errors
 | 
						|
            == 0 if there is output remaining to be flushed
 | 
						|
            == 1 if the output was fully written to the socket
 | 
						|
 */
 | 
						|
PUBLIC int websFlush(Webs *wp, bool block)
 | 
						|
{
 | 
						|
    WebsBuf     *op;
 | 
						|
    ssize       nbytes, written;
 | 
						|
    int         errCode, wasBlocking;
 | 
						|
 | 
						|
    if (block) {
 | 
						|
        wasBlocking = socketSetBlock(wp->sid, 1);
 | 
						|
    }
 | 
						|
    op = &wp->output;
 | 
						|
    if (wp->flags & WEBS_CHUNKING) {
 | 
						|
        trace(6, "websFlush chunking finalized %d", wp->finalized);
 | 
						|
        if (flushChunkData(wp) && wp->finalized) {
 | 
						|
            trace(6, "websFlush: write chunk trailer");
 | 
						|
            bufPutStr(op, "\r\n0\r\n\r\n");
 | 
						|
            bufAddNull(op);
 | 
						|
            wp->flags &= ~WEBS_CHUNKING;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    trace(6, "websFlush: buflen %d", bufLen(op));
 | 
						|
    written = 0;
 | 
						|
    while ((nbytes = bufLen(op)) > 0) {
 | 
						|
        if ((written = websWriteSocket(wp, op->servp, nbytes)) < 0) {
 | 
						|
            errCode = socketGetError(wp->sid);
 | 
						|
            if (errCode == EWOULDBLOCK || errCode == EAGAIN) {
 | 
						|
                /* Not an error */
 | 
						|
                written = 0;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            /*
 | 
						|
                Connection Error
 | 
						|
             */
 | 
						|
            wp->flags &= ~WEBS_KEEP_ALIVE;
 | 
						|
            bufFlush(op);
 | 
						|
            wp->state = WEBS_COMPLETE;
 | 
						|
            break;
 | 
						|
        } else if (written == 0) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        trace(6, "websFlush: wrote %d to socket", written);
 | 
						|
        bufAdjustStart(op, written);
 | 
						|
        bufCompact(op);
 | 
						|
        nbytes = bufLen(op);
 | 
						|
    }
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    if (bufLen(op) == 0 && wp->finalized) {
 | 
						|
        wp->state = WEBS_COMPLETE;
 | 
						|
    }
 | 
						|
    if (block) {
 | 
						|
        socketSetBlock(wp->sid, wasBlocking);
 | 
						|
    }
 | 
						|
    if (written < 0) {
 | 
						|
        /* I/O Error */
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    return bufLen(op) == 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Respond to a writable event. First write any tx buffer by calling websFlush.
 | 
						|
    Then write body data if writeProc is defined. If all written, ensure transition to complete state.
 | 
						|
    Calls websPump() to advance state.
 | 
						|
 */
 | 
						|
static void writeEvent(Webs *wp)
 | 
						|
{
 | 
						|
    WebsBuf     *op;
 | 
						|
 | 
						|
    op = &wp->output;
 | 
						|
    if (bufLen(op) > 0) {
 | 
						|
        websFlush(wp, 0);
 | 
						|
    }
 | 
						|
    if (bufLen(op) == 0 && wp->writeData) {
 | 
						|
        (wp->writeData)(wp);
 | 
						|
    }
 | 
						|
    if (wp->state != WEBS_RUNNING) {
 | 
						|
        websPump(wp);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetBackgroundWriter(Webs *wp, WebsWriteProc proc)
 | 
						|
{
 | 
						|
    WebsSocket  *sp;
 | 
						|
    WebsBuf     *op;
 | 
						|
 | 
						|
    assert(proc);
 | 
						|
 | 
						|
    wp->writeData = proc;
 | 
						|
    op = &wp->output;
 | 
						|
 | 
						|
    if (bufLen(op) > 0) {
 | 
						|
        websFlush(wp, 0);
 | 
						|
    }
 | 
						|
    if (bufLen(op) == 0) {
 | 
						|
        (wp->writeData)(wp);
 | 
						|
    }
 | 
						|
    if (wp->sid >= 0 && wp->state < WEBS_COMPLETE) {
 | 
						|
        sp = socketPtr(wp->sid);
 | 
						|
        socketCreateHandler(wp->sid, sp->handlerMask | SOCKET_WRITABLE, socketEvent, wp);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Write a block of data of length to the user's browser. Output is buffered and flushed via websFlush.
 | 
						|
    This routine will never return "short". i.e. it will return the requested size to write or -1.
 | 
						|
    Buffer data. Will flush as required. May return -1 on write errors.
 | 
						|
 */
 | 
						|
PUBLIC ssize websWriteBlock(Webs *wp, cchar *buf, ssize size)
 | 
						|
{
 | 
						|
    WebsBuf     *op;
 | 
						|
    ssize       written, thisWrite, len, room;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(buf);
 | 
						|
    assert(size >= 0);
 | 
						|
 | 
						|
    if (wp->state >= WEBS_COMPLETE) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    op = (wp->flags & WEBS_CHUNKING) ? &wp->chunkbuf : &wp->output;
 | 
						|
    written = len = 0;
 | 
						|
 | 
						|
    while (size > 0 && wp->state < WEBS_COMPLETE) {
 | 
						|
        if (bufRoom(op) < size) {
 | 
						|
            /*
 | 
						|
                This will do a blocking I/O write. Will only ever fail for I/O errors.
 | 
						|
             */
 | 
						|
            if (websFlush(wp, 1) < 0) {
 | 
						|
                return -1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if ((room = bufRoom(op)) == 0) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        thisWrite = min(room, size);
 | 
						|
        bufPutBlk(op, buf, thisWrite);
 | 
						|
        size -= thisWrite;
 | 
						|
        buf += thisWrite;
 | 
						|
        written += thisWrite;
 | 
						|
    }
 | 
						|
    bufAddNull(op);
 | 
						|
    if (wp->state >= WEBS_COMPLETE && written == 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    return written;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Decode a URL (or part thereof). Allows insitu decoding.
 | 
						|
 */
 | 
						|
PUBLIC void websDecodeUrl(char *decoded, char *input, ssize len)
 | 
						|
{
 | 
						|
    char    *ip,  *op;
 | 
						|
    int     num, i, c;
 | 
						|
 | 
						|
    assert(decoded);
 | 
						|
    assert(input);
 | 
						|
 | 
						|
    if (!decoded) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    if (!input) {
 | 
						|
        *decoded = '\0';
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    if (len < 0) {
 | 
						|
        len = strlen(input);
 | 
						|
    }
 | 
						|
    op = decoded;
 | 
						|
    for (ip = input; *ip && len > 0; ip++, op++) {
 | 
						|
        if (*ip == '+') {
 | 
						|
            *op = ' ';
 | 
						|
        } else if (*ip == '%' && isxdigit((uchar) ip[1]) && isxdigit((uchar) ip[2]) &&
 | 
						|
                  !(ip[1] == '0' && ip[2] == '0')) {
 | 
						|
            /*
 | 
						|
                Convert %nn to a single character
 | 
						|
             */
 | 
						|
            ip++;
 | 
						|
            for (i = 0, num = 0; i < 2; i++, ip++) {
 | 
						|
                c = tolower((uchar) *ip);
 | 
						|
                if (c >= 'a' && c <= 'f') {
 | 
						|
                    num = (num * 16) + 10 + c - 'a';
 | 
						|
                } else {
 | 
						|
                    num = (num * 16) + c - '0';
 | 
						|
                }
 | 
						|
            }
 | 
						|
            *op = (char) num;
 | 
						|
            ip--;
 | 
						|
 | 
						|
        } else {
 | 
						|
            *op = *ip;
 | 
						|
        }
 | 
						|
        len--;
 | 
						|
    }
 | 
						|
    *op = '\0';
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#if ME_GOAHEAD_ACCESS_LOG && !ME_ROM
 | 
						|
/*
 | 
						|
    Output a log message in Common Log Format: See http://httpd.apache.org/docs/1.3/logs.html#common
 | 
						|
 */
 | 
						|
static void logRequest(Webs *wp, int code)
 | 
						|
{
 | 
						|
    char        *buf, timeStr[28], zoneStr[6], dataStr[16];
 | 
						|
    ssize       len;
 | 
						|
    WebsTime    timer;
 | 
						|
    struct tm   localt;
 | 
						|
#if WINDOWS
 | 
						|
    DWORD       dwRet;
 | 
						|
    TIME_ZONE_INFORMATION tzi;
 | 
						|
#endif
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    time(&timer);
 | 
						|
#if WINDOWS
 | 
						|
    localtime_s(&localt, &timer);
 | 
						|
#else
 | 
						|
    localtime_r(&timer, &localt);
 | 
						|
#endif
 | 
						|
    strftime(timeStr, sizeof(timeStr), "%d/%b/%Y:%H:%M:%S", &localt);
 | 
						|
    timeStr[sizeof(timeStr) - 1] = '\0';
 | 
						|
#if WINDOWS
 | 
						|
    dwRet = GetTimeZoneInformation(&tzi);
 | 
						|
    fmt(zoneStr, sizeof(zoneStr), "%+03d00", -(int) (tzi.Bias/60));
 | 
						|
#elif !VXWORKS
 | 
						|
    fmt(zoneStr, sizeof(zoneStr), "%+03d00", (int) (localt.tm_gmtoff/3600));
 | 
						|
#else
 | 
						|
    zoneStr[0] = '\0';
 | 
						|
#endif
 | 
						|
    zoneStr[sizeof(zoneStr) - 1] = '\0';
 | 
						|
    if (wp->written != 0) {
 | 
						|
        fmt(dataStr, sizeof(dataStr), "%Ld", wp->written);
 | 
						|
        dataStr[sizeof(dataStr) - 1] = '\0';
 | 
						|
    } else {
 | 
						|
        dataStr[0] = '-'; dataStr[1] = '\0';
 | 
						|
    }
 | 
						|
    buf = NULL;
 | 
						|
    buf = sfmt("%s - %s [%s %s] \"%s %s %s\" %d %s\n",
 | 
						|
        wp->ipaddr, wp->username == NULL ? "-" : wp->username,
 | 
						|
        timeStr, zoneStr, wp->method, wp->path, wp->protoVersion, code, dataStr);
 | 
						|
    len = strlen(buf);
 | 
						|
    write(accessFd, buf, len);
 | 
						|
    wfree(buf);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Request and connection timeout. The timeout triggers if we have not read any data from the
 | 
						|
    users browser in the last WEBS_TIMEOUT period. If we have heard from the browser, simply
 | 
						|
    re-issue the timeout.
 | 
						|
 */
 | 
						|
static void checkTimeout(void *arg, int id)
 | 
						|
{
 | 
						|
    Webs        *wp;
 | 
						|
    int         elapsed, delay;
 | 
						|
 | 
						|
    wp = (Webs*) arg;
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    elapsed = getTimeSinceMark(wp) * 1000;
 | 
						|
    if (websDebug) {
 | 
						|
        websRestartEvent(id, (int) WEBS_TIMEOUT);
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    if (elapsed >= WEBS_TIMEOUT) {
 | 
						|
        if (!(wp->flags & WEBS_HEADERS_CREATED)) {
 | 
						|
            if (wp->state > WEBS_BEGIN) {
 | 
						|
                websError(wp, HTTP_CODE_REQUEST_TIMEOUT, "Request exceeded timeout");
 | 
						|
            } else {
 | 
						|
                websError(wp, HTTP_CODE_REQUEST_TIMEOUT, "Idle connection closed");
 | 
						|
            }
 | 
						|
        }
 | 
						|
        wp->state = WEBS_COMPLETE;
 | 
						|
        complete(wp, 0);
 | 
						|
        websFree(wp);
 | 
						|
        /* WARNING: wp not valid here */
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    delay = WEBS_TIMEOUT - elapsed;
 | 
						|
    assert(delay > 0);
 | 
						|
    websRestartEvent(id, delay);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int setLocalHost(void)
 | 
						|
{
 | 
						|
    struct in_addr  intaddr;
 | 
						|
    char            host[128], *ipaddr;
 | 
						|
 | 
						|
    if (gethostname(host, sizeof(host)) < 0) {
 | 
						|
        error("Cannot get hostname: errno %d", errno);
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
#if VXWORKS
 | 
						|
    intaddr.s_addr = (ulong) hostGetByName(host);
 | 
						|
    ipaddr = inet_ntoa(intaddr);
 | 
						|
    websSetIpAddr(ipaddr);
 | 
						|
    websSetHost(ipaddr);
 | 
						|
    #if _WRS_VXWORKS_MAJOR < 6
 | 
						|
        free(ipaddr);
 | 
						|
    #endif
 | 
						|
#elif ECOS
 | 
						|
    ipaddr = inet_ntoa(eth0_bootp_data.bp_yiaddr);
 | 
						|
    websSetIpAddr(ipaddr);
 | 
						|
    websSetHost(ipaddr);
 | 
						|
#elif TIDSP
 | 
						|
{
 | 
						|
    struct hostent  *hp;
 | 
						|
    if ((hp = gethostbyname(host)) == NULL) {
 | 
						|
        error("Cannot get host address for host %s: errno %d", host, errno);
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    memcpy((char*) &intaddr, (char *) hp->h_addr[0], (size_t) hp->h_length);
 | 
						|
    ipaddr = inet_ntoa(intaddr);
 | 
						|
    websSetIpAddr(ipaddr);
 | 
						|
    websSetHost(ipaddr);
 | 
						|
}
 | 
						|
#elif MACOSX
 | 
						|
{
 | 
						|
    struct hostent  *hp;
 | 
						|
    if ((hp = gethostbyname(host)) == NULL) {
 | 
						|
        if ((hp = gethostbyname(sfmt("%s.local", host))) == NULL) {
 | 
						|
            error("Cannot get host address for host %s: errno %d", host, errno);
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    memcpy((char*) &intaddr, (char *) hp->h_addr_list[0], (size_t) hp->h_length);
 | 
						|
    ipaddr = inet_ntoa(intaddr);
 | 
						|
    websSetIpAddr(ipaddr);
 | 
						|
    websSetHost(ipaddr);
 | 
						|
}
 | 
						|
#else
 | 
						|
{
 | 
						|
    struct hostent  *hp;
 | 
						|
    if ((hp = gethostbyname(host)) == NULL) {
 | 
						|
        error("Cannot get host address for host %s: errno %d", host, errno);
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    memcpy((char*) &intaddr, (char *) hp->h_addr_list[0], (size_t) hp->h_length);
 | 
						|
    ipaddr = inet_ntoa(intaddr);
 | 
						|
    websSetIpAddr(ipaddr);
 | 
						|
    websSetHost(ipaddr);
 | 
						|
}
 | 
						|
#endif
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetHost(cchar *host)
 | 
						|
{
 | 
						|
    scopy(websHost, sizeof(websHost), host);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetHostUrl(cchar *url)
 | 
						|
{
 | 
						|
    assert(url && *url);
 | 
						|
 | 
						|
    wfree(websHostUrl);
 | 
						|
    websHostUrl = sclone(url);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetIpAddr(cchar *ipaddr)
 | 
						|
{
 | 
						|
    assert(ipaddr && *ipaddr);
 | 
						|
    scopy(websIpAddr, sizeof(websIpAddr), ipaddr);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#if ME_GOAHEAD_LEGACY
 | 
						|
PUBLIC void websSetRequestFilename(Webs *wp, cchar *filename)
 | 
						|
{
 | 
						|
    assert(websValid(wp));
 | 
						|
    assert(filename && *filename);
 | 
						|
 | 
						|
    wfree(wp->filename);
 | 
						|
    wp->filename = sclone(filename);
 | 
						|
    websSetVar(wp, "PATH_TRANSLATED", wp->filename);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websRewriteRequest(Webs *wp, cchar *url)
 | 
						|
{
 | 
						|
    char    *buf, *path;
 | 
						|
 | 
						|
    wfree(wp->url);
 | 
						|
    wp->url = sclone(url);
 | 
						|
    wfree(wp->path);
 | 
						|
    wp->path = 0;
 | 
						|
 | 
						|
    if (websUrlParse(url, &buf, NULL, NULL, NULL, &path, NULL, NULL, NULL) < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    wp->path = sclone(path);
 | 
						|
    wfree(wp->filename);
 | 
						|
    wp->filename = 0;
 | 
						|
    wp->flags |= WEBS_REROUTE;
 | 
						|
    wfree(buf);
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC bool websValid(Webs *wp)
 | 
						|
{
 | 
						|
    int     wid;
 | 
						|
 | 
						|
    for (wid = 0; wid < websMax; wid++) {
 | 
						|
        if (wp == webs[wid]) {
 | 
						|
            return 1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Build an ASCII time string.  If sbuf is NULL we use the current time, else we use the last modified time of sbuf;
 | 
						|
 */
 | 
						|
PUBLIC char *websGetDateString(WebsFileInfo *sbuf)
 | 
						|
{
 | 
						|
    WebsTime    now;
 | 
						|
    struct tm   tm;
 | 
						|
    char        *cp;
 | 
						|
 | 
						|
    if (sbuf == NULL) {
 | 
						|
        time(&now);
 | 
						|
    } else {
 | 
						|
        now = sbuf->mtime;
 | 
						|
    }
 | 
						|
#if ME_UNIX_LIKE
 | 
						|
    gmtime_r(&now, &tm);
 | 
						|
#else
 | 
						|
    {
 | 
						|
        struct tm *tp;
 | 
						|
        tp = gmtime(&now);
 | 
						|
        tm = *tp;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    if ((cp = asctime(&tm)) != NULL) {
 | 
						|
        cp[strlen(cp) - 1] = '\0';
 | 
						|
        return sclone(cp);
 | 
						|
    }
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Take not of the request activity and mark the time. Set a timestamp so that, later, we can return the number of seconds
 | 
						|
    since we made the mark.
 | 
						|
 */
 | 
						|
PUBLIC void websNoteRequestActivity(Webs *wp)
 | 
						|
{
 | 
						|
    wp->timestamp = time(0);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Get the number of seconds since the last mark.
 | 
						|
 */
 | 
						|
static int getTimeSinceMark(Webs *wp)
 | 
						|
{
 | 
						|
    return (int) (time(0) - wp->timestamp);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC bool websValidUriChars(cchar *uri)
 | 
						|
{
 | 
						|
    ssize   pos;
 | 
						|
 | 
						|
    if (uri == 0 || *uri == 0) {
 | 
						|
        return 1;
 | 
						|
    }
 | 
						|
    pos = strspn(uri, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%");
 | 
						|
    if (pos < slen(uri)) {
 | 
						|
        error("Bad character in URI at \"%s\"", &uri[pos]);
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Parse the URL. A single buffer is allocated to store the parsed URL in *pbuf. This must be freed by the caller.
 | 
						|
 */
 | 
						|
PUBLIC int websUrlParse(cchar *url, char **pbuf, char **pscheme, char **phost, char **pport, char **ppath, char **pext,
 | 
						|
        char **preference, char **pquery)
 | 
						|
{
 | 
						|
    char    *tok, *delim, *host, *path, *port, *scheme, *reference, *query, *ext, *buf, *buf2;
 | 
						|
    ssize   buflen, ulen, len;
 | 
						|
    int     sep;
 | 
						|
 | 
						|
    assert(pbuf);
 | 
						|
    if (url == 0) {
 | 
						|
        url = "";
 | 
						|
    }
 | 
						|
    /*
 | 
						|
        Allocate twice. Need to null terminate the host so have to copy the path.
 | 
						|
     */
 | 
						|
    ulen = strlen(url);
 | 
						|
    len = ulen + 1;
 | 
						|
    buflen = len * 2;
 | 
						|
    if ((buf = walloc(buflen)) == NULL) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    buf2 = &buf[ulen + 1];
 | 
						|
    sncopy(buf, len, url, ulen);
 | 
						|
    sncopy(buf2, len, url, ulen);
 | 
						|
    url = buf;
 | 
						|
 | 
						|
    scheme = 0;
 | 
						|
    host = 0;
 | 
						|
    port = 0;
 | 
						|
    path = 0;
 | 
						|
    ext = 0;
 | 
						|
    query = 0;
 | 
						|
    reference = 0;
 | 
						|
    tok = buf;
 | 
						|
    sep = '/';
 | 
						|
 | 
						|
    /*
 | 
						|
        [scheme://][hostname[:port]][/path[.ext]][#ref][?query]
 | 
						|
        First trim query and then reference from the end
 | 
						|
     */
 | 
						|
    if ((query = strchr(tok, '?')) != NULL) {
 | 
						|
        *query++ = '\0';
 | 
						|
    }
 | 
						|
    if ((reference = strchr(tok, '#')) != NULL) {
 | 
						|
        *reference++ = '\0';
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
        [scheme://][hostname[:port]][/path]
 | 
						|
     */
 | 
						|
    if ((delim = strstr(tok, "://")) != 0) {
 | 
						|
        scheme = tok;
 | 
						|
        *delim = '\0';
 | 
						|
        tok = &delim[3];
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
        [hostname[:port]][/path]
 | 
						|
     */
 | 
						|
    if (*tok == '[' && ((delim = strchr(tok, ']')) != 0)) {
 | 
						|
        /* IPv6 [::] */
 | 
						|
        host = &tok[1];
 | 
						|
        *delim++ = '\0';
 | 
						|
        tok = delim;
 | 
						|
 | 
						|
    } else if (*tok && *tok != '/' && *tok != ':' && (scheme || strchr(tok, ':'))) {
 | 
						|
        /*
 | 
						|
           Supported forms:
 | 
						|
               scheme://hostname
 | 
						|
               hostname[:port][/path]
 | 
						|
         */
 | 
						|
        host = tok;
 | 
						|
        if ((tok = strpbrk(tok, ":/")) == 0) {
 | 
						|
            tok = "";
 | 
						|
        }
 | 
						|
        /* Don't terminate the hostname yet, need to see if tok is a ':' for a port. */
 | 
						|
        assert(tok);
 | 
						|
    }
 | 
						|
 | 
						|
    /* [:port][/path] */
 | 
						|
    if (*tok == ':') {
 | 
						|
        /* Terminate hostname */
 | 
						|
        *tok++ = '\0';
 | 
						|
        port = tok;
 | 
						|
        if ((tok = strchr(tok, '/')) == 0) {
 | 
						|
            tok = "";
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /* [/path] */
 | 
						|
    if (*tok) {
 | 
						|
        /*
 | 
						|
           Terminate hostname. This zeros the leading path slash.
 | 
						|
           This will be repaired before returning if ppath is set
 | 
						|
         */
 | 
						|
        sep = *tok;
 | 
						|
        *tok++ = '\0';
 | 
						|
        path = tok;
 | 
						|
        /* path[.ext[/extra]] */
 | 
						|
        if ((tok = strrchr(path, '.')) != 0) {
 | 
						|
            if (tok[1]) {
 | 
						|
                if ((delim = strrchr(path, '/')) != 0) {
 | 
						|
                    if (delim < tok) {
 | 
						|
                        ext = tok;
 | 
						|
                    }
 | 
						|
                } else {
 | 
						|
                    ext = tok;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    /*
 | 
						|
        Pass back the requested fields
 | 
						|
     */
 | 
						|
    *pbuf = buf;
 | 
						|
    if (pscheme) {
 | 
						|
        if (scheme == 0) {
 | 
						|
            scheme = "http";
 | 
						|
        }
 | 
						|
        *pscheme = scheme;
 | 
						|
    }
 | 
						|
    if (phost) {
 | 
						|
        if (host == 0) {
 | 
						|
            host = "localhost";
 | 
						|
        }
 | 
						|
        *phost = host;
 | 
						|
    }
 | 
						|
    if (pport) {
 | 
						|
        *pport = port;
 | 
						|
    }
 | 
						|
    if (ppath) {
 | 
						|
        if (path == 0) {
 | 
						|
            scopy(buf2, 1, "/");
 | 
						|
            path = buf2;
 | 
						|
        } else {
 | 
						|
            /* Copy path to reinsert leading slash */
 | 
						|
            scopy(&buf2[1], len - 1, path);
 | 
						|
            path = buf2;
 | 
						|
            *path = sep;
 | 
						|
        }
 | 
						|
        // websDecodeUrl(path, path, -1);
 | 
						|
        *ppath = path;
 | 
						|
    }
 | 
						|
    if (pquery) {
 | 
						|
        *pquery = query;
 | 
						|
    }
 | 
						|
    if (preference) {
 | 
						|
        *preference = reference;
 | 
						|
    }
 | 
						|
    if (pext) {
 | 
						|
        if (ext) {
 | 
						|
            websDecodeUrl(ext, ext, -1);
 | 
						|
#if ME_WIN_LIKE || MACOSX
 | 
						|
            slower(ext);
 | 
						|
#endif
 | 
						|
        }
 | 
						|
        *pext = ext;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Normalize a URI path to remove "./",  "../" and redundant separators.
 | 
						|
    Note: this does not make an abs path and does not map separators nor change case.
 | 
						|
    This validates the URI and expects it to begin with "/".
 | 
						|
    Returns an allocated path, caller must free.
 | 
						|
 */
 | 
						|
PUBLIC char *websNormalizeUriPath(cchar *pathArg)
 | 
						|
{
 | 
						|
    char    *dupPath, *path, *sp, *dp, *mark, **segments;
 | 
						|
    int     firstc, j, i, nseg, len;
 | 
						|
 | 
						|
    if (pathArg == 0 || *pathArg == '\0') {
 | 
						|
        return sclone("");
 | 
						|
    }
 | 
						|
    len = (int) slen(pathArg);
 | 
						|
    if ((dupPath = walloc(len + 2)) == 0) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    strcpy(dupPath, pathArg);
 | 
						|
 | 
						|
    if ((segments = walloc(sizeof(char*) * (len + 1))) == 0) {
 | 
						|
        wfree(dupPath);
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    nseg = len = 0;
 | 
						|
    firstc = *dupPath;
 | 
						|
    for (mark = sp = dupPath; *sp; sp++) {
 | 
						|
        if (*sp == '/') {
 | 
						|
            *sp = '\0';
 | 
						|
            while (sp[1] == '/') {
 | 
						|
                sp++;
 | 
						|
            }
 | 
						|
            segments[nseg++] = mark;
 | 
						|
            len += (int) (sp - mark);
 | 
						|
            mark = sp + 1;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    segments[nseg++] = mark;
 | 
						|
    len += (int) (sp - mark);
 | 
						|
    for (j = i = 0; i < nseg; i++, j++) {
 | 
						|
        sp = segments[i];
 | 
						|
        if (sp[0] == '.') {
 | 
						|
            if (sp[1] == '\0')  {
 | 
						|
                if ((i+1) == nseg) {
 | 
						|
                    /* Trim trailing "." */
 | 
						|
                    segments[j] = "";
 | 
						|
                } else {
 | 
						|
                    j--;
 | 
						|
                }
 | 
						|
            } else if (sp[1] == '.' && sp[2] == '\0')  {
 | 
						|
                j = max(j - 2, -1);
 | 
						|
                if ((i+1) == nseg) {
 | 
						|
                    nseg--;
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                /* .more-chars */
 | 
						|
                segments[j] = segments[i];
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            segments[j] = segments[i];
 | 
						|
        }
 | 
						|
    }
 | 
						|
    nseg = j;
 | 
						|
    assert(nseg >= 0);
 | 
						|
    if ((path = walloc(len + nseg + 1)) != 0) {
 | 
						|
        for (i = 0, dp = path; i < nseg; ) {
 | 
						|
            strcpy(dp, segments[i]);
 | 
						|
            len = (int) slen(segments[i]);
 | 
						|
            dp += len;
 | 
						|
            if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
 | 
						|
                *dp++ = '/';
 | 
						|
            }
 | 
						|
        }
 | 
						|
        *dp = '\0';
 | 
						|
    }
 | 
						|
    wfree(dupPath);
 | 
						|
    wfree(segments);
 | 
						|
    return path;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Validate a URI path for use in a HTTP request line
 | 
						|
    The URI must contain only valid characters and must being with "/" both before and after decoding.
 | 
						|
    A decoded, normalized URI path is returned.
 | 
						|
    The uri is modified. Returns an allocated path. Caller must free.
 | 
						|
 */
 | 
						|
PUBLIC char *websValidateUriPath(cchar *uri)
 | 
						|
{
 | 
						|
    char    *decoded, *normalized;
 | 
						|
 | 
						|
    if (uri == 0 || *uri != '/') {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    if (!websValidUriChars(uri)) {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    decoded = walloc(slen(uri) + 1);
 | 
						|
    websDecodeUrl(decoded, (char*) uri, -1);
 | 
						|
    normalized = websNormalizeUriPath(decoded);
 | 
						|
    wfree(decoded);
 | 
						|
 | 
						|
    if (normalized == 0) {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    if (*normalized != '/' || strchr(normalized, '\\')) {
 | 
						|
        wfree(normalized);
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    return normalized;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Open a web page. filename is the local filename. path is the URL path name.
 | 
						|
 */
 | 
						|
PUBLIC int websPageOpen(Webs *wp, int mode, int perm)
 | 
						|
{
 | 
						|
    assert(websValid(wp));
 | 
						|
    return (wp->docfd = websOpenFile(wp->filename, mode, perm));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websPageClose(Webs *wp)
 | 
						|
{
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    if (wp->docfd >= 0) {
 | 
						|
        websCloseFile(wp->docfd);
 | 
						|
        wp->docfd = -1;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websPageStat(Webs *wp, WebsFileInfo *sbuf)
 | 
						|
{
 | 
						|
    return websStatFile(wp->filename, sbuf);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websPageIsDirectory(Webs *wp)
 | 
						|
{
 | 
						|
    WebsFileInfo    sbuf;
 | 
						|
 | 
						|
    if (websStatFile(wp->filename, &sbuf) >= 0) {
 | 
						|
        return(sbuf.isDir);
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Read a web page. Returns the number of _bytes_ read. len is the size of buf, in bytes.
 | 
						|
 */
 | 
						|
PUBLIC ssize websPageReadData(Webs *wp, char *buf, ssize nBytes)
 | 
						|
{
 | 
						|
 | 
						|
    assert(websValid(wp));
 | 
						|
    return websReadFile(wp->docfd, buf, nBytes);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Move file pointer offset bytes.
 | 
						|
 */
 | 
						|
PUBLIC void websPageSeek(Webs *wp, Offset offset, int origin)
 | 
						|
{
 | 
						|
    assert(websValid(wp));
 | 
						|
 | 
						|
    websSeekFile(wp->docfd, offset, origin);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetCookie(Webs *wp, cchar *name, cchar *value, cchar *path, cchar *cookieDomain, int lifespan, int flags)
 | 
						|
{
 | 
						|
    WebsTime    when;
 | 
						|
    char        *cp, *expiresAtt, *expires, *domainAtt, *domain, *secure, *httponly, *cookie, *old, *sameSite;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(name && *name);
 | 
						|
 | 
						|
    if (path == 0) {
 | 
						|
        path = "/";
 | 
						|
    }
 | 
						|
    if (!cookieDomain) {
 | 
						|
        domain = sclone(wp->host);
 | 
						|
        if ((cp = strchr(domain, ':')) != 0) {
 | 
						|
            /* Strip port */
 | 
						|
            *cp = '\0';
 | 
						|
        }
 | 
						|
        if (*domain && domain[strlen(domain) - 1] == '.') {
 | 
						|
            /* Cleanup bonjour addresses with trailing dot */
 | 
						|
            domain[strlen(domain) - 1] = '\0';
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        domain = sclone(cookieDomain);
 | 
						|
    }
 | 
						|
    domainAtt = "";
 | 
						|
    if (smatch(domain, "localhost")) {
 | 
						|
        wfree(domain);
 | 
						|
        domain = sclone("");
 | 
						|
    } else {
 | 
						|
        domainAtt = "; domain=";
 | 
						|
        if (!strchr(domain, '.')) {
 | 
						|
            old = domain;
 | 
						|
            domain = sfmt(".%s", domain);
 | 
						|
            wfree(old);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if (lifespan > 0) {
 | 
						|
        expiresAtt = "; expires=";
 | 
						|
        when = time(0) + lifespan;
 | 
						|
        if ((expires = ctime(&when)) != NULL) {
 | 
						|
            expires[strlen(expires) - 1] = '\0';
 | 
						|
        }
 | 
						|
 | 
						|
    } else {
 | 
						|
        expiresAtt = "";
 | 
						|
        expires = "";
 | 
						|
    }
 | 
						|
    /*
 | 
						|
       Allow multiple cookie headers. Even if the same name. Later definitions take precedence
 | 
						|
     */
 | 
						|
    secure = (flags & WEBS_COOKIE_SECURE) ? "; secure" : "";
 | 
						|
    httponly = (flags & WEBS_COOKIE_HTTP) ?  "; httponly" : "";
 | 
						|
    sameSite = "";
 | 
						|
    if (flags & WEBS_COOKIE_SAME_LAX) {
 | 
						|
        sameSite = "; SameSite=Lax";
 | 
						|
    } else if (flags & WEBS_COOKIE_SAME_STRICT) {
 | 
						|
        sameSite = "; SameSite=Strict";
 | 
						|
    }
 | 
						|
    cookie = sfmt("%s=%s; path=%s%s%s%s%s%s%s%s", name, value, path, domainAtt, domain, expiresAtt, expires, secure,
 | 
						|
        httponly, sameSite);
 | 
						|
    hashEnter(wp->responseCookies, name, valueString(cookie, 0), 0);
 | 
						|
    wfree(domain);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Return the next token in the input stream. Does not allocate.
 | 
						|
    The content buffer is advanced to the next token.
 | 
						|
    The delimiter is a string to match and not a set of characters.
 | 
						|
    If the delimeter null, it means use white space (space or tab) as a delimiter.
 | 
						|
 */
 | 
						|
static char *getToken(Webs *wp, char *delim, int validation)
 | 
						|
{
 | 
						|
    WebsBuf     *buf;
 | 
						|
    char        *token, *endToken;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    buf = &wp->rxbuf;
 | 
						|
    /* Already null terminated but for safety */
 | 
						|
    bufAddNull(buf);
 | 
						|
    token = (char*) buf->servp;
 | 
						|
    endToken = (char*) buf->endp;
 | 
						|
 | 
						|
    /*
 | 
						|
        Eat white space before token
 | 
						|
     */
 | 
						|
    for (; token < (char*) buf->endp && (*token == ' ' || *token == '\t'); token++) {}
 | 
						|
 | 
						|
    if (delim) {
 | 
						|
        if ((endToken = strstr(token, delim)) == NULL) {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
        /* Only eat one occurrence of the delimiter */
 | 
						|
        buf->servp = endToken + strlen(delim);
 | 
						|
        *endToken = '\0';
 | 
						|
 | 
						|
    } else {
 | 
						|
        delim = " \t";
 | 
						|
        if ((endToken = strpbrk(token, delim)) == NULL) {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
        buf->servp = endToken + strspn(endToken, delim);
 | 
						|
        *endToken = '\0';
 | 
						|
    }
 | 
						|
    token = validateToken(token, endToken, validation);
 | 
						|
    return token;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static char *validateToken(char *token, char *endToken, int validation)
 | 
						|
{
 | 
						|
    char    *t;
 | 
						|
 | 
						|
    if (validation == TOKEN_HEADER_KEY) {
 | 
						|
        if (token == NULL || *token == '\0') {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
        if (strpbrk(token, "\"\\/ \t\r\n(),:;<=>?@[]{}")) {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
        for (t = token; *t; t++) {
 | 
						|
            if (!isprint(*t)) {
 | 
						|
                return NULL;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    } else if (validation == TOKEN_HEADER_VALUE) {
 | 
						|
        if (token < endToken) {
 | 
						|
            /* Trim white space */
 | 
						|
            for (t = &token[slen(token) - 1]; t >= token; t--) {
 | 
						|
                if (isspace((uchar) *t)) {
 | 
						|
                    *t = '\0';
 | 
						|
                } else {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        while (isspace((uchar) *token)) {
 | 
						|
            token++;
 | 
						|
        }
 | 
						|
        for (t = token; *t; t++) {
 | 
						|
            if (!isprint(*t)) {
 | 
						|
                return NULL;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    } else if (validation == TOKEN_URI_VALUE) {
 | 
						|
        if (!websValidUriChars(token)) {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
    } else if (validation == TOKEN_NUMBER) {
 | 
						|
        if (!snumber(token)) {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
    } else if (validation == TOKEN_WORD) {
 | 
						|
        if (strpbrk(token, " \t\r\n") != NULL) {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        if (strpbrk(token, "\r\n") != NULL) {
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return token;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websGetBackground(void)
 | 
						|
{
 | 
						|
    return websBackground;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetBackground(int on)
 | 
						|
{
 | 
						|
    websBackground = on;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websGetDebug(void)
 | 
						|
{
 | 
						|
    return websDebug;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websSetDebug(int on)
 | 
						|
{
 | 
						|
    websDebug = on;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static char *makeSessionID(Webs *wp)
 | 
						|
{
 | 
						|
    char        idBuf[64];
 | 
						|
    static int  nextSession = 0;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    fmt(idBuf, sizeof(idBuf), "%08x%08x%d", PTOI(wp) + PTOI(wp->url), (int) time(0), nextSession++);
 | 
						|
    return websMD5Block(idBuf, slen(idBuf), "::webs.session::");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websDestroySession(Webs *wp)
 | 
						|
{
 | 
						|
    websGetSession(wp, 0);
 | 
						|
    if (wp->session) {
 | 
						|
        hashDelete(sessions, wp->session->id);
 | 
						|
        sessionCount--;
 | 
						|
        freeSession(wp->session);
 | 
						|
        wp->session = 0;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC WebsSession *websCreateSession(Webs *wp)
 | 
						|
{
 | 
						|
    websDestroySession(wp);
 | 
						|
    return websGetSession(wp, 1);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
WebsSession *websAllocSession(Webs *wp, cchar *id, int lifespan)
 | 
						|
{
 | 
						|
    WebsSession     *sp;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    if ((sp = walloc(sizeof(WebsSession))) == 0) {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    sp->lifespan = lifespan;
 | 
						|
    sp->expires = time(0) + lifespan;
 | 
						|
    if (id == 0) {
 | 
						|
        sp->id = makeSessionID(wp);
 | 
						|
    } else {
 | 
						|
        sp->id = sclone(id);
 | 
						|
    }
 | 
						|
    if ((sp->cache = hashCreate(WEBS_SESSION_HASH)) == 0) {
 | 
						|
        wfree(sp->id);
 | 
						|
        wfree(sp);
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    if (hashEnter(sessions, sp->id, valueSymbol(sp), 0) == 0) {
 | 
						|
        wfree(sp->id);
 | 
						|
        wfree(sp);
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    return sp;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void freeSession(WebsSession *sp)
 | 
						|
{
 | 
						|
    assert(sp);
 | 
						|
 | 
						|
    if (sp->cache >= 0) {
 | 
						|
        hashFree(sp->cache);
 | 
						|
        sp->cache = -1;
 | 
						|
    }
 | 
						|
    wfree(sp->id);
 | 
						|
    wfree(sp);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
WebsSession *websGetSession(Webs *wp, int create)
 | 
						|
{
 | 
						|
    WebsKey     *sym;
 | 
						|
    char        *id;
 | 
						|
    int         flags;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    if (!wp->session) {
 | 
						|
        id = websGetSessionID(wp);
 | 
						|
        if ((sym = hashLookup(sessions, id)) == 0) {
 | 
						|
            if (!create) {
 | 
						|
                wfree(id);
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            if (sessionCount >= ME_GOAHEAD_LIMIT_SESSION_COUNT) {
 | 
						|
                error("Too many sessions %d/%d", sessionCount, ME_GOAHEAD_LIMIT_SESSION_COUNT);
 | 
						|
                wfree(id);
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            sessionCount++;
 | 
						|
            if ((wp->session = websAllocSession(wp, id, ME_GOAHEAD_LIMIT_SESSION_LIFE)) == 0) {
 | 
						|
                wfree(id);
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            flags = WEBS_COOKIE_SAME_LAX | WEBS_COOKIE_HTTP;
 | 
						|
            if (wp->flags & WEBS_SECURE) {
 | 
						|
                flags |= WEBS_COOKIE_SECURE;
 | 
						|
            }
 | 
						|
            websSetCookie(wp, WEBS_SESSION, wp->session->id, "/", NULL, 0, flags);
 | 
						|
        } else {
 | 
						|
            wp->session = (WebsSession*) sym->content.value.symbol;
 | 
						|
        }
 | 
						|
        wfree(id);
 | 
						|
    }
 | 
						|
    if (wp->session) {
 | 
						|
        wp->session->expires = time(0) + wp->session->lifespan;
 | 
						|
    }
 | 
						|
    return wp->session;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC char *websParseCookie(Webs *wp, char *name)
 | 
						|
{
 | 
						|
    char    *buf, *cookie, *end, *key, *tok, *value, *vtok;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    if (wp->cookie == 0 || name == 0 || *name == '\0') {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    buf = sclone(wp->cookie);
 | 
						|
    end = &buf[slen(buf)];
 | 
						|
    value = 0;
 | 
						|
 | 
						|
    for (tok = buf; tok && tok < end; ) {
 | 
						|
         cookie = stok(tok, ";", &tok);
 | 
						|
         key = stok(cookie, "=", &vtok);
 | 
						|
         if (smatch(key, name)) {
 | 
						|
             // Remove leading spaces first, then double quotes. Spaces inside double quotes preserved.
 | 
						|
             value = sclone(strim(strim(vtok, " ", WEBS_TRIM_BOTH), "\"", WEBS_TRIM_BOTH));
 | 
						|
             break;
 | 
						|
         }
 | 
						|
    }
 | 
						|
    wfree(buf);
 | 
						|
    return value;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC char *websGetSessionID(Webs *wp)
 | 
						|
{
 | 
						|
    assert(wp);
 | 
						|
 | 
						|
    if (wp->session) {
 | 
						|
        return wp->session->id;
 | 
						|
    }
 | 
						|
    return websParseCookie(wp, WEBS_SESSION);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC cchar *websGetSessionVar(Webs *wp, cchar *key, cchar *defaultValue)
 | 
						|
{
 | 
						|
    WebsSession     *sp;
 | 
						|
    WebsKey         *sym;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(key && *key);
 | 
						|
 | 
						|
    if ((sp = websGetSession(wp, 1)) != 0) {
 | 
						|
        if ((sym = hashLookup(sp->cache, key)) == 0) {
 | 
						|
            return defaultValue;
 | 
						|
        }
 | 
						|
        return (char*) sym->content.value.symbol;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC void websRemoveSessionVar(Webs *wp, cchar *key)
 | 
						|
{
 | 
						|
    WebsSession     *sp;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(key && *key);
 | 
						|
 | 
						|
    if ((sp = websGetSession(wp, 1)) != 0) {
 | 
						|
        hashDelete(sp->cache, key);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PUBLIC int websSetSessionVar(Webs *wp, cchar *key, cchar *value)
 | 
						|
{
 | 
						|
    WebsSession  *sp;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    assert(key && *key);
 | 
						|
    assert(value);
 | 
						|
 | 
						|
    if ((sp = websGetSession(wp, 1)) == 0) {
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    if (hashEnter(sp->cache, key, valueString(value, VALUE_ALLOCATE), 0) == 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void pruneSessions(void)
 | 
						|
{
 | 
						|
    WebsSession     *sp;
 | 
						|
    WebsTime        when;
 | 
						|
    WebsKey         *sym, *next;
 | 
						|
    int             oldCount;
 | 
						|
 | 
						|
    if (sessions >= 0) {
 | 
						|
        oldCount = sessionCount;
 | 
						|
        when = time(0);
 | 
						|
        for (sym = hashFirst(sessions); sym; sym = next) {
 | 
						|
            next = hashNext(sessions, sym);
 | 
						|
            sp = (WebsSession*) sym->content.value.symbol;
 | 
						|
            if (sp->expires <= when) {
 | 
						|
                hashDelete(sessions, sp->id);
 | 
						|
                sessionCount--;
 | 
						|
                freeSession(sp);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (oldCount != sessionCount || sessionCount) {
 | 
						|
            trace(4, "Prune %d sessions. Remaining: %d", oldCount - sessionCount, sessionCount);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    websRestartEvent(pruneId, WEBS_SESSION_PRUNE);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void freeSessions(void)
 | 
						|
{
 | 
						|
    WebsSession     *sp;
 | 
						|
    WebsKey         *sym, *next;
 | 
						|
 | 
						|
    if (sessions >= 0) {
 | 
						|
        for (sym = hashFirst(sessions); sym; sym = next) {
 | 
						|
            next = hashNext(sessions, sym);
 | 
						|
            sp = (WebsSession*) sym->content.value.symbol;
 | 
						|
            hashDelete(sessions, sp->id);
 | 
						|
            freeSession(sp);
 | 
						|
        }
 | 
						|
        hashFree(sessions);
 | 
						|
        sessions = -1;
 | 
						|
        sessionCount = 0;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    One line embedding
 | 
						|
 */
 | 
						|
PUBLIC int websServer(cchar *endpoint, cchar *documents)
 | 
						|
{
 | 
						|
    int     finished = 0;
 | 
						|
 | 
						|
    if (websOpen(documents, "route.txt") < 0) {
 | 
						|
        error("Cannot initialize server. Exiting.");
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    if (websLoad("auth.txt") < 0) {
 | 
						|
        error("Cannot load auth.txt");
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    if (websListen(endpoint) < 0) {
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
    websServiceEvents(&finished);
 | 
						|
    websClose();
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void setFileLimits(void)
 | 
						|
{
 | 
						|
#if ME_UNIX_LIKE
 | 
						|
    struct rlimit r;
 | 
						|
    int           limit;
 | 
						|
 | 
						|
    limit = ME_GOAHEAD_LIMIT_FILES;
 | 
						|
    if (limit) {
 | 
						|
        r.rlim_cur = r.rlim_max = limit;
 | 
						|
        if (setrlimit(RLIMIT_NOFILE, &r) < 0) {
 | 
						|
            error("Cannot set file limit to %d", limit);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    getrlimit(RLIMIT_NOFILE, &r);
 | 
						|
    trace(6, "Max files soft %d, max %d", r.rlim_cur, r.rlim_max);
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
    Output an error message and cleanup
 | 
						|
 */
 | 
						|
PUBLIC void websError(Webs *wp, int code, cchar *fmt, ...)
 | 
						|
{
 | 
						|
    va_list     args;
 | 
						|
    char        *msg, *buf;
 | 
						|
    char        *encoded;
 | 
						|
    int         status;
 | 
						|
 | 
						|
    assert(wp);
 | 
						|
    wp->error = 1;
 | 
						|
    if (code & WEBS_CLOSE) {
 | 
						|
        wp->flags &= ~WEBS_KEEP_ALIVE;
 | 
						|
        wp->connError++;
 | 
						|
    }
 | 
						|
    status = code & WEBS_CODE_MASK;
 | 
						|
#if !ME_ROM
 | 
						|
    if (wp->putfd >= 0) {
 | 
						|
        close(wp->putfd);
 | 
						|
        wp->putfd = -1;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
    if (wp->rxRemaining && status != 200 && status != 301 && status != 302 && status != 401) {
 | 
						|
        /* Close connection so we don't have to consume remaining content */
 | 
						|
        wp->flags &= ~WEBS_KEEP_ALIVE;
 | 
						|
    }
 | 
						|
    encoded = websEscapeHtml(wp->url);
 | 
						|
    wfree(wp->url);
 | 
						|
    wp->url = encoded;
 | 
						|
    if (fmt) {
 | 
						|
        if (!(code & WEBS_NOLOG)) {
 | 
						|
            va_start(args, fmt);
 | 
						|
            msg = sfmtv(fmt, args);
 | 
						|
            va_end(args);
 | 
						|
            trace(2, "%s", msg);
 | 
						|
            wfree(msg);
 | 
						|
        }
 | 
						|
        buf = sfmt("\
 | 
						|
<html>\r\n\
 | 
						|
    <head><title>Document Error: %s</title></head>\r\n\
 | 
						|
    <body>\r\n\
 | 
						|
        <h2>Access Error: %s</h2>\r\n\
 | 
						|
    </body>\r\n\
 | 
						|
</html>\r\n", websErrorMsg(code), websErrorMsg(code));
 | 
						|
    } else {
 | 
						|
        buf = 0;
 | 
						|
    }
 | 
						|
    websResponse(wp, code, buf);
 | 
						|
    wfree(buf);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Return the error message for a given code
 | 
						|
 */
 | 
						|
PUBLIC cchar *websErrorMsg(int code)
 | 
						|
{
 | 
						|
    WebsError   *ep;
 | 
						|
 | 
						|
    assert(code >= 0);
 | 
						|
    code &= WEBS_CODE_MASK;
 | 
						|
    for (ep = websErrors; ep->code; ep++) {
 | 
						|
        if (code == ep->code) {
 | 
						|
            return ep->msg;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return websErrorMsg(HTTP_CODE_INTERNAL_SERVER_ERROR);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
    Accessors
 | 
						|
 */
 | 
						|
PUBLIC cchar *websGetCookie(Webs *wp) { return wp->cookie; }
 | 
						|
PUBLIC cchar *websGetDir(Webs *wp) { return wp->route && wp->route->dir ? wp->route->dir : websGetDocuments(); }
 | 
						|
PUBLIC int  websGetEof(Webs *wp) { return wp->eof; }
 | 
						|
PUBLIC cchar *websGetExt(Webs *wp) { return wp->ext; }
 | 
						|
PUBLIC cchar *websGetFilename(Webs *wp) { return wp->filename; }
 | 
						|
PUBLIC cchar *websGetHost(Webs *wp) { return wp->host; }
 | 
						|
PUBLIC cchar *websGetIfaddr(Webs *wp) { return wp->ifaddr; }
 | 
						|
PUBLIC cchar *websGetIpaddr(Webs *wp) { return wp->ipaddr; }
 | 
						|
PUBLIC cchar *websGetMethod(Webs *wp) { return wp->method; }
 | 
						|
PUBLIC cchar *websGetPassword(Webs *wp) { return wp->password; }
 | 
						|
PUBLIC cchar *websGetPath(Webs *wp) { return wp->path; }
 | 
						|
PUBLIC int   websGetPort(Webs *wp) { return wp->port; }
 | 
						|
PUBLIC cchar *websGetProtocol(Webs *wp) { return wp->protocol; }
 | 
						|
PUBLIC cchar *websGetQuery(Webs *wp) { return wp->query; }
 | 
						|
PUBLIC cchar *websGetServer(void) { return websHost; }
 | 
						|
PUBLIC cchar *websGetServerAddress(void) { return websIpAddr; }
 | 
						|
PUBLIC cchar *websGetServerAddressUrl(void) { return websIpAddrUrl; }
 | 
						|
PUBLIC cchar *websGetServerUrl(void) { return websHostUrl; }
 | 
						|
PUBLIC cchar *websGetUrl(Webs *wp) { return wp->url; }
 | 
						|
PUBLIC cchar *websGetUserAgent(Webs *wp) { return wp->userAgent; }
 | 
						|
PUBLIC cchar *websGetUsername(Webs *wp) { return wp->username; }
 | 
						|
 | 
						|
/*
 | 
						|
    Copyright (c) Embedthis Software. All Rights Reserved.
 | 
						|
    This software is distributed under a commercial license. Consult the LICENSE.md
 | 
						|
    distributed with this software for full details and copyrights.
 | 
						|
 */
 |