/* * webs.c -- GoAhead Embedded HTTP webs server * * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. * * See the file "license.txt" for usage and redistribution license requirements */ /******************************** Description *********************************/ /* * This module implements an embedded HTTP/1.1 webs server. It supports * loadable URL handlers that define the nature of URL processing performed. */ /********************************* Includes ***********************************/ #include "wsIntrn.h" /******************************** 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 */ /*********************************** Locals ***********************************/ /* * Standard HTTP error codes */ websErrorType websErrors[] = { { 200, T("Data follows") }, { 204, T("No Content") }, { 301, T("Redirect") }, { 302, T("Redirect") }, { 304, T("User local copy") }, { 400, T("Page not found") }, { 401, T("Password Required") }, { 404, T("Site or Page Not Found") }, { 405, T("Access Denied") }, { 500, T("Web Error") }, { 503, T("Site Temporarily Unavailable. Try again") }, { 0, NULL } }; #if 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 */ /**************************** Forward Declarations ****************************/ static int websAccept(int sid, char *ipaddr, int port); static int websAlloc(int sid); static char_t *websErrorMsg(int code); static void websFree(webs_t wp); static void websFreeVar(sym_t* sp); 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 websReadEvent(webs_t wp); static void websSocketEvent(int sid, int mask, int data); static void websTimeout(long wp); static void websTimeoutCancel(webs_t wp); static void websMarkTime(webs_t wp); static int websGetTimeSinceMark(webs_t wp); #if WEBS_LOG_SUPPORT static void websLog(webs_t wp, int code); #endif #if 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; a_assert(port > 0); a_assert(retries >= 0); #if WEBS_PAGE_ROM websRomOpen(); #endif /* * Create a mime type lookup table for quickly determining the content type */ websMime = symOpen(256); 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; } #if 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; /* * 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); } #if WEBS_LOG_SUPPORT if (websLogFd >= 0) { close(websLogFd); websLogFd = -1; } #endif #if WEBS_PAGE_ROM websRomClose(); #endif symClose(websMime, NULL); 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; } goahead_trace(0, T("webs: Listening for HTTP requests on port %d\n"), port); /* * Determine the full URL address to access the home page for this web server */ websPort = port; bfreeSafe(B_L, websHostUrl); websHostUrl = NULL; if (port == 80) { websHostUrl = bstrdup(B_L, websHost); } else { gsnprintf(&websHostUrl, WEBS_MAX_URL + 80, T("%s:%d"), websHost, port); } return port; } /******************************************************************************/ /* * Close webs listen port */ void websCloseListen() { if (websListenSock >= 0) { socketCloseConnection(websListenSock); websListenSock = -1; } bfreeSafe(B_L, websHostUrl); websHostUrl = NULL; } /******************************************************************************/ /* * Accept a connection */ static int websAccept(int sid, char *ipaddr, int port) { 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); ascToUni(wp->ipaddr, ipaddr, sizeof(wp->ipaddr)); /* * 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 = emfCreateTimer(WEBS_TIMEOUT, websTimeout, (long) wp); goahead_trace(5, 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 (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. */ static void websReadEvent(webs_t wp) { char_t *text; int rc, nbytes, len, done; a_assert(wp); a_assert(websValid(wp)); websMarkTime(wp); /* * Read as many lines as possible. socketGets is called to read the header * and socketRead is called to read posted data. */ text = NULL; 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 (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. */ 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) { done++; break; } done++; break; } /* * No more data so process the request */ websUrlHandlerRequest(wp); done++; break; case WEBS_POST: /* * POST without content-length specification */ 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 (text) { bfree(B_L, text); } } /******************************************************************************/ /* * Get input from the browser. Return TRUE (!0) if the request has been * handled. Return -1 on errors, 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) { nbytes = socketRead(wp->sid, buf, len); 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)) == NULL) { websError(wp, 503, T("Insufficient memory")); return -1; } } } else { nbytes = socketGets(wp->sid, &text); if (nbytes < 0) { /* * Error, EOF or incomplete */ if (socketEof(wp->sid)) { /* * 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); } } /* * 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. */ if (wp->state == WEBS_HEADER && ringqLen(&wp->header) <= 0) { websParseRequest(wp); websUrlHandlerRequest(wp); } 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; /* Get more data */ } 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, *url, *host, *query, *path, *port, *ext, *buf; 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; } /* * 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); wp->query = bstrdup(B_L, query); wp->host = bstrdup(B_L, host); wp->path = bstrdup(B_L, path); 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)); #if 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 */ static void websParseRequest(webs_t wp) { char_t *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 */ gsnprintf(&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 (gstrcmp(key, T("authorization")) == 0) { char_t password[FNAMESIZE]; /* * The incoming value is password:username */ if ((cp = gstrchr(value, ' ')) != NULL) { websDecode64(password, ++cp, sizeof(password)); } else { websDecode64(password, value, sizeof(password)); } if ((cp = gstrchr(password, ':')) != NULL) { *cp++ = '\0'; } if (cp) { wp->password = bstrdup(B_L, cp); } else { wp->password = bstrdup(B_L, T("")); } /* * Parse the content length */ } else if (gstrcmp(key, T("content-length")) == 0) { wp->flags |= WEBS_CLEN; wp->clen = gatoi(value); websSetVar(wp, T("CONTENT_LENGTH"), value); #if 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 #if 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 /* * Store the cookie */ } else if (gstrcmp(key, T("cookie")) == 0) { wp->flags |= WEBS_COOKIE; wp->cookie = bstrdup(B_L, value); #if 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, ';')) { *cp = '\0'; } if (cp = gstrstr(value, T(", "))) { cp += 2; } if (gstrstr(cp, T("GMT"))) { gsnprintf(&cmd, 64, T("clock scan %s -gmt 1"), cp); } else { gsnprintf(&cmd, 64, T("clock scan %s"), cp); } if (wp->since = dateParse(tip, cmd)) { wp->flags |= WEBS_IF_MODIFIED; } bfreeSafe(B_L, cmd); #endif } } } #if WEBS_IF_MODIFIED_SUPPORT /******************************************************************************/ /* * Parse the date and time string. */ static time_t dateParse(time_t tip, char_t *cmd) { return (time_t)0; } #endif /******************************************************************************/ /* * 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; 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_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); /* * 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) { websSetVar(wp, keyword, value); } keyword = gstrtok(NULL, T("&")); } #if 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 existance. */ 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; } /******************************************************************************/ /* * Cancel the request timeout. Note may be called multiple times. */ static void websTimeoutCancel(webs_t wp) { a_assert(websValid(wp)); if (wp->timeout) { emfDeleteTimer(wp->timeout); wp->timeout = NULL; } } /******************************************************************************/ /* * 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; websWrite(wp, T("HTTP/1.0 %d %s\r\n"), code, websErrorMsg(code)); /* by license terms the following line of code must * not be modified. */ websWrite(wp, T("Server: GoAhead-Webs\r\n")); 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")); if ((date = websGetDateString(NULL)) != NULL) { websWrite(wp, T("Date: %s\r\n"), date); bfree(B_L, date); } 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 (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; 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++; } gsnprintf(&urlbuf, WEBS_MAX_URL + 80, T("http://%s/%s"), websGetVar(wp, T("HTTP_HOST"), websHostUrl), url); url = urlbuf; } /* * Add human readable message for completeness. Should not be required. */ gsnprintf(&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 */ void websError(webs_t wp, int code, char_t *fmt, ...) { va_list args; char_t *msg, *userMsg, *buf; a_assert(websValid(wp)); a_assert(fmt); websStats.errors++; va_start(args, fmt); userMsg = NULL; gvsnprintf(&userMsg, WEBS_BUFSIZE, fmt, args); va_end(args); 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; gsnprintf(&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) { 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 (gvsnprintf(&buf, WEBS_BUFSIZE, fmt, vargs) >= WEBS_BUFSIZE) { goahead_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 websWriteBlockData to always write binary or ASCII data with no * unicode conversion. This returns the number of char_t's processed. */ int websWriteBlock(webs_t wp, char_t *buf, int nChars) { #if ! UNICODE return websWriteBlockData(wp, buf, nChars); #else int r; char *charBuf; a_assert(buf); a_assert(nChars >= 0); if ((charBuf = ballocUniToAsc(buf, nChars)) == NULL) { return -1; } r = websWriteBlockData(wp, charBuf, nChars); bfree(B_L, charBuf); return r; #endif } /******************************************************************************/ /* * 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. * This returns the number of chars processed. */ int websWriteBlockData(webs_t wp, char *buf, int nChars) { int len, done; a_assert(wp); a_assert(websValid(wp)); a_assert(buf); a_assert(nChars >= 0); done = len = 0; while (nChars > 0) { if ((len = socketWrite(wp->sid, buf, nChars)) < 0) { return -1; } /* * Block in flush if the last write could not take any more data */ socketFlush(wp->sid, len == 0); nChars -= len; buf += len; done += len; } return done; } /******************************************************************************/ /* * 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); num = 0; 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; 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'; } /******************************************************************************/ #if WEBS_LOG_SUPPORT /* * Output a log message */ static void websLog(webs_t wp, int code) { char_t *buf; char *abuf; int len; a_assert(websValid(wp)); buf = NULL; gsnprintf(&buf, WEBS_MAX_URL + 80, T("%d %s %d %d\n"), time(0), wp->url, code, wp->written); 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. */ static void websTimeout(long iwp) { webs_t wp; int delay, tm; wp = (webs_t) iwp; a_assert(websValid(wp)); tm = websGetTimeSinceMark(wp) * 1000; if (tm >= WEBS_TIMEOUT) { websStats.timeouts++; wp->timeout = NULL; websDone(wp, 404); } else { delay = WEBS_TIMEOUT - tm; a_assert(delay > 0); wp->timeout = emfCreateTimer(delay, websTimeout, (long) wp); } } /******************************************************************************/ /* * 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; } #if WEBS_PROXY_SUPPORT if (! (wp->flags & WEBS_LOCAL_PAGE)) { websStats.activeNetRequests--; } #endif #if WEBS_LOG_SUPPORT if (! (wp->flags & WEBS_REQUEST_DONE)) { websLog(wp, code); } #endif /* * Close any opened document by a handler */ websPageClose(wp); /* * 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) == 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 = emfCreateTimer(WEBS_TIMEOUT, websTimeout, (long) wp); return; } } else { websTimeoutCancel(wp); socketCloseConnection(wp->sid); } websFree(wp); } /******************************************************************************/ /* * Allocate a new webs structure */ static 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->dir = NULL; 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(64); return wid; } /******************************************************************************/ /* * Free a webs structure */ static 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->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); symClose(wp->cgiVars, websFreeVar); if (wp->header.buf) { ringqClose(&wp->header); } websMax = hFree((void***) &webs, wp->wid); bfree(B_L, wp); a_assert(websMax >= 0); } /******************************************************************************/ /* * Callback from symClose. Free the variable. */ static void websFreeVar(sym_t* sp) { valueFree(&sp->content); } /******************************************************************************/ /* * Return the server address */ char_t* websGetHost() { return websHost; } /******************************************************************************/ /* * 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)); #if 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; } /******************************************************************************/ /* * Close the document handle. */ int websCloseFileHandle(webs_t wp) { a_assert(websValid(wp)); #ifndef WEBS_PAGE_ROM if (wp->docfd >= 0) { close(wp->docfd); wp->docfd = -1; } #endif 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; char_t* 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. */ static void websMarkTime(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; } /******************************************************************************/