/*
* 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;
}
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);
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_asse