From f7a9549bd6ad83f2e0bceec9cddacfa5e3f84a54 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Mon, 1 May 2006 17:11:04 +0000 Subject: libbu++ is finally laid out the way it should be, trunk, branches, and tags. --- src/arraylist.cpp | 100 +++++ src/arraylist.h | 80 ++++ src/cgi.cpp | 644 +++++++++++++++++++++++++++++ src/cgi.h | 196 +++++++++ src/connection.cpp | 432 +++++++++++++++++++ src/connection.h | 387 +++++++++++++++++ src/connectionmanager.cpp | 343 +++++++++++++++ src/connectionmanager.h | 138 +++++++ src/connectionmonitor.cpp | 23 ++ src/connectionmonitor.h | 41 ++ src/flexbuf.cpp | 206 +++++++++ src/flexbuf.h | 160 +++++++ src/hashfunction.cpp | 10 + src/hashfunction.h | 48 +++ src/hashfunctioncasestring.cpp | 39 ++ src/hashfunctioncasestring.h | 28 ++ src/hashfunctionint.cpp | 20 + src/hashfunctionint.h | 26 ++ src/hashfunctionstring.cpp | 36 ++ src/hashfunctionstring.h | 27 ++ src/hashtable.cpp | 345 ++++++++++++++++ src/hashtable.h | 299 ++++++++++++++ src/http.cpp | 371 +++++++++++++++++ src/http.h | 271 ++++++++++++ src/linkedlist.cpp | 227 ++++++++++ src/linkedlist.h | 87 ++++ src/linkmessage.cpp | 53 +++ src/linkmessage.h | 39 ++ src/list.cpp | 27 ++ src/list.h | 101 +++++ src/md5.cpp | 190 +++++++++ src/md5.h | 81 ++++ src/multilog.cpp | 143 +++++++ src/multilog.h | 145 +++++++ src/multilogchannel.cpp | 13 + src/multilogchannel.h | 46 +++ src/multilogtext.cpp | 152 +++++++ src/multilogtext.h | 70 ++++ src/pproc.cpp | 60 +++ src/pproc.h | 35 ++ src/pqueue.cpp | 33 ++ src/pqueue.h | 48 +++ src/programchain.cpp | 113 +++++ src/programchain.h | 88 ++++ src/programlink.cpp | 71 ++++ src/programlink.h | 99 +++++ src/protocol.cpp | 31 ++ src/protocol.h | 58 +++ src/protocoltelnet.cpp | 315 ++++++++++++++ src/protocoltelnet.h | 77 ++++ src/queue.cpp | 26 ++ src/queue.h | 45 ++ src/ringlist.cpp | 106 +++++ src/ringlist.h | 112 +++++ src/stack.cpp | 33 ++ src/stack.h | 50 +++ src/test/hashtest.cpp | 107 +++++ src/test/httpsrv/httpconnectionmonitor.cpp | 72 ++++ src/test/httpsrv/httpconnectionmonitor.h | 16 + src/test/httpsrv/main.cpp | 21 + src/test/md5test.cpp | 19 + src/test/teltest/main.cpp | 21 + src/test/teltest/telnetmonitor.cpp | 53 +++ src/test/teltest/telnetmonitor.h | 26 ++ src/test/xmlreadtest.cpp | 29 ++ src/test/xmlrepltest.cpp | 31 ++ src/test/xmlwritetest.cpp | 41 ++ src/tokenstring.cpp | 172 ++++++++ src/tokenstring.h | 120 ++++++ src/xmldocument.cpp | 142 +++++++ src/xmldocument.h | 163 ++++++++ src/xmlfilereader.cpp | 63 +++ src/xmlfilereader.h | 47 +++ src/xmlfilewriter.cpp | 22 + src/xmlfilewriter.h | 44 ++ src/xmlnode.cpp | 454 ++++++++++++++++++++ src/xmlnode.h | 236 +++++++++++ src/xmlreader.cpp | 412 ++++++++++++++++++ src/xmlreader.h | 133 ++++++ src/xmlstringreader.cpp | 37 ++ src/xmlstringreader.h | 49 +++ src/xmlstringwriter.cpp | 23 ++ src/xmlstringwriter.h | 50 +++ src/xmlwriter.cpp | 173 ++++++++ src/xmlwriter.h | 96 +++++ 85 files changed, 9916 insertions(+) create mode 100644 src/arraylist.cpp create mode 100644 src/arraylist.h create mode 100644 src/cgi.cpp create mode 100644 src/cgi.h create mode 100644 src/connection.cpp create mode 100644 src/connection.h create mode 100644 src/connectionmanager.cpp create mode 100644 src/connectionmanager.h create mode 100644 src/connectionmonitor.cpp create mode 100644 src/connectionmonitor.h create mode 100644 src/flexbuf.cpp create mode 100644 src/flexbuf.h create mode 100644 src/hashfunction.cpp create mode 100644 src/hashfunction.h create mode 100644 src/hashfunctioncasestring.cpp create mode 100644 src/hashfunctioncasestring.h create mode 100644 src/hashfunctionint.cpp create mode 100644 src/hashfunctionint.h create mode 100644 src/hashfunctionstring.cpp create mode 100644 src/hashfunctionstring.h create mode 100644 src/hashtable.cpp create mode 100644 src/hashtable.h create mode 100644 src/http.cpp create mode 100644 src/http.h create mode 100644 src/linkedlist.cpp create mode 100644 src/linkedlist.h create mode 100644 src/linkmessage.cpp create mode 100644 src/linkmessage.h create mode 100644 src/list.cpp create mode 100644 src/list.h create mode 100644 src/md5.cpp create mode 100644 src/md5.h create mode 100644 src/multilog.cpp create mode 100644 src/multilog.h create mode 100644 src/multilogchannel.cpp create mode 100644 src/multilogchannel.h create mode 100644 src/multilogtext.cpp create mode 100644 src/multilogtext.h create mode 100644 src/pproc.cpp create mode 100644 src/pproc.h create mode 100644 src/pqueue.cpp create mode 100644 src/pqueue.h create mode 100644 src/programchain.cpp create mode 100644 src/programchain.h create mode 100644 src/programlink.cpp create mode 100644 src/programlink.h create mode 100644 src/protocol.cpp create mode 100644 src/protocol.h create mode 100644 src/protocoltelnet.cpp create mode 100644 src/protocoltelnet.h create mode 100644 src/queue.cpp create mode 100644 src/queue.h create mode 100644 src/ringlist.cpp create mode 100644 src/ringlist.h create mode 100644 src/stack.cpp create mode 100644 src/stack.h create mode 100644 src/test/hashtest.cpp create mode 100644 src/test/httpsrv/httpconnectionmonitor.cpp create mode 100644 src/test/httpsrv/httpconnectionmonitor.h create mode 100644 src/test/httpsrv/main.cpp create mode 100644 src/test/md5test.cpp create mode 100644 src/test/teltest/main.cpp create mode 100644 src/test/teltest/telnetmonitor.cpp create mode 100644 src/test/teltest/telnetmonitor.h create mode 100644 src/test/xmlreadtest.cpp create mode 100644 src/test/xmlrepltest.cpp create mode 100644 src/test/xmlwritetest.cpp create mode 100644 src/tokenstring.cpp create mode 100644 src/tokenstring.h create mode 100644 src/xmldocument.cpp create mode 100644 src/xmldocument.h create mode 100644 src/xmlfilereader.cpp create mode 100644 src/xmlfilereader.h create mode 100644 src/xmlfilewriter.cpp create mode 100644 src/xmlfilewriter.h create mode 100644 src/xmlnode.cpp create mode 100644 src/xmlnode.h create mode 100644 src/xmlreader.cpp create mode 100644 src/xmlreader.h create mode 100644 src/xmlstringreader.cpp create mode 100644 src/xmlstringreader.h create mode 100644 src/xmlstringwriter.cpp create mode 100644 src/xmlstringwriter.h create mode 100644 src/xmlwriter.cpp create mode 100644 src/xmlwriter.h (limited to 'src') diff --git a/src/arraylist.cpp b/src/arraylist.cpp new file mode 100644 index 0000000..ef21426 --- /dev/null +++ b/src/arraylist.cpp @@ -0,0 +1,100 @@ +#include "arraylist.h" +#include +#include + +ArrayList::ArrayList( int initSize, int growByFactor ) +{ + apData = new void *[initSize]; + nSize = 0; + nCapacity = initSize; + nGrowByFactor = growByFactor; +} + +ArrayList::~ArrayList( ) +{ + delete[] apData; +} + +void *ArrayList::getAt( int index ) +{ + if( index < 0 || index > nSize ) + return NULL; + + return apData[index]; +} + +void ArrayList::append( void *data ) +{ + insertBefore( data, nSize ); +} + +void ArrayList::insertBefore( void *data, int pos ) +{ + if( pos < 0 || pos > nSize ) + return; + + checkResize(); + memmove( &apData[pos+1], &apData[pos], (nSize-pos)*sizeof(void*) ); + apData[pos] = data; + nSize++; +} + +int ArrayList::getSize( ) +{ + return nSize; +} + +bool ArrayList::isEmpty( ) +{ + return nSize==0; +} + +void ArrayList::deleteAt( int index ) +{ + if( index < 0 || index >= nSize ) + return; + + memmove( &apData[index], &apData[index+1], (nSize-index-1)*sizeof(void *) ); + nSize--; +} + +void ArrayList::empty() +{ + // Probably the easiest as far as things go. + nSize = 0; +} + +void ArrayList::resizeTo( int newSize ) +{ + void **apNew = new void *[newSize]; + memmove( apNew, apData, nSize*sizeof(void *) ); + nCapacity = newSize; + delete[] apData; + apData = apNew; +} + +void ArrayList::checkResize() +{ + if( nSize >= nCapacity ) + { + resizeTo( nCapacity + nGrowByFactor ); + } +} + +void ArrayList::setSize( int newSize ) +{ + if( newSize < 0 ) + return; + + nSize = newSize; + checkResize(); +} + +void ArrayList::setAt( int index, void *data ) +{ + if( index < 0 || index >= nSize ) + return; + + apData[index] = data; +} + diff --git a/src/arraylist.h b/src/arraylist.h new file mode 100644 index 0000000..74992cf --- /dev/null +++ b/src/arraylist.h @@ -0,0 +1,80 @@ +/** \file arraylist.h + * Describes the ArrayList class. + *@author Mike Buland + */ +#ifndef ARRAY_LIST_H +#define ARRAY_LIST_H + +#include "list.h" + +/** A simple list which uses an array. This is a great choice if you won't do + * a lot of adding and deleting and need a fast random access list. Otherwise + * use the LinkedList. + *@author Mike Buland + */ +class ArrayList : public List +{ +public: + /** Creates an arraylist with some pre-defined specs spelled out. + *@param initSize the inital number of elements to allocate. + *@param growByFactor How much to increase the size of the array by + * each time we run out of room. + */ + ArrayList( int initSize=100, int growByFactor=10 ); + /** + * Destroy the ArrayList + */ + ~ArrayList(); + + void *getAt( int nIndex ); + void append( void *pData ); + void insertBefore( void *pData, int nPos = 0 ); + int getSize( ); + bool isEmpty( ); + void deleteAt( int nIndex ); + void empty(); + void setSize( int nNewSize ); + void setAt( int nIndex, void *pData ); + +private: + /** + * Checks to see if the system needs to be resized, if it does, this will + * automatically resize based on your parameters. + */ + void checkResize(); + + /** + * Resize the system to a specified size. If it is larger, then all data + * will be retained, if smaller the elements at the end will be cut off. + *@param newSize The number of elements to include after resizing. + */ + void resizeTo( int newSize ); + + /** + * Actual master array of pointers. This is done to follow the List specs. + * All data transactions are performed with pointers or compatable + * primitive data-types. + */ + void **apData; + + /** + * The number of filled in elements in the array. This is the practical + * real size of the ArrayList for all userspace applications. + */ + int nSize; + + /** + * The number of elements allocated in memory. Not all of these have to be + * filled in, and it is usually larger than nSize so that adding and + * deleting elements is fast and easy. + */ + int nCapacity; + + /** + * The amount to grow by whenever the array needs resizing. + */ + int nGrowByFactor; +}; + +#endif + diff --git a/src/cgi.cpp b/src/cgi.cpp new file mode 100644 index 0000000..1fecbbe --- /dev/null +++ b/src/cgi.cpp @@ -0,0 +1,644 @@ +#include +#include +#include +#include +#include + +#include "cgi.h" + +Cgi::Cgi( const char *strSource ) : + aContent( new HashFunctionString(), 151, true ) +{ + int length, j, k, mode = 0, slen = 0; + char hexbuf[3] = { 0, 0, 0 }; + char *buf, chr; + Item *cur = NULL; + int nCur = 0; + + if( strSource != NULL ) + { + loadContent( strSource ); + } + + if( ( getenv( "CONTENT_LENGTH" ) ) ) + { + if( !strcmp + ( getenv( "CONTENT_TYPE" ), + "application/x-www-form-urlencoded" ) ) + { + length = atoi( getenv( "CONTENT_LENGTH" ) ); + buf = new char[length + 1]; + fread( buf, 1, length, stdin ); + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_STDINPUT; + for( j = 0; j < length; j++ ) + { + switch ( buf[j] ) + { + case '=': + cur->name = new char[slen + 1]; + slen = 0; + break; + + case '&': + cur->value = new char[slen + 1]; + cur->len = slen; + slen = 0; + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_STDINPUT; + break; + + default: + switch ( buf[j] ) + { + case '%': /* per-cents mean a hex-code for an ASCII char */ + j += 2; + slen++; + break; + + default: /* Nothing special, move along, folks... */ + slen++; + break; + } + break; + } + } + cur->value = new char[slen + 1]; + cur->len = slen; + slen = 0; + mode = 0; + cur = ( Item * ) aVars.getAt( 0 ); + k = 0; + nCur = 0; + for( j = 0; j < length; j++ ) + { + switch ( buf[j] ) + { + case '=': + mode = 1; + k = 0; + break; + + case '&': + mode = 0; + k = 0; + nCur++; + cur = ( Item * ) aVars.getAt( nCur ); + break; + + default: + switch ( buf[j] ) + { + case '%': /* per-cents mean a hex-code for an ASCII char */ + hexbuf[0] = buf[++j]; + hexbuf[1] = buf[++j]; + chr = ( char ) ( strtol( hexbuf, NULL, 16 ) ); + break; + + case '+': /* Pluses mean spaces, odd, I know... */ + chr = ' '; + break; + + default: /* Nothing special, move along, folks... */ + chr = buf[j]; + break; + } + if( mode == 0 ) + { + cur->name[k] = chr; + cur->name[++k] = '\0'; + } + else + { + cur->value[k] = chr; + cur->value[++k] = '\0'; + } + break; + } + } + delete buf; + } + else if( !strncmp + ( getenv( "CONTENT_TYPE" ), "multipart/form-data;", 20 ) ) + { + char *boundary, *oname; + int blen, j, k, olen; + + length = atoi( getenv( "CONTENT_LENGTH" ) ); + buf = new char[length + 1]; + fread( buf, 1, length, stdin ); + for( blen = 0; buf[blen + 1] != '\n'; blen++ ); + boundary = new char[blen + 1]; + memcpy( boundary, buf, blen ); + boundary[blen] = '\0'; + j = blen + 2; + for( ;; ) + { + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_STDINPUT; + if( !strncmp + ( buf + j, "Content-Disposition: form-data; name=\"", + 38 ) ) + { + j += 38; + for( k = 0; buf[j + k] != '\"'; k++ ); + oname = cur->name = new char[k + 1]; + memcpy( cur->name, buf + j, k ); + olen = k; + cur->name[k] = '\0'; + j += k + 1; + if( !strncmp( buf + j, "; filename=\"", 12 ) ) /* Must be a file */ + { + /* Acquire file name */ + j += 12; + for( k = 0; buf[j + k] != '\"'; k++ ); + cur->value = new char[k + 1]; + memcpy( cur->value, buf + j, k ); + cur->value[k] = '\0'; + cur->len = k; + j += k + 3; + + /* Acquire content type */ + if( !strncmp( "Content-Type: ", buf + j, 14 ) ) + { + j += 14; + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_STDINPUT; + cur->name = new char[olen + 1]; + memcpy( cur->name, oname, olen + 1 ); + for( k = 0; buf[j + k + 1] != '\n'; k++ ); + cur->value = new char[k + 1]; + memcpy( cur->value, buf + j, k ); + cur->value[k] = '\0'; + cur->len = k; + j += k; + } + else + { + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_STDINPUT; + cur->name = new char[olen + 1]; + memcpy( cur->name, oname, olen + 1 ); + cur->value = new char[1]; + cur->value[0] = '\0'; + cur->len = 0; + } + j += 4; + + /* Acquire content */ + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_STDINPUT; + cur->name = new char[olen + 1]; + memcpy( cur->name, oname, olen + 1 ); + if( !strncmp( buf + j + k, boundary, blen ) ) + { + cur->value = new char[1]; + cur->value[0] = '\0'; + j += blen + 4; + } + else if( !strncmp( buf + j + k + 1, boundary, blen ) ) + { + cur->value = new char[1]; + cur->value[0] = '\0'; + j += blen + 5; + } + else + { + for( k = 0; + strncmp( buf + j + k + 2, boundary, blen ); + k++ ); + cur->value = new char[k + 1]; + memcpy( cur->value, buf + j, k ); + cur->value[k] = '\0'; + cur->len = k; + j += k + blen + 4; + } + } + else + { + j += 4; + for( k = 0; + strncmp( buf + j + k + 2, boundary, blen ); + k++ ); + cur->value = new char[k + 1]; + memcpy( cur->value, buf + j, k ); + cur->value[k] = '\0'; + cur->len = k; + j += k + blen + 4; + } + if( buf[j + 1] == '\n' ) + j += 2; + if( j >= length ) + break; + } + else + { + cur->name = ( char * ) "ERROR"; + cur->value = ( char * ) "Error here"; + } + } + } + delete buf; + } + + if( ( buf = getenv( "HTTP_COOKIE" ) ) ) + { + int lbase = aVars.getSize( ); + length = strlen( buf ); + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_COOKIE; + for( j = 0; j < length; j++ ) + { + switch ( buf[j] ) + { + case '=': + cur->name = new char[slen + 1]; + slen = 0; + break; + + case ';': + cur->value = new char[slen + 1]; + cur->len = slen; + slen = 0; + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_COOKIE; + break; + + default: + switch ( buf[j] ) + { + case '%': /* per-cents mean a hex-code for an ASCII char */ + j += 2; + slen++; + break; + + default: /* Nothing special, move along, folks... */ + slen++; + break; + } + break; + } + } + cur->value = new char[slen + 1]; + cur->len = slen; + slen = 0; + cur = ( Item * ) aVars.getAt( lbase ); + mode = 0; + k = 0; + nCur = lbase; + for( j = 0; j < length; j++ ) + { + switch ( buf[j] ) + { + case '=': + mode = 1; + k = 0; + break; + + case ';': + mode = 0; + k = 0; + nCur++; + cur = ( Item * ) aVars.getAt( nCur ); + break; + + default: + switch ( buf[j] ) + { + case '%': /* per-cents mean a hex-code for an ASCII char */ + hexbuf[0] = buf[++j]; + hexbuf[1] = buf[++j]; + chr = ( char ) ( strtol( hexbuf, NULL, 16 ) ); + break; + + case '+': /* Pluses mean spaces, odd, I know... */ + chr = ' '; + break; + + case ' ': + continue; + break; + + default: /* Nothing special, move along, folks... */ + chr = buf[j]; + break; + } + if( mode == 0 ) + { + cur->name[k] = chr; + cur->name[++k] = '\0'; + } + else + { + cur->value[k] = chr; + cur->value[++k] = '\0'; + } + break; + } + } + } + + if( ( buf = getenv( "QUERY_STRING" ) ) ) + { + if( strlen( buf ) > 0 ) + { + int lbase = aVars.getSize( ); + length = strlen( buf ); + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_CMDLINE; + for( j = 0; j < length; j++ ) + { + switch ( buf[j] ) + { + case '=': + cur->name = new char[slen + 1]; + slen = 0; + break; + + case '&': + cur->value = new char[slen + 1]; + cur->len = slen; + slen = 0; + cur = new Item( ); + aVars.append( cur ); + cur->type = VAR_CMDLINE; + break; + + default: + switch ( buf[j] ) + { + case '%': /* per-cents mean a hex-code for an ASCII char */ + j += 2; + slen++; + break; + + default: /* Nothing special, move along, folks... */ + slen++; + break; + } + break; + } + } + cur->value = new char[slen + 1]; + cur->len = slen; + slen = 0; + cur = ( Item * ) aVars.getAt( lbase ); + nCur = lbase; + mode = 0; + k = 0; + for( j = 0; j < length; j++ ) + { + switch ( buf[j] ) + { + case '=': + mode = 1; + k = 0; + break; + + case '&': + mode = 0; + k = 0; + nCur++; + cur = ( Item * ) aVars.getAt( nCur ); + break; + + default: + switch ( buf[j] ) + { + case '%': /* per-cents mean a hex-code for an ASCII char */ + hexbuf[0] = buf[++j]; + hexbuf[1] = buf[++j]; + chr = ( char ) ( strtol( hexbuf, NULL, 16 ) ); + break; + + case '+': /* Pluses mean spaces, odd, I know... */ + chr = ' '; + break; + + default: /* Nothing special, move along, folks... */ + chr = buf[j]; + break; + } + if( mode == 0 ) + { + cur->name[k] = chr; + cur->name[++k] = '\0'; + } + else + { + cur->value[k] = chr; + cur->value[++k] = '\0'; + } + break; + } + } + } + } +} + +Cgi::~Cgi( ) +{ +} + +char *Cgi::getVarValue( const char *name, int skip, unsigned char type ) +{ + for( int j = 0; j < aVars.getSize( ); j++ ) + { + Item *cur = ( Item * ) aVars.getAt( j ); + if( !strcmp( cur->name, name ) ) + { + if( ( cur->type & type ) ) + { + if( skip <= 0 ) + { + return cur->value; + } + else + { + skip--; + } + } + } + } + return NULL; +} + +int Cgi::getVarLength( const char *name, int skip, unsigned char type ) +{ + for( int j = 0; j < aVars.getSize( ); j++ ) + { + Item *cur = ( Item * ) aVars.getAt( j ); + if( !strcmp( cur->name, name ) ) + { + if( ( cur->type & type ) ) + { + if( skip <= 0 ) + { + return cur->len; + } + else + { + skip--; + } + } + } + } + return -1; +} + +void Cgi::writeDebugInfo() +{ + printf( "
\n" );
+    printf( "0x%02X - stdInput | 0x%02X - cookie | 0x%02X - cmdLine\n\n",
+             VAR_STDINPUT, VAR_COOKIE, VAR_CMDLINE );
+    for( int j = 0; j < aVars.getSize(  ); j++ )
+    {
+        Item *item = ( Item * ) aVars.getAt( j );
+        printf("[%s] = \"%s\" [0x%02X]\n", item->name,
+                 item->value, item->type );
+    }
+    printf( "
\n" ); +} + +void Cgi::writeContentHeader( int type ) +{ + switch( type ) + { + case headerHTML: + printf("Content-type: text/html\n\n"); + break; + } +} + +void Cgi::writeContent( const char *name, ...) +{ + char *templ = (char *)aContent.get(name); + + if( templ ) + { + va_list ap; + + va_start (ap, name); + vprintf (templ, ap); + va_end (ap); + } + else + { + printf("Error finding content labeled \"%s\"\n", name ); + } +} + +void Cgi::loadContent( const char *strSource ) +{ + FILE *fh = NULL; + if( strSource == NULL ) + { + extern char *program_invocation_short_name; + char *tmpName = new char[strlen(program_invocation_short_name)+10]; + memset( tmpName, 0, strlen(program_invocation_short_name)+10 ); + strcpy( tmpName, program_invocation_short_name ); + strcat( tmpName, ".content" ); + fh = fopen( tmpName, "rt" ); + delete tmpName; + } + else + { + fh = fopen( strSource, "rt" ); + } + + if( fh == NULL ) return; + + struct stat xStats; + + fstat( fileno( fh ), &xStats ); + + char *bigBuf = new char[xStats.st_size+1]; + memset( bigBuf, 0, xStats.st_size+1 ); + fread( bigBuf, 1, xStats.st_size, fh ); + fclose( fh ); + + // Now we can actually load stuff from the file, first we need to make us up a format... + int lSize=0; + struct Content + { + char *name; + char *value; + } xCont; + int j = 0; + while( j < xStats.st_size ) + { + // We're looking for a content-block init statement + for( ; j < xStats.st_size; j++ ) + { + if( bigBuf[j] == '#' ) + { + if( bigBuf[j+1] == '{' ) + { + break; + } + } + } + j=j+2; + if( j >= xStats.st_size ) break; + for( ; bigBuf[j] == ' ' || bigBuf[j] == '\t'; j++ ); + for( lSize = 0; lSize+j < xStats.st_size && bigBuf[lSize+j] != '\n' && bigBuf[lSize+j] != '\r'; lSize++ ); + xCont.name = new char[lSize+1]; + memset( xCont.name, 0, lSize+1 ); + memcpy( xCont.name, &bigBuf[j], lSize ); + j += lSize+1; + + for( lSize = 0; lSize+j < xStats.st_size; lSize++ ) + { + if( bigBuf[lSize+j] == '#' ) + { + if( bigBuf[lSize+j+1] == '}' ) + { + break; + } + } + } + xCont.value = new char[lSize+1]; + memset( xCont.value, 0, lSize+1 ); + memcpy( xCont.value, &bigBuf[j], lSize ); + + aContent.insert( xCont.name, xCont.value ); + + j += lSize + 2; + } +} + +void Cgi::writeCookie( char const *name, char const *value, char const *expires, char const *path, char const *domain, bool secure ) +{ + printf("Set-Cookie: %s=%s", name, value ); + + if( expires != NULL ) + { + printf("; expires=%s", expires ); + } + + if( path != NULL ) + { + printf("; path=%s", path ); + } + + if( domain != NULL ) + { + printf("; domain=%s", domain ); + } + + if( secure ) + { + printf("; secure"); + } + + printf("\n"); +} diff --git a/src/cgi.h b/src/cgi.h new file mode 100644 index 0000000..8e9a584 --- /dev/null +++ b/src/cgi.h @@ -0,0 +1,196 @@ +/**\file cgi.h + * Describes extra params needed to use the Cgi class as well as the class + * itself. + *@author Mike Buland + */ + +#include "linkedlist.h" +#include "hashtable.h" +#include "hashfunctionstring.h" + +#define VAR_STDINPUT 0x01 /**< Variable came from stdinput, web form */ +#define VAR_COOKIE 0x02 /**< Variable came from a cookie */ +#define VAR_CMDLINE 0x04 /**< Variable came from commandline / uri */ +#define VAR_ANY 0xFF /**< Mask including all other types */ + +/** + * Cgi header processor originally designed for apache cgi programs. When used + * from apache with what I beleive are some sort of standard set of command + * line parameters and environment variables. This always worked for all of my + * purposes. This class will automatically extract all data from the system + * that you need and places it into tables and things for easy access. + * There are three types of input that data can come from, StandardInput, + * CommandLine, and Cookies. StandardInput is when you get formdata in + * multi-part forms, Cookies should usually be cookies that you set, and + * command line is everything after the question mark in the URL. + * This also contains some simple helpers for putting templated data into the + * HTTP data feed. + *@author Mike Buland + */ +class Cgi +{ +public: + /** + * Create a complete CGI object, this object will automatically read data + * from all available sources and be ready for use on the very next line! + * If strSource is filled in it will also automatically read in a content + * file, which is a simple file format containing named blocks of reusable + * templates. + *@param strSource Set to a filename in order to load up a content file. + */ + Cgi( const char *strSource = NULL ); + + /** + * Destroy the cgi object. + */ + ~Cgi( ); + + /** + * Get's the value for a variable as a character string. The name is the + * name that was given on the URL or in the form or cookie. Skip can be + * set to any value above zero to retreive subsequent variables with the + * same name. The most obvious use of this is when dealing with file + * uploads, each file upload sends you three variables with the same name + * and different content. Finally the variable type determines where you + * will accept this variable from. This is generally a bit of a security + * thing, if you store login info in a cookie and don't want people getting + * in by faking the appropriate URL. + *@param name The name of the variable you wish to retreive. + *@param skip THe number of variables with the given name to skip before + * returning something meaningful. The only way to determine how many + * variables with the same name there are is to skip until you get a NULL + * value returned. + *@param type Can be set to any combination of VAR_STDINPUT, VAR_COOKIE, + * VAR_CMDLINE, or just VAR_ANY. This takes bitflags, so you can or the + * values together. If a variable is found but came from the wrong source + * it won't match any other criteria and will be treated as though it + * doesn't exist. + *@returns A null-terminated string representing the value of the requested + * variable, or NULL if the variable did not exist. If a variable does + * exist but has no value the string returned will start with a NULL char, + * but be a valid string. + */ + char *getVarValue( const char *name, int skip=0, unsigned char type=VAR_ANY ); + + /** + * This functions identically in every way to getVarValue, except that + * instead of returning a pointer to the variable's value, it returns the + * length of the variable's value string. The params are the same and so + * a call to both functions with the same params should yeild a value and + * a corresponding length. + *@param name The name of the variable you wish to retreive. + *@param skip THe number of variables with the given name to skip before + * returning something meaningful. The only way to determine how many + * variables with the same name there are is to skip until you get a NULL + * value returned. + *@param type Can be set to any combination of VAR_STDINPUT, VAR_COOKIE, + * VAR_CMDLINE, or just VAR_ANY. This takes bitflags, so you can or the + * values together. If a variable is found but came from the wrong source + * it won't match any other criteria and will be treated as though it + * doesn't exist. + *@returns The length of the value-string of the requested variable. If + * the requested variable is not found, -1 is returned. + */ + int getVarLength( const char *name, int skip=0, unsigned char type=VAR_ANY ); + + /** + * A handy little function that writes a load of debug info related to + * parsing CGI params to the standard output in html. This is generally + * best used at the end of a page. + */ + void writeDebugInfo(); + + /** + * Write a content header to the standard output. This should also be the + * first thing that you do (except for writing cookies) after initializing + * the Cgi class. You can select a type of header or content from the + * header enum, and a properly formatted header will show up on the + * standard output. + *@param type Any value from the header enum in this class. The default is + * to write an html header, probably the most common as well. + */ + void writeContentHeader( int type=headerHTML ); + + /** + * Write content to the stnadard output. The content variable should have + * been loaded during construction of the Cgi object or with the + * loadContent function. The content variable should be formatted just like + * a printf string, so that anything you want to put into it will have a % + * symbol replacement code, like %s, %d, etc. Since this actually uses a + * type of printf function everything from those docs work here. + *@param name The name of the content variable to format and write to + * stnadard output. + *@param ... As many params as you want to include, ala printf. + */ + void writeContent( const char *name, ...); + + /** + * Load a content file. I don't want to describe the format here, you can + * just read the code or find an example for now. Sorry. + *@param strSource The name of the file to open and read in to get the + * content loaded. + */ + void loadContent( const char *strSource = NULL ); + + /** + * Write a cookie-set header to the output stream. This should be done + * before any other content-headers are written. The specifics of this + * function are very simple, since I rely on the user's understanding of + * how standard HTTP/1.1 or HTTP/1.0 cookie syntax works. If you don't + * care then just use the name and value and the defaults should keep you + * in good stead for a long time. + *@param name The name of the cookie variable to set. + *@param value The value to set to that variable. + *@param expires The formatted string value for the date and time this + * cookie should expire. A NULL here will put a "until the browser closes" + * tag in. + *@param path The path (URL) that this cookie belongs to. If you run a lot + * of hosted servers or sub-sites that may have some shared URL bits then + * you may want to set this. The cookie should only be sent to URL's that + * match this as their first part. + *@param domain The domain that is allowed to read this, if not set, it's + * the domain the web browser contacted when they got the cookie. + *@param secure I'm not sure, I think it's something to tell if the cookie + * is safe to keep because any potentially valuable data is encypted or + * otherwise unusable. I could be wrong. + */ + void writeCookie( char const *name, char const *value, char const *expires=NULL, char const *path=NULL, char const *domain=NULL, bool secure=false ); + + /** + * A simple helper class to contain variable data. + */ + class Item + { + public: + /** + * Build an empty Item. + */ + Item( ) + { + name = NULL; + value = NULL; + len = 0; + type = 0; + } + /** The name of the item. */ + char *name; + /** The value of the item. */ + char *value; + /** The length of the item's value. */ + unsigned long len; + /** The type of the item (where it came from). */ + unsigned char type; + }; + + /** Header values */ + enum + { + headerHTML + }; + +private: + /** Keeps track of all contained variables. */ + LinkedList aVars; + /** Keeps track of all content variables. */ + HashTable aContent; +}; diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..a277ea7 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,432 @@ +#include "connection.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Connection::Connection() +{ + nSocket = -1; + bActive = false; + bDisconnectMe = false; + pProtocol = NULL; +} + +Connection::~Connection() +{ + if( pProtocol != NULL ) delete pProtocol; +} + +bool Connection::appendOutput( const char *lpOutput, int nSize ) +{ + return xOutputBuf.appendData( lpOutput, nSize ); +} + +bool Connection::appendOutput( const char lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const short lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const int lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const long lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const float lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const double lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const unsigned char lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const unsigned short lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const unsigned long lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendOutput( const unsigned int lOutput ) +{ + return xOutputBuf.appendData( lOutput ); +} + +bool Connection::appendInput( const char *lpInput, int nSize ) +{ + return xInputBuf.appendData( lpInput, nSize ); +} + +int Connection::scanInputFor( char cTarget ) +{ + const char *lpTmp = xInputBuf.getData(); + int jMax = xInputBuf.getLength(); + + for( int j = 0; j < jMax; j++ ) + { + if( lpTmp[j] == cTarget ) + { + return j; + } + } + + return -1; +} + +const char *Connection::getOutput() +{ + return xOutputBuf.getData(); +} + +const char *Connection::getInput() +{ + return xInputBuf.getData(); +} + +void Connection::setSocket( int nNewSocket ) +{ + nSocket = nNewSocket; +} + +int Connection::getSocket() +{ + return nSocket; +} + +bool Connection::isActive() +{ + return bActive; +} + +void Connection::close() +{ + if( bActive ) + { + fsync( nSocket ); + ::close( nSocket ); + } + bActive = false; + //nSocket = -1; + xInputBuf.clearData(); + xOutputBuf.clearData(); + if( pProtocol != NULL ) + { + delete pProtocol; + pProtocol = NULL; + } +} + +bool Connection::open( int nNewSocket ) +{ + bActive = true; + setSocket( nNewSocket ); + bDisconnectMe = false; + + return true; +} + +bool Connection::open( const char *sAddr, int nPort ) +{ + struct sockaddr_in xServerName; + bActive = false; + + /* Create the socket. */ + nSocket = socket( PF_INET, SOCK_STREAM, 0 ); + if( nSocket < 0 ) + { + bActive = false; + return false; + } + + /* Connect to the server. */ + { + struct hostent *hostinfo; + + xServerName.sin_family = AF_INET; + xServerName.sin_port = htons( nPort ); + hostinfo = gethostbyname( sAddr ); + if (hostinfo == NULL) + { + return false; + } + xServerName.sin_addr = *(struct in_addr *) hostinfo->h_addr; + } + + int ret = connect( + nSocket, + (struct sockaddr *)&xServerName, + sizeof(xServerName) + ); + + if( ret < 0 ) + { + return false; + } + + bActive = true; + bDisconnectMe = false; + + return true; +} + +bool Connection::readInput() +{ + char buffer[2048]; + int nbytes; + int nTotalRead=0; + + for(;;) + { + memset( buffer, 0, 2048 ); + + nbytes = read( nSocket, buffer, 2048 ); + if (nbytes < 0) + { + /* Read error. */ + //perror("readInput"); + return false; + } + else if (nbytes == 0) + { + /* End-of-file. */ + //perror("readInput"); + return false; + } + else + { + nTotalRead += nbytes; + appendInput( buffer, nbytes ); + /* Data read. */ + if( nbytes < 2047 ) + { + if( pProtocol != NULL && nTotalRead > 0 ) + { + pProtocol->onNewData(); + } + + return true; + } + } + } + + return true; +} + +bool Connection::readInput( int nSec, int nUSec ) +{ + fd_set rfds; + struct timeval tv; + int retval; + + /* Watch stdin (fd 0) to see when it has input. */ + FD_ZERO(&rfds); + FD_SET(nSocket, &rfds); + /* Wait up to five seconds. */ + tv.tv_sec = nSec; + tv.tv_usec = nUSec; + + retval = select( nSocket+1, &rfds, NULL, NULL, &tv ); + /* Don't rely on the value of tv now! */ + + if (retval == -1) + { + // Oh my god!!! some kind of horrible problem!!!! + return false; + } + else if( retval ) + { + // None of them have data, but the connection is still active. + return readInput(); + } + else + { + return true; + } +} + +bool Connection::clearOutput() +{ + return xOutputBuf.clearData(); +} + +bool Connection::clearInput() +{ + return xInputBuf.clearData(); +} + +#define min( a, b ) ((asetConnection( this ); +} + +int Connection::getInputAmnt() +{ + return xInputBuf.getLength(); +} + +int Connection::getOutputAmnt() +{ + return xOutputBuf.getLength(); +} + +class Protocol *Connection::getProtocol() +{ + return pProtocol; +} + +void Connection::printInputDebug( const char *lpPrefix, FILE *fh, int nBytesMax ) +{ + printDataDebug( + (const unsigned char *)xInputBuf.getData(), + xInputBuf.getLength(), + "input", + lpPrefix, + fh, + nBytesMax + ); +} + +void Connection::printOutputDebug( const char *lpPrefix, FILE *fh, int nBytesMax ) +{ + printDataDebug( + (const unsigned char *)xOutputBuf.getData(), + xOutputBuf.getLength(), + "output", + lpPrefix, + fh, + nBytesMax + ); +} + +void Connection::printDataDebug( const unsigned char *pData, long nDataLen, const char *lpName, const char *lpPrefix, FILE *fh, int nBytesMax ) +{ + if( nBytesMax > 0 ) + { + nDataLen = (nBytesMax32 && pData[j+k]<=128)?(pData[j+k]):('.') ); + } + fprintf( fh, "\n"); + j += kmax; + if( j >= nDataLen ) break; + } + fprintf( fh, lpPrefix ); + for( int l = 0; l < 8*3+2*8+2; l++ ) fprintf( fh, (l!=8*3)?("-"):("+") ); fprintf( fh, "\n"); +} + diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..efb8630 --- /dev/null +++ b/src/connection.h @@ -0,0 +1,387 @@ +/**\file + * Contains the Connection class. + *@author Mike Buland + */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "multilog.h" +#include "flexbuf.h" +#include "protocol.h" + +/** Represents a single connection on a network. While these connections + * may be treated more or less just like files, occasionally problems arise + * when writing data at any time you feel like. Therefore you run all your + * data through a Connection, which buffers all data and makes sure no + * buffers are exceeded and nothing inappropriate for the recipient of the + * data is sent. + *@author Mike Buland + */ +class Connection +{ +public: + /** + * Construct a blank and non-connected Connection. The created object is + * not yet connected to anything, and most of the functions except open are + * unusable. + */ + Connection(); + + /** + * Destroy the connection, clean up all pending data requests and close the + * contained socket. This does not send out pending data, especially since + * such an operation could take considerable time, depending on the pending + * data and state of the receiving end. + */ + ~Connection(); + + /** + * Open a connection to a remote server. This sets up this connection as + * a client instead of a server and does all of the work that needs to be + * done to actually open an INET_AF connection, which is a lot of work. + *@param sAddr The address to connect to. This can be in any format + * normally understood by your system to be an address, ip, domain name, + * etc. + *@param nPort The port number to connect to on the remote server. + *@returns True if the connection was successful and everything is setup, + * false if there were any of a dozen errors and the connection is not set. + *@todo Make this function add log entries to a standard MultiLog if + * something goes wrong. + */ + bool open( const char *sAddr, int nPort ); + + /** Append the given data to the output. The data is presumed to be null + * terminated. To put binary data into the stream, use the other + * appendOutput function. This should be the only method used to + * communicate with the socket. + *@param lpOutput The data to add to the output queue. + *@param nSize How much data is in the lpOutput buffer. If this value + * is -1 then the program treats lpOutput as a null-terminated string. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const char *lpOutput, int nSize=-1 ); + + /** + * Append the character to the output. + *@param lOutput The character to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const char lOutput ); + + /** + * Append the short to the output. + *@param lOutput The short to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const short lOutput ); + + /** + * Append the int to the output. + *@param lOutput The int to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const int lOutput ); + + /** + * Append the long to the output. + *@param lOutput The long to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const long lOutput ); + + /** + * Append the float to the output. + *@param lOutput The float to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const float lOutput ); + + /** + * Append the double to the output. + *@param lOutput The double to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const double lOutput ); + + /** + * Append the unsigned char to the output. + *@param lOutput The unsigned char to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const unsigned char lOutput ); + + /** + * Append the unsigned short to the output. + *@param lOutput The unsigned short to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const unsigned short lOutput ); + + /** + * Append the unsigned int to the output. + *@param lOutput The unsigned int to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const unsigned int lOutput ); + + /** + * Append the unsigned long to the output. + *@param lOutput The unsigned long to add to the output queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendOutput( const unsigned long lOutput ); + + /** + * Writes all input data in the buffer in a dual-view ascii and hex display + * to a file. There are a number of options that also help with debugging. + *@param lpPrefix Text to be added to the begining of every line written + * out. The default is a blank string. + *@param fh The file to write the data to in text mode. This is stdout by + * default, but could be any already open file handle. + *@param nBytesMax The maximum number of bytes to write to the output. The + * amount of data can be overwhelming sometimes, so you can limit it. The + * default value is -1, which is also unlimited. + */ + void printInputDebug( const char *lpPrefix="", FILE *fh=stdout, int nBytesMax=-1 ); + + /** + * Writes all output data in the buffer in a dual-view ascii and hex display + * to a file. There are a number of options that also help with debugging. + *@param lpPrefix Text to be added to the begining of every line written + * out. The default is a blank string. + *@param fh The file to write the data to in text mode. This is stdout by + * default, but could be any already open file handle. + *@param nBytesMax The maximum number of bytes to write to the output. The + * amount of data can be overwhelming sometimes, so you can limit it. The + * default value is -1, which is also unlimited. + */ + void printOutputDebug( const char *lpPrefix="", FILE *fh=stdout, int nBytesMax=-1 ); + + /** + * This is the low-level generic function that is called by both + * printInputDebug and printOutputDebug. It works effectively just like + * both of them, except that you can give it a raw pointer to the data to + * print out. This probably doesn't belong in this class, but this was + * where I was when I needed it. + *@param pData A pointer to the data to write. This is not treated as a + * null terminated string, so make sure that the nDataLen param is set + * properly. + *@param nDataLen The number of bytes that are in pData and that you want to + * see. + *@param lpName The name of the data, this is used in the header where it + * says "Displaying nnn bytes of ." A good example would be input + * or output. + *@param lpPrefix Text to put before every line output. This just makes it + * easier to tell large blocks apart in the output. + *@param fh The file handle to write all data to. + *@param nBytesMax The maximum number of bytes. This parameter is stupid. + * If it is set to -1, then nDataLen is used, otherwise the smaller value is + * used as the number of bytes to output. + *@todo Put this function somewhere more deserving. + *@todo Remove the nBytesMax param, we need that in the other functions, + * not this one! + */ + void printDataDebug( const unsigned char *pData, long nDataLen, const char *lpName, const char *lpPrefix, FILE *fh, int nBytesMax ); + + /** Append the given data to the input. The data is presumed to be null + * terminated. To put binary data into the stream, use the other + * appendInput function. This is mainly used by internal routines. + *@param lpInput The data to add to the input queue. + *@param nSize How much data is in the lpInput buffer. If this value + * is -1 then the program treats lpOutput as a null-terminated string. + *@returns True if everything is ok, false otherwise. + */ + bool appendInput( const char *lpInput, int nSize=-1 ); + + /** Searches through the current pending input for a certain character. + * This is useful for finding out where exactly the end of a line is, for + * example, to see if a command has been entered yet. + *@param cTarget The character to search for. + *@returns The position of the target relative to the begining of the input + * or -1 if the target wasn't found. + */ + int scanInputFor( char cTarget ); + + /** Gets a pointer to the output buffer. This is mainly used by internal + * routines, and is cleared every click when data is sent out again. + *@returns A pointer to the buffer holding the pending output data. + */ + const char *getOutput(); + + /** Gets a pointer to the start of the input buffer's active data + * section. Use this to gain access to the input you need to do + * your job. + *@returns A pointer to the data in the input buffer. Do not delete this. + */ + const char *getInput(); + + /** Clears all pending output, this is mainly just used internally. + *@returns True if operation was a success, otherwise false. + */ + bool clearOutput(); + + /** Clears all pending input, weather it's been used or not. Please + * refrain from calling this during normal operation, use usedInput + * instead, it's much safer. + *@returns True if the operation was a success, false otherwise. + */ + bool clearInput(); + + /** Sets the socket that should be used internally. + *@param nNewSocket The new socket to work with. + */ + void setSocket( int nNewSocket ); + + /** Gets the handle (number) of the working socket. This can be a + * dangerous function to call, please refrain from calling it directly + * if any alternative can be found. + *@returns The number of the working socket. + */ + int getSocket(); + + /** Determines if the connection is still active. + *@returns True if the connection is active, false otherwise. + */ + bool isActive(); + + /** Clears all buffers and sets up the connection to be reused. + * Does not actually close the socket, that's handled by the + * ConnectionManager + */ + void close(); + + /** Opens a socket. Really just sets up the connection for use since + * the socket itself was created and opened by the ConnectionManager. + * This also calls setSocket so you don't have to. + *@param nNewSocket The socket to assosiate with. + */ + bool open( int nNewSocket ); + + /** + * Reads all pending input from the connection. If this is called outside + * of the ConnectionManager it will usually block indefinately waiting for + * new data. The only way to change this behaviour is to modify the socket + * low-level when you connect it manually, or, preferably use the other + * readInput function to control blocking time. + *@returns True socket is still connected, otherwise false. + */ + bool readInput(); + + /** + * Reads all pending input from the connection, blocking up to nSec + * seconds and nUSec micro-seconds for the data. This uses select to + * simulate blocking, but has the same effect as standard io blocking. + * If you don't want to block, just set both values to zero. + *@param nSec Max seconds to wait. + *@param nUSec Max micro-seconds to wait. + */ + bool readInput( int nSec, int nUSec ); + + /** Writes all data that is pending to the socket. + *@returns True if all data was written succesfully, false otherwise. + */ + bool writeOutput(); + + /** Determines if the connection has output waiting to go out. + *@returns true if there is pending output, otherwise false. + */ + bool hasOutput(); + + /** Sets internal flags so that this connection will be deleted next + * time through the ConnectionManager. + */ + void disconnect(); + + /** Determines if this connection is ready to be disconnected or not. + *@returns True if it is time to disconnect, false if it isn't. + */ + bool needDisconnect(); + + /** Tells the caller if there is pending input waiting to be processed. + *@returns True if there is pending input that has not been used, returns + * false if there isn't. + */ + bool hasInput(); + + /** Removes bytes from the begining of the input queue. Use this after + * getting the input and processing as much as you need to. + *@param nAmount The number of bytes used. + *@returns true if the update was successful, otherwise false. + */ + bool usedInput( int nAmount ); + + /** Sets the protocol to be used by this connection. All data in and out + * passes through the protocol object, which may process that data to + * filter out and process any special messages that may have been + * included. Everything that isn't processed can be accessed in the + * standard method. + *@param pNewProtocol A pointer to a protocol object that you want to + * use. + */ + void setProtocol( class Protocol *pNewProtocol ); + + /** Gets the number of bytes that are waiting in the input queue, the data + * that has yet to be processed. + *@returns The number of bytes in the input queue. + */ + int getInputAmnt(); + + /** Gets the number of bytes that are waiting in the output queue, the data + * that has yet to be sent to the connected socket. + *@returns The number of bytes in the input queue. + */ + int getOutputAmnt(); + + /** Gets a pointer to the protocol that is attatched to this connection + * object. This is useful to set modes, and send special commands in + * addition to the standard raw data reads and writes that are normally + * permitted. In fact, in everything besides a raw telnet protocol all + * data should be sent through the protocol and not the connection object. + *@returns A pointer to the Protocol assosiated with this connection. + */ + class Protocol *getProtocol(); + +private: + /** + * A buffer to keep data read from the socket in. This is filled in by + * the function readInput, which is automatically called by the + * ConnectionManager whenever new data is ready. + */ + FlexBuf xInputBuf; + + /** + * A buffer to keep data that should be sent to the socket. This is filled + * in by using the AppendOutput functions and is sent to the socket using + * the writeOutput function, which is automatically called every cycle by + * the ConnectionManager when there is pending data. + */ + FlexBuf xOutputBuf; + + /** + * The socket that the user is connected to. This is not the same as the + * socket number of the listening socket, this is the unique socket on the + * system that the data is coming to. + */ + int nSocket; + + /** + * True=active connection, False=connection lost + */ + bool bActive; + + /** + * True=disconnect next cycle (after data is transmitted), Flse=keep going. + */ + bool bDisconnectMe; + + /** + * A pointer to a protocol handler that can automatically process the data + * in the buffers. This is optional if you use the connections on your own + * but reccomended if you use this with the rest of the ConnectionManager + * system. + */ + class Protocol *pProtocol; +}; + +#endif diff --git a/src/connectionmanager.cpp b/src/connectionmanager.cpp new file mode 100644 index 0000000..36ff961 --- /dev/null +++ b/src/connectionmanager.cpp @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "connectionmanager.h" +#include + +ConnectionManager::ConnectionManager() +{ + pLog = MultiLog::getLog(); + nMasterSocket = -1; + pMonitor = NULL; +} + +ConnectionManager::~ConnectionManager() +{ + std::list::const_iterator i; + for( i = lActive.begin(); i != lActive.end(); i++ ) + { + delete (*i); + } + for( i = lInactive.begin(); i != lInactive.end(); i++ ) + { + delete (*i); + } +} + +bool ConnectionManager::startServer( int nPort, int nInitPool ) +{ + /* Create the socket and set it up to accept connections. */ + struct sockaddr_in name; + + /* Create the socket. */ + nMasterSocket = socket (PF_INET, SOCK_STREAM, 0); + if (nMasterSocket < 0) + { + pLog->LineLog( MultiLog::LError, "Couldn't create a listen socket."); + return false; + } + + /* Give the socket a name. */ + name.sin_family = AF_INET; + name.sin_port = htons( nPort ); + + // I think this specifies who we will accept connections from, + // a good thing to make configurable later on + name.sin_addr.s_addr = htonl( INADDR_ANY ); + + int opt = 1; + setsockopt( nMasterSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)); + + if (bind (nMasterSocket, (struct sockaddr *) &name, sizeof (name)) < 0) + { + pLog->LineLog( MultiLog::LError, "Couldn't bind to the listen socket."); + return false; + } + + if (listen (nMasterSocket, 1) < 0) + { + pLog->LineLog( MultiLog::LError, "Couldn't begin listening to the server socket."); + return false; + } + + /* Initialize the set of active sockets. */ + FD_ZERO (&fdActive); + FD_ZERO (&fdRead); + FD_ZERO (&fdWrite); + FD_ZERO (&fdException); + FD_SET (nMasterSocket, &fdActive); + + for( int j = 0; j < nInitPool; j++ ) + { + lInactive.insert( lInactive.begin(), new Connection() ); + } + + return true; +} + +bool ConnectionManager::startServer( int nPort, int nInitPool, int nNumTries, int nTimeout ) +{ + struct timeval xTimeout; + + for( int j = 0; j < nNumTries; j++ ) + { + pLog->LineLog( MultiLog::LStatus, "Attempting to create server socket (attempt [%d/%d])...", j+1, nNumTries ); + if( startServer( nPort, nInitPool ) == true ) + { + return true; + } + else if( j < nNumTries-1 ) + { + pLog->LineLog( MultiLog::LStatus, "Waiting for %d secconds to allow port to clear...", nTimeout ); + xTimeout.tv_sec = nTimeout; + xTimeout.tv_usec = 0; + if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &xTimeout) < 0) { + pLog->LineLog( MultiLog::LError, "Error using select to sleep for a while."); + } + usleep( nTimeout ); + } + } + + return false; +} + +bool ConnectionManager::scanConnections( int nTimeout, bool bForceTimeout ) +{ + struct timeval xTimeout; + + xTimeout.tv_sec = nTimeout / 1000000; + xTimeout.tv_usec = nTimeout % 1000000; + + /* Block until input arrives on one or more active sockets. */ + fdRead = fdActive; + fdWrite = fdActive; + fdException = fdActive; + + // We removed the write checking because it just checks to see if you *can* + // write...that's stupid, they're all open, so it always exits immediately + // if there are ANY connections there... + if( TEMP_FAILURE_RETRY( select( FD_SETSIZE, &fdRead, (fd_set *)0/*&fdWrite*/, &fdException, &xTimeout ) ) < 0 ) + { + pLog->LineLog( MultiLog::LError, "Error attempting to scan open connections."); + perror("ConnectionManager"); + return false; + } + // Now we use select to sleep as well as to scan for connections, now we + // just need to fix the fact that if there are no connections, the seccond + // select call doesn't return until there is a connection... + if( bForceTimeout ) + { + if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &xTimeout) < 0) { + pLog->LineLog( MultiLog::LError, "Error using select to sleep for a while."); + } + } + + /* Service all the sockets with input pending. */ + for( int i = 0; i < FD_SETSIZE; ++i ) + { + if( FD_ISSET( i, &fdRead ) ) + { + if( i == nMasterSocket ) + { + addConnection(); + } + else + { + Connection *pCon = findActiveConnection( i ); + if( pCon == NULL ) + { + pLog->LineLog( MultiLog::LError, "A connection object was lost, or never created!"); + return false; + } + + /* Data arriving on an already-connected socket. */ + if( pCon->readInput() != true ) + { + pLog->LineLog( MultiLog::LStatus, "Closing connection due to disconnect."); + close( i ); + FD_CLR( i, &fdActive ); + pMonitor->onClosedConnection( pCon ); + pCon->close(); + } + else + { + // We actually read something...but the connection handles + // protocol notification, so we don't need to do anything + // here... + } + } + } + } + std::list::iterator i; + for( i = lActive.begin(); i != lActive.end(); i++ ) + { + if( (*i)->isActive() == false ) + { + std::list::iterator l = i; + i--; + lInactive.insert( lInactive.end(), *l ); + lActive.erase( l ); + continue; + } + if( (*i)->hasOutput() ) + { + (*i)->writeOutput(); + } + if( (*i)->needDisconnect() ) + { + int prt = (*i)->getSocket(); + close( prt ); + FD_CLR( prt, &fdActive ); + pMonitor->onClosedConnection( *i ); + (*i)->close(); + lInactive.insert( lInactive.end(), *i ); + std::list::iterator l = i; + i--; + lActive.erase( l ); + pLog->LineLog( MultiLog::LStatus, "Closing connection due to server request."); + } + } + + return true; +} + +bool ConnectionManager::shutdownServer() +{ + while( !lActive.empty() ) + { + Connection *i = *(lActive.begin()); + if( i->isActive() ) + { + i->close(); + pMonitor->onClosedConnection( i ); + lInactive.insert( lInactive.end(), i ); + lActive.erase( lActive.begin() ); + } + } +/* + for( int i = 0; i < nPoolSize; i++ ) + { + + int prt = axConPool[i].getSocket(); + close( prt ); +// FD_CLR( prt, &fdActive ); + pMonitor->onClosedConnection( &axConPool[i] ); + axConPool[i].close(); + } +*/ + shutdown( nMasterSocket, SHUT_RDWR ); + close( nMasterSocket ); + + return true; +} + +bool ConnectionManager::broadcastMessage( const char *lpData, int nExcludeSocket ) +{ + std::list::const_iterator i; + for( i = lActive.begin(); i != lActive.end(); i++ ) + { + if( (*i)->isActive() && + (*i)->getSocket() != nExcludeSocket ) + { + (*i)->appendOutput( lpData ); + } + } + + return true; +} + +bool ConnectionManager::addConnection() +{ + struct sockaddr_in clientname; + size_t size; + int newSocket; + + size = sizeof( clientname ); +#ifdef __CYGWIN__ + newSocket = accept( nMasterSocket, (struct sockaddr *) &clientname, (int *)&size ); +#else + newSocket = accept( nMasterSocket, (struct sockaddr *) &clientname, &size ); +#endif + if( newSocket < 0 ) + { + pLog->LineLog( MultiLog::LError, "Error accepting a new connection!" ); + return false; + } +// char *tmpa = inet_ntoa(clientname.sin_addr); + char tmpa[20]; + inet_ntop( AF_INET, (void *)&clientname.sin_addr, tmpa, 20 ); + pLog->LineLog( MultiLog::LStatus, "New connection from host %s, port %hd.", tmpa, ntohs (clientname.sin_port) ); +/* + int nCnt = 0; + for( int j = 0; j < nPoolSize; j++ ) + { + if( axConPool[j].isActive() ) + { + nCnt++; + } + } + pLog->LineLog( MultiLog::LStatus, "Connections %d/%d.", nCnt, nPoolSize ); + */ +// free( tmpa ); + FD_SET( newSocket, &fdActive ); + + //void nonblock(socket_t s) + { + int flags; + + flags = fcntl(newSocket, F_GETFL, 0); + flags |= O_NONBLOCK; + if (fcntl(newSocket, F_SETFL, flags) < 0) + { + return false; + } + } + + Connection *pCon = getInactiveConnection(); + pCon->open( newSocket ); + + pMonitor->onNewConnection( pCon ); + + lActive.insert( lActive.end(), pCon ); + + return true; +} + +Connection *ConnectionManager::getInactiveConnection() +{ + if( lInactive.empty() ) + { + return new Connection(); + } + Connection *pCon = *(lInactive.begin()); + lInactive.erase( lInactive.begin() ); + return pCon; +} + +Connection *ConnectionManager::findActiveConnection( int nSocket ) +{ + std::list::const_iterator i; + for( i = lActive.begin(); i != lActive.end(); i++ ) + { + if( (*i)->getSocket() == nSocket ) + { + return *i; + } + } + + return NULL; +} + +void ConnectionManager::setConnectionMonitor( ConnectionMonitor *pNewMonitor ) +{ + pMonitor = pNewMonitor; +} diff --git a/src/connectionmanager.h b/src/connectionmanager.h new file mode 100644 index 0000000..53249a7 --- /dev/null +++ b/src/connectionmanager.h @@ -0,0 +1,138 @@ +/** + *@file + * Contains the ConnectionManager. + *@author Mike Buland + */ + +#ifndef CONNECTIONMANAGER_H +#define CONNECTIONMANAGER_H + +#include "multilog.h" +#include "connection.h" +#include "connectionmonitor.h" +#include +#include + +/** Manges incoming network connections as a server. Creates and works with + * Connection objects. All operations are performed on TCP/IP v4 right now, + * and on a single port, although any number of connections can be handled. + *@author Mike Buland + */ +class ConnectionManager +{ +public: + /** + * Sets up the basics, like storage for the pool, and so on. This does not + * actually start a server, bind to a port, or create a connection pool. + * That's all handled by startServer(). + */ + ConnectionManager(); + + /** + * Cleans up everything, and even clears out all still-connected Connection + * objects. + */ + ~ConnectionManager(); + + /** + * Starts a server socket and binds to it, listening for new connections. + *@param nPort The port to listen on. + *@param nInitPool The size of the initial connection pool. This will + * grow automatically if necesarry. + *@returns True if the socket was bound to the port and serving was + * started. False if there was a problem connecting to the port. + */ + bool startServer( int nPort, int nInitPool ); + + /** + * This is identicle to the simpler startServer function except that it + * will automatically try to connect multiple times in case the first + * attempt or two doesn't work for some reason. Initially this was + * written to compensate for server sockets staying locked after they were + * closed for a while. + *@param nPort The port to listen on. + *@param nInitPool The size of the initial connection pool. This will + * grow automatically if necesarry. + *@param nNumTries The maximum number of times to try to connect. + *@param nTimeout The amount of time to wait in-between connection + * attempts. + *@returns True if the socket was bound to the port and serving was + * started. False if there was a problem connecting to the port. + */ + bool startServer( int nPort, int nInitPool, int nNumTries, int nTimeout ); + + /** + * Scans all open connections, halting the calling processes until data + * is received or nTimeout ms have gone by. While waiting for the timeout + * to complete the process is placed into an idle mode. + *@param nTimeout The number of millisecconds to wait if there is nothing + * to actually do. + *@param bForceTimeout If set to true, this will force the scanner to wait + * for the timout to complete before returning, even if there was pending + * data. + */ + bool scanConnections( int nTimeout, bool bForceTimeout ); + + /** Shutdown the server and all assosiated sockets. + *@returns True if every socket was closed without problem. + */ + bool shutdownServer(); + + /** Sends a message directly to every connected port. + *@param lpData A null-terminated string of data to send. + *@param nExcludeSocket An optional socket to exclude from the broadcast. + *@returns True if every socket that should have gotten the message did. + */ + bool broadcastMessage( const char *lpData, int nExcludeSocket=-1 ); + + /** Sets a monitor for the manager. The monitor is sent notifications + * whenever a socket is connected, disconnected, or whenever an error + * occurs. + *@param pNewMonitor A pointer to a preconstructed ConnectionMonitor + */ + void setConnectionMonitor( ConnectionMonitor *pNewMonitor ); + +private: + /** + * Take care of the work of actually accepting a connection. This will + * accept the connection, set the initial modes, and add it to the master + * list of active connections, as well as fire off any messages that need + * to be handled by anything else. + *@returns True if everything worked, False otherwise. + */ + bool addConnection(); + + /** + * Seraches the internal lists of connections for one with a specific + * socket. + *@param nSocket The socket the connection is using for communication. + * This is the unique socket and not the one that the connection was + * initially to. + *@returns NULL if no connection was found, otherwise a pointer to a live + * Connection object. + */ + Connection *findActiveConnection( int nSocket ); + + /** + * Searches the connection pool for an object that isn't in use yet, and + * returns it, ready to be filled in and used. + *@returns An unused connection object ready for use. + *@todo Check this code over to insure that the pool grows appropriately + * when enough extra connections are detected. + */ + Connection *getInactiveConnection(); + + MultiLog *pLog; /**< A pointer to the active MultiLog */ + int nMasterSocket; /**< The listening or server socket. */ + fd_set fdActive; /**< The active socket set. */ + fd_set fdRead; /**< The sockets ready for a read. */ + fd_set fdWrite; /**< The sockets ready for a write. */ + fd_set fdException; /**< The sockets that have gotten errors. */ + std::list lInactive; /**< The pool of inactive Connections */ + std::list lActive; /**< The pool of active Connections */ + + /** The ConnectionMonitor to notify of new connections. */ + ConnectionMonitor *pMonitor; +}; + +#endif diff --git a/src/connectionmonitor.cpp b/src/connectionmonitor.cpp new file mode 100644 index 0000000..1b49f5d --- /dev/null +++ b/src/connectionmonitor.cpp @@ -0,0 +1,23 @@ +/*************************************************************************** + connectionmonitor.cpp - description + ------------------- + begin : Mon Sep 8 2003 + copyright : (C) 2003 by Mike Buland + email : eichlan@yf-soft.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "connectionmonitor.h" + +ConnectionMonitor::ConnectionMonitor(){ +} +ConnectionMonitor::~ConnectionMonitor(){ +} diff --git a/src/connectionmonitor.h b/src/connectionmonitor.h new file mode 100644 index 0000000..b96b533 --- /dev/null +++ b/src/connectionmonitor.h @@ -0,0 +1,41 @@ +/**@file + * Describes the ConnectionMonitor class. + */ +#ifndef CONNECTIONMONITOR_H +#define CONNECTIONMONITOR_H + +#include "connection.h" + +/** Connection Monitor defines the base class of the objects that will be + * notified whenever a connection is created or destroyed. + *@author Mike Buland + */ +class ConnectionMonitor +{ +public: + /** + * This is only here for completeness. It does nothing. + */ + ConnectionMonitor(); + + /** + * This is only here for completeness. It does nothing. + */ + virtual ~ConnectionMonitor(); + + /** Receives the notification that new connection was received. + *@param pCon The connection that was created. + *@returns Should return a true value if everything is OK, a false to + * force a shutdown. + */ + virtual bool onNewConnection( Connection *pCon ) = 0; + + /** Receives the notification that a connection was closed. + *@param pCon The connection that was closed. + *@returns Should return a true value if everything is OK, a false to + * force a shutdown. + */ + virtual bool onClosedConnection( Connection *pCon ) = 0; +}; + +#endif diff --git a/src/flexbuf.cpp b/src/flexbuf.cpp new file mode 100644 index 0000000..acd55a7 --- /dev/null +++ b/src/flexbuf.cpp @@ -0,0 +1,206 @@ +#include "flexbuf.h" +#include + +FlexBuf::FlexBuf() +{ + lpBuf = new char[1024]; + nLastChar = 0; + nFirstChar = 0; + nSize = 1024; + nFill = 0; + clearData(); +} + +FlexBuf::~FlexBuf() +{ + delete[] lpBuf; +} + +bool FlexBuf::appendData( const char *lpData, int nDSize ) +{ + int nStrLen; + if( nDSize < 0 ) + { + nStrLen = strlen( lpData ); + } + else + { + nStrLen = nDSize; + } + + if( nLastChar + nStrLen + 1 > nSize ) + { + if( nFill + nStrLen + 1 < nSize ) + { + memcpy( lpBuf, lpBuf+nFirstChar, nFill ); + nLastChar -= nFirstChar; + nFirstChar = 0; + } + else + { + nSize += nStrLen+1; + char *lpNewBuf = new char[nSize]; + memcpy( lpNewBuf, lpBuf+nFirstChar, nFill ); + delete[] lpBuf; + lpBuf = lpNewBuf; + nLastChar -= nFirstChar; + nFirstChar = 0; + } + } + + memcpy( &lpBuf[nLastChar], lpData, nStrLen ); + nLastChar += nStrLen; + nFill += nStrLen; + lpBuf[nLastChar] = '\0'; + + return true; +} + +bool FlexBuf::appendData( const char lData ) +{ + if( nLastChar + 2 > nSize ) + { + if( nFill+2 < nSize ) + { + memcpy( lpBuf, lpBuf+nFirstChar, nFill ); + nLastChar -= nFirstChar; + nFirstChar = 0; + } + else + { + nSize += 1024; + char *lpNewBuf = new char[nSize]; + memcpy( lpNewBuf, lpBuf+nFirstChar, nFill ); + delete[] lpBuf; + lpBuf = lpNewBuf; + nLastChar -= nFirstChar; + nFirstChar = 0; + } + } + + lpBuf[nLastChar] = lData; + nLastChar++; + nFill++; + lpBuf[nLastChar] = '\0'; + + return true; +} + +bool FlexBuf::appendData( const short lData ) +{ + return appendData( (const char *)&lData, sizeof(short) ); +} + +bool FlexBuf::appendData( const int lData ) +{ + return appendData( (const char *)&lData, sizeof(int) ); +} + +bool FlexBuf::appendData( const long lData ) +{ + return appendData( (const char *)&lData, sizeof(long) ); +} + +bool FlexBuf::appendData( const float lData ) +{ + return appendData( (const char *)&lData, sizeof(float) ); +} + +bool FlexBuf::appendData( const double lData ) +{ + return appendData( (const char *)&lData, sizeof(double) ); +} + +bool FlexBuf::appendData( const unsigned char lData ) +{ + return appendData( (const char)lData ); +} + +bool FlexBuf::appendData( const unsigned short lData ) +{ + return appendData( (const char *)&lData, sizeof(short) ); +} + +bool FlexBuf::appendData( const unsigned long lData ) +{ + return appendData( (const char *)&lData, sizeof(long) ); +} + +bool FlexBuf::appendData( const unsigned int lData ) +{ + return appendData( (const char *)&lData, sizeof(int) ); +} + +bool FlexBuf::clearData() +{ + nFirstChar = nLastChar = nFill = 0; + lpBuf[nLastChar] = '\0'; + + return true; +} + +const char *FlexBuf::getData() +{ + return (lpBuf+nFirstChar); +} + +int FlexBuf::getLength() +{ + return nFill; +} + +int FlexBuf::getCapacity() +{ + return nSize; +} + +bool FlexBuf::usedData( int nAmount ) +{ + // Remove from the end if negative + if( nAmount < 0 ) + { + if( nFill+nAmount < 0 ) + { + nFill = nFirstChar = nLastChar = 0; + return true; + } + nLastChar += nAmount; + nFill += nAmount; + return true; + } + if( nAmount > nFill ) + { + nAmount = nSize; +// return false; + } + + //nLastChar -= nAmount; + nFirstChar += nAmount; + nFill -= nAmount; + + if( nFill == 0 ) + { + nFirstChar = nLastChar = 0; + } + + //if( nLastChar > 0 ) + //{ + //memmove( lpBuf, &lpBuf[nAmount], nLastChar ); + //} + + return true; +} + +int FlexBuf::findChar( char cTarget ) +{ + for( int j = nFirstChar; j < nLastChar; j++ ) + { + if( lpBuf[j] == cTarget ) + { + return j; + } + } + + return -1; +} + diff --git a/src/flexbuf.h b/src/flexbuf.h new file mode 100644 index 0000000..5ce4a89 --- /dev/null +++ b/src/flexbuf.h @@ -0,0 +1,160 @@ +/**\flexbuf.h + * Describes the FlexBuf class. + *@author Mike Buland + */ + +#ifndef FLEXBUF_H +#define FLEXBUF_H + +/** Stores any amount of data, but starts small, growing as necesarry. + * It is optimized to work with stream type situations, with data being + * added to the end while it is being taken from the begning. + *@todo Set this class up to auto-shrink back to a specified sized buffer each + * time it has shrunk below that for enough operations. + *@author Mike Buland + */ +class FlexBuf +{ +public: + /** + * Construct a blank FlexBuf containing about 1k of buffer space. + */ + FlexBuf(); + + /** + * Clean up the FlexBuf, delete all buffers. + */ + ~FlexBuf(); + + /** Appends a whole string of data to the buffer. The string + * must be null terminated. + *@param lpData The data to append to the buffer. + *@param nDSize The size of the data described by lpData. If this + * value is -1 lpData is treated as a null-terminated string. + *@returns True if no problems occured, false otherwise. + */ + bool appendData( const char *lpData, int nDSize=-1 ); + + /** Appends a single character to the end of the buffer. + *@param lData The character to append to the buffer. + *@returns True if no problems occured, false otherwise. + */ + bool appendData( const char lData ); + + /** + * Append the short to the buffer. + *@param lData The short to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const short lData ); + + /** + * Append the int to the buffer. + *@param lData The int to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const int lData ); + + /** + * Append the long to the buffer. + *@param lData The long to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const long lData ); + + /** + * Append the float to the buffer. + *@param lData The float to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const float lData ); + + /** + * Append the double to the buffer. + *@param lData The double to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const double lData ); + + /** + * Append the unsigned char to the buffer. + *@param lData The unsigned char to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const unsigned char lData ); + + /** + * Append the unsigned short to the buffer. + *@param lData The unsigned short to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const unsigned short lData ); + + /** + * Append the unsigned int to the buffer. + *@param lData The unsigned int to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const unsigned int lData ); + + /** + * Append the unsigned long to the buffer. + *@param lData The unsigned long to add to the buffer queue. + *@returns True if everything is ok, false otherwise. + */ + bool appendData( const unsigned long lData ); + + /** Removes all pending data from the buffer. + *@returns True if no problems occured, false otherwise. + */ + bool clearData(); + + /** Gets a pointer to the internal buffer, at the begining of the current + * data stream. + *@returns A pointer to the internal data buffer. + */ + const char *getData(); + + /** Gets the length of the current buffer (how much data is really in the + * buffer, not it's current capacity, for that check getCapacity) + *@returns The length of the current buffer. + */ + int getLength(); + + /** Gets the current capacity of the FlexBuf. If the size nears this value + * then the entire buffer is resized to accomidate more data. + *@returns The current capacity of the FlexBuf. + */ + int getCapacity(); + + /** + * Removes nAmount bytes from the begning of the buffer. Actually, if + * nAmount happens to be negative it will remove tha absolute value of + * nValue bytes from the end of the buffer, like the old delData command. + *@param nAmount The number of bytes used. + *@returns True if everything was successful, false if there was an error. + */ + bool usedData( int nAmount ); + + /** Finds the first instance of the given character in the buffer and + * returns an index to it. + *@param cTarget The character you're looking for. + *@returns The index of the first instance of the given character, or + * -1 if it just wasn't found. + */ + int findChar( char cTarget ); + +private: + /** The raw storage location of the FlexBuf. */ + char *lpBuf; + /** The real size of the FlexBuf. */ + int nSize; + /** Where the last char is. */ + int nLastChar; + /** Where the first char is. */ + int nFirstChar; + /** The amount of real data in the FlexBuf. This is effectively nLastChar-nFirstChar. */ + int nFill; +}; + +#endif diff --git a/src/hashfunction.cpp b/src/hashfunction.cpp new file mode 100644 index 0000000..51f2259 --- /dev/null +++ b/src/hashfunction.cpp @@ -0,0 +1,10 @@ +#include "hashfunction.h" + +HashFunction::HashFunction() +{ +} + +HashFunction::~HashFunction() +{ +} + diff --git a/src/hashfunction.h b/src/hashfunction.h new file mode 100644 index 0000000..cbcf70f --- /dev/null +++ b/src/hashfunction.h @@ -0,0 +1,48 @@ +#ifndef HASH_FUNCTION +#define HASH_FUNCTION + +/** This represents the shell of a hash function. It must be aggregated in + * order to be used. Please read about it's two functions for specificatins + * relating to what values will be passed to them and what they should return + * for creating your own hash functions. + *@author Mike Buland. + */ +class HashFunction +{ +public: + /** + * Standard Constructor. + */ + HashFunction(); + + /** + * Standard Deconstructor. + */ + virtual ~HashFunction(); + + /** Hashes the value represnted by id. This must return a fairly unique + * number in the range of 0-2^32 (or whatever the size of an unsigned long + * is on your system) based on the id given. The faster the number changes + * the better in a general sence. The return value will be the index + * (after probing takes place) to the data assosiated with an id, so this + * function should always produce the same number for any given id. + *@param id The identifier to use to create a unique numerical identifier. + *@returns A mostly unique numerical identifier generated using the given + * id. + */ + virtual unsigned long int hash( const void *id ) = 0; + + /** This function must compare two ids in the format that this hashfunction + * accepts. For example, if the hash function hashes strings it should + * probably { return strcmp( id1, id2 ) == 0 }. + *@param id1 One value to use in the comparison + *@param id2 Another value to use in the comparison + *@returns True if the two values match, otherwise false. + */ + virtual bool cmpIDs( const void *id1, const void *id2 ) = 0; + +// virtual void *createPersistantID( const void *id ) = 0; +// virtual void destroyPersistantID( const void *id ) = 0; +}; + +#endif diff --git a/src/hashfunctioncasestring.cpp b/src/hashfunctioncasestring.cpp new file mode 100644 index 0000000..6361f45 --- /dev/null +++ b/src/hashfunctioncasestring.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include "hashfunctioncasestring.h" + +HashFunctionCaseString::HashFunctionCaseString() +{ +} + +HashFunctionCaseString::~HashFunctionCaseString() +{ +} + +unsigned long int HashFunctionCaseString::hash( const void *id ) +{ + const char *str = (const char *)id; + unsigned long int nPos = 0; + for( int j = 0; str[j] != '\0'; j++ ) + { + nPos = tolower(str[j]) + (nPos << 6) + (nPos << 16) - nPos; +// nPos += nPos<<16|(((unsigned long int)tolower(str[j]))<<((j*7)%24)); + } + return nPos; +} + +bool HashFunctionCaseString::cmpIDs( const void *id1, const void *id2 ) +{ + const char *str1 = (const char *)id1; + const char *str2 = (const char *)id2; + + int j; + for( j = 0; str1[j] != '\0' && str2[j] != '\0'; j++ ) + { + if( tolower(str1[j]) != tolower(str2[j]) ) + return false; + } + return (str1[j]==str2[j]); +} + diff --git a/src/hashfunctioncasestring.h b/src/hashfunctioncasestring.h new file mode 100644 index 0000000..9ca3d48 --- /dev/null +++ b/src/hashfunctioncasestring.h @@ -0,0 +1,28 @@ +#ifndef HASH_FUNCTION_CASE_STRING +#define HASH_FUNCTION_CASE_STRING + +#include "hashfunction.h" + +/** A hash function for string data. This hash function does strings, but is + * actually generalized to handle any binary stream of characters terminated + * by a null character. This is different than HashFunctionString in that + * this does comparisons without regaurd to case. + *@author Mike Buland. + */ +class HashFunctionCaseString : public HashFunction +{ +public: + /** + * Standard Constructor. + */ + HashFunctionCaseString(); + + /** + * Standard Deconstructor. + */ + ~HashFunctionCaseString(); + unsigned long int hash( const void *id ); + bool cmpIDs( const void *id1, const void *id2 ); +}; + +#endif diff --git a/src/hashfunctionint.cpp b/src/hashfunctionint.cpp new file mode 100644 index 0000000..4bd0feb --- /dev/null +++ b/src/hashfunctionint.cpp @@ -0,0 +1,20 @@ +#include "hashfunctionint.h" + +HashFunctionInt::HashFunctionInt() +{ +} + +HashFunctionInt::~HashFunctionInt() +{ +} + +unsigned long int HashFunctionInt::hash( const void *id ) +{ + return (unsigned long)(id); +} + +bool HashFunctionInt::cmpIDs( const void *id1, const void *id2 ) +{ + return (unsigned long)(id1) == (unsigned long)(id2); +} + diff --git a/src/hashfunctionint.h b/src/hashfunctionint.h new file mode 100644 index 0000000..57bce89 --- /dev/null +++ b/src/hashfunctionint.h @@ -0,0 +1,26 @@ +#ifndef HASH_FUNCTION_INT +#define HASH_FUNCTION_INT + +#include "hashfunction.h" + +/** A hash function for integer data. Really, this does almost nothing except + * ensure we're dealing with positive indicies. + *@author Mike Buland. + */ +class HashFunctionInt : public HashFunction +{ +public: + /** + * Standard Constructor. + */ + HashFunctionInt(); + + /** + * Standard Deconstructor. + */ + ~HashFunctionInt(); + unsigned long int hash( const void *id ); + bool cmpIDs( const void *id1, const void *id2 ); +}; + +#endif diff --git a/src/hashfunctionstring.cpp b/src/hashfunctionstring.cpp new file mode 100644 index 0000000..8ea9f57 --- /dev/null +++ b/src/hashfunctionstring.cpp @@ -0,0 +1,36 @@ +#include "hashfunctionstring.h" + +HashFunctionString::HashFunctionString() +{ +} + +HashFunctionString::~HashFunctionString() +{ +} + +unsigned long int HashFunctionString::hash( const void *id ) +{ + const char *str = (const char *)id; + unsigned long int nPos = 0; + for( int j = 0; str[j] != '\0'; j++ ) + { + nPos = str[j] + (nPos << 6) + (nPos << 16) - nPos; +// nPos += nPos<<16|(((unsigned long int)str[j])<<((j*7)%24)); + } + return nPos; +} + +bool HashFunctionString::cmpIDs( const void *id1, const void *id2 ) +{ + const char *str1 = (const char *)id1; + const char *str2 = (const char *)id2; + + int j; + for( j = 0; str1[j] != '\0' && str2[j] != '\0'; j++ ) + { + if( str1[j] != str2[j] ) + return false; + } + return (str1[j]==str2[j]); +} + diff --git a/src/hashfunctionstring.h b/src/hashfunctionstring.h new file mode 100644 index 0000000..566f8ae --- /dev/null +++ b/src/hashfunctionstring.h @@ -0,0 +1,27 @@ +#ifndef HASH_FUNCTION_STRING +#define HASH_FUNCTION_STRING + +#include "hashfunction.h" + +/** A hash function for string data. This hash function does strings, but is + * actually generalized to handle any binary stream of characters terminated + * by a null character. + *@author Mike Buland. + */ +class HashFunctionString : public HashFunction +{ +public: + /** + * Standard Constructor. + */ + HashFunctionString(); + + /** + * Standard Deconstructor. + */ + ~HashFunctionString(); + unsigned long int hash( const void *id ); + bool cmpIDs( const void *id1, const void *id2 ); +}; + +#endif diff --git a/src/hashtable.cpp b/src/hashtable.cpp new file mode 100644 index 0000000..9dfe653 --- /dev/null +++ b/src/hashtable.cpp @@ -0,0 +1,345 @@ +#include +#include +#include + +#include "hashtable.h" + +HashTable::HashTable( HashFunction *hNewFunc, unsigned long int nInitSize, bool bAllowDupes ) +{ + hFunc = hNewFunc; + nTableSize = nextPrime( nInitSize ); + aTable = new HashNode[nTableSize]; + //for( int j = 0; j < nTableSize; j++ ) if( aTable[j].id || aTable[j].data || aTable[j].bDeleted ) printf("Unclean entry\n"); + nSize = 0; + nFilled = 0; + this->bAllowDupes = bAllowDupes; +} + +HashTable::~HashTable() +{ + delete[] aTable; + delete hFunc; +} + +void HashTable::set( int j, const void *newID, const void *newData ) +{ + if( newData == NULL ) + { + printf("Inserting NULL data is indestinguishable from uninserted data!\n"); + } + aTable[j].id = newID; + aTable[j].data = newData; +} + +bool HashTable::isFilled( int j ) +{ + return (aTable[j].id != NULL)||(aTable[j].bDeleted); +} + +bool HashTable::reHash( unsigned long int nNewSize ) +{ + HashNode *aOldTable = aTable; + unsigned long int oldSize = nTableSize; + + // If the table can still be used if we just get rid of deleted items, don't + // change the size of the table, otherwise, go ahead and use the number + // passed in. + if( nSize > nTableSize>>1 ) + { + nTableSize = nextPrime( nNewSize ); + } + + aTable = newTable( nTableSize ); + //for( int j = 0; j < nTableSize; j++ ) if( aTable[j].id || aTable[j].data || aTable[j].bDeleted ) printf("Unclean entry\n"); + + nSize = 0; + nFilled = 0; + + for( unsigned long int j = 0; j < oldSize; j++ ) + { + if( aOldTable[j].id != NULL && aOldTable[j].bDeleted == false ) + { + insert( aOldTable[j].id, aOldTable[j].data ); + } + } + + delete[] aOldTable; +} + +unsigned long int HashTable::probe( unsigned long int nStart, const void *id ) +{ + int nHash = nStart; + nStart = nStart%nTableSize; + if( bAllowDupes == true ) + { + for( + unsigned long int j=0; + isFilled( nStart ) && j < 32; + nStart = (nStart+(1<cmpIDs( aTable[nStart].id, id ) == true && + aTable[nStart].bDeleted == false ) + { + return nStart; + } + } + } + } + // This is our insurance, if the table is full, then go ahead and rehash, + // then try again. + if( isFilled( nStart ) ) + { + reHash( getCapacity()*2 ); + return probe( nHash, id ); + } + return nStart; +} + +HashTable::HashNode *HashTable::newTable( unsigned long int nNewSize ) +{ + return new HashNode[nNewSize]; +} + +#ifdef HASH_DEBUG_VIS +void HashTable::printDebugLine( const char *exData ) +{ + char *buf = new char[getCapacity()+3]; + int j; + buf[0] = '['; + for( j = 0; j < getCapacity(); j++ ) + { + buf[j+1] = (aTable[j].bDeleted)?('X'):((isFilled( j ))?('#'):('-')); + } + buf[j+1] = ']'; + buf[j+2] = '\0'; + printf("%s %s\n", buf, exData ); + delete[] buf; +} +#endif + +bool HashTable::insert( const void *id, const void *data ) +{ + unsigned long int nPos = probe( hFunc->hash( id ), id )%nTableSize; + + if( bAllowDupes == true ) + { + if( aTable[nPos].id == NULL && aTable[nPos].bDeleted == false ) + { + set( nPos, id, data ); +#ifdef HASH_DEBUG_VIS + printDebugLine( (const char *)id ); +#endif + nSize++; + nFilled++; + return true; + } + else + { + return false; + } + } + else + { + if( aTable[nPos].id == NULL && aTable[nPos].bDeleted == false ) + { + set( nPos, id, data ); +#ifdef HASH_DEBUG_VIS + printDebugLine( (const char *)id ); +#endif + nSize++; + nFilled++; + return true; + } + else if( hFunc->cmpIDs( aTable[nPos].id, id ) == true ) + { + set( nPos, id, data ); +#ifdef HASH_DEBUG_VIS + printDebugLine( (const char *)id ); +#endif + return true; + } + else + { + return false; + } + } +} + +const void *HashTable::get( const void *id, unsigned long int nSkip ) +{ + unsigned long int nPos = hFunc->hash( id )%nTableSize; + + for( unsigned long int j=0; j < 32; nPos = (nPos+(1<cmpIDs( id, aTable[nPos].id ) && + aTable[nPos].bDeleted == false ) + { + if( nSkip == 0 ) + { + return aTable[nPos].data; + } + else + { + nSkip--; + } + } + } + + return NULL; +} + +void *HashTable::getFirstItemPos() +{ + HashPos *pos = new HashPos; + return pos; +} + +const void *HashTable::getItemData( void *xPos ) +{ + return aTable[((HashPos *)xPos)->nPos].data; +} + +const void *HashTable::getItemID( void *xPos ) +{ + return aTable[((HashPos *)xPos)->nPos].id; +} + +void *HashTable::getNextItemPos( void *xPos ) +{ + HashPos *pos = (HashPos *)xPos; + if( pos->bStarted == false ) + { + pos->bStarted = true; + pos->nPos = 0; + } + else + { + pos->nPos++; + } + if( pos->nPos < nTableSize ) + { + for( ; pos->nPos < nTableSize; pos->nPos++ ) + { + if( isFilled( pos->nPos ) && + aTable[pos->nPos].bDeleted == false ) + { + return xPos; + } + } + } + + delete pos; + + return NULL; +} + +// Big-O sqrt(n) +// Change this to be erethpothynies table with a storage +// lookup later on. +bool HashTable::isPrime (int num) +{ + if (num == 2) // the only even prime + return true; + else if (num % 2 == 0) // other even numbers are composite + return false; + else + { + //bool prime = true; + int divisor = 3; + int upperLimit = static_cast(sqrt(num) + 1); + while (divisor <= upperLimit) + { + if (num % divisor == 0) + return false; + // prime = false; + divisor +=2; + } + return true; + } +} + +// Big-O n^(3/2) +int HashTable::nextPrime( int base ) +{ + int nPrime; + for( nPrime = base; isPrime( nPrime ) == false; nPrime++ ); + return nPrime; +} + +unsigned long int HashTable::getCapacity() +{ + return nTableSize; +} + +unsigned long int HashTable::getSize() +{ + return nSize; +} + +double HashTable::getLoad() +{ + return (double)(nFilled)/(double)(nTableSize); +} + +const void *HashTable::operator[](const void *id) +{ + return get( id ); +} + +bool HashTable::del( const void *id, int nSkip ) +{ + unsigned long int nPos = hFunc->hash( id )%nTableSize; + + for( unsigned long int j=0; j < 32; nPos = (nPos+(1<cmpIDs( id, aTable[nPos].id ) && + aTable[nPos].bDeleted == false ) + { + if( nSkip == 0 ) + { + aTable[nPos].bDeleted = true; +// aTable[nPos]. + nSize--; +#ifdef HASH_DEBUG_VIS + printDebugLine( (const char *)id ); +#endif + return true; + } + else + { + nSkip--; + } + } + } + + return false; +} + diff --git a/src/hashtable.h b/src/hashtable.h new file mode 100644 index 0000000..d14be71 --- /dev/null +++ b/src/hashtable.h @@ -0,0 +1,299 @@ +/**\hashtable.h + * Describes the HashFunction, HashFunctionString, and HashTable classes. It + * was just easier to put them all in one set of files. + *@author Mike Buland + */ + +#ifndef HASH_TABLE_H +#define HASH_TABLE_H + +//Uncomment this line to see a cool text-mode visualization of what's going on +//#define HASH_DEBUG_VIS 1 + +#include +#include +#include + +#include "hashfunction.h" + +/** + * A simple yet flexable hash-table. This uses several tricks to help ensure + * that the table is always running at maximum efficiency. You no longer have + * to specify a "danger fill level" when more space is needed a rehash is + * automatically trigered. Deleting elements is fully supported, as well as + * duplicate elements. To work with and allow duplicates simple construct your + * HashTable the way you normally would, but when deleting or getting elements + * you can specify a skip value. This effectively allows you to treat elements + * with duplicate ID's as though they were in a zero-based array. The first + * element inserted with a given ID would be at skip zero, the next at skip 1 + * and so on. This allows you to quickly search for elements with duplicate + * names, just stop when you get a null for a skip number, i.e. + *
+ *   for( int j = 0;; j++ )
+ *   {
+ *       void *pData = hash.get( myID, j );
+ *       if( !pData ) break;
+ *       // Do something interesting with pData
+ *   }
+ * 
+ * There are new features in this HashTable that also allow for memory saving + * when dealing with systems where many elements are being deleted from the + * table. In those cases the elements deleted cannot be simply deleted, instead + * they have to be marked as deleted and hidden from the user, but maintained in + * the table so that future hashing operations don't fail. When rehashing + * occurs all elements marked as deleted are quietly removed. In these cases, + * if the number of deleted items would free enough space in the table for the + * table to be used efficiently without resizing, it is left the same size and + * rehashing is performed effectively in place, allowing the deleted items to + * be removed. + *
+ * For info on adding new hashing algorithms, please see the HashFunction class. + *@author Mike Buland + *@todo Fix probing for tables that allow duplicates, and delete an item, then + * insert an item with the same name. + */ +class HashTable +{ +public: + /** Constructs a hash table. + *@param hNewFunc A pointer to a hashfunction class to use. If this is + * null the default general string type will be used. + *@param nInitSize The initial size of the hashtable. + *@param bAllowDupes Setting this value to true allows the system to + * insert more than one copy of any given key. This can be tricky, and + * will require you to use the nSkip parameter on the get function. + */ + HashTable( HashFunction *hNewFunc, unsigned long int nInitSize, bool bAllowDupes=false ); + + /** + * Destroys the hashtable, cleaning up all internal storage, but not stored + * elements. Also deletes the HashFunction passed in in the constructor. + */ + ~HashTable(); + + /** Inserts an item into the hashtable. This function will trigger a + * rehash if adding another item would force the table's load factor over + * the danger level. + *@param id used to find the data later. + *@param data The data item to insert into the table with the identifier + * id + *@returns True if insertion was successfull, and false if it failed. + */ + bool insert( const void *id, const void *data ); + + /** Gets an item in the hashtable based on the id of that item. If there + * is more than one item with the same id you can use the nSkip parameter + * to access all of them. + *@param id The id of the item you're trying to find. + *@param nSkip The number of items with that id to skip before returning + * with the requested item. + *@returns A pointer to the data stored at the given id. + */ + const void *get( const void *id, unsigned long int nSkip=0 ); + + /** Gets the total capacity of the hashtable. This is actually the number + * of total positions available inside the hashtable at the moment. This + * will change when the hashtable's load exceeds the danger level. + * Please note that this is NOT the actual amount of space available. + * In reality you can only access about 45-50 percent of that space. + *@returns The total capacity. + */ + unsigned long int getCapacity(); + + /** Gets the number of filled in items in the hash table. This is roughly + * equivelent to the getSize function assosiated with the Lists. + *@returns The number of filled in items in the hash table. + */ + unsigned long int getSize(); + + /** Gets the load (percentage) of filled in items in the table. This is + * technically the size divided by the capacity, but is definately usefull + * since it's required to check if it's time to rehash. + *@returns The table load in the range 0.0 to 1.0 + */ + double getLoad(); + + /** Sets up an xPos object for use indexing the items in the table. Call + * this first and follow the directions for getNextItemPos below to + * iterate through every item in the table, while avoiding the empty + * spaces. + *@returns A pointer to a xPos object telling the hashtable where to find + * the item you're looking at. + */ + void *getFirstItemPos(); + + /** Get the item's data that is being pointed to by xPos. This is only + * valid after xPos was created using getFirstItemPos and getNextItemPos + * was called at least once. + *@param xPos supplied by getFirstItemPos. + *@returns The key value that was used to insert the data into the table. + */ + const void *getItemData( void *xPos ); + + /** Get the item's ID that is being pointed to by xPos. This is only + * valid after xPos was created using getFirstItemPos and getNextItemPos + * was called at least once. + *@param xPos supplied by getFirstItemPos. + *@returns The key value that was used to insert the data into the table. + */ + const void *getItemID( void *xPos ); + + /** Used for iterating through a hash table sequentially. This will + * update the xPos pointer to point to the next time, all ready to + * be accessed with getItemID and getItemData. This must be called at + * least once before xPos is meaningful, and will return a NULL when it + * has reached the last item. + *@param xPos This must be an object created by a call to the function + * getFirstItemPos, and is only meaningful to the internal routines. + * Aborting a call in the middle (not running to the end of the table) + * may result in a memory leak at the moment. + *@returns xPos if still iterating through the list, otherwise it will + * return NULL when the end has been reached and the xPos variable has + * been deleted. + */ + void *getNextItemPos( void *xPos ); + + /** A helpful operator to make accessing items easier. Please note that + * this simply returns a pointer to the data stored internally, and cannot + * be used like the STL operator to store new data, use insert for that. + *@param id The identifier used to store the requested item. + *@returns The data value assosiated with the given id, or NULL if it + * wasn't found in the table. + */ + const void *operator[](const void *id); + + /** + * Delete the specified item from the hashtable. This actually keeps the + * data and marks it deleted. For all intents and purposes to the user it + * is deleted, except that the space is still used until a rehash is forced. + * This means that in hashtables where elements are being inserted and + * deleted frequently you may run into a higher rate of expansion. + *@param id The ID to delete. + *@param nSkip The number of similar id's to skip before deleting in a + * hashtable that allows duplicates. + *@returns True if the element was found and deleted, false otherwise. + */ + bool del( const void *id, int nSkip=0 ); + +private: + /** + * Contains info related to a position in the hashtable. Used for + * searching through hashtables one item at a time, in order. This class + * should never be created by anything but a HashTable, and should never + * be referenced directly. Instead the hashtable returns a void pointer, + * which is what should be passed back in next time you use a search + * function. Always finish a search, since the object is deleted at the + * end of the search. + *@author Mike Buland + */ + class HashPos + { + public: + /** Create a blank HashPos. */ + HashPos() { bStarted=false; nPos = 0; }; + /** Has the search been started? */ + bool bStarted; + /** The position (index) into the backend storage structure. */ + unsigned long int nPos; + }; + + /** + * All data related to a single element in the hashtable. This should + * really only be used and manipulated by the HashTable itself. + *@author Mike Buland + */ + typedef struct HashNode + { + public: + /** Create a new, empty HashNode. */ + HashNode() { id = NULL; data = NULL; bDeleted = false; }; + /** A pointer to the original ID that was used to key the data. */ + const void *id; + /** A pointer to the data stored along with the above ID. */ + const void *data; + /** Weather or not this data should really...exist */ + bool bDeleted; + } HashNode; + +private: + /** + * Just sets the values in the element to some friendly values. + *@param newID The new ID to store. + *@param newData The new Data to store. + */ + void set( int j, const void *newID, const void *newData ); + /** + * Tells you if the node is filled or not. + *@returns True=an ID has been stored here, False=no ID. + */ + bool isFilled( int j ); + /** + * This actually resizes, but since every resize requires a reHash to go + * along with it, that's the name. This actually creates a new buffer for + * all of the contained data and then pulls every old element that was in + * the old table out and performs the hashing placement calculations again. + * This function skips all data that was marked as deleted, so at this + * point it really will be. + *@param nNewSize The new size to set the table to while re-hashing. + *@returns True if the operation was successful, false otherwise. + */ + bool reHash( unsigned long int nNewSize ); + + /** + * Helper function to allocate a new table. Really just does the memory + * allocation. + *@param nNewSize The size of the table to generate. + *@returns A new, blank array of HashNode objects the size you specified. + */ + HashNode *newTable( unsigned long int nNewSize ); + + /** + * This function is used once an actual hash code is obtained. nStart is + * the given hash code, which is then wrapped to the size of the table. If + * there is data at that location, tests are performed to see if it's the + * right one. If it is, then it is returned, otherwise a series of further + * tests based on a 2^n search pattern is performed. The position of the + * requested data in the back-end storage is returned if found, otherwise + * another less useful value is returned... + *@param nStart The initial hashcode of the ID testing for. + *@param id A pointer to the id that is being searched for. + *@returns The real location of the data requested. + */ + unsigned long int probe( unsigned long int nStart, const void *id ); + + /** + * Simple helper function to determine if a number is prime or not. + * This function runs in sqrt(n) time. + *@param num Number to test for prime-hood. + *@returns True if the number is prime, false otherwise. + */ + bool isPrime( int num ); + + /** + * Given any number, this function finds the first number after it that is + * prime. Since this number is a multiple internally it's rare that the + * starting number would be prime. + *@param base The number to start the prime search on. + *@returns The first prime after the number given. + */ + int nextPrime( int base ); + +#ifdef HASH_DEBUG_VIS + void printDebugLine( const char *exData ); +#endif + + /** A pointer to the HashFunction subclass instance to use. */ + HashFunction *hFunc; + /** The complete array of HashNode objects to store data in. */ + HashNode *aTable; + /** The actual size of the table, not how many elements are in it. */ + unsigned long int nTableSize; + /** The number of elements that are in the table. */ + unsigned long int nSize; + /** The number of elements that are unavailable now. */ + unsigned long int nFilled; + /** Allow duplicate ID's in the table. */ + bool bAllowDupes; +}; + +#endif diff --git a/src/http.cpp b/src/http.cpp new file mode 100644 index 0000000..11950b7 --- /dev/null +++ b/src/http.cpp @@ -0,0 +1,371 @@ +#include +#include +#include "http.h" +#include "hashfunctionstring.h" + +Http::Http( Connection *pConnection ) : hReqHeader( new HashFunctionString(), 100 ) +{ + pCon = pConnection; + nParseState = parseInit; +} + +Http::~Http() +{ + for( int j = 0; j < lStrings.getSize(); j++ ) + { + delete (std::string *)lStrings[j]; + } +} + +bool Http::parseRequest() +{ + for(;;) + { + pCon->readInput(); + switch( nParseState ) + { + case parseInit: + { + int nLen = pCon->scanInputFor( CR ); + if( nLen == -1 ) + { + return false; + } + else + { + nReqType = getRequestType( pCon->getInput() ); + pCon->usedInput( pCon->scanInputFor(' ')+1 ); + + nLen = pCon->scanInputFor(' '); + sReqURI.append( pCon->getInput(), nLen ); + pCon->usedInput( nLen+1 ); + + if( !strncmp( pCon->getInput(), "HTTP/", 5 ) ) + { + char mbuf[2]={'\0','\0'}; + unsigned char major, minor; + + pCon->usedInput( 5 ); + mbuf[0] = pCon->getInput()[0]; + major = (unsigned char)atoi(mbuf); + mbuf[0] = pCon->getInput()[2]; + minor = (unsigned char)atoi(mbuf); + setRequestVersion( major, minor ); + if( checkRequestVer() ) + { + nParseState = parseHeader; + } + else + { + setResponseStatus( statusHTTPVersionNotSupported ); + printf("Verson not supported.\n"); + return true; + } + + pCon->usedInput( 5 ); + } + else + { + setResponseStatus( statusBadRequest ); + } + + //return false; + } + } + break; + + case parseHeader: + { + int nLen = pCon->scanInputFor( CR ); + if( nLen == -1 ) + { + return false; + } + else if( nLen == 0 ) + { + // We've got our double-newline, time for content. + pCon->usedInput( 2 ); + setResponseStatus( statusOK ); + return true; + } + else + { + nLen = pCon->scanInputFor(':'); + if( nLen == -1 ) + { + printf("No colon? what are you trying to pull?\n"); + } + else + { + std::string *pName = new std::string( pCon->getInput(), nLen ); + lStrings.append( pName ); + pCon->usedInput( nLen+1 ); + + nLen = pCon->scanInputFor( CR ); + std::string *pValue = convSpaceString( pCon->getInput(), nLen ); + lStrings.append( pValue ); + pCon->usedInput( nLen+2 ); + + hReqHeader.insert( + pName->c_str(), + pValue->c_str() + ); + + printf("::%s = \"%s\"\n", + pName->c_str(), + pValue->c_str() + ); + } + } + } + break; + + case parseFinished: + break; + } + } +} + +bool Http::buildResponse( short nResponseCode, const char *sResponse ) +{ + if( nResponseCode > 0 ) + { + nResStatus = nResponseCode; + } + + if( sResponse == NULL ) + { + sResStatusStr = "uh yeah"; + } + else + { + sResStatusStr = sResponse; + } + + time_t curTime; + time( &curTime ); + gmtime_r( &curTime, &tResTime ); + + sServerStr = "YFHttp/0.0.1"; + bResPersistant = false; + + //char buf[30]; + //strftime( buf, 30, "%a, %d %b %Y %H:%M:%S GMT", &tResponseTime ); + + return true; +} + +bool Http::sendResponse() +{ + char buf[256]; + + sprintf( buf, "HTTP/1.1 %d %s\r\n", nResStatus, sResStatusStr.c_str() ); + pCon->appendOutput( buf ); + + strftime( buf, 256, "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", &tResTime ); + pCon->appendOutput( buf ); + + sprintf( buf, "Server: %s\r\n", sServerStr.c_str() ); + pCon->appendOutput( buf ); + + if( bResPersistant ) + { + } + else + { + pCon->appendOutput("Connection: close\r\n"); + } + + sprintf( buf, "Content-Type: %s\r\n", sResMime.c_str() ); + pCon->appendOutput( buf ); + + sprintf( buf, "Content-Length: %d\r\n", sResContent.size() ); + pCon->appendOutput( buf ); + + pCon->appendOutput("\r\n"); + + pCon->appendOutput( sResContent.c_str(), sResContent.size() ); + + return true; +} + +void Http::setResponsePersistant( bool bPersistant ) +{ + bResPersistant = bPersistant; +} + +void Http::setResponseContent( const char *sMime, const char *sContent, int nLen ) +{ + sResMime = sMime; + sResContent.erase(); + sResContent.append( sContent, nLen ); +} + +std::string *Http::convSpaceString( const char *sStr, int nLen ) +{ + int nNewLen = 0; + bool bStart = true; + bool bSpace = false; + + for( int j = 0; j < nLen; j++ ) + { + if( sStr[j] == ' ' || sStr[j] == '\t' ) + { + if( bStart ) + { + } + else if( bSpace == false ) + { + bSpace = true; + nNewLen++; + } + } + else + { + bStart = false; + bSpace = false; + nNewLen++; + } + } + if( bSpace ) + { + nNewLen--; + } + + std::string *pSStr = new std::string; + //char *pStr = pSStr->c_str(); + nNewLen = 0; + bStart = true; + bSpace = false; + + for( int j = 0; j < nLen; j++ ) + { + if( sStr[j] == ' ' || sStr[j] == '\t' ) + { + if( bStart ) + { + } + else if( bSpace == false ) + { + bSpace = true; + *pSStr += ' '; + //pStr[nNewLen++] = ' '; + } + } + else + { + bStart = false; + bSpace = false; + *pSStr += sStr[j]; + //pStr[nNewLen++] = sStr[j]; + } + } + if( bSpace == true ) + { + nNewLen--; +// pStr[nNewLen] = '\0'; + } + + return pSStr; +} + +const char *Http::getRequestURI() +{ + return sReqURI.c_str(); +} + +short Http::getRequestType( const char *sType ) +{ + if( !strncmp( sType, "OPTIONS", 7 ) ) + { + return reqOptions; + } + else if( !strncmp( sType, "GET", 3 ) ) + { + return reqGet; + } + else if( !strncmp( sType, "HEAD", 4 ) ) + { + return reqHead; + } + else if( !strncmp( sType, "POST", 4 ) ) + { + return reqPost; + } + else if( !strncmp( sType, "PUT", 3 ) ) + { + return reqPut; + } + else if( !strncmp( sType, "DELETE", 6 ) ) + { + return reqDelete; + } + else if( !strncmp( sType, "TRACE", 5 ) ) + { + return reqTrace; + } + else if( !strncmp( sType, "CONNECT", 7 ) ) + { + return reqConnect; + } + else + { + printf(" Uh oh, extension!\n"); + return reqExtension; + } +} + +const char *Http::getRequestType( short nType ) +{ + switch( nType ) + { + case reqOptions: return "OPTIONS"; + case reqGet: return "GET"; + case reqHead: return "HEAD"; + case reqPost: return "POST"; + case reqPut: return "PUT"; + case reqDelete: return "DELETE"; + case reqTrace: return "TRACE"; + case reqConnect: return "CONNECT"; + case reqExtension: return "EXTENSION"; + default: return "INVALID VALUE"; + } +} + +short Http::getRequestType() +{ + return nReqType; +} + +const char *Http::getRequestTypeStr() +{ + return getRequestType( nReqType ); +} + +void Http::setResponseStatus( short nStatus ) +{ + nResStatus = nStatus; +} + +void Http::setRequestVersion( unsigned char nMajor, unsigned char nMinor ) +{ + cReqVersion = (nMajor<<4)|nMinor; +} + +unsigned char Http::getRequestMinorVer() +{ + return cReqVersion&0x0F; +} + +unsigned char Http::getRequestMajorVer() +{ + return cReqVersion>>4; +} + +bool Http::checkRequestVer() +{ + if( cReqVersion == HTTP11 ) + return true; + return false; +} + diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..4ee4470 --- /dev/null +++ b/src/http.h @@ -0,0 +1,271 @@ +/**\file http.h + * Describe a Hyper Text Transfer Protocol processor. This class will allow + * any program to act as either an HTTP server, client, or both. It contains + * a number of additional helpers and subclasses. + *@author Mike Buland + */ + +#ifndef HTTP_H +#define HTTP_H + +#include +#include "connection.h" +#include "linkedlist.h" +#include "hashtable.h" + +#define CR '\r' /**< The ASCII value of a Carrage Return */ +#define LF '\n' /**< The ASCII value of a Line Feed */ +#define CRLF CR LF /**< Combo of CR+LF for use in http */ + +/** + * Macro to create combined http version codes. This just makes processing a + * little bit faster for the most part. + *@param maj Major version number, between 0 and 15 + *@param min Minor version number, between 0 and 15 + *@returns A one byte combined version number suitable for use in switches. + */ +#define HTTPVER( maj, min ) ((maj<<4)|(min)) + +#define HTTP10 HTTPVER( 1, 0 ) /**< Combined version code for http 1.0 */ +#define HTTP11 HTTPVER( 1, 1 ) /**< Combined version code for http 1.1 */ + +/** + * This is the master HTTP processing class. One instance handles one + * transaction, in the future a different mechanism may be thought up, but for + * now this means that you must create multiple objects to handle a single + * connection that contains multiple requests. + * In the constructor the Http class is given a connection object. This object + * should already be initialized and connected to whatever socket it wants to + * be sending and receiving data to and from. Once that's done you can call + * parseRequest if you're acting as a server, or a variety of buildRequest + * functions to create and send a request if you're a client. + * Please note that this class does not provide any HTTP or extended format + * processing systems, but will allow for mime types tables to be registered. + *@author Mike Buland + */ +class Http +{ +public: + /** + * Create an Http object tied to an existing connection object. + *@param pConnection The live connection object to deal with. + */ + Http( Connection *pConnection ); + + /** + * Standard Deconstructor. + */ + ~Http(); + + /** + * Perform all parsing needed to figure out what an HTTP client wants from + * us. This will setup a number of properties in the Http object itself + * and has the possibility of setting one or more response states initially. + * These states should be checked for immediately after parsing to see if + * an appropriate error message should be generated. These errors can + * include issues with protocol, data formats, or unknown versions of the + * protocol. + *@returns True means that all processing is finished, false means that + * the parseRequest function should be called again when more data is + * ready. A return value of true does not indicate success, only that + * processing is finished, the getResponseStatus function should be called + * to see what status was set in the parse routine. A 200 indicates that + * as far as the parser is concerned, everything when smoothly. Otherwise + * it's your responsibility to build the appropriate error response body + * (like an html file) and send it as the response. + */ + bool parseRequest(); + + /** + * Get a request type's internal Http object id based on the string + * representation. These can be any HTTP/1.1 standard request type. + *@param sType The string that should be checked for type. This is in all + * caps, just like if it came from the HTTP client, which is most often + * the case. + *@returns The numerical ID of the given request type. Please note that + * HTTP/1.1 standard specifies that any string is valid here as long as + * the non-basic string is a request type understood by the serving + * software. This means that anything that is non-standard will return + * a type reqExtension and not an error. This is not a mistake. + */ + short getRequestType( const char *sType ); + + /** + * Get the string representation of an Http object request type integer ID. + * This is used mainly for debugging to be sure the system has what we + * think it has. + *@param nType The integer ID of the request type to process. + *@returns The HTTP/1.1 string representation of that Http object ID code. + */ + const char *getRequestType( short nType ); + + /** + * Returns the Http object request type ID code that is stored in the + * object by either the parseRequest function or use of the buildRequest + * functions. + *@returns The ID of the request type stored in the object. + */ + short getRequestType(); + + /** + * Same as getRequestType, only returns the string representation. + *@returns The string representation of the request type ID stored in the + * object. + */ + const char *getRequestTypeStr(); + + /** + * Sets the version of the request used by the system. This will be used + * by parse request, but is also part of the buildRequest tool functions. + *@param nMajor The major version number. + *@param nMinor The minor version number. + */ + void setRequestVersion( unsigned char nMajor, unsigned char nMinor ); + + /** + * Gets the major version number of the protocol used/to be used in this + * request. + *@returns The major version number of the request protocol. + */ + unsigned char getRequestMinorVer(); + + /** + * Gets the minor version number of the protocol used/to be used in this + * request. + *@returns The minor version number of the request protocol. + */ + unsigned char getRequestMajorVer(); + + /** + * Checks the stored request version against an internal table of supported + * protocol versions. + *@returns True if the protocol version is supported, false otherwise. + */ + bool checkRequestVer(); + + /** + * Converts an arbitrary string to a new string object with space saving + * operations performed ala the HTTP/1.1 specs. All leading and trailing + * whitespace is stripped, and all whitespace within the string is reduced + * to a single space char. + *@param sStr A pointer to the string data to process. + *@param nLen The length of the string to process. Since this function is + * often called on stream data, there is no null terminator where we need + * one. This is here for convinience so the data doesn't need to be hacked + * up or moved to an intermediate buffer. + *@returns A new string that may well be shorter than the original but that + * will have the same value as far as the HTTP/1.1 specs are concerned. + */ + std::string *convSpaceString( const char *sStr, int nLen ); + + /** + * Gets a string pointer to the URI that was/is being requested. This can + * be any RFC standard URI, with or without protocol and domain. + *@returns A pointer to the URI that was/is being requested. + */ + const char *getRequestURI(); + + /** + * Set a new response status. This status can be anything that the HTTP + * specs allow. Other values are allowed as well, but beware, not all + * servers/clients will accept values that are not in the tables in this + * class. + *@param nStatus The status to set. + */ + void setResponseStatus( short nStatus ); + + bool buildResponse( short nResponseCode=-1, const char *sResponse=NULL ); + void setResponseContent( const char *sMime, const char *sContent, int nLen ); + void setResponsePersistant( bool bPersistant ); + bool sendResponse(); + + enum + { + reqOptions, + reqGet, + reqHead, + reqPost, + reqPut, + reqDelete, + reqTrace, + reqConnect, + reqExtension + }; + + enum + { + statusContinue = 100, + statusSwitchProto = 101, + + statusOK = 200, + statusCreated = 201, + statusAccepted = 202, + statusNonAuthInfo = 203, + statusNoContent = 204, + statusResetContent = 205, + statusPartialContent = 206, + + statusMultiChoices = 300, + statusMovedPermanently = 301, + statusFound = 302, + statusSeeOther = 303, + statusNotModified = 304, + statusUseProxy = 305, + statusUnused = 306, + statusTempRedirect = 307, + + statusBadRequest = 400, + statusUnauthorized = 401, + statusPaymentRequired = 402, + statusForbidden = 403, + statusNotFound = 404, + statusMethodNotAllowed = 405, + statusNotAcceptable = 406, + statusProxyAuthRequired = 407, + statusRequestTimeout = 408, + statusConflict = 409, + statusGone = 410, + statusLengthRequired = 411, + statusPreconditionFailed = 412, + statusRequestEntityTooLarge = 413, + statusRequestURITooLong = 414, + statusUnsupportedMediaType = 415, + statusRequestedRangeNotSatisfiable = 416, + statusExpectationFailed = 417, + + statusInternalServerError = 500, + statusNotImplemented = 501, + statusBadGateway = 502, + statusServiceUnavailable = 503, + statusGatewayTimeout = 504, + statusHTTPVersionNotSupported = 505 + }; + +private: + Connection *pCon; + unsigned char nParseState; + + short nReqType; + std::string *pReqStr; + std::string sReqURI; + unsigned char cReqVersion; + HashTable hReqHeader; + LinkedList lStrings; + + std::string sServerStr; + std::string sResMime; + std::string sResContent; + std::string sResStatusStr; + bool bResPersistant; + struct tm tResTime; + short nResStatus; + + enum + { + parseInit, + parseHeader, + parseFinished + }; +}; + +#endif diff --git a/src/linkedlist.cpp b/src/linkedlist.cpp new file mode 100644 index 0000000..78a615a --- /dev/null +++ b/src/linkedlist.cpp @@ -0,0 +1,227 @@ +/*************************************************************************** + linkedlist.cpp - description + ------------------- + begin : Sun Oct 19 2003 + copyright : (C) 2003 by Mike Buland + email : eichlan@yf-soft.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "linkedlist.h" + +LinkedList::LinkedList( ) +{ + pBase = NULL; + pTop = NULL; + pLast = NULL; + nSize = 0; + nLast = -1; +} + +LinkedList::~LinkedList( ) +{ +/* + Link *pCur = pBase; + while( pCur ) + { + Link *pLast = pCur; + pCur = pCur->pNext; + delete pLast; + } +*/ + empty(); +} + +void *LinkedList::getAt( int index ) +{ + if( index < 0 || index >= nSize ) + return NULL; + + return getPtrTo( index )->pData; +} + +void LinkedList::append( void *data ) +{ + if( pBase == NULL ) + { + pBase = new Link( data ); + pTop = pBase; + nSize++; + } + else + { + pTop->pNext = new Link( data ); + pTop = pTop->pNext; + nSize++; + } +} + +void LinkedList::insertBefore( void *data, int pos ) +{ + if( pos < 0 || pos > nSize ) + return; + + if( pos == 0 ) + { + Link *pTmp = new Link( data, pBase ); + if( pBase == NULL ) + { + pTop = pTmp; + } + pBase = pTmp; + if( nLast >= 0 ) nLast++; + nSize++; + } + else + { + Link *pCur; + if( (pCur = getPtrTo( pos-1 )) == NULL ) + { + return; + } + Link *pNew = new Link( data, pCur->pNext ); + pCur->pNext = pNew; + if( pNew->pNext == NULL ) + { + pTop = pNew; + } + if( nLast >= pos ) nLast++; + nSize++; + } +} + +int LinkedList::getSize( ) +{ + return nSize; +} + +bool LinkedList::isEmpty( ) +{ + if( nSize == 0 ) + return true; + return false; +} + +void LinkedList::deleteAt( int index ) +{ + if( index >= nSize || + pBase == NULL ) + return; + + if( index == 0 ) + { + Link *pTmp = pBase->pNext; + delete pBase; + pBase = pTmp; + if( nLast >= 0 ) nLast--; + nSize--; + if( pBase == NULL ) + { + pTop = NULL; + } + else if( pBase->pNext == NULL ) + { + pTop = pBase; + } + } + else + { + Link *pCur = getPtrTo( index-1 ); + if( pCur->pNext == pTop ) + { + pTop = pCur; + } + Link *pTmp; + if( pCur->pNext == NULL ) + { + pTmp = NULL; + } + else + { + pTmp = pCur->pNext->pNext; + } + delete pCur->pNext; + pCur->pNext = pTmp; + if( nLast == index ) nLast = -1; + else if( index < nLast ) nLast--; + nSize--; + } +} + +void LinkedList::empty() +{ + while( nSize > 0 ) + { + deleteAt( 0 ); + } +} + +void LinkedList::setSize( int newSize ) +{ + if( newSize < nSize ) + { + // Delete items off of the end of the list. + while( nSize > newSize ) + { + deleteAt( nSize-1 ); + } + } + else + { + // Add null items to the end of the list. + while( nSize < newSize ) + { + append( NULL ); + } + } +} + +void LinkedList::setAt( int index, void *data ) +{ + if( index >= nSize || index < 0 ) + return; + + getPtrTo( index )->pData = data; +} + +LinkedList::Link *LinkedList::getPtrTo( int index ) +{ + if( index < 0 || index >= nSize ) + return NULL; + if( index == nLast ) + { + return pLast; + } + if( index == 0 ) + { + pLast = pBase; + nLast = 0; + return pBase; + } + else + { + Link *pCur = pBase; + int nCur = 0; + if( nLast < index && nLast >= 0 ) + { + pCur = pLast; + nCur = nLast; + } + while( nCur != index ) + { + pCur = pCur->pNext; + nCur++; + } + nLast = index; + pLast = pCur; + return pCur; + } +} diff --git a/src/linkedlist.h b/src/linkedlist.h new file mode 100644 index 0000000..c45cc9b --- /dev/null +++ b/src/linkedlist.h @@ -0,0 +1,87 @@ +/**@file + * Describes the LinkedList implementation of the List ADT. + *@author Mike Buland + */ + +#ifndef LINKEDLIST_H +#define LINKEDLIST_H + +#include +#include "list.h" + +/** A linked-item implementation of the List ADT. Since the data is linked + * sequentially this is a great choice for lists that will grow and shrink + * a lot, but don't require as much random access. This implementation + * includes optomizations that make iterating through data, and appending + * items to the list take O(1) time. + *@author Mike Buland + */ +class LinkedList : public List +{ +public: + /** + * Construct a blank LinkedList. + */ + LinkedList(); + + /** + * Delete all list data, but do not delete any of the contained elements. + */ + ~LinkedList(); + + void *getAt( int nIndex ); + void append( void *pData ); + void insertBefore( void *pData, int nPos = 0 ); + int getSize( ); + bool isEmpty( ); + void deleteAt( int nIndex ); + void empty(); + void setSize( int nNewSize ); + void setAt( int nIndex, void *pData ); + +private: + /** + * A link in the linked list. + */ + class Link + { + public: + /** + * Construct an empty link. + */ + Link() + { + pData = NULL; + pNext = NULL; + } + /** + * Construct a link filled in with useful data. + *@param newData The data this link should hold. + *@param newNext The next link that this link should point to. + */ + Link( void *newData = NULL, Link * newNext = NULL ) + { + pData = newData; + pNext = newNext; + } + void *pData; /**< A pointer to the contained data. */ + Link *pNext; /**< A pointer to the next link in the chain */ + }; + + /** + * Finds a pointer to the link at index index. This is the core function + * called for all seek operations, and has been optimized as heavily as + * possible. + *@param index The zero-based index of the desired element. + *@returns A pointer to the requested Link, or NULL if it isn't found. + */ + Link *getPtrTo( int index ); + Link *pBase; /**< The first link in the list. */ + Link *pTop; /**< The Last link in the list. */ + Link *pLast; /**< The previously requested link. */ + int nSize; /**< The number of contained links. */ + int nLast; /**< The index of the previously requested link. */ +}; + +#endif + diff --git a/src/linkmessage.cpp b/src/linkmessage.cpp new file mode 100644 index 0000000..ce838f5 --- /dev/null +++ b/src/linkmessage.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2003 by Mike Buland * + * eichlan@yf-soft.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ +#include "linkmessage.h" +#include + +LinkMessage::LinkMessage( int nNewMsg ) +{ + nMsg = nNewMsg; +} + +LinkMessage::~LinkMessage() +{ +} + +/* +void LinkMessage::setBroadcast( bool bOn ) +{ + bBroadcast = bOn; +} + +bool LinkMessage::isBroadcast() +{ + return bBroadcast; +} + + +void LinkMessage::setFromID( int id ) +{ + nFromLinkID = id; +} + +int LinkMessage::getFromID() +{ + return nFromLinkID; +} + +void LinkMessage::setToID( int id ) +{ + nTargetLinkID = id; +} + +int LinkMessage::getToID() +{ + return nTargetLinkID; +} +*/ diff --git a/src/linkmessage.h b/src/linkmessage.h new file mode 100644 index 0000000..6cdfb2f --- /dev/null +++ b/src/linkmessage.h @@ -0,0 +1,39 @@ +/**\file linkmessage.h + */ + +#ifndef LINKMESSAGE_H +#define LINKMESSAGE_H + +/** + * A message to be broadcast accross ProgramLinks in a ProgramChain. Generally + * one would make a subclass of this in order to transmit more useful + * information, but sometimes it isn't necesarry. + *@author Mike Buland + */ +class LinkMessage +{ +public: + /** + * Construct a blank LinkMessage. + */ + LinkMessage() {}; + + /** + * Deconstruct a LinkMessage. + */ + virtual ~LinkMessage(); + + /** + * Create a LinkMessage object with a specific message assosiated with it + * to start with. + *@param nNewMsg The message to use in the Message object. + */ + LinkMessage( int nNewMsg ); + + /** + * The message contained in the Message object. + */ + int nMsg; +}; + +#endif diff --git a/src/list.cpp b/src/list.cpp new file mode 100644 index 0000000..c8b88c1 --- /dev/null +++ b/src/list.cpp @@ -0,0 +1,27 @@ +/*************************************************************************** + list.cpp - description + ------------------- + begin : Sun Oct 19 2003 + copyright : (C) 2003 by Mike Buland + email : eichlan@yf-soft.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "list.h" + +List::List( ) +{ +} + +List::~List( ) +{ +} + diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..c71b328 --- /dev/null +++ b/src/list.h @@ -0,0 +1,101 @@ +#ifndef LIST_H +#define LIST_H + + +/** The basic List class ADT. This, on it's own, does absolutely nothing, but + * does define all standard interface functions to access a list. + *@author Mike Buland + */ +class List +{ +public: + /** + * Construct a list. + */ + List(); + + /** + * Desconstruct a list. + */ + virtual ~List(); + + /** Gets the value at a specified index. + *@param nIndex The index of the item to return. + *@returns The specified item, or NULL if the index was beyond the range + * of the list. + *@author Mike Buland + */ + virtual void *getAt( int nIndex ) = 0; + + /** Append the given data to the end of the list. This increases the + * size of the list by one. + *@param pData The data to append to the list. + *@author Mike Buland + */ + virtual void append( void *pData ) = 0; + + /** Inserts an item at the specified position in the list. The + * new item takes the index that you specify, and all other items + * are moved up one position. The size of the list is increased by + * one. + *@param pData The value to insert into the list. + *@param nPos Where to insert the data into the list. + *@author Mike Buland + */ + virtual void insertBefore( void *pData, int nPos = 0 ) = 0; + + /** Determines the size of the list, in elements. + *@returns The size of the list. + *@author Mike Buland + */ + virtual int getSize( ) = 0; + + /** Determines if the list is empty or not. + *@returns True if the list is empty, or false if the list has + * data in it (if the size is greater than zero). + *@author Mike Buland + */ + virtual bool isEmpty( ) = 0; + + /** Deletes an item at the specified index and moves all other + * values down one index. The size of the list is decreased by one. + *@param nIndex The index of the item to delete. + *@author Mike Buland + */ + virtual void deleteAt( int nIndex ) = 0; + + /** Completely empties the list, and sets the effective size to + * zero. + *@author Mike Buland + */ + virtual void empty() = 0; + + /** Sets the size of the list. This can be larger or smaller + * than what it was previously. If larger, new blank items will + * be added to the end of the list. If smaller than the old list + * items will be deleted from the end. + *@param nNewSize The new size of the list. + *@author Mike Buland + */ + virtual void setSize( int nNewSize ) = 0; + + /** Sets a member at a specified location to a new value. + * If the member being set is outside of the range of the + * current list it should be expanded. + *@param nIndex The zero-based index of the item to change. + *@param pData The new value for that index. + *@author Mike Buland + */ + virtual void setAt( int nIndex, void *pData ) = 0; + + /** Makes the List work like an array. Just say listObj[2] to get + * the third element. + *@param nIndex The index to access in the list. + *@returns A pointer to the data at element index. + *@author Mike Buland + */ + void *operator[]( int nIndex ) { return getAt( nIndex ); }; +}; + +#endif + diff --git a/src/md5.cpp b/src/md5.cpp new file mode 100644 index 0000000..ed7e4ac --- /dev/null +++ b/src/md5.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include "md5.h" + +// This is a fun macro that tells us where the length char goes after the data +// section in the padded data segment. It's short for OBfuscation LOCaction. +#define OBLOC(len) ((((len + 64) >> 9) << 4) + 14) +// This performs a wrapping bitwise shift, kinda' fun! + +#define bit_roll( num, cnt ) \ + (((num) << (cnt)) | (((num) >> (32 - (cnt))) & ~(-1<<(cnt)))) + +//#define md5_cmn( q, a, b, x, s, t ) (bit_roll((a + q + x + t), s) + b) + +// The following are handy wrappers for the cmn function +#define md5_ff( a, b, c, d, x, s, t ) \ + (md5_cmn((b & c) | ((~b) & d), a, b, x, s, t)) + +#define md5_gg( a, b, c, d, x, s, t ) \ + (md5_cmn((b & d) | (c & (~d)), a, b, x, s, t)) + +#define md5_hh( a, b, c, d, x, s, t ) \ + (md5_cmn(b ^ c ^ d, a, b, x, s, t)) + +#define md5_ii( a, b, c, d, x, s, t ) \ + (md5_cmn(c ^ (b | (~d)), a, b, x, s, t)) + +inline long md5_cmn( long q, long a, long b, long x, long s, long t ) +{ + return bit_roll((a + q + x + t), s) + b; +} + +md5::md5() +{ +} + +md5::~md5() +{ +} + +/* + * Calculate the MD5 of an array of little-endian words, and a bit length + */ +void md5::core_md5( long *x, long len, md5sum *output ) +{ + long a = 1732584193, olda; + long b = -271733879, oldb; + long c = -1732584194, oldc; + long d = 271733878, oldd; + + for( long i = 0; i < len; i += 16 ) + { + olda = a; + oldb = b; + oldc = c; + oldd = d; + + a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = a + olda; + b = b + oldb; + c = c + oldc; + d = d + oldd; + } + + output->data[0] = a; + output->data[1] = b; + output->data[2] = c; + output->data[3] = d; + delete[] x; +} + +long *md5::c2l( const char *str, long len, long *nNewLen ) +{ + long len8 = len*8; + long mlen = OBLOC( len8 ); + long flen = (((mlen/16)+((mlen%16)?(1):(0))))*16; + long *aBin = new long[flen]; + memset( aBin, 0, flen ); + + for( long i = 0; i < len8; i+=8 ) + { + aBin[i>>5] |= ((long)str[i/8]) << (i%32); + } + + aBin[len8 >> 5] |= 0x80 << ((len8) % 32); + aBin[OBLOC( len8 )] = len8; + + (*nNewLen) = flen; + + return aBin; +} + +void md5::l2hexstr( long *binarray, char *str ) +{ + static const char hex_tab[] = {"0123456789abcdef"}; + //static char str[33]; + + int k = 0; + for( int i = 0; i < 16; i++) + { + str[k++] = hex_tab[(binarray[i>>2] >> ((i%4)*8+4)) & 0xF]; + str[k++] = hex_tab[(binarray[i>>2] >> ((i%4)*8 )) & 0xF]; + } +} + +void md5::sumString( md5sum *pSum, const char *sStr ) +{ + sumData( pSum, sStr, strlen( sStr ) ); +} + +void md5::sumData( md5sum *pSum, const char *aData, long nLen ) +{ + long nNewLen; + long *aOb = c2l( aData, nLen, &nNewLen ); + core_md5( aOb, nNewLen, pSum ); +} + +void md5::sumToHex( md5sum *pSum, char *sHex ) +{ + l2hexstr( pSum->data, sHex ); +} + diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 0000000..810345e --- /dev/null +++ b/src/md5.h @@ -0,0 +1,81 @@ +#ifndef MD5_H +#define MD5_H + +/** + * Used to store an MD5 sum in a handy container. + */ +typedef struct +{ + /** The actual data-storage for an MD5 sum. */ + long data[4]; +} md5sum; + +/** + * Class for easily calculating MD5 sums of just about any data. + *@author Mike Buland + */ +class md5 +{ +public: + /** Build an MD5 sum builder. */ + md5(); + + /** Deconstruct */ + ~md5(); + + /** + * Create a sum of a standard c string, null terminated. This is probably + * the easiest function to use. + *@param pSum The MD5 sum structure to fill up. + *@param sStr The null-terminated string to turn into an MD5 sum. + */ + void sumString( md5sum *pSum, const char *sStr ); + + /** + * Create a sum of an array of arbitrary data. This is the most handy for + * dealing with files and so on. + *@param pSum The MD5 sum structure to fill up. + *@param aData A pointer to the base of the data to sum. + *@param nLen The number of bytes to use in the sum. + */ + void sumData( md5sum *pSum, const char *aData, long nLen ); + + /** + * Convert an md5sum to standard hex representation. Make sure that sHex + * contains at least 17 characters of space. + *@param pSum The sum structure to convert to hex. + *@param sHex The string to store the hex value in. + */ + void sumToHex( md5sum *pSum, char *sHex ); + +private: + /** + * Do the bulk of the work of the md5 algorithm. + *@param x I'm not sure. I'll need to look it up. + *@param len The length of the data. + *@param output The sum structure to put the output in. + */ + void core_md5( long *x, long len, md5sum *output ); + + /** + * Convert an array of charaters to an array of longs in a very crafty way. + * This also applies standard MD5 obfuscation to the resulting array, and + * makes it fit within MD5 size constraints. + *@param str The data to convert. + *@param len The length of the data. + *@param nNewLen A pointer to a variable that will hold the new length of + * the resulting array of longs. + *@returns The newly obfuscated and resized long array. + */ + long *c2l( const char *str, long len, long *nNewLen ); + + /** + * Backend helper to convert an array of longs into a hex string. + *@param binarray The binary data to convert. + *@param str The string to store the hex string in. + */ + void l2hexstr( long *binarray, char *str ); + +}; + +#endif diff --git a/src/multilog.cpp b/src/multilog.cpp new file mode 100644 index 0000000..64ff967 --- /dev/null +++ b/src/multilog.cpp @@ -0,0 +1,143 @@ +/*************************************************************************** + multilog.cpp - description + ------------------- + begin : Sat Sep 6 2003 + copyright : (C) 2003 by Mike Buland + email : eichlan@yf-soft.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "multilog.h" +#include +#include +#include +#include + +#include "multilogchannel.h" + +// This section is what we need to make this a singleton +// this makes this class easy to use from anywhere, without +// worrying about re-creating every output form and all of that crazy jazz +MultiLog *MultiLog::singleLog = NULL; + +MultiLog *MultiLog::getLog() +{ + if( singleLog == NULL ) + { + singleLog = new MultiLog; + atexit( cleanup ); + } + return singleLog; +} + +void MultiLog::cleanup() +{ + if( singleLog != NULL ) + { + delete singleLog; + singleLog = NULL; + } +} + +MultiLog::MultiLog() +{ + lChannel = new LinkedList(); + rEntry = new RingList( 150 ); + nEntriesLost = 0; +} + +MultiLog::~MultiLog() +{ + int nMax = lChannel->getSize(); + for( int j = 0; j < nMax; j++ ) + { + ((MultiLogChannel *)lChannel->getAt(j))->closeLog(); + delete ((MultiLogChannel *)lChannel->getAt(j)); + } + delete lChannel; + + for( int j = 0; j < rEntry->getSize(); j++ ) + { + delete (LogEntry *)rEntry->getAt( j ); + } + delete rEntry; +} +/* +void MultiLog::Log( int nLevel, const char *lpFormat, ...) +{ + switch( nLevel ) + { + default: + break; + } + va_list ap; + va_start(ap, lpFormat); + + vprintf( lpFormat, ap ); + + va_end(ap); +}*/ + +void MultiLog::DetailLog( int nLevel, const char *lpFile, int nLine, const char *lpFunction, const char *lpFormat, ...) +{ + LogEntry *e = new LogEntry(); + + va_list ap; + va_start(ap, lpFormat); + char *text; + vasprintf( &text, lpFormat, ap ); + va_end(ap); + + time( &e->xTime ); + e->nLevel = nLevel; + e->nLine = nLine; + e->lpFile = new char[strlen(lpFile)+1]; + strcpy( e->lpFile, lpFile ); + e->lpText = new char[strlen(text)+1]; + strcpy( e->lpText, text ); + free( text ); + + append( e ); +} + +void MultiLog::append( LogEntry *pEntry ) +{ + rEntry->append( pEntry ); + if( rEntry->getPushBuf() ) + { + delete (LogEntry *)rEntry->getPushBuf(); + nEntriesLost++; + } + + for( int j = 0; j < lChannel->getSize(); j++ ) + { + ((MultiLogChannel *)lChannel->getAt( j ))->append( pEntry ); + } +} + +void MultiLog::addChannel( MultiLogChannel *pChannel ) +{ + lChannel->append( pChannel ); + + pChannel->openLog(); + + for( int j = 0; j < rEntry->getSize(); j++ ) + { + pChannel->append( (LogEntry *)rEntry->getAt( j ) ); + } +} + +MultiLog::LogEntry::~LogEntry() +{ + delete[] lpFile; + delete[] lpText; +} + diff --git a/src/multilog.h b/src/multilog.h new file mode 100644 index 0000000..30ad8d7 --- /dev/null +++ b/src/multilog.h @@ -0,0 +1,145 @@ +#ifndef MULTILOG_H +#define MULTILOG_H + +#include +#include +#include + +#include "ringlist.h" +#include "linkedlist.h" + +/** + * Calls the DetailLog function but includes pre-processor macros to fill in + * most of the fields for you. This makes your life a lot easier, and makes the + * log useful for system debugging as well as just letting people know what's + * going on. + *@param LEVEL The log level, comes from an enum in the MultiLog class. + *@param FORMAT The text to store in the log, using printf style formatting. + *@param ... Parameters to help format the text in the FROMAT param. + */ +#define LineLog( LEVEL, FORMAT, ...) DetailLog( LEVEL, __FILE__, __LINE__, __PRETTY_FUNCTION__, FORMAT, ##__VA_ARGS__ ) + +/** MultiLog keeps track of logfile info in a myriad of varieties, and is + * easily configurable between them all. It allows output to the standard + * output, error output, files, networks, and streams, which includes memory + * buffers. + * MultiLog uses the singleton pattern to keep only a single instance of + * the log. Instead of instantiating a new copy, call the getLog method. + *@author Mike Buland + */ +class MultiLog +{ +public: + /** + * Keeps track of a single log entry, in a standard format, that can be + * processed by any MultiLogChannel derrived class. + *@author Mike Buland + */ + typedef struct LogEntry + { + /** Safely delete a log entry. */ + ~LogEntry(); + time_t xTime; /**< The time the log entry was made. */ + int nLevel; /**< The log-level of the entry. */ + char *lpFile; /**< The name of the file this entry came from. */ + int nLine; /**< The line number that this log came from. */ + char *lpText; /**< The text content of this log entry. */ + } LogEntry; + +private: + /** + * Private constructor, this ensures that this is a singleton. + */ + MultiLog(); + + /** + * The only instance of MultiLog ever. + */ + static MultiLog *singleLog; + + /** + * Append a new logentry to the log list, possibly pushing an old entry off. + *@param pEntry The new entry to append. + */ + void append( LogEntry *pEntry ); + + /** + * The actual log entry storage mechanism. + */ + RingList *rEntry; + + /** + * The number of entries that have rolled off the end of the RingList. + */ + unsigned long nEntriesLost; + + /** + * A list of all channels that are registered with the MultiLog. + */ + LinkedList *lChannel; + +public: + /** + * Destroy the multilog. + *@todo Why is this public? Does it need to be? + */ + ~MultiLog(); + + /** Sends info to the logfile. + *@param nLevel The type of data being logged (error, info, etc.) + *@param lpFormat The data to send to the log. + *@author Mike Buland + */ + //void Log( int nLevel, const char *lpFormat, ...); + + /** Sends info to the logfile with extra information, including the files + * that it was called from and the line in the code. Besides that, it's + * exactly the same as Log. Please use the LineLog macro to make DetailLog + * really easy to use. It operates exacly like Log, but inserts the + * builtin macros as the lpFile and nLine parameters. + *@param nLevel The type of data being logged (error, info, etc.) + *@param lpFile The name of the file that called the log function. + *@param nLine The line in the file that this log function was called from. + *@param lpFunction The name of the function that called the log function. + *@param lpFormat The data to send to the log. + *@author Mike Buland + */ + void DetailLog( int nLevel, const char *lpFile, int nLine, const char *lpFunction, const char *lpFormat, ...); + + /** Gets a pointer to the only instantion of the MultiLog that can exist. + * If there is no instantion in existance, it creates one, so it's + * foolproof. + *@returns A pointer to the only MultiLog instantion. + *@author Mike Buland + */ + static MultiLog *getLog(); + + /** Performs standard cleanup and deletes the only instantiation of MultiLog + * that can exist. This is just the same as delete and will nicely close + * all open logs. always call this when you are done with your MultiLog. + */ + static void cleanup(); + + /** + * Adds a logging channel to the MultiLog channel chain. Every added + * channel will automatically receive a complete log of everything that + * happened before the channel was added as well as all future messages. + *@param pChannel A pointer to the pre-contructed channel object to add. + */ + void addChannel( class MultiLogChannel *pChannel ); + + /** The various pre-defined levels available to use when logging. + * The person logging can make up their own, just make sure to remember + * which value is which (all levels are integers). + *@author Mike Buland + */ + enum Levels + { + LError, + LWarning, + LInfo, + LStatus + }; +}; + +#endif diff --git a/src/multilogchannel.cpp b/src/multilogchannel.cpp new file mode 100644 index 0000000..ee4c9bf --- /dev/null +++ b/src/multilogchannel.cpp @@ -0,0 +1,13 @@ +// +// C++ Implementation: multilogchannel +// +// Description: +// +// +// Author: Mike Buland , (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include "multilogchannel.h" + diff --git a/src/multilogchannel.h b/src/multilogchannel.h new file mode 100644 index 0000000..d891a65 --- /dev/null +++ b/src/multilogchannel.h @@ -0,0 +1,46 @@ +#ifndef MULTILOGCHANNEL_H +#define MULTILOGCHANNEL_H + +#include "multilog.h" + +/** + * The baseclass for any MultiLog output channel. Any class that implements + * all of these functions can be put in the log chain and will be sent + * messages from active MultiLoggers. + *@author Mike Buland + */ +class MultiLogChannel +{ +public: + /** + * Deconstruct a MultiLogChannel. + */ + virtual ~MultiLogChannel() {}; + + /** + * Should perform any operations that need to take place in order to start + * the output of data into this channel. This will be called once by the + * MultiLog when the MultiLogChannel is registered. + *@returns True means that everything can go as planned. False means that + * the MultiLog should remove this channel from the list and delete it. + */ + virtual bool openLog() = 0; + + /** + * Should append a log entry to the long, by whatever means are necesarry. + *@param pEntry The LogEntry to append. + *@returns True means that everything can go as planned. False means that + * the MultiLog should remove this channel from the list and delete it. + */ + virtual bool append( MultiLog::LogEntry *pEntry ) = 0; + + /** + * Should perform any operations that need to take place in order to safely + * close and cleanup the log. + *@returns True means that everything can go as planned. False means that + * the MultiLog should remove this channel from the list and delete it. + */ + virtual bool closeLog() = 0; +}; + +#endif diff --git a/src/multilogtext.cpp b/src/multilogtext.cpp new file mode 100644 index 0000000..be64595 --- /dev/null +++ b/src/multilogtext.cpp @@ -0,0 +1,152 @@ + +#include +#include +#include +#include +#include +#include +#include "multilogtext.h" + +MultiLogText::MultiLogText( const char *sFileName, const char *lpFormat ) +{ + this->lpFormat = NULL; + nFD = open( sFileName, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH ); + setLogFormat( lpFormat ); +} + +MultiLogText::MultiLogText( int nFileDesc, const char *lpFormat ) +{ + this->lpFormat = NULL; + nFD = nFileDesc; + setLogFormat( lpFormat ); +} + +MultiLogText::~MultiLogText() +{ + if( nFD != -1 ) + { + close( nFD ); + } + + delete[] lpFormat; +} + +bool MultiLogText::setLogFormat( const char *lpFormat ) +{ + char buf[200]; + int k = 0; + static char fmts[10][4]={ + {'y', 'd', '0', '1'}, + {'m', 'd', '0', '2'}, + {'d', 'd', '0', '3'}, + {'h', 'd', '0', '4'}, + {'M', 'd', '0', '5'}, + {'s', 'd', '0', '6'}, + {'l', 'd', '0', '7'}, + {'f', 's', '0', '8'}, + {'L', 'd', '0', '9'}, + {'t', 's', '1', '0'}, + }; + + for( int j = 0; lpFormat[j] != '\0'; j++ ) + { + if( lpFormat[j] == '%' ) + { + buf[k++] = '%'; + int nPlace = k++; + k++; + buf[k++] = '$'; + bool bDone = false; + for( j++; bDone == false; j++ ) + { + int l; + for( l = 0; l < 10; l++ ) + { + if( lpFormat[j] == fmts[l][0] ) + { + buf[nPlace] = fmts[l][2]; + buf[nPlace+1] = fmts[l][3]; + buf[k++] = fmts[l][1]; + bDone = true; + break; + } + } + if( l == 10 ) + { + buf[k++] = lpFormat[j]; + } + } + j--; + } + else + { + buf[k++] = lpFormat[j]; + } + } + buf[k++] = '\n'; + buf[k] = '\0'; + + if( this->lpFormat != NULL ) + { + delete[] this->lpFormat; + } + this->lpFormat = new char[k+1]; + strcpy( this->lpFormat, buf ); + + return true; +} + +bool MultiLogText::openLog() +{ + if( nFD == -1 ) + { + return false; + } + return true; +} + +bool MultiLogText::append( MultiLog::LogEntry *pEntry ) +{ + if( nFD == -1 ) + { + return false; + } + + char *line = NULL; + struct tm *pTime; + pTime = localtime( &pEntry->xTime ); + asprintf( + &line, + lpFormat, + pTime->tm_year+1900, + pTime->tm_mon+1, + pTime->tm_mday, + pTime->tm_hour, + pTime->tm_min, + pTime->tm_sec, + pEntry->nLevel, + pEntry->lpFile, + pEntry->nLine, + pEntry->lpText + ); + write( nFD, line, strlen(line) ); + free( line ); + + return true; +} + +bool MultiLogText::closeLog() +{ + if( nFD == -1 ) + { + return false; + } + // Don't close it if it's sdtout or errorout + if( nFD > 2 ) + { + close( nFD ); + } + nFD = -1; + return true; +} + diff --git a/src/multilogtext.h b/src/multilogtext.h new file mode 100644 index 0000000..aa32405 --- /dev/null +++ b/src/multilogtext.h @@ -0,0 +1,70 @@ +#ifndef MULTILOGTEXT_H +#define MULTILOGTEXT_H + +#include "multilogchannel.h" + +/** + * Simple MultiLogChannel that takes the logdata, formats it textually, and + * writes it to a text device, either a file or the screen, yay! This takes + * the place of the old standard logging facility. + * The entries in the format follow the standard printf % style, and are as + * follows: + *
    + *
  • %y - current year
  • + *
  • %m - current month
  • + *
  • %d - current day
  • + *
  • %h - current hour (24-hour format)
  • + *
  • %M - current minute
  • + *
  • %s - current seccond
  • + *
  • %l - Loglevel (numerical)
  • + *
  • %f - Filename
  • + *
  • %L - Line number
  • + *
  • %t - Full text of the log entry
  • + *
+ *@author Mike Buland + */ +class MultiLogText : public MultiLogChannel +{ +public: + /** + * Construct a MultiLogText object around a specific filename and format. + * The file named by sFileName will be opened for writting in text+append + * mode. No existing data will be destroyed. + *@param sFileName The file to output log-data to. + *@param lpFormat The format using the above specifications to be used for + * every log entry. + */ + MultiLogText( const char *sFileName, const char *lpFormat ); + + /** + * Construct a MultiLogText object around a specific file and format. + * The file descriptor passed in should describe an already opened and set- + * up file or device. This could easily be a socket or stdout or stderr. + *@param nFileDesc The already opened descriptor to send data to. + *@param lpFormat The format using the above specifications to be used for + * every log entry. + */ + MultiLogText( int nFileDesc, const char *lpFormat ); + + /** + * Destroy the object. + */ + ~MultiLogText(); + + bool openLog(); + bool append( MultiLog::LogEntry *pEntry ); + bool closeLog(); + + /** + * Change the log format on the fly. + *@param lpFormat The new format to use for all future log entries. + *@returns True if everything was fine, false for catastrophic failure. + */ + bool setLogFormat( const char *lpFormat ); + +private: + int nFD; /**< The file descriptor we're writing to. */ + char *lpFormat; /**< The format that we're using, converted for printf. */ +}; + +#endif diff --git a/src/pproc.cpp b/src/pproc.cpp new file mode 100644 index 0000000..f5cb869 --- /dev/null +++ b/src/pproc.cpp @@ -0,0 +1,60 @@ +#include +#include +#include "pproc.h" + +void processParams( int argc, char *argv[], PPROC *pproc ) +{ + // Loop over all the params except the first, no params, no looping! + for( int j = 1; j < argc; j++ ) + { + //printf("Param[%d]: \"%s\"\n", j, argv[j] ); + if( argv[j][0] == '-' ) + { + if( argv[j][1] == '-' ) + { + // Proccess a long-word param string + for( int k = 0; + pproc[k].proc != NULL || pproc[k].stateVar != NULL; + k++ ) + { + if( !strcmp( pproc[k].lpWord, &argv[j][2] ) ) + { + if( pproc[k].proc != NULL ) + { + j += pproc[k].proc( argc-j, &argv[j] ); + } + if( pproc[k].stateVar != NULL ) + { + (*(pproc[k].stateVar)) = pproc[k].bSetState; + } + } + } + } + else + { + // Process a one char param string + for( int k = 0; + pproc[k].proc != NULL || pproc[k].stateVar != NULL; + k++ ) + { + if( pproc[k].cChar == argv[j][1] ) + { + if( pproc[k].proc != NULL ) + { + j += pproc[k].proc( argc-j, &argv[j] ); + } + if( pproc[k].stateVar != NULL ) + { + (*(pproc[k].stateVar)) = pproc[k].bSetState; + } + } + } + } + } + else + { + // Handle generic params here. + } + } +} + diff --git a/src/pproc.h b/src/pproc.h new file mode 100644 index 0000000..bf5063c --- /dev/null +++ b/src/pproc.h @@ -0,0 +1,35 @@ +#ifndef PPROC_H_ +#define PPROC_H_ + +/** + * Contains all required info to handle a single program parameter. + *@author Mike Buland + */ +typedef struct PPROC +{ + const char *lpWord; /**< The full text-word to use as a param. */ + const char cChar; /**< The short char version of the param. */ + /** + * Pointer to the function to call when this param is triggered. + *@param argc The number of params after and including the one that + * triggered this call. + *@param argv The array of commandline tokens to use as parameters. + *@returns 0 for everything is ok. A number greater than zero signals that + * this parameter function used n parameters and they should be skipped by + * the processParams function. + */ + int (*proc)( int argc, char *argv[] ); + bool *stateVar; /**< A pointer to a bool to be setwhen this is triggered */ + bool bSetState; /**< The state to set the above bool to. */ +} PPROC; + +/** + * Process command line parameters based on a null-terminated array of PPROC + * structures. + *@param argc Should come straight from your main function's argc. + *@param argv Should come straight from your main function's argv. + *@param pproc The array of params that this function can respond to. + */ +void processParams( int argc, char *argv[], PPROC *pproc ); + +#endif /*PPROC_H_*/ diff --git a/src/pqueue.cpp b/src/pqueue.cpp new file mode 100644 index 0000000..1f0b8b5 --- /dev/null +++ b/src/pqueue.cpp @@ -0,0 +1,33 @@ +#include "pqueue.h" + +PQueue::PQueue( int nNewNumQueues ) +{ + nNumQueues = nNewNumQueues; + aQueue = new Queue[nNumQueues]; +} + +PQueue::~PQueue() +{ + delete[] aQueue; +} + +void PQueue::enqueue( void *pData, int nQueueLevel ) +{ + if( nQueueLevel < 0 || nQueueLevel >= nNumQueues ) + return; + + aQueue[nQueueLevel].enqueue( pData ); +} + +void *PQueue::dequeue() +{ + for( int j = 0; j < nNumQueues; j++ ) + { + if( aQueue[j].isEmpty() == false ) + { + return aQueue[j].dequeue(); + } + } + + return NULL; +} diff --git a/src/pqueue.h b/src/pqueue.h new file mode 100644 index 0000000..1b45f75 --- /dev/null +++ b/src/pqueue.h @@ -0,0 +1,48 @@ +#ifndef PQUEUE_H +#define PQUEUE_H + +#include "queue.h" + +/** Priority queue. This is just like a queue, but something with a higher + * priority will always come off the queue before something with a lower + * priority, even if it's added after. Otherwise works just like a queue. + *@author Mike Buland + */ +class PQueue +{ +public: + /** Create a queue with any number of different priorities. + *@param nNewNumQueues The number of queues, the default is 3 + */ + PQueue( int nNewNumQueues=3 ); + + /** + * Cleanup all contained queues. + */ + ~PQueue(); + + /** Add a new item to the queue at the specified priority. A lower + * number means a higher priority! + *@param pData A pointer to the data to add to the queue + *@param nQueueLevel The priority to set the new data to + */ + void enqueue( void *pData, int nQueueLevel ); + + /** Pull the next item off the queue, high priority first. + *@returns A pointer to the data that was next in the priority queue + */ + void *dequeue(); + +private: + /** + * The queues we use for real data storage. + */ + Queue *aQueue; + + /** + * The number of priorities or queus that we need. + */ + int nNumQueues; +}; + +#endif diff --git a/src/programchain.cpp b/src/programchain.cpp new file mode 100644 index 0000000..4e53ac8 --- /dev/null +++ b/src/programchain.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + programchain.cpp - description + ------------------- + begin : Sat Sep 6 2003 + copyright : (C) 2003 by Mike Buland + email : eichlan@yf-soft.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include "programchain.h" + +ProgramChain::ProgramChain() +{ + pLog = MultiLog::getLog(); + pLog->LineLog( MultiLog::LStatus, "Program Chain Initialized." ); +} + +ProgramChain::~ProgramChain() +{ +} + +bool ProgramChain::addLink( ProgramLink *pLink ) +{ + if( pLink->init() == false ) + { + emergencyShutdown(); + return false; + } + + lLink.append( pLink ); + + pLink->setChain( this ); + + return true; +} + +ProgramLink *ProgramChain::getLink( const char *lpName ) +{ + char a; + a = lpName[0]; + return NULL; +} + +ProgramLink *ProgramChain::getBaseLink() +{ + return NULL; +} + +bool ProgramChain::execChainOnce() +{ + int nLen = lLink.getSize(); + for( int j = 0; j < nLen; j++ ) + { + if( ((ProgramLink *)lLink[j])->timeSlice() == false ) + { + pLog->LineLog( MultiLog::LInfo, "Shutting down due to signal from link #%d", j ); + emergencyShutdown(); + return false; + } + } + + return true; +} + +bool ProgramChain::enterChainLoop() +{ + for(;;) + { + if( execChainOnce() == false ) + { + return false; + } + } + + return true; +} + +void ProgramChain::emergencyShutdown() +{ + int nLen = lLink.getSize(); + for( int j = 0; j < nLen; j++ ) + { + ((ProgramLink *)lLink[j])->deInit(); + delete (ProgramLink *)lLink[j]; + } + lLink.empty(); +} + +LinkMessage *ProgramChain::broadcastIRM( LinkMessage *pMsgOut, ProgramLink *pSender ) +{ + int nLen = lLink.getSize(); + for( int j = 0; j < nLen; j++ ) + { + LinkMessage *pMsg = ((ProgramLink *)lLink[j])->processIRM( pMsgOut ); + if( pMsg != NULL ) + { + delete pMsgOut; + return pMsg; + } + } + + delete pMsgOut; + return NULL; +} diff --git a/src/programchain.h b/src/programchain.h new file mode 100644 index 0000000..34f64f8 --- /dev/null +++ b/src/programchain.h @@ -0,0 +1,88 @@ +#ifndef PROGRAMCHAIN_H +#define PROGRAMCHAIN_H + +#include "linkedlist.h" +#include "multilog.h" +#include "programlink.h" + +/** The Program Chain links together program "chunks" to more easily facilitate + * a generalized program loop with modular extensions. + *@author Mike Buland + */ +class ProgramChain +{ +public: + /** + * Construct an empty chain. + */ + ProgramChain(); + + /** + * Destroy your chain. + */ + ~ProgramChain(); + + /** Adds a link to the end of the chain. + *@param pLink A pointer to the link to add to the chain. + *@returns True if adding the link was successful, otherwise false + *@author Mike Buland + */ + bool addLink( ProgramLink *pLink ); + + /** Gets a link by name. + *@param lpName The name of the link you're looking for. Every link has a + * name, apparently. + *@returns A pointer to the specified ProgramLink, or NULL if none were found + * matching your criteria. + *@author Mike Buland + */ + class ProgramLink *getLink( const char *lpName ); + + /** Gets the very first link in the chain. + *@returns A pointer to the first link in the chain. + *@author Mike Buland + */ + class ProgramLink *getBaseLink(); + + /** Runs through the chain once. Useful if you want to have more control over + * the operation of the chain. + *@returns true if every link returned true. If at least one link returns false, + * then returns false. + *@author Mike Buland + */ + bool execChainOnce(); + + /** Enters the master chain loop, looping over the entire chain and executing + * every link's TimeSlice routine in order, over and over, until a link returns + * a false value. + *@returns False, always. It returns true unless a link returned false, but loops + * until a link does return false. + *@author Mike Buland + **/ + bool enterChainLoop(); + + /** Broadcasts an Immediate Response Message to all active links, save the + * sender. Whatever link first responds with a non-null response message + * will have it's messages sent back to the broadcasting link as the returns + * of this function call. Therefore it is very important that all message + * processing code is handled in a fairly timely fasion. + *@param pMsgOut The message to broadcast in hopes of a response. + *@param pSender The message that sent out the message and doesn't want to + * receive it's own message. This should always just be "this". + *@returns The message that was returned by the first link to return a + * non-null response. If all messages return null responses then this also + * returns null. Please note that whoever calls this will be responsible + * for deleting the message returned by it, if non-null. + */ + class LinkMessage *broadcastIRM( LinkMessage *pMsgOut, ProgramLink *pSender ); + +private: + /** + * Shuts down all operation no matter what point in the operation we were. + */ + void emergencyShutdown(); + MultiLog *pLog; /**< A pointer to a log. */ + LinkedList lLink; /**< The linked list that contains all of the links. */ +}; + +#endif diff --git a/src/programlink.cpp b/src/programlink.cpp new file mode 100644 index 0000000..de13be8 --- /dev/null +++ b/src/programlink.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + programlink.cpp - description + ------------------- + begin : Sat Sep 6 2003 + copyright : (C) 2003 by Mike Buland + email : eichlan@yf-soft.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "programlink.h" +#include "programchain.h" + +ProgramLink::ProgramLink() +{ +} + +ProgramLink::~ProgramLink() +{ +} + +LinkMessage *ProgramLink::sendIRM( LinkMessage *pMsgOut ) +{ + return pChain->broadcastIRM( pMsgOut, this ); +} + +void ProgramLink::setChain( ProgramChain *pNewChain ) +{ + pChain = pNewChain; +} + +/* +void ProgramLink::postMessage( LinkMessage *pMsg, int nLvl ) +{ + if( nLvl == msgToChain ) + { + qMsgToChain.enqueue( pMsg ); + } + else if( nLvl == msgToLink ) + { + qMsgToLink.enqueue( pMsg ); + } + else + { + // ERROR! + } +} + +LinkMessage *ProgramLink::getMessage( int nLvl ) +{ + if( nLvl == msgToChain ) + { + return (LinkMessage *)qMsgToChain.dequeue(); + } + else if( nLvl == msgToLink ) + { + return (LinkMessage *)qMsgToLink.dequeue(); + } + else + { + // ERROR! + } +} +*/ diff --git a/src/programlink.h b/src/programlink.h new file mode 100644 index 0000000..6499fc2 --- /dev/null +++ b/src/programlink.h @@ -0,0 +1,99 @@ +#ifndef PROGRAMLINK_H +#define PROGRAMLINK_H + +class ProgramLink; +#include "queue.h" +#include "linkmessage.h" +#include "programchain.h" + +/** + * Program Link is the base class for any object that will be a piece of the + * main program chain loop. + *@author Mike Buland + */ +class ProgramLink +{ +friend class ProgramChain; +public: + /** + * Construct a program link. + */ + ProgramLink(); + + /** + * Deconstruct. + */ + virtual ~ProgramLink(); + + /** + * Initialization code required for a link that wasn't performed in the + * constructor. + *@returns true if initialization was successful. A false value will halt + * the chain. + */ + virtual bool init()=0; + + /** + * DeInitialization code that should happen, but doesn't belong in the + * destructor. + *@returns true means everything worked, false means failure, but is + * meaningless. + */ + virtual bool deInit()=0; + + /** + * Executed once per link per chain pass. Contains the guts of the program. + *@returns true if everything went well. A false value will halt the chain. + */ + virtual bool timeSlice()=0; + + /** + * This must be handled in order to process Instant Response Messages. + * This function should return null on all messages that it doesn't + * understand how to handle, and construct new messages to return to sender + * in the cases where it does understand. + *@param pMsgIn The message that must be processed. + *@returns Either a new message in cases where a response is required, + * or null if nothing needs to be done by this link. + */ + virtual LinkMessage *processIRM( LinkMessage *pMsgIn ) = 0; + + /** + * Broadcast a LinkMessage to all other links in the system. Each other + * link will get a call of their processIRM function. If the message gets + * a response then you will regain control immediately, otherwise the system + * will give all other Links a chance to respond before returning NULL. + *@param pMsgOut The message to broadcast. + *@returns The message response, or NULL if no Link understood your message. + */ + LinkMessage *sendIRM( LinkMessage *pMsgOut ); + +private: + /** + * Set which chain we're assosiated with. This is hope IRM messages make + * it out to the rest of the world. + *@param pNewChain A pointer to the containing program chain. + */ + void setChain( class ProgramChain *pNewChain ); + + /** + * The pointer to the containing chain. + */ + class ProgramChain *pChain; +/* + void postMessage( LinkMessage *pMsg, int nLvl ); + LinkMessage *getMessage( int nLvl ); + + enum + { + msgToChain, + msgToLink + }; + +private: + Queue qMsgToChain; + Queue qMsgToLink; +*/ +}; + +#endif diff --git a/src/protocol.cpp b/src/protocol.cpp new file mode 100644 index 0000000..1b2621f --- /dev/null +++ b/src/protocol.cpp @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2003 by Mike Buland * + * eichlan@yf-soft.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ +#include "protocol.h" + +Protocol::Protocol() +{ + pConnection = NULL; +} + +Protocol::~Protocol() +{ +} + +void Protocol::setConnection( Connection *pNewConnection ) +{ + pConnection = pNewConnection; + + onNewConnection(); +} + +Connection *Protocol::getConnection() +{ + return pConnection; +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..cd18e37 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,58 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include "connection.h" + +/** This is the template for a class that handles specialized input and output + * to connections of different types with different protocols. + *@author Mike Buland + */ +class Protocol +{ +public: + /** Constructor */ + Protocol(); + /** Deconstructor */ + virtual ~Protocol(); + + /** + * Function is called every time there is new data on the line. This is + * called directly from the Connection class to process data. This is not + * called whever there is pending data on the input, but every time new data + * is added to the input buffer. + *@returns True if processing went alright, false if something went wrong, + * I suppose. In truth this value is thrown away right now. + *@todo Either make a return value of false mean something, or make these + * void. + */ + virtual bool onNewData()=0; + + /** + * Function is called when there is a new connection. This should only + * happen once per Protocol object, but gives each protocol object a + * chance to perform connection handshaking and initialization at a point + * where they know that they have a handle to an active Connection. + *@returns See onNewData + */ + virtual bool onNewConnection()=0; + + /** + * Sets the Protocol's Connection object. This is rather important, and + * handled usually by the ConnectionManager. + *@param pNewConnection The Connection object that this protocol will use to + * deal with the outside world. + */ + void setConnection( class Connection *pNewConnection ); + + /** + * Get a pointer to this object's Connection object, or NULL if one was + * never set. If used with the ConnectionManager that should never happen. + *@returns A pointer to the active Connection. + */ + Connection *getConnection(); + +private: + class Connection *pConnection; /**< The pointer to the Connection. */ +}; + +#endif diff --git a/src/protocoltelnet.cpp b/src/protocoltelnet.cpp new file mode 100644 index 0000000..7beea5b --- /dev/null +++ b/src/protocoltelnet.cpp @@ -0,0 +1,315 @@ +#include "protocoltelnet.h" +#include + +ProtocolTelnet::ProtocolTelnet() +{ + nTermType = termUnInited; + bEchoOn = true; +} + +ProtocolTelnet::~ProtocolTelnet() +{ +} + +bool ProtocolTelnet::onNewConnection() +{ + Connection *pCon = getConnection(); + + pCon->appendOutput( (char)IAC ); + pCon->appendOutput( (char)WILL ); + pCon->appendOutput( (char)SUPPRESSGA ); + + pCon->appendOutput( (char)IAC ); + pCon->appendOutput( (char)DO ); + pCon->appendOutput( (char)SUPPRESSGA ); + + pCon->appendOutput( (char)IAC ); + pCon->appendOutput( (char)DONT ); + pCon->appendOutput( (char)TERMTYPE ); + +// pCon->appendOutput( IAC ); +// pCon->appendOutput( SB ); +// pCon->appendOutput( TERMTYPE ); +// pCon->appendOutput( 1 ); +// pCon->appendOutput( IAC ); +// pCon->appendOutput( SE ); + + pCon->appendOutput( (char)IAC ); + pCon->appendOutput( (char)DONT ); + pCon->appendOutput( (char)ECHO ); + + pCon->appendOutput( (char)IAC ); + pCon->appendOutput( (char)WILL ); + pCon->appendOutput( (char)ECHO ); + +// 255(IAC),251(WILL),3 +} + +bool ProtocolTelnet::onNewData() +{ + Connection *pCon = getConnection(); + if( !pCon->hasInput() ) + { + return true; + } + + int nInSize = pCon->getInputAmnt(); + char *lpInStr = (char *)pCon->getInput(); + + // Here we interpret the basic commands and un-encapsulate them, so to + // speak. We'll allow this, even if the terminal is in raw mode, we + // just won't send anything in response... + for( int j = 0; j < nInSize; j++ ) + { + switch( (unsigned char)lpInStr[j] ) + { + case '\r': + fbEdited.appendData('\n'); + if( bEchoOn ) pCon->appendOutput("\n\r"); + break; + + case '\n': + break; + + case '\177': // backspace + if( fbEdited.getLength() > 0 ) + { + fbEdited.usedData( -1 ); // Delete one char from the end + if( bEchoOn ) pCon->appendOutput(ESC "[D"); // Move the cursor back one + if( bEchoOn ) pCon->appendOutput(ESC "[P"); // Delete one character + } + break; + + case '\x1B': // escape sequence + if( (unsigned char)lpInStr[j+1] == '[' ) + { + switch( (unsigned char)lpInStr[j+2] ) + { + case 'A': // Up + break; + + case 'B': // Down + break; + + case 'C': // Right + break; + + case 'D': // Left + break; + } + j+=2; + } + break; + + case 0: // NOP: No operation + break; + + case IAC: // IAC: Interpret as command + switch( lpInStr[j+1] ) + { + case SE: // SE: End of subnegotiation parameters. + break; + + case NOP: // NOP: No operation + break; + + case DM: // DM: Data mark. Indicates the position of a Synch event within the data stream. This should always be accompanied by a TCP urgent notification. + break; + + case BRK: // BRK: Break. Indicates that the "break" or "attention" key was hit. + break; + + case IP: // IP: Suspend, interrupt or abort the process to which the NVT is connected. + break; + + case AO: // AO: Abort output. Allows the current process to run to completion but do not send its output to the user. + break; + + case AYT: // AYT: Are you there. Send back to the NVT some visible evidence that the AYT was received. + break; + + case EC: // EC: Erase character. The receiver should delete the last preceding undeleted character from the data stream. + break; + + case EL: // EL: Erase line. Delete characters from the data stream back to but not including the previous CRLF. + break; + + case GA: // GA: Go ahead. Used, under certain circumstances, to tell the other end that it can transmit. + break; + + case SB: // SB: Subnegotiation of the indicated option follows. + switch( lpInStr[j+2] ) + { + case TERMTYPE: + if( lpInStr[j+3] == 0 ) + { + for( int k = 0; j+4+k < nInSize; k++ ) + { + if( (unsigned char)lpInStr[j+4+k] == IAC && + (unsigned char)lpInStr[j+5+k] == SE ) + { + lpInStr[j+4+k] = 0; + //@TODO: Do something with the term type... + printf("Term type: %s\n", &lpInStr[j+4] ); + j += 5+k; + } + } + } + else + { + } + break; + + default: + //printf("unknown subnegotiation parameters! (%d)\n", lpInStr[j+2] ); + break; + } + break; + + case WILL: // WILL: Indicates the desire to begin performing + switch( lpInStr[j+2] ) + { + case SUPPRESSGA: + j += 2; +// pCon->usedInput( 3 ); + break; + + case TERMTYPE: + j += 2; +// pCon->usedInput( 3 ); + break; + + case ECHO: + j += 2; +// pCon->usedInput( 3 ); + break; + + case NAWS: + default: + pCon->appendOutput( (char)ESC[0] ); + pCon->appendOutput( (char)DONT ); + pCon->appendOutput( lpInStr[j+2] ); + //printf("unknown will command used! (%d)\n", lpInStr[j+2] ); + j += 2; + break; + } + break; + + case WONT: // WONT: Indicates the refusal to perform + switch( lpInStr[j+2] ) + { + case ECHO: + j += 2; +// pCon->usedInput( 3 ); + break; + + default: + //printf("unknown wont command used! (%d)\n", lpInStr[j+2] ); + j += 2; + break; + } + break; + + case DO: // DO: Indicates the request that the other party perform + switch( lpInStr[j+2] ) + { + case ECHO: + j += 2; + break; + + case SUPPRESSGA: + j += 2; + break; + + default: + pCon->appendOutput( (char)ESC[0] ); + pCon->appendOutput( (char)DONT ); + pCon->appendOutput( lpInStr[j+2] ); + //printf("unknown do command used! (%d)\n", lpInStr[j+2] ); + j += 2; + break; + } +// pCon->usedInput( 3 ); + break; + + case DONT: // DONT: Indicates the demand that the other party stop performing + switch( lpInStr[j+2] ) + { + case ECHO: + j += 2; +// pCon->usedInput( 3 ); + break; + + default: + printf("unknown dont command used! (%d)\n", lpInStr[j+2] ); + j += 2; + break; + } + break; + } + break; + + default: + fbEdited.appendData( lpInStr[j] ); + if( bEchoOn ) pCon->appendOutput( lpInStr[j] ); + break; + } + } + + pCon->usedInput( pCon->getInputAmnt() ); + + return true; +} + +char *ProtocolTelnet::getLine( bool bFullOnly ) +{ + int i = fbEdited.findChar('\n'); + + if( i < 0 ) + { + if( bFullOnly == false ) + { + i = fbEdited.getLength(); + } + else + { + return NULL; + } + } + + char *lpStr = new char[i+1]; + strncpy( lpStr, fbEdited.getData(), i ); + lpStr[i] = '\0'; + + fbEdited.usedData( i+1 ); + + return lpStr; +} + +char *ProtocolTelnet::peekLine( bool bFullOnly ) +{ + int i = fbEdited.findChar('\n'); + + if( i < 0 ) + { + if( bFullOnly == false ) + { + i = fbEdited.getLength(); + } + else + { + return NULL; + } + } + + char *lpStr = new char[i+1]; + strncpy( lpStr, fbEdited.getData(), i ); + lpStr[i] = '\0'; + + return lpStr; +} + +void ProtocolTelnet::setEcho( bool bEchoOn ) +{ + this->bEchoOn = bEchoOn; +} diff --git a/src/protocoltelnet.h b/src/protocoltelnet.h new file mode 100644 index 0000000..4b2fb32 --- /dev/null +++ b/src/protocoltelnet.h @@ -0,0 +1,77 @@ +#ifndef PROTOCOLTELNET_H +#define PROTOCOLTELNET_H + +#include "protocol.h" +#include "flexbuf.h" + +#define ESC "\x1B" /**< A telnet escape code. */ + +/** Handles all specialized protocol actions related to the telnet protocol. + * This includes setting modes, non-scrollable regions, and so on. + *@author Mike Buland + */ +class ProtocolTelnet : public Protocol +{ +public: + ProtocolTelnet(); + ~ProtocolTelnet(); + + bool onNewData(); + bool onNewConnection(); + + char *getLine( bool bFullOnly = true ); + char *peekLine( bool bFullOnly = true ); + + void setEcho( bool bEchoOn = true ); + + enum + { + termUnInited, + termRaw, + termUnknown, + termVT220, + termXTerm + }; + + enum + { + SE = 240, // SE: End of subnegotiation parameters. + NOP = 241, // NOP: No operation + DM = 242, // DM: Data mark. Indicates the position of a Synch event within the data stream. This should always be accompanied by a TCP urgent notification. + BRK = 243, // BRK: Break. Indicates that the "break" or "attention" key was hit. + IP = 244, // IP: Suspend, interrupt or abort the process to which the NVT is connected. + AO = 245, // AO: Abort output. Allows the current process to run to completion but do not send its output to the user. + AYT = 246, // AYT: Are you there. Send back to the NVT some visible evidence that the AYT was received. + EC = 247, // EC: Erase character. The receiver should delete the last preceding undeleted character from the data stream. + EL = 248, // EL: Erase line. Delete characters from the data stream back to but not including the previous CRLF. + GA = 249, // GA: Go ahead. Used, under certain circumstances, to tell the other end that it can transmit. + SB = 250, // SB: Subnegotiation of the indicated option follows. + WILL = 251, // WILL: Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. + WONT = 252, // WONT: Indicates the refusal to perform, or continue performing, the indicated option. + DO = 253, // DO: Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. + DONT = 254, // DONT: Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. + IAC = 255 // IAC: Interpret as command + }; + + enum + { + ECHO = 1, // Explain who'll echo + SUPPRESSGA = 3, // Suppress Go Ahead + TERMTYPE = 24, // Terminal Type + NAWS = 31, // Window size + TERMSPEED = 32, // Terminal Speed + LINEMODE = 34 // Linemode + }; + +private: + int nTermType; + + int nTermWidth; + int nTermHeight; + + FlexBuf fbEdited; + + bool bEchoOn; +}; + +#endif diff --git a/src/queue.cpp b/src/queue.cpp new file mode 100644 index 0000000..42999fe --- /dev/null +++ b/src/queue.cpp @@ -0,0 +1,26 @@ +#include "queue.h" + +void Queue::enqueue( void *data ) +{ + lQueueData.append( data ); +} + +void *Queue::dequeue() +{ + void *dat = lQueueData[0]; + if( dat != NULL ) + { + lQueueData.deleteAt( 0 ); + } + return dat; +} + +bool Queue::isEmpty() +{ + return lQueueData.isEmpty(); +} + +void Queue::empty() +{ + lQueueData.empty(); +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..692f5d8 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,45 @@ +#ifndef QUEUE_H +#define QUEUE_H +#include "linkedlist.h" + +/** + * An ultra-simple queue implementation. It just uses a linked list as it's + * container so we don't have to worry about anything! + *@author Mike Buland + */ +class Queue +{ +public: + /** + * Puts a new item at the end of the queue. + *@param data A new value to put at the end of the queue. + */ + void enqueue( void *data ); + + /** + * Gets the begining item of the queue off and returns it. + *@returns The value at the front of the queue. + */ + void *dequeue(); + + /** + * Checks if the queueu is empty. + *@returns True if the queueu is empty, and false if it has things in it. + */ + bool isEmpty(); + + /** + * Empty the queue. + */ + void empty(); + + /** + * Get a pointer to the internal list object. + *@returns A pointer to the internal list object. + */ + LinkedList *getList() { return &lQueueData; }; + +private: + LinkedList lQueueData; /**< Where all of the real data is stored. */ +}; +#endif diff --git a/src/ringlist.cpp b/src/ringlist.cpp new file mode 100644 index 0000000..9efbbc4 --- /dev/null +++ b/src/ringlist.cpp @@ -0,0 +1,106 @@ +// +// C++ Implementation: ringlist +// +// Description: +// +// +// Author: Mike Buland , (C) 2005 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#include + +#include "ringlist.h" + +RingList::RingList( int nInitSize ) + : List() +{ + nFirstIndex = 0; + nRealLength = nInitSize; + nDataLength = 0; + apData = new void*[nInitSize]; + pPushBuf = NULL; +} + +RingList::~RingList() +{ + delete[] apData; +} + +void *RingList::getAt( int nIndex ) +{ + if( nIndex < 0 || nIndex >= nDataLength ) + { + return NULL; + } + + return apData[(nFirstIndex+nIndex)%nRealLength]; +} + +void RingList::append( void *pData ) +{ + int nIndex = (nFirstIndex+nDataLength)%nRealLength; + + pPushBuf = apData[nIndex]; + apData[nIndex] = pData; + + if( nDataLength == nRealLength ) + { + nFirstIndex = (nFirstIndex+1)%nRealLength; + } + else + { + nDataLength++; + // We really didn't need it this time... + pPushBuf = NULL; + } +} + +void RingList::insertBefore( void *pData, int nPos ) +{ + // Not implemented right now, don't even try it! +} + +int RingList::getSize() +{ + return nDataLength; +} + +bool RingList::isEmpty() +{ + return nDataLength==0; +} + +void RingList::deleteAt( int nIndex ) +{ + // Also not implemented yet +} + +void RingList::empty() +{ + nFirstIndex = 0; + nDataLength = 0; +} + +void RingList::setSize( int nNewSize ) +{ + if( apData ) + { + delete[] apData; + } + nFirstIndex = 0; + nRealLength = nNewSize; + nDataLength = 0; + apData = new void*[nNewSize]; +} + +void RingList::setAt( int nIndex, void *pData ) +{ + apData[(nIndex+nFirstIndex)%nRealLength] = pData; +} + +void *RingList::getPushBuf() +{ + return pPushBuf; +} diff --git a/src/ringlist.h b/src/ringlist.h new file mode 100644 index 0000000..1a4d3a9 --- /dev/null +++ b/src/ringlist.h @@ -0,0 +1,112 @@ +#ifndef RINGLIST_H +#define RINGLIST_H + +#include "list.h" + +/** + * A RingList or Ring Buffer implementation. This is a list that never grows in + * size once it is created, but instead once it is full new items added to the + * RingList replace the oldest items and the zero-index is virtually shifted. + * Since no data is actually moved when zero-index moves, this is very + * efficient. + *
+ * The items removed are not actually deleted by the RingList, so instead they + * are first moved into a temporary "Push Buffer" that can be accessed so that + * elements pushed off the edge of the RingList can be accessed for cleanup. + *@author Mike Buland + */ +class RingList : public List +{ +public: + /** + * Construct a RingList with a fixed initial size. This size never changes + * unless setSize is called later during normal operation. + *@param nInitSize The number of elements to allocate. + */ + RingList( int nInitSize ); + + /** + * Clean up the data structures, but not the contained elements. + */ + ~RingList(); + + /** + * Get an element at the specified index. + *@param nIndex The index of the element to retreive. + *@returns A pointer to the requested element, or NULL if the element is + * not found or not initialized yet. + */ + void *getAt( int nIndex ); + + /** + * Append an element to the end of the list, overwriting the begining if + * necesarry. + *@param pData The pointer to append to the RingList. + */ + void append( void *pData ); + + /** + * Insert an element before another in the RingList, pushing all after it + * down the list. + *@param pData The data to insert. + *@param nPos The position that new the element should occupy in the list. + */ + void insertBefore( void *pData, int nPos = 0 ); + + /** + * Get the size of the array. + */ + int getSize(); + + /** + * Is the RingList empty? + *@returns True if it is empty, false otherwise. + */ + bool isEmpty(); + + /** + * Delete an element in the list, moving all later elements down one index. + *@param nIndex The index of the element to delete. + */ + void deleteAt( int nIndex ); + + /** + * Remove all elements from the RingList. + */ + void empty(); + + /** + * Set a new size for the RingList. Be careful with this one, if shrinking + * this may quietly create a memory leak. + *@param nNewSize The new size to set the RingList to. + *@todo Either fix this memory leak somehow or remove this function. + */ + void setSize( int nNewSize ); + + /** + * Set a specific element to a new value. + *@param nIndex The zero-based index to change the value of. + *@param pData The data to put at the location specified by nIndex. + */ + void setAt( int nIndex, void *pData ); + + /** + * Retrieve the contents of the push buffer. This is the data that is + * pushed off the end of the array if you append data and the list is full. + * This should be checked after every append operation to be sure there + * isn't anything that needs deleting. + *@returns The last value pushed off the RingList, or NULL if nothing was + * pushed off. + */ + void *getPushBuf(); + +private: + int nFirstIndex; /**< The index to be translated as zero. */ + int nRealLength; /**< The Amount of storage space available. */ + int nDataLength; /**< The number of elements filled in. */ + void **apData; /**< The actual data storage. */ + void *pPushBuf; /**< The push buffer. */ + +}; + +#endif diff --git a/src/stack.cpp b/src/stack.cpp new file mode 100644 index 0000000..8d9565c --- /dev/null +++ b/src/stack.cpp @@ -0,0 +1,33 @@ +#include "stack.h" + +void Stack::push( void *data ) +{ + lStackData.append( data ); +} + +void *Stack::top() +{ + return lStackData.getAt( lStackData.getSize()-1 ); +} + +void Stack::pop() +{ + lStackData.deleteAt( lStackData.getSize()-1 ); +} + +void *Stack::poptop() +{ + void *dat = top(); + pop(); + return dat; +} + +bool Stack::isEmpty() +{ + return lStackData.isEmpty(); +} + +void Stack::empty() +{ + lStackData.empty(); +} diff --git a/src/stack.h b/src/stack.h new file mode 100644 index 0000000..30e2a19 --- /dev/null +++ b/src/stack.h @@ -0,0 +1,50 @@ +#ifndef STACK_H +#define STACK_H +#include "linkedlist.h" + +/** An ultra-simple stack implementation that just uses a linked list. + *@author Mike Buland + */ +class Stack +{ +public: + /** Pushes a new value onto the top of the stack. + *@param data A new value for the stack. + *@author Mike Buland + */ + void push( void *data ); + + /** Returns the top value off of the stack, but doesn't remove it from the + * stack. + *@returns The value at the top of the stack. + *@author Mike Buland + */ + void *top(); + + /** Pops the top item off of the stack. + *@author Mike Buland + */ + void pop(); + + /** Gets the top item off of the stack, pops it off the stack, and returns + * it. + *@returns The value at the top of the stack. + *@author Mike Buland + */ + void *poptop(); + + /** Checks if the stack is empty. + *@returns True if the stack is empty, and false if it has things in it. + *@author Mike Buland + */ + bool isEmpty(); + + /** Empty the stack. + *@author Mike Buland + */ + void empty(); + +private: + LinkedList lStackData; /**< The actual stack data. */ +}; +#endif diff --git a/src/test/hashtest.cpp b/src/test/hashtest.cpp new file mode 100644 index 0000000..f31a3f8 --- /dev/null +++ b/src/test/hashtest.cpp @@ -0,0 +1,107 @@ +#include +#include +#include "hashtable.h" +#include "hashfunctioncasestring.h" + +int main() +{ + const char *names[]={ + "Homer the Great", + "And Maggie Makes Three", + "Bart's Comet", + "Homie The Clown", + "Bart Vs Australia", + "Homer vs Patty and Selma", + "A star is burns", + "Lisa's Wedding", + "Two Dozen and One Greyhounds", + "The PTA Disbands", + "Round Springfield", + "The Springfield connection", + "Lemon of Troy", + "Who Shot Mr. Burns (Pt. 1)", + "Who Shot Mr. Burns (pt. 2)", + "Radioactive Man", + "Home Sweet Homediddly-dum-doodly", + "Bart Sells His Soul", + "Lisa the Vegetarian", + "Treehouse of horror VI", + "King Size Homer", + "Mother Simpson", + "Sideshow Bob's Last Gleaming", + "The Simpson's 138th Show Spectacular", + "Marge Be Not Proud", + "Team Homer", + "Two Bad Neighbors", + "Scenes From the Class Struggle in Springfield", + "Bart the Fink", + "Lisa the Iconoclast", + "Homer the Smithers", + "The Day the Violence Died", + "A Fish Called Selma", + "Bart on the road", + "22 Short Films about Springfield", + "The Curse of the Flying Hellfish", + "Much Apu about Nothing", + "Homerpalooza", + "The Summer of 4 Ft 2", + "Treehouse of Horror VII", + "You Only Move Twice", + "The Homer They Fall", + "Burns Baby Burns", + "Bart After Dark", + "A Millhouse Divided", + "Lisas Date With Destiny", + "Hurricane Neddy", + "The Mysterious Voyage of Our Homer", + "The Springfield Files", + "The Twisted World of Marge Simpson", + "Mountain of Madness", + NULL + }; + + HashTable h( new HashFunctionCaseString(), 5, false ); + + int j; + printf("Inserting...\n"); + for( j = 0; j < 10; j++ ) + { + h.insert( names[j], (void *)(j+1) ); + h.insert( names[j], (void *)(j+1) ); + printf("Capacity: %d, Size: %d, Load: %f\n", + h.getCapacity(), + h.getSize(), + h.getLoad() + ); + } + + for( j = 0; j < 10; j++ ) + { + printf("\"%s\" = %d\n", names[j], (int)h[names[j]] ); + } + + printf("\nDeleting some...\n"); + + for( int k = 0; k < 7; k++ ) + { + h.del( names[k] ); + //h.insert( names[j], (void *)(j+1) ); + printf("Capacity: %d, Size: %d, Load: %f\n", + h.getCapacity(), + h.getSize(), + h.getLoad() + ); + } + + printf("\nInserting more...\n"); + + for( ; names[j] != NULL; j++ ) + { + h.insert( names[j], (void *)(j+1) ); + printf("Capacity: %d, Size: %d, Load: %f\n", + h.getCapacity(), + h.getSize(), + h.getLoad() + ); + } +} diff --git a/src/test/httpsrv/httpconnectionmonitor.cpp b/src/test/httpsrv/httpconnectionmonitor.cpp new file mode 100644 index 0000000..4eb6817 --- /dev/null +++ b/src/test/httpsrv/httpconnectionmonitor.cpp @@ -0,0 +1,72 @@ +#include "httpconnectionmonitor.h" +#include "http.h" +#include + +HttpConnectionMonitor::HttpConnectionMonitor() +{ +} + +HttpConnectionMonitor::~HttpConnectionMonitor() +{ +} + +bool HttpConnectionMonitor::onNewConnection( Connection *pCon ) +{ + Http hp( pCon ); + + pCon->readInput( 60, 0 ); + printf("#######################\n%s\n#######################\n", pCon->getInput() ); + + while( hp.parseRequest() == false ); + printf("Done parsing.\n\n"); + + if( hp.getRequestType() == Http::reqGet ) + { + printf("\"\"\"%s\"\"\"\n", hp.getRequestURI() ); + if( !strcmp( hp.getRequestURI(), "/" ) ) + { + std::string content("Server Test</test></head><body>This is a test of a new system where all the pages will be more or less dynamic...<br>If you want to try to login, you can do that here:<br><form method=\"post\" action=\"showvars\" enctype=\"multipart/form-data\">Name: <input type=\"text\" name=\"name\"><br>Password: <input type=\"password\" name=\"pass\"><br><input type=\"submit\" name=\"action\" value=\"login\"></form></body></html>"); + hp.buildResponse(); + hp.setResponseContent( + "text/html", + content.c_str(), + content.size() + ); + hp.sendResponse(); + } + else + { + std::string content("<html><head><title>URL Not Found</test></head><body>There is no content mapped to the URL you requested. Please try another one.</body></html>"); + hp.buildResponse( 404, "File not found."); + hp.setResponseContent( + "text/html", + content.c_str(), + content.size() + ); + hp.sendResponse(); + } + } + else + { + printf("Non get: %s\n", hp.getRequestTypeStr() ); + } + pCon->writeOutput(); + + if( pCon->hasInput() ) + { + std::string s( pCon->getInput(), pCon->getInputAmnt() ); + + printf("Reamining data\n==============\n%s\n==============\n", + s.c_str() ); + } + + pCon->disconnect(); + + return true; +} + +bool HttpConnectionMonitor::onClosedConnection( Connection *pCon ) +{ + return true; +} + diff --git a/src/test/httpsrv/httpconnectionmonitor.h b/src/test/httpsrv/httpconnectionmonitor.h new file mode 100644 index 0000000..63f29e4 --- /dev/null +++ b/src/test/httpsrv/httpconnectionmonitor.h @@ -0,0 +1,16 @@ +#ifndef HTTPCONNECTIONMONITOR_H +#define HTTPCONNECTIONMONITOR_H + +#include "connectionmonitor.h" + +class HttpConnectionMonitor : public ConnectionMonitor +{ +public: + HttpConnectionMonitor(); + ~HttpConnectionMonitor(); + + bool onNewConnection( Connection *pCon ); + bool onClosedConnection( Connection *pCon ); +}; + +#endif diff --git a/src/test/httpsrv/main.cpp b/src/test/httpsrv/main.cpp new file mode 100644 index 0000000..4ee1ad3 --- /dev/null +++ b/src/test/httpsrv/main.cpp @@ -0,0 +1,21 @@ +#include "connectionmanager.h" +#include "httpconnectionmonitor.h" + +int main() +{ + printf("Starting server...\n"); + + ConnectionManager srv; + HttpConnectionMonitor http; + + srv.setConnectionMonitor( &http ); + + srv.startServer( 7331, 40 ); + + for(;;) + { + srv.scanConnections( 5000, false ); + } + + return 0; +} diff --git a/src/test/md5test.cpp b/src/test/md5test.cpp new file mode 100644 index 0000000..6f832df --- /dev/null +++ b/src/test/md5test.cpp @@ -0,0 +1,19 @@ +#include <stdio.h> +#include <string.h> +#include "md5.h" + +int main() +{ + md5 mproc; + md5sum sum; + char hexstr[33]; + + memset( hexstr, 0, 33 ); + + mproc.sumString( &sum, "qwertyuiopasdfgh" ); + mproc.sumToHex( &sum, hexstr ); + printf("sum: %s\n", hexstr ); + printf("chk: 1ebfc043d8880b758b13ddc8aa1638ef\n"); + + return 0; +} diff --git a/src/test/teltest/main.cpp b/src/test/teltest/main.cpp new file mode 100644 index 0000000..ce968c4 --- /dev/null +++ b/src/test/teltest/main.cpp @@ -0,0 +1,21 @@ +#include "connectionmanager.h" +#include "telnetmonitor.h" + +int main() +{ + printf("Starting server...\n"); + + ConnectionManager srv; + TelnetMonitor telnet; + + srv.setConnectionMonitor( &telnet ); + + srv.startServer( 4001, 40 ); + + for(;;) + { + srv.scanConnections( 5000, false ); + } + + return 0; +} diff --git a/src/test/teltest/telnetmonitor.cpp b/src/test/teltest/telnetmonitor.cpp new file mode 100644 index 0000000..001932f --- /dev/null +++ b/src/test/teltest/telnetmonitor.cpp @@ -0,0 +1,53 @@ +#include "telnetmonitor.h" +#include "protocoltelnet.h" +#include <sys/stat.h> + +TelnetMonitor::TelnetMonitor() +{ +} + +TelnetMonitor::~TelnetMonitor() +{ +} + +bool TelnetMonitor::init() +{ + return true; +} + +bool TelnetMonitor::deInit() +{ + return true; +} + +bool TelnetMonitor::timeSlice() +{ + for( int j = 0; j < lCon.getSize(); j++ ) + { + if( ((Connection *)lCon[j])->hasInput() ) + { + printf("%s\n", ((Connection *)lCon[j])->getInput() ); + } + } + return true; +} + +LinkMessage* TelnetMonitor::processIRM( LinkMessage *pMsg ) +{ +} + +bool TelnetMonitor::onNewConnection( Connection *pCon ) +{ + ProtocolTelnet *pt = new ProtocolTelnet(); + pCon->setProtocol( pt ); + + lCon.append( pt ); + + return true; +} + +bool TelnetMonitor::onClosedConnection( Connection *pCon ) +{ + return true; +} + diff --git a/src/test/teltest/telnetmonitor.h b/src/test/teltest/telnetmonitor.h new file mode 100644 index 0000000..95c8493 --- /dev/null +++ b/src/test/teltest/telnetmonitor.h @@ -0,0 +1,26 @@ +#ifndef HTTPCONNECTIONMONITOR_H +#define HTTPCONNECTIONMONITOR_H + +#include "connectionmonitor.h" +#include "programlink.h" +#include "linkedlist.h" + +class TelnetMonitor : public ConnectionMonitor, public ProgramLink +{ +public: + TelnetMonitor(); + ~TelnetMonitor(); + + bool init(); + bool deInit(); + bool timeSlice(); + LinkMessage* processIRM( LinkMessage *pMsgIn ); + + bool onNewConnection( Connection *pCon ); + bool onClosedConnection( Connection *pCon ); + +private: + LinkedList lCon; +}; + +#endif diff --git a/src/test/xmlreadtest.cpp b/src/test/xmlreadtest.cpp new file mode 100644 index 0000000..5fbd021 --- /dev/null +++ b/src/test/xmlreadtest.cpp @@ -0,0 +1,29 @@ +#include "../xmlfilereader.h" +#include "../xmlstringreader.h" +#include "../xmlfilewriter.h" + +int main( int argc, char *argv[] ) +{ + if( argc < 4 ) + { + printf("Usage: %s f <file in> <file out>\n", argv[0] ); + printf(" %s s <xml string> <file out>\n\n", argv[0] ); + return 0; + } + + if( argv[1][0] == 'f' ) + { + XmlFileReader r( argv[2], true ); + XmlFileWriter w( argv[3], "\t", r.getRoot() ); + w.write(); + //XmlWriter::write( argv[3], r.getRoot(), "\t" ); + } + else if( argv[1][0] == 's' ) + { + XmlStringReader r( argv[2], true ); + //XmlWriter::write( argv[3], r.getRoot(), "\t" ); + } + + return 0; +} + diff --git a/src/test/xmlrepltest.cpp b/src/test/xmlrepltest.cpp new file mode 100644 index 0000000..1fe9ec2 --- /dev/null +++ b/src/test/xmlrepltest.cpp @@ -0,0 +1,31 @@ +#include "xmlwriter.h" + +int main() +{ + printf("Testing Xml Replacement...\n"); + XmlDocument w; + + w.addNode("text"); + w.setContent("this text is before the node. "); + w.addNode("keepme", "This one we keep...", true ); + w.setContent("this text is after."); + w.addNode("deleteme", "This one we don't...", true ); + w.setContent("this is last..." ); + w.closeNode(); + + //XmlWriter::writeNode( stdout, w.getRoot(), 0, NULL ); + + printf("\n\n"); + + XmlNode *xNode = w.getRoot()->detatchNode( 1 ); + + //XmlWriter::writeNode( stdout, w.getRoot(), 0, NULL ); + + printf("\n\n"); + + //XmlWriter::writeNode( stdout, xNode, 0, NULL ); + + printf("\n\n"); + + return 0; +} diff --git a/src/test/xmlwritetest.cpp b/src/test/xmlwritetest.cpp new file mode 100644 index 0000000..340c9a3 --- /dev/null +++ b/src/test/xmlwritetest.cpp @@ -0,0 +1,41 @@ +#include "xmlfilewriter.h" +#include "xmlstringwriter.h" + +void fillItIn( XmlWriter &w ) +{ + w.addNode("thinglist"); + + w.addNode("thing"); + w.addProperty("type", "Weapon"); + + w.addNode("id", "Klophin Staff", true ); + w.addNode("name", "Klophin Staff", true ); + w.addNode("durability", "0.01", true ); + w.addNode("size", "0.1", true ); + + w.addNode("config"); + w.addNode("damage", "3d6+4", true ); + w.addNode("class", "melee", true ); + w.addNode("type", "bludgeon", true ); + w.addNode("damagedesc", "club/clubs", true ); + w.closeNode(); + + w.closeNode(); + + w.closeNode(); +} + +int main() +{ + printf("Testing XmlWriter...\n"); + XmlFileWriter wf("test.xml", "\t"); + + fillItIn( wf ); + + XmlStringWriter ws("\t"); + fillItIn( ws ); + + printf("Now the string version:\n\n%s\n", ws.getString().c_str() ); + + return 0; +} diff --git a/src/tokenstring.cpp b/src/tokenstring.cpp new file mode 100644 index 0000000..0c861ac --- /dev/null +++ b/src/tokenstring.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + * Copyright (C) 2003 by Mike Buland * + * eichlan@Xagafinelle * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ +#include "tokenstring.h" +#include <string.h> + +TokenString::TokenString( const char *lpNewTokenString ) +{ + lpTokenString = NULL; + if( lpNewTokenString ) + { + parseLine( lpNewTokenString ); + } +} + +TokenString::~TokenString() +{ + delete[] lpTokenString; + for( int j = 0; j < lToken.getSize(); j++ ) + { + delete[] (((Token *)lToken[j])->lpToken); + delete ((Token *)lToken[j]); + } +} + +void TokenString::parseLine( const char *lpNewTokenString ) +{ + if( lpTokenString != NULL ) + { + delete[] lpTokenString; + lpTokenString = NULL; + for( int j = 0; j < lToken.getSize(); j++ ) + { + delete[] (((Token *)lToken[j])->lpToken); + delete ((Token *)lToken[j]); + } + lToken.empty(); + } + if( lpNewTokenString == NULL ) + { + lpTokenString = new char[1]; + lpTokenString[0] = '\0'; + lToken.setSize(0); + return; + } + // First order of business, make an internal copy so someone can get it + // if they want to. + int nLen = strlen(lpNewTokenString); + lpTokenString = new char[nLen+1]; + strcpy( lpTokenString, lpNewTokenString ); + + // Now we do a preliminary parse. This could be effected by later + // editing and aliasing, but we'll see... + int nTkStart, nTkEnd; + int mode=0; // 0 = startSearch, 1=endSearch + for( int j = 0; j <= nLen; j++ ) + { + if( mode == 0 ) + { + if( lpTokenString[j] != ' ' && + lpTokenString[j] != '\t' ) + { + nTkStart = j; + mode = 1; + } + } + else + { + if( lpTokenString[j] == ' ' || + lpTokenString[j] == '\t' || + lpTokenString[j] == '\0' ) + { + nTkEnd = j-1; + mode = 0; + + appendToken( nTkStart, nTkEnd ); + } + } + } +} + +void TokenString::appendToken( int nStart, int nEnd ) +{ + Token *pToken = new Token; + pToken->lpOrig = &lpTokenString[nStart]; + + // nStart and nEnd are inclusive, we must add two for the end, and the null + pToken->lpToken = new char[nEnd-nStart+2]; + memcpy( pToken->lpToken, &lpTokenString[nStart], nEnd-nStart+1 ); + pToken->lpToken[nEnd-nStart+1] = '\0'; + +// printf("%s\n", pToken->lpToken ); + lToken.append( pToken ); +} + +void TokenString::insertToken( int nStart, int nEnd, char *lpOldOrig, const char *lpNewToken, int nIndex ) +{ + Token *pToken = new Token; + pToken->lpOrig = lpOldOrig; + + // nStart and nEnd are inclusive, we must add two for the end, and the null + pToken->lpToken = new char[nEnd-nStart+2]; + memcpy( pToken->lpToken, &lpNewToken[nStart], nEnd-nStart+1 ); + pToken->lpToken[nEnd-nStart+1] = '\0'; + + lToken.insertBefore( pToken, nIndex ); +} + +int TokenString::getNumTokens() +{ + return lToken.getSize(); +} + +char *TokenString::getToken( int nIndex ) +{ + if( nIndex >= lToken.getSize() ) return NULL; + return (char *)(((Token *)lToken[nIndex])->lpToken); +} + +char *TokenString::getTokenString( int nIndex ) +{ + if( nIndex >= lToken.getSize() ) return NULL; + return (char *)(((Token *)lToken[nIndex])->lpOrig); +} + +void TokenString::expandTokenTo( int nIndex, char *lpNewToken ) +{ + // First, we delete the token at nIndex, then we keep inserting + // at that position... + // We also have to remember the index to the original string, + // since most of what we're expanding to won't be in the origingal + // we need to keep these indexes updated in order to make other parts + // of the system happy. + char *lpOldOrig = ((Token *)lToken[nIndex])->lpOrig; + delete[] ((Token *)lToken[nIndex])->lpToken; + delete ((Token *)lToken[nIndex]); + lToken.deleteAt( nIndex ); + + // We'll do this just like we did above, but instead we'll + // do tricky things when we find tokens... + int nLen = strlen(lpNewToken); + int nTkStart, nTkEnd, nNewIndex=nIndex; + int mode=0; // 0 = startSearch, 1=endSearch + for( int j = 0; j <= nLen; j++ ) + { + if( mode == 0 ) + { + if( lpNewToken[j] != ' ' && lpNewToken[j] != '\t' ) + { + nTkStart = j; + mode = 1; + } + } + else + { + if( lpNewToken[j] == ' ' || lpNewToken[j] == '\t' || lpNewToken[j] == '\0' ) + { + nTkEnd = j-1; + mode = 0; + + insertToken( nTkStart, nTkEnd, lpOldOrig, lpNewToken, nNewIndex ); + nNewIndex++; + } + } + } +} diff --git a/src/tokenstring.h b/src/tokenstring.h new file mode 100644 index 0000000..25f710b --- /dev/null +++ b/src/tokenstring.h @@ -0,0 +1,120 @@ +/*************************************************************************** + * Copyright (C) 2003 by Mike Buland * + * eichlan@Xagafinelle * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + ***************************************************************************/ +#ifndef TOKENSTRING_H +#define TOKENSTRING_H + +#include "linkedlist.h" + +/** A single tokenized command line. Contains all information necesarry to + * nicely access a stand-alone command line and to perform alias expansion + * inside of that command line. + * When expanding a token, the original command line is left intact, so any + * command usng a command line verbatum (getTokenString not getToken) will get + * the original, and not the expanded version. + * Since indexing into the original command line is also done by token, it + * means that using getTokenString( 0 ) will not always get you the first + * character of the command line, it will get you the first non-whitespace + * character. + * Furthermore, when expanding the expantion string is tokenized as well, + * but since the original string is unchanged, all tokens that expand any + * given index will all retain the same index into the original command line. + *@todo Update this to allow it to break on different types of token + * delimiters. + *@author Mike Buland + */ +class TokenString{ +public: + /** Automatically call parseLine when created. + *@param lpNewTokenString The command line to tokenize + *@author Mike Buland + */ + TokenString( const char *lpNewTokenString=NULL ); + ~TokenString(); + + /** Performs a tokenizing parse on the given command line, setting it as + * the internal command line for all future tokenizing (excluding + * expansion) + *@param lpNewTokenString The new command line to set to this object. + *@author Mike Buland + */ + void parseLine( const char *lpNewTokenString ); + + /** Appends a token to the list of available tokens. This references the + * internal pointer to the command line, so no token string must be + * specified. + *@param nStart The first character of the token to insert. + *@param nEnd The last character of the token to insert. + *@author Mike Buland + */ + void appendToken( int nStart, int nEnd ); + + /** Gets the number of tokens. This is particularly useful post-aliasing + * since the number of tokens may not match what is percieved from the + * original command line. + *@returns The number of available tokens. + *@author Mike Buland + */ + int getNumTokens(); + + /** Gets a processed token specified by index. + *@param nIndex The index of the token to retrieve. + *@returns A pointer to the requested token. Please note that these tokens + * may not match the original command line. + *@author Mike Buland + */ + char *getToken( int nIndex ); + + /** Gets the original command line based on tokens. Use this if you want + * to perform your own processing on parts of the command line, without + * resorting to tokens. + * The first character in the returned string will always be + * non-whitespace. + *@param nIndex The index of the token to start at, zero gets you the whole + * command line. + *@returns A pointer to the internal original command line string, starting + * at the position of the first non-whitespace character of the token + * specified. + *@author Mike Buland + */ + char *getTokenString( int nIndex=0 ); + + /** Expands a token, replacing it with the string lpNewToken, but + * processing the new string for tokens before performing the replacement + *@param nIndex Which token should be replaced. + *@param lpNewToken The string to replace the token with. + *@author Mike Buland + */ + void expandTokenTo( int nIndex, char *lpNewToken ); + + /** Inserts a token at any position in the command line. This does not + * effect the original command line. + *@param nStart The start of the token in the string lpNewToken. (inclusive) + *@param nEnd The end of the token in the string lpToken. (inclusive) + *@param lpOldOrig The pointer to the position in the orginal command + * line where this new token should point. + *@param lpNewToken The string containing the new token. May contain more + * than just one token. + *@param nIndex The position to insert the token to. + *@author Mike Buland + */ + void insertToken( int nStart, int nEnd, char *lpOldOrig, const char *lpNewToken, int nIndex ); + +private: + char *lpTokenString; + LinkedList lToken; + + typedef struct Token + { + char *lpOrig; // This is just a pointer back to lpTokenString + char *lpToken; // This is really a whole token + } Token; +}; + +#endif diff --git a/src/xmldocument.cpp b/src/xmldocument.cpp new file mode 100644 index 0000000..234ff81 --- /dev/null +++ b/src/xmldocument.cpp @@ -0,0 +1,142 @@ +#include <stdio.h> +#include <stdlib.h> +#include "xmlwriter.h" + +XmlDocument::XmlDocument( XmlNode *pRoot ) +{ + this->pRoot = pRoot; + pCurrent = NULL; + bCompleted = (pRoot!=NULL); +} + +XmlDocument::~XmlDocument() +{ + if( pRoot ) + { + delete pRoot; + } +} + +void XmlDocument::addNode( const char *sName, const char *sContent, bool bClose ) +{ + if( pRoot == NULL ) + { + // This is the first node, so ignore position and just insert it. + pCurrent = pRoot = new XmlNode( sName, NULL, sContent ); + } + else + { + pCurrent = pCurrent->addChild( sName, sContent ); + } + + if( bClose ) + { + closeNode(); + } +} + +void XmlDocument::setName( const char *sName ) +{ + pCurrent->setName( sName ); +} + +bool XmlDocument::isCompleted() +{ + return bCompleted; +} + +XmlNode *XmlDocument::getRoot() +{ + return pRoot; +} + +XmlNode *XmlDocument::getCurrent() +{ + return pCurrent; +} + +void XmlDocument::closeNode() +{ + if( pCurrent != NULL ) + { + pCurrent = pCurrent->getParent(); + + if( pCurrent == NULL ) + { + bCompleted = true; + } + } +} + +void XmlDocument::addProperty( const char *sName, const char *sValue ) +{ + if( pCurrent ) + { + pCurrent->addProperty( sName, sValue ); + } +} + +void XmlDocument::addProperty( const char *sName, const unsigned char nValue ) +{ + char buf[12]; + sprintf( buf, "%hhi", nValue ); + addProperty( sName, buf ); +} + +void XmlDocument::addProperty( const char *sName, const char nValue ) +{ + char buf[12]; + sprintf( buf, "%hhi", nValue ); + addProperty( sName, buf ); +} + +void XmlDocument::addProperty( const char *sName, const unsigned short nValue ) +{ + char buf[12]; + sprintf( buf, "%hi", nValue ); + addProperty( sName, buf ); +} + +void XmlDocument::addProperty( const char *sName, const short nValue ) +{ + char buf[12]; + sprintf( buf, "%hi", nValue ); + addProperty( sName, buf ); +} + +void XmlDocument::addProperty( const char *sName, const int nValue ) +{ + char buf[12]; + sprintf( buf, "%li", nValue ); + addProperty( sName, buf ); +} + +void XmlDocument::addProperty( const char *sName, const unsigned long nValue ) +{ + char buf[12]; + sprintf( buf, "%li", nValue ); + addProperty( sName, buf ); +} + +void XmlDocument::addProperty( const char *sName, const long nValue ) +{ + char buf[12]; + sprintf( buf, "%li", nValue ); + addProperty( sName, buf ); +} + +void XmlDocument::addProperty( const char *sName, const double dValue ) +{ + char buf[40]; + sprintf( buf, "%f", dValue ); + addProperty( sName, buf ); +} + +void XmlDocument::setContent( const char *sContent ) +{ + if( pCurrent ) + { + pCurrent->setContent( sContent ); + } +} + diff --git a/src/xmldocument.h b/src/xmldocument.h new file mode 100644 index 0000000..f9a8606 --- /dev/null +++ b/src/xmldocument.h @@ -0,0 +1,163 @@ +#ifndef XMLDOCUMENT +#define XMLDOCUMENT + +#include "xmlnode.h" + +/** + * Keeps track of an easily managed set of XmlNode information. Allows simple + * operations for logical writing to and reading from XML structures. Using + * already formed structures is simply done through the XmlNode structures, + * and the getRoot function here. Creation is performed through a simple set + * of operations that creates the data in a stream type format. + *@author Mike Buland + */ +class XmlDocument +{ +public: + /** + * Construct either a blank XmlDocuemnt or construct a document around an + * existing XmlNode. Be careful, once an XmlNode is passed into a document + * the document takes over ownership and will delete it when the XmlDocument + * is deleted. + *@param pRoot The XmlNode to use as the root of this document, or NULL if + * you want to start a new document. + */ + XmlDocument( XmlNode *pRoot=NULL ); + + /** + * Destroy all contained nodes. + */ + ~XmlDocument(); + + /** + * Add a new node to the document. The new node is appended to the end of + * the current context, i.e. XmlNode, and the new node, provided it isn't + * close as part of this operation, will become the current context. + *@param sName The name of the new node to add. + *@param sContent A content string to be placed inside of the new node. + *@param bClose Set this to true to close the node immediately after adding + * the node and setting the content and name. If this is set to true the + * node is appended, but the context node doesn't change. + */ + void addNode( const char *sName=NULL, const char *sContent=NULL, bool bClose=false ); + + /** + * Set the name of the current node context. + *@param sName The new name of the node. + */ + void setName( const char *sName ); + + /** + * Close the current node context. This will move the current context to + * the parent node of the former current node. If the current node was the + * root then the "completed" flag is set and no more operations are allowed. + */ + void closeNode(); + + /** + * Change the content of the current node at the current position between + * nodes. + *@param sContent The new content of the current node. + */ + void setContent( const char *sContent ); + + /** + * Add a named property to the current context node. + *@param sName The name of the property to add. + *@param sValue The string value of the property. + */ + void addProperty( const char *sName, const char *sValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param nValue The numerical value to add. + */ + void addProperty( const char *sName, const unsigned char nValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param nValue The numerical value to add. + */ + void addProperty( const char *sName, const char nValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param nValue The numerical value to add. + */ + void addProperty( const char *sName, const unsigned short nValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param nValue The numerical value to add. + */ + void addProperty( const char *sName, const short nValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param nValue The numerical value to add. + */ + void addProperty( const char *sName, const unsigned long nValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param nValue The numerical value to add. + */ + void addProperty( const char *sName, const long nValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param nValue The numerical value to add. + */ + void addProperty( const char *sName, const int nValue ); + + /** + * Add a named property to the current context node, converting the + * numerical parameter to text using standrd printf style conversion. + *@param sName The name of the property to add. + *@param dValue The numerical value to add. + */ + void addProperty( const char *sName, const double dValue ); + + /** + * The XmlDocuemnt is considered completed if the root node has been closed. + * Once an XmlDocument has been completed, you can no longer perform + * operations on it. + *@return True if completed, false if still in progress. + */ + bool isCompleted(); + + /** + * Get a pointer to the root object of this XmlDocument. + *@returns A pointer to an internally owned XmlNode. Do not delete this + * XmlNode. + */ + XmlNode *getRoot(); + + /** + * Get the current context node, which could be the same as the root node. + *@returns A pointer to an internally owned XmlNode. Do not delete this + * XmlNode. + */ + XmlNode *getCurrent(); + +private: + XmlNode *pRoot; /**< The root node. */ + XmlNode *pCurrent; /**< The current node. */ + bool bCompleted; /**< Is it completed? */ +}; + +#endif diff --git a/src/xmlfilereader.cpp b/src/xmlfilereader.cpp new file mode 100644 index 0000000..216c08a --- /dev/null +++ b/src/xmlfilereader.cpp @@ -0,0 +1,63 @@ +#include "xmlfilereader.h" +#include <string.h> + +XmlFileReader::XmlFileReader( const char *sFile, bool bStrip ) + : XmlReader( bStrip ) +{ + fh = fopen( sFile, "rt" ); + + if( fh == NULL ) + { + reportError("Couldn't open file."); + //nError = 1; + } + else + { + char buf[50]; + fgets( buf, 50, fh ); + + if( !strcmp( buf, "<?xml version=\"1.0\"?>\n" ) ) + { + buildDoc(); + } + } +} + +XmlFileReader::~XmlFileReader() +{ +} + +char XmlFileReader::getChar( int nIndex ) +{ + // Make sure we always have a little data left in the buffer + if( fbDataIn.getLength() <= nIndex+1 && fh ) + { + int nBytes = fbDataIn.getCapacity()-1; + char *buf = new char[nBytes]; + int nRead = fread( buf, 1, nBytes, fh ); + fbDataIn.appendData( buf, nRead ); + delete[] buf; + + if( nRead < nBytes ) + { + fclose( fh ); + fh = NULL; + } + } + if( fbDataIn.getLength() >= nIndex+1 ) + { + return fbDataIn.getData()[nIndex]; + } + else + { + return '\0'; + } +} + +void XmlFileReader::usedChar() +{ + if( fbDataIn.getLength() > 0 ) + { + fbDataIn.usedData( 1 ); + } +} diff --git a/src/xmlfilereader.h b/src/xmlfilereader.h new file mode 100644 index 0000000..3e996e6 --- /dev/null +++ b/src/xmlfilereader.h @@ -0,0 +1,47 @@ +#ifndef XMLFILEREADER +#define XMLFILEREADER + +#include <stdio.h> +#include "xmlreader.h" +#include "flexbuf.h" + +/** + * Takes care of reading in xml formatted data from a file. This could/should + * be made more arbitrary in the future so that we can read the data from any + * source. This is actually made quite simple already since all data read in + * is handled by one single helper function and then palced into a FlexBuf for + * easy access by the other functions. The FlexBuf also allows for block + * reading from disk, which improves speed by a noticable amount. + * <br> + * There are also some extra features implemented that allow you to break the + * standard XML reader specs and eliminate leading and trailing whitespace in + * all read content. This is useful in situations where you allow additional + * whitespace in the files to make them easily human readable. The resturned + * content will be NULL in sitautions where all content between nodes was + * stripped. + *@author Mike Buland + */ +class XmlFileReader : public XmlReader +{ +public: + /** + * Construct an XmlReader around an xml file on your file system. + *@param sFile The file to read. + *@param bStrip Set to true to strip out leading and trailing whitespace in + * node contents. + */ + XmlFileReader( const char *sFile, bool bStrip=false ); + + /** + * Destroy the reader and cleanup. + */ + ~XmlFileReader(); + +private: + char getChar( int nIndex = 0 ); + void usedChar(); + FILE *fh; /**< The file handle. */ + FlexBuf fbDataIn; /**< The input buffer. */ +}; + +#endif diff --git a/src/xmlfilewriter.cpp b/src/xmlfilewriter.cpp new file mode 100644 index 0000000..b62fb11 --- /dev/null +++ b/src/xmlfilewriter.cpp @@ -0,0 +1,22 @@ +#include <stdio.h> +#include <stdlib.h> +#include "xmlfilewriter.h" + +XmlFileWriter::XmlFileWriter( const char *sFileName, const char *sIndent, XmlNode *pRoot ) : + XmlWriter( sIndent, pRoot ) +{ + this->sFileName = sFileName; + fh = fopen( sFileName, "wt"); + fprintf( fh, "<?xml version=\"1.0\"?>\n"); +} + +XmlFileWriter::~XmlFileWriter() +{ + fclose( fh ); +} + +void XmlFileWriter::writeString( const char *sString ) +{ + fprintf( fh, sString ); +} + diff --git a/src/xmlfilewriter.h b/src/xmlfilewriter.h new file mode 100644 index 0000000..97b3e00 --- /dev/null +++ b/src/xmlfilewriter.h @@ -0,0 +1,44 @@ +#ifndef XML_FILE_WRITER +#define XML_FILE_WRITER + +#include "xmlnode.h" +#include "xmlwriter.h" + +/** + * Implements xml writing in the XML standard format. Also allows you to + * break that format and auto-indent your exported xml data for ease of + * reading. The auto-indenting will only be applied to sections that + * have no content of their own already. This means that except for + * whitespace all of your data will be preserved perfectly. + * You can create an XmlWriter object around a file, or access the static + * write function directly and just hand it a filename and a root XmlNode. + * When using an XmlWriter object the interface is identicle to that of + * the XmlDocument class, so reference that class for API info. However + * when the initial (or root) node is closed, and the document is finished + * the file will be created and written to automatically. The user can + * check to see if this is actually true by calling the isFinished + * function in the XmlDocument class. + *@author Mike Buland + */ +class XmlFileWriter : public XmlWriter +{ +public: + /** + * Construct a file writer around a given file. + *@param sFileName The file to create or overwrite and write XML into. + *@param sIndent The indent text to use, if any. + */ + XmlFileWriter( const char *sFileName, const char *sIndent=NULL, XmlNode *pRoot=NULL ); + + /** + * Destroy the writer. + */ + ~XmlFileWriter(); + +private: + void writeString( const char *sString ); + std::string sFileName; /**< The filename to write to. */ + FILE *fh; /**< The file handle to the open file. */ +}; + +#endif diff --git a/src/xmlnode.cpp b/src/xmlnode.cpp new file mode 100644 index 0000000..e5c77e2 --- /dev/null +++ b/src/xmlnode.cpp @@ -0,0 +1,454 @@ +#include "xmlnode.h" +#include "hashfunctionstring.h" + +XmlNode::XmlNode( const char *sName, XmlNode *pParent, const char *sContent ) : + hProperties( new HashFunctionString(), 53, false ), + hChildren( new HashFunctionString(), 53, true ) +{ + this->pParent = pParent; + if( sName != NULL ) + { + setName( sName ); + } + if( sContent != NULL ) + { + this->sPreContent = new std::string( sContent ); + } + else + { + this->sPreContent = NULL; + } + nCurContent = 0; +} + +XmlNode::~XmlNode() +{ + for( int j = 0; j < lChildren.getSize(); j++ ) + { + delete (XmlNode *)lChildren[j]; + } + for( int j = 0; j < lPropNames.getSize(); j++ ) + { + delete (std::string *)lPropNames[j]; + } + for( int j = 0; j < lPropValues.getSize(); j++ ) + { + delete (std::string *)lPropValues[j]; + } + for( int j = 0; j < lPostContent.getSize(); j++ ) + { + if( lPostContent[j] != NULL ) + { + delete (std::string *)lPostContent[j]; + } + } + if( sPreContent ) + { + delete sPreContent; + } +} + +void XmlNode::setName( const char *sName ) +{ + if( pParent ) + { + if( this->sName.size() == 0 ) + { + // We're not in the hash yet, so add us + this->sName = sName; + pParent->hChildren.insert( this->sName.c_str(), this ); + } + else + { + // Slightly more tricky, delete us, then add us... + pParent->hChildren.del( this->sName.c_str() ); + this->sName = sName; + pParent->hChildren.insert( this->sName.c_str(), this ); + } + } + else + { + // If we have no parent, then just set the name string, we don't need + // to worry about hashing. + this->sName = sName; + } +} + +void XmlNode::setContent( const char *sContent, int nIndex ) +{ + if( nIndex == -1 ) + { + nIndex = nCurContent; + } + if( nIndex == 0 ) + { + if( this->sPreContent ) + { + delete this->sPreContent; + } + + this->sPreContent = new std::string( sContent ); + } + else + { + nIndex--; + if( lPostContent[nIndex] ) + { + delete (std::string *)lPostContent[nIndex]; + } + + lPostContent.setAt( nIndex, new std::string( sContent ) ); + } +} + +const char *XmlNode::getContent( int nIndex ) +{ + if( nIndex == 0 ) + { + if( sPreContent ) + { + return sPreContent->c_str(); + } + } + else + { + nIndex--; + if( lPostContent[nIndex] ) + { + return ((std::string *)lPostContent[nIndex])->c_str(); + } + } + + return NULL; +} + +XmlNode *XmlNode::addChild( const char *sName, const char *sContent ) +{ + return addChild( new XmlNode( sName, this, sContent ) ); +} + +XmlNode *XmlNode::addChild( XmlNode *pNode ) +{ + lChildren.append( pNode ); + lPostContent.append( NULL ); + nCurContent++; + pNode->pParent = this; + + return pNode; +} + +XmlNode *XmlNode::getParent() +{ + return pParent; +} + +void XmlNode::addProperty( const char *sName, const char *sValue ) +{ + std::string *pName = new std::string( sName ); + std::string *pValue = new std::string( sValue ); + + hProperties.insert( pName->c_str(), pValue->c_str() ); + lPropNames.append( pName ); + lPropValues.append( pValue ); +} + +int XmlNode::getNumProperties() +{ + return lPropNames.getSize(); +} + +const char *XmlNode::getPropertyName( int nIndex ) +{ + std::string *tmp = ((std::string *)lPropNames[nIndex]); + if( tmp == NULL ) + return NULL; + return tmp->c_str(); +} + +const char *XmlNode::getProperty( int nIndex ) +{ + std::string *tmp = ((std::string *)lPropValues[nIndex]); + if( tmp == NULL ) + return NULL; + return tmp->c_str(); +} + +const char *XmlNode::getProperty( const char *sName ) +{ + const char *tmp = (const char *)hProperties[sName]; + if( tmp == NULL ) + return NULL; + return tmp; +} + +bool XmlNode::deleteProperty( int nIndex ) +{ + hProperties.del( ((std::string *)lPropNames[nIndex])->c_str() ); + + delete (std::string *)lPropNames[nIndex]; + delete (std::string *)lPropValues[nIndex]; + + lPropNames.deleteAt( nIndex ); + lPropValues.deleteAt( nIndex ); +} + +bool XmlNode::hasChildren() +{ + return lChildren.getSize()>0; +} + +int XmlNode::getNumChildren() +{ + return lChildren.getSize(); +} + +XmlNode *XmlNode::getChild( int nIndex ) +{ + return (XmlNode *)lChildren[nIndex]; +} + +XmlNode *XmlNode::getChild( const char *sName, int nSkip ) +{ + return (XmlNode *)hChildren.get( sName, nSkip ); +} + +const char *XmlNode::getName() +{ + return sName.c_str(); +} + +bool XmlNode::deleteNode( int nIndex, const char *sReplacementText ) +{ + XmlNode *xRet = detatchNode( nIndex, sReplacementText ); + + if( xRet == NULL ) + { + return false; + } + else + { + delete xRet; + return true; + } +} + +XmlNode *XmlNode::detatchNode( int nIndex, const char *sReplacementText ) +{ + if( nIndex < 0 || nIndex >= lChildren.getSize() ) + return NULL; + + // The real trick when deleteing a node isn't actually deleting it, it's + // reforming the content around the node that's now missing...hmmm... + + if( nIndex == 0 ) + { + // If the index is zero we have to deal with the pre-content + if( sReplacementText ) + { + if( sPreContent == NULL ) + { + sPreContent = new std::string( sReplacementText ); + } + else + { + *sPreContent += sReplacementText; + } + } + if( lPostContent.getSize() > 0 ) + { + if( lPostContent[0] != NULL ) + { + if( sPreContent == NULL ) + { + sPreContent = new std::string( + ((std::string *)lPostContent[0])->c_str() + ); + } + else + { + *sPreContent += + ((std::string *)lPostContent[0])->c_str(); + } + } + delete (std::string *)lPostContent[0]; + lPostContent.deleteAt( 0 ); + } + } + else + { + int nCont = nIndex-1; + // If it's above zero we deal with the post-content only + if( sReplacementText ) + { + if( lPostContent[nCont] == NULL ) + { + lPostContent.setAt( nCont, new std::string( sReplacementText ) ); + } + else + { + *((std::string *)lPostContent[nCont]) += sReplacementText; + } + } + if( lPostContent.getSize() > nIndex ) + { + if( lPostContent[nIndex] != NULL ) + { + if( lPostContent[nCont] == NULL ) + { + lPostContent.setAt( nCont, new std::string( + ((std::string *)lPostContent[nIndex])->c_str() + ) ); + } + else + { + *((std::string *)lPostContent[nCont]) += + ((std::string *)lPostContent[nIndex])->c_str(); + } + } + delete (std::string *)lPostContent[nIndex]; + lPostContent.deleteAt( nIndex ); + } + } + + XmlNode *xRet = (XmlNode *)lChildren[nIndex]; + hChildren.del( ((XmlNode *)lChildren[nIndex])->getName() ); + lChildren.deleteAt( nIndex ); + + return xRet; +} + +bool XmlNode::replaceNode( int nIndex, XmlNode *pNewNode ) +{ + if( nIndex < 0 || nIndex >= lChildren.getSize() ) + return false; + + delete (XmlNode *)lChildren[nIndex]; + lChildren.setAt( nIndex, pNewNode ); + pNewNode->pParent = this; + + return true; +} + +XmlNode *XmlNode::getCopy() +{ + XmlNode *pNew = new XmlNode(); + + pNew->sName = sName; + if( sPreContent ) + { + pNew->sPreContent = new std::string( sPreContent->c_str() ); + } + else + { + pNew->sPreContent = NULL; + } + pNew->nCurContent = 0; + + int nSize = lPostContent.getSize(); + pNew->lPostContent.setSize( nSize ); + for( int j = 0; j < nSize; j++ ) + { + if( lPostContent[j] ) + { + pNew->lPostContent.setAt( + j, new std::string( + ((std::string *)lPostContent[j])->c_str() + ) + ); + } + else + { + pNew->lPostContent.setAt( j, NULL ); + } + } + + nSize = lChildren.getSize(); + pNew->lChildren.setSize( nSize ); + for( int j = 0; j < nSize; j++ ) + { + XmlNode *pChild = ((XmlNode *)lChildren[j])->getCopy(); + pNew->lChildren.setAt( j, pChild ); + pChild->pParent = this; + hChildren.insert( pChild->getName(), pChild ); + } + + nSize = lPropNames.getSize(); + pNew->lPropNames.setSize( nSize ); + pNew->lPropValues.setSize( nSize ); + for( int j = 0; j < nSize; j++ ) + { + std::string *pProp = new std::string( ((std::string *)lPropNames[j])->c_str() ); + std::string *pVal = new std::string( ((std::string *)lPropValues[j])->c_str() ); + pNew->lPropNames.setAt( j, pProp ); + pNew->lPropValues.setAt( j, pVal ); + pNew->hProperties.insert( pProp->c_str(), pVal->c_str() ); + pNew->nCurContent++; + } + + return pNew; +} + +bool XmlNode::deleteNodeKeepChildren( int nIndex ) +{ + // This is a tricky one...we need to do some patching to keep things all + // even... + XmlNode *xRet = (XmlNode *)lChildren[nIndex]; + + if( xRet == NULL ) + { + return false; + } + else + { + if( getContent( nIndex ) ) + { + std::string sBuf( getContent( nIndex ) ); + sBuf += xRet->getContent( 0 ); + setContent( sBuf.c_str(), nIndex ); + } + else + { + setContent( xRet->getContent( 0 ), nIndex ); + } + + int nSize = xRet->lChildren.getSize(); + for( int j = 0; j < nSize; j++ ) + { + XmlNode *pCopy = ((XmlNode *)xRet->lChildren[j])->getCopy(); + pCopy->pParent = this; + lChildren.insertBefore( pCopy, nIndex+j ); + + if( xRet->lPostContent[j] ) + { + lPostContent.insertBefore( + new std::string( ((std::string *)xRet->lPostContent[j])->c_str() ), + nIndex+j + ); + } + else + { + lPostContent.insertBefore( NULL, nIndex+j ); + } + } + + if( getContent( nIndex+nSize ) ) + { + //SString sBuf( getContent( nIndex+nSize ) ); + //sBuf.catfrom( xRet->getContent( nSize ) ); + //setContent( sBuf, nIndex+nSize ); + } + else + { + setContent( xRet->getContent( nSize ), nIndex+nSize ); + } + + deleteNode( nIndex+nSize ); + return true; + } + +} + +bool XmlNode::replaceNodeWithChildren( int nIndex, XmlNode *pNewNode ) +{ +} + diff --git a/src/xmlnode.h b/src/xmlnode.h new file mode 100644 index 0000000..2b01226 --- /dev/null +++ b/src/xmlnode.h @@ -0,0 +1,236 @@ +#ifndef XMLNODE +#define XMLNODE + +#include <iostream> +#include "linkedlist.h" +#include "hashtable.h" + +/** + * Maintains all data pertient to an XML node, including sub-nodes and content. + * All child nodes can be accessed through index and through name via a hash + * table. This makes it very easy to gain simple and fast access to all of + * your data. For most applications, the memory footprint is also rather + * small. While XmlNode objects can be used directly to create XML structures + * it is highly reccomended that all operations be performed through the + * XmlDocument class. + *@author Mike Buland + */ +class XmlNode +{ +public: + /** + * Construct a new XmlNode. + *@param sName The name of the node. + *@param pParent The parent node. + *@param sContent The initial content string. + */ + XmlNode( + const char *sName=NULL, + XmlNode *pParent = NULL, + const char *sContent=NULL + ); + + /** + * Delete the node and cleanup all memory. + */ + ~XmlNode(); + + /** + * Change the name of the node. + *@param sName The new name of the node. + */ + void setName( const char *sName ); + + /** + * Construct a new node and add it as a child to this node, also return a + * pointer to the newly constructed node. + *@param sName The name of the new node. + *@param sContent The initial content of the new node. + *@returns A pointer to the newly created child node. + */ + XmlNode *addChild( const char *sName, const char *sContent=NULL ); + + /** + * Add an already created XmlNode as a child to this node. The new child + * XmlNode's parent will be changed appropriately and the parent XmlNode + * will take ownership of the child. + *@param pChild The child XmlNode to add to this XmlNode. + *@returns A pointer to the child node that was just added. + */ + XmlNode *addChild( XmlNode *pChild ); + + /** + * Add a new property to the XmlNode. Properties are name/value pairs. + *@param sName The name of the property. Specifying a name that's already + * in use will overwrite that property. + *@param sValue The textual value of the property. + */ + void addProperty( const char *sName, const char *sValue ); + + /** + * Get a pointer to the parent node, if any. + *@returns A pointer to the node's parent, or NULL if there isn't one. + */ + XmlNode *getParent(); + + /** + * Tells you if this node has children. + *@returns True if this node has at least one child, false otherwise. + */ + bool hasChildren(); + + /** + * Tells you how many children this node has. + *@returns The number of children this node has. + */ + int getNumChildren(); + + /** + * Get a child node at a specific index. + *@param nIndex The zero-based index of the child to retreive. + *@returns A pointer to the child, or NULL if you requested an invalid + * index. + */ + XmlNode *getChild( int nIndex ); + + /** + * Get a child with the specified name, and possibly skip value. For an + * explination of skip values see the HashTable. + *@param sName The name of the child to find. + *@param nSkip The number of nodes with that name to skip. + *@returns A pointer to the child, or NULL if no child with that name was + * found. + */ + XmlNode *getChild( const char *sName, int nSkip=0 ); + + /** + * Get a pointer to the name of this node. Do not change this, use setName + * instead. + *@returns A pointer to the name of this node. + */ + const char *getName(); + + /** + * Set the content of this node, optionally at a specific index. Using the + * default of -1 will set the content after the last added node. + *@param sContent The content string to use. + *@param nIndex The index of the content. + */ + void setContent( const char *sContent, int nIndex=-1 ); + + /** + * Get the content string at a given index, or zero for initial content. + *@param nIndex The index of the content. + *@returns A pointer to the content at that location. + */ + const char *getContent( int nIndex = 0 ); + + /** + * Get the number of properties in this node. + *@returns The number of properties in this node. + */ + int getNumProperties(); + + /** + * Get a property's name by index. + *@param nIndex The index of the property to examine. + *@returns A pointer to the name of the property specified, or NULL if none + * found. + */ + const char *getPropertyName( int nIndex ); + + /** + * Get a proprty's value by index. + *@param nIndex The index of the property to examine. + *@returns A pointer to the value of the property specified, or NULL if none + * found. + */ + const char *getProperty( int nIndex ); + + /** + * Get a propery's value by name. + *@param sName The name of the property to examine. + *@returns A pointer to the value of the property specified, or NULL if none + * found. + */ + const char *getProperty( const char *sName ); + + /** + * Delete a property by index. + *@param nIndex The index of the property to delete. + *@returns True if the property was found and deleted, false if it wasn't + * found. + */ + bool deleteProperty( int nIndex ); + + /** + * Delete a child node, possibly replacing it with some text. This actually + * fixes all content strings around the newly deleted child node. + *@param nIndex The index of the node to delete. + *@param sReplacementText The optional text to replace the node with. + *@returns True of the node was found, and deleted, false if it wasn't + * found. + */ + bool deleteNode( int nIndex, const char *sReplacementText = NULL ); + + /** + * Delete a given node, but move all of it's children and content up to + * replace the deleted node. All of the content of the child node is + * spliced seamlessly into place with the parent node's content. + *@param nIndex The node to delete. + *@returns True if the node was found and deleted, false if it wasn't. + */ + bool deleteNodeKeepChildren( int nIndex ); + + /** + * Detatch a given child node from this node. This effectively works just + * like a deleteNode, except that instead of deleting the node it is removed + * and returned, and all ownership is given up. + *@param nIndex The index of the node to detatch. + *@param sReplacementText The optional text to replace the detatched node + * with. + *@returns A pointer to the newly detatched node, which then passes + * ownership to the caller. + */ + XmlNode *detatchNode( int nIndex, const char *sReplacementText = NULL ); + + /** + * Replace a given node with a different node that is not currently owned by + * this XmlNode or any ancestor. + *@param nIndex The index of the node to replace. + *@param pNewNode The new node to replace the old node with. + *@returns True if the node was found and replaced, false if it wasn't. + */ + bool replaceNode( int nIndex, XmlNode *pNewNode ); + + /** + * Replace a given node with the children and content of a given node. + *@param nIndex The index of the node to replace. + *@param pNewNode The node that contains the children and content that will + * replace the node specified by nIndex. + *@returns True if the node was found and replaced, false if it wasn't. + */ + bool replaceNodeWithChildren( int nIndex, XmlNode *pNewNode ); + + /** + * Get a copy of this node and all children. getCopy is recursive, so + * beware copying large trees of xml. + *@returns A newly created copy of this node and all of it's children. + */ + XmlNode *getCopy(); + +private: + std::string sName; /**< The name of the node. */ + std::string *sPreContent; /**< The content that goes before any node. */ + LinkedList lChildren; /**< The children. */ + LinkedList lPostContent; /**< The content that comes after children. */ + HashTable hProperties; /**< Property hashtable. */ + HashTable hChildren; /**< Children hashtable. */ + LinkedList lPropNames; /**< List of property names. */ + LinkedList lPropValues; /**< List of property values. */ + XmlNode *pParent; /**< A pointer to the parent of this node. */ + int nCurContent; /**< The current content we're on, for using the -1 on + setContent. */ +}; + +#endif diff --git a/src/xmlreader.cpp b/src/xmlreader.cpp new file mode 100644 index 0000000..bb24157 --- /dev/null +++ b/src/xmlreader.cpp @@ -0,0 +1,412 @@ +#include "xmlreader.h" +#include <string.h> + +XmlReader::XmlReader( bool bStrip ) +{ + nError = 0; + this->bStrip = bStrip; +} + +XmlReader::~XmlReader() +{ +} + +#define gcall( x ) if( x == false ) return false; + +bool XmlReader::isws( char chr ) +{ + return ( chr == ' ' || chr == '\t' || chr == '\n' || chr == '\r' ); +} + +bool XmlReader::ws() +{ + while( true ) + { + char chr = getChar(); + if( isws( chr ) ) + { + usedChar(); + } + else + { + return true; + } + } + return true; +} + +bool XmlReader::buildDoc() +{ + // take care of initial whitespace + gcall( ws() ); + gcall( node() ); + + return true; +} + +bool XmlReader::node() +{ + gcall( startNode() ) + + // At this point, we are closing the startNode + char chr = getChar(); + if( chr == '>' ) + { + usedChar(); + + // Now we process the guts of the node. + gcall( content() ); + } + else if( chr == '/' ) + { + // This is the tricky one, one more validation, then we close the node. + usedChar(); + if( getChar() == '>' ) + { + closeNode(); + usedChar(); + } + else + { + reportError("Close node in singleNode malformed!"); + return false; + } + } + else + { + reportError("Close node expected, but not found."); + return false; + } + + return true; +} + +bool XmlReader::startNode() +{ + if( getChar() == '<' ) + { + usedChar(); + + if( getChar() == '/' ) + { + // Heh, it's actually a close node, go figure + FlexBuf fbName; + usedChar(); + gcall( ws() ); + + while( true ) + { + char chr = getChar(); + if( isws( chr ) || chr == '>' ) + { + // Here we actually compare the name we got to the name + // we already set, they have to match exactly. + if( !strcasecmp( getCurrent()->getName(), fbName.getData() ) ) + { + closeNode(); + break; + } + else + { + reportError("Got a mismatched node close tag."); + return false; + } + } + else + { + fbName.appendData( chr ); + usedChar(); + } + } + + gcall( ws() ); + if( getChar() == '>' ) + { + // Everything is cool. + usedChar(); + } + else + { + reportError("Got extra junk data instead of node close tag."); + return false; + } + } + else + { + // We're good, format is consistant + addNode(); + + // Skip extra whitespace + gcall( ws() ); + gcall( name() ); + gcall( ws() ); + gcall( paramlist() ); + gcall( ws() ); + } + } + else + { + reportError("Expected to find node opening char, '<'.\n"); + return false; + } + + return true; +} + +bool XmlReader::name() +{ + FlexBuf fbName; + + while( true ) + { + char chr = getChar(); + if( isws( chr ) || chr == '>' || chr == '/' ) + { + setName( fbName.getData() ); + return true; + } + else + { + fbName.appendData( chr ); + usedChar(); + } + } + + return true; +} + +bool XmlReader::paramlist() +{ + while( true ) + { + char chr = getChar(); + if( chr == '/' || chr == '>' ) + { + return true; + } + else + { + gcall( param() ); + gcall( ws() ); + } + } + + return true; +} + +char XmlReader::getEscape() +{ + // Right now, we just do # escapes... + if( getChar( 1 ) == '#' ) + { + usedChar(); + usedChar(); + char buf[4]; + int j = 0; + for( j = 0; getChar() != ';'; j++ ) + { + buf[j] = getChar(); + usedChar(); + } + usedChar(); + buf[j] = '\0'; + return (char)atoi( buf ); + } + else + { + return '\0'; + } +} + +bool XmlReader::param() +{ + FlexBuf fbName; + FlexBuf fbValue; + + while( true ) + { + char chr = getChar(); + if( isws( chr ) || chr == '=' ) + { + break; + } + else + { + fbName.appendData( chr ); + usedChar(); + } + } + + gcall( ws() ); + + if( getChar() == '=' ) + { + usedChar(); + + gcall( ws() ); + + char chr = getChar(); + if( chr == '"' ) + { + // Better quoted rhs + usedChar(); + + while( true ) + { + chr = getChar(); + if( chr == '"' ) + { + usedChar(); + addProperty( fbName.getData(), fbValue.getData() ); + return true; + } + else + { + if( chr == '&' ) + { + chr = getEscape(); + if( chr == '\0' ) return false; + fbValue.appendData( chr ); + } + else + { + fbValue.appendData( chr ); + usedChar(); + } + } + } + } + else + { + // Simple one-word rhs + while( true ) + { + chr = getChar(); + if( isws( chr ) || chr == '/' || chr == '>' ) + { + addProperty( fbName.getData(), fbValue.getData() ); + return true; + } + else + { + if( chr == '&' ) + { + chr = getEscape(); + if( chr == '\0' ) return false; + fbValue.appendData( chr ); + } + else + { + fbValue.appendData( chr ); + usedChar(); + } + } + } + } + } + else + { + reportError("Expected an equals to seperate the params."); + return false; + } + + return true; +} + +bool XmlReader::content() +{ + FlexBuf fbContent; + + if( bStrip ) gcall( ws() ); + + while( true ) + { + char chr = getChar(); + if( chr == '<' ) + { + if( getChar(1) == '/' ) + { + if( fbContent.getLength() > 0 ) + { + if( bStrip ) + { + int j; + for( j = fbContent.getLength()-1; isws(fbContent.getData()[j]); j-- ); + ((char *)fbContent.getData())[j+1] = '\0'; + } + setContent( fbContent.getData() ); + } + usedChar(); + usedChar(); + gcall( ws() ); + FlexBuf fbName; + while( true ) + { + chr = getChar(); + if( isws( chr ) || chr == '>' ) + { + if( !strcasecmp( getCurrent()->getName(), fbName.getData() ) ) + { + closeNode(); + break; + } + else + { + reportError("Mismatched close tag found."); + return false; + } + } + else + { + fbName.appendData( chr ); + usedChar(); + } + } + gcall( ws() ); + if( getChar() == '>' ) + { + usedChar(); + return true; + } + else + { + reportError("Malformed close tag."); + return false; + } + } + else + { + if( fbContent.getLength() > 0 ) + { + if( bStrip ) + { + int j; + for( j = fbContent.getLength()-1; isws(fbContent.getData()[j]); j-- ); + ((char *)fbContent.getData())[j+1] = '\0'; + } + setContent( fbContent.getData() ); + fbContent.clearData(); + } + gcall( node() ); + } + + if( bStrip ) gcall( ws() ); + } + else + { + fbContent.appendData( chr ); + usedChar(); + } + } +} + +void XmlReader::reportError( const char *sError ) +{ + printf("XmlReader error: %s\n", sError ); +} + +int XmlReader::getError() +{ + return nError; +} + diff --git a/src/xmlreader.h b/src/xmlreader.h new file mode 100644 index 0000000..a8a81f0 --- /dev/null +++ b/src/xmlreader.h @@ -0,0 +1,133 @@ +#ifndef XMLREADER +#define XMLREADER + +#include <stdio.h> +#include "xmldocument.h" +#include "flexbuf.h" + +/** + * Takes care of reading in xml formatted data from a file. This could/should + * be made more arbitrary in the future so that we can read the data from any + * source. This is actually made quite simple already since all data read in + * is handled by one single helper function and then palced into a FlexBuf for + * easy access by the other functions. The FlexBuf also allows for block + * reading from disk, which improves speed by a noticable amount. + * <br> + * There are also some extra features implemented that allow you to break the + * standard XML reader specs and eliminate leading and trailing whitespace in + * all read content. This is useful in situations where you allow additional + * whitespace in the files to make them easily human readable. The resturned + * content will be NULL in sitautions where all content between nodes was + * stripped. + *@author Mike Buland + */ +class XmlReader : public XmlDocument +{ +public: + /** + * Create a standard XmlReader. The optional parameter bStrip allows you to + * create a reader that will strip out all leading and trailing whitespace + * in content, a-la html. + *@param bStrip Strip out leading and trailing whitespace? + */ + XmlReader( bool bStrip=false ); + + /** + * Destroy this XmlReader. + */ + ~XmlReader(); + + /** + * Get the error code if an error happened. + *@returns The error code (I don't know what they are either) + */ + int getError(); + + /** + * Report an error to something, this is a really strange mechanism and + * should probably just be replaced with the multi-log system. + *@param sError The error to report. + */ + void reportError( const char *sError ); + + /** + * Build a document based on some kind of input. This is called + * automatically by the constructor. + */ + bool buildDoc(); + +private: + /** + * This is called by the low level automoton in order to get the next + * character. This function should return a character at the current + * position plus nIndex, but does not increment the current character. + *@param nIndex The index of the character from the current stream position. + *@returns A single character at the requested position, or 0 for end of + * stream. + */ + virtual char getChar( int nIndex = 0 ) = 0; + + /** + * Called to increment the current stream position by a single character. + */ + virtual void usedChar() = 0; + + /** + * Automoton function: is whitespace. + *@param chr A character + *@returns True if chr is whitespace, false otherwise. + */ + bool isws( char chr ); + + /** + * Automoton function: ws. Skips sections of whitespace. + *@returns True if everything was ok, False for end of stream. + */ + bool ws(); + + /** + * Automoton function: node. Processes an XmlNode + *@returns True if everything was ok, False for end of stream. + */ + bool node(); + + /** + * Automoton function: startNode. Processes the begining of a node. + *@returns True if everything was ok, False for end of stream. + */ + bool startNode(); + + /** + * Automoton function: name. Processes the name of a node. + *@returns True if everything was ok, False for end of stream. + */ + bool name(); + + char getEscape(); + + /** + * Automoton function: paramlist. Processes a list of node params. + *@returns True if everything was ok, False for end of stream. + */ + bool paramlist(); + + /** + * Automoton function: param. Processes a single parameter. + *@returns True if everything was ok, False for end of stream. + */ + bool param(); + + /** + * Automoton function: content. Processes node content. + *@returns True if everything was ok, False for end of stream. + */ + bool content(); + + FlexBuf fbContent; /**< buffer for the current node's content. */ + FlexBuf fbParamName; /**< buffer for the current param's name. */ + FlexBuf fbParamValue; /**< buffer for the current param's value. */ + bool bStrip; /**< Are we stripping whitespace? */ + int nError; /**< Is there an error? */ +}; + +#endif diff --git a/src/xmlstringreader.cpp b/src/xmlstringreader.cpp new file mode 100644 index 0000000..aa7174f --- /dev/null +++ b/src/xmlstringreader.cpp @@ -0,0 +1,37 @@ +#include "xmlstringreader.h" +#include <string.h> + +XmlStringReader::XmlStringReader( const char *sString, bool bStrip ) + : XmlReader( bStrip ) +{ + this->sString = sString; + + nIndex = 0; + nLength = strlen( sString ); + + buildDoc(); +} + +XmlStringReader::~XmlStringReader() +{ +} + +char XmlStringReader::getChar( int nAdd ) +{ + if( nLength >= nIndex+nAdd+1 ) + { + return sString[nIndex+nAdd]; + } + else + { + return '\0'; + } +} + +void XmlStringReader::usedChar() +{ + if( nLength >= nIndex+1 ) + { + nIndex++; + } +} diff --git a/src/xmlstringreader.h b/src/xmlstringreader.h new file mode 100644 index 0000000..07da83c --- /dev/null +++ b/src/xmlstringreader.h @@ -0,0 +1,49 @@ +#ifndef XMLSTRINGREADER +#define XMLSTRINGREADER + +#include <stdio.h> +#include "xmlreader.h" +#include "flexbuf.h" + +/** + * Takes care of reading in xml formatted data from a file. This could/should + * be made more arbitrary in the future so that we can read the data from any + * source. This is actually made quite simple already since all data read in + * is handled by one single helper function and then palced into a FlexBuf for + * easy access by the other functions. The FlexBuf also allows for block + * reading from disk, which improves speed by a noticable amount. + * <br> + * There are also some extra features implemented that allow you to break the + * standard XML reader specs and eliminate leading and trailing whitespace in + * all read content. This is useful in situations where you allow additional + * whitespace in the files to make them easily human readable. The resturned + * content will be NULL in sitautions where all content between nodes was + * stripped. + *@author Mike Buland + */ +class XmlStringReader : public XmlReader +{ +public: + /** + * Create a new string reader around an already created and formatted + * null-terminated string. + *@param sString A pointer to the string data that will be used. This data + * is not changed during processing. + *@param bStrip Strip out leading and trailing whitespace. + */ + XmlStringReader( const char *sString, bool bStrip=false ); + + /** + * Destroy this string reader. + */ + ~XmlStringReader(); + +private: + char getChar( int nIndex = 0 ); + void usedChar(); + const char *sString; /**< Internal pointer to the input string. */ + int nIndex; /**< Our index into the string */ + int nLength; /**< The computed length of the string */ +}; + +#endif diff --git a/src/xmlstringwriter.cpp b/src/xmlstringwriter.cpp new file mode 100644 index 0000000..adeed6a --- /dev/null +++ b/src/xmlstringwriter.cpp @@ -0,0 +1,23 @@ +#include <stdio.h> +#include <stdlib.h> +#include "xmlstringwriter.h" + +XmlStringWriter::XmlStringWriter( const char *sIndent ) : + XmlWriter( sIndent ) +{ +} + +XmlStringWriter::~XmlStringWriter() +{ +} + +void XmlStringWriter::writeString( const char *sString ) +{ + sXml += sString; +} + +std::string &XmlStringWriter::getString() +{ + return sXml; +} + diff --git a/src/xmlstringwriter.h b/src/xmlstringwriter.h new file mode 100644 index 0000000..530db3e --- /dev/null +++ b/src/xmlstringwriter.h @@ -0,0 +1,50 @@ +#ifndef XML_STRING_WRITER +#define XML_STRING_WRITER + +#include "xmlnode.h" +#include "xmlwriter.h" + +/** + * Implements xml writing in the XML standard format. Also allows you to + * break that format and auto-indent your exported xml data for ease of + * reading. The auto-indenting will only be applied to sections that + * have no content of their own already. This means that except for + * whitespace all of your data will be preserved perfectly. + * You can create an XmlWriter object around a file, or access the static + * write function directly and just hand it a filename and a root XmlNode. + * When using an XmlWriter object the interface is identicle to that of + * the XmlDocument class, so reference that class for API info. However + * when the initial (or root) node is closed, and the document is finished + * the file will be created and written to automatically. The user can + * check to see if this is actually true by calling the isFinished + * function in the XmlDocument class. + *@author Mike Buland + */ +class XmlStringWriter : public XmlWriter +{ +public: + /** + * Construct a string writer using an internal string buffer. + *@param sIndent Optional indent to add to each line. + */ + XmlStringWriter( const char *sIndent=NULL ); + + /** + * Destroy the string writer and the internal string. + */ + ~XmlStringWriter(); + + /** + * Get the string that was built. This is only valid after the document has + * been completed, so check isCompleted or be sure your addNode and + * closeNode calls match up. + *@returns A reference to the internal string object. + */ + std::string &getString(); + +private: + void writeString( const char *sString ); + std::string sXml; /**< The string object we "write" to. */ +}; + +#endif diff --git a/src/xmlwriter.cpp b/src/xmlwriter.cpp new file mode 100644 index 0000000..236939d --- /dev/null +++ b/src/xmlwriter.cpp @@ -0,0 +1,173 @@ +#include <stdio.h> +#include <stdlib.h> +#include "xmlwriter.h" + +XmlWriter::XmlWriter( const char *sIndent, XmlNode *pRoot ) : + XmlDocument( pRoot ) +{ + if( sIndent == NULL ) + { + this->sIndent = ""; + } + else + { + this->sIndent = sIndent; + } +} + +XmlWriter::~XmlWriter() +{ +} + +void XmlWriter::write() +{ + write( getRoot(), sIndent.c_str() ); +} + +void XmlWriter::write( XmlNode *pRoot, const char *sIndent ) +{ + writeNode( pRoot, 0, sIndent ); +} + +void XmlWriter::closeNode() +{ + XmlDocument::closeNode(); + + if( isCompleted() ) + { + write( getRoot(), sIndent.c_str() ); + } +} + +void XmlWriter::writeIndent( int nIndent, const char *sIndent ) +{ + if( sIndent == NULL ) return; + for( int j = 0; j < nIndent; j++ ) + { + writeString( sIndent ); + } +} + +std::string XmlWriter::escape( std::string sIn ) +{ + std::string sOut; + + std::string::const_iterator i; + for( i = sIn.begin(); i != sIn.end(); i++ ) + { + if( ((*i >= ' ' && *i <= '9') || + (*i >= 'a' && *i <= 'z') || + (*i >= 'A' && *i <= 'Z') ) && + (*i != '\"' && *i != '\'' ) + ) + { + sOut += *i; + } + else + { + sOut += "&#"; + char buf[4]; + sprintf( buf, "%d", *i ); + sOut += buf; + sOut += ';'; + } + } + + return sOut; +} + +void XmlWriter::writeNodeProps( XmlNode *pNode, int nIndent, const char *sIndent ) +{ + for( int j = 0; j < pNode->getNumProperties(); j++ ) + { + writeString(" "); + writeString( pNode->getPropertyName( j ) ); + writeString("=\""); + writeString( escape( pNode->getProperty( j ) ).c_str() ); + writeString("\""); + } +} + +void XmlWriter::writeNode( XmlNode *pNode, int nIndent, const char *sIndent ) +{ + if( pNode->hasChildren() ) + { + writeIndent( nIndent, sIndent ); + writeString("<"); + writeString( pNode->getName() ); + writeNodeProps( pNode, nIndent, sIndent ); + if( sIndent ) + writeString(">\n"); + else + writeString(">"); + + if( pNode->getContent( 0 ) ) + { + writeIndent( nIndent+1, sIndent ); + if( sIndent ) + { + writeString( pNode->getContent( 0 ) ); + writeString("\n"); + } + else + writeString( pNode->getContent( 0 ) ); + } + + int nNumChildren = pNode->getNumChildren(); + for( int j = 0; j < nNumChildren; j++ ) + { + writeNode( pNode->getChild( j ), nIndent+1, sIndent ); + if( pNode->getContent( j+1 ) ) + { + writeIndent( nIndent+1, sIndent ); + if( sIndent ) + { + writeString( pNode->getContent( j+1 ) ); + writeString("\n"); + } + else + writeString( pNode->getContent( j+1 ) ); + } + } + + writeIndent( nIndent, sIndent ); + if( sIndent ) + { + writeString("</"); + writeString( pNode->getName() ); + writeString(">\n"); + } + else + { + writeString("</"); + writeString( pNode->getName() ); + writeString(">"); + } + } + else if( pNode->getContent() ) + { + writeIndent( nIndent, sIndent ); + writeString("<"); + writeString( pNode->getName() ); + writeNodeProps( pNode, nIndent, sIndent ); + writeString(">"); + writeString( pNode->getContent() ); + writeString("</"); + writeString( pNode->getName() ); + writeString(">"); + if( sIndent ) + writeString("\n"); + } + else + { + writeIndent( nIndent, sIndent ); + writeString("<"); + writeString( pNode->getName() ); + writeNodeProps( pNode, nIndent, sIndent ); + if( sIndent ) + writeString("/>\n"); + else + writeString("/>"); + } +} + diff --git a/src/xmlwriter.h b/src/xmlwriter.h new file mode 100644 index 0000000..5bc3f0a --- /dev/null +++ b/src/xmlwriter.h @@ -0,0 +1,96 @@ +#ifndef XMLWRITER +#define XMLWRITER + +#include "xmlnode.h" +#include "xmldocument.h" + +/** + * Implements xml writing in the XML standard format. Also allows you to + * break that format and auto-indent your exported xml data for ease of + * reading. The auto-indenting will only be applied to sections that + * have no content of their own already. This means that except for + * whitespace all of your data will be preserved perfectly. + * You can create an XmlWriter object around a file, or access the static + * write function directly and just hand it a filename and a root XmlNode. + * When using an XmlWriter object the interface is identicle to that of + * the XmlDocument class, so reference that class for API info. However + * when the initial (or root) node is closed, and the document is finished + * the file will be created and written to automatically. The user can + * check to see if this is actually true by calling the isFinished + * function in the XmlDocument class. + *@author Mike Buland + */ +class XmlWriter : public XmlDocument +{ +public: + /** + * Construct a standard XmlWriter. + *@param sIndent Set this to something other than NULL to include it as an + * indent before each node in the output that doesn't already have content. + * If you are using the whitespace stripping option in the XmlReader and set + * this to a tab or some spaces it will never effect the content of your + * file. + */ + XmlWriter( const char *sIndent=NULL, XmlNode *pRoot=NULL ); + + /** + * Destroy the writer. + */ + ~XmlWriter(); + + /** + * This override of the parent class closeNode function calls the parent + * class, but also triggers a write operation when the final node is closed. + * This means that by checking the isCompleted() function the user may also + * check to see if their file has been written or not. + */ + void closeNode(); + + void write(); + +private: + std::string sIndent; /**< The indent string */ + + std::string escape( std::string sIn ); + + /** + * Write the file. + *@param pNode The root node + *@param sIndent The indent text. + */ + void write( XmlNode *pNode, const char *sIndent=NULL ); + + /** + * Write a node in the file, including children. + *@param pNode The node to write. + *@param nIndent The indent level (the number of times to include sIndent) + *@param sIndent The indent text. + */ + void writeNode( XmlNode *pNode, int nIndent, const char *sIndent ); + + /** + * Write the properties of a node. + *@param pNode The node who's properties to write. + *@param nIndent The indent level of the containing node + *@param sIndent The indent text. + */ + void writeNodeProps( XmlNode *pNode, int nIndent, const char *sIndent ); + + /** + * Called to write the actual indent. + *@param nIndent The indent level. + *@param sIndent The indent text. + */ + void writeIndent( int nIndent, const char *sIndent ); + + /** + * This is the function that must be overridden in order to use this class. + * It must write the null-terminated string sString, minus the mull, + * verbatum to it's output device. Adding extra characters for any reason + * will break the XML formatting. + *@param sString The string data to write to the output. + */ + virtual void writeString( const char *sString ) = 0; +}; + +#endif -- cgit v1.2.3