📄 webs.c
字号:
/* * 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: webs.c,v 1.3.6.1 2006/02/08 16:15:09 joel Exp $ *//******************************** 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("User 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 }};#if WEBS_LOG_SUPPORTstatic char_t websLogname[64] = T("log.txt"); /* Log filename */static int websLogFd; /* Log file handle */#endifstatic 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);#if WEBS_LOG_SUPPORTstatic void websLog(webs_t wp, int code);#endif#if WEBS_IF_MODIFIED_SUPPORTstatic 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);#if 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();#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; 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); }#if WEBS_LOG_SUPPORT if (websLogFd >= 0) { close(websLogFd); websLogFd = -1; }#endif#if 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, 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 = 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 (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)); 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; 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)); gwrite(fd, T("\n"), sizeof(char_t)); 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. */ 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 */ 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 */ /* Bugfix for POST DoS attack with invalid content length */ if (socketEof(wp->sid)) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -