From c1cdaa0ce8017b075487e6670f89eb4e715258ea Mon Sep 17 00:00:00 2001 From: Joel Sherrill Date: Wed, 27 Oct 1999 12:50:33 +0000 Subject: Patch from Emmanuel Raguet and Eric Valette to add a port of the GoAhead web server (httpd) to the RTEMS build tree. They have successfully used this BSP on i386/pc386 and PowerPC/mcp750. Mark and Joel spoke with Nick Berliner on 26 Oct 1999 about this port and got verbal approval to include it in RTEMS distributions. --- cpukit/httpd/Makefile.in | 59 ++ cpukit/httpd/asp.c | 313 +++++++ cpukit/httpd/balloc.c | 836 ++++++++++++++++++ cpukit/httpd/default.c | 389 +++++++++ cpukit/httpd/ej.h | 226 +++++ cpukit/httpd/ejlex.c | 679 +++++++++++++++ cpukit/httpd/ejparse.c | 1665 ++++++++++++++++++++++++++++++++++++ cpukit/httpd/form.c | 163 ++++ cpukit/httpd/h.c | 171 ++++ cpukit/httpd/handler.c | 284 +++++++ cpukit/httpd/mime.c | 112 +++ cpukit/httpd/misc.c | 581 +++++++++++++ cpukit/httpd/ringq.c | 537 ++++++++++++ cpukit/httpd/rom.c | 198 +++++ cpukit/httpd/rtems_webserver.h | 7 + cpukit/httpd/security.c | 109 +++ cpukit/httpd/socket.c | 991 +++++++++++++++++++++ cpukit/httpd/sym.c | 452 ++++++++++ cpukit/httpd/uemf.c | 193 +++++ cpukit/httpd/uemf.h | 666 +++++++++++++++ cpukit/httpd/url.c | 203 +++++ cpukit/httpd/value.c | 74 ++ cpukit/httpd/wbase64.c | 149 ++++ cpukit/httpd/webcomp.c | 177 ++++ cpukit/httpd/webmain.c | 410 +++++++++ cpukit/httpd/webpage.c | 138 +++ cpukit/httpd/webrom.c | 12 + cpukit/httpd/webs.c | 1841 ++++++++++++++++++++++++++++++++++++++++ cpukit/httpd/webs.h | 182 ++++ cpukit/httpd/websuemf.c | 39 + cpukit/httpd/wsIntrn.h | 268 ++++++ 31 files changed, 12124 insertions(+) create mode 100644 cpukit/httpd/Makefile.in create mode 100644 cpukit/httpd/asp.c create mode 100644 cpukit/httpd/balloc.c create mode 100644 cpukit/httpd/default.c create mode 100644 cpukit/httpd/ej.h create mode 100644 cpukit/httpd/ejlex.c create mode 100644 cpukit/httpd/ejparse.c create mode 100644 cpukit/httpd/form.c create mode 100644 cpukit/httpd/h.c create mode 100644 cpukit/httpd/handler.c create mode 100644 cpukit/httpd/mime.c create mode 100644 cpukit/httpd/misc.c create mode 100644 cpukit/httpd/ringq.c create mode 100644 cpukit/httpd/rom.c create mode 100644 cpukit/httpd/rtems_webserver.h create mode 100644 cpukit/httpd/security.c create mode 100644 cpukit/httpd/socket.c create mode 100644 cpukit/httpd/sym.c create mode 100644 cpukit/httpd/uemf.c create mode 100644 cpukit/httpd/uemf.h create mode 100644 cpukit/httpd/url.c create mode 100644 cpukit/httpd/value.c create mode 100644 cpukit/httpd/wbase64.c create mode 100644 cpukit/httpd/webcomp.c create mode 100644 cpukit/httpd/webmain.c create mode 100644 cpukit/httpd/webpage.c create mode 100644 cpukit/httpd/webrom.c create mode 100644 cpukit/httpd/webs.c create mode 100644 cpukit/httpd/webs.h create mode 100644 cpukit/httpd/websuemf.c create mode 100644 cpukit/httpd/wsIntrn.h (limited to 'cpukit/httpd') diff --git a/cpukit/httpd/Makefile.in b/cpukit/httpd/Makefile.in new file mode 100644 index 0000000000..2951acb714 --- /dev/null +++ b/cpukit/httpd/Makefile.in @@ -0,0 +1,59 @@ +# +# $Id$ +# + +@SET_MAKE@ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +top_builddir = .. +subdir = rtems_servers + +RTEMS_ROOT = @RTEMS_ROOT@ +PROJECT_ROOT = @PROJECT_ROOT@ + +VPATH = @srcdir@ + +LIBNAME = lib.a +LIB = ${ARCH}/${LIBNAME} + +# C and C++ source names, if any, go here -- minus the .c or .cc +C_PIECES=asp balloc wbase64 default ejlex ejparse form h handler mime \ + misc webpage ringq rom security socket sym uemf url value webcomp \ + webrom webs websuemf webmain +C_FILES = $(C_PIECES:%=%.c) +C_O_FILES = $(C_PIECES:%=${ARCH}/%.o) + +SRCS = $(C_FILES) +OBJS = $(C_O_FILES) + +include $(RTEMS_ROOT)/make/custom/@RTEMS_BSP@.cfg +include $(RTEMS_ROOT)/make/lib.cfg + +INSTALL_CHANGE = @INSTALL_CHANGE@ + +# +# Add local stuff here using += +# + +DEFINES += -DWEBS -DUEMF -DOS="LINUX" -DLINUX +CPPFLAGS += +CFLAGS += + +# +# Add your list of files to delete here. The config files +# already know how to delete some stuff, so you may want +# to just run 'make clean' first to see what gets missed. +# 'make clobber' already includes 'make clean' +# + +CLEAN_ADDITIONS += $(LIB) +CLOBBER_ADDITIONS += + +all: ${ARCH} $(LIB) + +$(LIB): $(SRCS) ${OBJS} + $(make-library) + +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + cd $(top_builddir) \ + && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status diff --git a/cpukit/httpd/asp.c b/cpukit/httpd/asp.c new file mode 100644 index 0000000000..4611a8f422 --- /dev/null +++ b/cpukit/httpd/asp.c @@ -0,0 +1,313 @@ +/* + * asp.c -- Active Server Page Support + * + * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * The ASP module processes ASP pages and executes embedded scripts. It + * support an open scripting architecture with in-built support for + * Ejscript(TM). + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/********************************** Locals ************************************/ + +static sym_fd_t websAspFunctions = -1; /* Symbol table of functions */ + +/***************************** Forward Declarations ***************************/ + +static char_t *strtokcmp(char_t* s1, char_t* s2); +static char_t *skipWhite(char_t *s); + +/************************************* Code ***********************************/ +/* + * Create script spaces and commands + */ + +int websAspOpen() +{ +/* + * Create the table for ASP functions + */ + websAspFunctions = symOpen(128); + +/* + * Create standard ASP commands + */ + websAspDefine(T("write"), websAspWrite); + return 0; +} + +/************************************* Code ***********************************/ +/* + * Close Asp symbol table. + */ + +void websAspClose() +{ + if (websAspFunctions != -1) { + symClose(websAspFunctions, NULL); + } +} + +/******************************************************************************/ +/* + * Process ASP requests and expand all scripting commands. We read the + * entire ASP page into memory and then process. If you have really big + * documents, it is better to make them plain HTML files rather than ASPs. + */ + +int websAspRequest(webs_t wp, char_t* lpath) +{ + websStatType sbuf; + char *rbuf; + char_t *token, *lang, *result, *path, *ep, *cp, *buf, *nextp; + char_t *last; + int rc, engine, len, ejid; + + a_assert(websValid(wp)); + a_assert(lpath && *lpath); + + rc = -1; + buf = NULL; + rbuf = NULL; + engine = EMF_SCRIPT_EJSCRIPT; + wp->flags |= WEBS_HEADER_DONE; + path = websGetRequestPath(wp); + +/* + * Create Ejscript instance incase it is needed + */ + ejid = ejOpenEngine(wp->cgiVars, websAspFunctions); + if (ejid < 0) { + websError(wp, 200, T("Can't create Ejscript engine")); + goto done; + } + ejSetUserHandle(ejid, (int) wp); + + if (websPageStat(wp, lpath, path, &sbuf) < 0) { + websError(wp, 200, T("Can't stat %s"), lpath); + goto done; + } + +/* + * Create a buffer to hold the ASP file in-memory + */ + len = sbuf.size * sizeof(char); + if ((rbuf = balloc(B_L, len + 1)) == NULL) { + websError(wp, 200, T("Can't get memory")); + goto done; + } + rbuf[len] = '\0'; + + if (websPageReadData(wp, rbuf, len) != len) { + websError(wp, 200, T("Cant read %s"), lpath); + goto done; + } + websCloseFileHandle(wp); + +/* + * Convert to UNICODE if necessary. + */ + if ((buf = ballocAscToUni(rbuf)) == NULL) { + websError(wp, 200, T("Can't get memory")); + goto done; + } + +/* + * Scan for the next "<%" + */ + last = buf; + rc = 0; + while (rc == 0 && *last && ((nextp = gstrstr(last, T("<%"))) != NULL)) { + websWriteBlock(wp, last, (nextp - last)); + nextp = skipWhite(nextp + 2); + +/* + * Decode the language + */ + token = T("language"); + + if ((lang = strtokcmp(nextp, token)) != NULL) { + if ((cp = strtokcmp(lang, T("=javascript"))) != NULL) { + engine = EMF_SCRIPT_EJSCRIPT; + } else { + cp = nextp; + } + nextp = cp; + } + +/* + * Find tailing bracket and then evaluate the script + */ + if ((ep = gstrstr(nextp, T("%>"))) != NULL) { + + *ep = '\0'; + last = ep + 2; + nextp = skipWhite(nextp); +/* + * Handle backquoted newlines + */ + for (cp = nextp; *cp; ) { + if (*cp == '\\' && (cp[1] == '\r' || cp[1] == '\n')) { + *cp++ = ' '; + while (*cp == '\r' || *cp == '\n') { + *cp++ = ' '; + } + } else { + cp++; + } + } + +/* + * Now call the relevant script engine. Output is done directly + * by the ASP script procedure by calling websWrite() + */ + if (*nextp) { + result = NULL; + if (engine == EMF_SCRIPT_EJSCRIPT) { + rc = scriptEval(engine, nextp, &result, ejid); + } else { + rc = scriptEval(engine, nextp, &result, (int) wp); + } + if (rc < 0) { +/* + * On an error, discard all output accumulated so far + * and store the error in the result buffer. Be careful if the + * user has called websError() already. + */ + if (websValid(wp)) { + if (result) { + websWrite(wp, T("

ASP Error: %s

\n"), + result); + websWrite(wp, T("
%s
"), nextp); + bfree(B_L, result); + } else { + websWrite(wp, T("

ASP Error

\n%s\n"), + nextp); + } + websWrite(wp, T("\n")); + rc = 0; + } + goto done; + } + } + + } else { + websError(wp, 200, T("Unterminated script in %s: \n"), lpath); + rc = -1; + goto done; + } + } +/* + * Output any trailing HTML page text + */ + if (last && *last && rc == 0) { + websWriteBlock(wp, last, gstrlen(last)); + } + rc = 0; + +/* + * Common exit and cleanup + */ +done: + if (websValid(wp)) { + websCloseFileHandle(wp); + if (ejid >= 0) { + ejCloseEngine(ejid); + } + } + bfreeSafe(B_L, buf); + bfreeSafe(B_L, rbuf); + return rc; +} + +/******************************************************************************/ +/* + * Define an ASP Ejscript function. Bind an ASP name to a C procedure. + */ + +int websAspDefine(char_t *name, + int (*fn)(int ejid, webs_t wp, int argc, char_t **argv)) +{ + return ejSetGlobalFunctionDirect(websAspFunctions, name, + (int (*)(int, void*, int, char_t**)) fn); +} + +/******************************************************************************/ +/* + * Asp write command. This implemements <% write("text"); %> command + */ + +int websAspWrite(int ejid, webs_t wp, int argc, char_t **argv) +{ + int i; + + a_assert(websValid(wp)); + a_assert(argv); + + for (i = 0; i < argc; ) { + if (websWriteBlock(wp, argv[i], gstrlen(argv[i])) < 0) { + return -1; + } + if (++i < argc) { + if (websWriteBlock(wp, T(" "), 2) < 0) { + return -1; + } + } + } + return 0; +} + +/******************************************************************************/ +/* + * strtokcmp -- Find s2 in s1. We skip leading white space in s1. + * Return a pointer to the location in s1 after s2 ends. + */ + +static char_t* strtokcmp(char_t* s1, char_t* s2) +{ + int len; + + s1 = skipWhite(s1); + len = gstrlen(s2); + for (len = gstrlen(s2); len > 0 && (tolower(*s1) == tolower(*s2)); len--) { + if (*s2 == '\0') { + return s1; + } + s1++; + s2++; + } + if (len == 0) { + return s1; + } + return NULL; +} + +/******************************************************************************/ +/* + * Skip white space + */ + +static char_t *skipWhite(char_t *s) +{ + a_assert(s); + + if (s == NULL) { + return s; + } + while (*s && gisspace(*s)) { + s++; + } + return s; +} + +/******************************************************************************/ \ No newline at end of file diff --git a/cpukit/httpd/balloc.c b/cpukit/httpd/balloc.c new file mode 100644 index 0000000000..20612ed148 --- /dev/null +++ b/cpukit/httpd/balloc.c @@ -0,0 +1,836 @@ +/* + * balloc.c -- Block allocation module + * + * Copyright (c) GoAhead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * This module implements a very fast block allocation scheme suitable for + * ROMed environments. It maintains block class queues for rapid allocation + * and minimal fragmentation. This modules does not coalesce blocks. The + * storage space may be populated statically or via the traditional malloc + * mechanisms. Large blocks greater than the maximum class size may be + * allocated from the O/S or run-time system via malloc. To permit the use + * of malloc, call bopen with flags set to B_USE_MALLOC (this is the default). + * It is recommended that bopen be called first thing in the application. + * If it is not, it will be called with default values on the first call to + * balloc(). Note that this code is not designed for multi-threading purposes + * and it depends on newly declared variables being initialized to zero. + */ + +/********************************* Includes ***********************************/ + +#define IN_BALLOC + +#if UEMF + #include "uemf.h" +#else + #include "basic/basicInternal.h" +#endif + +#include +#include + +#if !NO_BALLOC +/********************************* Defines ************************************/ + +typedef struct { + union { + void *next; /* Pointer to next in q */ + int size; /* Actual requested size */ + } u; + int flags; /* Per block allocation flags */ +} bType; + +/* + * Define B_STATS if you wish to track memory block and stack usage + */ +#if B_STATS +/* + * Optional statistics + */ + +typedef struct { + long alloc; /* Block allocation calls */ + long inuse; /* Blocks in use */ +} bStatsType; + +typedef struct { + char_t file[FNAMESIZE]; + long allocated; /* Bytes currently allocated */ + long count; /* Current block count */ + long allocs; /* Count of alloc attempts */ +} bStatsFileType; + +/* + * This one is very expensive but great stats + */ +typedef struct { + void *ptr; /* Pointer to memory */ + bStatsFileType *who; /* Who allocated the memory */ +} bStatsBlkType; + +static bStatsType bStats[B_MAX_CLASS]; /* Per class stats */ +static bStatsFileType bStatsFiles[B_MAX_FILES];/* Per file stats */ +static bStatsBlkType bStatsBlks[B_MAX_BLOCKS];/* Per block stats */ +static int bStatsBlksMax; /* Max block entry */ +static int bStatsFilesMax; /* Max file entry */ +static int bStatsMemInUse; /* Memory currently in use */ +static int bStatsMemMax; /* Max memory ever used */ +static void *bStackMin = (void*) -1;/* Miniumum stack position */ +static void *bStackStart; /* Starting stack position */ +static int bStatsMemMalloc; /* Malloced memory */ +#endif /* B_STATS */ + + +/********************************** Locals ************************************/ +/* + * bQhead blocks are created as the original memory allocation is freed up. + * See bfree. + */ +static bType *bQhead[B_MAX_CLASS]; /* Per class block q head */ +static char *bFreeBuf; /* Pointer to free memory */ +static char *bFreeNext; /* Pointer to next free mem */ +static int bFreeSize; /* Size of free memory */ +static int bFreeLeft; /* Size of free left for use */ +static int bFlags = B_USE_MALLOC; /* Default to auto-malloc */ + +/*************************** Forward Declarations *****************************/ + +#if B_STATS +static void bStatsAlloc(B_ARGS_DEC, void *ptr, int q, int size); +static void bStatsFree(B_ARGS_DEC, void *ptr, int q, int size); +static void bstatsWrite(int handle, char_t *fmt, ...); +static int bStatsFileSort(const void *cp1, const void *cp2); +#endif /* B_STATS */ + +#if B_FILL || B_VERIFY_CAUSES_SEVERE_OVERHEAD +static void bFillBlock(void *buf, int bufsize); +#endif + +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD +static void verifyUsedBlock(bType *bp, int q); +static void verifyFreeBlock(bType *bp, int q); +static void verifyBallocSpace(); +#endif + +/********************************** Code **************************************/ +/* + * Initialize the balloc module. bopen should be called the very first thing + * after the application starts and bclose should be called the last thing + * before exiting. If bopen is not called, it will be called on the first + * allocation with default values. "buf" points to memory to use of size + * "bufsize". If buf is NULL, memory is allocated using malloc. flags may + * be set to B_USE_MALLOC if using malloc is okay. This routine will allocate + * an initial buffer of size bufsize for use by the application. + */ + +int bopen(void *buf, int bufsize, int flags) +{ + bFlags = flags; + + if (buf == NULL) { + if (bufsize == 0) { + bufsize = B_DEFAULT_MEM; + } + if ((buf = malloc(bufsize)) == NULL) { + return -1; + } +#if B_STATS + bStatsMemMalloc += bufsize; +#endif + } else { + bFlags |= B_USER_BUF; + } + + bFreeSize = bFreeLeft = bufsize; + bFreeBuf = bFreeNext = buf; +#if B_FILL || B_VERIFY_CAUSES_SEVERE_OVERHEAD + bFillBlock(buf, bufsize); +#endif +#if B_STATS + bStackStart = &buf; +#endif +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + verifyFreeBlock(buf, bufsize); +#endif + return 0; +} + +/******************************************************************************/ +/* + * Close down the balloc module and free all malloced memory. + */ + +void bclose() +{ +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + verifyBallocSpace(); +#endif + if (! (bFlags & B_USER_BUF)) { + free(bFreeBuf); + } +} + +/******************************************************************************/ +/* + * Allocate a block of the requested size. First check the block + * queues for a suitable one. + */ + +void *balloc(B_ARGS_DEC, int size) +{ + bType *bp; + int q, memSize, mask; + +/* + * Call bopen with default values if the application has not yet done so + */ + if (bFreeBuf == NULL) { + if (bopen(NULL, B_DEFAULT_MEM , 0) < 0) { + return NULL; + } + } +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + verifyBallocSpace(); +#endif + if (size < 0) { + return NULL; + } + +/* + * Determine the relevant block queue with a block big enough -- + * include room for the block header. + */ + mask = (size + sizeof(bType)) >> B_SHIFT; + for (q = 0; mask; mask >>= 1) { + q++; + } + + a_assert(0 <= q && q <= B_MAX_CLASS); + memSize = (1 << (B_SHIFT + q)); + + if (q >= B_MAX_CLASS) { +/* + * Size if bigger than the maximum class. Malloc if use has been okayed + */ + if (bFlags & B_USE_MALLOC) { +#if B_STATS + bstats(0, NULL); +#endif + bp = (bType*) malloc(memSize); + if (bp == NULL) { + trace(0, T("B: malloc failed for %s:%d, size %d\n"), + B_ARGS, memSize); + return NULL; + } +#if B_STATS + bStatsMemMalloc += memSize; +#endif +#if B_FILL || B_VERIFY_CAUSES_SEVERE_OVERHEAD + bFillBlock(bp, memSize); +#endif + + } else { + trace(0, T("B: balloc failed for %s:%d, size %d\n"), + B_ARGS, memSize); + return NULL; + } + bp->u.size = size; + bp->flags = B_MALLOCED; + + } else if ((bp = bQhead[q]) != NULL) { +/* + * Take first block off the relevant q if non-empty + */ + bQhead[q] = bp->u.next; +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + verifyFreeBlock(bp, q); +#endif +#if B_FILL || B_VERIFY_CAUSES_SEVERE_OVERHEAD + bFillBlock(bp, memSize); +#endif + bp->u.size = size; + bp->flags = 0; + + } else { + if (bFreeLeft > memSize) { +/* + * The q was empty, and the free list has spare memory so + * create a new block out of the primary free block + */ + bp = (bType*) bFreeNext; +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + verifyFreeBlock(bp, q); +#endif + bFreeNext += memSize; + bFreeLeft -= memSize; +#if B_FILL || B_VERIFY_CAUSES_SEVERE_OVERHEAD + bFillBlock(bp, memSize); +#endif + bp->u.size = size; + bp->flags = 0; + + } else if (bFlags & B_USE_MALLOC) { + static int once = 0; + if (once++ < 20) { +#if B_STATS + bstats(0, NULL); +#endif + } +/* + * Nothing left on the primary free list, so malloc a new block + */ + if ((bp = (bType*) malloc(memSize)) == NULL) { + trace(0, T("B: malloc failed for %s:%d size %d\n"), + B_ARGS, memSize); + return NULL; + } +#if B_STATS + bStatsMemMalloc += memSize; +#endif +#if B_FILL || B_VERIFY_CAUSES_SEVERE_OVERHEAD + bFillBlock(bp, memSize); +#endif + bp->u.size = size; + bp->flags = B_MALLOCED; + + } else { + trace(0, T("B: alloc failed for %s:%d size %d\n"), B_ARGS, size); + return NULL; + } + } + +#if B_STATS + bStatsAlloc(B_ARGS, bp, q, size); +#endif + bp->flags |= B_INTEGRITY; + + return (void*) ((char*) bp + sizeof(bType)); +} + +/******************************************************************************/ +/* + * Free a block back to the relevant free q. We don't free back to the O/S + * or run time system unless the block is greater than the maximum class size. + * We also do not coalesce blocks. + */ + +void bfree(B_ARGS_DEC, void *mp) +{ + bType *bp; + int mask, q; + +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + verifyBallocSpace(); +#endif + a_assert(mp); + + bp = (bType*) ((char*) mp - sizeof(bType)); + a_assert((bp->flags & B_INTEGRITY_MASK) == B_INTEGRITY); + if ((bp->flags & B_INTEGRITY_MASK) != B_INTEGRITY) { + return; + } + +/* + * Determine the relevant block queue + */ + mask = (bp->u.size + sizeof(bType)) >> B_SHIFT; + for (q = 0; mask; mask >>= 1) { + q++; + } + a_assert(0 <= q && q <= B_MAX_CLASS); + +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + verifyUsedBlock(bp,q); +#endif + if (bp->flags & B_MALLOCED) { + free(bp); + return; + } + +#if B_STATS + bStatsFree(B_ARGS, bp, q, bp->u.size); +#endif +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD + bFillBlock(bp, 1 << (B_SHIFT + q)); +#endif + +/* + * Simply link onto the head of the relevant q + */ + bp->u.next = bQhead[q]; + bQhead[q] = bp; +} + +/******************************************************************************/ +/* + * Safe free + */ + +void bfreeSafe(B_ARGS_DEC, void *mp) +{ + if (mp) { + bfree(B_ARGS, mp); + } +} + +/******************************************************************************/ +#if UNICODE +/* + * Duplicate a string, allow NULL pointers and then dup an empty string. + */ + +char *bstrdupA(B_ARGS_DEC, char *s) +{ + char *cp; + int len; + + if (s == NULL) { + s = ""; + } + len = strlen(s) + 1; + if (cp = balloc(B_ARGS, len)) { + strcpy(cp, s); + } + return cp; +} + +#endif /* UNICODE */ +/******************************************************************************/ +/* + * Duplicate an ascii string, allow NULL pointers and then dup an empty string. + * If UNICODE, bstrdup above works with wide chars, so we need this routine + * for ascii strings. + */ + +char_t *bstrdup(B_ARGS_DEC, char_t *s) +{ + char_t *cp; + int len; + + if (s == NULL) { + s = T(""); + } + len = gstrlen(s) + 1; + if ((cp = balloc(B_ARGS, len * sizeof(char_t))) != NULL) { + gstrcpy(cp, s); + } + return cp; +} + +/******************************************************************************/ +/* + * Reallocate a block. Allow NULL pointers and just do a malloc. + * Note: if the realloc fails, we return NULL and the previous buffer is + * preserved. + */ + +void *brealloc(B_ARGS_DEC, void *mp, int newsize) +{ + bType* bp; + void *newbuf; + + if (mp == NULL) { + return balloc(B_ARGS, newsize); + } + bp = (bType*) ((char*) mp - sizeof(bType)); + a_assert((bp->flags & B_INTEGRITY_MASK) == B_INTEGRITY); + if ((newbuf = balloc(B_ARGS, newsize)) != NULL) { + memcpy(newbuf, mp, bp->u.size); + bfree(B_ARGS, mp); + } + return newbuf; +} + + +/******************************************************************************/ +#if B_FILL || B_VERIFY_CAUSES_SEVERE_OVERHEAD +/* + * Fill the block (useful during development to catch zero fill assumptions) + */ + +static void bFillBlock(void *buf, int bufsize) +{ + memset(buf, B_FILL_CHAR, bufsize); +} +#endif + +/******************************************************************************/ +#if B_STATS +/* + * Statistics. Do output via calling the writefn callback function with + * "handle" as the output file handle. + */ + +void bstats(int handle, void (*writefn)(int handle, char_t *fmt, ...)) +{ + bStatsFileType *fp; + bType *bp; + int q, count, mem, total; + static int recurseProtect = 0; + + if (recurseProtect++ > 0) { + return; + } + + if (writefn == NULL) { + writefn = bstatsWrite; + } + +/* + * Print stats for each memory block + */ + (*writefn)(handle, T("\nMemory Stats\n")); + +/* + * The following tabular format is now used for the output. + * Q Size Free Bytes Inuse Bytes Allocs + * dd ddddd ddd ddddd dddd ddddd dddd + */ + (*writefn)(handle, " Q Size Free Bytes Inuse Bytes Allocs\n"); + + total = 0; + for (q = 0; q < B_MAX_CLASS; q++) { + count = 0; + for (bp = bQhead[q]; bp; bp = bp->u.next) { + a_assert((bp->flags & B_INTEGRITY_MASK) == B_INTEGRITY); + count++; + } + mem = count * (1 << (q + B_SHIFT)); + total += mem; + (*writefn)(handle, + T("%2d %5d %3d %5d %4d %5d %4d\n"), + q, 1 << (q + B_SHIFT), count, mem, bStats[q].inuse, + bStats[q].inuse * (1 << (q + B_SHIFT)), bStats[q].alloc); + } + + (*writefn)(handle, T("\n")); + +/* + * Print summary stats + */ + (*writefn)(handle, T("Initial free list size %7d\n"), bFreeSize); + (*writefn)(handle, T("Max memory malloced %7d\n"), bStatsMemMalloc); + (*writefn)(handle, T("Max memory ever used %7d\n"), bStatsMemMax); + (*writefn)(handle, T("Memory currently in use %7d\n"), bStatsMemInUse); + (*writefn)(handle, T("Max blocks allocated %7d\n"), bStatsBlksMax); + (*writefn)(handle, T("Maximum stack used %7d\n"), + (int) bStackStart - (int) bStackMin); + + (*writefn)(handle, T("Free memory on all queues %7d\n"), total); + (*writefn)(handle, T("Free list buffer left %7d\n"), bFreeLeft); + (*writefn)(handle, T("Total free memory %7d\n"), bFreeLeft + total); + +/* + * Print per file allocation stats + */ + qsort(bStatsFiles, bStatsFilesMax, sizeof(bStatsFileType), bStatsFileSort); + (*writefn)(handle, T("\nPer File Memory Stats\n")); + total = 0; + for (fp = bStatsFiles; fp < &bStatsFiles[bStatsFilesMax]; fp++) { + if (fp->file[0]) { + (*writefn)(handle, + T("%18s, bytes %7d, blocks in use %5d, total allocs %6d\n"), + fp->file, fp->allocated, fp->count, fp->allocs); + total += fp->allocated; + } + } + (*writefn)(handle, T("\nTotal allocated %7d\n"), total); + recurseProtect--; +} + +/******************************************************************************/ +/* + * File sort function. Used to sort per file stats + */ + +static int bStatsFileSort(const void *cp1, const void *cp2) +{ + bStatsFileType *s1, *s2; + + s1 = (bStatsFileType*) cp1; + s2 = (bStatsFileType*) cp2; + + if (s1->allocated < s2->allocated) + return -1; + else if (s1->allocated == s2->allocated) + return 0; + return 1; +} + +/******************************************************************************/ +/* + * Default output function. Just send to trace channel. + */ + +static void bstatsWrite(int handle, char_t *fmt, ...) +{ + va_list args; + char_t *buf; + + va_start(args, fmt); + buf = NULL; + gvsnprintf(&buf, VALUE_MAX_STRING, fmt, args); + va_end(args); + trace(0, buf); + if (buf) { + bfree(B_L, buf); + } +} + +/******************************************************************************/ +/* + * Accumulate allocation statistics + */ + +static void bStatsAlloc(B_ARGS_DEC, void *ptr, int q, int size) +{ + bStatsFileType *fp; + bStatsBlkType *bp; + char_t name[FNAMESIZE + 10]; + + a_assert(file && *file); + a_assert(0 <= q && q <= B_MAX_CLASS); + a_assert(size > 0); + + gsprintf(name, T("%s:%d"), B_ARGS); + + bStats[q].alloc++; + bStats[q].inuse++; + bStatsMemInUse += size; + + if (bStatsMemInUse > bStatsMemMax) { + bStatsMemMax = bStatsMemInUse; + } + +/* + * Track maximum stack usage. Assumes a stack growth down. Approximate as + * we only measure this on block allocation. + */ + if ((void*) &file < bStackMin) { + bStackMin = (void*) &file; + } + +/* + * Find the file and adjust the stats for this file + */ + for (fp = bStatsFiles; fp < &bStatsFiles[bStatsFilesMax]; fp++) { + if (fp->file[0] == file[0] && gstrcmp(fp->file, name) == 0) { + fp->allocated += size; + fp->count++; + fp->allocs++; + break; + } + } + +/* + * Find the first free slot for this file and add current block size. + */ + if (fp >= &bStatsFiles[bStatsFilesMax]) { + for (fp = bStatsFiles; fp < &bStatsFiles[B_MAX_FILES]; fp++) { + if (fp->file[0] == '\0') { + gstrncpy(fp->file, name, TSZ(fp->file)); + fp->allocated += size; + fp->count++; + fp->allocs++; + if ((fp - bStatsFiles) >= bStatsFilesMax) { + bStatsFilesMax = (fp - bStatsFiles) + 1; + } + break; + } + } + } + +/* + * Update the per block stats. Allocate a new slot. + */ + for (bp = bStatsBlks; bp < &bStatsBlks[B_MAX_BLOCKS]; bp++) { + if (bp->ptr == NULL) { + bp->ptr = ptr; + bp->who = fp; + if ((bp - bStatsBlks) >= bStatsBlksMax) { + bStatsBlksMax = (bp - bStatsBlks) + 1; + } + break; + } + } +} + +/******************************************************************************/ +/* + * Free statistics + */ + +static void bStatsFree(B_ARGS_DEC, void *ptr, int q, int size) +{ + bStatsFileType *fp; + bStatsBlkType *bp; + char_t name[FNAMESIZE + 10]; + + a_assert(file && *file); + a_assert(0 <= q && q <= B_MAX_CLASS); + a_assert(size > 0); + + bStatsMemInUse -= size; + bStats[q].inuse--; + + gsprintf(name, T("%s:%d"), B_ARGS); + +/* + * Update the per block stats + */ + for (bp = bStatsBlks; bp < &bStatsBlks[bStatsBlksMax]; bp++) { + if (bp->ptr == ptr) { + bp->ptr = NULL; + fp = bp->who; + fp->allocated -= size; + fp->count--; + return; + } + } + a_assert(0); + +} + +#else /* not B_STATS */ +/******************************************************************************/ +/* + * Dummy bstats for external calls that aren't protected by #if B_STATS. + */ + +void bstats(int handle, void (*writefn)(int handle, char_t *fmt, ...)) +{ +} +#endif /* B_STATS */ + +/******************************************************************************/ +#if B_VERIFY_CAUSES_SEVERE_OVERHEAD +/* + * The following routines verify the integrity of the balloc memory space. + * These functions depend use the B_FILL feature. Corruption is defined + * as bad integrity flags in allocated blocks or data other than B_FILL_CHAR + * being found anywhere in the space which is unallocated and that is not a + * next pointer in the free queues. a_assert is called if any corruption is + * found. CAUTION: These functions add severe processing overhead and should + * only be used when searching for a tough corruption problem. + */ + +/******************************************************************************/ +/* + * verifyUsedBlock verifies that a block which was previously allocated is + * still uncorrupted. + */ + +static void verifyUsedBlock(bType *bp, int q) +{ + int memSize, size; + char *p; + + memSize = (1 << (B_SHIFT + q)); + a_assert((bp->flags & ~B_MALLOCED) == B_INTEGRITY ); + size = bp->u.size; + for (p = ((char *)bp)+sizeof(bType)+size; p < ((char*)bp)+memSize; p++) { + a_assert(*p == B_FILL_CHAR); + } +} + +/******************************************************************************/ +/* + * verifyFreeBlock verifies that a previously free'd block in one of the queues + * is still uncorrupted. + */ + +static void verifyFreeBlock(bType *bp, int q) +{ + int memSize; + char *p; + + memSize = (1 << (B_SHIFT + q)); + for (p = ((char *)bp)+sizeof(void*); p < ((char*)bp)+memSize; p++) { + a_assert(*p == B_FILL_CHAR); + } + bp = (bType *)p; + a_assert((bp->flags & ~B_MALLOCED) == B_INTEGRITY || + bp->flags == B_FILL_WORD); +} + +/******************************************************************************/ +/* + * verifyBallocSpace reads through the entire balloc memory space and + * verifies that all allocated blocks are uncorrupted and that with the + * exception of free list next pointers all other unallocated space is + * filled with B_FILL_CHAR. + */ + +static void verifyBallocSpace() +{ + char *p; + bType *bp; + + p = bFreeBuf; + while (p < (bFreeBuf + bFreeSize)) { + bp = (bType *)p; + if (bp->u.size > 0xFFFFF) { + p += sizeof(bp->u); + while (p < (bFreeBuf + bFreeSize) && *p == B_FILL_CHAR) { + p++; + } + } else { + a_assert(((bp->flags & ~B_MALLOCED) == B_INTEGRITY) || + bp->flags == B_FILL_WORD); + p += (sizeof(bType) + bp->u.size); + while (p < (bFreeBuf + bFreeSize) && *p == B_FILL_CHAR) { + p++; + } + } + } +} +#endif /* B_VERIFY_CAUSES_SEVERE_OVERHEAD */ + +/******************************************************************************/ + +#else /* NO_BALLOC */ +int bopen(void *buf, int bufsize, int flags) +{ + return 0; +} + +/******************************************************************************/ + +void bclose() +{ +} + +/******************************************************************************/ +#if UNICODE +char_t* bstrdupNoBalloc(char_t* s) +{ + if (s) { + return wcsdup(s); + } else { + return wcsdup(T("")); + } +} +#endif /* UNICODE */ + +/******************************************************************************/ +char* bstrdupANoBalloc(char* s) +{ + char* buf; + + if (s == NULL) { + s = ""; + } + buf = malloc(strlen(s)+1); + strcpy(buf, s); + return buf; +} + +#endif /* NO_BALLOC */ +/******************************************************************************/ + diff --git a/cpukit/httpd/default.c b/cpukit/httpd/default.c new file mode 100644 index 0000000000..c485c22285 --- /dev/null +++ b/cpukit/httpd/default.c @@ -0,0 +1,389 @@ +/* + * default.c -- Default URL handler. Includes support for ASP. + * + * 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 provides default URL handling and Active Server Page support. + * + * In many cases we don't check the return code of calls to websWrite as + * it is easier, smaller and non-fatal to continue even when the requesting + * browser has gone away. + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/*********************************** Locals ***********************************/ + +static char_t *websDefaultPage; /* Default page name */ +static char_t *websDefaultDir; /* Default Web page directory */ + +/**************************** Forward Declarations ****************************/ + +static void websDefaultWriteEvent(webs_t wp); + +/*********************************** Code *************************************/ +/* + * Process a default URL request. This will validate the URL and handle "../" + * and will provide support for Active Server Pages. As the handler is the + * last handler to run, it always indicates that it has handled the URL + * by returning 1. + */ + +int websDefaultHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg, + char_t *url, char_t *path, char_t* query) +{ + websStatType sbuf; + char_t *lpath, *tmp; + char_t *date; + int bytes, flags, nchars; + + a_assert(websValid(wp)); + a_assert(url && *url); + a_assert(path && *path); + a_assert(query); + +/* + * Validate the URL and ensure that ".."s don't give access to unwanted files + */ + flags = websGetRequestFlags(wp); + + if (websValidateUrl(wp, path) < 0) { + websError(wp, 500, T("Invalid URL %s"), url); + return 1; + } + lpath = websGetRequestLpath(wp); + nchars = gstrlen(lpath) - 1; + if (lpath[nchars] == '/' || lpath[nchars] == '\\') { + lpath[nchars] = '\0'; + } + +/* + * If the file is a directory, redirect using the nominated default page + */ + if (websPageIsDirectory(lpath)) { + nchars = gstrlen(path); + if (path[nchars-1] == '/' || path[nchars-1] == '\\') { + path[--nchars] = '\0'; + } + nchars += gstrlen(websDefaultPage) + 2; + tmp = NULL; + gsnprintf(&tmp, nchars, T("%s/%s"), path, websDefaultPage); + websRedirect(wp, tmp); + bfreeSafe(B_L, tmp); + return 1; + } + +/* + * Open the document. Stat for later use. + */ + if (websPageOpen(wp, lpath, path, SOCKET_RDONLY | SOCKET_BINARY, + 0666) < 0) { + websError(wp, 400, + T("Can't open document %s
for URL %s"), + lpath, url); + return 1; + } + if (websPageStat(wp, lpath, path, &sbuf) < 0) { + websError(wp, 400, T("Can't stat page %s
for URL %s"), + lpath, url); + } + +/* + * If the page has not been modified since the user last received it and it + * is not dynamically generated each time (ASP), then optimize request by + * sending a 304 Use local copy response + */ + websStats.localHits++; +#if WEBS_IF_MODIFIED_SUPPORT + if (flags & WEBS_IF_MODIFIED && !(flags & WEBS_ASP)) { + if (sbuf.mtime <= wp->since) { + websWrite(wp, T("HTTP/1.0 304 Use local copy\r\n")); + + /* by license terms the following line of code must + * not be modified. + */ + websWrite(wp, T("Server: GoAhead-Webs\r\n")); + + if (flags && WEBS_KEEP_ALIVE) { + websWrite(wp, T("Connection: keep-alive\r\n")); + } + websWrite(wp, T("\r\n")); + websSetRequestFlags(wp, flags |= WEBS_HEADER_DONE); + websDone(wp, 304); + return 1; + } + } +#endif + +/* + * Output the normal HTTP response header + */ + if ((date = websGetDateString(NULL)) != NULL) { + websWrite(wp, T("HTTP/1.0 200 OK\r\nDate: %s\r\n"), date); + +/* + * By license terms the following line of code must not be modified. +*/ + websWrite(wp, T("Server: GoAhead-Webs\r\n")); + bfree(B_L, date); + } + flags |= WEBS_HEADER_DONE; + +/* + * If this is an ASP request, ensure the remote browser doesn't cache it. + * Send back both HTTP/1.0 and HTTP/1.1 cache control directives + */ + if (flags & WEBS_ASP) { + bytes = 0; + websWrite(wp, T("Pragma: no-cache\r\nCache-Control: no-cache\r\n")); + + } else { + if ((date = websGetDateString(&sbuf)) != NULL) { + websWrite(wp, T("Last-modified: %s\r\n"), date); + bfree(B_L, date); + } + bytes = sbuf.size; + } + + if (bytes) { + websWrite(wp, T("Content-length: %d\r\n"), bytes); + websSetRequestBytes(wp, bytes); + } + websWrite(wp, T("Content-type: %s\r\n"), websGetRequestType(wp)); + + if ((flags & WEBS_KEEP_ALIVE) && !(flags & WEBS_ASP)) { + websWrite(wp, T("Connection: keep-alive\r\n")); + } + websWrite(wp, T("\r\n")); + +/* + * Evaluate ASP requests + */ + if (flags & WEBS_ASP) { + if (websAspRequest(wp, lpath) < 0) { + return 1; + } + websDone(wp, 200); + return 1; + } + +/* + * All done if the browser did a HEAD request + */ + if (flags & WEBS_HEAD_REQUEST) { + websDone(wp, 200); + return 1; + } +/* + * For normal web documents, return the data via background write + */ + websSetRequestSocketHandler(wp, SOCKET_WRITABLE, websDefaultWriteEvent); + return 1; +} + +/******************************************************************************/ +/* + * Validate the URL path and process ".." path segments. Return -1 if the URL + * is bad. + */ + +int websValidateUrl(webs_t wp, char_t *path) +{ + char_t *parts[64]; /* Array of ptr's to URL parts */ + char_t *token, *dir, *lpath; + int i, len, npart; + + a_assert(websValid(wp)); + a_assert(path); + + dir = websGetRequestDir(wp); + if (dir == NULL || *dir == '\0') { + return -1; + } + +/* + * Copy the string so we don't destroy the original + */ + path = bstrdup(B_L, path); + websDecodeUrl(path, path, gstrlen(path)); + + len = npart = 0; + parts[0] = NULL; + token = gstrtok(path, T("/")); + +/* + * Look at each directory segment and process "." and ".." segments + * Don't allow the browser to pop outside the root web. + */ + while (token != NULL) { + if (gstrcmp(token, T("..")) == 0) { + if (npart > 0) { + npart--; + } + + } else if (gstrcmp(token, T(".")) != 0) { + parts[npart] = token; + len += gstrlen(token) + 1; + npart++; + } + token = gstrtok(NULL, T("/")); + } + +/* + * Create local path for document. Need extra space all "/" and null. + */ + if (npart) { + lpath = balloc(B_L, (gstrlen(dir) + 1 + len + 1) * sizeof(char_t)); + gstrcpy(lpath, dir); + + for (i = 0; i < npart; i++) { + gstrcat(lpath, T("/")); + gstrcat(lpath, parts[i]); + } + websSetRequestLpath(wp, lpath); + bfree(B_L, path); + bfree(B_L, lpath); + + } else { + bfree(B_L, path); + return -1; + } + return 0; +} + +/******************************************************************************/ +/* + * Do output back to the browser in the background. This is a socket + * write handler. + */ + +static void websDefaultWriteEvent(webs_t wp) +{ + int len, wrote, flags, bytes, written; + char * buf; + + a_assert(websValid(wp)); + + flags = websGetRequestFlags(wp); + + wrote = 0; + bytes = 0; + written = websGetRequestWritten(wp); + +/* + * We only do this for non-ASP documents + */ + if ( !(flags & WEBS_ASP)) { + bytes = websGetRequestBytes(wp); +/* + * Note: websWriteBlock may return less than we wanted. It will return + * -1 on a socket error + */ + if ((buf = balloc(B_L, PAGE_READ_BUFSIZE)) == NULL) { + websError(wp, 200, T("Can't get memory")); + } + else { + while ((len = websPageReadData(wp, buf, PAGE_READ_BUFSIZE)) > 0) { + if ((wrote = websWriteBlockData(wp, buf, len)) < 0) { + break; + } + written += wrote; + if (wrote != len) { + websPageSeek(wp, - (wrote - len)); + break; + } + } +/* + * Safety. If we are at EOF, we must be done + */ + if (len == 0) { + a_assert(written >= bytes); + written = bytes; + } + bfree(B_L, buf); + } + } + +/* + * We're done if an error, or all bytes output + */ + websSetRequestWritten(wp, written); + if (wrote < 0 || written >= bytes) { + websDone(wp, 200); + } +} + +/******************************************************************************/ +/* + * Closing down. Free resources. + */ + +void websDefaultClose() +{ + if (websDefaultPage) { + bfree(B_L, websDefaultPage); + } + if (websDefaultDir) { + bfree(B_L, websDefaultDir); + } +} + +/******************************************************************************/ +/* + * Get the default page for URL requests ending in "/" + */ + +char_t *websGetDefaultPage() +{ + return websDefaultPage; +} + +/******************************************************************************/ +/* + * Get the default web directory + */ + +char_t *websGetDefaultDir() +{ + return websDefaultDir; +} + +/******************************************************************************/ +/* + * Set the default page for URL requests ending in "/" + */ + +void websSetDefaultPage(char_t *page) +{ + a_assert(page && *page); + + if (websDefaultPage) { + bfree(B_L, websDefaultPage); + } + websDefaultPage = bstrdup(B_L, page); +} + +/******************************************************************************/ +/* + * Set the default web directory + */ + +void websSetDefaultDir(char_t *dir) +{ + a_assert(dir && *dir); + if (websDefaultDir) { + bfree(B_L, websDefaultDir); + } + websDefaultDir = bstrdup(B_L, dir); +} + +/******************************************************************************/ + diff --git a/cpukit/httpd/ej.h b/cpukit/httpd/ej.h new file mode 100644 index 0000000000..91eb72a4d2 --- /dev/null +++ b/cpukit/httpd/ej.h @@ -0,0 +1,226 @@ +/* + * ej.h -- Ejscript(TM) header + * + * Copyright (c) Go Ahead Software, Inc., 1992-1999 + * + * See the file "license.txt" for information on usage and redistribution + */ + +#ifndef _h_EJ +#define _h_EJ 1 + +/******************************** Description *********************************/ + +/* + * Go Ahead Ejscript(TM) header. This defines the Ejscript API and internal + * structures. + */ + +/********************************* Includes ***********************************/ + +#include +#include +#include +#ifndef CE + #include +#endif + +#if LYNX + #include +#endif + +#ifdef QNX4 + #include +#endif + +#if UEMF + #include "uemf.h" +#else + #include + #include + #include "basic/basicInternal.h" + #include "emf/emf.h" + #include "webs/webs.h" +#endif + +/********************************** Defines ***********************************/ +/* + * Constants + */ +#define EJ_INC 110 /* Growth for tags/tokens */ +#define EJ_OFFSET 1 /* hAlloc doesn't like 0 entries */ +#define EJ_MAX_RECURSE 100 /* Sanity for maximum recursion */ + +/* + * Ejscript Lexical analyser tokens + */ +#define TOK_ERR -1 /* Any error */ +#define TOK_LPAREN 1 /* ( */ +#define TOK_RPAREN 2 /* ) */ +#define TOK_IF 3 /* if */ +#define TOK_ELSE 4 /* else */ +#define TOK_LBRACE 5 /* { */ +#define TOK_RBRACE 6 /* } */ +#define TOK_LOGICAL 7 /* ||, &&, ! */ +#define TOK_EXPR 8 /* +, -, /, % */ +#define TOK_SEMI 9 /* ; */ +#define TOK_LITERAL 10 /* literal string */ +#define TOK_FUNCTION 11 /* function name */ +#define TOK_NEWLINE 12 /* newline white space */ +#define TOK_ID 13 /* function name */ +#define TOK_EOF 14 /* End of script */ +#define TOK_COMMA 15 /* Comma */ +#define TOK_VAR 16 /* var */ +#define TOK_ASSIGNMENT 17 /* = */ +#define TOK_FOR 18 /* for */ +#define TOK_INC_DEC 19 /* ++, -- */ +#define TOK_RETURN 20 /* return */ + +/* + * Expression operators + */ +#define EXPR_LESS 1 /* < */ +#define EXPR_LESSEQ 2 /* <= */ +#define EXPR_GREATER 3 /* > */ +#define EXPR_GREATEREQ 4 /* >= */ +#define EXPR_EQ 5 /* == */ +#define EXPR_NOTEQ 6 /* != */ +#define EXPR_PLUS 7 /* + */ +#define EXPR_MINUS 8 /* - */ +#define EXPR_DIV 9 /* / */ +#define EXPR_MOD 10 /* % */ +#define EXPR_LSHIFT 11 /* << */ +#define EXPR_RSHIFT 12 /* >> */ +#define EXPR_MUL 13 /* * */ +#define EXPR_ASSIGNMENT 14 /* = */ +#define EXPR_INC 15 /* ++ */ +#define EXPR_DEC 16 /* -- */ + +/* + * Conditional operators + */ +#define COND_AND 1 /* && */ +#define COND_OR 2 /* || */ +#define COND_NOT 3 /* ! */ + +/* + * States + */ +#define STATE_ERR -1 /* Error state */ +#define STATE_EOF 1 /* End of file */ +#define STATE_COND 2 /* Parsing a "(conditional)" stmt */ +#define STATE_COND_DONE 3 +#define STATE_RELEXP 4 /* Parsing a relational expr */ +#define STATE_RELEXP_DONE 5 +#define STATE_EXPR 6 /* Parsing an expression */ +#define STATE_EXPR_DONE 7 +#define STATE_STMT 8 /* Parsing General statement */ +#define STATE_STMT_DONE 9 +#define STATE_STMT_BLOCK_DONE 10 /* End of block "}" */ +#define STATE_ARG_LIST 11 /* Function arg list */ +#define STATE_ARG_LIST_DONE 12 +#define STATE_DEC_LIST 16 /* Declaration list */ +#define STATE_DEC_LIST_DONE 17 +#define STATE_DEC 18 +#define STATE_DEC_DONE 19 +#define STATE_BEGIN STATE_STMT + +/* + * Flags. Used in ej_t and as parameter to parse() + */ +#define FLAGS_EXE 0x1 /* Execute statements */ +#define FLAGS_VARIABLES 0x2 /* Allocated variables store */ +#define FLAGS_FUNCTIONS 0x4 /* Allocated function store */ + +/* + * Function call structure + */ +typedef struct { + char_t *fname; /* Function name */ + char_t **args; /* Args for function (halloc) */ + int nArgs; /* Number of args */ +} ejfunc_t; + +/* + * EJ evaluation block structure + */ +typedef struct ejEval { + ringq_t tokbuf; /* Current token */ + ringq_t script; /* Input script for parsing */ + char_t *putBackToken; /* Putback token string */ + int putBackTokenId; /* Putback token ID */ + char_t *line; /* Current line */ + int lineLength; /* Current line length */ + int lineNumber; /* Parse line number */ + int lineColumn; /* Column in line */ +} ejinput_t; + +/* + * Per Ejscript session structure + */ +typedef struct ej { + ejinput_t *input; /* Input evaluation block */ + sym_fd_t functions; /* Symbol table for functions */ + sym_fd_t *variables; /* hAlloc list of variables */ + int variableMax; /* Number of entries */ + ejfunc_t *func; /* Current function */ + char_t *result; /* Current expression result */ + char_t *error; /* Error message */ + char_t *token; /* Pointer to token string */ + int tid; /* Current token id */ + int eid; /* Halloc handle */ + int flags; /* Flags */ + int userHandle; /* User defined handle */ +} ej_t; + +/******************************** Prototypes **********************************/ + +extern int ejOpenEngine(sym_fd_t variables, sym_fd_t functions); +extern void ejCloseEngine(int eid); +extern int ejOpenBlock(int eid); +extern int ejCloseBlock(int eid, int vid); +extern char_t *ejEval(int eid, char_t *script, char_t **emsg); +extern char_t *ejEvalBlock(int eid, char_t *script, char_t **emsg); +extern char_t *ejEvalFile(int eid, char_t *path, char_t **emsg); +extern int ejSetGlobalFunction(int eid, char_t *name, + int (*fn)(int eid, void *handle, int argc, char_t **argv)); +extern void *ejGetGlobalFunction(int eid, char_t *name); +extern int ejSetGlobalFunctionDirect(sym_fd_t functions, char_t *name, + int (*fn)(int eid, void *handle, int argc, char_t **argv)); +extern int ejArgs(int argc, char_t **argv, char_t *fmt, ...); +extern void ejError(ej_t* ep, char_t* fmt, ...); +extern void ejSetUserHandle(int eid, int handle); +extern int ejGetUserHandle(int eid); +extern int ejGetLineNumber(int eid); +extern void ejSetResult(int eid, char_t *s); +extern char_t *ejGetResult(int eid); +extern void ejSetVar(int eid, char_t *var, char_t *value); +extern void ejSetLocalVar(int eid, char_t *var, char_t *value); +extern int ejGetVar(int eid, char_t *var, char_t **value); +extern void ejSetGlobalVar(int eid, char_t *var, char_t *value); + +extern int ejLexOpen(ej_t* ep); +extern void ejLexClose(ej_t* ep); +extern int ejLexOpenScript(ej_t* ep, char_t *script); +extern void ejLexCloseScript(ej_t* ep); +extern void ejLexSaveInputState(ej_t* ep, ejinput_t* state); +extern void ejLexFreeInputState(ej_t* ep, ejinput_t* state); +extern void ejLexRestoreInputState(ej_t* ep, ejinput_t* state); +extern int ejLexGetToken(ej_t* ep, int state); +extern void ejLexPutbackToken(ej_t* ep, int tid, char_t *string); + +extern sym_fd_t ejGetVariableTable(int eid); +extern sym_fd_t ejGetFunctionTable(int eid); + +extern int ejEmfOpen(int eid); +extern void ejEmfClose(int eid); + +extern int ejEmfDbRead(int eid, void *handle, int argc, char_t **argv); +extern int ejEmfDbReadKeyed(int eid, void *handle, int argc, char_t **argv); +extern int ejEmfDbTableGetNrow(int eid, void *handle, int argc, char_t **argv); +extern int ejEmfTrace(int eid, void *handle, int argc, char_t **argv); +extern int ejEmfDbWrite(int eid, void *handle, int argc, char_t **argv); + +#endif /* _h_EJ */ + +/*****************************************************************************/ diff --git a/cpukit/httpd/ejlex.c b/cpukit/httpd/ejlex.c new file mode 100644 index 0000000000..67e1504bd7 --- /dev/null +++ b/cpukit/httpd/ejlex.c @@ -0,0 +1,679 @@ +/* + * ejlex.c -- Ejscript(TM) Lexical Analyser + * + * Copyright (c) Go Ahead Software, Inc., 1995-1999 + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * Ejscript lexical analyser. This implementes a lexical analyser for a + * a subset of the JavaScript language. + */ + +/********************************** Includes **********************************/ + +#include "ej.h" + +#if UEMF + #include "uemf.h" +#else + #include "basic/basicInternal.h" +#endif + +/****************************** Forward Declarations **************************/ + +static int getLexicalToken(ej_t* ep, int state); +static int tokenAddChar(ej_t *ep, int c); +static int inputGetc(ej_t* ep); +static void inputPutback(ej_t* ep, int c); + +/************************************* Code ***********************************/ +/* + * Setup the lexical analyser + */ + +int ejLexOpen(ej_t* ep) +{ + return 0; +} + +/******************************************************************************/ +/* + * Close the lexicial analyser + */ + +void ejLexClose(ej_t* ep) +{ +} + +/******************************************************************************/ +/* + * Open a new input script + */ + +int ejLexOpenScript(ej_t* ep, char_t *script) +{ + ejinput_t *ip; + + a_assert(ep); + a_assert(script); + + if ((ep->input = balloc(B_L, sizeof(ejinput_t))) == NULL) { + return -1; + } + ip = ep->input; + memset(ip, 0, sizeof(*ip)); + + a_assert(ip); + a_assert(ip->putBackToken == NULL); + a_assert(ip->putBackTokenId == 0); + +/* + * Create the parse token buffer and script buffer + */ + if (ringqOpen(&ip->tokbuf, EJ_INC, -1) < 0) { + return -1; + } + if (ringqOpen(&ip->script, EJ_INC, -1) < 0) { + return -1; + } +/* + * Put the Ejscript into a ring queue for easy parsing + */ + ringqPutstr(&ip->script, script); + + ip->lineNumber = 1; + ip->lineLength = 0; + ip->lineColumn = 0; + ip->line = NULL; + + return 0; +} + +/******************************************************************************/ +/* + * Close the input script + */ + +void ejLexCloseScript(ej_t* ep) +{ + ejinput_t *ip; + + a_assert(ep); + + ip = ep->input; + a_assert(ip); + + if (ip->putBackToken) { + bfree(B_L, ip->putBackToken); + ip->putBackToken = NULL; + } + ip->putBackTokenId = 0; + + if (ip->line) { + bfree(B_L, ip->line); + ip->line = NULL; + } + + ringqClose(&ip->tokbuf); + ringqClose(&ip->script); + + bfree(B_L, ip); +} + +/******************************************************************************/ +/* + * Save the input state + */ + +void ejLexSaveInputState(ej_t* ep, ejinput_t* state) +{ + ejinput_t *ip; + + a_assert(ep); + + ip = ep->input; + a_assert(ip); + + *state = *ip; + if (ip->putBackToken) { + state->putBackToken = bstrdup(B_L, ip->putBackToken); + } +} + +/******************************************************************************/ +/* + * Restore the input state + */ + +void ejLexRestoreInputState(ej_t* ep, ejinput_t* state) +{ + ejinput_t *ip; + + a_assert(ep); + + ip = ep->input; + a_assert(ip); + + ip->tokbuf = state->tokbuf; + ip->script = state->script; + ip->putBackTokenId = state->putBackTokenId; + if (ip->putBackToken) { + bfree(B_L, ip->putBackToken); + } + if (state->putBackToken) { + ip->putBackToken = bstrdup(B_L, state->putBackToken); + } +} + +/******************************************************************************/ +/* + * Free a saved input state + */ + +void ejLexFreeInputState(ej_t* ep, ejinput_t* state) +{ + if (state->putBackToken) { + bfree(B_L, state->putBackToken); + } +} + +/******************************************************************************/ +/* + * Get the next Ejscript token + */ + +int ejLexGetToken(ej_t* ep, int state) +{ + ep->tid = getLexicalToken(ep, state); + trace(7, T("ejGetToken: %d, \"%s\"\n"), ep->tid, ep->token); + return ep->tid; +} + +/******************************************************************************/ +/* + * Get the next Ejscript token + */ + +static int getLexicalToken(ej_t* ep, int state) +{ + ringq_t *inq, *tokq; + ejinput_t* ip; + int done, tid, c, quote, style, back_quoted, lval, i; + + a_assert(ep); + ip = ep->input; + a_assert(ip); + + inq = &ip->script; + tokq = &ip->tokbuf; + + ep->tid = -1; + tid = -1; + ep->token = T(""); + + ringqFlush(tokq); + + if (ip->putBackTokenId > 0) { + ringqPutstr(tokq, ip->putBackToken); + tid = ip->putBackTokenId; + ip->putBackTokenId = 0; + ep->token = (char_t*) tokq->servp; + return tid; + } + + if ((c = inputGetc(ep)) < 0) { + return TOK_EOF; + } + + for (done = 0; !done; ) { + switch (c) { + case -1: + return TOK_EOF; + + case ' ': + case '\t': + case '\r': + do { + if ((c = inputGetc(ep)) < 0) + break; + } while (c == ' ' || c == '\t' || c == '\r'); + break; + + case '\n': + return TOK_NEWLINE; + + case '(': + tokenAddChar(ep, c); + return TOK_LPAREN; + + case ')': + tokenAddChar(ep, c); + return TOK_RPAREN; + + case '{': + tokenAddChar(ep, c); + return TOK_LBRACE; + + case '}': + tokenAddChar(ep, c); + return TOK_RBRACE; + + case '+': + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c != '+' ) { + inputPutback(ep, c); + tokenAddChar(ep, EXPR_PLUS); + return TOK_EXPR; + } + tokenAddChar(ep, EXPR_INC); + return TOK_INC_DEC; + + case '-': + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c != '-' ) { + inputPutback(ep, c); + tokenAddChar(ep, EXPR_MINUS); + return TOK_EXPR; + } + tokenAddChar(ep, EXPR_DEC); + return TOK_INC_DEC; + + case '*': + tokenAddChar(ep, EXPR_MUL); + return TOK_EXPR; + + case '%': + tokenAddChar(ep, EXPR_MOD); + return TOK_EXPR; + + case '/': +/* + * Handle the division operator and comments + */ + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c != '*' && c != '/') { + inputPutback(ep, c); + tokenAddChar(ep, EXPR_DIV); + return TOK_EXPR; + } + style = c; +/* + * Eat comments. Both C and C++ comment styles are supported. + */ + while (1) { + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c == '\n' && style == '/') { + break; + } else if (c == '*') { + c = inputGetc(ep); + if (style == '/') { + if (c == '\n') { + break; + } + } else { + if (c == '/') { + break; + } + } + } + } +/* + * Continue looking for a token, so get the next character + */ + if ((c = inputGetc(ep)) < 0) { + return TOK_EOF; + } + break; + + case '<': /* < and <= */ + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c == '<') { + tokenAddChar(ep, EXPR_LSHIFT); + return TOK_EXPR; + } else if (c == '=') { + tokenAddChar(ep, EXPR_LESSEQ); + return TOK_EXPR; + } + tokenAddChar(ep, EXPR_LESS); + inputPutback(ep, c); + return TOK_EXPR; + + case '>': /* > and >= */ + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c == '>') { + tokenAddChar(ep, EXPR_RSHIFT); + return TOK_EXPR; + } else if (c == '=') { + tokenAddChar(ep, EXPR_GREATEREQ); + return TOK_EXPR; + } + tokenAddChar(ep, EXPR_GREATER); + inputPutback(ep, c); + return TOK_EXPR; + + case '=': /* "==" */ + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c == '=') { + tokenAddChar(ep, EXPR_EQ); + return TOK_EXPR; + } + inputPutback(ep, c); + return TOK_ASSIGNMENT; + + case '!': /* "!=" */ + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + if (c == '=') { + tokenAddChar(ep, EXPR_NOTEQ); + return TOK_EXPR; + } + tokenAddChar(ep, COND_NOT); + return TOK_LOGICAL; + + case ';': + tokenAddChar(ep, c); + return TOK_SEMI; + + case ',': + tokenAddChar(ep, c); + return TOK_COMMA; + + case '|': /* "||" */ + if ((c = inputGetc(ep)) < 0 || c != '|') { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + tokenAddChar(ep, COND_OR); + return TOK_LOGICAL; + + case '&': /* "&&" */ + if ((c = inputGetc(ep)) < 0 || c != '&') { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + tokenAddChar(ep, COND_AND); + return TOK_LOGICAL; + + case '\"': /* String quote */ + case '\'': + quote = c; + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Syntax Error")); + return TOK_ERR; + } + back_quoted = 0; + while (c != quote) { + if (c == '\\' && !back_quoted) { + back_quoted++; + } else if (back_quoted) { + if (gisdigit((char_t) c)) { + lval = 0; + for (i = 0; i < 3; i++) { + if ('0' <= c && c <= '7') { + break; + } + lval = lval * 8 + c; + if ((c = inputGetc(ep)) < 0) { + break; + } + } + c = (int) lval; + + } else if (back_quoted) { + switch (c) { + case 'n': + c = '\n'; break; + case 'b': + c = '\b'; break; + case 'f': + c = '\f'; break; + case 'r': + c = '\r'; break; + case 't': + c = '\t'; break; + case 'x': + lval = 0; + for (i = 0; i < 2; i++) { + if (! gisxdigit((char_t) c)) { + break; + } + lval = lval * 16 + c; + if ((c = inputGetc(ep)) < 0) { + break; + } + } + c = (int) lval; + break; + case 'u': + lval = 0; + for (i = 0; i < 4; i++) { + if (! gisxdigit((char_t) c)) { + break; + } + lval = lval * 16 + c; + if ((c = inputGetc(ep)) < 0) { + break; + } + } + c = (int) lval; + break; + case '\'': + case '\"': + break; + } + } + back_quoted = 0; + if (tokenAddChar(ep, c) < 0) { + return TOK_ERR; + } + } else { + if (tokenAddChar(ep, c) < 0) { + return TOK_ERR; + } + } + if ((c = inputGetc(ep)) < 0) { + ejError(ep, T("Unmatched Quote")); + return TOK_ERR; + } + } + return TOK_LITERAL; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + do { + if (tokenAddChar(ep, c) < 0) { + return TOK_ERR; + } + if ((c = inputGetc(ep)) < 0) + break; + } while (gisdigit((char_t) c)); + inputPutback(ep, c); + return TOK_LITERAL; + + default: +/* + * Identifiers or a function names + */ + back_quoted = 0; + while (1) { + if (c == '\\' && !back_quoted) { + back_quoted++; + } else { + back_quoted = 0; + if (tokenAddChar(ep, c) < 0) { + break; + } + } + if ((c = inputGetc(ep)) < 0) { + break; + } + if (!back_quoted && (!gisalnum((char_t) c) && c != '$' && + c != '_')) { + break; + } + } + if (! gisalpha(*tokq->servp) && *tokq->servp != '$' && + *tokq->servp != '_') { + ejError(ep, T("Invalid identifier %s"), tokq->servp); + return TOK_ERR; + } +/* + * Check for reserved words (only "if", "else", "var", "for" + * and "return" at the moment) + */ + if (state == STATE_STMT) { + if (gstrcmp(ep->token, T("if")) == 0) { + return TOK_IF; + } else if (gstrcmp(ep->token, T("else")) == 0) { + return TOK_ELSE; + } else if (gstrcmp(ep->token, T("var")) == 0) { + return TOK_VAR; + } else if (gstrcmp(ep->token, T("for")) == 0) { + return TOK_FOR; + } else if (gstrcmp(ep->token, T("return")) == 0) { + return TOK_RETURN; + } + } + +/* + * skip white space after token to find out whether this is + * a function or not. + */ + while (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + if ((c = inputGetc(ep)) < 0) + break; + } + + tid = (c == '(') ? TOK_FUNCTION : TOK_ID; + done++; + } + } + +/* + * Putback the last extra character for next time + */ + inputPutback(ep, c); + return tid; +} + +/******************************************************************************/ +/* + * Putback the last token read + */ + +void ejLexPutbackToken(ej_t* ep, int tid, char_t *string) +{ + ejinput_t* ip; + + a_assert(ep); + ip = ep->input; + a_assert(ip); + + if (ip->putBackToken) { + bfree(B_L, ip->putBackToken); + } + ip->putBackTokenId = tid; + ip->putBackToken = bstrdup(B_L, string); +} + +/******************************************************************************/ +/* + * Add a character to the token ringq buffer + */ + +static int tokenAddChar(ej_t *ep, int c) +{ + ejinput_t* ip; + + a_assert(ep); + ip = ep->input; + a_assert(ip); + + if (ringqPutc(&ip->tokbuf, (char_t) c) < 0) { + ejError(ep, T("Token too big")); + return -1; + } + * ((char_t*) ip->tokbuf.endp) = '\0'; + ep->token = (char_t*) ip->tokbuf.servp; + + return 0; +} + +/******************************************************************************/ +/* + * Get another input character + */ + +static int inputGetc(ej_t* ep) +{ + ejinput_t *ip; + int c, len; + + a_assert(ep); + ip = ep->input; + + if ((len = ringqLen(&ip->script)) == 0) { + return -1; + } + + c = ringqGetc(&ip->script); + + if (c == '\n') { + ip->lineNumber++; + ip->lineColumn = 0; + } else { + if ((ip->lineColumn + 2) >= ip->lineLength) { + ip->lineLength += EJ_INC; + ip->line = brealloc(B_L, ip->line, ip->lineLength * sizeof(char_t)); + } + ip->line[ip->lineColumn++] = c; + ip->line[ip->lineColumn] = '\0'; + } + return c; +} + +/******************************************************************************/ +/* + * Putback a character onto the input queue + */ + +static void inputPutback(ej_t* ep, int c) +{ + ejinput_t *ip; + + a_assert(ep); + + ip = ep->input; + ringqInsertc(&ip->script, (char_t) c); + ip->lineColumn--; + ip->line[ip->lineColumn] = '\0'; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/ejparse.c b/cpukit/httpd/ejparse.c new file mode 100644 index 0000000000..1734f6f719 --- /dev/null +++ b/cpukit/httpd/ejparse.c @@ -0,0 +1,1665 @@ +/* + * ejparse.c -- Ejscript(TM) Parser + * + * Copyright (c) Go Ahead Software, Inc., 1995-1999 + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * Ejscript parser. This implementes a subset of the JavaScript language. + * Multiple Ejscript parsers can be opened at a time. + */ + +/********************************** Includes **********************************/ + +#include "ej.h" + +/********************************** Local Data ********************************/ + +ej_t **ejHandles; /* List of ej handles */ +int ejMax = -1; /* Maximum size of */ + +/****************************** Forward Declarations **************************/ + +static ej_t *ejPtr(int eid); +static void clearString(char_t **ptr); +static void setString(char_t **ptr, char_t *s); +static void appendString(char_t **ptr, char_t *s); +static void freeVar(sym_t* sp); +static int parse(ej_t *ep, int state, int flags); +static int parseStmt(ej_t *ep, int state, int flags); +static int parseDeclaration(ej_t *ep, int state, int flags); +static int parseArgs(ej_t *ep, int state, int flags); +static int parseCond(ej_t *ep, int state, int flags); +static int parseExpr(ej_t *ep, int state, int flags); +static int evalExpr(ej_t *ep, char_t *lhs, int rel, char_t *rhs); +static int evalCond(ej_t *ep, char_t *lhs, int rel, char_t *rhs); +static int evalFunction(ej_t *ep); +static void freeFunc(ejfunc_t *func); + +/************************************* Code ***********************************/ +/* + * Initialize a Ejscript engine + */ + +int ejOpenEngine(sym_fd_t variables, sym_fd_t functions) +{ + ej_t *ep; + int eid, vid; + + if ((eid = hAllocEntry((void***) &ejHandles, &ejMax, sizeof(ej_t))) < 0) { + return -1; + } + ep = ejHandles[eid]; + ep->eid = eid; + +/* + * Create a top level symbol table if one is not provided for variables and + * functions. Variables may create other symbol tables for block level + * declarations so we use hAlloc to manage a list of variable tables. + */ + if ((vid = hAlloc((void***) &ep->variables)) < 0) { + ejMax = hFree((void***) &ejHandles, ep->eid); + return -1; + } + if (vid >= ep->variableMax) { + ep->variableMax = vid + 1; + } + + if (variables == -1) { + ep->variables[vid] = symOpen(64) + EJ_OFFSET; + ep->flags |= FLAGS_VARIABLES; + + } else { + ep->variables[vid] = variables + EJ_OFFSET; + } + + if (functions == -1) { + ep->functions = symOpen(64); + ep->flags |= FLAGS_FUNCTIONS; + } else { + ep->functions = functions; + } + + ejLexOpen(ep); + +/* + * Define standard constants + */ + ejSetGlobalVar(ep->eid, T("null"), NULL); + +#if EMF + ejEmfOpen(ep->eid); +#endif + return ep->eid; +} + +/******************************************************************************/ +/* + * Close + */ + +void ejCloseEngine(int eid) +{ + ej_t *ep; + int i; + + if ((ep = ejPtr(eid)) == NULL) { + return; + } + +#if EMF + ejEmfClose(eid); +#endif + + bfreeSafe(B_L, ep->error); + ep->error = NULL; + bfreeSafe(B_L, ep->result); + ep->result = NULL; + + ejLexClose(ep); + + if (ep->flags & FLAGS_VARIABLES) { + for (i = ep->variableMax - 1; i >= 0; i--) { + symClose(ep->variables[i] - EJ_OFFSET, freeVar); + ep->variableMax = hFree((void***) &ep->variables, i); + } + } + if (ep->flags & FLAGS_FUNCTIONS) { + symClose(ep->functions, freeVar); + } + + ejMax = hFree((void***) &ejHandles, ep->eid); + bfree(B_L, ep); +} + +/******************************************************************************/ +/* + * Callback from symClose. Free the variable. + */ + +static void freeVar(sym_t* sp) +{ + valueFree(&sp->content); +} + +/******************************************************************************/ +/* + * Evaluate a Ejscript file + */ + +#if DEV +char_t *ejEvalFile(int eid, char_t *path, char_t **emsg) +{ + gstat_t sbuf; + ej_t *ep; + char_t *script, *rs; + char *fileBuf; + int fd; + + a_assert(path && *path); + + if (emsg) { + *emsg = NULL; + } + if ((ep = ejPtr(eid)) == NULL) { + return NULL; + } + + if ((fd = gopen(path, O_RDONLY | O_BINARY, 0666)) < 0) { + ejError(ep, T("Bad handle %d"), eid); + return NULL; + } + if (gstat(path, &sbuf) < 0) { + close(fd); + ejError(ep, T("Cant stat %s"), path); + return NULL; + } + if ((fileBuf = balloc(B_L, sbuf.st_size + 1)) == NULL) { + close(fd); + ejError(ep, T("Cant malloc %d"), sbuf.st_size); + return NULL; + } + if (read(fd, fileBuf, sbuf.st_size) != (int)sbuf.st_size) { + close(fd); + bfree(B_L, fileBuf); + ejError(ep, T("Error reading %s"), path); + return NULL; + } + fileBuf[sbuf.st_size] = '\0'; + close(fd); + + if ((script = ballocAscToUni(fileBuf)) == NULL) { + bfree(B_L, fileBuf); + ejError(ep, T("Cant malloc %d"), sbuf.st_size + 1); + return NULL; + } + bfree(B_L, fileBuf); + + rs = ejEvalBlock(eid, script, emsg); + + bfree(B_L, script); + return rs; +} +#endif + +/******************************************************************************/ +/* + * Create a new variable scope block so that consecutive ejEval calls may + * be made with the same varible scope. This space MUST be closed with + * ejCloseBlock when the evaluations are complete. + */ + +int ejOpenBlock(int eid) +{ + ej_t *ep; + int vid; + + if((ep = ejPtr(eid)) == NULL) { + return -1; + } + if ((vid = hAlloc((void***) &ep->variables)) < 0) { + return -1; + } + if (vid >= ep->variableMax) { + ep->variableMax = vid + 1; + } + ep->variables[vid] = symOpen(64) + EJ_OFFSET; + return vid; + +} + +/******************************************************************************/ +/* + * Close a variable scope block. The vid parameter is the return value from + * the call to ejOpenBlock + */ + +int ejCloseBlock(int eid, int vid) +{ + ej_t *ep; + + if((ep = ejPtr(eid)) == NULL) { + return -1; + } + symClose(ep->variables[vid] - EJ_OFFSET, freeVar); + ep->variableMax = hFree((void***) &ep->variables, vid); + return 0; + +} +/******************************************************************************/ +/* + * Create a new variable scope block and evaluate a script. All variables + * created during this context will be automatically deleted when complete. + */ + +char_t *ejEvalBlock(int eid, char_t *script, char_t **emsg) +{ + char_t* returnVal; + int vid; + + a_assert(script); + + vid = ejOpenBlock(eid); + returnVal = ejEval(eid, script, emsg); + ejCloseBlock(eid, vid); + + return returnVal; +} + +/******************************************************************************/ +/* + * Parse and evaluate a Ejscript. The caller may provide a symbol table to + * use for variables and function definitions. Return char_t pointer on + * success otherwise NULL pointer is returned. + */ + +char_t *ejEval(int eid, char_t *script, char_t **emsg) +{ + ej_t *ep; + ejinput_t *oldBlock; + int state; + + a_assert(script); + + if (emsg) { + *emsg = NULL; + } + if ((ep = ejPtr(eid)) == NULL) { + return NULL; + } + + setString(&ep->result, T("")); + +/* + * Allocate a new evaluation block, and save the old one + */ + oldBlock = ep->input; + ejLexOpenScript(ep, script); + +/* + * Do the actual parsing and evaluation + */ + do { + state = parse(ep, STATE_BEGIN, FLAGS_EXE); + } while (state != STATE_EOF && state != STATE_ERR); + + ejLexCloseScript(ep); + +/* + * Return any error string to the user + */ + if (state == STATE_ERR && emsg) { + *emsg = bstrdup(B_L, ep->error); + } + +/* + * Restore the old evaluation block + */ + ep->input = oldBlock; + + if (state == STATE_EOF) { + return ep->result; + } + if (state == STATE_ERR) { + return NULL; + } + return ep->result; +} + +/******************************************************************************/ +/* + * Recursive descent parser for Ejscript + */ + +static int parse(ej_t *ep, int state, int flags) +{ + a_assert(ep); + + switch (state) { +/* + * Any statement, function arguments or conditional expressions + */ + case STATE_STMT: + case STATE_DEC: + state = parseStmt(ep, state, flags); + break; + + case STATE_EXPR: + state = parseStmt(ep, state, flags); + break; + +/* + * Variable declaration list + */ + case STATE_DEC_LIST: + state = parseDeclaration(ep, state, flags); + break; + +/* + * Function argument string + */ + case STATE_ARG_LIST: + state = parseArgs(ep, state, flags); + break; + +/* + * Logical condition list (relational operations separated by &&, ||) + */ + case STATE_COND: + state = parseCond(ep, state, flags); + break; + +/* + * Expression list + */ + case STATE_RELEXP: + state = parseExpr(ep, state, flags); + break; + } + + if (state == STATE_ERR && ep->error == NULL) { + ejError(ep, T("Syntax error")); + } + return state; +} + +/******************************************************************************/ +/* + * Parse any statement including functions and simple relational operations + */ + +static int parseStmt(ej_t *ep, int state, int flags) +{ + ejfunc_t func; + ejfunc_t *saveFunc; + ejinput_t condScript, endScript, bodyScript, incrScript; + char_t *value; + char_t *identifier; + int done, expectSemi, thenFlags, elseFlags, tid, cond, forFlags; + + a_assert(ep); + +/* + * Set these to NULL, else we try to free them if an error occurs. + */ + endScript.putBackToken = NULL; + bodyScript.putBackToken = NULL; + incrScript.putBackToken = NULL; + condScript.putBackToken = NULL; + + expectSemi = 0; + saveFunc = NULL; + + for (done = 0; !done; ) { + tid = ejLexGetToken(ep, state); + + switch (tid) { + default: + ejLexPutbackToken(ep, TOK_EXPR, ep->token); + done++; + break; + + case TOK_ERR: + state = STATE_ERR; + done++; + break; + + case TOK_EOF: + state = STATE_EOF; + done++; + break; + + case TOK_NEWLINE: + break; + + case TOK_SEMI: +/* + * This case is when we discover no statement and just a lone ';' + */ + if (state != STATE_STMT) { + ejLexPutbackToken(ep, tid, ep->token); + } + done++; + break; + + case TOK_ID: +/* + * This could either be a reference to a variable or an assignment + */ + identifier = NULL; + setString(&identifier, ep->token); +/* + * Peek ahead to see if this is an assignment + */ + tid = ejLexGetToken(ep, state); + if (tid == TOK_ASSIGNMENT) { + if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) { + goto error; + } + if (flags & FLAGS_EXE) { + if ( state == STATE_DEC ) { + ejSetLocalVar(ep->eid, identifier, ep->result); + } + else { + if (ejGetVar(ep->eid, identifier, &value) > 0) { + ejSetLocalVar(ep->eid, identifier, ep->result); + } else { + ejSetGlobalVar(ep->eid, identifier, ep->result); + } + } + } + + } else if (tid == TOK_INC_DEC ) { + value = NULL; + if ( flags & FLAGS_EXE ) { + if (ejGetVar(ep->eid, identifier, &value) < 0) { + ejError(ep, T("Undefined variable %s\n"), identifier); + goto error; + } + setString(&ep->result, value); + if (evalExpr(ep, value, (int) *ep->token, T("1")) < 0) { + state = STATE_ERR; + break; + } + ejSetGlobalVar(ep->eid, identifier, ep->result); + } + + } else { +/* + * If we are processing a declaration, allow undefined vars + */ + value = NULL; + if (state == STATE_DEC) { + if (ejGetVar(ep->eid, identifier, &value) > 0) { + ejError(ep, T("Variable already declared"), + identifier); + clearString(&identifier); + goto error; + } + ejSetLocalVar(ep->eid, identifier, NULL); + } else { + if ( flags & FLAGS_EXE ) { + if (ejGetVar(ep->eid, identifier, &value) < 0) { + ejError(ep, T("Undefined variable %s\n"), + identifier); + clearString(&identifier); + goto error; + } + } + } + setString(&ep->result, value); + ejLexPutbackToken(ep, tid, ep->token); + } + clearString(&identifier); + + if (state == STATE_STMT) { + expectSemi++; + } + done++; + break; + + case TOK_LITERAL: +/* + * Set the result to the literal (number or string constant) + */ + setString(&ep->result, ep->token); + if (state == STATE_STMT) { + expectSemi++; + } + done++; + break; + + case TOK_FUNCTION: +/* + * We must save any current ep->func value for the current stack frame + */ + if (ep->func) { + saveFunc = ep->func; + } + memset(&func, 0, sizeof(ejfunc_t)); + setString(&func.fname, ep->token); + ep->func = &func; + + setString(&ep->result, T("")); + if (ejLexGetToken(ep, state) != TOK_LPAREN) { + freeFunc(&func); + goto error; + } + + if (parse(ep, STATE_ARG_LIST, flags) != STATE_ARG_LIST_DONE) { + freeFunc(&func); + ep->func = saveFunc; + goto error; + } +/* + * Evaluate the function if required + */ + if (flags & FLAGS_EXE && evalFunction(ep) < 0) { + freeFunc(&func); + ep->func = saveFunc; + goto error; + } + + freeFunc(&func); + ep->func = saveFunc; + + if (ejLexGetToken(ep, state) != TOK_RPAREN) { + goto error; + } + if (state == STATE_STMT) { + expectSemi++; + } + done++; + break; + + case TOK_IF: + if (state != STATE_STMT) { + goto error; + } + if (ejLexGetToken(ep, state) != TOK_LPAREN) { + goto error; + } +/* + * Evaluate the entire condition list "(condition)" + */ + if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) { + goto error; + } + if (ejLexGetToken(ep, state) != TOK_RPAREN) { + goto error; + } +/* + * This is the "then" case. We need to always parse both cases and + * execute only the relevant case. + */ + if (*ep->result == '1') { + thenFlags = flags; + elseFlags = flags & ~FLAGS_EXE; + } else { + thenFlags = flags & ~FLAGS_EXE; + elseFlags = flags; + } +/* + * Process the "then" case + */ + if (parse(ep, STATE_STMT, thenFlags) != STATE_STMT_DONE) { + goto error; + } + tid = ejLexGetToken(ep, state); + if (tid != TOK_ELSE) { + ejLexPutbackToken(ep, tid, ep->token); + done++; + break; + } +/* + * Process the "else" case + */ + if (parse(ep, STATE_STMT, elseFlags) != STATE_STMT_DONE) { + goto error; + } + done++; + break; + + case TOK_FOR: +/* + * Format for the expression is: + * + * for (initial; condition; incr) { + * body; + * } + */ + if (state != STATE_STMT) { + goto error; + } + if (ejLexGetToken(ep, state) != TOK_LPAREN) { + goto error; + } + +/* + * Evaluate the for loop initialization statement + */ + if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) { + goto error; + } + if (ejLexGetToken(ep, state) != TOK_SEMI) { + goto error; + } + +/* + * The first time through, we save the current input context just + * to each step: prior to the conditional, the loop increment and the + * loop body. + */ + ejLexSaveInputState(ep, &condScript); + if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) { + goto error; + } + cond = (*ep->result != '0'); + + if (ejLexGetToken(ep, state) != TOK_SEMI) { + goto error; + } + +/* + * Don't execute the loop increment statement or the body first time + */ + forFlags = flags & ~FLAGS_EXE; + ejLexSaveInputState(ep, &incrScript); + if (parse(ep, STATE_EXPR, forFlags) != STATE_EXPR_DONE) { + goto error; + } + if (ejLexGetToken(ep, state) != TOK_RPAREN) { + goto error; + } + +/* + * Parse the body and remember the end of the body script + */ + ejLexSaveInputState(ep, &bodyScript); + if (parse(ep, STATE_STMT, forFlags) != STATE_STMT_DONE) { + goto error; + } + ejLexSaveInputState(ep, &endScript); + +/* + * Now actually do the for loop. Note loop has been rotated + */ + while (cond && (flags & FLAGS_EXE) ) { +/* + * Evaluate the body + */ + ejLexRestoreInputState(ep, &bodyScript); + if (parse(ep, STATE_STMT, flags) != STATE_STMT_DONE) { + goto error; + } +/* + * Evaluate the increment script + */ + ejLexRestoreInputState(ep, &incrScript); + if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) { + goto error; + } +/* + * Evaluate the condition + */ + ejLexRestoreInputState(ep, &condScript); + if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) { + goto error; + } + cond = (*ep->result != '0'); + } + ejLexRestoreInputState(ep, &endScript); + done++; + break; + + case TOK_VAR: + if (parse(ep, STATE_DEC_LIST, flags) != STATE_DEC_LIST_DONE) { + goto error; + } + done++; + break; + + case TOK_COMMA: + ejLexPutbackToken(ep, TOK_EXPR, ep->token); + done++; + break; + + case TOK_LPAREN: + if (state == STATE_EXPR) { + if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) { + goto error; + } + if (ejLexGetToken(ep, state) != TOK_RPAREN) { + goto error; + } + return STATE_EXPR_DONE; + } + done++; + break; + + case TOK_RPAREN: + ejLexPutbackToken(ep, tid, ep->token); + return STATE_EXPR_DONE; + + case TOK_LBRACE: +/* + * This handles any code in braces except "if () {} else {}" + */ + if (state != STATE_STMT) { + goto error; + } + +/* + * Parse will return STATE_STMT_BLOCK_DONE when the RBRACE is seen + */ + do { + state = parse(ep, STATE_STMT, flags); + } while (state == STATE_STMT_DONE); + + if (ejLexGetToken(ep, state) != TOK_RBRACE) { + goto error; + } + return STATE_STMT_DONE; + + case TOK_RBRACE: + if (state == STATE_STMT) { + ejLexPutbackToken(ep, tid, ep->token); + return STATE_STMT_BLOCK_DONE; + } + goto error; + + case TOK_RETURN: + if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) { + goto error; + } + if (flags & FLAGS_EXE) { + while ( ejLexGetToken(ep, state) != TOK_EOF ); + done++; + return STATE_EOF; + } + break; + } + } + + if (expectSemi) { + tid = ejLexGetToken(ep, state); + if (tid != TOK_SEMI && tid != TOK_NEWLINE) { + goto error; + } + +/* + * Skip newline after semi-colon + */ + tid = ejLexGetToken(ep, state); + if (tid != TOK_NEWLINE) { + ejLexPutbackToken(ep, tid, ep->token); + } + } + +/* + * Free resources and return the correct status + */ +doneParse: + if (tid == TOK_FOR) { + ejLexFreeInputState(ep, &condScript); + ejLexFreeInputState(ep, &incrScript); + ejLexFreeInputState(ep, &endScript); + ejLexFreeInputState(ep, &bodyScript); + } + if (state == STATE_STMT) { + return STATE_STMT_DONE; + } else if (state == STATE_DEC) { + return STATE_DEC_DONE; + } else if (state == STATE_EXPR) { + return STATE_EXPR_DONE; + } else if (state == STATE_EOF) { + return state; + } else { + return STATE_ERR; + } + +/* + * Common error exit + */ +error: + state = STATE_ERR; + goto doneParse; +} + +/******************************************************************************/ +/* + * Parse variable declaration list + */ + +static int parseDeclaration(ej_t *ep, int state, int flags) +{ + int tid; + + a_assert(ep); + +/* + * Declarations can be of the following forms: + * var x; + * var x, y, z; + * var x = 1 + 2 / 3, y = 2 + 4; + * + * We set the variable to NULL if there is no associated assignment. + */ + + do { + if ((tid = ejLexGetToken(ep, state)) != TOK_ID) { + return STATE_ERR; + } + ejLexPutbackToken(ep, tid, ep->token); + +/* + * Parse the entire assignment or simple identifier declaration + */ + if (parse(ep, STATE_DEC, flags) != STATE_DEC_DONE) { + return STATE_ERR; + } + +/* + * Peek at the next token, continue if comma seen + */ + tid = ejLexGetToken(ep, state); + if (tid == TOK_SEMI) { + return STATE_DEC_LIST_DONE; + } else if (tid != TOK_COMMA) { + return STATE_ERR; + } + } while (tid == TOK_COMMA); + + if (tid != TOK_SEMI) { + return STATE_ERR; + } + return STATE_DEC_LIST_DONE; +} + +/******************************************************************************/ +/* + * Parse function arguments + */ + +static int parseArgs(ej_t *ep, int state, int flags) +{ + int tid, aid; + + a_assert(ep); + + do { + state = parse(ep, STATE_RELEXP, flags); + if (state == STATE_EOF || state == STATE_ERR) { + return state; + } + if (state == STATE_RELEXP_DONE) { + aid = hAlloc((void***) &ep->func->args); + ep->func->args[aid] = bstrdup(B_L, ep->result); + ep->func->nArgs++; + } +/* + * Peek at the next token, continue if more args (ie. comma seen) + */ + tid = ejLexGetToken(ep, state); + if (tid != TOK_COMMA) { + ejLexPutbackToken(ep, tid, ep->token); + } + } while (tid == TOK_COMMA); + + if (tid != TOK_RPAREN && state != STATE_RELEXP_DONE) { + return STATE_ERR; + } + return STATE_ARG_LIST_DONE; +} + +/******************************************************************************/ +/* + * Parse conditional expression (relational ops separated by ||, &&) + */ + +static int parseCond(ej_t *ep, int state, int flags) +{ + char_t *lhs, *rhs; + int tid, operator; + + a_assert(ep); + + setString(&ep->result, T("")); + rhs = lhs = NULL; + operator = 0; + + do { +/* + * Recurse to handle one side of a conditional. Accumulate the + * left hand side and the final result in ep->result. + */ + state = parse(ep, STATE_RELEXP, flags); + if (state != STATE_RELEXP_DONE) { + state = STATE_ERR; + break; + } + if (operator > 0) { + setString(&rhs, ep->result); + if (evalCond(ep, lhs, operator, rhs) < 0) { + state = STATE_ERR; + break; + } + } + setString(&lhs, ep->result); + + tid = ejLexGetToken(ep, state); + if (tid == TOK_LOGICAL) { + operator = (int) *ep->token; + + } else if (tid == TOK_RPAREN || tid == TOK_SEMI) { + ejLexPutbackToken(ep, tid, ep->token); + state = STATE_COND_DONE; + break; + + } else { + ejLexPutbackToken(ep, tid, ep->token); + } + + } while (state == STATE_RELEXP_DONE); + + if (lhs) { + bfree(B_L, lhs); + } + if (rhs) { + bfree(B_L, rhs); + } + return state; +} + +/******************************************************************************/ +/* + * Parse expression (leftHandSide operator rightHandSide) + */ + +static int parseExpr(ej_t *ep, int state, int flags) +{ + char_t *lhs, *rhs; + int rel, tid; + + a_assert(ep); + + setString(&ep->result, T("")); + rhs = lhs = NULL; + rel = 0; + + do { +/* + * This loop will handle an entire expression list. We call parse + * to evalutate each term which returns the result in ep->result. + */ + state = parse(ep, STATE_EXPR, flags); + if (state != STATE_EXPR_DONE) { + state = STATE_ERR; + break; + } + if (rel > 0) { + setString(&rhs, ep->result); + if (evalExpr(ep, lhs, rel, rhs) < 0) { + state = STATE_ERR; + break; + } + } + setString(&lhs, ep->result); + + if ((tid = ejLexGetToken(ep, state)) == TOK_EXPR) { + rel = (int) *ep->token; + + } else if (tid == TOK_INC_DEC) { + rel = (int) *ep->token; + + } else { + ejLexPutbackToken(ep, tid, ep->token); + state = STATE_RELEXP_DONE; + } + + } while (state == STATE_EXPR_DONE); + + if (rhs) + bfree(B_L, rhs); + if (lhs) + bfree(B_L, lhs); + return state; +} + +/******************************************************************************/ +/* + * Evaluate a condition. Implements &&, ||, ! + */ + +static int evalCond(ej_t *ep, char_t *lhs, int rel, char_t *rhs) +{ + char_t buf[16]; + int l, r, lval; + + a_assert(lhs); + a_assert(rhs); + a_assert(rel > 0); + + lval = 0; + if (gisdigit(*lhs) && gisdigit(*rhs)) { + l = gatoi(lhs); + r = gatoi(rhs); + switch (rel) { + case COND_AND: + lval = l && r; + break; + case COND_OR: + lval = l || r; + break; + default: + ejError(ep, T("Bad operator %d"), rel); + return -1; + } + } else { + if (!gisdigit(*lhs)) { + ejError(ep, T("Conditional must be numeric"), lhs); + } else { + ejError(ep, T("Conditional must be numeric"), rhs); + } + } + + stritoa(lval, buf, sizeof(buf)); + setString(&ep->result, buf); + return 0; +} + +/******************************************************************************/ +/* + * Evaluate an operation + */ + +static int evalExpr(ej_t *ep, char_t *lhs, int rel, char_t *rhs) +{ + char_t *cp, buf[16]; + int numeric, l, r, lval; + + a_assert(lhs); + a_assert(rhs); + a_assert(rel > 0); + +/* + * All of the characters in the lhs and rhs must be numeric + */ + numeric = 1; + for (cp = lhs; *cp; cp++) { + if (!gisdigit(*cp)) { + numeric = 0; + break; + } + } + if (numeric) { + for (cp = rhs; *cp; cp++) { + if (!gisdigit(*cp)) { + numeric = 0; + break; + } + } + } + if (numeric) { + l = gatoi(lhs); + r = gatoi(rhs); + switch (rel) { + case EXPR_PLUS: + lval = l + r; + break; + case EXPR_INC: + lval = l + 1; + break; + case EXPR_MINUS: + lval = l - r; + break; + case EXPR_DEC: + lval = l - 1; + break; + case EXPR_MUL: + lval = l * r; + break; + case EXPR_DIV: + if (r != 0) { + lval = l / r; + } else { + lval = 0; + } + break; + case EXPR_MOD: + if (r != 0) { + lval = l % r; + } else { + lval = 0; + } + break; + case EXPR_LSHIFT: + lval = l << r; + break; + case EXPR_RSHIFT: + lval = l >> r; + break; + case EXPR_EQ: + lval = l == r; + break; + case EXPR_NOTEQ: + lval = l != r; + break; + case EXPR_LESS: + lval = (l < r) ? 1 : 0; + break; + case EXPR_LESSEQ: + lval = (l <= r) ? 1 : 0; + break; + case EXPR_GREATER: + lval = (l > r) ? 1 : 0; + break; + case EXPR_GREATEREQ: + lval = (l >= r) ? 1 : 0; + break; + default: + ejError(ep, T("Bad operator %d"), rel); + return -1; + } + + } else { + switch (rel) { + case EXPR_PLUS: + clearString(&ep->result); + appendString(&ep->result, lhs); + appendString(&ep->result, rhs); + return 0; + case EXPR_LESS: + lval = gstrcmp(lhs, rhs) < 0; + break; + case EXPR_LESSEQ: + lval = gstrcmp(lhs, rhs) <= 0; + break; + case EXPR_GREATER: + lval = gstrcmp(lhs, rhs) > 0; + break; + case EXPR_GREATEREQ: + lval = gstrcmp(lhs, rhs) >= 0; + break; + case EXPR_EQ: + lval = gstrcmp(lhs, rhs) == 0; + break; + case EXPR_NOTEQ: + lval = gstrcmp(lhs, rhs) != 0; + break; + case EXPR_INC: + case EXPR_DEC: + case EXPR_MINUS: + case EXPR_DIV: + case EXPR_MOD: + case EXPR_LSHIFT: + case EXPR_RSHIFT: + default: + ejError(ep, T("Bad operator")); + return -1; + } + } + + stritoa(lval, buf, sizeof(buf)); + setString(&ep->result, buf); + return 0; +} + +/******************************************************************************/ +/* + * Evaluate a function + */ + +static int evalFunction(ej_t *ep) +{ + sym_t *sp; + int (*fn)(int eid, void *handle, int argc, char_t **argv); + + if ((sp = symLookup(ep->functions, ep->func->fname)) == NULL) { + ejError(ep, T("Undefined procedure %s"), ep->func->fname); + return -1; + } + fn = (int (*)(int, void*, int, char_t**)) sp->content.value.integer; + if (fn == NULL) { + ejError(ep, T("Undefined procedure %s"), ep->func->fname); + return -1; + } + + return (*fn)(ep->eid, (void*) ep->userHandle, ep->func->nArgs, + ep->func->args); +} + +/******************************************************************************/ +/* + * Output a parse ej_error message + */ + +void ejError(ej_t* ep, char_t* fmt, ...) +{ + va_list args; + ejinput_t *ip; + char_t *errbuf, *msgbuf; + + a_assert(ep); + a_assert(fmt); + ip = ep->input; + + va_start(args, fmt); + msgbuf = NULL; + gvsnprintf(&msgbuf, E_MAX_ERROR, fmt, args); + va_end(args); + + if (ep && ip) { + errbuf = NULL; + gsnprintf(&errbuf, E_MAX_ERROR, T("%s\n At line %d, line => \n\n%s\n"), + msgbuf, ip->lineNumber, ip->line); + bfreeSafe(B_L, ep->error); + ep->error = errbuf; + } + bfreeSafe(B_L, msgbuf); +} + +/******************************************************************************/ +/* + * Clear a string value + */ + +static void clearString(char_t **ptr) +{ + a_assert(ptr); + + if (*ptr) { + bfree(B_L, *ptr); + } + *ptr = NULL; +} + +/******************************************************************************/ +/* + * Set a string value + */ + +static void setString(char_t **ptr, char_t *s) +{ + a_assert(ptr); + + if (*ptr) { + bfree(B_L, *ptr); + } + *ptr = bstrdup(B_L, s); +} + +/******************************************************************************/ +/* + * Append to the pointer value + */ + +static void appendString(char_t **ptr, char_t *s) +{ + int len, oldlen; + + a_assert(ptr); + + if (*ptr) { + len = gstrlen(s); + oldlen = gstrlen(*ptr); + *ptr = brealloc(B_L, *ptr, (len + oldlen + 1) * sizeof(char_t)); + gstrcpy(&(*ptr)[oldlen], s); + } else { + *ptr = bstrdup(B_L, s); + } +} + +/******************************************************************************/ +/* + * Define a function + */ + +int ejSetGlobalFunction(int eid, char_t *name, + int (*fn)(int eid, void *handle, int argc, char_t **argv)) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return -1; + } + return ejSetGlobalFunctionDirect(ep->functions, name, fn); +} + +/******************************************************************************/ +/* + * Define a function directly into the function symbol table. + */ + +int ejSetGlobalFunctionDirect(sym_fd_t functions, char_t *name, + int (*fn)(int eid, void *handle, int argc, char_t **argv)) +{ + if (symEnter(functions, name, valueInteger((long) fn), 0) == NULL) { + return -1; + } + return 0; +} + +/******************************************************************************/ +/* + * Get a function definition + */ + +void *ejGetGlobalFunction(int eid, char_t *name) +{ + ej_t *ep; + sym_t *sp; + int (*fn)(int eid, void *handle, int argc, char_t **argv); + + if ((ep = ejPtr(eid)) == NULL) { + return NULL; + } + if ((sp = symLookup(ep->functions, name)) != NULL) { + fn = (int (*)(int, void*, int, char_t**)) sp->content.value.integer; + return (void*) fn; + } + return NULL; +} + +/******************************************************************************/ +/* + * Utility routine to crack Ejscript arguments. Return the number of args + * seen. This routine only supports %s and %d type args. + * + * Typical usage: + * + * if (ejArgs(argc, argv, "%s %d", &name, &age) < 2) { + * error("Insufficient args\n"); + * return -1; + * } + */ + +int ejArgs(int argc, char_t **argv, char_t *fmt, ...) +{ + va_list vargs; + char_t *cp, **sp; + int *ip; + int argn; + + va_start(vargs, fmt); + + if (argv == NULL) { + return 0; + } + + for (argn = 0, cp = fmt; cp && *cp && argv[argn]; ) { + if (*cp++ != '%') { + continue; + } + + switch (*cp) { + case 'd': + ip = va_arg(vargs, int*); + *ip = gatoi(argv[argn]); + break; + + case 's': + sp = va_arg(vargs, char_t**); + *sp = argv[argn]; + break; + + default: +/* + * Unsupported + */ + a_assert(0); + } + argn++; + } + + va_end(vargs); + return argn; +} + +/******************************************************************************/ +/* + * Define the user handle + */ + +void ejSetUserHandle(int eid, int handle) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return; + } + ep->userHandle = handle; +} + +/******************************************************************************/ +/* + * Get the user handle + */ + +int ejGetUserHandle(int eid) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return -1; + } + return ep->userHandle; +} + +/******************************************************************************/ +/* + * Get the current line number + */ + +int ejGetLineNumber(int eid) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return -1; + } + return ep->input->lineNumber; +} + +/******************************************************************************/ +/* + * Set the result + */ + +void ejSetResult(int eid, char_t *s) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return; + } + setString(&ep->result, s); +} + +/******************************************************************************/ +/* + * Get the result + */ + +char_t *ejGetResult(int eid) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return NULL; + } + return ep->result; +} + +/******************************************************************************/ +/* + * Set a variable. Note: a variable with a value of NULL means declared but + * undefined. The value is defined in the top-most variable frame. + */ + +void ejSetVar(int eid, char_t *var, char_t *value) +{ + ej_t *ep; + value_t v; + + a_assert(var && *var); + + if ((ep = ejPtr(eid)) == NULL) { + return; + } + if (value == NULL) { + v = valueString(value, 0); + } else { + v = valueString(value, VALUE_ALLOCATE); + } + symEnter(ep->variables[ep->variableMax - 1] - EJ_OFFSET, var, v, 0); +} + +/******************************************************************************/ +/* + * Set a local variable. Note: a variable with a value of NULL means + * declared but undefined. The value is defined in the top-most variable frame. + */ + +void ejSetLocalVar(int eid, char_t *var, char_t *value) +{ + ej_t *ep; + value_t v; + + a_assert(var && *var); + + if ((ep = ejPtr(eid)) == NULL) { + return; + } + if (value == NULL) { + v = valueString(value, 0); + } else { + v = valueString(value, VALUE_ALLOCATE); + } + symEnter(ep->variables[ep->variableMax - 1] - EJ_OFFSET, var, v, 0); +} + +/******************************************************************************/ +/* + * Set a global variable. Note: a variable with a value of NULL means + * declared but undefined. The value is defined in the global variable frame. + */ + +void ejSetGlobalVar(int eid, char_t *var, char_t *value) +{ + ej_t *ep; + value_t v; + + a_assert(var && *var); + + if ((ep = ejPtr(eid)) == NULL) { + return; + } + if (value == NULL) { + v = valueString(value, 0); + } else { + v = valueString(value, VALUE_ALLOCATE); + } + symEnter(ep->variables[0] - EJ_OFFSET, var, v, 0); +} + +/******************************************************************************/ +/* + * Get a variable + */ + +int ejGetVar(int eid, char_t *var, char_t **value) +{ + ej_t *ep; + sym_t *sp; + int i; + + a_assert(var && *var); + a_assert(value); + + if ((ep = ejPtr(eid)) == NULL) { + return -1; + } + + for (i = ep->variableMax - 1; i >= 0; i--) { + if ((sp = symLookup(ep->variables[i] - EJ_OFFSET, var)) == NULL) { + continue; + } + a_assert(sp->content.type == string); + *value = sp->content.value.string; + return i; + } + return -1; +} + +/******************************************************************************/ +#if UNUSED +/* + * Get the variable symbol table + */ + +sym_fd_t ejGetVariableTable(int eid) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return -1; + } + return ep->variables; +} +#endif +/******************************************************************************/ +/* + * Get the functions symbol table + */ + +sym_fd_t ejGetFunctionTable(int eid) +{ + ej_t *ep; + + if ((ep = ejPtr(eid)) == NULL) { + return -1; + } + return ep->functions; +} + +/******************************************************************************/ +/* + * Free an argument list + */ + +static void freeFunc(ejfunc_t *func) +{ + int i; + + for (i = func->nArgs - 1; i >= 0; i--) { + bfree(B_L, func->args[i]); + func->nArgs = hFree((void***) &func->args, i); + } + if (func->fname) { + bfree(B_L, func->fname); + func->fname = NULL; + } +} + +/******************************************************************************/ +/* + * Get Ejscript pointer + */ + +static ej_t *ejPtr(int eid) +{ + a_assert(0 <= eid && eid < ejMax); + + if (eid < 0 || eid >= ejMax || ejHandles[eid] == NULL) { + ejError(NULL, T("Bad handle %d"), eid); + return NULL; + } + return ejHandles[eid]; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/form.c b/cpukit/httpd/form.c new file mode 100644 index 0000000000..05d5600ada --- /dev/null +++ b/cpukit/httpd/form.c @@ -0,0 +1,163 @@ +/* + * form.c -- Form processing (in-memory CGI) for the GoAhead Web 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 the /goform handler. It emulates CGI processing + * but performs this in-process and not as an external process. This enables + * a very high performance implementation with easy parsing and decoding + * of query strings and posted data. + */ + +/*********************************** Includes *********************************/ + +#include "wsIntrn.h" + +/************************************ Locals **********************************/ + +static sym_fd_t formSymtab = -1; /* Symbol table for form handlers */ + +/************************************* Code ***********************************/ +/* + * Process a form request. Returns 1 always to indicate it handled the URL + */ + +int websFormHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg, + char_t *url, char_t *path, char_t* query) +{ + sym_t *sp; + char_t formBuf[FNAMESIZE]; + char_t *cp, *formName; + int (*fn)(void *sock, char_t* path, char_t *args); + + a_assert(websValid(wp)); + a_assert(url && *url); + a_assert(path && *path == '/'); + + websStats.formHits++; + +/* + * Extract the form name + */ + gstrncpy(formBuf, path, TSZ(formBuf)); + if ((formName = gstrchr(&formBuf[1], '/')) == NULL) { + websError(wp, 200, T("Missing form name")); + return 1; + } + formName++; + if ((cp = gstrchr(formName, '/')) != NULL) { + *cp = '\0'; + } + +/* + * Lookup the C form function first and then try tcl (no javascript support + * yet). + */ + sp = symLookup(formSymtab, formName); + if (sp == NULL) { + websError(wp, 200, T("Form %s is not defined"), formName); + } else { + fn = (int (*)(void*, char_t*, char_t*)) sp->content.value.integer; + a_assert(fn); + if (fn) { +/* + * For good practice, forms must call websDone() + */ + (*fn)((void*) wp, formName, query); + if (websValid(wp)) { + websError(wp, 200, T("Form didn't call websDone")); + } + } + } + return 1; +} + +/******************************************************************************/ +/* + * Define a form function in the "form" map space. + */ + +int websFormDefine(char_t *name, void (*fn)(webs_t wp, char_t *path, + char_t *query)) +{ + static int once = 0; + + a_assert(name && *name); + a_assert(fn); + + if (fn == NULL) { + return -1; + } + + if (once++ == 0) { + websFormOpen(); + } + symEnter(formSymtab, name, valueInteger((int) fn), (int) NULL); + return 0; +} + +/******************************************************************************/ +/* + * Open the symbol table for forms. + */ + +void websFormOpen() +{ + formSymtab = symOpen(64); +} + +/******************************************************************************/ +/* + * Close the symbol table for forms. + */ + +void websFormClose() +{ + if (formSymtab != -1) { + symClose(formSymtab, NULL); + } +} + +/******************************************************************************/ +/* + * Write a webs header. This is a convenience routine to write a common + * header for a form back to the browser. + */ + +void websHeader(webs_t wp) +{ + a_assert(websValid(wp)); + + websWrite(wp, T("HTTP/1.0 200 OK\n")); + +/* + * By license terms the following line of code must not be modified + */ + websWrite(wp, T("Server: GoAhead-Webs\r\n")); + + websWrite(wp, T("Pragma: no-cache\n")); + websWrite(wp, T("Cache-control: no-cache\n")); + websWrite(wp, T("Content-Type: text/html\n")); + websWrite(wp, T("\n")); + websWrite(wp, T("\n")); +} + +/******************************************************************************/ +/* + * Write a webs footer + */ + +void websFooter(webs_t wp) +{ + a_assert(websValid(wp)); + + websWrite(wp, T("\n")); +} + +/******************************************************************************/ diff --git a/cpukit/httpd/h.c b/cpukit/httpd/h.c new file mode 100644 index 0000000000..ed7a4553eb --- /dev/null +++ b/cpukit/httpd/h.c @@ -0,0 +1,171 @@ +/* + * h.c -- Handle allocation module + * + * Copyright (c) GoAhead Software Inc., 1995-1999. All Rights Reserved. + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * This module provides a simple API to allocate and free handles + * It maintains a dynamic array of pointers. These usually point to + * per-handle structures. + */ + +/********************************* Includes ***********************************/ + +#if UEMF + #include "uemf.h" +#else + #include "basic/basicInternal.h" +#endif + +/********************************** Defines ***********************************/ +/* + * The handle list stores the length of the list and the number of used + * handles in the first two words. These are hidden from the caller by + * returning a pointer to the third word to the caller + */ + +#define H_LEN 0 /* First entry holds length of list */ +#define H_USED 1 /* Second entry holds number of used */ +#define H_OFFSET 2 /* Offset to real start of list */ + +#define H_INCR 16 /* Grow handle list in chunks this size */ + +/*********************************** Code *************************************/ +/* + * Allocate a new file handle. On the first call, the caller must set the + * handle map to be a pointer to a null pointer. *map points to the second + * element in the handle array. + */ + +int hAlloc(void ***map) +{ + int *mp; + int handle, len, memsize, incr; + + a_assert(map); + + if (*map == NULL) { + incr = H_INCR; + memsize = (incr + H_OFFSET) * sizeof(void**); + if ((mp = (int*) balloc(B_L, memsize)) == NULL) { + return -1; + } + memset(mp, 0, memsize); + mp[H_LEN] = incr; + mp[H_USED] = 0; + *map = (void**) &mp[H_OFFSET]; + } else { + mp = &((*(int**)map)[-H_OFFSET]); + } + + len = mp[H_LEN]; + +/* + * Find the first null handle + */ + if (mp[H_USED] < mp[H_LEN]) { + for (handle = 0; handle < len; handle++) + if (mp[handle+H_OFFSET] == 0) { + mp[H_USED]++; + return handle; + } + } else { + handle = len; + } + +/* + * No free handle so grow the handle list. Grow list in chunks of H_INCR. + */ + len += H_INCR; + memsize = (len + H_OFFSET) * sizeof(void**); + if ((mp = (int*) brealloc(B_L, (void*) mp, memsize)) == NULL) { + return -1; + } + *map = (void**) &mp[H_OFFSET]; + mp[H_LEN] = len; + memset(&mp[H_OFFSET + len - H_INCR], 0, sizeof(int*) * H_INCR); + mp[H_USED]++; + return handle; +} + +/******************************************************************************/ +/* + * Free a handle. This function returns the value of the largest + * handle in use plus 1, to be saved as a max value. + */ + +int hFree(void ***map, int handle) +{ + int *mp; + int len; + + a_assert(map); + mp = &((*(int**)map)[-H_OFFSET]); + a_assert(mp[H_LEN] >= H_INCR); + + a_assert(mp[handle + H_OFFSET]); + a_assert(mp[H_USED]); + mp[handle + H_OFFSET] = 0; + if (--(mp[H_USED]) == 0) { + bfree(B_L, (void*) mp); + *map = NULL; + } + +/* + * Find the greatest handle number in use. + */ + if (*map == NULL) { + handle = -1; + } else { + len = mp[H_LEN]; + if (mp[H_USED] < mp[H_LEN]) { + for (handle = len - 1; handle >= 0; handle--) { + if (mp[handle + H_OFFSET]) + break; + } + } else { + handle = len; + } + } + return handle + 1; +} + +/******************************************************************************/ +/* + * Allocate an entry in the halloc array. + */ + +int hAllocEntry(void ***list, int *max, int size) +{ + char_t *cp; + int id; + + a_assert(list); + a_assert(max); + + if ((id = hAlloc((void***) list)) < 0) { + return -1; + } + + if (size > 0) { + if ((cp = balloc(B_L, size)) == NULL) { + hFree(list, id); + return -1; + } + a_assert(cp); + memset(cp, 0, size); + + (*list)[id] = (void*) cp; + } + + if (id >= *max) { + *max = id + 1; + } + return id; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/handler.c b/cpukit/httpd/handler.c new file mode 100644 index 0000000000..d481ec3c0c --- /dev/null +++ b/cpukit/httpd/handler.c @@ -0,0 +1,284 @@ +/* + * handler.c -- URL handler support + * + * 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 a URL handler interface and API to permit + * the addition of user definable URL processors. + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/*********************************** Locals ***********************************/ + +static websUrlHandlerType* websUrlHandler; /* URL handler list */ +static int websUrlHandlerMax; /* Number of entries */ + +/**************************** Forward Declarations ****************************/ + +static int websUrlHandlerSort(const void* p1, const void* p2); +static int websPublishHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, + int sid, char_t *url, char_t *path, char_t *query); + +/*********************************** Code *************************************/ +/* + * Initialize the URL handler module + */ + +int websUrlHandlerOpen() +{ + websAspOpen(); + return 0; +} + +/******************************************************************************/ +/* + * Close the URL handler module + */ + +void websUrlHandlerClose() +{ + websUrlHandlerType* sp; + + websAspClose(); + for (sp = websUrlHandler; sp < &websUrlHandler[websUrlHandlerMax]; sp++) { + bfree(B_L, sp->urlPrefix); + if (sp->webDir) { + bfree(B_L, sp->webDir); + } + } + bfree(B_L, websUrlHandler); + websUrlHandlerMax = 0; +} + +/******************************************************************************/ +/* + * Define a new URL handler. urlPrefix is the URL prefix to match. webDir is + * an optional root directory path for a web directory. arg is an optional + * arg to pass to the URL handler. flags defines the matching order. Valid + * flags include WEBS_HANDLER_LAST, WEBS_HANDLER_FIRST. If multiple users + * specify last or first, their order is defined alphabetically by the + * urlPrefix. + */ + +int websUrlHandlerDefine(char_t *urlPrefix, char_t *webDir, int arg, + int (*handler)(webs_t wp, char_t *urlPrefix, char_t *webdir, int arg, + char_t *url, char_t *path, char_t *query), int flags) +{ + websUrlHandlerType *sp; + int len; + + a_assert(urlPrefix); + a_assert(handler); + +/* + * Grow the URL handler array to create a new slot + */ + len = (websUrlHandlerMax + 1) * sizeof(websUrlHandlerType); + if ((websUrlHandler = brealloc(B_L, websUrlHandler, len)) == NULL) { + return -1; + } + sp = &websUrlHandler[websUrlHandlerMax++]; + memset(sp, 0, sizeof(websUrlHandlerType)); + + sp->urlPrefix = bstrdup(B_L, urlPrefix); + sp->len = gstrlen(sp->urlPrefix); + if (webDir) { + sp->webDir = bstrdup(B_L, webDir); + } else { + sp->webDir = bstrdup(B_L, T("")); + } + sp->handler = handler; + sp->arg = arg; + sp->flags = flags; + +/* + * Sort in decreasing URL length order observing the flags for first and last + */ + qsort(websUrlHandler, websUrlHandlerMax, sizeof(websUrlHandlerType), + websUrlHandlerSort); + return 0; +} + +/******************************************************************************/ +/* + * Delete an existing URL handler. We don't reclaim the space of the old + * handler, just NULL the entry. Return -1 if handler is not found. + */ + +int websUrlHandlerDelete(int (*handler)(webs_t wp, char_t *urlPrefix, + char_t *webDir, int arg, char_t *url, char_t *path, char_t *query)) +{ + websUrlHandlerType *sp; + int i; + + for (i = 0; i < websUrlHandlerMax; i++) { + sp = &websUrlHandler[i]; + if (sp->handler == handler) { + sp->handler = NULL; + return 0; + } + } + return -1; +} + +/******************************************************************************/ +/* + * Sort in decreasing URL length order observing the flags for first and last + */ + +static int websUrlHandlerSort(const void* p1, const void* p2) +{ + websUrlHandlerType *s1, *s2; + int rc; + + a_assert(p1); + a_assert(p2); + + s1 = (websUrlHandlerType*) p1; + s2 = (websUrlHandlerType*) p2; + + if ((s1->flags & WEBS_HANDLER_FIRST) || (s2->flags & WEBS_HANDLER_LAST)) { + return -1; + } + + if ((s2->flags & WEBS_HANDLER_FIRST) || (s1->flags & WEBS_HANDLER_LAST)) { + return 1; + } + + if ((rc = gstrcmp(s1->urlPrefix, s2->urlPrefix)) == 0) { + if (s1->len < s2->len) { + return 1; + } else if (s1->len > s2->len) { + return -1; + } + } + return -rc; +} + +/******************************************************************************/ +/* + * Publish a new web directory (Use the default URL handler) + */ + +int websPublish(char_t *urlPrefix, char_t *path) +{ + return websUrlHandlerDefine(urlPrefix, path, 0, websPublishHandler, 0); +} + +/******************************************************************************/ +/* + * Return the directory for a given prefix. Ignore empty prefixes + */ + +char_t *websGetPublishDir(char_t *path, char_t **urlPrefix) +{ + websUrlHandlerType *sp; + int i; + + for (i = 0; i < websUrlHandlerMax; i++) { + sp = &websUrlHandler[i]; + if (sp->urlPrefix[0] == '\0') { + continue; + } + if (sp->handler && gstrncmp(sp->urlPrefix, path, sp->len) == 0) { + if (urlPrefix) { + *urlPrefix = sp->urlPrefix; + } + return sp->webDir; + } + } + return NULL; +} + +/******************************************************************************/ +/* + * Publish URL handler. We just patch the web page Directory and let the + * default handler do the rest. + */ + +static int websPublishHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, + int sid, char_t *url, char_t *path, char_t *query) +{ + int len; + + a_assert(websValid(wp)); + a_assert(path); + +/* + * Trim the urlPrefix off the path and set the webdirectory. Add one to step + * over the trailing '/' + */ + len = gstrlen(urlPrefix) + 1; + websSetRequestPath(wp, webDir, &path[len]); + return 0; +} + +/******************************************************************************/ +/* + * See if any valid handlers are defined for this request. If so, call them + * and continue calling valid handlers until one accepts the request. + * Return true if a handler was invoked, else return FALSE. + */ + +int websUrlHandlerRequest(webs_t wp) +{ + websUrlHandlerType *sp; + int i, first; + + a_assert(websValid(wp)); + +/* + * Delete the socket handler as we don't want to start reading any + * data on the connection as it may be for the next pipelined HTTP/1.1 + * request if using Keep Alive + */ + socketDeleteHandler(wp->sid); + wp->state = WEBS_PROCESSING; + websStats.handlerHits++; + + websSetRequestPath(wp, websGetDefaultDir(), NULL); + +/* + * We loop over each handler in order till one accepts the request. + * The security handler will handle the request if access is NOT allowed. + */ + first = 1; + for (i = 0; i < websUrlHandlerMax; i++) { + sp = &websUrlHandler[i]; + if (sp->handler && gstrncmp(sp->urlPrefix, wp->path, sp->len) == 0) { + if (first) { + websSetEnv(wp); + first = 0; + } + if ((*sp->handler)(wp, sp->urlPrefix, sp->webDir, sp->arg, + wp->url, wp->path, wp->query)) { + return 1; + } + if (!websValid(wp)) { + trace(0, + T("webs: handler %s called websDone, but didn't return 1\n"), + sp->urlPrefix); + return 1; + } + } + } +/* + * If no handler processed the request, then return an error. Note: It was + * the handlers responsibility to call websDone + */ + if (i >= websUrlHandlerMax) { + websError(wp, 200, T("No handler for this URL %s"), wp->url); + } + return 0; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/mime.c b/cpukit/httpd/mime.c new file mode 100644 index 0000000000..2aa2422ca9 --- /dev/null +++ b/cpukit/httpd/mime.c @@ -0,0 +1,112 @@ +/* + * mime.c -- Web server mime types + * + * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * Mime types and file extensions. This module maps URL extensions to + * content types. + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/******************************** Global Data *********************************/ +/* + * Addd entries to the MimeList as required for your content + */ + +websMimeType websMimeList[] = { + { T("application/java"), T(".class") }, + { T("application/java"), T(".jar") }, + { T("text/html"), T(".asp") }, + { T("text/html"), T(".htm") }, + { T("text/html"), T(".html") }, + { T("image/gif"), T(".gif") }, + { T("image/jpeg"), T(".jpg") }, + { T("text/css"), T(".css") }, + { T("text/plain"), T(".txt") }, + +#if MORE_MIME_TYPES + { T("application/binary"), T(".exe") }, + { T("application/compress"), T(".z") }, + { T("application/gzip"), T(".gz") }, + { T("application/octet-stream"), T(".bin") }, + { T("application/oda"), T(".oda") }, + { T("application/pdf"), T(".pdf") }, + { T("application/postscript"), T(".ai") }, + { T("application/postscript"), T(".eps") }, + { T("application/postscript"), T(".ps") }, + { T("application/rtf"), T(".rtf") }, + { T("application/x-bcpio"), T(".bcpio") }, + { T("application/x-cpio"), T(".cpio") }, + { T("application/x-csh"), T(".csh") }, + { T("application/x-dvi"), T(".dvi") }, + { T("application/x-gtar"), T(".gtar") }, + { T("application/x-hdf"), T(".hdf") }, + { T("application/x-latex"), T(".latex") }, + { T("application/x-mif"), T(".mif") }, + { T("application/x-netcdf"), T(".nc") }, + { T("application/x-netcdf"), T(".cdf") }, + { T("application/x-ns-proxy-autoconfig"), T(".pac") }, + { T("application/x-patch"), T(".patch") }, + { T("application/x-sh"), T(".sh") }, + { T("application/x-shar"), T(".shar") }, + { T("application/x-sv4cpio"), T(".sv4cpio") }, + { T("application/x-sv4crc"), T(".sv4crc") }, + { T("application/x-tar"), T(".tar") }, + { T("application/x-tcl"), T(".tcl") }, + { T("application/x-tex"), T(".tex") }, + { T("application/x-texinfo"), T(".texinfo") }, + { T("application/x-texinfo"), T(".texi") }, + { T("application/x-troff"), T(".t") }, + { T("application/x-troff"), T(".tr") }, + { T("application/x-troff"), T(".roff") }, + { T("application/x-troff-man"), T(".man") }, + { T("application/x-troff-me"), T(".me") }, + { T("application/x-troff-ms"), T(".ms") }, + { T("application/x-ustar"), T(".ustar") }, + { T("application/x-wais-source"), T(".src") }, + { T("application/zip"), T(".zip") }, + { T("audio/basic"), T(".au snd") }, + { T("audio/x-aiff"), T(".aif") }, + { T("audio/x-aiff"), T(".aiff") }, + { T("audio/x-aiff"), T(".aifc") }, + { T("audio/x-wav"), T(".wav") }, + { T("audio/x-wav"), T(".ram") }, + { T("image/ief"), T(".ief") }, + { T("image/jpeg"), T(".jpeg") }, + { T("image/jpeg"), T(".jpe") }, + { T("image/tiff"), T(".tiff") }, + { T("image/tiff"), T(".tif") }, + { T("image/x-cmu-raster"), T(".ras") }, + { T("image/x-portable-anymap"), T(".pnm") }, + { T("image/x-portable-bitmap"), T(".pbm") }, + { T("image/x-portable-graymap"), T(".pgm") }, + { T("image/x-portable-pixmap"), T(".ppm") }, + { T("image/x-rgb"), T(".rgb") }, + { T("image/x-xbitmap"), T(".xbm") }, + { T("image/x-xpixmap"), T(".xpm") }, + { T("image/x-xwindowdump"), T(".xwd") }, + { T("text/html"), T(".cfm") }, + { T("text/html"), T(".shtm") }, + { T("text/html"), T(".shtml") }, + { T("text/richtext"), T(".rtx") }, + { T("text/tab-separated-values"), T(".tsv") }, + { T("text/x-setext"), T(".etx") }, + { T("video/mpeg"), T(".mpeg mpg mpe") }, + { T("video/quicktime"), T(".qt") }, + { T("video/quicktime"), T(".mov") }, + { T("video/x-msvideo"), T(".avi") }, + { T("video/x-sgi-movie"), T(".movie") }, +#endif + { NULL, NULL}, +}; + +/*****************************************************************************/ diff --git a/cpukit/httpd/misc.c b/cpukit/httpd/misc.c new file mode 100644 index 0000000000..5fbe63329c --- /dev/null +++ b/cpukit/httpd/misc.c @@ -0,0 +1,581 @@ +/* + * misc.c -- Miscellaneous routines. + * + * Copyright (c) GoAhead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/********************************* Includes ***********************************/ + +#if UEMF + #include "uemf.h" +#else + #include "basic/basicInternal.h" +#endif + +/********************************* Defines ************************************/ +/* + * Sprintf buffer structure. Make the increment 8 less than 64 so that + * a balloc can use a 64 byte block. + */ + +#define STR_REALLOC 0x1 /* Reallocate the buffer as required */ +#define STR_INC 58 /* Growth increment */ + +typedef struct { + char_t *s; /* Pointer to buffer */ + int size; /* Current buffer size */ + int max; /* Maximum buffer size */ + int count; /* Buffer count */ + int flags; /* Allocation flags */ +} strbuf_t; + +/* + * Sprintf formatting flags + */ +enum flag { + flag_none = 0, + flag_minus = 1, + flag_plus = 2, + flag_space = 4, + flag_hash = 8, + flag_zero = 16, + flag_short = 32, + flag_long = 64 +}; + +/************************** Forward Declarations ******************************/ + +static int dsnprintf(char_t **s, int size, char_t *fmt, va_list arg, + int msize); +static int strnlen(char_t *s, unsigned int n); +static void put_char(strbuf_t *buf, char_t c); +static void put_string(strbuf_t *buf, char_t *s, int len, + int width, int prec, enum flag f); +static void put_ulong(strbuf_t *buf, unsigned long int value, int base, + int upper, char_t *prefix, int width, int prec, enum flag f); + +/************************************ Code ************************************/ +/* + * "basename" returns a pointer to the last component of a pathname + * LINUX and LynxOS have their own basename function + */ + +#if ! LINUX & ! LYNX +char_t *basename(char_t* name) +{ + char_t *cp; + +#if NW || WIN + if (((cp = gstrrchr(name, '\\')) == NULL) && + ((cp = gstrrchr(name, '/')) == NULL)) { + return name; +#else + if ((cp = gstrrchr(name, '/')) == NULL) { + return name; +#endif + } else if (*(cp + 1) == '\0' && cp == name) { + return name; + } else if (*(cp + 1) == '\0' && cp != name) { + return T(""); + } else { + return ++cp; + } +} +#endif /* ! LINUX & ! LYNX */ + +/******************************************************************************/ +/* + * Returns a pointer to the directory component of a pathname. bufsize is + * the size of the buffer in BYTES! + */ + +char_t *dirname(char_t* buf, char_t* name, int bufsize) +{ + char_t* cp; + int len; + + a_assert(name); + a_assert(buf); + a_assert(bufsize > 0); + +#if WIN || NW + if ((cp = gstrrchr(name, '/')) == NULL && + (cp = gstrrchr(name, '\\')) == NULL) +#else + if ((cp = gstrrchr(name, '/')) == NULL) +#endif + { + gstrcpy(buf, T(".")); + return buf; + } + + if ((*(cp + 1) == '\0' && cp == name)) { + gstrncpy(buf, T("."), TSZ(bufsize)); + gstrcpy(buf, T(".")); + return buf; + } + + len = cp - name; + + if (len < bufsize) { + gstrncpy(buf, name, len); + buf[len] = '\0'; + } else { + gstrncpy(buf, name, TSZ(bufsize)); + buf[bufsize - 1] = '\0'; + } + + return buf; +} + +/******************************************************************************/ +/* + * sprintf and vsprintf are bad, ok. You can easily clobber memory. Use + * gsnprintf and gvsnprintf instead! These functions do _not_ support floating + * point, like %e, %f, %g... + */ + +int gsnprintf(char_t **s, int n, char_t *fmt, ...) +{ + va_list ap; + int result; + + a_assert(s); + a_assert(fmt); + + *s = NULL; + va_start(ap, fmt); + result = gvsnprintf(s, n, fmt, ap); + va_end(ap); + return result; +} + +/******************************************************************************/ +/* + * This function appends the formatted string to the supplied string, + * reallocing if required. + */ + +int gsprintfRealloc(char_t **s, int n, int msize, char_t *fmt, ...) +{ + va_list ap; + int result; + + a_assert(s); + a_assert(fmt); + + if (msize == -1) { + *s = NULL; + } + va_start(ap, fmt); + result = dsnprintf(s, n, fmt, ap, msize); + va_end(ap); + return result; +} + +/******************************************************************************/ +/* + * A vsprintf replacement. + */ + +int gvsnprintf(char_t **s, int n, char_t *fmt, va_list arg) +{ + a_assert(s); + a_assert(fmt); + + return dsnprintf(s, n, fmt, arg, 0); +} + +/******************************************************************************/ +/* + * Dynamic sprintf implementation. Supports dynamic buffer allocation also. + * This function can be called multiple times to grow an existing allocated + * buffer. In this case, msize is set to the size of the previously allocated + * buffer. The buffer will be realloced, as required. If msize is set, we + * return the size of the allocated buffer for use with the next call. For + * the first call, msize can be set to -1. + */ + +static int dsnprintf(char_t **s, int size, char_t *fmt, va_list arg, int msize) +{ + strbuf_t buf; + char_t c; + + a_assert(s); + a_assert(fmt); + + memset(&buf, 0, sizeof(buf)); + buf.s = *s; + + if (*s == NULL || msize != 0) { + buf.max = size; + buf.flags |= STR_REALLOC; + if (msize != 0) { + buf.size = max(msize, 0); + } + if (*s != NULL && msize != 0) { + buf.count = gstrlen(*s); + } + } else { + buf.size = size; + } + + while ((c = *fmt++) != '\0') { + if (c != '%' || (c = *fmt++) == '%') { + put_char(&buf, c); + } else { + enum flag f = flag_none; + int width = 0; + int prec = -1; + for ( ; c != '\0'; c = *fmt++) { + if (c == '-') { + f |= flag_minus; + } else if (c == '+') { + f |= flag_plus; + } else if (c == ' ') { + f |= flag_space; + } else if (c == '#') { + f |= flag_hash; + } else if (c == '0') { + f |= flag_zero; + } else { + break; + } + } + if (c == '*') { + width = va_arg(arg, int); + if (width < 0) { + f |= flag_minus; + width = -width; + } + c = *fmt++; + } else { + for ( ; gisdigit(c); c = *fmt++) { + width = width * 10 + (c - '0'); + } + } + if (c == '.') { + f &= ~flag_zero; + c = *fmt++; + if (c == '*') { + prec = va_arg(arg, int); + c = *fmt++; + } else { + for (prec = 0; gisdigit(c); c = *fmt++) { + prec = prec * 10 + (c - '0'); + } + } + } + if (c == 'h' || c == 'l') { + f |= (c == 'h' ? flag_short : flag_long); + c = *fmt++; + } + if (c == 'd' || c == 'i') { + long int value; + if (f & flag_short) { + value = (short int) va_arg(arg, int); + } else if (f & flag_long) { + value = va_arg(arg, long int); + } else { + value = va_arg(arg, int); + } + if (value >= 0) { + if (f & flag_plus) { + put_ulong(&buf, value, 10, 0, T("+"), width, prec, f); + } else if (f & flag_space) { + put_ulong(&buf, value, 10, 0, T(" "), width, prec, f); + } else { + put_ulong(&buf, value, 10, 0, NULL, width, prec, f); + } + } else { + put_ulong(&buf, -value, 10, 0, T("-"), width, prec, f); + } + } else if (c == 'o' || c == 'u' || c == 'x' || c == 'X') { + unsigned long int value; + if (f & flag_short) { + value = (unsigned short int) va_arg(arg, unsigned int); + } else if (f & flag_long) { + value = va_arg(arg, unsigned long int); + } else { + value = va_arg(arg, unsigned int); + } + if (c == 'o') { + if (f & flag_hash && value != 0) { + put_ulong(&buf, value, 8, 0, T("0"), width, prec, f); + } else { + put_ulong(&buf, value, 8, 0, NULL, width, prec, f); + } + } else if (c == 'u') { + put_ulong(&buf, value, 10, 0, NULL, width, prec, f); + } else { + if (f & flag_hash && value != 0) { + if (c == 'x') { + put_ulong(&buf, value, 16, 0, T("0x"), width, + prec, f); + } else { + put_ulong(&buf, value, 16, 1, T("0X"), width, + prec, f); + } + } else { + put_ulong(&buf, value, 16, 0, NULL, width, prec, f); + } + } + + } else if (c == 'c') { + char_t value = va_arg(arg, int); + put_char(&buf, value); + + } else if (c == 's' || c == 'S') { + char_t *value = va_arg(arg, char_t *); + if (value == NULL) { + put_string(&buf, T("(null)"), -1, width, prec, f); + } else if (f & flag_hash) { + put_string(&buf, + value + 1, (char_t) *value, width, prec, f); + } else { + put_string(&buf, value, -1, width, prec, f); + } + } else if (c == 'p') { + void *value = va_arg(arg, void *); + put_ulong(&buf, + (unsigned long int) value, 16, 0, T("0x"), width, prec, f); + } else if (c == 'n') { + if (f & flag_short) { + short int *value = va_arg(arg, short int *); + *value = buf.count; + } else if (f & flag_long) { + long int *value = va_arg(arg, long int *); + *value = buf.count; + } else { + int *value = va_arg(arg, int *); + *value = buf.count; + } + } else { + put_char(&buf, c); + } + } + } + if (buf.s == NULL) { + put_char(&buf, '\0'); + } + +/* + * If the user requested a dynamic buffer (*s == NULL), ensure it is returned. + */ + if (*s == NULL || msize != 0) { + *s = buf.s; + } + + if (*s != NULL && size > 0) { + if (buf.count < size) { + (*s)[buf.count] = '\0'; + } else { + (*s)[buf.size - 1] = '\0'; + } + } + + if (msize != 0) { + return buf.size; + } + return buf.count; +} + +/******************************************************************************/ +/* + * Return the length of a string limited by a given length + */ + +static int strnlen(char_t *s, unsigned int n) +{ + unsigned int len; + + len = gstrlen(s); + return min(len, n); +} + +/******************************************************************************/ +/* + * Add a character to a string buffer + */ + +static void put_char(strbuf_t *buf, char_t c) +{ + if (buf->count >= buf->size) { + if (! (buf->flags & STR_REALLOC)) { + return; + } + buf->size += STR_INC; + if (buf->size > buf->max && buf->size > STR_INC) { + a_assert(buf->size <= buf->max); + buf->size -= STR_INC; + return; + } + if (buf->s == NULL) { + buf->s = balloc(B_L, buf->size * sizeof(char_t*)); + } else { + buf->s = brealloc(B_L, buf->s, buf->size * sizeof(char_t*)); + } + } + buf->s[buf->count] = c; + ++buf->count; +} + +/******************************************************************************/ +/* + * Add a string to a string buffer + */ + +static void put_string(strbuf_t *buf, char_t *s, int len, int width, + int prec, enum flag f) +{ + int i; + + if (len < 0) { + len = strnlen(s, prec >= 0 ? prec : ULONG_MAX); + } else if (prec >= 0 && prec < len) { + len = prec; + } + if (width > len && !(f & flag_minus)) { + for (i = len; i < width; ++i) { + put_char(buf, ' '); + } + } + for (i = 0; i < len; ++i) { + put_char(buf, s[i]); + } + if (width > len && f & flag_minus) { + for (i = len; i < width; ++i) { + put_char(buf, ' '); + } + } +} + +/******************************************************************************/ +/* + * Add a long to a string buffer + */ + +static void put_ulong(strbuf_t *buf, unsigned long int value, int base, + int upper, char_t *prefix, int width, int prec, enum flag f) +{ + unsigned long x, x2; + int len, zeros, i; + + for (len = 1, x = 1; x < ULONG_MAX / base; ++len, x = x2) { + x2 = x * base; + if (x2 > value) { + break; + } + } + zeros = (prec > len) ? prec - len : 0; + width -= zeros + len; + if (prefix != NULL) { + width -= strnlen(prefix, ULONG_MAX); + } + if (!(f & flag_minus)) { + for (i = 0; i < width; ++i) { + put_char(buf, ' '); + } + } + if (prefix != NULL) { + put_string(buf, prefix, -1, 0, -1, flag_none); + } + for (i = 0; i < zeros; ++i) { + put_char(buf, '0'); + } + for ( ; x > 0; x /= base) { + int digit = (value / x) % base; + put_char(buf, (char) ((digit < 10 ? '0' : (upper ? 'A' : 'a') - 10) + + digit)); + } + if (f & flag_minus) { + for (i = 0; i < width; ++i) { + put_char(buf, ' '); + } + } +} + +/******************************************************************************/ +/* + * Convert an ansi string to a unicode string. On an error, we return the + * original ansi string which is better than returning NULL. nBytes is the + * size of the destination buffer (ubuf) in _bytes_. + */ + +char_t *ascToUni(char_t *ubuf, char *str, int nBytes) +{ +#if UNICODE + if (MultiByteToWideChar(CP_ACP, 0, str, nBytes / sizeof(char_t), ubuf, + nBytes / sizeof(char_t)) < 0) { + return (char_t*) str; + } +#else + memcpy(ubuf, str, nBytes); +#endif + return ubuf; +} + +/******************************************************************************/ +/* + * Convert a unicode string to an ansi string. On an error, return the + * original unicode string which is better than returning NULL. + * N.B. nBytes is the number of _bytes_ in the destination buffer, buf. + */ + +char *uniToAsc(char *buf, char_t* ustr, int nBytes) +{ +#if UNICODE + if (WideCharToMultiByte(CP_ACP, 0, ustr, nBytes, buf, nBytes, NULL, + NULL) < 0) { + return (char*) ustr; + } +#else + memcpy(buf, ustr, nBytes); +#endif + return (char*) buf; +} + + +/******************************************************************************/ +/* + * allocate (balloc) a buffer and do ascii to unicode conversion into it. + * cp points to the ascii string which must be NULL terminated. + * Return a pointer to the unicode buffer which must be bfree'd later. + * Return NULL on failure to get buffer. + */ +char_t *ballocAscToUni(char * cp) +{ + char_t * unip; + int ulen; + + ulen = (strlen(cp) + 1) * sizeof(char_t); + if ((unip = balloc(B_L, ulen)) == NULL) { + return NULL; + } + ascToUni(unip, cp, ulen); + return unip; +} + +/******************************************************************************/ +/* + * allocate (balloc) a buffer and do unicode to ascii conversion into it. + * unip points to the unicoded string. ulen is the number of characters + * in the unicode string including teminating null, if there is one. + * Return a pointer to the ascii buffer which must be bfree'd later. + * Return NULL on failure to get buffer. + */ +char *ballocUniToAsc(char_t * unip, int ulen) +{ + char * cp; + + if ((cp = balloc(B_L, ulen)) == NULL) { + return NULL; + } + uniToAsc(cp, unip, ulen); + return cp; +} + +/******************************************************************************/ + diff --git a/cpukit/httpd/ringq.c b/cpukit/httpd/ringq.c new file mode 100644 index 0000000000..62c4634eef --- /dev/null +++ b/cpukit/httpd/ringq.c @@ -0,0 +1,537 @@ +/* + * ringq.c -- Ring queue buffering module + * + * Copyright (c) GoAhead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * A ring queue allows maximum utilization of memory for data storage and is + * ideal for input/output buffering. This module provides a highly effecient + * implementation and a vehicle for dynamic strings. + * + * WARNING: This is a public implementation and callers have full access to + * the queue structure and pointers. Change this module very carefully. + * + * This module follows the open/close model. + * + * Operation of a ringq where rq is a pointer to a ringq : + * + * rq->buflen contains the size of the buffer. + * rq->buf will point to the start of the buffer. + * rq->servp will point to the first (un-consumed) data byte. + * rq->endp will point to the next free location to which new data is added + * rq->endbuf will point to one past the end of the buffer. + * + * Eg. If the ringq contains the data "abcdef", it might look like : + * + * +-------------------------------------------------------------------+ + * | | | | | | | | a | b | c | d | e | f | | | | | + * +-------------------------------------------------------------------+ + * ^ ^ ^ ^ + * | | | | + * rq->buf rq->servp rq->endp rq->enduf + * + * The queue is empty when servp == endp. This means that the queue will hold + * at most rq->buflen -1 bytes. It is the fillers responsibility to ensure + * the ringq is never filled such that servp == endp. + * + * It is the fillers responsibility to "wrap" the endp back to point to + * rq->buf when the pointer steps past the end. Correspondingly it is the + * consumers responsibility to "wrap" the servp when it steps to rq->endbuf. + * The ringqPutc and ringqGetc routines will do this automatically. + */ + +/********************************* Includes ***********************************/ + +#if UEMF + #include "uemf.h" +#else + #include "basic/basicInternal.h" +#endif + +/*********************************** Defines **********************************/ +/* + * Faster than a function call + */ + +#define RINGQ_LEN(rq) \ + ((rq->servp > rq->endp) ? \ + (rq->buflen + (rq->endp - rq->servp)) : \ + (rq->endp - rq->servp)) + +/***************************** Forward Declarations ***************************/ + +static int ringq_grow(ringq_t *rq); + +/*********************************** Code *************************************/ +/* + * Create a new ringq. "increment" is the amount to increase the size of the + * ringq should it need to grow to accomodate data being added. "maxsize" is + * an upper limit (sanity level) beyond which the q must not grow. Set maxsize + * to -1 to imply no upper limit. The buffer for the ringq is always + * dynamically allocated. Set maxsize + */ + +int ringqOpen(ringq_t *rq, int increment, int maxsize) +{ + a_assert(rq); + a_assert(increment >= 0); + + if ((rq->buf = balloc(B_L, increment)) == NULL) { + return -1; + } + rq->maxsize = maxsize; + rq->buflen = increment; + rq->increment = increment; + rq->endbuf = &rq->buf[rq->buflen]; + rq->servp = rq->buf; + rq->endp = rq->buf; + *rq->servp = '\0'; + return 0; +} + +/******************************************************************************/ +/* + * Delete a ringq and free the ringq buffer. + */ + +void ringqClose(ringq_t *rq) +{ + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (rq == NULL) { + return; + } + + ringqFlush(rq); + bfree(B_L, (char*) rq->buf); + rq->buf = NULL; +} + +/******************************************************************************/ +/* + * Return the length of the ringq. Users must fill the queue to a high + * water mark of at most one less than the queue size. + */ + +int ringqLen(ringq_t *rq) +{ + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (rq->servp > rq->endp) { + return rq->buflen + rq->endp - rq->servp; + } + else { + return rq->endp - rq->servp; + } +} + +/******************************************************************************/ +/* + * Get a byte from the queue + */ + +int ringqGetc(ringq_t *rq) +{ + char_t c; + char_t* cp; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (rq->servp == rq->endp) { + return -1; + } + + cp = (char_t*) rq->servp; + c = *cp++; + rq->servp = (unsigned char *) cp; + if (rq->servp >= rq->endbuf) { + rq->servp = rq->buf; + } + return c; +} + +/******************************************************************************/ +/* + * Add a char to the queue. Note if being used to store wide strings + * this does not add a trailing '\0'. Grow the q as required. + */ + +int ringqPutc(ringq_t *rq, char_t c) +{ + char_t* cp; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (ringqPutBlkMax(rq) < (int) sizeof(char_t) && !ringq_grow(rq)) { + return -1; + } + + cp = (char_t*) rq->endp; + *cp++ = (char_t) c; + rq->endp = (unsigned char *) cp; + if (rq->endp >= rq->endbuf) { + rq->endp = rq->buf; + } + return 0; +} + +/******************************************************************************/ +/* + * Insert a wide character at the front of the queue + */ + +int ringqInsertc(ringq_t *rq, char_t c) +{ + char_t* cp; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (ringqPutBlkMax(rq) < (int) sizeof(char_t) && !ringq_grow(rq)) { + return -1; + } + if (rq->servp <= rq->buf) { + rq->servp = rq->endbuf; + } + cp = (char_t*) rq->servp; + *--cp = (char_t) c; + rq->servp = (unsigned char *) cp; + return 0; +} + +/******************************************************************************/ +/* + * Add a string to the queue. Add a trailing wide null (two nulls) + */ + +int ringqPutstr(ringq_t *rq, char_t *str) +{ + int rc; + + a_assert(rq); + a_assert(str); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + rc = ringqPutBlk(rq, (unsigned char*) str, gstrlen(str) * sizeof(char_t)); + *((char_t*) rq->endp) = (char_t) '\0'; + return rc; +} + +/******************************************************************************/ +#if UNICODE +/* + * Get a byte from the queue + */ + +int ringqGetcA(ringq_t *rq) +{ + unsigned char c; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (rq->servp == rq->endp) { + return -1; + } + + c = *rq->servp++; + if (rq->servp >= rq->endbuf) { + rq->servp = rq->buf; + } + return c; +} + +/******************************************************************************/ +/* + * Add a byte to the queue. Note if being used to store strings this does not + * add a trailing '\0'. Grow the q as required. + */ + +int ringqPutcA(ringq_t *rq, char c) +{ + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (ringqPutBlkMax(rq) == 0 && !ringq_grow(rq)) { + return -1; + } + + *rq->endp++ = (unsigned char) c; + if (rq->endp >= rq->endbuf) { + rq->endp = rq->buf; + } + return 0; +} + +/******************************************************************************/ +/* + * Insert a byte at the front of the queue + */ + +int ringqInsertcA(ringq_t *rq, char c) +{ + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + if (ringqPutBlkMax(rq) == 0 && !ringq_grow(rq)) { + return -1; + } + if (rq->servp <= rq->buf) { + rq->servp = rq->endbuf; + } + *--rq->servp = (unsigned char) c; + return 0; +} + +/******************************************************************************/ +/* + * Add a string to the queue. Add a trailing null (not really in the q). + * ie. beyond the last valid byte. + */ + +int ringqPutstrA(ringq_t *rq, char *str) +{ + int rc; + + a_assert(rq); + a_assert(str); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + rc = ringqPutBlk(rq, (unsigned char*) str, strlen(str)); + rq->endp[0] = '\0'; + return rc; +} + +#endif /* UNICODE */ +/******************************************************************************/ +/* + * Add a block of data to the ringq. Return the number of bytes added. + * Grow the q as required. + */ + +int ringqPutBlk(ringq_t *rq, unsigned char *buf, int size) +{ + int this, bytes_put; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + a_assert(buf); + a_assert(0 <= size); + +/* + * Loop adding the maximum bytes we can add in a single straight line copy + */ + bytes_put = 0; + while (size > 0) { + this = min(ringqPutBlkMax(rq), size); + if (this <= 0) { + if (! ringq_grow(rq)) { + break; + } + this = min(ringqPutBlkMax(rq), size); + } + + memcpy(rq->endp, buf, this); + buf += this; + rq->endp += this; + size -= this; + bytes_put += this; + + if (rq->endp >= rq->endbuf) { + rq->endp = rq->buf; + } + } + return bytes_put; +} + +/******************************************************************************/ +/* + * Get a block of data from the ringq. Return the number of bytes returned. + */ + +int ringqGetBlk(ringq_t *rq, unsigned char *buf, int size) +{ + int this, bytes_read; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + a_assert(buf); + a_assert(0 <= size && size < rq->buflen); + +/* + * Loop getting the maximum bytes we can get in a single straight line copy + */ + bytes_read = 0; + while (size > 0) { + this = ringqGetBlkMax(rq); + this = min(this, size); + if (this <= 0) { + break; + } + + memcpy(buf, rq->servp, this); + buf += this; + rq->servp += this; + size -= this; + bytes_read += this; + + if (rq->servp >= rq->endbuf) { + rq->servp = rq->buf; + } + } + return bytes_read; +} + +/******************************************************************************/ +/* + * Return the maximum number of bytes the ring q can accept via a single + * block copy. Useful if the user is doing their own data insertion. + */ + +int ringqPutBlkMax(ringq_t *rq) +{ + int space, in_a_line; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + space = rq->buflen - RINGQ_LEN(rq) - 1; + in_a_line = rq->endbuf - rq->endp; + + return min(in_a_line, space); +} + +/******************************************************************************/ +/* + * Return the maximum number of bytes the ring q can provide via a single + * block copy. Useful if the user is doing their own data retrieval. + */ + +int ringqGetBlkMax(ringq_t *rq) +{ + int len, in_a_line; + + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + + len = RINGQ_LEN(rq); + in_a_line = rq->endbuf - rq->servp; + + return min(in_a_line, len); +} + +/******************************************************************************/ +/* + * Adjust the endp pointer after the user has copied data into the queue. + */ + +void ringqPutBlkAdj(ringq_t *rq, int size) +{ + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + a_assert(0 <= size && size < rq->buflen); + + rq->endp += size; + if (rq->endp >= rq->endbuf) { + rq->endp -= rq->buflen; + } +/* + * Flush the queue if the endp pointer is corrupted via a bad size + */ + if (rq->endp >= rq->endbuf) { + error(E_L, E_LOG, T("Bad end pointer")); + ringqFlush(rq); + } +} + +/******************************************************************************/ +/* + * Adjust the servp pointer after the user has copied data from the queue. + */ + +void ringqGetBlkAdj(ringq_t *rq, int size) +{ + a_assert(rq); + a_assert(rq->buflen == (rq->endbuf - rq->buf)); + a_assert(0 < size && size < rq->buflen); + + rq->servp += size; + if (rq->servp >= rq->endbuf) { + rq->servp -= rq->buflen; + } +/* + * Flush the queue if the servp pointer is corrupted via a bad size + */ + if (rq->servp >= rq->endbuf) { + error(E_L, E_LOG, T("Bad serv pointer")); + ringqFlush(rq); + } +} + +/******************************************************************************/ +/* + * Flush all data in a ring q. Reset the pointers. + */ + +void ringqFlush(ringq_t *rq) +{ + a_assert(rq); + + rq->servp = rq->buf; + rq->endp = rq->buf; + *rq->servp = '\0'; +} + +/******************************************************************************/ +/* + * Grow the buffer. Return true if the buffer can be grown. Grow using + * the increment size specified when opening the ringq. Don't grow beyond + * the maximum possible size. + */ + +static int ringq_grow(ringq_t *rq) +{ + unsigned char *newbuf; + int len; + + a_assert(rq); + + if (rq->maxsize >= 0 && rq->buflen >= rq->maxsize) { + return 0; + } + + len = ringqLen(rq); + + if ((newbuf = balloc(B_L, rq->buflen + rq->increment)) == NULL) { + return 0; + } + ringqGetBlk(rq, newbuf, ringqLen(rq)); + bfree(B_L, (char*) rq->buf); + +#if OLD + rq->endp = &newbuf[endp]; + rq->servp = &newbuf[servp]; + rq->endbuf = &newbuf[rq->buflen]; + rq->buf = newbuf; +#endif + + rq->buflen += rq->increment; + rq->endp = newbuf; + rq->servp = newbuf; + rq->buf = newbuf; + rq->endbuf = &rq->buf[rq->buflen]; + + ringqPutBlk(rq, newbuf, len); + return 1; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/rom.c b/cpukit/httpd/rom.c new file mode 100644 index 0000000000..4e6c8f1e93 --- /dev/null +++ b/cpukit/httpd/rom.c @@ -0,0 +1,198 @@ +/* + * rom.c -- Support for ROMed page retrieval. + * + * 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 provides web page retrieval from compiled web pages. Use the + * webcomp program to compile web pages and link into the GoAhead WebServer. + * This module uses a hashed symbol table for fast page lookup. + * + * Usage: webcomp -f webPageFileList -p Prefix >webrom.c + */ + +/********************************* Includes ***********************************/ + +#include + +#if CE +#define EINVAL 22 +#define EBADF 9 +#else +#include +#endif + +#include "wsIntrn.h" + +/******************************** Local Data **********************************/ + +#if WEBS_PAGE_ROM + +sym_fd_t romTab; /* Symbol table for web pages */ + +/*********************************** Code *************************************/ +/* + * Open the ROM module + */ + +int websRomOpen() +{ + websRomPageIndexType *wip; + int nchars; + char_t name[SYM_MAX]; + + romTab = symOpen(64); + + for (wip = websRomPageIndex; wip->path; wip++) { + gstrncpy(name, wip->path, SYM_MAX); + nchars = gstrlen(name) - 1; + if (nchars > 0 && + (name[nchars] == '/' || name[nchars] == '\\')) { + name[nchars] = '\0'; + } + symEnter(romTab, name, valueInteger((int) wip), 0); + } + return 0; +} + +/******************************************************************************/ +/* + * Close the ROM module + */ + +void websRomClose() +{ + symClose(romTab, NULL); +} + +/******************************************************************************/ +/* + * Open a web page + */ + +int websRomPageOpen(webs_t wp, char_t *path, int mode, int perm) +{ + websRomPageIndexType *wip; + sym_t *sp; + + a_assert(websValid(wp)); + a_assert(path && *path); + + if ((sp = symLookup(romTab, path)) == NULL) { + return -1; + } + wip = (websRomPageIndexType*) sp->content.value.integer; + wip->pos = 0; + return (wp->docfd = wip - websRomPageIndex); +} + +/******************************************************************************/ +/* + * Close a web page + */ + +void websRomPageClose(int fd) +{ +} + +/******************************************************************************/ +/* + * Stat a web page + */ + +int websRomPageStat(char_t *path, websStatType* sbuf) +{ + websRomPageIndexType *wip; + sym_t *sp; + + a_assert(path && *path); + + if ((sp = symLookup(romTab, path)) == NULL) { + return -1; + } + wip = (websRomPageIndexType*) sp->content.value.integer; + + memset(sbuf, 0, sizeof(websStatType)); + sbuf->size = wip->size; + if (wip->page == NULL) { + sbuf->isDir = 1; + } + return 0; +} + +/******************************************************************************/ +/* + * Read a web page + */ + +int websRomPageReadData(webs_t wp, char *buf, int nBytes) +{ + websRomPageIndexType *wip; + int len; + + a_assert(websValid(wp)); + a_assert(buf); + a_assert(wp->docfd >= 0); + + wip = &websRomPageIndex[wp->docfd]; + + len = min(wip->size - wip->pos, nBytes); + memcpy(buf, &wip->page[wip->pos], len); + wip->pos += len; + return len; +} + +/******************************************************************************/ +/* + * Position a web page + */ + +long websRomPageSeek(webs_t wp, long offset, int origin) +{ + websRomPageIndexType *wip; + long pos; + + a_assert(websValid(wp)); + a_assert(origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END); + a_assert(wp->docfd >= 0); + + wip = &websRomPageIndex[wp->docfd]; + + if (origin != SEEK_SET && origin != SEEK_CUR && origin != SEEK_END) { + errno = EINVAL; + return -1; + } + + if (wp->docfd < 0) { + errno = EBADF; + return -1; + } + + pos = offset; + switch (origin) { + case SEEK_CUR: + pos = wip->pos + offset; + break; + case SEEK_END: + pos = wip->size + offset; + break; + default: + break; + } + + if (pos < 0) { + errno = EBADF; + return -1; + } + + return (wip->pos = pos); +} + +#endif + +/******************************************************************************/ diff --git a/cpukit/httpd/rtems_webserver.h b/cpukit/httpd/rtems_webserver.h new file mode 100644 index 0000000000..f1e4046ef5 --- /dev/null +++ b/cpukit/httpd/rtems_webserver.h @@ -0,0 +1,7 @@ +/* + * rtems_webserver.h -- + * + */ + +int rtems_initialize_webserver(); + diff --git a/cpukit/httpd/security.c b/cpukit/httpd/security.c new file mode 100644 index 0000000000..01d4000f40 --- /dev/null +++ b/cpukit/httpd/security.c @@ -0,0 +1,109 @@ +/* + * security.c -- Security handler + * + * 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 provides a basic security policy. It supports a single global + * password and ignores the username. Encoding/decoding of the password is + * -not- done. + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/******************************** Local Data **********************************/ + +static char_t websPassword[WEBS_MAX_PASS]; /* Access password (decoded) */ + +/*********************************** Code *************************************/ +/* + * Determine if this request should be honored + */ + +int websSecurityHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg, + char_t *url, char_t *path, char_t *query) +{ + char_t *type, *password; + int flags; + + a_assert(websValid(wp)); + a_assert(url && *url); + a_assert(path && *path); + +/* + * Get the critical request details + */ + type = websGetRequestType(wp); + password = websGetRequestPassword(wp); + flags = websGetRequestFlags(wp); + +/* + * Validate the users password if required (local access is always allowed) + * We compare the decoded form of the password. + */ + if (*websPassword && !(flags & WEBS_LOCAL_REQUEST)) { + + if (password && *password) { + if (gstrcmp(password, websPassword) != 0) { + websStats.access++; + websError(wp, 200, T("Access Denied\nWrong Password")); + websSetPassword(T("")); + return 1; + } + } else { +/* + * This will cause the browser to display a password / username + * dialog + */ + websStats.errors++; + websError(wp, 401, T("Access Denied\r\n\ + Access to this document requires a password.\ + \r\n")); + return 1; + } + } + return 0; +} + +/******************************************************************************/ +/* + * Delete the default security handler + */ + +void websSecurityDelete() +{ + websUrlHandlerDelete(websSecurityHandler); +} + +/******************************************************************************/ +/* + * Store the new password, expect a decoded password. Store in websPassword in + * the decoded form. + */ + +void websSetPassword(char_t *password) +{ + a_assert(password); + + gstrncpy(websPassword, password, TSZ(websPassword)); +} + +/******************************************************************************/ +/* + * Get password, return the decoded form + */ + +char_t *websGetPassword() +{ + return websPassword; +} + +/******************************************************************************/ + diff --git a/cpukit/httpd/socket.c b/cpukit/httpd/socket.c new file mode 100644 index 0000000000..996255081d --- /dev/null +++ b/cpukit/httpd/socket.c @@ -0,0 +1,991 @@ +/* + * socket.c -- Socket support module for UNIX + * + * Copyright (c) Go Ahead, 1995-1999 + */ + +/******************************** Description *********************************/ + +/* + * SCO Unix Socket Module. This supports non-blocking buffered socket I/O. + */ + +/********************************** Includes **********************************/ + +#include +#include +#include +#include +#include + +#include "uemf.h" + +/*********************************** Defines **********************************/ + +typedef struct { + char host[64]; /* Host name */ + ringq_t inBuf; /* Input ring queue */ + ringq_t outBuf; /* Output ring queue */ + ringq_t lineBuf; /* Line ring queue */ + socketAccept_t accept; /* Accept handler */ + socketHandler_t handler; /* User I/O handler */ + int handler_data; /* User handler data */ + int sid; /* Index into socket[] */ + int port; /* Port to listen on */ + int flags; /* Current state flags */ + int readyMask; /* Events now ready */ + int interestMask; /* Events interest */ + int error; /* Last error */ + int sock; /* Actual socket handle */ +} socket_t; + +/************************************ Locals **********************************/ + +static socket_t** socketList; /* List of open sockets */ +static int socketMax; /* Maximum size of socket */ +static int socketHighestFd = -1; /* Highest socket fd opened */ + +/***************************** Forward Declarations ***************************/ + +static int socketAlloc(char* host, int port, socketAccept_t accept, int flags); +static void socketFree(int sid); +static void socketAccept(socket_t* sp); +static int socketGetInput(int sid, char* buf, int toRead, int* errCode); +static int socketDoOutput(socket_t* sp, char* buf, int toWrite, int* errCode); +static int socketDoEvent(socket_t *sp); +static int socketGetError(); +static int socketWaitForEvent(socket_t* sp, int events, int* errCode); +static int socketNonBlock(socket_t *sp); +static socket_t* socketPtr(int sid); + +/*********************************** Code *************************************/ +/* + * Open socket module + */ + +int socketOpen() +{ + return 0; +} + +/******************************************************************************/ +/* + * Close the socket module, by closing all open connections + */ + +void socketClose() +{ + int i; + + for (i = socketMax; i >= 0; i--) { + if (socketList && socketList[i]) { + socketCloseConnection(i); + } + } +} + +/******************************************************************************/ +/* + * Open a client or server socket. Host is NULL if we want server capability. + */ + +int socketOpenConnection(char* host, int port, socketAccept_t accept, int flags) +{ + socket_t *sp; + struct sockaddr_in sockaddr; + struct hostent *hostent; /* Host database entry */ + int sid, rc; + +/* + * Allocate a socket structure + */ + if ((sid = socketAlloc(host, port, accept, flags)) < 0) { + return -1; + } + sp = socketList[sid]; + a_assert(sp); + +/* + * Create the socket address structure + */ + memset((char *) &sockaddr, '\0', sizeof(struct sockaddr_in)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons((short) (port & 0xFFFF)); + + if (host == NULL) { + sockaddr.sin_addr.s_addr = INADDR_ANY; + } else { + sockaddr.sin_addr.s_addr = inet_addr(host); + if (sockaddr.sin_addr.s_addr == INADDR_NONE) { + hostent = gethostbyname(host); + if (hostent != NULL) { + memcpy((char *) &sockaddr.sin_addr, + (char *) hostent->h_addr_list[0], + (size_t) hostent->h_length); + } else { + errno = ENXIO; + socketFree(sid); + return -1; + } + } + } + +/* + * Create the socket. Set the close on exec flag so children don't + * inherit the socket. + */ + sp->sock = socket(AF_INET, SOCK_STREAM, 0); + if (sp->sock < 0) { + socketFree(sid); + return -1; + } + fcntl(sp->sock, F_SETFD, FD_CLOEXEC); + socketHighestFd = max(socketHighestFd, sp->sock); + +/* + * Host is set if we are the client + */ + if (host) { +/* + * Connect to the remote server + */ + if (connect(sp->sock, (struct sockaddr *) &sockaddr, + sizeof(sockaddr)) < 0) { + socketFree(sid); + return -1; + } + socketNonBlock(sp); + + } else { +/* + * Bind to the socket endpoint with resule and the call listen() + ** to start listening + */ + rc = 1; + setsockopt(sp->sock, SOL_SOCKET, SO_REUSEADDR, (char *)&rc, sizeof(rc)); + if (bind(sp->sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) + < 0) { + socketFree(sid); + return -1; + } + sp->flags |= SOCKET_LISTENING; + + if (listen(sp->sock, SOMAXCONN) < 0) { + socketFree(sid); + return -1; + } + sp->interestMask = SOCKET_READABLE; + } + return sid; +} + +/******************************************************************************/ +/* + * Close a socket + */ + +void socketCloseConnection(int sid) +{ + socket_t* sp; + + if ((sp = socketPtr(sid)) == NULL) { + return; + } + +/* + * We always flush all output before closing. Unlink from the emf event + * mechanism and then free (and close) the connection + */ + socketFlush(sid, 1); + socketFree(sid); +} + +/******************************************************************************/ +/* + * Accept a connection. Called by socketDoEvent + */ + +static void socketAccept(socket_t* sp) +{ + struct sockaddr_in addr; + socket_t *nsp; + size_t len; + int newSock, nid; + + a_assert(sp); + +/* + * Accept the connection and prevent inheriting by children (F_SETFD) + */ + len = sizeof(struct sockaddr_in); + if ((newSock = accept(sp->sock, (struct sockaddr *) &addr, &len)) < 0) { + return; + } + fcntl(newSock, F_SETFD, FD_CLOEXEC); + socketHighestFd = max(socketHighestFd, newSock); + +/* + * Create a socket structure and insert into the socket list + */ + nid = socketAlloc(sp->host, sp->port, sp->accept, 0); + nsp = socketList[nid]; + a_assert(nsp); + nsp->sock = newSock; + + if (nsp == NULL) { + return; + } +/* + * Call the user accept callback, the user must call socketCreateHandler + * to register for further events of interest. + */ + if (sp->accept != NULL) { + if ((sp->accept)(nid, inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)) < 0) { + socketFree(nid); + return; + } + } + socketNonBlock(nsp); +} + +/******************************************************************************/ +/* + * Write to a socket. This may block if the underlying socket cannot + * absorb the data. Returns -1 on error, otherwise the number of bytes + * written. + */ + +int socketWrite(int sid, char* buf, int bufsize) +{ + socket_t* sp; + ringq_t* rq; + int len, bytesWritten, room; + + a_assert(buf); + a_assert(bufsize >= 0); + + if ((sp = socketPtr(sid)) == NULL) { + return -1; + } + +/* + * Loop adding as much data to the output ringq as we can absorb + * Flush when the ringq is too full and continue. + */ + rq = &sp->outBuf; + for (bytesWritten = 0; bufsize > 0; ) { + if ((room = ringqPutBlkMax(rq)) == 0) { + if (socketFlush(sid, 0) < 0) { + return -1; + } + if ((room = ringqPutBlkMax(rq)) == 0) { + break; + } + continue; + } + len = min(room, bufsize); + ringqPutBlk(rq, (unsigned char*) buf, len); + bytesWritten += len; + bufsize -= len; + buf += len; + } + return bytesWritten; +} + +/******************************************************************************/ +/* + * Read from a socket. Return the number of bytes read if successful. This + * may be less than the requested "bufsize" and may be zero. Return -1 for + * errors. Return 0 for EOF. Otherwise return the number of bytes read. Since + * this may be zero, callers should use socketEof() to distinguish between + * this and EOF. Note: this ignores the line buffer, so a previous socketGets + * which read a partial line may cause a subsequent socketRead to miss + * some data. + */ + +int socketRead(int sid, char* buf, int bufsize) +{ + socket_t* sp; + ringq_t* rq; + int len, room, errCode, bytesRead; + + a_assert(buf); + a_assert(bufsize > 0); + + if ((sp = socketPtr(sid)) == NULL) { + return -1; + } + + if (sp->flags & SOCKET_EOF) { + return 0; + } + + rq = &sp->inBuf; + for (bytesRead = 0; bufsize > 0; ) { + len = min(ringqLen(rq), bufsize); + if (len <= 0) { + room = ringqPutBlkMax(rq); + len = socketGetInput(sid, (char*) rq->endp, room, &errCode); + if (len < 0) { + if (errCode == EWOULDBLOCK) { + if (bytesRead >= 0) { + return bytesRead; + } + } + return -1; + + } else if (len == 0) { +/* + * This is EOF, but we may have already read some data so pass that + * back first before notifying EOF. The next read will return 0 + * to indicate EOF. + */ + return bytesRead; + } + ringqPutBlkAdj(rq, len); + len = min(len, bufsize); + } + memcpy(&buf[bytesRead], rq->servp, len); + ringqGetBlkAdj(rq, len); + bufsize -= len; + bytesRead += len; + } + return bytesRead; +} + +/******************************************************************************/ +/* + * Get a string from a socket. This returns data in *buf in a malloced string + * after trimming the '\n'. If there is zero bytes returned, *buf will be set + * to NULL. It returns -1 for error, EOF or when no complete line yet read. + * Otherwise the length of the line is returned. If a partial line is read + * socketInputBuffered or socketEof can be used to distinguish between EOF + * and partial line still buffered. This routine eats and ignores carriage + * returns. + */ + +int socketGets(int sid, char** buf) +{ + socket_t* sp; + ringq_t* lq; + char c; + int rc, len; + + a_assert(buf); + + if ((sp = socketPtr(sid)) == NULL) { + return -1; + } + lq = &sp->lineBuf; + + while (1) { + + if ((rc = socketRead(sid, &c, 1)) < 0) { + return rc; + + } else if (rc == 0) { +/* + * If there is a partial line and we are at EOF, pretend we saw a '\n' + */ + if (ringqLen(lq) > 0 && (sp->flags & SOCKET_EOF)) { + c = '\n'; + } else { + return -1; + } + } +/* + * If a newline is seen, return the data excluding the new line to the + * caller. If carriage return is seen, just eat it. + */ + if (c == '\n') { + len = ringqLen(lq); + if (len > 0) { + if ((*buf = balloc(B_L, len + 1)) == NULL) { + return -1; + } + memset(*buf, 0, len + 1); + ringqGetBlk(lq, (unsigned char*) *buf, len); + } else { + *buf = NULL; + } + return len; + + } else if (c == '\r') { + continue; + } + ringqPutc(lq, c); + } +} + +/******************************************************************************/ +/* + * Flush a socket. Do not wait, just initiate output and return. + * This will return -1 on errors and 0 if successful. + */ + +int socketFlush(int sid, int block) +{ + socket_t* sp; + ringq_t* rq; + int len, bytesWritten, errCode; + + a_assert(block == 0 || block == 1); + + if ((sp = socketPtr(sid)) == NULL) { + return -1; + } + rq = &sp->outBuf; + +/* + * Set the background flushing flag which socketDoEvent will check to + * continue the flush. + */ + if (!block) { + sp->flags |= SOCKET_FLUSHING; + } + +/* + * Break from loop if not blocking after initiating output. If we are blocking + * we wait for a write event. + */ + while (ringqLen(rq) > 0) { + len = ringqGetBlkMax(&sp->outBuf); + bytesWritten = socketDoOutput(sp, (char*) rq->servp, len, &errCode); + if (bytesWritten < 0) { + if (errCode == EINTR) { + continue; + } else if (errCode == EWOULDBLOCK || errCode == EAGAIN) { + if (! block) { + return 0; + } + if (socketWaitForEvent(sp, SOCKET_WRITABLE | SOCKET_EXCEPTION, + &errCode)) { + continue; + } + } + return -1; + } + ringqGetBlkAdj(rq, bytesWritten); + if (! block) { + break; + } + } + return 0; +} + +/******************************************************************************/ +/* + * Return the count of input characters buffered. We look at both the line + * buffer and the input (raw) buffer. Return -1 on error or EOF. + */ + +int socketInputBuffered(int sid) +{ + socket_t* sp; + + if ((sp = socketPtr(sid)) == NULL) { + return -1; + } + if (socketEof(sid)) { + return -1; + } + return ringqLen(&sp->lineBuf) + ringqLen(&sp->inBuf); +} + +/******************************************************************************/ +/* + * Return true if EOF + */ + +int socketEof(int sid) +{ + socket_t* sp; + + if ((sp = socketPtr(sid)) == NULL) { + return -1; + } + return sp->flags & SOCKET_EOF; +} + +/******************************************************************************/ +/* + * Create a user handler for this socket. The handler called whenever there + * is an event of interest as defined by interestMask (SOCKET_READABLE, ...) + */ + +void socketCreateHandler(int sid, int interestMask, socketHandler_t handler, + int data) +{ + socket_t* sp; + + if ((sp = socketPtr(sid)) == NULL) { + return; + } + sp->handler = handler; + sp->handler_data = data; + sp->interestMask = interestMask; +} + +/******************************************************************************/ +/* + * Delete a handler + */ + +void socketDeleteHandler(int sid) +{ + socket_t* sp; + + if ((sp = socketPtr(sid)) == NULL) { + return; + } + sp->handler = NULL; + sp->interestMask = 0; +} + +/******************************************************************************/ +/* + * Get more input from the socket and return in buf. + * Returns 0 for EOF, -1 for errors and otherwise the number of bytes read. + */ + +static int socketGetInput(int sid, char* buf, int toRead, int* errCode) +{ + struct sockaddr_in server; + socket_t* sp; + int len, bytesRead; + + a_assert(buf); + a_assert(errCode); + + *errCode = 0; + + if ((sp = socketPtr(sid)) == NULL) { + return -1; + } + +/* + * If we have previously seen an EOF condition, then just return + */ + if (sp->flags & SOCKET_EOF) { + return 0; + } + +/* + * Read the data + */ + if (sp->flags & SOCKET_BROADCAST) { + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_BROADCAST; + server.sin_port = htons((short)(sp->port & 0xFFFF)); + len = sizeof(server); + bytesRead = recvfrom(sp->sock, buf, toRead, 0, + (struct sockaddr*) &server, &len); + } else { + bytesRead = recv(sp->sock, buf, toRead, 0); + } + + if (bytesRead < 0) { + if (errno == ECONNRESET) { + return 0; + } + *errCode = socketGetError(); + return -1; + + } else if (bytesRead == 0) { + sp->flags |= SOCKET_EOF; + } + return bytesRead; +} + +/******************************************************************************/ +/* + * Socket output procedure. Return -1 on errors otherwise return the number + * of bytes written. + */ + +static int socketDoOutput(socket_t* sp, char* buf, int toWrite, int* errCode) +{ + struct sockaddr_in server; + int bytes; + + a_assert(sp); + a_assert(buf); + a_assert(toWrite > 0); + a_assert(errCode); + + *errCode = 0; + +/* + * Write the data + */ + if (sp->flags & SOCKET_BROADCAST) { + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_BROADCAST; + server.sin_port = htons((short)(sp->port & 0xFFFF)); + bytes = sendto(sp->sock, buf, toWrite, 0, + (struct sockaddr*) &server, sizeof(server)); + } else { + bytes = send(sp->sock, buf, toWrite, 0); + } + + if (bytes == 0 && bytes != toWrite) { + *errCode = EWOULDBLOCK; + return -1; + } + + if (bytes < 0) { + *errCode = socketGetError(); + } + return bytes; +} + +/******************************************************************************/ +/* + * Return TRUE if there is a socket with an event ready to process, + */ + +int socketReady() +{ + socket_t *sp; + int i; + + for (i = 0; i < socketMax; i++) { + if ((sp = socketList[i]) == NULL) { + continue; + } + if (sp->readyMask & sp->interestMask) { + return 1; + } + } + return 0; +} + +/******************************************************************************/ +/* + * Wait for a handle to become readable or writable and return a number of + * noticed events. + */ + +int socketSelect() +{ + socket_t *sp; + fd_mask *readFds, *writeFds, *exceptFds; + int sid, len, nwords, index, bit, nEvents; + +/* + * Allocate and zero the select masks + */ + nwords = (socketHighestFd + NFDBITS - 1) / NFDBITS; + len = nwords * sizeof(int); + + readFds = balloc(B_L, len); + memset(readFds, 0, len); + writeFds = balloc(B_L, len); + memset(writeFds, 0, len); + exceptFds = balloc(B_L, len); + memset(exceptFds, 0, len); + +/* + * Set the select event masks for events to watch + */ + for (sid = 0; sid < socketMax; sid++) { + if ((sp = socketList[sid]) == NULL) { + continue; + } + a_assert(sp); + +/* + * Initialize the ready masks and compute the mask offsets. + */ + index = sp->sock / (NBBY * sizeof(fd_mask)); + bit = 1 << (sp->sock % (NBBY * sizeof(fd_mask))); + +/* + * Set the appropriate bit in the ready masks for the sp->sock. + */ + if (sp->interestMask & SOCKET_READABLE) { + readFds[index] |= bit; + } + if (sp->interestMask & SOCKET_WRITABLE) { + writeFds[index] |= bit; + } + if (sp->interestMask & SOCKET_EXCEPTION) { + exceptFds[index] |= bit; + } + } + +/* + * Wait for the event or a timeout. + */ + nEvents = select(socketHighestFd + 1, (fd_set *) readFds, + (fd_set *) writeFds, (fd_set *) exceptFds, NULL); + if (nEvents > 0) { + for (sid = 0; sid < socketMax; sid++) { + if ((sp = socketList[sid]) == NULL) { + continue; + } + + index = sp->sock / (NBBY * sizeof(fd_mask)); + bit = 1 << (sp->sock % (NBBY * sizeof(fd_mask))); + + if (readFds[index] & bit) { + sp->readyMask |= SOCKET_READABLE; + } + if (writeFds[index] & bit) { + sp->readyMask |= SOCKET_WRITABLE; + } + if (exceptFds[index] & bit) { + sp->readyMask |= SOCKET_EXCEPTION; + } + } + } + + bfree(B_L, readFds); + bfree(B_L, writeFds); + bfree(B_L, exceptFds); + + return nEvents; +} + +/******************************************************************************/ +/* + * Process socket events + */ + +void socketProcess() +{ + socket_t *sp; + int sid; + +/* + * Process each socket + */ + for (sid = 0; sid < socketMax; sid++) { + if ((sp = socketList[sid]) == NULL) { + continue; + } + if ((sp->readyMask & sp->interestMask) || + ((sp->interestMask & SOCKET_READABLE) && + socketInputBuffered(sid))) { + socketDoEvent(sp); + } + } +} + +/******************************************************************************/ +/* + * Process and event on the event queue + */ + +static int socketDoEvent(socket_t *sp) +{ + ringq_t* rq; + int sid; + + a_assert(sp); + + sid = sp->sid; + if (sp->readyMask & SOCKET_READABLE) { + if (sp->flags & SOCKET_LISTENING) { + socketAccept(sp); + sp->readyMask = 0; + return 1; + } + } else { +/* + * If there is still read data in the buffers, trigger the read handler + * NOTE: this may busy spin if the read handler doesn't read the data + */ + if (sp->interestMask & SOCKET_READABLE && socketInputBuffered(sid)) { + sp->readyMask |= SOCKET_READABLE; + } + } + + +/* + * If now writable and flushing in the background, continue flushing + */ + if (sp->readyMask & SOCKET_WRITABLE) { + if (sp->flags & SOCKET_FLUSHING) { + rq = &sp->outBuf; + if (ringqLen(rq) > 0) { + socketFlush(sp->sid, 0); + } else { + sp->flags &= ~SOCKET_FLUSHING; + } + } + } + +/* + * Now invoke the users socket handler. NOTE: the handler may delete the + * socket, so we must be very careful after calling the handler. + */ + if (sp->handler && (sp->interestMask & sp->readyMask)) { + (sp->handler)(sid, sp->interestMask & sp->readyMask, + sp->handler_data); +/* + * Make sure socket pointer is still valid, then set the readyMask + * to 0. + */ + if (socketPtr(sid)) { + sp->readyMask = 0; + } + } + return 1; +} + +/******************************************************************************/ +/* + * Allocate a new socket structure + */ + +static int socketAlloc(char* host, int port, socketAccept_t accept, int flags) +{ + socket_t *sp; + int sid; + + if ((sid = hAlloc((void***) &socketList)) < 0) { + return -1; + } + if ((sp = (socket_t*) balloc(B_L, sizeof(socket_t))) == NULL) { + hFree((void***) &socket, sid); + return -1; + } + memset(sp, 0, sizeof(socket_t)); + socketList[sid] = sp; + if (sid >= socketMax) + socketMax = sid + 1; + + sp->sid = sid; + sp->accept = accept; + sp->port = port; + sp->flags = flags; + + if (host) { + strncpy(sp->host, host, sizeof(sp->host)); + } + + ringqOpen(&sp->inBuf, SOCKET_BUFSIZ, SOCKET_BUFSIZ); + ringqOpen(&sp->outBuf, SOCKET_BUFSIZ, SOCKET_BUFSIZ); + ringqOpen(&sp->lineBuf, SOCKET_BUFSIZ, -1); + + return sid; +} + +/******************************************************************************/ +/* + * Free a socket structure + */ + +static void socketFree(int sid) +{ + socket_t* sp; + int i; + + if ((sp = socketPtr(sid)) == NULL) { + return; + } + if (sp->sock >= 0) { + close(sp->sock); + } + + ringqClose(&sp->inBuf); + ringqClose(&sp->outBuf); + ringqClose(&sp->lineBuf); + + bfree(B_L, sp); + socketMax = hFree((void***) &socketList, sid); + +/* + * Calculate the new highest socket number + */ + socketHighestFd = -1; + for (i = 0; i < socketMax; i++) { + if ((sp = socketList[i]) == NULL) { + continue; + } + socketHighestFd = max(socketHighestFd, sp->sock); + } +} + +/******************************************************************************/ +/* + * Validate a socket handle + */ + +static socket_t* socketPtr(int sid) +{ + if (sid < 0 || sid >= socketMax || socketList[sid] == NULL) { + a_assert(NULL); + return NULL; + } + + a_assert(socketList[sid]); + return socketList[sid]; +} + +/******************************************************************************/ +/* + * Get the operating system error code + */ + +static int socketGetError() +{ + return errno; +} + +/******************************************************************************/ +/* + * Wait until an event occurs on a socket. Return 1 on success, 0 on failure. + */ + +static int socketWaitForEvent(socket_t* sp, int interestMask, int* errCode) +{ + a_assert(sp); + + while (socketSelect()) { + if (sp->readyMask & interestMask) { + break; + } + } + if (sp->readyMask & SOCKET_EXCEPTION) { + return -1; + } else if (sp->readyMask & SOCKET_WRITABLE) { + return 0; + } else { + *errCode = errno = EWOULDBLOCK; + return -1; + } +} + +/******************************************************************************/ +/* + * Put the socket into non-blocking mode + */ + +static int socketNonBlock(socket_t *sp) +{ + int flags; + + flags = fcntl(sp->sock, F_GETFL) | O_NONBLOCK; + if (fcntl(sp->sock, F_SETFL, flags) < 0) { + return -1; + } + return 0; +} + +/******************************************************************************/ +/* + * Duplicate stdin and stdout + */ + +int DuplicateStdFile (int sid){ + int i; + + if (0 != dup2(socketList[sid]->sock, 0) || 1 != dup2(socketList[sid]->sock, 1)) + return -1; + + return 0; + +} diff --git a/cpukit/httpd/sym.c b/cpukit/httpd/sym.c new file mode 100644 index 0000000000..c7b47b5e00 --- /dev/null +++ b/cpukit/httpd/sym.c @@ -0,0 +1,452 @@ +/* + * sym.c -- Symbol Table module + * + * Copyright (c) GoAhead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ +/* + * This module implements a highly efficient generic symbol table with + * update and access routines. Symbols are simple character strings and + * the values they take can be flexible types as defined by value_t. + * This modules allows multiple symbol tables to be created. + */ + +/********************************* Includes ***********************************/ + +#if UEMF + #include "uemf.h" +#else + #include "basic/basicInternal.h" +#endif + +/********************************* Defines ************************************/ + +typedef struct { /* Symbol table descriptor */ + int inuse; /* Is this entry in use */ + int hash_size; /* Size of the table below */ + sym_t **hash_table; /* Allocated at run time */ +} sym_tabent_t; + +/********************************* Globals ************************************/ + +static sym_tabent_t **sym; /* List of symbol tables */ +static int sym_max; /* One past the max symbol table */ + +static int htIndex; /* Current location in table */ +static sym_t* next; /* Next symbol in iteration */ + +/**************************** Forward Declarations ****************************/ + +static int hashIndex(sym_tabent_t *tp, char_t *name); +static sym_t *hash(sym_tabent_t *tp, char_t *name); +static int calc_prime(int size); + +/*********************************** Code *************************************/ +/* + * Create a symbol table. + */ + +sym_fd_t symOpen(int hash_size) +{ + sym_fd_t sd; + sym_tabent_t *tp; + + a_assert(hash_size > 2); + +/* + * Create a new handle for this symbol table + */ + if ((sd = hAlloc((void***) &sym)) < 0) { + return -1; + } + +/* + * Create a new symbol table structure and zero + */ + if ((tp = (sym_tabent_t*) balloc(B_L, sizeof(sym_tabent_t))) == NULL) { + sym_max = hFree((void***) &sym, sd); + return -1; + } + memset(tp, 0, sizeof(sym_tabent_t)); + if (sd >= sym_max) { + sym_max = sd + 1; + } + a_assert(0 <= sd && sd < sym_max); + sym[sd] = tp; + +/* + * Now create the hash table for fast indexing. + */ + tp->hash_size = calc_prime(hash_size); + tp->hash_table = (sym_t**) balloc(B_L, tp->hash_size * sizeof(sym_t*)); + a_assert(tp->hash_table); + memset(tp->hash_table, 0, tp->hash_size * sizeof(sym_t*)); + + return sd; +} + +/******************************************************************************/ +/* + * Close this symbol table. Call a cleanup function to allow the caller + * to free resources associated with each symbol table entry. + */ + +void symClose(sym_fd_t sd, void (*cleanup)(sym_t *symp)) +{ + sym_tabent_t *tp; + sym_t *sp, *forw; + int i; + + a_assert(0 <= sd && sd < sym_max); + tp = sym[sd]; + a_assert(tp); + +/* + * Free all symbols in the hash table, then the hash table itself. + */ + for (i = 0; i < tp->hash_size; i++) { + for (sp = tp->hash_table[i]; sp; sp = forw) { + forw = sp->forw; + if (cleanup) { + (*cleanup)(sp); + } + valueFree(&sp->name); + bfree(B_L, (void*) sp); + sp = forw; + } + } + bfree(B_L, (void*) tp->hash_table); + + sym_max = hFree((void***) &sym, sd); + bfree(B_L, (void*) tp); +} + +/******************************************************************************/ +/* + * Default callback for freeing the value. + */ + +void symFreeVar(sym_t* sp) +{ + valueFree(&sp->content); +} + +/******************************************************************************/ +/* + * Return the first symbol in the hashtable if there is one. This call is used + * as the first step in traversing the table. A call to symFirst should be + * followed by calls to symNext to get all the rest of the entries. + */ + +sym_t* symFirst(sym_fd_t sd) +{ + sym_tabent_t *tp; + sym_t *sp, *forw; + int i; + + a_assert(0 <= sd && sd < sym_max); + tp = sym[sd]; + a_assert(tp); + +/* + * Find the first symbol in the hashtable and return a pointer to it. + */ + for (i = 0; i < tp->hash_size; i++) { + for (sp = tp->hash_table[i]; sp; sp = forw) { + forw = sp->forw; + + if (forw == NULL) { + htIndex = i + 1; + next = tp->hash_table[htIndex]; + } else { + htIndex = i; + next = forw; + } + return sp; + } + } + return NULL; +} + +/******************************************************************************/ +/* + * Return the next symbol in the hashtable if there is one. See symFirst. + */ + +sym_t* symNext(sym_fd_t sd) +{ + sym_tabent_t *tp; + sym_t *sp, *forw; + int i; + + a_assert(0 <= sd && sd < sym_max); + tp = sym[sd]; + a_assert(tp); + +/* + * Find the first symbol in the hashtable and return a pointer to it. + */ + for (i = htIndex; i < tp->hash_size; i++) { + for (sp = next; sp; sp = forw) { + forw = sp->forw; + + if (forw == NULL) { + htIndex = i + 1; + next = tp->hash_table[htIndex]; + } else { + htIndex = i; + next = forw; + } + return sp; + } + next = tp->hash_table[i + 1]; + } + return NULL; +} + +/******************************************************************************/ +/* + * Lookup a symbol and return a pointer to the symbol entry. If not present + * then return a NULL. + */ + +sym_t *symLookup(sym_fd_t sd, char_t *name) +{ + sym_tabent_t *tp; + sym_t *sp; + char_t *cp; + + a_assert(0 <= sd && sd < sym_max); + tp = sym[sd]; + a_assert(tp); + + if (name == NULL || *name == '\0') { + return NULL; + } + +/* + * Do an initial hash and then follow the link chain to find the right entry + */ + for (sp = hash(tp, name); sp; sp = sp->forw) { + cp = sp->name.value.string; + if (cp[0] == name[0] && gstrcmp(cp, name) == 0) { + break; + } + } + return sp; +} + +/******************************************************************************/ +/* + * Enter a symbol into the table. If already there, update its value. + * Always succeeds if memory available. + */ + +sym_t *symEnter(sym_fd_t sd, char_t *name, value_t v, int arg) +{ + sym_tabent_t *tp; + sym_t *sp, *last; + char_t *cp; + int hindex; + + a_assert(name && *name); + a_assert(0 <= sd && sd < sym_max); + tp = sym[sd]; + a_assert(tp); + +/* + * Calculate the first daisy-chain from the hash table. If non-zero, then + * we have daisy-chain, so scan it and look for the symbol. + */ + last = NULL; + hindex = hashIndex(tp, name); + if ((sp = tp->hash_table[hindex]) != NULL) { + for (; sp; sp = sp->forw) { + cp = sp->name.value.string; + if (cp[0] == name[0] && gstrcmp(cp, name) == 0) { + break; + } + last = sp; + } + if (sp) { +/* + * Found, so update the value + * If the caller stores handles which require freeing, they + * will be lost here. It is the callers responsibility to free + * resources before overwriting existing contents. We will here + * free allocated strings which occur due to value_instring(). + * We should consider providing the cleanup function on the open rather + * than the close and then we could call it here and solve the problem. + */ + if (sp->content.valid) { + valueFree(&sp->content); + } + sp->content = v; + sp->arg = arg; + return sp; + } +/* + * Not found so allocate and append to the daisy-chain + */ + sp = (sym_t*) balloc(B_L, sizeof(sym_t)); + if (sp == NULL) { + return NULL; + } + sp->name = valueString(name, VALUE_ALLOCATE); + sp->content = v; + sp->forw = (sym_t*) NULL; + sp->arg = arg; + last->forw = sp; + + } else { +/* + * Daisy chain is empty so we need to start the chain + */ + sp = (sym_t*) balloc(B_L, sizeof(sym_t)); + if (sp == NULL) { + return NULL; + } + tp->hash_table[hindex] = sp; + tp->hash_table[hashIndex(tp, name)] = sp; + + sp->forw = (sym_t*) NULL; + sp->content = v; + sp->arg = arg; + sp->name = valueString(name, VALUE_ALLOCATE); + } + return sp; +} + +/******************************************************************************/ +/* + * Delete a symbol from a table + */ + +int symDelete(sym_fd_t sd, char_t *name) +{ + sym_tabent_t *tp; + sym_t *sp, *last; + char_t *cp; + int hindex; + + a_assert(name && *name); + a_assert(0 <= sd && sd < sym_max); + tp = sym[sd]; + a_assert(tp); + +/* + * Calculate the first daisy-chain from the hash table. If non-zero, then + * we have daisy-chain, so scan it and look for the symbol. + */ + last = NULL; + hindex = hashIndex(tp, name); + if ((sp = tp->hash_table[hindex]) != NULL) { + for ( ; sp; sp = sp->forw) { + cp = sp->name.value.string; + if (cp[0] == name[0] && gstrcmp(cp, name) == 0) { + break; + } + last = sp; + } + } + if (sp == (sym_t*) NULL) { /* Not Found */ + return -1; + } + +/* + * Unlink and free the symbol. Last will be set if the element to be deleted + * is not first in the chain. + */ + if (last) { + last->forw = sp->forw; + } else { + tp->hash_table[hindex] = sp->forw; + } + valueFree(&sp->name); + bfree(B_L, (void*) sp); + + return 0; +} + +/******************************************************************************/ +/* + * Hash a symbol and return a pointer to the hash daisy-chain list + * All symbols reside on the chain (ie. none stored in the hash table itself) + */ + +static sym_t *hash(sym_tabent_t *tp, char_t *name) +{ + a_assert(tp); + + return tp->hash_table[hashIndex(tp, name)]; +} + +/******************************************************************************/ +/* + * Compute the hash function and return an index into the hash table + * We use a basic additive function that is then made modulo the size of the + * table. + */ + +static int hashIndex(sym_tabent_t *tp, char_t *name) +{ + unsigned int sum; + int i; + + a_assert(tp); +/* + * Add in each character shifted up progressively by 7 bits. The shift + * amount is rounded so as to not shift too far. It thus cycles with each + * new cycle placing character shifted up by one bit. + */ + i = 0; + sum = 0; + while (*name) { + sum += (((int) *name++) << i); + i = (i + 7) % (BITS(int) - BITSPERBYTE); + } + return sum % tp->hash_size; +} + +/******************************************************************************/ +/* + * Check if this number is a prime + */ + +static int is_prime(int n) +{ + int i; + + a_assert(n > 0); + + for (i = 2; i < n; i++) { + if (n % i == 0) { + return 0; + } + } + return 1; +} + +/******************************************************************************/ +/* + * Calculate the largest prime smaller than size. + */ + +static int calc_prime(int size) +{ + int count; + + a_assert(size > 0); + + for (count = size; count > 0; count--) { + if (is_prime(count)) { + return count; + } + } + return 1; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/uemf.c b/cpukit/httpd/uemf.c new file mode 100644 index 0000000000..8a6675f944 --- /dev/null +++ b/cpukit/httpd/uemf.c @@ -0,0 +1,193 @@ +/* + * uemf.c -- GoAhead Micro Embedded Management Framework + * + * Copyright (c) Go Ahead Software, Inc., 1995-1999 + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/********************************** Description *******************************/ + +/* + * This module provides compatibility with the full GoAhead EMF. + * It is a collection of routines which permits the GoAhead WebServer to + * run stand-alone and to also load as a solution pack under the GoAhead EMF. + */ + +/*********************************** Includes *********************************/ + +#include "uemf.h" + +/********************************** Local Data ********************************/ + +int emfInst; /* Application instance handle */ + +/************************************* Code ***********************************/ +/* + * Error message that doesn't need user attention. Customize this code + * to direct error messages to whereever the developer wishes + */ + +void error(E_ARGS_DEC, int flags, char_t *fmt, ...) +{ + if (flags & E_LOG) { + /* Log error message */ + + } else if (flags & E_ASSERT) { + /* Assert message */ + + } else if (flags & E_USER) { + /* Display message to the user */ + + } +} + +/******************************************************************************/ +/* + * Trace log. Customize this function to log trace output + */ + +void trace(int level, char_t *afmt, ...) +{ +#if DEBUG + va_list args; + char_t *buf; + + va_start(args, afmt); + buf = NULL; + gvsnprintf(&buf, VALUE_MAX_STRING, afmt, args); + if (buf) { + gprintf(buf); + bfree(B_L, buf); + } + va_end(args); +#endif +} + +/******************************************************************************/ +/* + * Save the instance handle + */ + +void emfInstSet(int inst) +{ + emfInst = inst; +} + +/******************************************************************************/ +/* + * Get the instance handle + */ + +int emfInstGet() +{ + return emfInst; +} + +/******************************************************************************/ +/* + * Convert a string to lower case + */ + +char_t *strlower(char_t *string) +{ + char_t *s; + + a_assert(string); + + if (string == NULL) { + return NULL; + } + + s = string; + while (*s) { + if (gisupper(*s)) { + *s = (char_t) gtolower(*s); + } + s++; + } + *s = '\0'; + return string; +} + +/******************************************************************************/ +/* + * Convert a string to upper case + */ + +char_t *strupper(char_t *string) +{ + char_t *s; + + a_assert(string); + if (string == NULL) { + return NULL; + } + + s = string; + while (*s) { + if (gislower(*s)) { + *s = (char_t) gtoupper(*s); + } + s++; + } + *s = '\0'; + return string; +} + +/******************************************************************************/ +/* + * Convert integer to ascii string. Allow a NULL string in which case we + * allocate a dynamic buffer. + */ + +char_t *stritoa(int n, char_t *string, int width) +{ + char_t *cp, *lim, *s; + char_t buf[16]; /* Just temp to hold number */ + int next, minus; + + a_assert(string && width > 0); + + if (string == NULL) { + if (width == 0) { + width = 10; + } + if ((string = balloc(B_L, width + 1)) == NULL) { + return NULL; + } + } + if (n < 0) { + minus = 1; + n = -n; + width--; + } else { + minus = 0; + } + + cp = buf; + lim = &buf[width - 1]; + while (n > 9 && cp < lim) { + next = n; + n /= 10; + *cp++ = (char_t) (next - n * 10 + '0'); + } + if (cp < lim) { + *cp++ = (char_t) (n + '0'); + } + + s = string; + if (minus) { + *s++ = '-'; + } + + while (cp > buf) { + *s++ = *--cp; + } + + *s++ = '\0'; + return string; +} + +/******************************************************************************/ + diff --git a/cpukit/httpd/uemf.h b/cpukit/httpd/uemf.h new file mode 100644 index 0000000000..1c0d348256 --- /dev/null +++ b/cpukit/httpd/uemf.h @@ -0,0 +1,666 @@ +/* + * uemf.h -- Go Ahead Micro Embedded Management Framework Header + * + * Copyright (c) Go Ahead Software, Inc., 1995-1999 + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +#ifndef _h_UEMF +#define _h_UEMF 1 + +/******************************** Description *********************************/ + +/* + * Go Ahead Web Server header. This defines the Web public APIs + */ + +/******************************* Per O/S Includes *****************************/ + +#if WIN + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#if CE + #include + #include + #include + #include + #include "CE/wincompat.h" +#endif + +#if NW + #include +#endif + +#if UNIX + #include +#endif + +#if LINUX + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#if LYNX + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#if UW + #include +#endif + +#if VXW486 + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#if QNX4 + #include + #include + #include + #include + #include + #include + #include +#endif + +/********************************** Includes **********************************/ + +#include +#include +#include + +/********************************** Unicode ***********************************/ +/* + * Constants and limits. Also FNAMESIZE and PATHSIZE are currently defined + * in param.h to be 128 and 512 + */ +#define TRACE_MAX (4096 - 48) +#define VALUE_MAX_STRING (4096 - 48) +#define SYM_MAX (512) +#define XML_MAX 4096 /* Maximum size for tags/tokens */ +#define BUF_MAX 4096 /* General sanity check for bufs */ + +/* + * Type for unicode systems + */ +#if UNICODE + +/* + * To convert strings to UNICODE. We have a level of indirection so things + * like T(__FILE__) will expand properly. + */ +#define T(x) __TXT(x) +#define __TXT(s) L ## s +typedef unsigned short char_t; +typedef unsigned short uchar_t; + +/* + * Text size of buffer macro. A buffer bytes will hold (size / char size) + * characters. + */ +#define TSZ(x) (sizeof(x) / sizeof(char_t)) + +/* + * How many ASCII bytes are required to represent this UNICODE string? + */ +#define TASTRL(x) ((wcslen(x) + 1) * sizeof(char_t)) + + +#define gmain wmain + +#define gsprintf swprintf +#define gprintf wprintf +#define gfprintf fwprintf +#define gsscanf swscanf +#define gvsprintf vswprintf + +#define gstrcpy wcscpy +#define gstrncpy wcsncpy +#define gstrncat wcsncat +#define gstrlen wcslen +#define gstrcat wcscat +#define gstrcmp wcscmp +#define gstrncmp wcsncmp +#define gstricmp wcsicmp +#define gstrchr wcschr +#define gstrrchr wcsrchr +#define gstrtok wcstok +#define gstrnset wcsnset +#define gstrstr wcsstr + +#define gfopen _wfopen +#define gopen _wopen +#define gcreat _wcreat +#define gfgets fgetws +#define gfputs fputws +#define gunlink _wunlink +#define grename _wrename +#define gtmpnam _wtmpnam +#define gtempnam _wtempnam +#define gfindfirst _wfindfirst +#define gfinddata_t _wfinddata_t +#define gfindnext _wfindnext +#define gfindclose _findclose +#define gstat _wstat +#define gaccess _waccess + +typedef struct _stat gstat_t; + +#define gmkdir _wmkdir +#define gchdir _wchdir +#define grmdir _wrmdir +#define gremove _wremove +#define ggetcwd _wgetcwd + +#define gtolower towlower +#define gtoupper towupper +#define gisspace iswspace +#define gisdigit iswdigit +#define gisxdigit iswxdigit +#define gisalnum iswalnum +#define gisalpha iswalpha +#define gisupper iswupper +#define gislower iswlower +#define gatoi(s) wcstol(s, NULL, 10) + +#define gctime _wctime +#define ggetenv _wgetenv +#define gexecvp _wexecvp + +#else /* ! UNICODE */ + +#define T(s) s +#define TSZ(x) (sizeof(x)) +#define TASTRL(x) (strlen(x) + 1) +typedef char char_t; +#if WIN +typedef unsigned char uchar_t; +#endif + +#define gsprintf sprintf +#define gprintf printf +#define gfprintf fprintf +#define gsscanf sscanf +#define gvsprintf vsprintf + +#define gstrcpy strcpy +#define gstrncpy strncpy +#define gstrncat strncat +#define gstrlen strlen +#define gstrcat strcat +#define gstrcmp strcmp +#define gstrncmp strncmp +#define gstricmp stricmp +#define gstrchr strchr +#define gstrrchr strrchr +#define gstrtok strtok +#define gstrnset strnset +#define gstrstr strstr + +#define gfopen fopen +#define gopen open +#define gcreat creat +#define gfgets fgets +#define gfputs fputs +#define gunlink unlink +#define grename rename +#define gtmpnam tmpnam +#define gtempnam tempnam +#define gfindfirst _findfirst +#define gfinddata_t _finddata_t +#define gfindnext _findnext +#define gfindclose _findclose +#define gstat stat +#define gaccess access + +typedef struct stat gstat_t; + +#define gmkdir mkdir +#define gchdir chdir +#define grmdir rmdir +#define gremove remove +#define ggetcwd getcwd + +#define gtolower tolower +#define gtoupper toupper +#define gisspace isspace +#define gisdigit isdigit +#define gisxdigit isxdigit +#define gisalnum isalnum +#define gisalpha isalpha +#define gisupper isupper +#define gislower islower +#define gatoi atoi + +#define gctime ctime +#define ggetenv getenv +#define gexecvp execvp +#ifndef VXW486 +#define gmain main +#endif +#endif + +/********************************** Defines ***********************************/ + +#define FNAMESIZE 256 /* Max length of file names */ + +#define E_MAX_ERROR 4096 +/* + * Error types + */ +#define E_ASSERT 0x1 /* Assertion error */ +#define E_LOG 0x2 /* Log error to log file */ +#define E_USER 0x3 /* Error that must be displayed */ + +#define E_L T(__FILE__), __LINE__ +#define E_ARGS_DEC char_t *file, int line +#define E_ARGS file, line + +#if ASSERT + #define a_assert(C) if (C) ; else error(E_L, E_ASSERT, T("%s"), #C) +#else + #define a_assert(C) if (1) ; else +#endif + +#define VALUE_VALID { {0}, integer, 1 } +#define VALUE_INVALID { {0}, undefined, 0 } + +/* + * Allocation flags + */ +#define VALUE_ALLOCATE 0x1 + +#define value_numeric(t) (t == integer) +#define value_str(t) (t >= string || t <= bytes) +#define value_ok(t) (t > undefined && t <= symbol) + +/* + * These values are not prefixed so as to aid code readability + */ +#ifndef UW +#pragma pack(2) +#endif + +typedef enum { + undefined = 0, + integer = 1, + string = 2, + bytes = 3, + errmsg = 4 +} value_type_t; + +/* + * In UW, bit fields default to unsigned unless explicitly defined as signed. + * Unfortunately, enum become ints, but not explicitly signed. Thus, if using + * an enum type in a bit field, it becomes unsigned, but we need signed. So + * for UW we declare value_type_t to be a signed int to make this all work. + */ +typedef struct { + + union { + long integer; + char_t *string; + char_t *bytes; + char_t *errmsg; + void *symbol; + } value; + +#if UW + signed int type : 8; +#else + value_type_t type : 8; +#endif + + unsigned int valid : 1; + unsigned int user_def_1 : 1; + unsigned int allocated : 1; /* String was balloced */ +} value_t; + +/* + * Extract a string from the value depending whether inline or via pointer + */ +#define value_strget(v) \ + (((v)->type == bytes) ? (v)->value.bytes : (v)->value.string) + +#ifndef UW +#pragma pack() +#endif + +/******************************************************************************/ +/* + * A ring queue allows maximum utilization of memory for data storage and is + * ideal for input/output buffering. This module provides a highly effecient + * implementation and a vehicle for dynamic strings. + * + * WARNING: This is a public implementation and callers have full access to + * the queue structure and pointers. Change this module very carefully. + * + * This module follows the open/close model. + * + * Operation of a ringq where rq is a pointer to a ringq : + * + * rq->buflen contains the size of the buffer. + * rq->buf will point to the start of the buffer. + * rq->servp will point to the first (un-consumed) data byte. + * rq->endp will point to the next free location to which new data is added + * rq->endbuf will point to one past the end of the buffer. + * + * Eg. If the ringq contains the data "abcdef", it might look like : + * + * +-------------------------------------------------------------------+ + * | | | | | | | | a | b | c | d | e | f | | | | | + * +-------------------------------------------------------------------+ + * ^ ^ ^ ^ + * | | | | + * rq->buf rq->servp rq->endp rq->enduf + * + * The queue is empty when servp == endp. This means that the queue will hold + * at most rq->buflen -1 bytes. It is the fillers responsibility to ensure + * the ringq is never filled such that servp == endp. + * + * It is the fillers responsibility to "wrap" the endp back to point to + * rq->buf when the pointer steps past the end. Correspondingly it is the + * consumers responsibility to "wrap" the servp when it steps to rq->endbuf. + * The ringqPutc and ringqGetc routines will do this automatically. + */ + +/* + * Ring queue buffer structure + */ +typedef struct { + unsigned char *buf; /* Holding buffer for data */ + unsigned char *servp; /* Pointer to start of data */ + unsigned char *endp; /* Pointer to end of data */ + unsigned char *endbuf; /* Pointer to end of buffer */ + int buflen; /* Length of ring queue */ + int maxsize; /* Maximum size */ + int increment; /* Growth increment */ +} ringq_t; + +/* + * Block allocation definitions + */ +#define B_L __FILE__, __LINE__ +#define B_ARGS_DEC char *file, int line +#define B_ARGS file, line + +/* + * Block classes are: 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + * 16384, 32768, 65536 + */ +#define B_SHIFT 4 /* Convert size to class */ +#define B_ROUND ((1 << (B_SHIFT)) - 1) +#define B_MAX_CLASS 13 /* Maximum class number + 1 */ +#define B_MALLOCED 0x80000000 /* Block was malloced */ +#define B_DEFAULT_MEM (64 * 1024) /* Default memory allocation */ +#define B_MAX_FILES (512) /* Maximum number of files */ +#define B_FILL_CHAR (0x77) /* Fill byte for buffers */ +#define B_FILL_WORD (0x77777777) /* Fill word for buffers */ +#define B_MAX_BLOCKS (64 * 1024) /* Maximum allocated blocks */ + +/* + * Flags. The integer value is used as an arbitrary value to fill the flags. + */ + +#define B_USE_MALLOC 0x1 /* Okay to use malloc if required */ +#define B_USER_BUF 0x2 /* User supplied buffer for mem */ +#define B_INTEGRITY 0x8124000 /* Integrity value */ +#define B_INTEGRITY_MASK 0xFFFF000 /* Integrity mask */ + +/* + * The symbol table record for each symbol entry + */ + +typedef struct sym_t { + struct sym_t *forw; /* Pointer to next hash list */ + value_t name; /* Name of symbol */ + value_t content; /* Value of symbol */ + int arg; /* Parameter value */ +} sym_t; + +typedef int sym_fd_t; /* Returned by symOpen */ + +/* + * Socket flags + */ +#define SOCKET_EOF 0x1 /* Seen end of file */ +#define SOCKET_CONNECTING 0x2 /* Connect in progress */ +#define SOCKET_BROADCAST 0x4 /* Broadcast mode */ +#define SOCKET_PENDING 0x8 /* Message pending on this socket */ +#define SOCKET_FLUSHING 0x10 /* Background flushing */ +#define SOCKET_LISTENING 0x20 /* Server socket listening */ + +/* + * Socket error values + */ +#define SOCKET_WOULDBLOCK 1 /* Socket would block on I/O */ +#define SOCKET_RESET 2 /* Socket has been reset */ +#define SOCKET_NETDOWN 3 /* Network is down */ +#define SOCKET_AGAIN 4 /* Issue the request again */ +#define SOCKET_INTR 5 /* Call was interrupted */ +#define SOCKET_INVAL 6 /* Invalid */ + +/* + * Handler event masks + */ +#define SOCKET_READABLE 0x2 /* Make socket readable */ +#define SOCKET_WRITABLE 0x4 /* Make socket writable */ +#define SOCKET_EXCEPTION 0x8 /* Interested in exceptions */ + +#define SOCKET_BUFSIZ 512 /* Underlying buffer size */ + +typedef void (*socketHandler_t)(int sid, int mask, int data); +typedef int (*socketAccept_t)(int sid, char *ipaddr, int port); + +/* + * Script engines + */ +#define EMF_SCRIPT_JSCRIPT 0 /* javascript */ +#define EMF_SCRIPT_TCL 1 /* tcl */ +#define EMF_SCRIPT_EJSCRIPT 2 /* Ejscript */ +#define EMF_SCRIPT_MAX 3 + +#define MAXINT INT_MAX +#define BITSPERBYTE 8 +#define BITS(type) (BITSPERBYTE * (int) sizeof(type)) + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +/******************************* Per O/S Defines ******************************/ + +#if VXW486 || LINUX || LYNX +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#define SOCKET_ERROR -1 +#endif + +#if WIN +#undef R_OK +#define R_OK 4 +#undef W_OK +#define W_OK 2 +#undef X_OK +#define X_OK 1 +#undef F_OK +#define F_OK 0 +#endif + +/********************************* Prototypes *********************************/ + +extern void bclose(); +extern int bopen(void *buf, int bufsize, int flags); +#if NO_BALLOC +#undef B_L +#define B_L x +#define balloc(x, num) malloc(num) +#define bfree(x, p) free(p) +#define bfreeSafe(x, p) \ + if (p) { bfree(x, p); } else +#define brealloc(x, p, num) realloc(p, num) +extern char *bstrdupANoBalloc(char *s); +#define bstrdupA(x, s) bstrdupANoBalloc(s) +#if UNICODE +extern char_t *bstrdupNoBalloc(char_t *s); +#define bstrdup(x, s) bstrdupNoBalloc(s) +#define gstrdup(x, s) bstrdupNoBalloc(s) +#else /* Not UNICODE */ +#define bstrdup(x, s) bstrdupANoBalloc(s) +#define gstrdup(x, s) bstrdupANoBalloc(s) +#endif /* UNICODE */ + +#else /* BALLOC */ +extern void *balloc(B_ARGS_DEC, int size); +extern void bfree(B_ARGS_DEC, void *mp); +extern void *brealloc(B_ARGS_DEC, void *buf, int newsize); +extern void bstats(int handle, void (*writefn)(int fd, char_t *fmt, ...)); +extern char_t *bstrdup(B_ARGS_DEC, char_t *s); +extern void bfreeSafe(B_ARGS_DEC, void* mp); +#define gstrdup(B_ARGS, s) bstrdup(B_ARGS, s) +#if UNICODE +extern char *bstrdupA(B_ARGS_DEC, char *s); +#else +#define bstrdupA bstrdup +#endif /* UNICODE */ +#endif /* BALLOC */ + +#if !LINUX +extern char_t* basename(char_t* name); +#endif + +extern void *emfCreateTimer(int delay, void (*routine)(long arg), long arg); +extern void emfDeleteTimer(void *id); +extern int emfInstGet(); +extern void emfInstSet(int inst); +extern void error(E_ARGS_DEC, int flags, char_t *fmt, ...); + +extern int hAlloc(void*** map); +extern int hFree(void*** map, int handle); +extern int hAllocEntry(void ***list, int *max, int size); + +extern int ringqOpen(ringq_t *rq, int increment, int maxsize); +extern void ringqClose(ringq_t *rq); +extern int ringqLen(ringq_t *rq); + +extern int ringqPutc(ringq_t *rq, char_t c); +extern int ringqInsertc(ringq_t *rq, char_t c); +extern int ringqPutstr(ringq_t *rq, char_t *str); +extern int ringqGetc(ringq_t *rq); + +extern int gvsnprintf(char_t **s, int n, char_t *fmt, va_list arg); +extern int gsnprintf(char_t **s, int n, char_t *fmt, ...); + +#if UNICODE +extern int ringqPutcA(ringq_t* rq, char c); +extern int ringqInsertcA(ringq_t* rq, char c); +extern int ringqPutstrA(ringq_t* rq, char* str); +extern int ringqGetcA(ringq_t* rq); +#else +#define ringqPutcA ringqPutc +#define ringqInsertcA ringqInsertc +#define ringqPutstrA ringqPutstr +#define ringqGetcA ringqGetc +#endif + +extern int ringqPutBlk(ringq_t *rq, unsigned char *buf, int len); +extern int ringqPutBlkMax(ringq_t *rq); +extern void ringqPutBlkAdj(ringq_t *rq, int size); +extern int ringqGetBlk(ringq_t *rq, unsigned char *buf, int len); +extern int ringqGetBlkMax(ringq_t *rq); +extern void ringqGetBlkAdj(ringq_t *rq, int size); +extern void ringqFlush(ringq_t *rq); + +extern int scriptSetVar(int engine, char_t *var, char_t *value); +extern int scriptEval(int engine, char_t *cmd, char_t **rslt, int chan); + +extern void socketClose(); +extern void socketCloseConnection(int sid); +extern void socketCreateHandler(int sid, int mask, socketHandler_t + handler, int arg); +extern void socketDeleteHandler(int sid); +extern int socketEof(int sid); +extern int socketFlush(int sid, int block); +extern int socketGets(int sid, char_t** buf); +extern int socketInputBuffered(int sid); +extern int socketOpen(); +extern int socketOpenConnection(char *host, int port, + socketAccept_t accept, int flags); +extern void socketProcess(); +extern int socketRead(int sid, char *buf, int len); +extern int socketReady(); +extern int socketWrite(int sid, char *buf, int len); +extern int socketWriteString(int sid, char_t *buf); +extern int socketSelect(); + +extern char_t *strlower(char_t *string); +extern char_t *strupper(char_t *string); + +extern char_t *stritoa(int n, char_t *string, int width); + +extern sym_fd_t symOpen(int hash_size); +extern void symClose(sym_fd_t sd, void (*cleanup)(sym_t *sp)); +extern sym_t *symLookup(sym_fd_t sd, char_t *name); +extern sym_t *symEnter(sym_fd_t sd, char_t *name, value_t v, int arg); +extern int symDelete(sym_fd_t sd, char_t *name); +extern void symWalk(sym_fd_t sd, void (*fn)(sym_t *symp)); + +extern void trace(int lev, char_t *fmt, ...); + +extern value_t valueInteger(long value); +extern value_t valueString(char_t *value, int flags); +extern value_t valueErrmsg(char_t *value); +extern void valueFree(value_t *v); + +extern char_t *ascToUni(char_t *ubuf, char *str, int nBytes); +extern char *uniToAsc(char *buf, char_t *ustr, int nBytes); +extern char_t *ballocAscToUni(char * cp); +extern char *ballocUniToAsc(char_t * unip, int ulen); + +#endif /* _h_UEMF */ + +/******************************************************************************/ diff --git a/cpukit/httpd/url.c b/cpukit/httpd/url.c new file mode 100644 index 0000000000..75746fd490 --- /dev/null +++ b/cpukit/httpd/url.c @@ -0,0 +1,203 @@ +/* + * url.c -- Parse URLs + * + * 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 parses URLs into their components. + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/********************************* Statics ************************************/ +/* + * htmExt is declared in this way to avoid a Linux and Solaris segmentation + * fault when a constant string is passed to strlower which could change its + * argument. + */ + +char_t htmExt[] = T(".htm"); + + +/*********************************** Code *************************************/ +/* + * Return the mime type for the given URL given a URL. + * The caller supplies the buffer to hold the result. + * charCnt is the number of characters the buffer will hold, ascii or UNICODE. + */ + +char_t *websUrlType(char_t *url, char_t *buf, int charCnt) +{ + sym_t *sp; + char_t *ext, *parsebuf; + + a_assert(url && *url); + a_assert(buf && charCnt > 0); + + if (url == NULL || *url == '\0') { + gstrcpy(buf, T("text/plain")); + return buf; + } + if (websUrlParse(url, &parsebuf, NULL, NULL, NULL, NULL, NULL, + NULL, &ext) < 0) { + gstrcpy(buf, T("text/plain")); + return buf; + } + strlower(ext); + +/* + * Lookup the mime type symbol table to find the relevant content type + */ + if ((sp = symLookup(websMime, ext)) != NULL) { + gstrncpy(buf, sp->content.value.string, charCnt); + } else { + gstrcpy(buf, T("text/plain")); + } + bfree(B_L, parsebuf); + return buf; +} + +/******************************************************************************/ +/* + * Parse the URL. A buffer is allocated to store the parsed URL in *pbuf. + * This must be freed by the caller. NOTE: tag is not yet fully supported. + */ + +int websUrlParse(char_t *url, char_t **pbuf, char_t **phost, char_t **ppath, + char_t **pport, char_t **pquery, char_t **pproto, char_t **ptag, + char_t **pext) +{ + char_t *tok, *cp, *host, *path, *port, *proto, *tag, *query, *ext; + char_t *last_delim, *hostbuf, *portbuf, *buf; + int c, len, ulen; + + a_assert(url); + a_assert(pbuf); + + ulen = gstrlen(url); +/* + * We allocate enough to store a separate hostname and port number fields. + * As there are 3 strings in the one buffer, we need room for 3 null chars. + * We allocate MAX_PORT_LEN char_t's for the port number. + */ + len = ulen * 2 + MAX_PORT_LEN + 3; + if ((buf = balloc(B_L, len * sizeof(char_t))) == NULL) { + return -1; + } + portbuf = &buf[len - MAX_PORT_LEN - 1]; + hostbuf = &buf[ulen+1]; + gstrcpy(buf, url); + url = buf; + +/* + * Convert the current listen port to a string. We use this if the URL has + * no explicit port setting + */ + stritoa(websGetPort(), portbuf, MAX_PORT_LEN); + port = portbuf; + path = T("/"); + proto = T("http"); + host = T("localhost"); + query = T(""); + ext = htmExt; + tag = T(""); + + if (gstrncmp(url, T("http://"), 7) == 0) { + tok = &url[7]; + tok[-3] = '\0'; + proto = url; + host = tok; + for (cp = tok; *cp; cp++) { + if (*cp == '/') { + break; + } + if (*cp == ':') { + *cp++ = '\0'; + port = cp; + tok = cp; + } + } + if ((cp = gstrchr(tok, '/')) != NULL) { +/* + * If a full URL is supplied, we need to copy the host and port + * portions into static buffers. + */ + c = *cp; + *cp = '\0'; + gstrncpy(hostbuf, host, ulen); + gstrncpy(portbuf, port, MAX_PORT_LEN); + *cp = c; + host = hostbuf; + port = portbuf; + path = cp; + tok = cp; + } + + } else { + path = url; + tok = url; + } + +/* + * Parse the tag and the query + */ + if ((cp = gstrchr(tok, '#')) != NULL) { + *cp++ = '\0'; + path = tok; + tok = cp; + } + if ((cp = gstrchr(tok, '?')) != NULL) { + *cp++ = '\0'; + query = cp; + } + +/* + * Only do the following if asked for the extension + */ + if (pext) { + if ((cp = gstrrchr(path, '.')) != NULL) { + if ((last_delim = gstrrchr(path, '/')) != NULL) { + if (last_delim > cp) { + ext = htmExt; + } else { + ext = cp; + } + } else { + ext = cp; + } + } else { + if (path[gstrlen(path) - 1] == '/') { + ext = htmExt; + } + } + } + +/* + * Pass back the fields requested (if not NULL) + */ + if (phost) + *phost = host; + if (ppath) + *ppath = path; + if (pport) + *pport = port; + if (pproto) + *pproto = proto; + if (pquery) + *pquery = query; + if (ptag) + *ptag = tag; + if (pext) + *pext = ext; + *pbuf = buf; + return 0; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/value.c b/cpukit/httpd/value.c new file mode 100644 index 0000000000..5872382556 --- /dev/null +++ b/cpukit/httpd/value.c @@ -0,0 +1,74 @@ +/* + * value.c -- Generic type (holds all types) + * + * Copyright (c) Go Ahead Software, Inc., 1995-1999 + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * This module provides a generic type that can hold all possible types. + * It is designed to provide maximum effeciency. + */ + +/********************************* Includes ***********************************/ + +#include "uemf.h" + +/*********************************** Locals ***********************************/ + + +/*********************************** Code *************************************/ +/* + * Initialize a integer value. + */ + +value_t valueInteger(long value) +{ + value_t v = VALUE_VALID; + + v.value.integer = value; + return v; +} + +/******************************************************************************/ +/* + * Initialize a string value. Note: not allocation + */ + +value_t valueString(char_t *value, int flags) +{ + value_t v = VALUE_VALID; + + v.type = string; + if (flags & VALUE_ALLOCATE) { + v.allocated = 1; + v.value.string = gstrdup(B_L, value); + } else { + v.allocated = 0; + v.value.string = value; + } + return v; +} + +/******************************************************************************/ +/* + * Free any storage allocated for a value. + */ + +void valueFree(value_t *v) +{ + a_assert(v); + + if (v->valid && v->allocated && v->type == string && + v->value.string != NULL) { + bfree(B_L, v->value.string); + } + v->type = undefined; + v->valid = 0; + v->allocated = 0; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/wbase64.c b/cpukit/httpd/wbase64.c new file mode 100644 index 0000000000..0898b16728 --- /dev/null +++ b/cpukit/httpd/wbase64.c @@ -0,0 +1,149 @@ +/* + * base64.c -- Base64 Mime encoding + * + * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * The base64 command encodes and decodes a string in mime base64 format + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/******************************** Local Data **********************************/ +/* + * Mapping of ANSI chars to base64 Mime encoding alphabet (see below) + */ + +static char_t map64[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +static char_t alphabet64[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', +}; + +/*********************************** Code *************************************/ +/* + * Decode a buffer from "string" and into "outbuf" + */ + +int websDecode64(char_t* outbuf, char_t* string, int outlen) +{ + unsigned long shiftbuf; + char_t* cp; + char_t* op; + int c, i, j, shift; + + op = outbuf; + *op = '\0'; + cp = string; + while (*cp && *cp != '=') { +/* + * Map 4 (6bit) input bytes and store in a single long (shiftbuf) + */ + shiftbuf = 0; + shift = 18; + for (i = 0; i < 4 && *cp && *cp != '='; i++, cp++) { + c = map64[*cp & 0xff]; + if (c == -1) { + error(E_L, E_LOG, T("Bad string: %s at %c index %d"), string, + c, i); + return -1; + } + shiftbuf = shiftbuf | (c << shift); + shift -= 6; + } +/* + * Interpret as 3 normal 8 bit bytes (fill in reverse order). + * Check for potential buffer overflow before filling. + */ + --i; + if ((op + i) >= &outbuf[outlen]) { + gstrcpy(outbuf, T("String too big")); + return -1; + } + for (j = 0; j < i; j++) { + *op++ = (char_t) ((shiftbuf >> (8 * (2 - j))) & 0xff); + } + *op = '\0'; + } + return 0; +} + + +/******************************************************************************/ +/* + * Encode a buffer from "string" into "outbuf" + */ + +void websEncode64(char_t* outbuf, char_t* string, int outlen) +{ + unsigned long shiftbuf; + char_t* cp; + char_t* op; + int x, i, j, shift; + + op = outbuf; + *op = '\0'; + cp = string; + while (*cp) { +/* + * Take three characters and create a 24 bit number in shiftbuf + */ + shiftbuf = 0; + for (j = 2; j >= 0 && *cp; j--, cp++) { + shiftbuf |= ((*cp & 0xff) << (j * 8)); + } +/* + * Now convert shiftbuf to 4 base64 letters. The i,j magic calculates + * how many letters need to be output. + */ + shift = 18; + for (i = ++j; i < 4 && op < &outbuf[outlen] ; i++) { + x = (shiftbuf >> shift) & 0x3f; + *op++ = alphabet64[(shiftbuf >> shift) & 0x3f]; + shift -= 6; + } +/* + * Pad at the end with '=' + */ + while (j-- > 0) { + *op++ = '='; + } + *op = '\0'; + } +} +/******************************************************************************/ diff --git a/cpukit/httpd/webcomp.c b/cpukit/httpd/webcomp.c new file mode 100644 index 0000000000..90767da01f --- /dev/null +++ b/cpukit/httpd/webcomp.c @@ -0,0 +1,177 @@ +/* + * webcomp -- Compile web pages into C source + * + * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * Usage: webcomp prefix filelist >webrom.c + * + * filelist is a file containing the pathnames of all web pages + * prefix is a path prefix to remove from all the web page pathnames + * webrom.c is the resulting C source file to compile and link. + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/**************************** Forward Declarations ****************************/ + +static int compile(char_t *fileList, char_t *prefix); +static void usage(); + +/*********************************** Code *************************************/ +/* + * Main program for webpack test harness + */ + +int gmain(int argc, char_t* argv[]) +{ + char_t *fileList, *prefix; + + fileList = NULL; + + if (argc != 3) { + usage(); + } + + prefix = argv[1]; + fileList = argv[2]; + + if (compile(fileList, prefix) < 0) { + return -1; + } + return 0; +} + +/******************************************************************************/ +/* + * Output usage message + */ + +static void usage() +{ + fprintf(stderr, "usage: webcomp prefix filelist >output.c\n"); + exit(2); +} + +/******************************************************************************/ +/* + * Compile the web pages + */ + +static int compile(char_t *fileList, char_t *prefix) +{ + gstat_t sbuf; + FILE *lp; + time_t now; + char_t file[FNAMESIZE]; + char_t *cp; + char buf[512]; + unsigned char *p; + int j, i, len, fd, nFile; + +/* + * Open list of files + */ + if ((lp = fopen(fileList, "r")) == NULL) { + fprintf(stderr, "Can't open file list %s\n", fileList); + return -1; + } + + time(&now); + fprintf(stdout, "/*\n * webrom.c -- Compiled Web Pages\n *\n"); + fprintf(stdout, " * Compiled by GoAhead WebCompile: %s */\n\n", + ctime(&now)); + fprintf(stdout, "#include \"wsIntrn.h\"\n\n"); + fprintf(stdout, "#ifndef WEBS_PAGE_ROM\n"); + fprintf(stdout, "websRomPageIndexType websRomPageIndex[] = {\n"); + fprintf(stdout, " { 0, 0, 0 },\n};\n"); + fprintf(stdout, "#else\n"); + +/* + * Open each input file and compile each web page + */ + nFile = 0; + while (fgets(file, sizeof(file), lp) != NULL) { + if ((p = strchr(file, '\n')) || (p = strchr(file, '\r'))) { + *p = '\0'; + } + if (gstat(file, &sbuf) == 0 && sbuf.st_mode & S_IFDIR) { + continue; + } + if ((fd = gopen(file, O_RDONLY | O_BINARY)) < 0) { + fprintf(stderr, "Can't open file %s\n", file); + return -1; + } + fprintf(stdout, "static unsigned char page_%d[] = {\n", nFile); + + while ((len = read(fd, buf, sizeof(buf))) > 0) { + p = buf; + for (i = 0; i < len; ) { + fprintf(stdout, " "); + for (j = 0; p < &buf[len] && j < 16; j++, p++) { + fprintf(stdout, "%3d,", *p); + } + i += j; + fprintf(stdout, "\n"); + } + } + fprintf(stdout, " 0 };\n\n"); + + close(fd); + nFile++; + } + fclose(lp); + +/* + * Now output the page index + */ + fprintf(stdout, "websRomPageIndexType websRomPageIndex[] = {\n"); + + if ((lp = fopen(fileList, "r")) == NULL) { + fprintf(stderr, "Can't open file list %s\n", fileList); + return -1; + } + nFile = 0; + while (fgets(file, sizeof(file), lp) != NULL) { + if ((p = strchr(file, '\n')) || (p = strchr(file, '\r'))) { + *p = '\0'; + } +/* + * Remove the prefix and add a leading "/" when we print the path + */ + if (strncmp(file, prefix, gstrlen(prefix)) == 0) { + cp = &file[gstrlen(prefix)]; + } else { + cp = file; + } + if (*cp == '/') { + cp++; + } + + if (gstat(file, &sbuf) == 0 && sbuf.st_mode & S_IFDIR) { + fprintf(stdout, " { T(\"/%s\"), 0, 0 },\n", cp); + continue; + } + fprintf(stdout, " { T(\"/%s\"), page_%d, %d },\n", cp, nFile, + sbuf.st_size); + nFile++; + } + fclose(lp); + + fprintf(stdout, " { 0, 0, 0 },\n"); + fprintf(stdout, "};\n"); + fprintf(stdout, "#endif /* WEBS_PAGE_ROM */\n"); + + fclose(lp); + fflush(stdout); + return 0; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/webmain.c b/cpukit/httpd/webmain.c new file mode 100644 index 0000000000..9c12cd394c --- /dev/null +++ b/cpukit/httpd/webmain.c @@ -0,0 +1,410 @@ +/* + * main.c -- Main program for the GoAhead WebServer (LINUX version) + * + * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/******************************** Description *********************************/ + +/* + * Main program for for the GoAhead WebServer. This is a demonstration + * main program to initialize and configure the web server. + */ + +/********************************* Includes ***********************************/ + +#include "uemf.h" +#include "wsIntrn.h" +#include +#include +#include + +/*********************************** Locals ***********************************/ +/* + * Change configuration here + */ + +static char_t *rootWeb = T("web"); /* Root web directory */ +static char_t *password = T(""); /* Security password */ +static int port = 80; /* Server port */ +static int retries = 5; /* Server port retries */ +static int finished; /* Finished flag */ + +/* + * Structure to hold timer events + */ +typedef struct { + void (*routine)(long arg); /* Timer routine */ + long arg; /* Argument to routine */ +} websTimer_t; + +/* The following holds the pointer to an allocated websTimer_t structure . + * Using this method only one timer can be active at a time, but + * for the WebServer, this should be OK. + */ +websTimer_t *tp; + +/****************************** Forward Declarations **************************/ + +static int initWebs(); +static int aspTest(int eid, webs_t wp, int argc, char_t **argv); +static void formTest(webs_t wp, char_t *path, char_t *query); +static int websHomePageHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, + int arg, char_t* url, char_t* path, char_t* query); +static void timerProc(int signo); +#if B_STATS +static void printMemStats(int handle, char_t *fmt, ...); +static void memLeaks(); +#endif +static timer_t timer_id; +static void rtems_httpd_daemon(); + +/*********************************** Code *************************************/ +/* + * Main -- entry point from LINUX + */ +int rtems_initialize_webserver() +{ + rtems_status_code sc; + rtems_id tid; + int priority; + + /*********************************************************************** + * Default HTTPD priority. + **********************************************************************/ + priority = 40; + + sc = rtems_task_create(rtems_build_name('H', 'T', 'P', 'D'), + priority, 8*1024, + RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR | + RTEMS_INTERRUPT_LEVEL(0), + RTEMS_NO_FLOATING_POINT | RTEMS_LOCAL, + &tid); + if (sc != RTEMS_SUCCESSFUL) + { + return(RTEMS_UNSATISFIED); + } + + sc = rtems_task_start(tid, rtems_httpd_daemon, 0); + if (sc != RTEMS_SUCCESSFUL) + { + return(RTEMS_UNSATISFIED); + } + + return(RTEMS_SUCCESSFUL); + +} + +static void +rtems_httpd_daemon() +{ +/* + * Initialize the memory allocator. Allow use of malloc and start with a + * 10K heap. + */ + bopen(NULL, (10 * 1024), B_USE_MALLOC); + +/* + * Initialize the web server + */ + if (initWebs() < 0) { + rtems_panic("Unable to initialize Web server !!\n"); + } + +/* + * Basic event loop. SocketReady returns true when a socket is ready for + * service. SocketSelect will block until an event occurs. SocketProcess + * will actually do the servicing. + */ + while (!finished) { + if (socketReady() || socketSelect()) { + socketProcess(); + } + } + +/* + * Close the socket module, report memory leaks and close the memory allocator + */ + websCloseServer(); + socketClose(); +#if B_STATS + memLeaks(); +#endif + bclose(); + rtems_task_delete( RTEMS_SELF ); +} + +/******************************************************************************/ +/* + * Initialize the web server. + */ + +static int initWebs() +{ + struct hostent* hp; + struct in_addr intaddr; + char host[128], dir[128], webdir[128]; + char *cp; + char_t wbuf[128]; + +/* + * Initialize the socket subsystem + */ + socketOpen(); + +/* + * Define the local Ip address, host name, default home page and the + * root web directory. + */ + if (gethostname(host, sizeof(host)) < 0) { + error(E_L, E_LOG, T("Can't get hostname")); + return -1; + } + +/* intaddr.s_addr = (unsigned long) hostGetByName(host); */ + if ((hp = gethostbyname(host)) == NULL) { + error(E_L, E_LOG, T("Can't get host address")); + return -1; + } + memcpy((char *) &intaddr, (char *) hp->h_addr_list[0], + (size_t) hp->h_length); + +/* + * Set ../web as the root web. Modify this to suit your needs + */ + getcwd(dir, sizeof(dir)); + if ((cp = strrchr(dir, '/'))) { + *cp = '\0'; + } + sprintf(webdir, "%s/%s", dir, rootWeb); + +/* + * Configure the web server options before opening the web server + */ + websSetDefaultDir(webdir); + ascToUni(wbuf, inet_ntoa(intaddr), sizeof(wbuf)); + websSetIpaddr(wbuf); + ascToUni(wbuf, host, sizeof(wbuf)); + websSetHost(wbuf); + +/* + * Configure the web server options before opening the web server + */ + websSetDefaultPage(T("default.asp")); + websSetPassword(password); + +/* + * Open the web server on the given port. If that port is taken, try + * the next sequential port for up to "retries" attempts. + */ + websOpenServer(port, retries); + +/* + * First create the URL handlers. Note: handlers are called in sorted order + * with the longest path handler examined first. Here we define the security + * handler, forms handler and the default web page handler. + */ + websUrlHandlerDefine(T(""), NULL, 0, websSecurityHandler, + WEBS_HANDLER_FIRST); + websUrlHandlerDefine(T("/goform"), NULL, 0, websFormHandler, 0); + websUrlHandlerDefine(T(""), NULL, 0, websDefaultHandler, + WEBS_HANDLER_LAST); + +/* + * Now define two test procedures. Replace these with your application + * relevant ASP script procedures and form functions. + */ + websAspDefine(T("aspTest"), aspTest); + websFormDefine(T("formTest"), formTest); + +/* + * Create a handler for the default home page + */ + websUrlHandlerDefine(T("/"), NULL, 0, websHomePageHandler, 0); + return 0; +} + +/******************************************************************************/ +/* + * Test Javascript binding for ASP. This will be invoked when "aspTest" is + * embedded in an ASP page. See web/asp.asp for usage. Set browser to + * "localhost/asp.asp" to test. + */ + +static int aspTest(int eid, webs_t wp, int argc, char_t **argv) +{ + char_t *name, *address; + + if (ejArgs(argc, argv, T("%s %s"), &name, &address) < 2) { + websError(wp, 400, T("Insufficient args\n")); + return -1; + } + return websWrite(wp, T("Name: %s, Address %s"), name, address); +} +/******************************************************************************/ +/* + * Test form for posted data (in-memory CGI). This will be called when the + * form in web/asp.asp is invoked. Set browser to "localhost/asp.asp" to test. + */ + +static void formTest(webs_t wp, char_t *path, char_t *query) +{ + char_t *name, *address; + + name = websGetVar(wp, T("name"), T("Joe Smith")); + address = websGetVar(wp, T("address"), T("1212 Milky Way Ave.")); + + websHeader(wp); + websWrite(wp, T("

Name: %s, Address: %s

\n"), name, address); + websFooter(wp); + websDone(wp, 200); +} + +/******************************************************************************/ +/* + * Create a timer to invoke the routine in "delay" milliseconds. + */ + +void *emfCreateTimer(int delay, void (*routine)(long arg), long arg) +{ + struct sigaction act; + struct itimerspec its = { {0,0}, {0,0} }; + struct sigevent se; + int status; + + if ((tp = balloc(B_L, sizeof(websTimer_t)))) { + tp->routine = routine; + tp->arg = arg; + } + else { + return NULL; + } + + se.sigev_notify = SIGEV_THREAD; + se.sigev_value.sival_ptr = tp; + se.sigev_notify_function = (void (*)(union sigval)) timerProc; + + /* + * NOT POSIX? + * se.sigev_notify_attributes = NULL; + */ + + + status = timer_create(CLOCK_REALTIME, &se, &timer_id); + if (status != 0) { + bfree(B_L, tp); + return NULL; + } + /* convert delay millisecs to secs and usecs required by struct */ + its.it_value.tv_sec = delay / 1000; + its.it_value.tv_nsec = (delay % 1000) * 1000000; + + status = timer_settime(timer_id, 0, &its, 0); + if (status != 0) { + bfree(B_L, tp); + return NULL; + } + +#if 0 + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + act.sa_handler = timerProc; + sigaction(SIGALRM, &act, NULL); + + /* convert delay millisecs to secs and usecs required by struct */ + its.it_value.tv_sec = delay / 1000; + its.it_value.tv_usec = (delay % 1000) * 1000; + + if (setitimer(ITIMER_REAL, &its, NULL) == -1) { + bfree(B_L, tp); + return NULL; + } +#endif + return tp; +} + +/******************************************************************************/ +/* + * Delete a timer + */ + +void emfDeleteTimer(void * id) +{ + websTimer_t *wtp; + /*struct itimerval its = { {0,0}, {0,0} };*/ + + wtp = (websTimer_t *)id; + /* setitimer(ITIMER_REAL, &its, NULL);*/ + timer_delete(timer_id); + bfree(B_L, wtp); +} + +/******************************************************************************/ +/* + * Timer handler + */ + +static void timerProc(int signo) +{ + websTimer_t wtp = *tp; + +/* Copy the timer structure to a local first and delete it before calling + * the function, since the function could create another timer. In this + * implementation, only one timer can be allocated at a time. + */ + + bfree(B_L, tp); + (wtp.routine)(wtp.arg); +} + +/******************************************************************************/ +/* + * Home page handler + */ + +static int websHomePageHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, + int arg, char_t* url, char_t* path, char_t* query) +{ +/* + * If the empty or "/" URL is invoked, redirect default URLs to the home page + */ + if (*url == '\0' || gstrcmp(url, T("/")) == 0) { + websRedirect(wp, T("home.asp")); + return 1; + } + return 0; +} + +/******************************************************************************/ + +#if B_STATS +static void memLeaks() +{ + int fd; + + if ((fd = gopen(T("leak.txt"), O_CREAT | O_TRUNC | O_WRONLY)) >= 0) { + bstats(fd, printMemStats); + close(fd); + } +} + +/******************************************************************************/ +/* + * Print memory usage / leaks + */ + +static void printMemStats(int handle, char_t *fmt, ...) +{ + va_list args; + char_t buf[256]; + + va_start(args, fmt); + vsprintf(buf, fmt, args); + va_end(args); + write(handle, buf, strlen(buf)); +} +#endif + +/******************************************************************************/ diff --git a/cpukit/httpd/webpage.c b/cpukit/httpd/webpage.c new file mode 100644 index 0000000000..d2b976e38f --- /dev/null +++ b/cpukit/httpd/webpage.c @@ -0,0 +1,138 @@ +/* + * Page.c -- Support for page retrieval. + * + * 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 provides page retrieval handling. It provides support for + * reading web pages from file systems and has expansion for ROMed web + * pages. + */ + +/********************************* Includes ***********************************/ + +#include "wsIntrn.h" + +/*********************************** Code *************************************/ +/* + * Open a web page. lpath is the local filename. path is the URL path name. + */ + +int websPageOpen(webs_t wp, char_t *lpath, char_t *path, int mode, int perm) +{ + a_assert(websValid(wp)); + +#if WEBS_PAGE_ROM + return websRomPageOpen(wp, path, mode, perm); +#else + return (wp->docfd = gopen(lpath, mode, perm)); +#endif /* WEBS_PAGE_ROM */ +} + +/******************************************************************************/ +/* + * Close a web page + */ + +void websPageClose(webs_t wp) +{ +#if WEBS_PAGE_ROM + websRomPageClose(wp->docfd); +#else + if (wp->docfd >= 0) { + close(wp->docfd); + wp->docfd = -1; + } +#endif +} + +/******************************************************************************/ +/* + * Stat a web page lpath is the local filename. path is the URL path name. + */ + +int websPageStat(webs_t wp, char_t *lpath, char_t *path, websStatType* sbuf) +{ +#if WEBS_PAGE_ROM + return websRomPageStat(path, sbuf); +#else + gstat_t s; + + if (gstat(lpath, &s) < 0) { + return -1; + } + sbuf->size = s.st_size; + sbuf->mtime = s.st_mtime; + sbuf->isDir = s.st_mode & S_IFDIR; + return 0; +#endif +} + +/******************************************************************************/ +/* + * Is this file a directory? + */ + +int websPageIsDirectory(char_t *lpath) +{ +#if WEBS_PAGE_ROM + websStatType sbuf; + + if (websRomPageStat(lpath, &sbuf) >= 0) { + return(sbuf.isDir); + } else { + return 0; + } +#else + gstat_t sbuf; + + if (gstat(lpath, &sbuf) >= 0) { + return(sbuf.st_mode & S_IFDIR); + } else { + return 0; + } +#endif +} + + +/******************************************************************************/ +/* + * Read a web page. Returns the number of _bytes_ read. + * len is the size of buf, in bytes. + */ + +int websPageReadData(webs_t wp, char *buf, int nBytes) +{ + +#if WEBS_PAGE_ROM + a_assert(websValid(wp)); + return websRomPageReadData(wp, buf, nBytes); +#else + a_assert(websValid(wp)); + return read(wp->docfd, buf, nBytes); +#endif +} + +/******************************************************************************/ +/* + * Move file pointer offset bytes. + */ + +void websPageSeek(webs_t wp, long offset) +{ + a_assert(websValid(wp)); + +#if WEBS_PAGE_ROM + websRomPageSeek(wp, offset, SEEK_CUR); +#else + lseek(wp->docfd, offset, SEEK_CUR); +#endif +} + +/******************************************************************************/ + diff --git a/cpukit/httpd/webrom.c b/cpukit/httpd/webrom.c new file mode 100644 index 0000000000..225d8bbf22 --- /dev/null +++ b/cpukit/httpd/webrom.c @@ -0,0 +1,12 @@ +/* + * webrom.c -- Compiled Web Pages + * + * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. + * See the file "license.txt" for usage and redistribution license requirements + */ + +#include "wsIntrn.h" + +websRomPageIndexType websRomPageIndex[] = { + { 0, 0, 0 }, +}; diff --git a/cpukit/httpd/webs.c b/cpukit/httpd/webs.c new file mode 100644 index 0000000000..e198cc1e9a --- /dev/null +++ b/cpukit/httpd/webs.c @@ -0,0 +1,1841 @@ +/* + * 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_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) { + 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; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/webs.h b/cpukit/httpd/webs.h new file mode 100644 index 0000000000..46e11bc2fc --- /dev/null +++ b/cpukit/httpd/webs.h @@ -0,0 +1,182 @@ +/* + * webs.h -- Go Ahead Web public header + * + * Copyright (c) Go Ahead Software Inc., 1992-1999. All Rights Reserved. + * + * See the file "license.txt" for information on usage and redistribution + */ + +#ifndef _h_WEBS +#define _h_WEBS 1 + +/******************************** Description *********************************/ + +/* + * Go Ahead Web Server header. This defines the Web public APIs. + * Include this header for files that contain ASP or Form procedures. + * Include wsIntrn.h when creating URL handlers. + */ + +/********************************* Includes ***********************************/ + +#include "ej.h" + +/********************************** Defines ***********************************/ + +#define WEBS_HEADER_BUFINC 512 /* Header buffer size */ +#define WEBS_ASP_BUFINC 512 /* Asp expansion increment */ +#define WEBS_MAX_PASS 32 /* Size of password */ +#define WEBS_BUFSIZE 1000 /* websWrite max output string */ +#define WEBS_MAX_HEADER (5 * 1024) /* Sanity check header */ +#define WEBS_MAX_URL 4096 /* Maximum URL size for sanity */ +#define WEBS_SOCKET_BUFSIZ 256 /* Bytes read from socket */ + +/* + * Request flags. Also returned by websGetRequestFlags(). + */ +#define WEBS_LOCAL_PAGE 0x1 /* Request for local webs page */ +#define WEBS_KEEP_ALIVE 0x2 /* HTTP/1.1 keep alive */ +#define WEBS_DONT_USE_CACHE 0x4 /* Not implemented cache support */ +#define WEBS_COOKIE 0x8 /* Cookie supplied in request */ +#define WEBS_IF_MODIFIED 0x10 /* If-modified-since in request */ +#define WEBS_POST_REQUEST 0x20 /* Post request operation */ +#define WEBS_LOCAL_REQUEST 0x40 /* Request from this system */ +#define WEBS_HOME_PAGE 0x80 /* Request for the home page */ +#define WEBS_ASP 0x100 /* ASP request */ +#define WEBS_HEAD_REQUEST 0x200 /* Head request */ +#define WEBS_CLEN 0x400 /* Request had a content length */ +#define WEBS_FORM 0x800 /* Request is a form */ +#define WEBS_REQUEST_DONE 0x1000 /* Request complete */ +#define WEBS_POST_DATA 0x2000 /* Already appended post data */ +#define WEBS_HEADER_DONE 0x40000 /* Already output the HTTP header */ + +/* + * URL handler flags + */ +#define WEBS_HANDLER_FIRST 0x1 /* Process this handler first */ +#define WEBS_HANDLER_LAST 0x2 /* Process this handler last */ + +/* + * Per socket connection webs structure + */ +typedef struct websRec { + ringq_t header; /* Header dynamic string */ + time_t since; /* Parsed if-modified-since time */ + sym_fd_t cgiVars; /* CGI standard variables */ + sym_fd_t cgiQuery; /* CGI decoded query string */ + time_t timestamp; /* Last transaction with browser */ + void* timeout; /* Timeout handle */ + char_t ipaddr[32]; /* Connecting ipaddress */ + char_t type[64]; /* Mime type */ + char_t* dir; /* Directory containing the page */ + char_t* path; /* Path name without query */ + char_t* url; /* Full request url */ + char_t* host; /* Requested host */ + char_t* lpath; /* Cache local path name */ + char_t* query; /* Request query */ + char_t* decodedQuery; /* Decoded request query */ + char_t* password; /* Authorization password */ + char_t* userName; /* Authorization username */ + char_t* cookie; /* Cookie string */ + char_t* userAgent; /* User agent (browser) */ + int sid; /* Socket id (handler) */ + int port; /* Request port number */ + int state; /* Current state */ + int flags; /* Current flags -- see above */ + int code; /* Request result code */ + int clen; /* Content length */ + int wid; /* Index into webs */ + int docfd; /* Document file descriptor */ + int numbytes; /* Bytes to transfer to browser */ + int written; /* Bytes actually transferred */ + void (*writeSocket)(struct websRec *wp); +} websRec; + +typedef websRec *webs_t; +typedef websRec websType; + +/******************************** Prototypes **********************************/ + +extern int websAspDefine(char_t *name, + int (*fn)(int ejid, webs_t wp, int argc, char_t **argv)); +extern int websAspRequest(webs_t wp, char_t *lpath); +extern void websCloseListen(); +extern int websDecode64(char_t *outbuf, char_t *string, int buflen); +extern void websDecodeUrl(char_t *token, char_t *decoded, int len); +extern void websDone(webs_t wp, int code); +extern void websEncode64(char_t *outbuf, char_t *string, int buflen); +extern void websError(webs_t wp, int code, char_t *msg, ...); +extern void websFooter(webs_t wp); +extern int websFormDefine(char_t *name, void (*fn)(webs_t wp, + char_t *path, char_t *query)); +extern char_t *websGetDefaultDir(); +extern char_t *websGetDefaultPage(); +extern char_t *websGetHostUrl(); +extern char_t *websGetPassword(); +extern int websGetPort(); +extern char_t *websGetPublishDir(char_t *path, char_t **urlPrefix); +extern int websGetRequestBytes(webs_t wp); +extern char_t *websGetRequestDir(webs_t wp); +extern int websGetRequestFlags(webs_t wp); +extern char_t *websGetRequestIpaddr(webs_t wp); +extern char_t *websGetRequestLpath(webs_t wp); +extern char_t *websGetRequestPath(webs_t wp); +extern char_t *websGetRequestPassword(webs_t wp); +extern char_t *websGetRequestType(webs_t wp); +extern int websGetRequestWritten(webs_t wp); +extern char_t *websGetVar(webs_t wp, char_t *var, char_t *def); +extern void websHeader(webs_t wp); +extern int websOpenListen(int port, int retries); +extern int websPageOpen(webs_t wp, char_t *lpath, char_t *path, + int mode, int perm); +extern void websPageClose(webs_t wp); +extern int websPublish(char_t *urlPrefix, char_t *path); +extern void websRedirect(webs_t wp, char_t *url); +extern void websSecurityDelete(); +extern int websSecurityHandler(webs_t wp, char_t *urlPrefix, + char_t *webDir, int arg, char_t *url, char_t *path, + char_t *query); +extern void websSetDefaultDir(char_t *dir); +extern void websSetDefaultPage(char_t *page); +extern void websSetEnv(webs_t wp); +extern void websSetHost(char_t *host); +extern void websSetIpaddr(char_t *ipaddr); +extern void websSetPassword(char_t *password); +extern void websSetRequestBytes(webs_t wp, int bytes); +extern void websSetRequestFlags(webs_t wp, int flags); +extern void websSetRequestLpath(webs_t wp, char_t *lpath); +extern void websSetRequestPath(webs_t wp, char_t *dir, char_t *path); +extern char_t *websGetRequestUserName(webs_t wp); +extern void websSetRequestWritten(webs_t wp, int written); +extern void websSetVar(webs_t wp, char_t *var, char_t *value); +extern int websTestVar(webs_t wp, char_t *var); +extern int websUrlHandlerDefine(char_t *urlPrefix, char_t *webDir, + int arg, int (*fn)(webs_t wp, char_t *urlPrefix, + char_t *webDir, int arg, char_t *url, char_t *path, + char_t *query), int flags); +extern int websUrlHandlerDelete(int (*fn)(webs_t wp, char_t *urlPrefix, + char_t *webDir, int arg, char_t *url, char_t *path, + char_t *query)); +extern int websUrlHandlerRequest(webs_t wp); +extern int websUrlParse(char_t *url, char_t **buf, char_t **host, + char_t **path, char_t **port, char_t **query, + char_t **proto, char_t **tag, char_t **ext); +extern char_t *websUrlType(char_t *webs, char_t *buf, int charCnt); +extern int websWrite(webs_t wp, char_t* fmt, ...); +extern int websWriteBlock(webs_t wp, char_t *buf, int nChars); +extern int websWriteBlockData(webs_t wp, char *buf, int nChars); +extern int websValid(webs_t wp); +extern int websValidateUrl(webs_t wp, char_t *path); +extern int websCloseFileHandle(webs_t wp); + +/* + * Prototypes for functions available when running as part of the + * GoAhead Embedded Management Framework (EMF) + */ +#if EMF +extern void websFormExplain(webs_t wp, char_t *path, char_t *query); +#endif + +#endif /* _h_WEBS */ + +/******************************************************************************/ diff --git a/cpukit/httpd/websuemf.c b/cpukit/httpd/websuemf.c new file mode 100644 index 0000000000..48a30f25d0 --- /dev/null +++ b/cpukit/httpd/websuemf.c @@ -0,0 +1,39 @@ +/* + * websuemf.c -- GoAhead Micro Embedded Management Framework + * + * Copyright (c) Go Ahead Software Inc., 1995-1999. All Rights Reserved. + * + * See the file "license.txt" for usage and redistribution license requirements + */ + +/********************************** Description *******************************/ + +/* + * This modules provides compatibility with the full GoAhead EMF. + */ + +/*********************************** Includes *********************************/ + +#include "wsIntrn.h" + +/************************************* Code ***********************************/ +/* + * Evaluate a script + */ + +int scriptEval(int engine, char_t* cmd, char_t** result, int chan) +{ + int ejid; + + if (engine == EMF_SCRIPT_EJSCRIPT) { + ejid = (int) chan; + if (ejEval(ejid, cmd, NULL) ) { + return 0; + } else { + return -1; + } + } + return -1; +} + +/******************************************************************************/ diff --git a/cpukit/httpd/wsIntrn.h b/cpukit/httpd/wsIntrn.h new file mode 100644 index 0000000000..bd77ef8862 --- /dev/null +++ b/cpukit/httpd/wsIntrn.h @@ -0,0 +1,268 @@ +/* + * wsIntrn.h -- Internal Go Ahead Web server header + * + * Copyright (c) Go Ahead Software Inc., 1992-1999. All Rights Reserved. + * + * See the file "license.txt" for information on usage and redistribution + */ + +#ifndef _h_WEBS_INTERNAL +#define _h_WEBS_INTERNAL 1 + +/******************************** Description *********************************/ + +/* + * Internal Go Ahead Web Server header. This defines the Web private APIs + * Include this header when you want to create URL handlers. + */ + +/*********************************** Defines **********************************/ + +/* + * Define this to enable login of web accesses to a file + * #define WEBS_LOG_SUPPORT 1 + * + * Define this to enable HTTP/1.1 keep alive support + * #define WEBS_KEEP_ALIVE_SUPPORT 1 + * + * Define this to enable if-modified-since support + * #define WEBS_IF_MODIFIED_SUPPORT 1 + * + * Define this to support proxy capability and track local vs remote request + * Note: this is not yet fully implemented. + * #define WEBS_PROXY_SUPPORT 1 + * + * Define this to support reading pages from ROM + * Note: this is not yet fully implemented. + * #define WEBS_PAGE_ROM 1 + * + * Define this to enable memory allocation and stack usage tracking + * #define B_STATS 1 + */ + +/********************************** Includes **********************************/ + +#include +#include +#include +#include + +#if WIN + #include + #include + #include +#endif + +#if CE +#if ! UEMF + #include +#endif +#endif + +#if NW + #include + #include +#endif + +#if LYNX + #include + #include + #include + #include +#endif + +#if UNIX + #include + #include + #include + #include +#endif + +#if QNX4 + #include + #include + #include + #include + #include +#endif + +#if UW + #include + #include +#endif + +#if VXW486 + #include + #include + #include +#endif + +#if UEMF + #include "uemf.h" + #include "ej.h" +#else + #include "emf/emfInternal.h" +#endif + +#include "webs.h" + +/********************************** Defines ***********************************/ +/* + * Read handler flags and state + */ +#define WEBS_BEGIN 0x1 /* Beginning state */ +#define WEBS_HEADER 0x2 /* Ready to read first line */ +#define WEBS_POST 0x4 /* POST without content */ +#define WEBS_POST_CLEN 0x8 /* Ready to read content for POST */ +#define WEBS_PROCESSING 0x10 /* Processing request */ +#define WEBS_KEEP_TIMEOUT 15000 /* Keep-alive timeout (15 secs) */ +#define WEBS_TIMEOUT 60000 /* General request timeout (60) */ + +#define PAGE_READ_BUFSIZE 512 /* bytes read from page files */ +#define MAX_PORT_LEN 10 /* max digits in port number */ + +/* + * URL handler structure. Stores the leading URL path and the handler + * function to call when the URL path is seen. + */ +typedef struct { + int (*handler)(webs_t wp, char_t *urlPrefix, char_t *webDir, int arg, + char_t *url, char_t *path, + char_t *query); /* Callback URL handler function */ + char_t *webDir; /* Web directory if required */ + char_t *urlPrefix; /* URL leading prefix */ + int len; /* Length of urlPrefix for speed */ + int arg; /* Argument to provide to handler */ + int flags; /* Flags */ +} websUrlHandlerType; + +/* + * Webs statistics + */ +typedef struct { + long errors; /* General errors */ + long redirects; + long net_requests; + long activeNetRequests; + long activeBrowserRequests; + long timeouts; + long access; /* Access violations */ + long localHits; + long remoteHits; + long formHits; + long handlerHits; +} websStatsType; + +extern websStatsType websStats; /* Web access stats */ + +/* + * Error code list + */ +typedef struct { + int code; /* HTTP error code */ + char_t *msg; /* HTTP error message */ +} websErrorType; + +/* + * Mime type list + */ +typedef struct { + char_t *type; /* Mime type */ + char_t *ext; /* File extension */ +} websMimeType; + +/* + * File information structure. + */ +typedef struct { + unsigned long size; /* File length */ + int isDir; /* Set if directory */ + time_t mtime; /* Modified time */ +} websStatType; + +/* + * Compiled Rom Page Index + */ +typedef struct { + char_t *path; /* Web page URL path */ + unsigned char *page; /* Web page data */ + int size; /* Size of web page in bytes */ + int pos; /* Current read position */ +} websRomPageIndexType; + +/* + * Defines for file open. + */ +#ifndef CE +#define SOCKET_RDONLY O_RDONLY +#define SOCKET_BINARY O_BINARY +#else /* CE */ +#define SOCKET_RDONLY 0x1 +#define SOCKET_BINARY 0x2 +#endif /* CE */ + +extern websRomPageIndexType websRomPageIndex[]; +extern websMimeType websMimeList[]; /* List of mime types */ +extern sym_fd_t websMime; /* Set of mime types */ +extern webs_t* webs; /* Session list head */ +extern int websMax; /* List size */ +extern char_t websHost[64]; /* Name of this host */ +extern char_t websIpaddr[64]; /* IP address of this host */ +extern char_t *websHostUrl; /* URL for this host */ +extern int websPort; /* Port number */ + +/******************************** Prototypes **********************************/ + +extern int websAspOpen(); +extern void websAspClose(); +extern void websFormOpen(); +extern void websFormClose(); +extern int websAspWrite(int ejid, webs_t wp, int argc, char_t **argv); +extern void websDefaultClose(); +extern int websDefaultHandler(webs_t wp, char_t *urlPrefix, + char_t *webDir, int arg, char_t *url, char_t *path, + char_t *query); +extern int websFormHandler(webs_t wp, char_t *urlPrefix, char_t *webDir, + int arg, char_t *url, char_t *path, char_t *query); +extern int websOpen(int sid); +extern void websResponse(webs_t wp, int code, char_t *msg, + char_t *redirect); +extern int websJavaScriptEval(webs_t wp, char_t *script); +extern int websPageReadData(webs_t wp, char *buf, int nBytes); +extern int websPageOpen(webs_t wp, char_t *lpath, char_t *path, int mode, int perm); +extern void websPageClose(webs_t wp); +extern void websPageSeek(webs_t wp, long offset); +extern int websPageStat(webs_t wp, char_t *lpath, char_t *path, + websStatType *sbuf); +extern int websPageIsDirectory(char_t *lpath); +extern int websRomOpen(); +extern void websRomClose(); +extern int websRomPageOpen(webs_t wp, char_t *path, int mode, int perm); +extern void websRomPageClose(int fd); +extern int websRomPageReadData(webs_t wp, char *buf, int len); +extern int websRomPageStat(char_t *path, websStatType *sbuf); +extern long websRomPageSeek(webs_t wp, long offset, int origin); +extern void websSetRequestSocketHandler(webs_t wp, int mask, + void (*fn)(webs_t wp)); +extern int websSolutionHandler(webs_t wp, char_t *urlPrefix, + char_t *webDir, int arg, char_t *url, char_t *path, + char_t *query); +extern void websUrlHandlerClose(); +extern int websUrlHandlerOpen(); +extern int websOpenServer(int port, int retries); +extern void websCloseServer(); +extern char_t* websGetDateString(websStatType* sbuf); + +/* + * Prototypes for functions available when running as part of the + * GoAhead Embedded Management Framework (EMF) + */ +#if EMF +extern int websEmfOpen(); +extern void websEmfClose(); +extern void websSetEmfEnvironment(webs_t wp); +#endif + +#endif /* _h_WEBS_INTERNAL */ + +/******************************************************************************/ -- cgit v1.2.3