/* * webs.c -- GoAhead Embedded HTTP webs server * * Copyright (c) GoAhead Software Inc., 1995-2000. All Rights Reserved. * * See the file "license.txt" for usage and redistribution license requirements * * $Id$ */ /******************************** Description *********************************/ /* * This module implements an embedded HTTP/1.1 web server. It supports * loadable URL handlers that define the nature of URL processing performed. */ /********************************* Includes ***********************************/ #include "wsIntrn.h" #ifdef DIGEST_ACCESS_SUPPORT #include "websda.h" #endif /******************************** Global Data *********************************/ websStatsType websStats; /* Web access stats */ webs_t *webs; /* Open connection list head */ sym_fd_t websMime; /* Set of mime types */ int websMax; /* List size */ int websPort; /* Listen port for server */ char_t websHost[64]; /* Host name for the server */ char_t websIpaddr[64]; /* IP address for the server */ char_t *websHostUrl = NULL; /* URL to access server */ char_t *websIpaddrUrl = NULL; /* URL to access server */ /*********************************** Locals ***********************************/ /* * Standard HTTP error codes */ websErrorType websErrors[] = { { 200, T("Data follows") }, { 204, T("No Content") }, { 301, T("Redirect") }, { 302, T("Redirect") }, { 304, T("Use local copy") }, { 400, T("Page not found") }, { 401, T("Unauthorized") }, { 403, T("Forbidden") }, { 404, T("Site or Page Not Found") }, { 405, T("Access Denied") }, { 500, T("Web Error") }, { 501, T("Not Implemented") }, { 503, T("Site Temporarily Unavailable. Try again.") }, { 0, NULL } }; #ifdef WEBS_LOG_SUPPORT static char_t websLogname[64] = T("log.txt"); /* Log filename */ static int websLogFd; /* Log file handle */ #endif static int websListenSock; /* Listen socket */ static char_t websRealm[64] = T("GoAhead"); /* Realm name */ static int websOpenCount = 0; /* count of apps using this module */ /**************************** Forward Declarations ****************************/ /*static char_t *websErrorMsg(int code);*/ static int websGetInput(webs_t wp, char_t **ptext, int *nbytes); static int websParseFirst(webs_t wp, char_t *text); static void websParseRequest(webs_t wp); static void websSocketEvent(int sid, int mask, int data); static int websGetTimeSinceMark(webs_t wp); #ifdef WEBS_LOG_SUPPORT static void websLog(webs_t wp, int code); #endif #ifdef WEBS_IF_MODIFIED_SUPPORT static time_t dateParse(time_t tip, char_t *cmd); #endif /*********************************** Code *************************************/ /* * Open the GoAhead WebServer */ int websOpenServer(int port, int retries) { websMimeType *mt; if (++websOpenCount != 1) { return websPort; } a_assert(port > 0); a_assert(retries >= 0); #ifdef WEBS_PAGE_ROM websRomOpen(); #endif webs = NULL; websMax = 0; /* * Create a mime type lookup table for quickly determining the content type */ websMime = symOpen(WEBS_SYM_INIT * 4); a_assert(websMime >= 0); for (mt = websMimeList; mt->type; mt++) { symEnter(websMime, mt->ext, valueString(mt->type, 0), 0); } /* * Open the URL handler module. The caller should create the required * URL handlers after calling this function. */ if (websUrlHandlerOpen() < 0) { return -1; } websFormOpen(); #ifdef WEBS_LOG_SUPPORT /* * Optional request log support */ websLogFd = gopen(websLogname, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0666); a_assert(websLogFd >= 0); #endif return websOpenListen(port, retries); } /******************************************************************************/ /* * Close the GoAhead WebServer */ void websCloseServer() { webs_t wp; int wid; if (--websOpenCount > 0) { return; } /* * Close the listen handle first then all open connections. */ websCloseListen(); /* * Close each open browser connection and free all resources */ for (wid = websMax; webs && wid >= 0; wid--) { if ((wp = webs[wid]) == NULL) { continue; } socketCloseConnection(wp->sid); websFree(wp); } #ifdef WEBS_LOG_SUPPORT if (websLogFd >= 0) { close(websLogFd); websLogFd = -1; } #endif #ifdef WEBS_PAGE_ROM websRomClose(); #endif symClose(websMime); websFormClose(); websUrlHandlerClose(); } /******************************************************************************/ /* * Open the GoAhead WebServer listen port */ int websOpenListen(int port, int retries) { int i, orig; a_assert(port > 0); a_assert(retries >= 0); orig = port; /* * Open the webs webs listen port. If we fail, try the next port. */ for (i = 0; i <= retries; i++) { websListenSock = socketOpenConnection(NULL, port, websAccept, 0); if (websListenSock >= 0) { break; } port++; } if (i > retries) { error(E_L, E_USER, T("Couldn't open a socket on ports %d - %d"), orig, port - 1); return -1; } /* * Determine the full URL address to access the home page for this web server */ websPort = port; bfreeSafe(B_L, websHostUrl); bfreeSafe(B_L, websIpaddrUrl); websIpaddrUrl = websHostUrl = NULL; if (port == 80) { websHostUrl = bstrdup(B_L, websHost); websIpaddrUrl = bstrdup(B_L, websIpaddr); } else { fmtAlloc(&websHostUrl, WEBS_MAX_URL + 80, T("%s:%d"), websHost, port); fmtAlloc(&websIpaddrUrl, WEBS_MAX_URL + 80, T("%s:%d"), websIpaddr, port); } trace(0, T("webs: Listening for HTTP requests at address %s\n"), websIpaddrUrl); return port; } /******************************************************************************/ /* * Close webs listen port */ void websCloseListen() { if (websListenSock >= 0) { socketCloseConnection(websListenSock); websListenSock = -1; } bfreeSafe(B_L, websHostUrl); bfreeSafe(B_L, websIpaddrUrl); websIpaddrUrl = websHostUrl = NULL; } /******************************************************************************/ /* * Accept a connection */ int websAccept(int sid, char *ipaddr, int port, int listenSid) { webs_t wp; int wid; a_assert(ipaddr && *ipaddr); a_assert(sid >= 0); a_assert(port >= 0); /* * Allocate a new handle for this accepted connection. This will allocate * a webs_t structure in the webs[] list */ if ((wid = websAlloc(sid)) < 0) { return -1; } wp = webs[wid]; a_assert(wp); wp->listenSid = listenSid; ascToUni(wp->ipaddr, ipaddr, min(sizeof(wp->ipaddr), strlen(ipaddr) + 1)); /* * 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 (gstrcmp(wp->ipaddr, T("127.0.0.1")) == 0 || gstrcmp(wp->ipaddr, websIpaddr) == 0 || gstrcmp(wp->ipaddr, websHost) == 0) { wp->flags |= WEBS_LOCAL_REQUEST; } /* * Arrange for websSocketEvent to be called when read data is available */ socketCreateHandler(sid, SOCKET_READABLE, websSocketEvent, (int) wp); /* * Arrange for a timeout to kill hung requests */ wp->timeout = emfSchedCallback(WEBS_TIMEOUT, websTimeout, (void *) wp); trace(8, T("webs: accept request\n")); 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 an (int) in iwp. */ static void websSocketEvent(int sid, int mask, int iwp) { webs_t wp; wp = (webs_t) iwp; a_assert(wp); if (! websValid(wp)) { return; } if (mask & SOCKET_READABLE) { websReadEvent(wp); } if (mask & SOCKET_WRITABLE) { if (websValid(wp) && wp->writeSocket) { (*wp->writeSocket)(wp); } } } /******************************************************************************/ /* * 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. */ void websReadEvent(webs_t wp) { char_t *text; int rc, nbytes, len, done, fd; a_assert(wp); a_assert(websValid(wp)); websSetTimeMark(wp); /* * Read as many lines as possible. socketGets is called to read the header * and socketRead is called to read posted data. */ text = NULL; fd = -1; for (done = 0; !done; ) { if (text) { bfree(B_L, text); text = NULL; } /* * Get more input into "text". Returns 0, if more data is needed * to continue, -1 if finished with the request, or 1 if all * required data is available for current state. */ while ((rc = websGetInput(wp, &text, &nbytes)) == 0) { ; } /* * websGetInput returns -1 if it finishes with the request */ if (rc < 0) { break; } /* * This is the state machine for the web server. */ switch(wp->state) { case WEBS_BEGIN: /* * Parse the first line of the Http header */ if (websParseFirst(wp, text) < 0) { done++; break; } wp->state = WEBS_HEADER; break; case WEBS_HEADER: /* * Store more of the HTTP header. As we are doing line reads, we * need to separate the lines with '\n' */ if (ringqLen(&wp->header) > 0) { ringqPutStr(&wp->header, T("\n")); } ringqPutStr(&wp->header, text); break; case WEBS_POST_CLEN: /* * POST request with content specified by a content length. * If this is a CGI request, write the data to the cgi stdin. * socketGets was used to get the data and it strips \n's so * add them back in here. */ #ifndef __NO_CGI_BIN if (wp->flags & WEBS_CGI_REQUEST) { if (fd == -1) { fd = gopen(wp->cgiStdin, O_CREAT | O_WRONLY | O_BINARY, 0666); } gwrite(fd, text, gstrlen(text)); /* * NOTE that the above comment is wrong -- if the content length * is set, websGetInput() does NOT use socketGets(), it uses * socketRead(), so the line below that adds an additional newline * is destructive. */ /*gwrite(fd, T("\n"), sizeof(char_t));*/ /* * Line removed as per BUG02488 * nbytes += 1; */ } else #endif if (wp->query) { if (wp->query[0] && !(wp->flags & WEBS_POST_DATA)) { /* * Special case where the POST request also had query data * specified in the URL, ie. url?query_data. In this case * the URL query data is separated by a '&' from the posted * query data. */ len = gstrlen(wp->query); wp->query = brealloc(B_L, wp->query, (len + gstrlen(text) + 2) * sizeof(char_t)); wp->query[len++] = '&'; gstrcpy(&wp->query[len], text); } else { /* * The existing query data came from the POST request so just * append it. */ if (text != NULL) { len = gstrlen(wp->query); wp->query = brealloc(B_L, wp->query, (len + gstrlen(text) + 1) * sizeof(char_t)); if (wp->query) { gstrcpy(&wp->query[len], text); } } } } else { wp->query = bstrdup(B_L, text); } /* * Calculate how much more post data is to be read. */ wp->flags |= WEBS_POST_DATA; wp->clen -= nbytes; if (wp->clen > 0) { if (nbytes > 0) { break; } done++; break; } /* * No more data so process the request, (but be sure to close * the input file first!). */ if (fd != -1) { gclose (fd); fd = -1; } websUrlHandlerRequest(wp); done++; break; case WEBS_POST: /* * POST without content-length specification * If this is a CGI request, write the data to the cgi stdin. * socketGets was used to get the data and it strips \n's so * add them back in here. */ #ifndef __NO_CGI_BIN if (wp->flags & WEBS_CGI_REQUEST) { if (fd == -1) { fd = gopen(wp->cgiStdin, O_CREAT | O_WRONLY | O_BINARY, 0666); } gwrite(fd, text, gstrlen(text)); gwrite(fd, T("\n"), sizeof(char_t)); } else #endif if (wp->query && *wp->query && !(wp->flags & WEBS_POST_DATA)) { len = gstrlen(wp->query); wp->query = brealloc(B_L, wp->query, (len + gstrlen(text) + 2) * sizeof(char_t)); if (wp->query) { wp->query[len++] = '&'; gstrcpy(&wp->query[len], text); } } else { wp->query = bstrdup(B_L, text); } wp->flags |= WEBS_POST_DATA; done++; break; default: websError(wp, 404, T("Bad state")); done++; break; } } if (fd != -1) { fd = gclose (fd); } if (text) { bfree(B_L, text); } } /******************************************************************************/ /* * Get input from the browser. Return TRUE (!0) if the request has been * handled. Return -1 on errors or if the request has been processed, * 1 if input read, and 0 to instruct the caller to call again for more input. * * Note: socketRead will Return the number of bytes read if successful. This * may be less than the requested "bufsize" and may be zero. It returns -1 for * errors. It returns 0 for EOF. Otherwise it returns the number of bytes * read. Since this may be zero, callers should use socketEof() to * distinguish between this and EOF. */ static int websGetInput(webs_t wp, char_t **ptext, int *pnbytes) { char_t *text; char buf[WEBS_SOCKET_BUFSIZ+1]; int nbytes, len, clen; a_assert(websValid(wp)); a_assert(ptext); a_assert(pnbytes); *ptext = text = NULL; *pnbytes = 0; /* * If this request is a POST with a content length, we know the number * of bytes to read so we use socketRead(). */ if (wp->state == WEBS_POST_CLEN) { len = (wp->clen > WEBS_SOCKET_BUFSIZ) ? WEBS_SOCKET_BUFSIZ : wp->clen; } else { len = 0; } if (len > 0) { #ifdef WEBS_SSL_SUPPORT if (wp->flags & WEBS_SECURE) { nbytes = websSSLRead(wp->wsp, buf, len); } else { nbytes = socketRead(wp->sid, buf, len); } #else nbytes = socketRead(wp->sid, buf, len); #endif if (nbytes < 0) { /* Error */ websDone(wp, 0); return -1; } else if (nbytes == 0) { /* EOF or No data available */ return -1; } else { /* Valid data */ /* * Convert to UNICODE if necessary. First be sure the string * is NULL terminated. */ buf[nbytes] = '\0'; if ((text = ballocAscToUni(buf, nbytes)) == NULL) { websError(wp, 503, T("Insufficient memory")); return -1; } } } else { #ifdef WEBS_SSL_SUPPORT if (wp->flags & WEBS_SECURE) { nbytes = websSSLGets(wp->wsp, &text); } else { nbytes = socketGets(wp->sid, &text); } #else nbytes = socketGets(wp->sid, &text); #endif if (nbytes < 0) { int eof; /* * Error, EOF or incomplete */ #ifdef WEBS_SSL_SUPPORT if (wp->flags & WEBS_SECURE) { /* * If state is WEBS_BEGIN and the request is secure, a -1 will * usually indicate SSL negotiation */ if (wp->state == WEBS_BEGIN) { eof = 1; } else { eof = websSSLEof(wp->wsp); } } else { eof = socketEof(wp->sid); } #else eof = socketEof(wp->sid); #endif if (eof) { /* * If this is a post request without content length, process * the request as we now have all the data. Otherwise just * close the connection. */ if (wp->state == WEBS_POST) { websUrlHandlerRequest(wp); } else { websDone(wp, 0); } } else { /* * If an error occurred and it wasn't an eof, close the connection */ #ifdef HP_FIX websDone(wp, 0); #endif /*HP_FIX*/ } /* * If state is WEBS_HEADER and the ringq is empty, then this is a * simple request with no additional header fields to process and * no empty line terminator. */ /* * NOTE: this fix for earlier versions of browsers is troublesome * because if we don't receive the entire header in the first pass * this code assumes we were only expecting a one line header, which * is not necessarily the case. So we weren't processing the whole * header and weren't fufilling requests properly. */ #ifdef UNUSED if (wp->state == WEBS_HEADER && ringqLen(&wp->header) <= 0) { websParseRequest(wp); websUrlHandlerRequest(wp); } #endif return -1; } else if (nbytes == 0) { if (wp->state == WEBS_HEADER) { /* * Valid empty line, now finished with header */ websParseRequest(wp); if (wp->flags & WEBS_POST_REQUEST) { if (wp->flags & WEBS_CLEN) { wp->state = WEBS_POST_CLEN; clen = wp->clen; } else { wp->state = WEBS_POST; clen = 1; } if (clen > 0) { /* * Return 0 to get more data. */ return 0; } return 1; } /* * We've read the header so go and handle the request */ websUrlHandlerRequest(wp); } return -1; } } a_assert(text); a_assert(nbytes > 0); *ptext = text; *pnbytes = nbytes; return 1; } /******************************************************************************/ /* * Parse the first line of a HTTP request */ static int websParseFirst(webs_t wp, char_t *text) { char_t *op, *proto, *protoVer, *url, *host, *query, *path, *port, *ext; char_t *buf; int testPort; a_assert(websValid(wp)); a_assert(text && *text); /* * Determine the request type: GET, HEAD or POST */ op = gstrtok(text, T(" \t")); if (op == NULL || *op == '\0') { websError(wp, 400, T("Bad HTTP request")); return -1; } if (gstrcmp(op, T("GET")) != 0) { if (gstrcmp(op, T("POST")) == 0) { wp->flags |= WEBS_POST_REQUEST; } else if (gstrcmp(op, T("HEAD")) == 0) { wp->flags |= WEBS_HEAD_REQUEST; } else { websError(wp, 400, T("Bad request type")); return -1; } } /* * Store result in the form (CGI) variable store */ websSetVar(wp, T("REQUEST_METHOD"), op); url = gstrtok(NULL, T(" \t\n")); if (url == NULL || *url == '\0') { websError(wp, 400, T("Bad HTTP request")); return -1; } protoVer = gstrtok(NULL, T(" \t\n")); /* * 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 = proto = query = ext = NULL; if (websUrlParse(url, &buf, &host, &path, &port, &query, &proto, NULL, &ext) < 0) { websError(wp, 400, T("Bad URL format")); return -1; } wp->url = bstrdup(B_L, url); #ifndef __NO_CGI_BIN if (gstrstr(url, CGI_BIN) != NULL) { wp->flags |= WEBS_CGI_REQUEST; if (wp->flags & WEBS_POST_REQUEST) { wp->cgiStdin = websGetCgiCommName(); } } #endif wp->query = bstrdup(B_L, query); wp->host = bstrdup(B_L, host); wp->path = bstrdup(B_L, path); wp->protocol = bstrdup(B_L, proto); wp->protoVersion = bstrdup(B_L, protoVer); if ((testPort = socketGetPort(wp->listenSid)) >= 0) { wp->port = testPort; } else { wp->port = gatoi(port); } if (gstrcmp(ext, T(".asp")) == 0) { wp->flags |= WEBS_ASP; } bfree(B_L, buf); websUrlType(url, wp->type, TSZ(wp->type)); #ifdef WEBS_PROXY_SUPPORT /* * Determine if this is a request for local webs data. If it is not a proxied * request from the browser, we won't see the "http://" or the system name, so * we assume it must be talking to us directly for local webs data. * Note: not fully implemented yet. */ if (gstrstr(wp->url, T("http://")) == NULL || ((gstrcmp(wp->host, T("localhost")) == 0 || gstrcmp(wp->host, websHost) == 0) && (wp->port == websPort))) { wp->flags |= WEBS_LOCAL_PAGE; if (gstrcmp(wp->path, T("/")) == 0) { wp->flags |= WEBS_HOME_PAGE; } } #endif ringqFlush(&wp->header); return 0; } /******************************************************************************/ /* * Parse a full request */ #define isgoodchar(s) (gisalnum((s)) || ((s) == '/') || ((s) == '_') || \ ((s) == '.') || ((s) == '-') ) static void websParseRequest(webs_t wp) { char_t *authType, *upperKey, *cp, *browser, *lp, *key, *value; a_assert(websValid(wp)); /* * Define default CGI values */ websSetVar(wp, T("HTTP_AUTHORIZATION"), T("")); /* * 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'. */ browser = NULL; for (lp = (char_t*) wp->header.servp; lp && *lp; ) { cp = lp; if ((lp = gstrchr(lp, '\n')) != NULL) { lp++; } if ((key = gstrtok(cp, T(": \t\n"))) == NULL) { continue; } if ((value = gstrtok(NULL, T("\n"))) == NULL) { value = T(""); } while (gisspace(*value)) { value++; } strlower(key); /* * Create a variable (CGI) for each line in the header */ fmtAlloc(&upperKey, (gstrlen(key) + 6), T("HTTP_%s"), key); for (cp = upperKey; *cp; cp++) { if (*cp == '-') *cp = '_'; } strupper(upperKey); websSetVar(wp, upperKey, value); bfree(B_L, upperKey); /* * Track the requesting agent (browser) type */ if (gstrcmp(key, T("user-agent")) == 0) { wp->userAgent = bstrdup(B_L, value); /* * Parse the user authorization. ie. password */ } else if (gstricmp(key, T("authorization")) == 0) { /* * Determine the type of Authorization Request */ authType = bstrdup (B_L, value); a_assert (authType); /* * Truncate authType at the next non-alpha character */ cp = authType; while (gisalpha(*cp)) { cp++; } *cp = '\0'; wp->authType = bstrdup(B_L, authType); bfree(B_L, authType); if (gstricmp(wp->authType, T("basic")) == 0) { char_t userAuth[FNAMESIZE]; /* * The incoming value is username:password (Basic authentication) */ if ((cp = gstrchr(value, ' ')) != NULL) { *cp = '\0'; /* * bugfix 5/24/02 -- we were leaking the memory pointed to by * wp->authType that was allocated just before the if() * statement that we are currently in. Thanks to Simon Byholm. */ bfree(B_L, wp->authType); wp->authType = bstrdup(B_L, value); websDecode64(userAuth, ++cp, sizeof(userAuth)); } else { websDecode64(userAuth, value, sizeof(userAuth)); } /* * Split userAuth into userid and password */ if ((cp = gstrchr(userAuth, ':')) != NULL) { *cp++ = '\0'; } if (cp) { wp->userName = bstrdup(B_L, userAuth); wp->password = bstrdup(B_L, cp); } else { wp->userName = bstrdup(B_L, T("")); wp->password = bstrdup(B_L, T("")); } /* * Set the flags to indicate digest authentication */ wp->flags |= WEBS_AUTH_BASIC; } else { #ifdef DIGEST_ACCESS_SUPPORT /* * The incoming value is slightly more complicated (Digest) */ char_t *np; /* pointer to end of tag name */ char_t tp; /* temporary character holding space */ char_t *vp; /* pointer to value */ char_t *npv; /* pointer to end of value, "next" pointer */ char_t tpv; /* temporary character holding space */ /* * Set the flags to indicate digest authentication */ wp->flags |= WEBS_AUTH_DIGEST; /* * Move cp to Next word beyond "Digest", * vp to first char after '='. */ cp = value; while (isgoodchar(*cp)) { cp++; } while (!isgoodchar(*cp)) { cp++; } /* * Find beginning of value */ vp = gstrchr(cp, '='); while (vp) { /* * Zero-terminate tag name */ np = cp; while (isgoodchar(*np)) { np++; } tp = *np; *np = 0; /* * Advance value pointer to first legit character */ vp++; while (!isgoodchar(*vp)) { vp++; } /* * Zero-terminate value */ npv = vp; while (isgoodchar(*npv)) { npv++; } tpv = *npv; *npv = 0; /* * Extract the fields */ if (gstricmp(cp, T("username")) == 0) { wp->userName = bstrdup(B_L, vp); } else if (gstricmp(cp, T("response")) == 0) { wp->digest = bstrdup(B_L, vp); } else if (gstricmp(cp, T("opaque")) == 0) { wp->opaque = bstrdup(B_L, vp); } else if (gstricmp(cp, T("uri")) == 0) { wp->uri = bstrdup(B_L, vp); } else if (gstricmp(cp, T("realm")) == 0) { wp->realm = bstrdup(B_L, vp); } else if (gstricmp(cp, T("nonce")) == 0) { wp->nonce = bstrdup(B_L, vp); } else if (gstricmp(cp, T("nc")) == 0) { wp->nc = bstrdup(B_L, vp); } else if (gstricmp(cp, T("cnonce")) == 0) { wp->cnonce = bstrdup(B_L, vp); } else if (gstricmp(cp, T("qop")) == 0) { wp->qop = bstrdup(B_L, vp); } /* * Restore tag name and value zero-terminations */ *np = tp; *npv = tpv; /* * Advance tag name and value pointers */ cp = npv; while (*cp && isgoodchar(*cp)) { cp++; } while (*cp && !isgoodchar(*cp)) { cp++; } if (*cp) { vp = gstrchr(cp, '='); } else { vp = NULL; } } #endif /* DIGEST_ACCESS_SUPPORT */ } /* if (gstrcmp(wp->authType)) */ /* * Parse the content length */ } else if (gstrcmp(key, T("content-length")) == 0) { /* * 11 Oct 02 BgP -- The server would crash if an attacker sent a POST * message with a content-length value <= 0. We assume that anyone * sending this is malicious, and the POST is read from the socket, * but it is ignored, and the socket is closed. */ wp->clen = gatoi(value); if (wp->clen > 0) { wp->flags |= WEBS_CLEN; websSetVar(wp, T("CONTENT_LENGTH"), value); } else { wp->clen = 0; } /* * Parse the content type */ } else if (gstrcmp(key, T("content-type")) == 0) { websSetVar(wp, T("CONTENT_TYPE"), value); #ifdef WEBS_KEEP_ALIVE_SUPPORT } else if (gstrcmp(key, T("connection")) == 0) { strlower(value); if (gstrcmp(value, T("keep-alive")) == 0) { wp->flags |= WEBS_KEEP_ALIVE; } #endif #ifdef WEBS_PROXY_SUPPORT /* * This may be useful if you wish to keep a local cache of web pages * for proxied requests. */ } else if (gstrcmp(key, T("pragma")) == 0) { char_t tmp[256]; gstrncpy(tmp, value, TSZ(tmp)); strlower(tmp); if (gstrstr(tmp, T("no-cache"))) { wp->flags |= WEBS_DONT_USE_CACHE; } #endif /* WEBS_PROXY_SUPPORT */ /* * Store the cookie */ } else if (gstrcmp(key, T("cookie")) == 0) { wp->flags |= WEBS_COOKIE; wp->cookie = bstrdup(B_L, value); #ifdef WEBS_IF_MODIFIED_SUPPORT /* * See if the local page has been modified since the browser last * requested this document. If not, just return a 302 */ } else if (gstrcmp(key, T("if-modified-since")) == 0) { char_t *cmd; time_t tip = 0; if ((cp = gstrchr(value, ';')) != NULL) { *cp = '\0'; } fmtAlloc(&cmd, 64, T("%s"), value); if ((wp->since = dateParse(tip, cmd)) != 0) { wp->flags |= WEBS_IF_MODIFIED; } bfreeSafe(B_L, cmd); #endif /* WEBS_IF_MODIFIED_SUPPORT */ } } } /******************************************************************************/ /* * 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. */ void websSetEnv(webs_t wp) { char_t portBuf[8]; char_t *keyword, *value, *valCheck, *valNew; a_assert(websValid(wp)); websSetVar(wp, T("QUERY_STRING"), wp->query); websSetVar(wp, T("GATEWAY_INTERFACE"), T("CGI/1.1")); websSetVar(wp, T("SERVER_HOST"), websHost); websSetVar(wp, T("SERVER_NAME"), websHost); websSetVar(wp, T("SERVER_URL"), websHostUrl); websSetVar(wp, T("REMOTE_HOST"), wp->ipaddr); websSetVar(wp, T("REMOTE_ADDR"), wp->ipaddr); websSetVar(wp, T("PATH_INFO"), wp->path); stritoa(websPort, portBuf, sizeof(portBuf)); websSetVar(wp, T("SERVER_PORT"), portBuf); websSetVar(wp, T("SERVER_ADDR"), websIpaddr); fmtAlloc(&value, FNAMESIZE, T("%s/%s"), WEBS_NAME, WEBS_VERSION); websSetVar(wp, T("SERVER_SOFTWARE"), value); bfreeSafe(B_L, value); websSetVar(wp, T("SERVER_PROTOCOL"), wp->protoVersion); /* * 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. */ wp->decodedQuery = bstrdup(B_L, wp->query); keyword = gstrtok(wp->decodedQuery, T("&")); while (keyword != NULL) { if ((value = gstrchr(keyword, '=')) != NULL) { *value++ = '\0'; websDecodeUrl(keyword, keyword, gstrlen(keyword)); websDecodeUrl(value, value, gstrlen(value)); } else { value = T(""); } if (*keyword) { /* * If keyword has already been set, append the new value to what has * been stored. */ if ((valCheck = websGetVar(wp, keyword, NULL)) != 0) { fmtAlloc(&valNew, 256, T("%s %s"), valCheck, value); websSetVar(wp, keyword, valNew); bfreeSafe(B_L, valNew); } else { websSetVar(wp, keyword, value); } } keyword = gstrtok(NULL, T("&")); } #ifdef EMF /* * Add GoAhead Embedded Management Framework defines */ websSetEmfEnvironment(wp); #endif } /******************************************************************************/ /* * Define a webs (CGI) variable for this connection. Also create in relevant * scripting engines. Note: the incoming value may be volatile. */ void websSetVar(webs_t wp, char_t *var, char_t *value) { value_t v; a_assert(websValid(wp)); /* * value_instring will allocate the string if required. */ if (value) { v = valueString(value, VALUE_ALLOCATE); } else { v = valueString(T(""), VALUE_ALLOCATE); } symEnter(wp->cgiVars, var, v, 0); } /******************************************************************************/ /* * Return TRUE if a webs variable exists for this connection. */ int websTestVar(webs_t wp, char_t *var) { sym_t *sp; a_assert(websValid(wp)); if (var == NULL || *var == '\0') { return 0; } if ((sp = symLookup(wp->cgiVars, 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. */ char_t *websGetVar(webs_t wp, char_t *var, char_t *defaultGetValue) { sym_t *sp; a_assert(websValid(wp)); a_assert(var && *var); if ((sp = symLookup(wp->cgiVars, var)) != NULL) { a_assert(sp->content.type == string); if (sp->content.value.string) { return sp->content.value.string; } else { return T(""); } } return defaultGetValue; } /******************************************************************************/ /* * Return TRUE if a webs variable is set to a given value */ int websCompareVar(webs_t wp, char_t *var, char_t *value) { a_assert(websValid(wp)); a_assert(var && *var); if (gstrcmp(value, websGetVar(wp, var, T(" __UNDEF__ "))) == 0) { return 1; } return 0; } /******************************************************************************/ /* * Cancel the request timeout. Note may be called multiple times. */ void websTimeoutCancel(webs_t wp) { a_assert(websValid(wp)); if (wp->timeout >= 0) { emfUnschedCallback(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. */ void websResponse(webs_t wp, int code, char_t *message, char_t *redirect) { char_t *date; a_assert(websValid(wp)); /* * IE3.0 needs no Keep Alive for some return codes. */ wp->flags &= ~WEBS_KEEP_ALIVE; /* * Only output the header if a header has not already been output. */ if ( !(wp->flags & WEBS_HEADER_DONE)) { wp->flags |= WEBS_HEADER_DONE; /* * Redirect behaves much better when sent with HTTP/1.0 */ if (redirect != NULL) { websWrite(wp, T("HTTP/1.0 %d %s\r\n"), code, websErrorMsg(code)); } else { websWrite(wp, T("HTTP/1.1 %d %s\r\n"), code, websErrorMsg(code)); } /* * By license terms the following line of code must not be modified. */ websWrite(wp, T("Server: %s\r\n"), WEBS_NAME); /* * Timestamp/Date is usually the next to go */ if ((date = websGetDateString(NULL)) != NULL) { websWrite(wp, T("Date: %s\r\n"), date); bfree(B_L, date); } /* * If authentication is required, send the auth header info */ if (code == 401) { if (!(wp->flags & WEBS_AUTH_DIGEST)) { websWrite(wp, T("WWW-Authenticate: Basic realm=\"%s\"\r\n"), websGetRealm()); #ifdef DIGEST_ACCESS_SUPPORT } else { char_t *nonce, *opaque; /* $$$ before... (note commas instead of semicolons...) nonce = websCalcNonce(wp), opaque = websCalcOpaque(wp), $$$ after */ nonce = websCalcNonce(wp); opaque = websCalcOpaque(wp); /* ...$$$ end */ websWrite(wp, T("WWW-Authenticate: Digest realm=\"%s\", domain=\"%s\",") T("qop=\"%s\", nonce=\"%s\", opaque=\"%s\",") T("algorithm=\"%s\", stale=\"%s\"\r\n"), websGetRealm(), websGetHostUrl(), T("auth"), nonce, opaque, T("MD5"), T("FALSE")); bfree(B_L, nonce); bfree(B_L, opaque); #endif } } if (wp->flags & WEBS_KEEP_ALIVE) { websWrite(wp, T("Connection: keep-alive\r\n")); } websWrite(wp, T("Pragma: no-cache\r\nCache-Control: no-cache\r\n")); websWrite(wp, T("Content-Type: text/html\r\n")); /* * We don't do a string length here as the message may be multi-line. * Ie. will count as only one and we will have a content-length * that is too short. * * websWrite(wp, T("Content-Length: %s\r\n"), message); */ if (redirect) { websWrite(wp, T("Location: %s\r\n"), redirect); } websWrite(wp, T("\r\n")); } /* * If the browser didn't do a HEAD only request, send the message as well. */ if ((wp->flags & WEBS_HEAD_REQUEST) == 0 && message && *message) { websWrite(wp, T("%s\r\n"), message); } websDone(wp, code); } /******************************************************************************/ /* * Redirect the user to another webs page */ void websRedirect(webs_t wp, char_t *url) { char_t *msgbuf, *urlbuf, *redirectFmt; a_assert(websValid(wp)); a_assert(url); websStats.redirects++; msgbuf = urlbuf = NULL; /* * Some browsers require a http://host qualified URL for redirection */ if (gstrstr(url, T("http://")) == NULL) { if (*url == '/') { url++; } redirectFmt = T("http://%s/%s"); #ifdef WEBS_SSL_SUPPORT if (wp->flags & WEBS_SECURE) { redirectFmt = T("https://%s/%s"); } #endif fmtAlloc(&urlbuf, WEBS_MAX_URL + 80, redirectFmt, websGetVar(wp, T("HTTP_HOST"), websHostUrl), url); url = urlbuf; } /* * Add human readable message for completeness. Should not be required. */ fmtAlloc(&msgbuf, WEBS_MAX_URL + 80, T("\r\n\ This document has moved to a new location.\r\n\ Please update your documents to reflect the new location.\r\n\ \r\n"), url); websResponse(wp, 302, msgbuf, url); bfreeSafe(B_L, msgbuf); bfreeSafe(B_L, urlbuf); } /******************************************************************************/ /* * Output an error message and cleanup */ #ifdef qRichErrorPage extern int dmfRichError(webs_t wp, int code, char_t* userMsg); #endif void websError(webs_t wp, int code, char_t *fmt, ...) { va_list args; char_t *msg, *userMsg, *buf; #ifdef qRichErrorPage static int reEntry = 0; int errorOk; #endif a_assert(websValid(wp)); a_assert(fmt); websStats.errors++; va_start(args, fmt); userMsg = NULL; fmtValloc(&userMsg, WEBS_BUFSIZE, fmt, args); va_end(args); #ifdef qRichErrorPage if (!reEntry) { /* * The dmfRichError function that we're about to call may very well call * websError() as part of its work. If that happens, we do NOT want to * get into a never-ending recursive call chain. When we get back here * in a call from inside dmfRichError(), we check to see if we're * already trying to call dmfRichError. If we are, we just revert to the * old non-rich behavior and display a black on white error page. */ reEntry = 1; errorOk = dmfRichError(wp, code, userMsg); reEntry = 0; if (errorOk) { return; } /* ...else we need to fall through and execute the simple error page. */ } /* implicit else... */ #endif msg = T("Document Error: %s\r\n\

Access Error: %s

\r\n\ when trying to obtain %s

%s

\r\n"); /* * Ensure we have plenty of room */ buf = NULL; fmtAlloc(&buf, WEBS_BUFSIZE, msg, websErrorMsg(code), websErrorMsg(code), wp->url, userMsg); websResponse(wp, code, buf, NULL); bfreeSafe(B_L, buf); bfreeSafe(B_L, userMsg); } /******************************************************************************/ /* * Return the error message for a given code */ /*static char_t *websErrorMsg(int code)*/ char_t *websErrorMsg(int code) { websErrorType *ep; for (ep = websErrors; ep->code; ep++) { if (code == ep->code) { return ep->msg; } } a_assert(0); return T(""); } /******************************************************************************/ /* * Do formatted output to the browser. This is the public ASP and form * write procedure. */ int websWrite(webs_t wp, char_t *fmt, ...) { va_list vargs; char_t *buf; int rc; a_assert(websValid(wp)); va_start(vargs, fmt); buf = NULL; rc = 0; if (fmtValloc(&buf, WEBS_BUFSIZE, fmt, vargs) >= WEBS_BUFSIZE) { trace(0, T("webs: websWrite lost data, buffer overflow\n")); } va_end(vargs); a_assert(buf); if (buf) { rc = websWriteBlock(wp, buf, gstrlen(buf)); bfree(B_L, buf); } return rc; } /******************************************************************************/ /* * Write a block of data of length "nChars" to the user's browser. Public * write block procedure. If unicode is turned on this function expects * buf to be a unicode string and it converts it to ASCII before writing. * See websWriteDataNonBlock to always write binary or ASCII data with no * unicode conversion. This returns the number of char_t's processed. * It spins until nChars are flushed to the socket. For non-blocking * behavior, use websWriteDataNonBlock. */ int websWriteBlock(webs_t wp, char_t *buf, int nChars) { int len, done; char *asciiBuf, *pBuf; a_assert(wp); a_assert(websValid(wp)); a_assert(buf); a_assert(nChars >= 0); done = len = 0; /* * ballocUniToAsc will convert Unicode to strings to Ascii. If Unicode is * not turned on then ballocUniToAsc will not do the conversion. */ pBuf = asciiBuf = ballocUniToAsc(buf, nChars); while (nChars > 0) { #ifdef WEBS_SSL_SUPPORT if (wp->flags & WEBS_SECURE) { if ((len = websSSLWrite(wp->wsp, pBuf, nChars)) < 0) { bfree(B_L, asciiBuf); return -1; } websSSLFlush(wp->wsp); } else { if ((len = socketWrite(wp->sid, pBuf, nChars)) < 0) { bfree(B_L, asciiBuf); return -1; } socketFlush(wp->sid); } #else /* ! WEBS_SSL_SUPPORT */ if ((len = socketWrite(wp->sid, pBuf, nChars)) < 0) { bfree(B_L, asciiBuf); return -1; } socketFlush(wp->sid); #endif /* WEBS_SSL_SUPPORT */ nChars -= len; pBuf += len; done += len; } bfree(B_L, asciiBuf); return done; } /******************************************************************************/ /* * Write a block of data of length "nChars" to the user's browser. Same as * websWriteBlock except that it expects straight ASCII or binary and does no * unicode conversion before writing the data. If the socket cannot hold all * the data, it will return the number of bytes flushed to the socket before * it would have blocked. This returns the number of chars processed or -1 * if socketWrite fails. */ int websWriteDataNonBlock(webs_t wp, char *buf, int nChars) { int r; a_assert(wp); a_assert(websValid(wp)); a_assert(buf); a_assert(nChars >= 0); #ifdef WEBS_SSL_SUPPORT if (wp->flags & WEBS_SECURE) { r = websSSLWrite(wp->wsp, buf, nChars); websSSLFlush(wp->wsp); } else { r = socketWrite(wp->sid, buf, nChars); socketFlush(wp->sid); } #else r = socketWrite(wp->sid, buf, nChars); socketFlush(wp->sid); #endif return r; } /******************************************************************************/ /* * Decode a URL (or part thereof). Allows insitu decoding. */ void websDecodeUrl(char_t *decoded, char_t *token, int len) { char_t *ip, *op; int num, i, c; a_assert(decoded); a_assert(token); op = decoded; for (ip = token; *ip && len > 0; ip++, op++) { if (*ip == '+') { *op = ' '; } else if (*ip == '%' && gisxdigit(ip[1]) && gisxdigit(ip[2])) { /* * Convert %nn to a single character */ ip++; for (i = 0, num = 0; i < 2; i++, ip++) { c = tolower(*ip); if (c >= 'a' && c <= 'f') { num = (num * 16) + 10 + c - 'a'; } else { num = (num * 16) + c - '0'; } } *op = (char_t) num; ip--; } else { *op = *ip; } len--; } *op = '\0'; } /******************************************************************************/ #ifdef WEBS_LOG_SUPPORT /* * Output a log message */ static void websLog(webs_t wp, int code) { char_t *buf; char *abuf; int len; #define qAnlLog 1 #ifdef qAnlLog time_t timer; char_t* newLine = NULL; char_t* timeStr = NULL; #endif a_assert(websValid(wp)); buf = NULL; #ifdef qAnlLog time(&timer); timeStr = ctime(&timer); newLine = gstrchr(timeStr, '\n'); if (newLine) { *newLine = '\0'; } fmtAlloc(&buf, WEBS_MAX_URL + 80, T("%s\t%s\t%s\tcode = %d\n"), timeStr, wp->ipaddr, wp->url, code); #else fmtAlloc(&buf, WEBS_MAX_URL + 80, T("%d %s %d %d\n"), time(0), wp->url, code, wp->written); #endif len = gstrlen(buf); abuf = ballocUniToAsc(buf, len+1); write(websLogFd, abuf, len); bfreeSafe(B_L, buf); bfreeSafe(B_L, abuf); } #endif /* WEBS_LOG_SUPPORT */ /******************************************************************************/ /* * Request 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. */ void websTimeout(void *arg, int id) { webs_t wp; int delay, tm; wp = (webs_t) arg; a_assert(websValid(wp)); tm = websGetTimeSinceMark(wp) * 1000; if (tm >= WEBS_TIMEOUT) { websStats.timeouts++; emfUnschedCallback(id); /* * Clear the timeout id */ wp->timeout = -1; websDone(wp, 404); } else { delay = WEBS_TIMEOUT - tm; a_assert(delay > 0); emfReschedCallback(id, delay); } } /******************************************************************************/ /* * Called when the request is done. */ void websDone(webs_t wp, int code) { a_assert(websValid(wp)); /* * Disable socket handler in case keep alive set. */ socketDeleteHandler(wp->sid); if (code != 200) { wp->flags &= ~WEBS_KEEP_ALIVE; } #ifdef WEBS_PROXY_SUPPORT if (! (wp->flags & WEBS_LOCAL_PAGE)) { websStats.activeNetRequests--; } #endif #ifdef WEBS_LOG_SUPPORT if (! (wp->flags & WEBS_REQUEST_DONE)) { websLog(wp, code); } #endif /* * Close any opened document by a handler */ websPageClose(wp); /* * Exit if secure. */ #ifdef WEBS_SSL_SUPPORT if (wp->flags & WEBS_SECURE) { websTimeoutCancel(wp); websSSLFlush(wp->wsp); socketCloseConnection(wp->sid); websFree(wp); return; } #endif /* * If using Keep Alive (HTTP/1.1) we keep the socket open for a period * while waiting for another request on the socket. */ if (wp->flags & WEBS_KEEP_ALIVE) { if (socketFlush(wp->sid) == 0) { wp->state = WEBS_BEGIN; wp->flags |= WEBS_REQUEST_DONE; if (wp->header.buf) { ringqFlush(&wp->header); } socketCreateHandler(wp->sid, SOCKET_READABLE, websSocketEvent, (int) wp); websTimeoutCancel(wp); wp->timeout = emfSchedCallback(WEBS_TIMEOUT, websTimeout, (void *) wp); return; } } else { websTimeoutCancel(wp); socketSetBlock(wp->sid, 1); socketFlush(wp->sid); socketCloseConnection(wp->sid); } websFree(wp); } /******************************************************************************/ /* * Allocate a new webs structure */ int websAlloc(int sid) { webs_t wp; int wid; /* * Allocate a new handle for this connection */ if ((wid = hAllocEntry((void***) &webs, &websMax, sizeof(struct websRec))) < 0) { return -1; } wp = webs[wid]; wp->wid = wid; wp->sid = sid; wp->state = WEBS_BEGIN; wp->docfd = -1; wp->timeout = -1; wp->dir = NULL; wp->authType = NULL; wp->protocol = NULL; wp->protoVersion = NULL; wp->password = NULL; wp->userName = NULL; #ifdef DIGEST_ACCESS_SUPPORT wp->realm = NULL; wp->nonce = NULL; wp->digest = NULL; wp->uri = NULL; wp->opaque = NULL; wp->nc = NULL; wp->cnonce = NULL; wp->qop = NULL; #endif #ifdef WEBS_SSL_SUPPORT wp->wsp = NULL; #endif ringqOpen(&wp->header, WEBS_HEADER_BUFINC, WEBS_MAX_HEADER); /* * Create storage for the CGI variables. We supply the symbol tables for * both the CGI variables and for the global functions. The function table * is common to all webs instances (ie. all browsers) */ wp->cgiVars = symOpen(WEBS_SYM_INIT); return wid; } /******************************************************************************/ /* * Free a webs structure */ void websFree(webs_t wp) { a_assert(websValid(wp)); if (wp->path) bfree(B_L, wp->path); if (wp->url) bfree(B_L, wp->url); if (wp->host) bfree(B_L, wp->host); if (wp->lpath) bfree(B_L, wp->lpath); if (wp->query) bfree(B_L, wp->query); if (wp->decodedQuery) bfree(B_L, wp->decodedQuery); if (wp->authType) bfree(B_L, wp->authType); if (wp->password) bfree(B_L, wp->password); if (wp->userName) bfree(B_L, wp->userName); if (wp->cookie) bfree(B_L, wp->cookie); if (wp->userAgent) bfree(B_L, wp->userAgent); if (wp->dir) bfree(B_L, wp->dir); if (wp->protocol) bfree(B_L, wp->protocol); if (wp->protoVersion) bfree(B_L, wp->protoVersion); if (wp->cgiStdin) bfree(B_L, wp->cgiStdin); #ifdef DIGEST_ACCESS_SUPPORT if (wp->realm) bfree(B_L, wp->realm); if (wp->uri) bfree(B_L, wp->uri); if (wp->digest) bfree(B_L, wp->digest); if (wp->opaque) bfree(B_L, wp->opaque); if (wp->nonce) bfree(B_L, wp->nonce); if (wp->nc) bfree(B_L, wp->nc); if (wp->cnonce) bfree(B_L, wp->cnonce); if (wp->qop) bfree(B_L, wp->qop); #endif #ifdef WEBS_SSL_SUPPORT websSSLFree(wp->wsp); #endif symClose(wp->cgiVars); if (wp->header.buf) { ringqClose(&wp->header); } websMax = hFree((void***) &webs, wp->wid); bfree(B_L, wp); a_assert(websMax >= 0); } /******************************************************************************/ /* * Return the server address */ char_t *websGetHost() { return websHost; } /******************************************************************************/ /* * Return the the url to access the server. (ip address) */ char_t *websGetIpaddrUrl() { return websIpaddrUrl; } /******************************************************************************/ /* * Return the server address */ char_t *websGetHostUrl() { return websHostUrl; } /******************************************************************************/ /* * Return the listen port */ int websGetPort() { return websPort; } /******************************************************************************/ /* * Get the number of bytes to write */ int websGetRequestBytes(webs_t wp) { a_assert(websValid(wp)); return wp->numbytes; } /******************************************************************************/ /* * Get the directory for this request */ char_t *websGetRequestDir(webs_t wp) { a_assert(websValid(wp)); if (wp->dir == NULL) { return T(""); } return wp->dir; } /******************************************************************************/ /* * Get the flags for this request */ int websGetRequestFlags(webs_t wp) { a_assert(websValid(wp)); return wp->flags; } /******************************************************************************/ /* * Return the IP address */ char_t *websGetRequestIpaddr(webs_t wp) { a_assert(websValid(wp)); return wp->ipaddr; } /******************************************************************************/ /* * Set the local path for the request */ char_t *websGetRequestLpath(webs_t wp) { a_assert(websValid(wp)); #ifdef WEBS_PAGE_ROM return wp->path; #else return wp->lpath; #endif } /******************************************************************************/ /* * Get the path for this request */ char_t *websGetRequestPath(webs_t wp) { a_assert(websValid(wp)); if (wp->path == NULL) { return T(""); } return wp->path; } /******************************************************************************/ /* * Return the password */ char_t *websGetRequestPassword(webs_t wp) { a_assert(websValid(wp)); return wp->password; } /******************************************************************************/ /* * Return the request type */ char_t *websGetRequestType(webs_t wp) { a_assert(websValid(wp)); return wp->type; } /******************************************************************************/ /* * Return the username */ char_t *websGetRequestUserName(webs_t wp) { a_assert(websValid(wp)); return wp->userName; } /******************************************************************************/ /* * Get the number of bytes written */ int websGetRequestWritten(webs_t wp) { a_assert(websValid(wp)); return wp->written; } /******************************************************************************/ /* * Set the hostname */ void websSetHost(char_t *host) { gstrncpy(websHost, host, TSZ(websHost)); } /******************************************************************************/ /* * Set the host URL */ void websSetHostUrl(char_t *url) { a_assert(url && *url); bfreeSafe(B_L, websHostUrl); websHostUrl = gstrdup(B_L, url); } /******************************************************************************/ /* * Set the IP address */ void websSetIpaddr(char_t *ipaddr) { a_assert(ipaddr && *ipaddr); gstrncpy(websIpaddr, ipaddr, TSZ(websIpaddr)); } /******************************************************************************/ /* * Set the number of bytes to write */ void websSetRequestBytes(webs_t wp, int bytes) { a_assert(websValid(wp)); a_assert(bytes >= 0); wp->numbytes = bytes; } /******************************************************************************/ /* * Set the flags for this request */ void websSetRequestFlags(webs_t wp, int flags) { a_assert(websValid(wp)); wp->flags = flags; } /******************************************************************************/ /* * Set the local path for the request */ void websSetRequestLpath(webs_t wp, char_t *lpath) { a_assert(websValid(wp)); a_assert(lpath && *lpath); if (wp->lpath) { bfree(B_L, wp->lpath); } wp->lpath = bstrdup(B_L, lpath); websSetVar(wp, T("PATH_TRANSLATED"), wp->lpath); } /******************************************************************************/ /* * Update the URL path and the directory containing the web page */ void websSetRequestPath(webs_t wp, char_t *dir, char_t *path) { char_t *tmp; a_assert(websValid(wp)); if (dir) { tmp = wp->dir; wp->dir = bstrdup(B_L, dir); if (tmp) { bfree(B_L, tmp); } } if (path) { tmp = wp->path; wp->path = bstrdup(B_L, path); websSetVar(wp, T("PATH_INFO"), wp->path); if (tmp) { bfree(B_L, tmp); } } } /******************************************************************************/ /* * Set the Write handler for this socket */ void websSetRequestSocketHandler(webs_t wp, int mask, void (*fn)(webs_t wp)) { a_assert(websValid(wp)); wp->writeSocket = fn; socketCreateHandler(wp->sid, SOCKET_WRITABLE, websSocketEvent, (int) wp); } /******************************************************************************/ /* * Set the number of bytes written */ void websSetRequestWritten(webs_t wp, int written) { a_assert(websValid(wp)); wp->written = written; } /******************************************************************************/ /* * Reurn true if the webs handle is valid */ int websValid(webs_t 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; */ char_t *websGetDateString(websStatType *sbuf) { char_t* cp, *r; time_t now; if (sbuf == NULL) { time(&now); } else { now = sbuf->mtime; } if ((cp = gctime(&now)) != NULL) { cp[gstrlen(cp) - 1] = '\0'; r = bstrdup(B_L, cp); return r; } return NULL; } /******************************************************************************/ /* * Mark time. Set a timestamp so that, later, we can return the number of * seconds since we made the mark. Note that the mark my not be a * "real" time, but rather a relative marker. */ void websSetTimeMark(webs_t wp) { wp->timestamp = time(0); } /******************************************************************************/ /* * Get the number of seconds since the last mark. */ static int websGetTimeSinceMark(webs_t wp) { return time(0) - wp->timestamp; } /******************************************************************************/ /* * Store the new realm name */ void websSetRealm(char_t *realmName) { a_assert(realmName); gstrncpy(websRealm, realmName, TSZ(websRealm)); } /******************************************************************************/ /* * Return the realm name (used for authorization) */ char_t *websGetRealm() { return websRealm; } #ifdef WEBS_IF_MODIFIED_SUPPORT /******************************************************************************/ /* * These functions are intended to closely mirror the syntax for HTTP-date * from RFC 2616 (HTTP/1.1 spec). This code was submitted by Pete Bergstrom. */ /* * RFC1123Date = wkday "," SP date1 SP time SP "GMT" * RFC850Date = weekday "," SP date2 SP time SP "GMT" * ASCTimeDate = wkday SP date3 SP time SP 4DIGIT * * Each of these functions tries to parse the value and update the index to * the point it leaves off parsing. */ typedef enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } MonthEnumeration; typedef enum { SUN, MON, TUE, WED, THU, FRI, SAT } WeekdayEnumeration; /******************************************************************************/ /* * Parse an N-digit value */ static int parseNDIGIT(char_t *buf, int digits, int *index) { int tmpIndex, returnValue; returnValue = 0; for (tmpIndex = *index; tmpIndex < *index+digits; tmpIndex++) { if (gisdigit(buf[tmpIndex])) { returnValue = returnValue * 10 + (buf[tmpIndex] - T('0')); } } *index = tmpIndex; return returnValue; } /******************************************************************************/ /* * Return an index into the month array */ static int parseMonth(char_t *buf, int *index) { /* * "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | * "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec" */ int tmpIndex, returnValue; returnValue = -1; tmpIndex = *index; switch (buf[tmpIndex]) { case 'A': switch (buf[tmpIndex+1]) { case 'p': returnValue = APR; break; case 'u': returnValue = AUG; break; } break; case 'D': returnValue = DEC; break; case 'F': returnValue = FEB; break; case 'J': switch (buf[tmpIndex+1]) { case 'a': returnValue = JAN; break; case 'u': switch (buf[tmpIndex+2]) { case 'l': returnValue = JUL; break; case 'n': returnValue = JUN; break; } break; } break; case 'M': switch (buf[tmpIndex+1]) { case 'a': switch (buf[tmpIndex+2]) { case 'r': returnValue = MAR; break; case 'y': returnValue = MAY; break; } break; } break; case 'N': returnValue = NOV; break; case 'O': returnValue = OCT; break; case 'S': returnValue = SEP; break; } if (returnValue >= 0) { *index += 3; } return returnValue; } /******************************************************************************/ /* * Parse a year value (either 2 or 4 digits) */ static int parseYear(char_t *buf, int *index) { int tmpIndex, returnValue; tmpIndex = *index; returnValue = parseNDIGIT(buf, 4, &tmpIndex); if (returnValue >= 0) { *index = tmpIndex; } else { returnValue = parseNDIGIT(buf, 2, &tmpIndex); if (returnValue >= 0) { /* * Assume that any year earlier than the start of the * epoch for time_t (1970) specifies 20xx */ if (returnValue < 70) { returnValue += 2000; } else { returnValue += 1900; } *index = tmpIndex; } } return returnValue; } /******************************************************************************/ /* * The formulas used to build these functions are from "Calendrical Calculations", * by Nachum Dershowitz, Edward M. Reingold, Cambridge University Press, 1997. */ #include const int GregorianEpoch = 1; /******************************************************************************/ /* * Determine if year is a leap year */ int GregorianLeapYearP(long year) { int result; long tmp; tmp = year % 400; if ((year % 4 == 0) && (tmp != 100) && (tmp != 200) && (tmp != 300)) { result = TRUE; } else { result = FALSE; } return result; } /******************************************************************************/ /* * Return the fixed date from the gregorian date */ long FixedFromGregorian(long month, long day, long year) { long fixedDate; fixedDate = (long)(GregorianEpoch - 1 + 365 * (year - 1) + floor((year - 1) / 4.0) - floor((double)(year - 1) / 100.0) + floor((double)(year - 1) / 400.0) + floor((367.0 * ((double)month) - 362.0) / 12.0)); if (month <= 2) { fixedDate += 0; } else if (TRUE == GregorianLeapYearP(year)) { fixedDate += -1; } else { fixedDate += -2; } fixedDate += day; return fixedDate; } /******************************************************************************/ /* * Return the gregorian year from a fixed date */ long GregorianYearFromFixed(long fixedDate) { long result, d0, n400, d1, n100, d2, n4, d3, n1, d4, year; d0 = fixedDate - GregorianEpoch; n400 = (long)(floor((double)d0 / (double)146097)); d1 = d0 % 146097; n100 = (long)(floor((double)d1 / (double)36524)); d2 = d1 % 36524; n4 = (long)(floor((double)d2 / (double)1461)); d3 = d2 % 1461; n1 = (long)(floor((double)d3 / (double)365)); d4 = (d3 % 365) + 1; year = 400 * n400 + 100 * n100 + 4 * n4 + n1; if ((n100 == 4) || (n1 == 4)) { result = year; } else { result = year + 1; } return result; } /******************************************************************************/ /* * Returns the Gregorian date from a fixed date * (not needed for this use, but included for completeness */ #if 0 GregorianFromFixed(long fixedDate, long *month, long *day, long *year) { long priorDays, correction; *year = GregorianYearFromFixed(fixedDate); priorDays = fixedDate - FixedFromGregorian(1, 1, *year); if (fixedDate < FixedFromGregorian(3,1,*year)) { correction = 0; } else if (true == GregorianLeapYearP(*year)) { correction = 1; } else { correction = 2; } *month = (long)(floor((12.0 * (double)(priorDays + correction) + 373.0) / 367.0)); *day = fixedDate - FixedFromGregorian(*month, 1, *year); } #endif /******************************************************************************/ /* * Returns the difference between two Gregorian dates */ long GregorianDateDifference( long month1, long day1, long year1, long month2, long day2, long year2) { return FixedFromGregorian(month2, day2, year2) - FixedFromGregorian(month1, day1, year1); } /******************************************************************************/ /* * Return the number of seconds into the current day */ #define SECONDS_PER_DAY 24*60*60 static int parseTime(char_t *buf, int *index) { /* * Format of buf is - 2DIGIT ":" 2DIGIT ":" 2DIGIT */ int returnValue, tmpIndex, hourValue, minuteValue, secondValue; hourValue = minuteValue = secondValue = -1; returnValue = -1; tmpIndex = *index; hourValue = parseNDIGIT(buf, 2, &tmpIndex); if (hourValue >= 0) { tmpIndex++; minuteValue = parseNDIGIT(buf, 2, &tmpIndex); if (minuteValue >= 0) { tmpIndex++; secondValue = parseNDIGIT(buf, 2, &tmpIndex); } } if ((hourValue >= 0) && (minuteValue >= 0) && (secondValue >= 0)) { returnValue = (((hourValue * 60) + minuteValue) * 60) + secondValue; *index = tmpIndex; } return returnValue; } /******************************************************************************/ /* * Return the equivalent of time() given a gregorian date */ static time_t dateToTimet(int year, int month, int day) { long dayDifference; /* * Bug fix by Jeff Reeder (Jun 14, 2002): The 'month' parameter is * numbered from 0 (Jan == 0), but FixedFromGregorian() takes * months numbered from 1 (January == 1). We need to add 1 * to the month */ dayDifference = FixedFromGregorian(month + 1, day, year) - FixedFromGregorian(1, 1, 1970); return dayDifference * SECONDS_PER_DAY; } /******************************************************************************/ /* * Return the number of seconds between Jan 1, 1970 and the parsed date * (corresponds to documentation for time() function) */ static time_t parseDate1or2(char_t *buf, int *index) { /* * Format of buf is either * 2DIGIT SP month SP 4DIGIT * or * 2DIGIT "-" month "-" 2DIGIT */ int dayValue, monthValue, yearValue, tmpIndex; time_t returnValue; returnValue = (time_t) -1; tmpIndex = *index; dayValue = monthValue = yearValue = -1; if (buf[tmpIndex] == T(',')) { /* * Skip over the ", " */ tmpIndex += 2; dayValue = parseNDIGIT(buf, 2, &tmpIndex); if (dayValue >= 0) { /* * Skip over the space or hyphen */ tmpIndex++; monthValue = parseMonth(buf, &tmpIndex); if (monthValue >= 0) { /* * Skip over the space or hyphen */ tmpIndex++; yearValue = parseYear(buf, &tmpIndex); } } if ((dayValue >= 0) && (monthValue >= 0) && (yearValue >= 0)) { if (yearValue < 1970) { /* * Allow for Microsoft IE's year 1601 dates */ returnValue = 0; } else { returnValue = dateToTimet(yearValue, monthValue, dayValue); } *index = tmpIndex; } } return returnValue; } /******************************************************************************/ /* * Return the number of seconds between Jan 1, 1970 and the parsed date */ static time_t parseDate3Time(char_t *buf, int *index) { /* * Format of buf is month SP ( 2DIGIT | ( SP 1DIGIT )) */ int dayValue, monthValue, yearValue, timeValue, tmpIndex; time_t returnValue; returnValue = (time_t) -1; tmpIndex = *index; dayValue = monthValue = yearValue = timeValue = -1; monthValue = parseMonth(buf, &tmpIndex); if (monthValue >= 0) { /* * Skip over the space */ tmpIndex++; if (buf[tmpIndex] == T(' ')) { /* * Skip over this space too */ tmpIndex++; dayValue = parseNDIGIT(buf, 1, &tmpIndex); } else { dayValue = parseNDIGIT(buf, 2, &tmpIndex); } /* * Now get the time and time SP 4DIGIT */ timeValue = parseTime(buf, &tmpIndex); if (timeValue >= 0) { /* * Now grab the 4DIGIT year value */ yearValue = parseYear(buf, &tmpIndex); } } if ((dayValue >= 0) && (monthValue >= 0) && (yearValue >= 0)) { returnValue = dateToTimet(yearValue, monthValue, dayValue); returnValue += timeValue; *index = tmpIndex; } return returnValue; } /******************************************************************************/ /* * Although this looks like a trivial function, I found I was replicating the implementation * seven times in the parseWeekday function. In the interests of minimizing code size * and redundancy, it is broken out into a separate function. The cost of an extra * function call I can live with given that it should only be called once per HTTP request. */ static int bufferIndexIncrementGivenNTest(char_t *buf, int testIndex, char_t testChar, int foundIncrement, int notfoundIncrement) { if (buf[testIndex] == testChar) { return foundIncrement; } return notfoundIncrement; } /******************************************************************************/ /* * Return an index into a logical weekday array */ static int parseWeekday(char_t *buf, int *index) { /* * Format of buf is either * "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun" * or * "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday" */ int tmpIndex, returnValue; returnValue = -1; tmpIndex = *index; switch (buf[tmpIndex]) { case 'F': returnValue = FRI; *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Friday"), 3); break; case 'M': returnValue = MON; *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Monday"), 3); break; case 'S': switch (buf[tmpIndex+1]) { case 'a': returnValue = SAT; *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'u', sizeof("Saturday"), 3); break; case 'u': returnValue = SUN; *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Sunday"), 3); break; } break; case 'T': switch (buf[tmpIndex+1]) { case 'h': returnValue = THU; *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'r', sizeof("Thursday"), 3); break; case 'u': returnValue = TUE; *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 's', sizeof("Tuesday"), 3); break; } break; case 'W': returnValue = WED; *index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'n', sizeof("Wednesday"), 3); break; } return returnValue; } /******************************************************************************/ /* * Parse the date and time string. */ static time_t dateParse(time_t tip, char_t *cmd) { int index, tmpIndex, weekday, timeValue; time_t parsedValue, dateValue; parsedValue = (time_t) 0; index = timeValue = 0; weekday = parseWeekday(cmd, &index); if (weekday >= 0) { tmpIndex = index; dateValue = parseDate1or2(cmd, &tmpIndex); if (dateValue >= 0) { index = tmpIndex + 1; /* * One of these two forms is being used * wkday "," SP date1 SP time SP "GMT" * weekday "," SP date2 SP time SP "GMT" */ timeValue = parseTime(cmd, &index); if (timeValue >= 0) { /* * Now match up that "GMT" string for completeness * Compute the final value if there were no problems in the parse */ if ((weekday >= 0) && (dateValue >= 0) && (timeValue >= 0)) { parsedValue = dateValue + timeValue; } } } else { /* * Try the other form - wkday SP date3 SP time SP 4DIGIT */ tmpIndex = index; parsedValue = parseDate3Time(cmd, &tmpIndex); } } return parsedValue; } #endif /* WEBS_IF_MODIFIED_SUPPORT */ /******************************************************************************/