diff options
Diffstat (limited to '')
85 files changed, 9916 insertions, 0 deletions
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 @@ | |||
| 1 | #include "arraylist.h" | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <string.h> | ||
| 4 | |||
| 5 | ArrayList::ArrayList( int initSize, int growByFactor ) | ||
| 6 | { | ||
| 7 | apData = new void *[initSize]; | ||
| 8 | nSize = 0; | ||
| 9 | nCapacity = initSize; | ||
| 10 | nGrowByFactor = growByFactor; | ||
| 11 | } | ||
| 12 | |||
| 13 | ArrayList::~ArrayList( ) | ||
| 14 | { | ||
| 15 | delete[] apData; | ||
| 16 | } | ||
| 17 | |||
| 18 | void *ArrayList::getAt( int index ) | ||
| 19 | { | ||
| 20 | if( index < 0 || index > nSize ) | ||
| 21 | return NULL; | ||
| 22 | |||
| 23 | return apData[index]; | ||
| 24 | } | ||
| 25 | |||
| 26 | void ArrayList::append( void *data ) | ||
| 27 | { | ||
| 28 | insertBefore( data, nSize ); | ||
| 29 | } | ||
| 30 | |||
| 31 | void ArrayList::insertBefore( void *data, int pos ) | ||
| 32 | { | ||
| 33 | if( pos < 0 || pos > nSize ) | ||
| 34 | return; | ||
| 35 | |||
| 36 | checkResize(); | ||
| 37 | memmove( &apData[pos+1], &apData[pos], (nSize-pos)*sizeof(void*) ); | ||
| 38 | apData[pos] = data; | ||
| 39 | nSize++; | ||
| 40 | } | ||
| 41 | |||
| 42 | int ArrayList::getSize( ) | ||
| 43 | { | ||
| 44 | return nSize; | ||
| 45 | } | ||
| 46 | |||
| 47 | bool ArrayList::isEmpty( ) | ||
| 48 | { | ||
| 49 | return nSize==0; | ||
| 50 | } | ||
| 51 | |||
| 52 | void ArrayList::deleteAt( int index ) | ||
| 53 | { | ||
| 54 | if( index < 0 || index >= nSize ) | ||
| 55 | return; | ||
| 56 | |||
| 57 | memmove( &apData[index], &apData[index+1], (nSize-index-1)*sizeof(void *) ); | ||
| 58 | nSize--; | ||
| 59 | } | ||
| 60 | |||
| 61 | void ArrayList::empty() | ||
| 62 | { | ||
| 63 | // Probably the easiest as far as things go. | ||
| 64 | nSize = 0; | ||
| 65 | } | ||
| 66 | |||
| 67 | void ArrayList::resizeTo( int newSize ) | ||
| 68 | { | ||
| 69 | void **apNew = new void *[newSize]; | ||
| 70 | memmove( apNew, apData, nSize*sizeof(void *) ); | ||
| 71 | nCapacity = newSize; | ||
| 72 | delete[] apData; | ||
| 73 | apData = apNew; | ||
| 74 | } | ||
| 75 | |||
| 76 | void ArrayList::checkResize() | ||
| 77 | { | ||
| 78 | if( nSize >= nCapacity ) | ||
| 79 | { | ||
| 80 | resizeTo( nCapacity + nGrowByFactor ); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | void ArrayList::setSize( int newSize ) | ||
| 85 | { | ||
| 86 | if( newSize < 0 ) | ||
| 87 | return; | ||
| 88 | |||
| 89 | nSize = newSize; | ||
| 90 | checkResize(); | ||
| 91 | } | ||
| 92 | |||
| 93 | void ArrayList::setAt( int index, void *data ) | ||
| 94 | { | ||
| 95 | if( index < 0 || index >= nSize ) | ||
| 96 | return; | ||
| 97 | |||
| 98 | apData[index] = data; | ||
| 99 | } | ||
| 100 | |||
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 @@ | |||
| 1 | /** \file arraylist.h | ||
| 2 | * Describes the ArrayList class. | ||
| 3 | *@author Mike Buland | ||
| 4 | */ | ||
| 5 | #ifndef ARRAY_LIST_H | ||
| 6 | #define ARRAY_LIST_H | ||
| 7 | |||
| 8 | #include "list.h" | ||
| 9 | |||
| 10 | /** A simple list which uses an array. This is a great choice if you won't do | ||
| 11 | * a lot of adding and deleting and need a fast random access list. Otherwise | ||
| 12 | * use the LinkedList. | ||
| 13 | *@author Mike Buland | ||
| 14 | */ | ||
| 15 | class ArrayList : public List | ||
| 16 | { | ||
| 17 | public: | ||
| 18 | /** Creates an arraylist with some pre-defined specs spelled out. | ||
| 19 | *@param initSize the inital number of elements to allocate. | ||
| 20 | *@param growByFactor How much to increase the size of the array by | ||
| 21 | * each time we run out of room. | ||
| 22 | */ | ||
| 23 | ArrayList( int initSize=100, int growByFactor=10 ); | ||
| 24 | /** | ||
| 25 | * Destroy the ArrayList | ||
| 26 | */ | ||
| 27 | ~ArrayList(); | ||
| 28 | |||
| 29 | void *getAt( int nIndex ); | ||
| 30 | void append( void *pData ); | ||
| 31 | void insertBefore( void *pData, int nPos = 0 ); | ||
| 32 | int getSize( ); | ||
| 33 | bool isEmpty( ); | ||
| 34 | void deleteAt( int nIndex ); | ||
| 35 | void empty(); | ||
| 36 | void setSize( int nNewSize ); | ||
| 37 | void setAt( int nIndex, void *pData ); | ||
| 38 | |||
| 39 | private: | ||
| 40 | /** | ||
| 41 | * Checks to see if the system needs to be resized, if it does, this will | ||
| 42 | * automatically resize based on your parameters. | ||
| 43 | */ | ||
| 44 | void checkResize(); | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Resize the system to a specified size. If it is larger, then all data | ||
| 48 | * will be retained, if smaller the elements at the end will be cut off. | ||
| 49 | *@param newSize The number of elements to include after resizing. | ||
| 50 | */ | ||
| 51 | void resizeTo( int newSize ); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Actual master array of pointers. This is done to follow the List specs. | ||
| 55 | * All data transactions are performed with pointers or compatable | ||
| 56 | * primitive data-types. | ||
| 57 | */ | ||
| 58 | void **apData; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * The number of filled in elements in the array. This is the practical | ||
| 62 | * real size of the ArrayList for all userspace applications. | ||
| 63 | */ | ||
| 64 | int nSize; | ||
| 65 | |||
| 66 | /** | ||
| 67 | * The number of elements allocated in memory. Not all of these have to be | ||
| 68 | * filled in, and it is usually larger than nSize so that adding and | ||
| 69 | * deleting elements is fast and easy. | ||
| 70 | */ | ||
| 71 | int nCapacity; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * The amount to grow by whenever the array needs resizing. | ||
| 75 | */ | ||
| 76 | int nGrowByFactor; | ||
| 77 | }; | ||
| 78 | |||
| 79 | #endif | ||
| 80 | |||
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 @@ | |||
| 1 | #include <string.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <stdarg.h> | ||
| 5 | #include <sys/stat.h> | ||
| 6 | |||
| 7 | #include "cgi.h" | ||
| 8 | |||
| 9 | Cgi::Cgi( const char *strSource ) : | ||
| 10 | aContent( new HashFunctionString(), 151, true ) | ||
| 11 | { | ||
| 12 | int length, j, k, mode = 0, slen = 0; | ||
| 13 | char hexbuf[3] = { 0, 0, 0 }; | ||
| 14 | char *buf, chr; | ||
| 15 | Item *cur = NULL; | ||
| 16 | int nCur = 0; | ||
| 17 | |||
| 18 | if( strSource != NULL ) | ||
| 19 | { | ||
| 20 | loadContent( strSource ); | ||
| 21 | } | ||
| 22 | |||
| 23 | if( ( getenv( "CONTENT_LENGTH" ) ) ) | ||
| 24 | { | ||
| 25 | if( !strcmp | ||
| 26 | ( getenv( "CONTENT_TYPE" ), | ||
| 27 | "application/x-www-form-urlencoded" ) ) | ||
| 28 | { | ||
| 29 | length = atoi( getenv( "CONTENT_LENGTH" ) ); | ||
| 30 | buf = new char[length + 1]; | ||
| 31 | fread( buf, 1, length, stdin ); | ||
| 32 | cur = new Item( ); | ||
| 33 | aVars.append( cur ); | ||
| 34 | cur->type = VAR_STDINPUT; | ||
| 35 | for( j = 0; j < length; j++ ) | ||
| 36 | { | ||
| 37 | switch ( buf[j] ) | ||
| 38 | { | ||
| 39 | case '=': | ||
| 40 | cur->name = new char[slen + 1]; | ||
| 41 | slen = 0; | ||
| 42 | break; | ||
| 43 | |||
| 44 | case '&': | ||
| 45 | cur->value = new char[slen + 1]; | ||
| 46 | cur->len = slen; | ||
| 47 | slen = 0; | ||
| 48 | cur = new Item( ); | ||
| 49 | aVars.append( cur ); | ||
| 50 | cur->type = VAR_STDINPUT; | ||
| 51 | break; | ||
| 52 | |||
| 53 | default: | ||
| 54 | switch ( buf[j] ) | ||
| 55 | { | ||
| 56 | case '%': /* per-cents mean a hex-code for an ASCII char */ | ||
| 57 | j += 2; | ||
| 58 | slen++; | ||
| 59 | break; | ||
| 60 | |||
| 61 | default: /* Nothing special, move along, folks... */ | ||
| 62 | slen++; | ||
| 63 | break; | ||
| 64 | } | ||
| 65 | break; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | cur->value = new char[slen + 1]; | ||
| 69 | cur->len = slen; | ||
| 70 | slen = 0; | ||
| 71 | mode = 0; | ||
| 72 | cur = ( Item * ) aVars.getAt( 0 ); | ||
| 73 | k = 0; | ||
| 74 | nCur = 0; | ||
| 75 | for( j = 0; j < length; j++ ) | ||
| 76 | { | ||
| 77 | switch ( buf[j] ) | ||
| 78 | { | ||
| 79 | case '=': | ||
| 80 | mode = 1; | ||
| 81 | k = 0; | ||
| 82 | break; | ||
| 83 | |||
| 84 | case '&': | ||
| 85 | mode = 0; | ||
| 86 | k = 0; | ||
| 87 | nCur++; | ||
| 88 | cur = ( Item * ) aVars.getAt( nCur ); | ||
| 89 | break; | ||
| 90 | |||
| 91 | default: | ||
| 92 | switch ( buf[j] ) | ||
| 93 | { | ||
| 94 | case '%': /* per-cents mean a hex-code for an ASCII char */ | ||
| 95 | hexbuf[0] = buf[++j]; | ||
| 96 | hexbuf[1] = buf[++j]; | ||
| 97 | chr = ( char ) ( strtol( hexbuf, NULL, 16 ) ); | ||
| 98 | break; | ||
| 99 | |||
| 100 | case '+': /* Pluses mean spaces, odd, I know... */ | ||
| 101 | chr = ' '; | ||
| 102 | break; | ||
| 103 | |||
| 104 | default: /* Nothing special, move along, folks... */ | ||
| 105 | chr = buf[j]; | ||
| 106 | break; | ||
| 107 | } | ||
| 108 | if( mode == 0 ) | ||
| 109 | { | ||
| 110 | cur->name[k] = chr; | ||
| 111 | cur->name[++k] = '\0'; | ||
| 112 | } | ||
| 113 | else | ||
| 114 | { | ||
| 115 | cur->value[k] = chr; | ||
| 116 | cur->value[++k] = '\0'; | ||
| 117 | } | ||
| 118 | break; | ||
| 119 | } | ||
| 120 | } | ||
| 121 | delete buf; | ||
| 122 | } | ||
| 123 | else if( !strncmp | ||
| 124 | ( getenv( "CONTENT_TYPE" ), "multipart/form-data;", 20 ) ) | ||
| 125 | { | ||
| 126 | char *boundary, *oname; | ||
| 127 | int blen, j, k, olen; | ||
| 128 | |||
| 129 | length = atoi( getenv( "CONTENT_LENGTH" ) ); | ||
| 130 | buf = new char[length + 1]; | ||
| 131 | fread( buf, 1, length, stdin ); | ||
| 132 | for( blen = 0; buf[blen + 1] != '\n'; blen++ ); | ||
| 133 | boundary = new char[blen + 1]; | ||
| 134 | memcpy( boundary, buf, blen ); | ||
| 135 | boundary[blen] = '\0'; | ||
| 136 | j = blen + 2; | ||
| 137 | for( ;; ) | ||
| 138 | { | ||
| 139 | cur = new Item( ); | ||
| 140 | aVars.append( cur ); | ||
| 141 | cur->type = VAR_STDINPUT; | ||
| 142 | if( !strncmp | ||
| 143 | ( buf + j, "Content-Disposition: form-data; name=\"", | ||
| 144 | 38 ) ) | ||
| 145 | { | ||
| 146 | j += 38; | ||
| 147 | for( k = 0; buf[j + k] != '\"'; k++ ); | ||
| 148 | oname = cur->name = new char[k + 1]; | ||
| 149 | memcpy( cur->name, buf + j, k ); | ||
| 150 | olen = k; | ||
| 151 | cur->name[k] = '\0'; | ||
| 152 | j += k + 1; | ||
| 153 | if( !strncmp( buf + j, "; filename=\"", 12 ) ) /* Must be a file */ | ||
| 154 | { | ||
| 155 | /* Acquire file name */ | ||
| 156 | j += 12; | ||
| 157 | for( k = 0; buf[j + k] != '\"'; k++ ); | ||
| 158 | cur->value = new char[k + 1]; | ||
| 159 | memcpy( cur->value, buf + j, k ); | ||
| 160 | cur->value[k] = '\0'; | ||
| 161 | cur->len = k; | ||
| 162 | j += k + 3; | ||
| 163 | |||
| 164 | /* Acquire content type */ | ||
| 165 | if( !strncmp( "Content-Type: ", buf + j, 14 ) ) | ||
| 166 | { | ||
| 167 | j += 14; | ||
| 168 | cur = new Item( ); | ||
| 169 | aVars.append( cur ); | ||
| 170 | cur->type = VAR_STDINPUT; | ||
| 171 | cur->name = new char[olen + 1]; | ||
| 172 | memcpy( cur->name, oname, olen + 1 ); | ||
| 173 | for( k = 0; buf[j + k + 1] != '\n'; k++ ); | ||
| 174 | cur->value = new char[k + 1]; | ||
| 175 | memcpy( cur->value, buf + j, k ); | ||
| 176 | cur->value[k] = '\0'; | ||
| 177 | cur->len = k; | ||
| 178 | j += k; | ||
| 179 | } | ||
| 180 | else | ||
| 181 | { | ||
| 182 | cur = new Item( ); | ||
| 183 | aVars.append( cur ); | ||
| 184 | cur->type = VAR_STDINPUT; | ||
| 185 | cur->name = new char[olen + 1]; | ||
| 186 | memcpy( cur->name, oname, olen + 1 ); | ||
| 187 | cur->value = new char[1]; | ||
| 188 | cur->value[0] = '\0'; | ||
| 189 | cur->len = 0; | ||
| 190 | } | ||
| 191 | j += 4; | ||
| 192 | |||
| 193 | /* Acquire content */ | ||
| 194 | cur = new Item( ); | ||
| 195 | aVars.append( cur ); | ||
| 196 | cur->type = VAR_STDINPUT; | ||
| 197 | cur->name = new char[olen + 1]; | ||
| 198 | memcpy( cur->name, oname, olen + 1 ); | ||
| 199 | if( !strncmp( buf + j + k, boundary, blen ) ) | ||
| 200 | { | ||
| 201 | cur->value = new char[1]; | ||
| 202 | cur->value[0] = '\0'; | ||
| 203 | j += blen + 4; | ||
| 204 | } | ||
| 205 | else if( !strncmp( buf + j + k + 1, boundary, blen ) ) | ||
| 206 | { | ||
| 207 | cur->value = new char[1]; | ||
| 208 | cur->value[0] = '\0'; | ||
| 209 | j += blen + 5; | ||
| 210 | } | ||
| 211 | else | ||
| 212 | { | ||
| 213 | for( k = 0; | ||
| 214 | strncmp( buf + j + k + 2, boundary, blen ); | ||
| 215 | k++ ); | ||
| 216 | cur->value = new char[k + 1]; | ||
| 217 | memcpy( cur->value, buf + j, k ); | ||
| 218 | cur->value[k] = '\0'; | ||
| 219 | cur->len = k; | ||
| 220 | j += k + blen + 4; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | else | ||
| 224 | { | ||
| 225 | j += 4; | ||
| 226 | for( k = 0; | ||
| 227 | strncmp( buf + j + k + 2, boundary, blen ); | ||
| 228 | k++ ); | ||
| 229 | cur->value = new char[k + 1]; | ||
| 230 | memcpy( cur->value, buf + j, k ); | ||
| 231 | cur->value[k] = '\0'; | ||
| 232 | cur->len = k; | ||
| 233 | j += k + blen + 4; | ||
| 234 | } | ||
| 235 | if( buf[j + 1] == '\n' ) | ||
| 236 | j += 2; | ||
| 237 | if( j >= length ) | ||
| 238 | break; | ||
| 239 | } | ||
| 240 | else | ||
| 241 | { | ||
| 242 | cur->name = ( char * ) "ERROR"; | ||
| 243 | cur->value = ( char * ) "Error here"; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | } | ||
| 247 | delete buf; | ||
| 248 | } | ||
| 249 | |||
| 250 | if( ( buf = getenv( "HTTP_COOKIE" ) ) ) | ||
| 251 | { | ||
| 252 | int lbase = aVars.getSize( ); | ||
| 253 | length = strlen( buf ); | ||
| 254 | cur = new Item( ); | ||
| 255 | aVars.append( cur ); | ||
| 256 | cur->type = VAR_COOKIE; | ||
| 257 | for( j = 0; j < length; j++ ) | ||
| 258 | { | ||
| 259 | switch ( buf[j] ) | ||
| 260 | { | ||
| 261 | case '=': | ||
| 262 | cur->name = new char[slen + 1]; | ||
| 263 | slen = 0; | ||
| 264 | break; | ||
| 265 | |||
| 266 | case ';': | ||
| 267 | cur->value = new char[slen + 1]; | ||
| 268 | cur->len = slen; | ||
| 269 | slen = 0; | ||
| 270 | cur = new Item( ); | ||
| 271 | aVars.append( cur ); | ||
| 272 | cur->type = VAR_COOKIE; | ||
| 273 | break; | ||
| 274 | |||
| 275 | default: | ||
| 276 | switch ( buf[j] ) | ||
| 277 | { | ||
| 278 | case '%': /* per-cents mean a hex-code for an ASCII char */ | ||
| 279 | j += 2; | ||
| 280 | slen++; | ||
| 281 | break; | ||
| 282 | |||
| 283 | default: /* Nothing special, move along, folks... */ | ||
| 284 | slen++; | ||
| 285 | break; | ||
| 286 | } | ||
| 287 | break; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | cur->value = new char[slen + 1]; | ||
| 291 | cur->len = slen; | ||
| 292 | slen = 0; | ||
| 293 | cur = ( Item * ) aVars.getAt( lbase ); | ||
| 294 | mode = 0; | ||
| 295 | k = 0; | ||
| 296 | nCur = lbase; | ||
| 297 | for( j = 0; j < length; j++ ) | ||
| 298 | { | ||
| 299 | switch ( buf[j] ) | ||
| 300 | { | ||
| 301 | case '=': | ||
| 302 | mode = 1; | ||
| 303 | k = 0; | ||
| 304 | break; | ||
| 305 | |||
| 306 | case ';': | ||
| 307 | mode = 0; | ||
| 308 | k = 0; | ||
| 309 | nCur++; | ||
| 310 | cur = ( Item * ) aVars.getAt( nCur ); | ||
| 311 | break; | ||
| 312 | |||
| 313 | default: | ||
| 314 | switch ( buf[j] ) | ||
| 315 | { | ||
| 316 | case '%': /* per-cents mean a hex-code for an ASCII char */ | ||
| 317 | hexbuf[0] = buf[++j]; | ||
| 318 | hexbuf[1] = buf[++j]; | ||
| 319 | chr = ( char ) ( strtol( hexbuf, NULL, 16 ) ); | ||
| 320 | break; | ||
| 321 | |||
| 322 | case '+': /* Pluses mean spaces, odd, I know... */ | ||
| 323 | chr = ' '; | ||
| 324 | break; | ||
| 325 | |||
| 326 | case ' ': | ||
| 327 | continue; | ||
| 328 | break; | ||
| 329 | |||
| 330 | default: /* Nothing special, move along, folks... */ | ||
| 331 | chr = buf[j]; | ||
| 332 | break; | ||
| 333 | } | ||
| 334 | if( mode == 0 ) | ||
| 335 | { | ||
| 336 | cur->name[k] = chr; | ||
| 337 | cur->name[++k] = '\0'; | ||
| 338 | } | ||
| 339 | else | ||
| 340 | { | ||
| 341 | cur->value[k] = chr; | ||
| 342 | cur->value[++k] = '\0'; | ||
| 343 | } | ||
| 344 | break; | ||
| 345 | } | ||
| 346 | } | ||
| 347 | } | ||
| 348 | |||
| 349 | if( ( buf = getenv( "QUERY_STRING" ) ) ) | ||
| 350 | { | ||
| 351 | if( strlen( buf ) > 0 ) | ||
| 352 | { | ||
| 353 | int lbase = aVars.getSize( ); | ||
| 354 | length = strlen( buf ); | ||
| 355 | cur = new Item( ); | ||
| 356 | aVars.append( cur ); | ||
| 357 | cur->type = VAR_CMDLINE; | ||
| 358 | for( j = 0; j < length; j++ ) | ||
| 359 | { | ||
| 360 | switch ( buf[j] ) | ||
| 361 | { | ||
| 362 | case '=': | ||
| 363 | cur->name = new char[slen + 1]; | ||
| 364 | slen = 0; | ||
| 365 | break; | ||
| 366 | |||
| 367 | case '&': | ||
| 368 | cur->value = new char[slen + 1]; | ||
| 369 | cur->len = slen; | ||
| 370 | slen = 0; | ||
| 371 | cur = new Item( ); | ||
| 372 | aVars.append( cur ); | ||
| 373 | cur->type = VAR_CMDLINE; | ||
| 374 | break; | ||
| 375 | |||
| 376 | default: | ||
| 377 | switch ( buf[j] ) | ||
| 378 | { | ||
| 379 | case '%': /* per-cents mean a hex-code for an ASCII char */ | ||
| 380 | j += 2; | ||
| 381 | slen++; | ||
| 382 | break; | ||
| 383 | |||
| 384 | default: /* Nothing special, move along, folks... */ | ||
| 385 | slen++; | ||
| 386 | break; | ||
| 387 | } | ||
| 388 | break; | ||
| 389 | } | ||
| 390 | } | ||
| 391 | cur->value = new char[slen + 1]; | ||
| 392 | cur->len = slen; | ||
| 393 | slen = 0; | ||
| 394 | cur = ( Item * ) aVars.getAt( lbase ); | ||
| 395 | nCur = lbase; | ||
| 396 | mode = 0; | ||
| 397 | k = 0; | ||
| 398 | for( j = 0; j < length; j++ ) | ||
| 399 | { | ||
| 400 | switch ( buf[j] ) | ||
| 401 | { | ||
| 402 | case '=': | ||
| 403 | mode = 1; | ||
| 404 | k = 0; | ||
| 405 | break; | ||
| 406 | |||
| 407 | case '&': | ||
| 408 | mode = 0; | ||
| 409 | k = 0; | ||
| 410 | nCur++; | ||
| 411 | cur = ( Item * ) aVars.getAt( nCur ); | ||
| 412 | break; | ||
| 413 | |||
| 414 | default: | ||
| 415 | switch ( buf[j] ) | ||
| 416 | { | ||
| 417 | case '%': /* per-cents mean a hex-code for an ASCII char */ | ||
| 418 | hexbuf[0] = buf[++j]; | ||
| 419 | hexbuf[1] = buf[++j]; | ||
| 420 | chr = ( char ) ( strtol( hexbuf, NULL, 16 ) ); | ||
| 421 | break; | ||
| 422 | |||
| 423 | case '+': /* Pluses mean spaces, odd, I know... */ | ||
| 424 | chr = ' '; | ||
| 425 | break; | ||
| 426 | |||
| 427 | default: /* Nothing special, move along, folks... */ | ||
| 428 | chr = buf[j]; | ||
| 429 | break; | ||
| 430 | } | ||
| 431 | if( mode == 0 ) | ||
| 432 | { | ||
| 433 | cur->name[k] = chr; | ||
| 434 | cur->name[++k] = '\0'; | ||
| 435 | } | ||
| 436 | else | ||
| 437 | { | ||
| 438 | cur->value[k] = chr; | ||
| 439 | cur->value[++k] = '\0'; | ||
| 440 | } | ||
| 441 | break; | ||
| 442 | } | ||
| 443 | } | ||
| 444 | } | ||
| 445 | } | ||
| 446 | } | ||
| 447 | |||
| 448 | Cgi::~Cgi( ) | ||
| 449 | { | ||
| 450 | } | ||
| 451 | |||
| 452 | char *Cgi::getVarValue( const char *name, int skip, unsigned char type ) | ||
| 453 | { | ||
| 454 | for( int j = 0; j < aVars.getSize( ); j++ ) | ||
| 455 | { | ||
| 456 | Item *cur = ( Item * ) aVars.getAt( j ); | ||
| 457 | if( !strcmp( cur->name, name ) ) | ||
| 458 | { | ||
| 459 | if( ( cur->type & type ) ) | ||
| 460 | { | ||
| 461 | if( skip <= 0 ) | ||
| 462 | { | ||
| 463 | return cur->value; | ||
| 464 | } | ||
| 465 | else | ||
| 466 | { | ||
| 467 | skip--; | ||
| 468 | } | ||
| 469 | } | ||
| 470 | } | ||
| 471 | } | ||
| 472 | return NULL; | ||
| 473 | } | ||
| 474 | |||
| 475 | int Cgi::getVarLength( const char *name, int skip, unsigned char type ) | ||
| 476 | { | ||
| 477 | for( int j = 0; j < aVars.getSize( ); j++ ) | ||
| 478 | { | ||
| 479 | Item *cur = ( Item * ) aVars.getAt( j ); | ||
| 480 | if( !strcmp( cur->name, name ) ) | ||
| 481 | { | ||
| 482 | if( ( cur->type & type ) ) | ||
| 483 | { | ||
| 484 | if( skip <= 0 ) | ||
| 485 | { | ||
| 486 | return cur->len; | ||
| 487 | } | ||
| 488 | else | ||
| 489 | { | ||
| 490 | skip--; | ||
| 491 | } | ||
| 492 | } | ||
| 493 | } | ||
| 494 | } | ||
| 495 | return -1; | ||
| 496 | } | ||
| 497 | |||
| 498 | void Cgi::writeDebugInfo() | ||
| 499 | { | ||
| 500 | printf( "<pre>\n" ); | ||
| 501 | printf( "0x%02X - stdInput | 0x%02X - cookie | 0x%02X - cmdLine\n\n", | ||
| 502 | VAR_STDINPUT, VAR_COOKIE, VAR_CMDLINE ); | ||
| 503 | for( int j = 0; j < aVars.getSize( ); j++ ) | ||
| 504 | { | ||
| 505 | Item *item = ( Item * ) aVars.getAt( j ); | ||
| 506 | printf("[%s] = \"%s\" [0x%02X]\n", item->name, | ||
| 507 | item->value, item->type ); | ||
| 508 | } | ||
| 509 | printf( "</pre>\n" ); | ||
| 510 | } | ||
| 511 | |||
| 512 | void Cgi::writeContentHeader( int type ) | ||
| 513 | { | ||
| 514 | switch( type ) | ||
| 515 | { | ||
| 516 | case headerHTML: | ||
| 517 | printf("Content-type: text/html\n\n"); | ||
| 518 | break; | ||
| 519 | } | ||
| 520 | } | ||
| 521 | |||
| 522 | void Cgi::writeContent( const char *name, ...) | ||
| 523 | { | ||
| 524 | char *templ = (char *)aContent.get(name); | ||
| 525 | |||
| 526 | if( templ ) | ||
| 527 | { | ||
| 528 | va_list ap; | ||
| 529 | |||
| 530 | va_start (ap, name); | ||
| 531 | vprintf (templ, ap); | ||
| 532 | va_end (ap); | ||
| 533 | } | ||
| 534 | else | ||
| 535 | { | ||
| 536 | printf("Error finding content labeled \"%s\"\n", name ); | ||
| 537 | } | ||
| 538 | } | ||
| 539 | |||
| 540 | void Cgi::loadContent( const char *strSource ) | ||
| 541 | { | ||
| 542 | FILE *fh = NULL; | ||
| 543 | if( strSource == NULL ) | ||
| 544 | { | ||
| 545 | extern char *program_invocation_short_name; | ||
| 546 | char *tmpName = new char[strlen(program_invocation_short_name)+10]; | ||
| 547 | memset( tmpName, 0, strlen(program_invocation_short_name)+10 ); | ||
| 548 | strcpy( tmpName, program_invocation_short_name ); | ||
| 549 | strcat( tmpName, ".content" ); | ||
| 550 | fh = fopen( tmpName, "rt" ); | ||
| 551 | delete tmpName; | ||
| 552 | } | ||
| 553 | else | ||
| 554 | { | ||
| 555 | fh = fopen( strSource, "rt" ); | ||
| 556 | } | ||
| 557 | |||
| 558 | if( fh == NULL ) return; | ||
| 559 | |||
| 560 | struct stat xStats; | ||
| 561 | |||
| 562 | fstat( fileno( fh ), &xStats ); | ||
| 563 | |||
| 564 | char *bigBuf = new char[xStats.st_size+1]; | ||
| 565 | memset( bigBuf, 0, xStats.st_size+1 ); | ||
| 566 | fread( bigBuf, 1, xStats.st_size, fh ); | ||
| 567 | fclose( fh ); | ||
| 568 | |||
| 569 | // Now we can actually load stuff from the file, first we need to make us up a format... | ||
| 570 | int lSize=0; | ||
| 571 | struct Content | ||
| 572 | { | ||
| 573 | char *name; | ||
| 574 | char *value; | ||
| 575 | } xCont; | ||
| 576 | int j = 0; | ||
| 577 | while( j < xStats.st_size ) | ||
| 578 | { | ||
| 579 | // We're looking for a content-block init statement | ||
| 580 | for( ; j < xStats.st_size; j++ ) | ||
| 581 | { | ||
| 582 | if( bigBuf[j] == '#' ) | ||
| 583 | { | ||
| 584 | if( bigBuf[j+1] == '{' ) | ||
| 585 | { | ||
| 586 | break; | ||
| 587 | } | ||
| 588 | } | ||
| 589 | } | ||
| 590 | j=j+2; | ||
| 591 | if( j >= xStats.st_size ) break; | ||
| 592 | for( ; bigBuf[j] == ' ' || bigBuf[j] == '\t'; j++ ); | ||
| 593 | for( lSize = 0; lSize+j < xStats.st_size && bigBuf[lSize+j] != '\n' && bigBuf[lSize+j] != '\r'; lSize++ ); | ||
| 594 | xCont.name = new char[lSize+1]; | ||
| 595 | memset( xCont.name, 0, lSize+1 ); | ||
| 596 | memcpy( xCont.name, &bigBuf[j], lSize ); | ||
| 597 | j += lSize+1; | ||
| 598 | |||
| 599 | for( lSize = 0; lSize+j < xStats.st_size; lSize++ ) | ||
| 600 | { | ||
| 601 | if( bigBuf[lSize+j] == '#' ) | ||
| 602 | { | ||
| 603 | if( bigBuf[lSize+j+1] == '}' ) | ||
| 604 | { | ||
| 605 | break; | ||
| 606 | } | ||
| 607 | } | ||
| 608 | } | ||
| 609 | xCont.value = new char[lSize+1]; | ||
| 610 | memset( xCont.value, 0, lSize+1 ); | ||
| 611 | memcpy( xCont.value, &bigBuf[j], lSize ); | ||
| 612 | |||
| 613 | aContent.insert( xCont.name, xCont.value ); | ||
| 614 | |||
| 615 | j += lSize + 2; | ||
| 616 | } | ||
| 617 | } | ||
| 618 | |||
| 619 | void Cgi::writeCookie( char const *name, char const *value, char const *expires, char const *path, char const *domain, bool secure ) | ||
| 620 | { | ||
| 621 | printf("Set-Cookie: %s=%s", name, value ); | ||
| 622 | |||
| 623 | if( expires != NULL ) | ||
| 624 | { | ||
| 625 | printf("; expires=%s", expires ); | ||
| 626 | } | ||
| 627 | |||
| 628 | if( path != NULL ) | ||
| 629 | { | ||
| 630 | printf("; path=%s", path ); | ||
| 631 | } | ||
| 632 | |||
| 633 | if( domain != NULL ) | ||
| 634 | { | ||
| 635 | printf("; domain=%s", domain ); | ||
| 636 | } | ||
| 637 | |||
| 638 | if( secure ) | ||
| 639 | { | ||
| 640 | printf("; secure"); | ||
| 641 | } | ||
| 642 | |||
| 643 | printf("\n"); | ||
| 644 | } | ||
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 @@ | |||
| 1 | /**\file cgi.h | ||
| 2 | * Describes extra params needed to use the Cgi class as well as the class | ||
| 3 | * itself. | ||
| 4 | *@author Mike Buland | ||
| 5 | */ | ||
| 6 | |||
| 7 | #include "linkedlist.h" | ||
| 8 | #include "hashtable.h" | ||
| 9 | #include "hashfunctionstring.h" | ||
| 10 | |||
| 11 | #define VAR_STDINPUT 0x01 /**< Variable came from stdinput, web form */ | ||
| 12 | #define VAR_COOKIE 0x02 /**< Variable came from a cookie */ | ||
| 13 | #define VAR_CMDLINE 0x04 /**< Variable came from commandline / uri */ | ||
| 14 | #define VAR_ANY 0xFF /**< Mask including all other types */ | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Cgi header processor originally designed for apache cgi programs. When used | ||
| 18 | * from apache with what I beleive are some sort of standard set of command | ||
| 19 | * line parameters and environment variables. This always worked for all of my | ||
| 20 | * purposes. This class will automatically extract all data from the system | ||
| 21 | * that you need and places it into tables and things for easy access. | ||
| 22 | * There are three types of input that data can come from, StandardInput, | ||
| 23 | * CommandLine, and Cookies. StandardInput is when you get formdata in | ||
| 24 | * multi-part forms, Cookies should usually be cookies that you set, and | ||
| 25 | * command line is everything after the question mark in the URL. | ||
| 26 | * This also contains some simple helpers for putting templated data into the | ||
| 27 | * HTTP data feed. | ||
| 28 | *@author Mike Buland | ||
| 29 | */ | ||
| 30 | class Cgi | ||
| 31 | { | ||
| 32 | public: | ||
| 33 | /** | ||
| 34 | * Create a complete CGI object, this object will automatically read data | ||
| 35 | * from all available sources and be ready for use on the very next line! | ||
| 36 | * If strSource is filled in it will also automatically read in a content | ||
| 37 | * file, which is a simple file format containing named blocks of reusable | ||
| 38 | * templates. | ||
| 39 | *@param strSource Set to a filename in order to load up a content file. | ||
| 40 | */ | ||
| 41 | Cgi( const char *strSource = NULL ); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Destroy the cgi object. | ||
| 45 | */ | ||
| 46 | ~Cgi( ); | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Get's the value for a variable as a character string. The name is the | ||
| 50 | * name that was given on the URL or in the form or cookie. Skip can be | ||
| 51 | * set to any value above zero to retreive subsequent variables with the | ||
| 52 | * same name. The most obvious use of this is when dealing with file | ||
| 53 | * uploads, each file upload sends you three variables with the same name | ||
| 54 | * and different content. Finally the variable type determines where you | ||
| 55 | * will accept this variable from. This is generally a bit of a security | ||
| 56 | * thing, if you store login info in a cookie and don't want people getting | ||
| 57 | * in by faking the appropriate URL. | ||
| 58 | *@param name The name of the variable you wish to retreive. | ||
| 59 | *@param skip THe number of variables with the given name to skip before | ||
| 60 | * returning something meaningful. The only way to determine how many | ||
| 61 | * variables with the same name there are is to skip until you get a NULL | ||
| 62 | * value returned. | ||
| 63 | *@param type Can be set to any combination of VAR_STDINPUT, VAR_COOKIE, | ||
| 64 | * VAR_CMDLINE, or just VAR_ANY. This takes bitflags, so you can or the | ||
| 65 | * values together. If a variable is found but came from the wrong source | ||
| 66 | * it won't match any other criteria and will be treated as though it | ||
| 67 | * doesn't exist. | ||
| 68 | *@returns A null-terminated string representing the value of the requested | ||
| 69 | * variable, or NULL if the variable did not exist. If a variable does | ||
| 70 | * exist but has no value the string returned will start with a NULL char, | ||
| 71 | * but be a valid string. | ||
| 72 | */ | ||
| 73 | char *getVarValue( const char *name, int skip=0, unsigned char type=VAR_ANY ); | ||
| 74 | |||
| 75 | /** | ||
| 76 | * This functions identically in every way to getVarValue, except that | ||
| 77 | * instead of returning a pointer to the variable's value, it returns the | ||
| 78 | * length of the variable's value string. The params are the same and so | ||
| 79 | * a call to both functions with the same params should yeild a value and | ||
| 80 | * a corresponding length. | ||
| 81 | *@param name The name of the variable you wish to retreive. | ||
| 82 | *@param skip THe number of variables with the given name to skip before | ||
| 83 | * returning something meaningful. The only way to determine how many | ||
| 84 | * variables with the same name there are is to skip until you get a NULL | ||
| 85 | * value returned. | ||
| 86 | *@param type Can be set to any combination of VAR_STDINPUT, VAR_COOKIE, | ||
| 87 | * VAR_CMDLINE, or just VAR_ANY. This takes bitflags, so you can or the | ||
| 88 | * values together. If a variable is found but came from the wrong source | ||
| 89 | * it won't match any other criteria and will be treated as though it | ||
| 90 | * doesn't exist. | ||
| 91 | *@returns The length of the value-string of the requested variable. If | ||
| 92 | * the requested variable is not found, -1 is returned. | ||
| 93 | */ | ||
| 94 | int getVarLength( const char *name, int skip=0, unsigned char type=VAR_ANY ); | ||
| 95 | |||
| 96 | /** | ||
| 97 | * A handy little function that writes a load of debug info related to | ||
| 98 | * parsing CGI params to the standard output in html. This is generally | ||
| 99 | * best used at the end of a page. | ||
| 100 | */ | ||
| 101 | void writeDebugInfo(); | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Write a content header to the standard output. This should also be the | ||
| 105 | * first thing that you do (except for writing cookies) after initializing | ||
| 106 | * the Cgi class. You can select a type of header or content from the | ||
| 107 | * header enum, and a properly formatted header will show up on the | ||
| 108 | * standard output. | ||
| 109 | *@param type Any value from the header enum in this class. The default is | ||
| 110 | * to write an html header, probably the most common as well. | ||
| 111 | */ | ||
| 112 | void writeContentHeader( int type=headerHTML ); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Write content to the stnadard output. The content variable should have | ||
| 116 | * been loaded during construction of the Cgi object or with the | ||
| 117 | * loadContent function. The content variable should be formatted just like | ||
| 118 | * a printf string, so that anything you want to put into it will have a % | ||
| 119 | * symbol replacement code, like %s, %d, etc. Since this actually uses a | ||
| 120 | * type of printf function everything from those docs work here. | ||
| 121 | *@param name The name of the content variable to format and write to | ||
| 122 | * stnadard output. | ||
| 123 | *@param ... As many params as you want to include, ala printf. | ||
| 124 | */ | ||
| 125 | void writeContent( const char *name, ...); | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Load a content file. I don't want to describe the format here, you can | ||
| 129 | * just read the code or find an example for now. Sorry. | ||
| 130 | *@param strSource The name of the file to open and read in to get the | ||
| 131 | * content loaded. | ||
| 132 | */ | ||
| 133 | void loadContent( const char *strSource = NULL ); | ||
| 134 | |||
| 135 | /** | ||
| 136 | * Write a cookie-set header to the output stream. This should be done | ||
| 137 | * before any other content-headers are written. The specifics of this | ||
| 138 | * function are very simple, since I rely on the user's understanding of | ||
| 139 | * how standard HTTP/1.1 or HTTP/1.0 cookie syntax works. If you don't | ||
| 140 | * care then just use the name and value and the defaults should keep you | ||
| 141 | * in good stead for a long time. | ||
| 142 | *@param name The name of the cookie variable to set. | ||
| 143 | *@param value The value to set to that variable. | ||
| 144 | *@param expires The formatted string value for the date and time this | ||
| 145 | * cookie should expire. A NULL here will put a "until the browser closes" | ||
| 146 | * tag in. | ||
| 147 | *@param path The path (URL) that this cookie belongs to. If you run a lot | ||
| 148 | * of hosted servers or sub-sites that may have some shared URL bits then | ||
| 149 | * you may want to set this. The cookie should only be sent to URL's that | ||
| 150 | * match this as their first part. | ||
| 151 | *@param domain The domain that is allowed to read this, if not set, it's | ||
| 152 | * the domain the web browser contacted when they got the cookie. | ||
| 153 | *@param secure I'm not sure, I think it's something to tell if the cookie | ||
| 154 | * is safe to keep because any potentially valuable data is encypted or | ||
| 155 | * otherwise unusable. I could be wrong. | ||
| 156 | */ | ||
| 157 | void writeCookie( char const *name, char const *value, char const *expires=NULL, char const *path=NULL, char const *domain=NULL, bool secure=false ); | ||
| 158 | |||
| 159 | /** | ||
| 160 | * A simple helper class to contain variable data. | ||
| 161 | */ | ||
| 162 | class Item | ||
| 163 | { | ||
| 164 | public: | ||
| 165 | /** | ||
| 166 | * Build an empty Item. | ||
| 167 | */ | ||
| 168 | Item( ) | ||
| 169 | { | ||
| 170 | name = NULL; | ||
| 171 | value = NULL; | ||
| 172 | len = 0; | ||
| 173 | type = 0; | ||
| 174 | } | ||
| 175 | /** The name of the item. */ | ||
| 176 | char *name; | ||
| 177 | /** The value of the item. */ | ||
| 178 | char *value; | ||
| 179 | /** The length of the item's value. */ | ||
| 180 | unsigned long len; | ||
| 181 | /** The type of the item (where it came from). */ | ||
| 182 | unsigned char type; | ||
| 183 | }; | ||
| 184 | |||
| 185 | /** Header values */ | ||
| 186 | enum | ||
| 187 | { | ||
| 188 | headerHTML | ||
| 189 | }; | ||
| 190 | |||
| 191 | private: | ||
| 192 | /** Keeps track of all contained variables. */ | ||
| 193 | LinkedList aVars; | ||
| 194 | /** Keeps track of all content variables. */ | ||
| 195 | HashTable aContent; | ||
| 196 | }; | ||
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 @@ | |||
| 1 | #include "connection.h" | ||
| 2 | #include <string.h> | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <errno.h> | ||
| 5 | #include <stdlib.h> | ||
| 6 | #include <unistd.h> | ||
| 7 | #include <sys/types.h> | ||
| 8 | #include <sys/socket.h> | ||
| 9 | #include <netinet/in.h> | ||
| 10 | #include <netdb.h> | ||
| 11 | #include <arpa/inet.h> | ||
| 12 | |||
| 13 | Connection::Connection() | ||
| 14 | { | ||
| 15 | nSocket = -1; | ||
| 16 | bActive = false; | ||
| 17 | bDisconnectMe = false; | ||
| 18 | pProtocol = NULL; | ||
| 19 | } | ||
| 20 | |||
| 21 | Connection::~Connection() | ||
| 22 | { | ||
| 23 | if( pProtocol != NULL ) delete pProtocol; | ||
| 24 | } | ||
| 25 | |||
| 26 | bool Connection::appendOutput( const char *lpOutput, int nSize ) | ||
| 27 | { | ||
| 28 | return xOutputBuf.appendData( lpOutput, nSize ); | ||
| 29 | } | ||
| 30 | |||
| 31 | bool Connection::appendOutput( const char lOutput ) | ||
| 32 | { | ||
| 33 | return xOutputBuf.appendData( lOutput ); | ||
| 34 | } | ||
| 35 | |||
| 36 | bool Connection::appendOutput( const short lOutput ) | ||
| 37 | { | ||
| 38 | return xOutputBuf.appendData( lOutput ); | ||
| 39 | } | ||
| 40 | |||
| 41 | bool Connection::appendOutput( const int lOutput ) | ||
| 42 | { | ||
| 43 | return xOutputBuf.appendData( lOutput ); | ||
| 44 | } | ||
| 45 | |||
| 46 | bool Connection::appendOutput( const long lOutput ) | ||
| 47 | { | ||
| 48 | return xOutputBuf.appendData( lOutput ); | ||
| 49 | } | ||
| 50 | |||
| 51 | bool Connection::appendOutput( const float lOutput ) | ||
| 52 | { | ||
| 53 | return xOutputBuf.appendData( lOutput ); | ||
| 54 | } | ||
| 55 | |||
| 56 | bool Connection::appendOutput( const double lOutput ) | ||
| 57 | { | ||
| 58 | return xOutputBuf.appendData( lOutput ); | ||
| 59 | } | ||
| 60 | |||
| 61 | bool Connection::appendOutput( const unsigned char lOutput ) | ||
| 62 | { | ||
| 63 | return xOutputBuf.appendData( lOutput ); | ||
| 64 | } | ||
| 65 | |||
| 66 | bool Connection::appendOutput( const unsigned short lOutput ) | ||
| 67 | { | ||
| 68 | return xOutputBuf.appendData( lOutput ); | ||
| 69 | } | ||
| 70 | |||
| 71 | bool Connection::appendOutput( const unsigned long lOutput ) | ||
| 72 | { | ||
| 73 | return xOutputBuf.appendData( lOutput ); | ||
| 74 | } | ||
| 75 | |||
| 76 | bool Connection::appendOutput( const unsigned int lOutput ) | ||
| 77 | { | ||
| 78 | return xOutputBuf.appendData( lOutput ); | ||
| 79 | } | ||
| 80 | |||
| 81 | bool Connection::appendInput( const char *lpInput, int nSize ) | ||
| 82 | { | ||
| 83 | return xInputBuf.appendData( lpInput, nSize ); | ||
| 84 | } | ||
| 85 | |||
| 86 | int Connection::scanInputFor( char cTarget ) | ||
| 87 | { | ||
| 88 | const char *lpTmp = xInputBuf.getData(); | ||
| 89 | int jMax = xInputBuf.getLength(); | ||
| 90 | |||
| 91 | for( int j = 0; j < jMax; j++ ) | ||
| 92 | { | ||
| 93 | if( lpTmp[j] == cTarget ) | ||
| 94 | { | ||
| 95 | return j; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | return -1; | ||
| 100 | } | ||
| 101 | |||
| 102 | const char *Connection::getOutput() | ||
| 103 | { | ||
| 104 | return xOutputBuf.getData(); | ||
| 105 | } | ||
| 106 | |||
| 107 | const char *Connection::getInput() | ||
| 108 | { | ||
| 109 | return xInputBuf.getData(); | ||
| 110 | } | ||
| 111 | |||
| 112 | void Connection::setSocket( int nNewSocket ) | ||
| 113 | { | ||
| 114 | nSocket = nNewSocket; | ||
| 115 | } | ||
| 116 | |||
| 117 | int Connection::getSocket() | ||
| 118 | { | ||
| 119 | return nSocket; | ||
| 120 | } | ||
| 121 | |||
| 122 | bool Connection::isActive() | ||
| 123 | { | ||
| 124 | return bActive; | ||
| 125 | } | ||
| 126 | |||
| 127 | void Connection::close() | ||
| 128 | { | ||
| 129 | if( bActive ) | ||
| 130 | { | ||
| 131 | fsync( nSocket ); | ||
| 132 | ::close( nSocket ); | ||
| 133 | } | ||
| 134 | bActive = false; | ||
| 135 | //nSocket = -1; | ||
| 136 | xInputBuf.clearData(); | ||
| 137 | xOutputBuf.clearData(); | ||
| 138 | if( pProtocol != NULL ) | ||
| 139 | { | ||
| 140 | delete pProtocol; | ||
| 141 | pProtocol = NULL; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | bool Connection::open( int nNewSocket ) | ||
| 146 | { | ||
| 147 | bActive = true; | ||
| 148 | setSocket( nNewSocket ); | ||
| 149 | bDisconnectMe = false; | ||
| 150 | |||
| 151 | return true; | ||
| 152 | } | ||
| 153 | |||
| 154 | bool Connection::open( const char *sAddr, int nPort ) | ||
| 155 | { | ||
| 156 | struct sockaddr_in xServerName; | ||
| 157 | bActive = false; | ||
| 158 | |||
| 159 | /* Create the socket. */ | ||
| 160 | nSocket = socket( PF_INET, SOCK_STREAM, 0 ); | ||
| 161 | if( nSocket < 0 ) | ||
| 162 | { | ||
| 163 | bActive = false; | ||
| 164 | return false; | ||
| 165 | } | ||
| 166 | |||
| 167 | /* Connect to the server. */ | ||
| 168 | { | ||
| 169 | struct hostent *hostinfo; | ||
| 170 | |||
| 171 | xServerName.sin_family = AF_INET; | ||
| 172 | xServerName.sin_port = htons( nPort ); | ||
| 173 | hostinfo = gethostbyname( sAddr ); | ||
| 174 | if (hostinfo == NULL) | ||
| 175 | { | ||
| 176 | return false; | ||
| 177 | } | ||
| 178 | xServerName.sin_addr = *(struct in_addr *) hostinfo->h_addr; | ||
| 179 | } | ||
| 180 | |||
| 181 | int ret = connect( | ||
| 182 | nSocket, | ||
| 183 | (struct sockaddr *)&xServerName, | ||
| 184 | sizeof(xServerName) | ||
| 185 | ); | ||
| 186 | |||
| 187 | if( ret < 0 ) | ||
| 188 | { | ||
| 189 | return false; | ||
| 190 | } | ||
| 191 | |||
| 192 | bActive = true; | ||
| 193 | bDisconnectMe = false; | ||
| 194 | |||
| 195 | return true; | ||
| 196 | } | ||
| 197 | |||
| 198 | bool Connection::readInput() | ||
| 199 | { | ||
| 200 | char buffer[2048]; | ||
| 201 | int nbytes; | ||
| 202 | int nTotalRead=0; | ||
| 203 | |||
| 204 | for(;;) | ||
| 205 | { | ||
| 206 | memset( buffer, 0, 2048 ); | ||
| 207 | |||
| 208 | nbytes = read( nSocket, buffer, 2048 ); | ||
| 209 | if (nbytes < 0) | ||
| 210 | { | ||
| 211 | /* Read error. */ | ||
| 212 | //perror("readInput"); | ||
| 213 | return false; | ||
| 214 | } | ||
| 215 | else if (nbytes == 0) | ||
| 216 | { | ||
| 217 | /* End-of-file. */ | ||
| 218 | //perror("readInput"); | ||
| 219 | return false; | ||
| 220 | } | ||
| 221 | else | ||
| 222 | { | ||
| 223 | nTotalRead += nbytes; | ||
| 224 | appendInput( buffer, nbytes ); | ||
| 225 | /* Data read. */ | ||
| 226 | if( nbytes < 2047 ) | ||
| 227 | { | ||
| 228 | if( pProtocol != NULL && nTotalRead > 0 ) | ||
| 229 | { | ||
| 230 | pProtocol->onNewData(); | ||
| 231 | } | ||
| 232 | |||
| 233 | return true; | ||
| 234 | } | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | return true; | ||
| 239 | } | ||
| 240 | |||
| 241 | bool Connection::readInput( int nSec, int nUSec ) | ||
| 242 | { | ||
| 243 | fd_set rfds; | ||
| 244 | struct timeval tv; | ||
| 245 | int retval; | ||
| 246 | |||
| 247 | /* Watch stdin (fd 0) to see when it has input. */ | ||
| 248 | FD_ZERO(&rfds); | ||
| 249 | FD_SET(nSocket, &rfds); | ||
| 250 | /* Wait up to five seconds. */ | ||
| 251 | tv.tv_sec = nSec; | ||
| 252 | tv.tv_usec = nUSec; | ||
| 253 | |||
| 254 | retval = select( nSocket+1, &rfds, NULL, NULL, &tv ); | ||
| 255 | /* Don't rely on the value of tv now! */ | ||
| 256 | |||
| 257 | if (retval == -1) | ||
| 258 | { | ||
| 259 | // Oh my god!!! some kind of horrible problem!!!! | ||
| 260 | return false; | ||
| 261 | } | ||
| 262 | else if( retval ) | ||
| 263 | { | ||
| 264 | // None of them have data, but the connection is still active. | ||
| 265 | return readInput(); | ||
| 266 | } | ||
| 267 | else | ||
| 268 | { | ||
| 269 | return true; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | bool Connection::clearOutput() | ||
| 274 | { | ||
| 275 | return xOutputBuf.clearData(); | ||
| 276 | } | ||
| 277 | |||
| 278 | bool Connection::clearInput() | ||
| 279 | { | ||
| 280 | return xInputBuf.clearData(); | ||
| 281 | } | ||
| 282 | |||
| 283 | #define min( a, b ) ((a<b)?(a):(b)) | ||
| 284 | |||
| 285 | bool Connection::writeOutput() | ||
| 286 | { | ||
| 287 | int nBytes = TEMP_FAILURE_RETRY( write( nSocket, xOutputBuf.getData(), min( 2048, xOutputBuf.getLength() ) ) ); | ||
| 288 | if( nBytes < 0 ) | ||
| 289 | { | ||
| 290 | perror("writeOutput"); | ||
| 291 | return true; | ||
| 292 | } | ||
| 293 | /* | ||
| 294 | if( nBytes < xOutputBuf.getLength() ) | ||
| 295 | { | ||
| 296 | printf("Havn't written all the data (%d/%d/%d%%)\n", nBytes, xOutputBuf.getLength(), nBytes/(xOutputBuf.getLength()*100) ); | ||
| 297 | } | ||
| 298 | else | ||
| 299 | { | ||
| 300 | printf("Wrote all pending data.\n"); | ||
| 301 | } | ||
| 302 | */ | ||
| 303 | xOutputBuf.usedData( nBytes ); | ||
| 304 | //clearOutput(); | ||
| 305 | |||
| 306 | return true; | ||
| 307 | } | ||
| 308 | |||
| 309 | bool Connection::hasOutput() | ||
| 310 | { | ||
| 311 | if( xOutputBuf.getLength() == 0 ) | ||
| 312 | { | ||
| 313 | return false; | ||
| 314 | } | ||
| 315 | else | ||
| 316 | { | ||
| 317 | return true; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | bool Connection::hasInput() | ||
| 322 | { | ||
| 323 | if( xInputBuf.getLength() == 0 ) | ||
| 324 | { | ||
| 325 | return false; | ||
| 326 | } | ||
| 327 | else | ||
| 328 | { | ||
| 329 | return true; | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | bool Connection::usedInput( int nAmount ) | ||
| 334 | { | ||
| 335 | return xInputBuf.usedData( nAmount ); | ||
| 336 | } | ||
| 337 | |||
| 338 | bool Connection::needDisconnect() | ||
| 339 | { | ||
| 340 | return bDisconnectMe; | ||
| 341 | } | ||
| 342 | |||
| 343 | void Connection::disconnect() | ||
| 344 | { | ||
| 345 | bDisconnectMe = true; | ||
| 346 | } | ||
| 347 | |||
| 348 | void Connection::setProtocol( class Protocol *pNewProtocol ) | ||
| 349 | { | ||
| 350 | pProtocol = pNewProtocol; | ||
| 351 | pProtocol->setConnection( this ); | ||
| 352 | } | ||
| 353 | |||
| 354 | int Connection::getInputAmnt() | ||
| 355 | { | ||
| 356 | return xInputBuf.getLength(); | ||
| 357 | } | ||
| 358 | |||
| 359 | int Connection::getOutputAmnt() | ||
| 360 | { | ||
| 361 | return xOutputBuf.getLength(); | ||
| 362 | } | ||
| 363 | |||
| 364 | class Protocol *Connection::getProtocol() | ||
| 365 | { | ||
| 366 | return pProtocol; | ||
| 367 | } | ||
| 368 | |||
| 369 | void Connection::printInputDebug( const char *lpPrefix, FILE *fh, int nBytesMax ) | ||
| 370 | { | ||
| 371 | printDataDebug( | ||
| 372 | (const unsigned char *)xInputBuf.getData(), | ||
| 373 | xInputBuf.getLength(), | ||
| 374 | "input", | ||
| 375 | lpPrefix, | ||
| 376 | fh, | ||
| 377 | nBytesMax | ||
| 378 | ); | ||
| 379 | } | ||
| 380 | |||
| 381 | void Connection::printOutputDebug( const char *lpPrefix, FILE *fh, int nBytesMax ) | ||
| 382 | { | ||
| 383 | printDataDebug( | ||
| 384 | (const unsigned char *)xOutputBuf.getData(), | ||
| 385 | xOutputBuf.getLength(), | ||
| 386 | "output", | ||
| 387 | lpPrefix, | ||
| 388 | fh, | ||
| 389 | nBytesMax | ||
| 390 | ); | ||
| 391 | } | ||
| 392 | |||
| 393 | void Connection::printDataDebug( const unsigned char *pData, long nDataLen, const char *lpName, const char *lpPrefix, FILE *fh, int nBytesMax ) | ||
| 394 | { | ||
| 395 | if( nBytesMax > 0 ) | ||
| 396 | { | ||
| 397 | nDataLen = (nBytesMax<nDataLen)?(nBytesMax):(nDataLen); | ||
| 398 | } | ||
| 399 | |||
| 400 | fprintf( fh, "%sDisplaying %d bytes of %s.\n", lpPrefix, nDataLen, lpName ); | ||
| 401 | int j = 0; | ||
| 402 | fprintf( fh, lpPrefix ); | ||
| 403 | for( int l = 0; l < 8*3+2*8+2; l++ ) fprintf( fh, (l!=8*3)?("-"):("+") ); fprintf( fh, "\n"); | ||
| 404 | for(;;) | ||
| 405 | { | ||
| 406 | fprintf( fh, lpPrefix ); | ||
| 407 | int kmax = 8; | ||
| 408 | if( nDataLen-j < 8 ) kmax = nDataLen-j; | ||
| 409 | for(int k = 0; k < 8; k++ ) | ||
| 410 | { | ||
| 411 | if( k < kmax ) | ||
| 412 | { | ||
| 413 | fprintf( fh, "%02X ", (int)((unsigned char)pData[j+k]) ); | ||
| 414 | } | ||
| 415 | else | ||
| 416 | { | ||
| 417 | fprintf( fh, "-- "); | ||
| 418 | } | ||
| 419 | } | ||
| 420 | printf("| "); | ||
| 421 | for(int k = 0; k < kmax; k++ ) | ||
| 422 | { | ||
| 423 | fprintf( fh, "%c ", (pData[j+k]>32 && pData[j+k]<=128)?(pData[j+k]):('.') ); | ||
| 424 | } | ||
| 425 | fprintf( fh, "\n"); | ||
| 426 | j += kmax; | ||
| 427 | if( j >= nDataLen ) break; | ||
| 428 | } | ||
| 429 | fprintf( fh, lpPrefix ); | ||
| 430 | for( int l = 0; l < 8*3+2*8+2; l++ ) fprintf( fh, (l!=8*3)?("-"):("+") ); fprintf( fh, "\n"); | ||
| 431 | } | ||
| 432 | |||
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 @@ | |||
| 1 | /**\file | ||
| 2 | * Contains the Connection class. | ||
| 3 | *@author Mike Buland | ||
| 4 | */ | ||
| 5 | |||
| 6 | #ifndef CONNECTION_H | ||
| 7 | #define CONNECTION_H | ||
| 8 | |||
| 9 | #include "multilog.h" | ||
| 10 | #include "flexbuf.h" | ||
| 11 | #include "protocol.h" | ||
| 12 | |||
| 13 | /** Represents a single connection on a network. While these connections | ||
| 14 | * may be treated more or less just like files, occasionally problems arise | ||
| 15 | * when writing data at any time you feel like. Therefore you run all your | ||
| 16 | * data through a Connection, which buffers all data and makes sure no | ||
| 17 | * buffers are exceeded and nothing inappropriate for the recipient of the | ||
| 18 | * data is sent. | ||
| 19 | *@author Mike Buland | ||
| 20 | */ | ||
| 21 | class Connection | ||
| 22 | { | ||
| 23 | public: | ||
| 24 | /** | ||
| 25 | * Construct a blank and non-connected Connection. The created object is | ||
| 26 | * not yet connected to anything, and most of the functions except open are | ||
| 27 | * unusable. | ||
| 28 | */ | ||
| 29 | Connection(); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Destroy the connection, clean up all pending data requests and close the | ||
| 33 | * contained socket. This does not send out pending data, especially since | ||
| 34 | * such an operation could take considerable time, depending on the pending | ||
| 35 | * data and state of the receiving end. | ||
| 36 | */ | ||
| 37 | ~Connection(); | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Open a connection to a remote server. This sets up this connection as | ||
| 41 | * a client instead of a server and does all of the work that needs to be | ||
| 42 | * done to actually open an INET_AF connection, which is a lot of work. | ||
| 43 | *@param sAddr The address to connect to. This can be in any format | ||
| 44 | * normally understood by your system to be an address, ip, domain name, | ||
| 45 | * etc. | ||
| 46 | *@param nPort The port number to connect to on the remote server. | ||
| 47 | *@returns True if the connection was successful and everything is setup, | ||
| 48 | * false if there were any of a dozen errors and the connection is not set. | ||
| 49 | *@todo Make this function add log entries to a standard MultiLog if | ||
| 50 | * something goes wrong. | ||
| 51 | */ | ||
| 52 | bool open( const char *sAddr, int nPort ); | ||
| 53 | |||
| 54 | /** Append the given data to the output. The data is presumed to be null | ||
| 55 | * terminated. To put binary data into the stream, use the other | ||
| 56 | * appendOutput function. This should be the only method used to | ||
| 57 | * communicate with the socket. | ||
| 58 | *@param lpOutput The data to add to the output queue. | ||
| 59 | *@param nSize How much data is in the lpOutput buffer. If this value | ||
| 60 | * is -1 then the program treats lpOutput as a null-terminated string. | ||
| 61 | *@returns True if everything is ok, false otherwise. | ||
| 62 | */ | ||
| 63 | bool appendOutput( const char *lpOutput, int nSize=-1 ); | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Append the character to the output. | ||
| 67 | *@param lOutput The character to add to the output queue. | ||
| 68 | *@returns True if everything is ok, false otherwise. | ||
| 69 | */ | ||
| 70 | bool appendOutput( const char lOutput ); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Append the short to the output. | ||
| 74 | *@param lOutput The short to add to the output queue. | ||
| 75 | *@returns True if everything is ok, false otherwise. | ||
| 76 | */ | ||
| 77 | bool appendOutput( const short lOutput ); | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Append the int to the output. | ||
| 81 | *@param lOutput The int to add to the output queue. | ||
| 82 | *@returns True if everything is ok, false otherwise. | ||
| 83 | */ | ||
| 84 | bool appendOutput( const int lOutput ); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Append the long to the output. | ||
| 88 | *@param lOutput The long to add to the output queue. | ||
| 89 | *@returns True if everything is ok, false otherwise. | ||
| 90 | */ | ||
| 91 | bool appendOutput( const long lOutput ); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Append the float to the output. | ||
| 95 | *@param lOutput The float to add to the output queue. | ||
| 96 | *@returns True if everything is ok, false otherwise. | ||
| 97 | */ | ||
| 98 | bool appendOutput( const float lOutput ); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Append the double to the output. | ||
| 102 | *@param lOutput The double to add to the output queue. | ||
| 103 | *@returns True if everything is ok, false otherwise. | ||
| 104 | */ | ||
| 105 | bool appendOutput( const double lOutput ); | ||
| 106 | |||
| 107 | /** | ||
| 108 | * Append the unsigned char to the output. | ||
| 109 | *@param lOutput The unsigned char to add to the output queue. | ||
| 110 | *@returns True if everything is ok, false otherwise. | ||
| 111 | */ | ||
| 112 | bool appendOutput( const unsigned char lOutput ); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Append the unsigned short to the output. | ||
| 116 | *@param lOutput The unsigned short to add to the output queue. | ||
| 117 | *@returns True if everything is ok, false otherwise. | ||
| 118 | */ | ||
| 119 | bool appendOutput( const unsigned short lOutput ); | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Append the unsigned int to the output. | ||
| 123 | *@param lOutput The unsigned int to add to the output queue. | ||
| 124 | *@returns True if everything is ok, false otherwise. | ||
| 125 | */ | ||
| 126 | bool appendOutput( const unsigned int lOutput ); | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Append the unsigned long to the output. | ||
| 130 | *@param lOutput The unsigned long to add to the output queue. | ||
| 131 | *@returns True if everything is ok, false otherwise. | ||
| 132 | */ | ||
| 133 | bool appendOutput( const unsigned long lOutput ); | ||
| 134 | |||
| 135 | /** | ||
| 136 | * Writes all input data in the buffer in a dual-view ascii and hex display | ||
| 137 | * to a file. There are a number of options that also help with debugging. | ||
| 138 | *@param lpPrefix Text to be added to the begining of every line written | ||
| 139 | * out. The default is a blank string. | ||
| 140 | *@param fh The file to write the data to in text mode. This is stdout by | ||
| 141 | * default, but could be any already open file handle. | ||
| 142 | *@param nBytesMax The maximum number of bytes to write to the output. The | ||
| 143 | * amount of data can be overwhelming sometimes, so you can limit it. The | ||
| 144 | * default value is -1, which is also unlimited. | ||
| 145 | */ | ||
| 146 | void printInputDebug( const char *lpPrefix="", FILE *fh=stdout, int nBytesMax=-1 ); | ||
| 147 | |||
| 148 | /** | ||
| 149 | * Writes all output data in the buffer in a dual-view ascii and hex display | ||
| 150 | * to a file. There are a number of options that also help with debugging. | ||
| 151 | *@param lpPrefix Text to be added to the begining of every line written | ||
| 152 | * out. The default is a blank string. | ||
| 153 | *@param fh The file to write the data to in text mode. This is stdout by | ||
| 154 | * default, but could be any already open file handle. | ||
| 155 | *@param nBytesMax The maximum number of bytes to write to the output. The | ||
| 156 | * amount of data can be overwhelming sometimes, so you can limit it. The | ||
| 157 | * default value is -1, which is also unlimited. | ||
| 158 | */ | ||
| 159 | void printOutputDebug( const char *lpPrefix="", FILE *fh=stdout, int nBytesMax=-1 ); | ||
| 160 | |||
| 161 | /** | ||
| 162 | * This is the low-level generic function that is called by both | ||
| 163 | * printInputDebug and printOutputDebug. It works effectively just like | ||
| 164 | * both of them, except that you can give it a raw pointer to the data to | ||
| 165 | * print out. This probably doesn't belong in this class, but this was | ||
| 166 | * where I was when I needed it. | ||
| 167 | *@param pData A pointer to the data to write. This is not treated as a | ||
| 168 | * null terminated string, so make sure that the nDataLen param is set | ||
| 169 | * properly. | ||
| 170 | *@param nDataLen The number of bytes that are in pData and that you want to | ||
| 171 | * see. | ||
| 172 | *@param lpName The name of the data, this is used in the header where it | ||
| 173 | * says "Displaying nnn bytes of <lpName>." A good example would be input | ||
| 174 | * or output. | ||
| 175 | *@param lpPrefix Text to put before every line output. This just makes it | ||
| 176 | * easier to tell large blocks apart in the output. | ||
| 177 | *@param fh The file handle to write all data to. | ||
| 178 | *@param nBytesMax The maximum number of bytes. This parameter is stupid. | ||
| 179 | * If it is set to -1, then nDataLen is used, otherwise the smaller value is | ||
| 180 | * used as the number of bytes to output. | ||
| 181 | *@todo Put this function somewhere more deserving. | ||
| 182 | *@todo Remove the nBytesMax param, we need that in the other functions, | ||
| 183 | * not this one! | ||
| 184 | */ | ||
| 185 | void printDataDebug( const unsigned char *pData, long nDataLen, const char *lpName, const char *lpPrefix, FILE *fh, int nBytesMax ); | ||
| 186 | |||
| 187 | /** Append the given data to the input. The data is presumed to be null | ||
| 188 | * terminated. To put binary data into the stream, use the other | ||
| 189 | * appendInput function. This is mainly used by internal routines. | ||
| 190 | *@param lpInput The data to add to the input queue. | ||
| 191 | *@param nSize How much data is in the lpInput buffer. If this value | ||
| 192 | * is -1 then the program treats lpOutput as a null-terminated string. | ||
| 193 | *@returns True if everything is ok, false otherwise. | ||
| 194 | */ | ||
| 195 | bool appendInput( const char *lpInput, int nSize=-1 ); | ||
| 196 | |||
| 197 | /** Searches through the current pending input for a certain character. | ||
| 198 | * This is useful for finding out where exactly the end of a line is, for | ||
| 199 | * example, to see if a command has been entered yet. | ||
| 200 | *@param cTarget The character to search for. | ||
| 201 | *@returns The position of the target relative to the begining of the input | ||
| 202 | * or -1 if the target wasn't found. | ||
| 203 | */ | ||
| 204 | int scanInputFor( char cTarget ); | ||
| 205 | |||
| 206 | /** Gets a pointer to the output buffer. This is mainly used by internal | ||
| 207 | * routines, and is cleared every click when data is sent out again. | ||
| 208 | *@returns A pointer to the buffer holding the pending output data. | ||
| 209 | */ | ||
| 210 | const char *getOutput(); | ||
| 211 | |||
| 212 | /** Gets a pointer to the start of the input buffer's active data | ||
| 213 | * section. Use this to gain access to the input you need to do | ||
| 214 | * your job. | ||
| 215 | *@returns A pointer to the data in the input buffer. Do not delete this. | ||
| 216 | */ | ||
| 217 | const char *getInput(); | ||
| 218 | |||
| 219 | /** Clears all pending output, this is mainly just used internally. | ||
| 220 | *@returns True if operation was a success, otherwise false. | ||
| 221 | */ | ||
| 222 | bool clearOutput(); | ||
| 223 | |||
| 224 | /** Clears all pending input, weather it's been used or not. Please | ||
| 225 | * refrain from calling this during normal operation, use usedInput | ||
| 226 | * instead, it's much safer. | ||
| 227 | *@returns True if the operation was a success, false otherwise. | ||
| 228 | */ | ||
| 229 | bool clearInput(); | ||
| 230 | |||
| 231 | /** Sets the socket that should be used internally. | ||
| 232 | *@param nNewSocket The new socket to work with. | ||
| 233 | */ | ||
| 234 | void setSocket( int nNewSocket ); | ||
| 235 | |||
| 236 | /** Gets the handle (number) of the working socket. This can be a | ||
| 237 | * dangerous function to call, please refrain from calling it directly | ||
| 238 | * if any alternative can be found. | ||
| 239 | *@returns The number of the working socket. | ||
| 240 | */ | ||
| 241 | int getSocket(); | ||
| 242 | |||
| 243 | /** Determines if the connection is still active. | ||
| 244 | *@returns True if the connection is active, false otherwise. | ||
| 245 | */ | ||
| 246 | bool isActive(); | ||
| 247 | |||
| 248 | /** Clears all buffers and sets up the connection to be reused. | ||
| 249 | * Does not actually close the socket, that's handled by the | ||
| 250 | * ConnectionManager | ||
| 251 | */ | ||
| 252 | void close(); | ||
| 253 | |||
| 254 | /** Opens a socket. Really just sets up the connection for use since | ||
| 255 | * the socket itself was created and opened by the ConnectionManager. | ||
| 256 | * This also calls setSocket so you don't have to. | ||
| 257 | *@param nNewSocket The socket to assosiate with. | ||
| 258 | */ | ||
| 259 | bool open( int nNewSocket ); | ||
| 260 | |||
| 261 | /** | ||
| 262 | * Reads all pending input from the connection. If this is called outside | ||
| 263 | * of the ConnectionManager it will usually block indefinately waiting for | ||
| 264 | * new data. The only way to change this behaviour is to modify the socket | ||
| 265 | * low-level when you connect it manually, or, preferably use the other | ||
| 266 | * readInput function to control blocking time. | ||
| 267 | *@returns True socket is still connected, otherwise false. | ||
| 268 | */ | ||
| 269 | bool readInput(); | ||
| 270 | |||
| 271 | /** | ||
| 272 | * Reads all pending input from the connection, blocking up to nSec | ||
| 273 | * seconds and nUSec micro-seconds for the data. This uses select to | ||
| 274 | * simulate blocking, but has the same effect as standard io blocking. | ||
| 275 | * If you don't want to block, just set both values to zero. | ||
| 276 | *@param nSec Max seconds to wait. | ||
| 277 | *@param nUSec Max micro-seconds to wait. | ||
| 278 | */ | ||
| 279 | bool readInput( int nSec, int nUSec ); | ||
| 280 | |||
| 281 | /** Writes all data that is pending to the socket. | ||
| 282 | *@returns True if all data was written succesfully, false otherwise. | ||
| 283 | */ | ||
| 284 | bool writeOutput(); | ||
| 285 | |||
| 286 | /** Determines if the connection has output waiting to go out. | ||
| 287 | *@returns true if there is pending output, otherwise false. | ||
| 288 | */ | ||
| 289 | bool hasOutput(); | ||
| 290 | |||
| 291 | /** Sets internal flags so that this connection will be deleted next | ||
| 292 | * time through the ConnectionManager. | ||
| 293 | */ | ||
| 294 | void disconnect(); | ||
| 295 | |||
| 296 | /** Determines if this connection is ready to be disconnected or not. | ||
| 297 | *@returns True if it is time to disconnect, false if it isn't. | ||
| 298 | */ | ||
| 299 | bool needDisconnect(); | ||
| 300 | |||
| 301 | /** Tells the caller if there is pending input waiting to be processed. | ||
| 302 | *@returns True if there is pending input that has not been used, returns | ||
| 303 | * false if there isn't. | ||
| 304 | */ | ||
| 305 | bool hasInput(); | ||
| 306 | |||
| 307 | /** Removes bytes from the begining of the input queue. Use this after | ||
| 308 | * getting the input and processing as much as you need to. | ||
| 309 | *@param nAmount The number of bytes used. | ||
| 310 | *@returns true if the update was successful, otherwise false. | ||
| 311 | */ | ||
| 312 | bool usedInput( int nAmount ); | ||
| 313 | |||
| 314 | /** Sets the protocol to be used by this connection. All data in and out | ||
| 315 | * passes through the protocol object, which may process that data to | ||
| 316 | * filter out and process any special messages that may have been | ||
| 317 | * included. Everything that isn't processed can be accessed in the | ||
| 318 | * standard method. | ||
| 319 | *@param pNewProtocol A pointer to a protocol object that you want to | ||
| 320 | * use. | ||
| 321 | */ | ||
| 322 | void setProtocol( class Protocol *pNewProtocol ); | ||
| 323 | |||
| 324 | /** Gets the number of bytes that are waiting in the input queue, the data | ||
| 325 | * that has yet to be processed. | ||
| 326 | *@returns The number of bytes in the input queue. | ||
| 327 | */ | ||
| 328 | int getInputAmnt(); | ||
| 329 | |||
| 330 | /** Gets the number of bytes that are waiting in the output queue, the data | ||
| 331 | * that has yet to be sent to the connected socket. | ||
| 332 | *@returns The number of bytes in the input queue. | ||
| 333 | */ | ||
| 334 | int getOutputAmnt(); | ||
| 335 | |||
| 336 | /** Gets a pointer to the protocol that is attatched to this connection | ||
| 337 | * object. This is useful to set modes, and send special commands in | ||
| 338 | * addition to the standard raw data reads and writes that are normally | ||
| 339 | * permitted. In fact, in everything besides a raw telnet protocol all | ||
| 340 | * data should be sent through the protocol and not the connection object. | ||
| 341 | *@returns A pointer to the Protocol assosiated with this connection. | ||
| 342 | */ | ||
| 343 | class Protocol *getProtocol(); | ||
| 344 | |||
| 345 | private: | ||
| 346 | /** | ||
| 347 | * A buffer to keep data read from the socket in. This is filled in by | ||
| 348 | * the function readInput, which is automatically called by the | ||
| 349 | * ConnectionManager whenever new data is ready. | ||
| 350 | */ | ||
| 351 | FlexBuf xInputBuf; | ||
| 352 | |||
| 353 | /** | ||
| 354 | * A buffer to keep data that should be sent to the socket. This is filled | ||
| 355 | * in by using the AppendOutput functions and is sent to the socket using | ||
| 356 | * the writeOutput function, which is automatically called every cycle by | ||
| 357 | * the ConnectionManager when there is pending data. | ||
| 358 | */ | ||
| 359 | FlexBuf xOutputBuf; | ||
| 360 | |||
| 361 | /** | ||
| 362 | * The socket that the user is connected to. This is not the same as the | ||
| 363 | * socket number of the listening socket, this is the unique socket on the | ||
| 364 | * system that the data is coming to. | ||
| 365 | */ | ||
| 366 | int nSocket; | ||
| 367 | |||
| 368 | /** | ||
| 369 | * True=active connection, False=connection lost | ||
| 370 | */ | ||
| 371 | bool bActive; | ||
| 372 | |||
| 373 | /** | ||
| 374 | * True=disconnect next cycle (after data is transmitted), Flse=keep going. | ||
| 375 | */ | ||
| 376 | bool bDisconnectMe; | ||
| 377 | |||
| 378 | /** | ||
| 379 | * A pointer to a protocol handler that can automatically process the data | ||
| 380 | * in the buffers. This is optional if you use the connections on your own | ||
| 381 | * but reccomended if you use this with the rest of the ConnectionManager | ||
| 382 | * system. | ||
| 383 | */ | ||
| 384 | class Protocol *pProtocol; | ||
| 385 | }; | ||
| 386 | |||
| 387 | #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 @@ | |||
| 1 | #include <time.h> | ||
| 2 | #include <string.h> | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <errno.h> | ||
| 5 | #include <stdlib.h> | ||
| 6 | #include <unistd.h> | ||
| 7 | #include <sys/types.h> | ||
| 8 | #include <sys/socket.h> | ||
| 9 | #include <termios.h> | ||
| 10 | #include <netinet/in.h> | ||
| 11 | #include <netdb.h> | ||
| 12 | #include <arpa/inet.h> | ||
| 13 | #include "connectionmanager.h" | ||
| 14 | #include <fcntl.h> | ||
| 15 | |||
| 16 | ConnectionManager::ConnectionManager() | ||
| 17 | { | ||
| 18 | pLog = MultiLog::getLog(); | ||
| 19 | nMasterSocket = -1; | ||
| 20 | pMonitor = NULL; | ||
| 21 | } | ||
| 22 | |||
| 23 | ConnectionManager::~ConnectionManager() | ||
| 24 | { | ||
| 25 | std::list<Connection *>::const_iterator i; | ||
| 26 | for( i = lActive.begin(); i != lActive.end(); i++ ) | ||
| 27 | { | ||
| 28 | delete (*i); | ||
| 29 | } | ||
| 30 | for( i = lInactive.begin(); i != lInactive.end(); i++ ) | ||
| 31 | { | ||
| 32 | delete (*i); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | bool ConnectionManager::startServer( int nPort, int nInitPool ) | ||
| 37 | { | ||
| 38 | /* Create the socket and set it up to accept connections. */ | ||
| 39 | struct sockaddr_in name; | ||
| 40 | |||
| 41 | /* Create the socket. */ | ||
| 42 | nMasterSocket = socket (PF_INET, SOCK_STREAM, 0); | ||
| 43 | if (nMasterSocket < 0) | ||
| 44 | { | ||
| 45 | pLog->LineLog( MultiLog::LError, "Couldn't create a listen socket."); | ||
| 46 | return false; | ||
| 47 | } | ||
| 48 | |||
| 49 | /* Give the socket a name. */ | ||
| 50 | name.sin_family = AF_INET; | ||
| 51 | name.sin_port = htons( nPort ); | ||
| 52 | |||
| 53 | // I think this specifies who we will accept connections from, | ||
| 54 | // a good thing to make configurable later on | ||
| 55 | name.sin_addr.s_addr = htonl( INADDR_ANY ); | ||
| 56 | |||
| 57 | int opt = 1; | ||
| 58 | setsockopt( nMasterSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)); | ||
| 59 | |||
| 60 | if (bind (nMasterSocket, (struct sockaddr *) &name, sizeof (name)) < 0) | ||
| 61 | { | ||
| 62 | pLog->LineLog( MultiLog::LError, "Couldn't bind to the listen socket."); | ||
| 63 | return false; | ||
| 64 | } | ||
| 65 | |||
| 66 | if (listen (nMasterSocket, 1) < 0) | ||
| 67 | { | ||
| 68 | pLog->LineLog( MultiLog::LError, "Couldn't begin listening to the server socket."); | ||
| 69 | return false; | ||
| 70 | } | ||
| 71 | |||
| 72 | /* Initialize the set of active sockets. */ | ||
| 73 | FD_ZERO (&fdActive); | ||
| 74 | FD_ZERO (&fdRead); | ||
| 75 | FD_ZERO (&fdWrite); | ||
| 76 | FD_ZERO (&fdException); | ||
| 77 | FD_SET (nMasterSocket, &fdActive); | ||
| 78 | |||
| 79 | for( int j = 0; j < nInitPool; j++ ) | ||
| 80 | { | ||
| 81 | lInactive.insert( lInactive.begin(), new Connection() ); | ||
| 82 | } | ||
| 83 | |||
| 84 | return true; | ||
| 85 | } | ||
| 86 | |||
| 87 | bool ConnectionManager::startServer( int nPort, int nInitPool, int nNumTries, int nTimeout ) | ||
| 88 | { | ||
| 89 | struct timeval xTimeout; | ||
| 90 | |||
| 91 | for( int j = 0; j < nNumTries; j++ ) | ||
| 92 | { | ||
| 93 | pLog->LineLog( MultiLog::LStatus, "Attempting to create server socket (attempt [%d/%d])...", j+1, nNumTries ); | ||
| 94 | if( startServer( nPort, nInitPool ) == true ) | ||
| 95 | { | ||
| 96 | return true; | ||
| 97 | } | ||
| 98 | else if( j < nNumTries-1 ) | ||
| 99 | { | ||
| 100 | pLog->LineLog( MultiLog::LStatus, "Waiting for %d secconds to allow port to clear...", nTimeout ); | ||
| 101 | xTimeout.tv_sec = nTimeout; | ||
| 102 | xTimeout.tv_usec = 0; | ||
| 103 | if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &xTimeout) < 0) { | ||
| 104 | pLog->LineLog( MultiLog::LError, "Error using select to sleep for a while."); | ||
| 105 | } | ||
| 106 | usleep( nTimeout ); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | return false; | ||
| 111 | } | ||
| 112 | |||
| 113 | bool ConnectionManager::scanConnections( int nTimeout, bool bForceTimeout ) | ||
| 114 | { | ||
| 115 | struct timeval xTimeout; | ||
| 116 | |||
| 117 | xTimeout.tv_sec = nTimeout / 1000000; | ||
| 118 | xTimeout.tv_usec = nTimeout % 1000000; | ||
| 119 | |||
| 120 | /* Block until input arrives on one or more active sockets. */ | ||
| 121 | fdRead = fdActive; | ||
| 122 | fdWrite = fdActive; | ||
| 123 | fdException = fdActive; | ||
| 124 | |||
| 125 | // We removed the write checking because it just checks to see if you *can* | ||
| 126 | // write...that's stupid, they're all open, so it always exits immediately | ||
| 127 | // if there are ANY connections there... | ||
| 128 | if( TEMP_FAILURE_RETRY( select( FD_SETSIZE, &fdRead, (fd_set *)0/*&fdWrite*/, &fdException, &xTimeout ) ) < 0 ) | ||
| 129 | { | ||
| 130 | pLog->LineLog( MultiLog::LError, "Error attempting to scan open connections."); | ||
| 131 | perror("ConnectionManager"); | ||
| 132 | return false; | ||
| 133 | } | ||
| 134 | // Now we use select to sleep as well as to scan for connections, now we | ||
| 135 | // just need to fix the fact that if there are no connections, the seccond | ||
| 136 | // select call doesn't return until there is a connection... | ||
| 137 | if( bForceTimeout ) | ||
| 138 | { | ||
| 139 | if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &xTimeout) < 0) { | ||
| 140 | pLog->LineLog( MultiLog::LError, "Error using select to sleep for a while."); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | /* Service all the sockets with input pending. */ | ||
| 145 | for( int i = 0; i < FD_SETSIZE; ++i ) | ||
| 146 | { | ||
| 147 | if( FD_ISSET( i, &fdRead ) ) | ||
| 148 | { | ||
| 149 | if( i == nMasterSocket ) | ||
| 150 | { | ||
| 151 | addConnection(); | ||
| 152 | } | ||
| 153 | else | ||
| 154 | { | ||
| 155 | Connection *pCon = findActiveConnection( i ); | ||
| 156 | if( pCon == NULL ) | ||
| 157 | { | ||
| 158 | pLog->LineLog( MultiLog::LError, "A connection object was lost, or never created!"); | ||
| 159 | return false; | ||
| 160 | } | ||
| 161 | |||
| 162 | /* Data arriving on an already-connected socket. */ | ||
| 163 | if( pCon->readInput() != true ) | ||
| 164 | { | ||
| 165 | pLog->LineLog( MultiLog::LStatus, "Closing connection due to disconnect."); | ||
| 166 | close( i ); | ||
| 167 | FD_CLR( i, &fdActive ); | ||
| 168 | pMonitor->onClosedConnection( pCon ); | ||
| 169 | pCon->close(); | ||
| 170 | } | ||
| 171 | else | ||
| 172 | { | ||
| 173 | // We actually read something...but the connection handles | ||
| 174 | // protocol notification, so we don't need to do anything | ||
| 175 | // here... | ||
| 176 | } | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | std::list<Connection *>::iterator i; | ||
| 181 | for( i = lActive.begin(); i != lActive.end(); i++ ) | ||
| 182 | { | ||
| 183 | if( (*i)->isActive() == false ) | ||
| 184 | { | ||
| 185 | std::list<Connection *>::iterator l = i; | ||
| 186 | i--; | ||
| 187 | lInactive.insert( lInactive.end(), *l ); | ||
| 188 | lActive.erase( l ); | ||
| 189 | continue; | ||
| 190 | } | ||
| 191 | if( (*i)->hasOutput() ) | ||
| 192 | { | ||
| 193 | (*i)->writeOutput(); | ||
| 194 | } | ||
| 195 | if( (*i)->needDisconnect() ) | ||
| 196 | { | ||
| 197 | int prt = (*i)->getSocket(); | ||
| 198 | close( prt ); | ||
| 199 | FD_CLR( prt, &fdActive ); | ||
| 200 | pMonitor->onClosedConnection( *i ); | ||
| 201 | (*i)->close(); | ||
| 202 | lInactive.insert( lInactive.end(), *i ); | ||
| 203 | std::list<Connection *>::iterator l = i; | ||
| 204 | i--; | ||
| 205 | lActive.erase( l ); | ||
| 206 | pLog->LineLog( MultiLog::LStatus, "Closing connection due to server request."); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | return true; | ||
| 211 | } | ||
| 212 | |||
| 213 | bool ConnectionManager::shutdownServer() | ||
| 214 | { | ||
| 215 | while( !lActive.empty() ) | ||
| 216 | { | ||
| 217 | Connection *i = *(lActive.begin()); | ||
| 218 | if( i->isActive() ) | ||
| 219 | { | ||
| 220 | i->close(); | ||
| 221 | pMonitor->onClosedConnection( i ); | ||
| 222 | lInactive.insert( lInactive.end(), i ); | ||
| 223 | lActive.erase( lActive.begin() ); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | /* | ||
| 227 | for( int i = 0; i < nPoolSize; i++ ) | ||
| 228 | { | ||
| 229 | |||
| 230 | int prt = axConPool[i].getSocket(); | ||
| 231 | close( prt ); | ||
| 232 | // FD_CLR( prt, &fdActive ); | ||
| 233 | pMonitor->onClosedConnection( &axConPool[i] ); | ||
| 234 | axConPool[i].close(); | ||
| 235 | } | ||
| 236 | */ | ||
| 237 | shutdown( nMasterSocket, SHUT_RDWR ); | ||
| 238 | close( nMasterSocket ); | ||
| 239 | |||
| 240 | return true; | ||
| 241 | } | ||
| 242 | |||
| 243 | bool ConnectionManager::broadcastMessage( const char *lpData, int nExcludeSocket ) | ||
| 244 | { | ||
| 245 | std::list<Connection *>::const_iterator i; | ||
| 246 | for( i = lActive.begin(); i != lActive.end(); i++ ) | ||
| 247 | { | ||
| 248 | if( (*i)->isActive() && | ||
| 249 | (*i)->getSocket() != nExcludeSocket ) | ||
| 250 | { | ||
| 251 | (*i)->appendOutput( lpData ); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | return true; | ||
| 256 | } | ||
| 257 | |||
| 258 | bool ConnectionManager::addConnection() | ||
| 259 | { | ||
| 260 | struct sockaddr_in clientname; | ||
| 261 | size_t size; | ||
| 262 | int newSocket; | ||
| 263 | |||
| 264 | size = sizeof( clientname ); | ||
| 265 | #ifdef __CYGWIN__ | ||
| 266 | newSocket = accept( nMasterSocket, (struct sockaddr *) &clientname, (int *)&size ); | ||
| 267 | #else | ||
| 268 | newSocket = accept( nMasterSocket, (struct sockaddr *) &clientname, &size ); | ||
| 269 | #endif | ||
| 270 | if( newSocket < 0 ) | ||
| 271 | { | ||
| 272 | pLog->LineLog( MultiLog::LError, "Error accepting a new connection!" ); | ||
| 273 | return false; | ||
| 274 | } | ||
| 275 | // char *tmpa = inet_ntoa(clientname.sin_addr); | ||
| 276 | char tmpa[20]; | ||
| 277 | inet_ntop( AF_INET, (void *)&clientname.sin_addr, tmpa, 20 ); | ||
| 278 | pLog->LineLog( MultiLog::LStatus, "New connection from host %s, port %hd.", tmpa, ntohs (clientname.sin_port) ); | ||
| 279 | /* | ||
| 280 | int nCnt = 0; | ||
| 281 | for( int j = 0; j < nPoolSize; j++ ) | ||
| 282 | { | ||
| 283 | if( axConPool[j].isActive() ) | ||
| 284 | { | ||
| 285 | nCnt++; | ||
| 286 | } | ||
| 287 | } | ||
| 288 | pLog->LineLog( MultiLog::LStatus, "Connections %d/%d.", nCnt, nPoolSize ); | ||
| 289 | */ | ||
| 290 | // free( tmpa ); | ||
| 291 | FD_SET( newSocket, &fdActive ); | ||
| 292 | |||
| 293 | //void nonblock(socket_t s) | ||
| 294 | { | ||
| 295 | int flags; | ||
| 296 | |||
| 297 | flags = fcntl(newSocket, F_GETFL, 0); | ||
| 298 | flags |= O_NONBLOCK; | ||
| 299 | if (fcntl(newSocket, F_SETFL, flags) < 0) | ||
| 300 | { | ||
| 301 | return false; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | Connection *pCon = getInactiveConnection(); | ||
| 306 | pCon->open( newSocket ); | ||
| 307 | |||
| 308 | pMonitor->onNewConnection( pCon ); | ||
| 309 | |||
| 310 | lActive.insert( lActive.end(), pCon ); | ||
| 311 | |||
| 312 | return true; | ||
| 313 | } | ||
| 314 | |||
| 315 | Connection *ConnectionManager::getInactiveConnection() | ||
| 316 | { | ||
| 317 | if( lInactive.empty() ) | ||
| 318 | { | ||
| 319 | return new Connection(); | ||
| 320 | } | ||
| 321 | Connection *pCon = *(lInactive.begin()); | ||
| 322 | lInactive.erase( lInactive.begin() ); | ||
| 323 | return pCon; | ||
| 324 | } | ||
| 325 | |||
| 326 | Connection *ConnectionManager::findActiveConnection( int nSocket ) | ||
| 327 | { | ||
| 328 | std::list<Connection *>::const_iterator i; | ||
| 329 | for( i = lActive.begin(); i != lActive.end(); i++ ) | ||
| 330 | { | ||
| 331 | if( (*i)->getSocket() == nSocket ) | ||
| 332 | { | ||
| 333 | return *i; | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 337 | return NULL; | ||
| 338 | } | ||
| 339 | |||
| 340 | void ConnectionManager::setConnectionMonitor( ConnectionMonitor *pNewMonitor ) | ||
| 341 | { | ||
| 342 | pMonitor = pNewMonitor; | ||
| 343 | } | ||
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 @@ | |||
| 1 | /** | ||
| 2 | *@file | ||
| 3 | * Contains the ConnectionManager. | ||
| 4 | *@author Mike Buland | ||
| 5 | */ | ||
| 6 | |||
| 7 | #ifndef CONNECTIONMANAGER_H | ||
| 8 | #define CONNECTIONMANAGER_H | ||
| 9 | |||
| 10 | #include "multilog.h" | ||
| 11 | #include "connection.h" | ||
| 12 | #include "connectionmonitor.h" | ||
| 13 | #include <sys/types.h> | ||
| 14 | #include <list> | ||
| 15 | |||
| 16 | /** Manges incoming network connections as a server. Creates and works with | ||
| 17 | * Connection objects. All operations are performed on TCP/IP v4 right now, | ||
| 18 | * and on a single port, although any number of connections can be handled. | ||
| 19 | *@author Mike Buland | ||
| 20 | */ | ||
| 21 | class ConnectionManager | ||
| 22 | { | ||
| 23 | public: | ||
| 24 | /** | ||
| 25 | * Sets up the basics, like storage for the pool, and so on. This does not | ||
| 26 | * actually start a server, bind to a port, or create a connection pool. | ||
| 27 | * That's all handled by startServer(). | ||
| 28 | */ | ||
| 29 | ConnectionManager(); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Cleans up everything, and even clears out all still-connected Connection | ||
| 33 | * objects. | ||
| 34 | */ | ||
| 35 | ~ConnectionManager(); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Starts a server socket and binds to it, listening for new connections. | ||
| 39 | *@param nPort The port to listen on. | ||
| 40 | *@param nInitPool The size of the initial connection pool. This will | ||
| 41 | * grow automatically if necesarry. | ||
| 42 | *@returns True if the socket was bound to the port and serving was | ||
| 43 | * started. False if there was a problem connecting to the port. | ||
| 44 | */ | ||
| 45 | bool startServer( int nPort, int nInitPool ); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * This is identicle to the simpler startServer function except that it | ||
| 49 | * will automatically try to connect multiple times in case the first | ||
| 50 | * attempt or two doesn't work for some reason. Initially this was | ||
| 51 | * written to compensate for server sockets staying locked after they were | ||
| 52 | * closed for a while. | ||
| 53 | *@param nPort The port to listen on. | ||
| 54 | *@param nInitPool The size of the initial connection pool. This will | ||
| 55 | * grow automatically if necesarry. | ||
| 56 | *@param nNumTries The maximum number of times to try to connect. | ||
| 57 | *@param nTimeout The amount of time to wait in-between connection | ||
| 58 | * attempts. | ||
| 59 | *@returns True if the socket was bound to the port and serving was | ||
| 60 | * started. False if there was a problem connecting to the port. | ||
| 61 | */ | ||
| 62 | bool startServer( int nPort, int nInitPool, int nNumTries, int nTimeout ); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Scans all open connections, halting the calling processes until data | ||
| 66 | * is received or nTimeout ms have gone by. While waiting for the timeout | ||
| 67 | * to complete the process is placed into an idle mode. | ||
| 68 | *@param nTimeout The number of millisecconds to wait if there is nothing | ||
| 69 | * to actually do. | ||
| 70 | *@param bForceTimeout If set to true, this will force the scanner to wait | ||
| 71 | * for the timout to complete before returning, even if there was pending | ||
| 72 | * data. | ||
| 73 | */ | ||
| 74 | bool scanConnections( int nTimeout, bool bForceTimeout ); | ||
| 75 | |||
| 76 | /** Shutdown the server and all assosiated sockets. | ||
| 77 | *@returns True if every socket was closed without problem. | ||
| 78 | */ | ||
| 79 | bool shutdownServer(); | ||
| 80 | |||
| 81 | /** Sends a message directly to every connected port. | ||
| 82 | *@param lpData A null-terminated string of data to send. | ||
| 83 | *@param nExcludeSocket An optional socket to exclude from the broadcast. | ||
| 84 | *@returns True if every socket that should have gotten the message did. | ||
| 85 | */ | ||
| 86 | bool broadcastMessage( const char *lpData, int nExcludeSocket=-1 ); | ||
| 87 | |||
| 88 | /** Sets a monitor for the manager. The monitor is sent notifications | ||
| 89 | * whenever a socket is connected, disconnected, or whenever an error | ||
| 90 | * occurs. | ||
| 91 | *@param pNewMonitor A pointer to a preconstructed ConnectionMonitor | ||
| 92 | */ | ||
| 93 | void setConnectionMonitor( ConnectionMonitor *pNewMonitor ); | ||
| 94 | |||
| 95 | private: | ||
| 96 | /** | ||
| 97 | * Take care of the work of actually accepting a connection. This will | ||
| 98 | * accept the connection, set the initial modes, and add it to the master | ||
| 99 | * list of active connections, as well as fire off any messages that need | ||
| 100 | * to be handled by anything else. | ||
| 101 | *@returns True if everything worked, False otherwise. | ||
| 102 | */ | ||
| 103 | bool addConnection(); | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Seraches the internal lists of connections for one with a specific | ||
| 107 | * socket. | ||
| 108 | *@param nSocket The socket the connection is using for communication. | ||
| 109 | * This is the unique socket and not the one that the connection was | ||
| 110 | * initially to. | ||
| 111 | *@returns NULL if no connection was found, otherwise a pointer to a live | ||
| 112 | * Connection object. | ||
| 113 | */ | ||
| 114 | Connection *findActiveConnection( int nSocket ); | ||
| 115 | |||
| 116 | /** | ||
| 117 | * Searches the connection pool for an object that isn't in use yet, and | ||
| 118 | * returns it, ready to be filled in and used. | ||
| 119 | *@returns An unused connection object ready for use. | ||
| 120 | *@todo Check this code over to insure that the pool grows appropriately | ||
| 121 | * when enough extra connections are detected. | ||
| 122 | */ | ||
| 123 | Connection *getInactiveConnection(); | ||
| 124 | |||
| 125 | MultiLog *pLog; /**< A pointer to the active MultiLog */ | ||
| 126 | int nMasterSocket; /**< The listening or server socket. */ | ||
| 127 | fd_set fdActive; /**< The active socket set. */ | ||
| 128 | fd_set fdRead; /**< The sockets ready for a read. */ | ||
| 129 | fd_set fdWrite; /**< The sockets ready for a write. */ | ||
| 130 | fd_set fdException; /**< The sockets that have gotten errors. */ | ||
| 131 | std::list<Connection *> lInactive; /**< The pool of inactive Connections */ | ||
| 132 | std::list<Connection *> lActive; /**< The pool of active Connections */ | ||
| 133 | |||
| 134 | /** The ConnectionMonitor to notify of new connections. */ | ||
| 135 | ConnectionMonitor *pMonitor; | ||
| 136 | }; | ||
| 137 | |||
| 138 | #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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | connectionmonitor.cpp - description | ||
| 3 | ------------------- | ||
| 4 | begin : Mon Sep 8 2003 | ||
| 5 | copyright : (C) 2003 by Mike Buland | ||
| 6 | email : eichlan@yf-soft.com | ||
| 7 | ***************************************************************************/ | ||
| 8 | |||
| 9 | /*************************************************************************** | ||
| 10 | * * | ||
| 11 | * This program is free software; you can redistribute it and/or modify * | ||
| 12 | * it under the terms of the GNU General Public License as published by * | ||
| 13 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 14 | * (at your option) any later version. * | ||
| 15 | * * | ||
| 16 | ***************************************************************************/ | ||
| 17 | |||
| 18 | #include "connectionmonitor.h" | ||
| 19 | |||
| 20 | ConnectionMonitor::ConnectionMonitor(){ | ||
| 21 | } | ||
| 22 | ConnectionMonitor::~ConnectionMonitor(){ | ||
| 23 | } | ||
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 @@ | |||
| 1 | /**@file | ||
| 2 | * Describes the ConnectionMonitor class. | ||
| 3 | */ | ||
| 4 | #ifndef CONNECTIONMONITOR_H | ||
| 5 | #define CONNECTIONMONITOR_H | ||
| 6 | |||
| 7 | #include "connection.h" | ||
| 8 | |||
| 9 | /** Connection Monitor defines the base class of the objects that will be | ||
| 10 | * notified whenever a connection is created or destroyed. | ||
| 11 | *@author Mike Buland | ||
| 12 | */ | ||
| 13 | class ConnectionMonitor | ||
| 14 | { | ||
| 15 | public: | ||
| 16 | /** | ||
| 17 | * This is only here for completeness. It does nothing. | ||
| 18 | */ | ||
| 19 | ConnectionMonitor(); | ||
| 20 | |||
| 21 | /** | ||
| 22 | * This is only here for completeness. It does nothing. | ||
| 23 | */ | ||
| 24 | virtual ~ConnectionMonitor(); | ||
| 25 | |||
| 26 | /** Receives the notification that new connection was received. | ||
| 27 | *@param pCon The connection that was created. | ||
| 28 | *@returns Should return a true value if everything is OK, a false to | ||
| 29 | * force a shutdown. | ||
| 30 | */ | ||
| 31 | virtual bool onNewConnection( Connection *pCon ) = 0; | ||
| 32 | |||
| 33 | /** Receives the notification that a connection was closed. | ||
| 34 | *@param pCon The connection that was closed. | ||
| 35 | *@returns Should return a true value if everything is OK, a false to | ||
| 36 | * force a shutdown. | ||
| 37 | */ | ||
| 38 | virtual bool onClosedConnection( Connection *pCon ) = 0; | ||
| 39 | }; | ||
| 40 | |||
| 41 | #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 @@ | |||
| 1 | #include "flexbuf.h" | ||
| 2 | #include <string.h> | ||
| 3 | |||
| 4 | FlexBuf::FlexBuf() | ||
| 5 | { | ||
| 6 | lpBuf = new char[1024]; | ||
| 7 | nLastChar = 0; | ||
| 8 | nFirstChar = 0; | ||
| 9 | nSize = 1024; | ||
| 10 | nFill = 0; | ||
| 11 | clearData(); | ||
| 12 | } | ||
| 13 | |||
| 14 | FlexBuf::~FlexBuf() | ||
| 15 | { | ||
| 16 | delete[] lpBuf; | ||
| 17 | } | ||
| 18 | |||
| 19 | bool FlexBuf::appendData( const char *lpData, int nDSize ) | ||
| 20 | { | ||
| 21 | int nStrLen; | ||
| 22 | if( nDSize < 0 ) | ||
| 23 | { | ||
| 24 | nStrLen = strlen( lpData ); | ||
| 25 | } | ||
| 26 | else | ||
| 27 | { | ||
| 28 | nStrLen = nDSize; | ||
| 29 | } | ||
| 30 | |||
| 31 | if( nLastChar + nStrLen + 1 > nSize ) | ||
| 32 | { | ||
| 33 | if( nFill + nStrLen + 1 < nSize ) | ||
| 34 | { | ||
| 35 | memcpy( lpBuf, lpBuf+nFirstChar, nFill ); | ||
| 36 | nLastChar -= nFirstChar; | ||
| 37 | nFirstChar = 0; | ||
| 38 | } | ||
| 39 | else | ||
| 40 | { | ||
| 41 | nSize += nStrLen+1; | ||
| 42 | char *lpNewBuf = new char[nSize]; | ||
| 43 | memcpy( lpNewBuf, lpBuf+nFirstChar, nFill ); | ||
| 44 | delete[] lpBuf; | ||
| 45 | lpBuf = lpNewBuf; | ||
| 46 | nLastChar -= nFirstChar; | ||
| 47 | nFirstChar = 0; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | memcpy( &lpBuf[nLastChar], lpData, nStrLen ); | ||
| 52 | nLastChar += nStrLen; | ||
| 53 | nFill += nStrLen; | ||
| 54 | lpBuf[nLastChar] = '\0'; | ||
| 55 | |||
| 56 | return true; | ||
| 57 | } | ||
| 58 | |||
| 59 | bool FlexBuf::appendData( const char lData ) | ||
| 60 | { | ||
| 61 | if( nLastChar + 2 > nSize ) | ||
| 62 | { | ||
| 63 | if( nFill+2 < nSize ) | ||
| 64 | { | ||
| 65 | memcpy( lpBuf, lpBuf+nFirstChar, nFill ); | ||
| 66 | nLastChar -= nFirstChar; | ||
| 67 | nFirstChar = 0; | ||
| 68 | } | ||
| 69 | else | ||
| 70 | { | ||
| 71 | nSize += 1024; | ||
| 72 | char *lpNewBuf = new char[nSize]; | ||
| 73 | memcpy( lpNewBuf, lpBuf+nFirstChar, nFill ); | ||
| 74 | delete[] lpBuf; | ||
| 75 | lpBuf = lpNewBuf; | ||
| 76 | nLastChar -= nFirstChar; | ||
| 77 | nFirstChar = 0; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | lpBuf[nLastChar] = lData; | ||
| 82 | nLastChar++; | ||
| 83 | nFill++; | ||
| 84 | lpBuf[nLastChar] = '\0'; | ||
| 85 | |||
| 86 | return true; | ||
| 87 | } | ||
| 88 | |||
| 89 | bool FlexBuf::appendData( const short lData ) | ||
| 90 | { | ||
| 91 | return appendData( (const char *)&lData, sizeof(short) ); | ||
| 92 | } | ||
| 93 | |||
| 94 | bool FlexBuf::appendData( const int lData ) | ||
| 95 | { | ||
| 96 | return appendData( (const char *)&lData, sizeof(int) ); | ||
| 97 | } | ||
| 98 | |||
| 99 | bool FlexBuf::appendData( const long lData ) | ||
| 100 | { | ||
| 101 | return appendData( (const char *)&lData, sizeof(long) ); | ||
| 102 | } | ||
| 103 | |||
| 104 | bool FlexBuf::appendData( const float lData ) | ||
| 105 | { | ||
| 106 | return appendData( (const char *)&lData, sizeof(float) ); | ||
| 107 | } | ||
| 108 | |||
| 109 | bool FlexBuf::appendData( const double lData ) | ||
| 110 | { | ||
| 111 | return appendData( (const char *)&lData, sizeof(double) ); | ||
| 112 | } | ||
| 113 | |||
| 114 | bool FlexBuf::appendData( const unsigned char lData ) | ||
| 115 | { | ||
| 116 | return appendData( (const char)lData ); | ||
| 117 | } | ||
| 118 | |||
| 119 | bool FlexBuf::appendData( const unsigned short lData ) | ||
| 120 | { | ||
| 121 | return appendData( (const char *)&lData, sizeof(short) ); | ||
| 122 | } | ||
| 123 | |||
| 124 | bool FlexBuf::appendData( const unsigned long lData ) | ||
| 125 | { | ||
| 126 | return appendData( (const char *)&lData, sizeof(long) ); | ||
| 127 | } | ||
| 128 | |||
| 129 | bool FlexBuf::appendData( const unsigned int lData ) | ||
| 130 | { | ||
| 131 | return appendData( (const char *)&lData, sizeof(int) ); | ||
| 132 | } | ||
| 133 | |||
| 134 | bool FlexBuf::clearData() | ||
| 135 | { | ||
| 136 | nFirstChar = nLastChar = nFill = 0; | ||
| 137 | lpBuf[nLastChar] = '\0'; | ||
| 138 | |||
| 139 | return true; | ||
| 140 | } | ||
| 141 | |||
| 142 | const char *FlexBuf::getData() | ||
| 143 | { | ||
| 144 | return (lpBuf+nFirstChar); | ||
| 145 | } | ||
| 146 | |||
| 147 | int FlexBuf::getLength() | ||
| 148 | { | ||
| 149 | return nFill; | ||
| 150 | } | ||
| 151 | |||
| 152 | int FlexBuf::getCapacity() | ||
| 153 | { | ||
| 154 | return nSize; | ||
| 155 | } | ||
| 156 | |||
| 157 | bool FlexBuf::usedData( int nAmount ) | ||
| 158 | { | ||
| 159 | // Remove from the end if negative | ||
| 160 | if( nAmount < 0 ) | ||
| 161 | { | ||
| 162 | if( nFill+nAmount < 0 ) | ||
| 163 | { | ||
| 164 | nFill = nFirstChar = nLastChar = 0; | ||
| 165 | return true; | ||
| 166 | } | ||
| 167 | nLastChar += nAmount; | ||
| 168 | nFill += nAmount; | ||
| 169 | return true; | ||
| 170 | } | ||
| 171 | if( nAmount > nFill ) | ||
| 172 | { | ||
| 173 | nAmount = nSize; | ||
| 174 | // return false; | ||
| 175 | } | ||
| 176 | |||
| 177 | //nLastChar -= nAmount; | ||
| 178 | nFirstChar += nAmount; | ||
| 179 | nFill -= nAmount; | ||
| 180 | |||
| 181 | if( nFill == 0 ) | ||
| 182 | { | ||
| 183 | nFirstChar = nLastChar = 0; | ||
| 184 | } | ||
| 185 | |||
| 186 | //if( nLastChar > 0 ) | ||
| 187 | //{ | ||
| 188 | //memmove( lpBuf, &lpBuf[nAmount], nLastChar ); | ||
| 189 | //} | ||
| 190 | |||
| 191 | return true; | ||
| 192 | } | ||
| 193 | |||
| 194 | int FlexBuf::findChar( char cTarget ) | ||
| 195 | { | ||
| 196 | for( int j = nFirstChar; j < nLastChar; j++ ) | ||
| 197 | { | ||
| 198 | if( lpBuf[j] == cTarget ) | ||
| 199 | { | ||
| 200 | return j; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | return -1; | ||
| 205 | } | ||
| 206 | |||
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 @@ | |||
| 1 | /**\flexbuf.h | ||
| 2 | * Describes the FlexBuf class. | ||
| 3 | *@author Mike Buland | ||
| 4 | */ | ||
| 5 | |||
| 6 | #ifndef FLEXBUF_H | ||
| 7 | #define FLEXBUF_H | ||
| 8 | |||
| 9 | /** Stores any amount of data, but starts small, growing as necesarry. | ||
| 10 | * It is optimized to work with stream type situations, with data being | ||
| 11 | * added to the end while it is being taken from the begning. | ||
| 12 | *@todo Set this class up to auto-shrink back to a specified sized buffer each | ||
| 13 | * time it has shrunk below that for enough operations. | ||
| 14 | *@author Mike Buland | ||
| 15 | */ | ||
| 16 | class FlexBuf | ||
| 17 | { | ||
| 18 | public: | ||
| 19 | /** | ||
| 20 | * Construct a blank FlexBuf containing about 1k of buffer space. | ||
| 21 | */ | ||
| 22 | FlexBuf(); | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Clean up the FlexBuf, delete all buffers. | ||
| 26 | */ | ||
| 27 | ~FlexBuf(); | ||
| 28 | |||
| 29 | /** Appends a whole string of data to the buffer. The string | ||
| 30 | * must be null terminated. | ||
| 31 | *@param lpData The data to append to the buffer. | ||
| 32 | *@param nDSize The size of the data described by lpData. If this | ||
| 33 | * value is -1 lpData is treated as a null-terminated string. | ||
| 34 | *@returns True if no problems occured, false otherwise. | ||
| 35 | */ | ||
| 36 | bool appendData( const char *lpData, int nDSize=-1 ); | ||
| 37 | |||
| 38 | /** Appends a single character to the end of the buffer. | ||
| 39 | *@param lData The character to append to the buffer. | ||
| 40 | *@returns True if no problems occured, false otherwise. | ||
| 41 | */ | ||
| 42 | bool appendData( const char lData ); | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Append the short to the buffer. | ||
| 46 | *@param lData The short to add to the buffer queue. | ||
| 47 | *@returns True if everything is ok, false otherwise. | ||
| 48 | */ | ||
| 49 | bool appendData( const short lData ); | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Append the int to the buffer. | ||
| 53 | *@param lData The int to add to the buffer queue. | ||
| 54 | *@returns True if everything is ok, false otherwise. | ||
| 55 | */ | ||
| 56 | bool appendData( const int lData ); | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Append the long to the buffer. | ||
| 60 | *@param lData The long to add to the buffer queue. | ||
| 61 | *@returns True if everything is ok, false otherwise. | ||
| 62 | */ | ||
| 63 | bool appendData( const long lData ); | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Append the float to the buffer. | ||
| 67 | *@param lData The float to add to the buffer queue. | ||
| 68 | *@returns True if everything is ok, false otherwise. | ||
| 69 | */ | ||
| 70 | bool appendData( const float lData ); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Append the double to the buffer. | ||
| 74 | *@param lData The double to add to the buffer queue. | ||
| 75 | *@returns True if everything is ok, false otherwise. | ||
| 76 | */ | ||
| 77 | bool appendData( const double lData ); | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Append the unsigned char to the buffer. | ||
| 81 | *@param lData The unsigned char to add to the buffer queue. | ||
| 82 | *@returns True if everything is ok, false otherwise. | ||
| 83 | */ | ||
| 84 | bool appendData( const unsigned char lData ); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Append the unsigned short to the buffer. | ||
| 88 | *@param lData The unsigned short to add to the buffer queue. | ||
| 89 | *@returns True if everything is ok, false otherwise. | ||
| 90 | */ | ||
| 91 | bool appendData( const unsigned short lData ); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Append the unsigned int to the buffer. | ||
| 95 | *@param lData The unsigned int to add to the buffer queue. | ||
| 96 | *@returns True if everything is ok, false otherwise. | ||
| 97 | */ | ||
| 98 | bool appendData( const unsigned int lData ); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Append the unsigned long to the buffer. | ||
| 102 | *@param lData The unsigned long to add to the buffer queue. | ||
| 103 | *@returns True if everything is ok, false otherwise. | ||
| 104 | */ | ||
| 105 | bool appendData( const unsigned long lData ); | ||
| 106 | |||
| 107 | /** Removes all pending data from the buffer. | ||
| 108 | *@returns True if no problems occured, false otherwise. | ||
| 109 | */ | ||
| 110 | bool clearData(); | ||
| 111 | |||
| 112 | /** Gets a pointer to the internal buffer, at the begining of the current | ||
| 113 | * data stream. | ||
| 114 | *@returns A pointer to the internal data buffer. | ||
| 115 | */ | ||
| 116 | const char *getData(); | ||
| 117 | |||
| 118 | /** Gets the length of the current buffer (how much data is really in the | ||
| 119 | * buffer, not it's current capacity, for that check getCapacity) | ||
| 120 | *@returns The length of the current buffer. | ||
| 121 | */ | ||
| 122 | int getLength(); | ||
| 123 | |||
| 124 | /** Gets the current capacity of the FlexBuf. If the size nears this value | ||
| 125 | * then the entire buffer is resized to accomidate more data. | ||
| 126 | *@returns The current capacity of the FlexBuf. | ||
| 127 | */ | ||
| 128 | int getCapacity(); | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Removes nAmount bytes from the begning of the buffer. Actually, if | ||
| 132 | * nAmount happens to be negative it will remove tha absolute value of | ||
| 133 | * nValue bytes from the end of the buffer, like the old delData command. | ||
| 134 | *@param nAmount The number of bytes used. | ||
| 135 | *@returns True if everything was successful, false if there was an error. | ||
| 136 | */ | ||
| 137 | bool usedData( int nAmount ); | ||
| 138 | |||
| 139 | /** Finds the first instance of the given character in the buffer and | ||
| 140 | * returns an index to it. | ||
| 141 | *@param cTarget The character you're looking for. | ||
| 142 | *@returns The index of the first instance of the given character, or | ||
| 143 | * -1 if it just wasn't found. | ||
| 144 | */ | ||
| 145 | int findChar( char cTarget ); | ||
| 146 | |||
| 147 | private: | ||
| 148 | /** The raw storage location of the FlexBuf. */ | ||
| 149 | char *lpBuf; | ||
| 150 | /** The real size of the FlexBuf. */ | ||
| 151 | int nSize; | ||
| 152 | /** Where the last char is. */ | ||
| 153 | int nLastChar; | ||
| 154 | /** Where the first char is. */ | ||
| 155 | int nFirstChar; | ||
| 156 | /** The amount of real data in the FlexBuf. This is effectively nLastChar-nFirstChar. */ | ||
| 157 | int nFill; | ||
| 158 | }; | ||
| 159 | |||
| 160 | #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 @@ | |||
| 1 | #include "hashfunction.h" | ||
| 2 | |||
| 3 | HashFunction::HashFunction() | ||
| 4 | { | ||
| 5 | } | ||
| 6 | |||
| 7 | HashFunction::~HashFunction() | ||
| 8 | { | ||
| 9 | } | ||
| 10 | |||
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 @@ | |||
| 1 | #ifndef HASH_FUNCTION | ||
| 2 | #define HASH_FUNCTION | ||
| 3 | |||
| 4 | /** This represents the shell of a hash function. It must be aggregated in | ||
| 5 | * order to be used. Please read about it's two functions for specificatins | ||
| 6 | * relating to what values will be passed to them and what they should return | ||
| 7 | * for creating your own hash functions. | ||
| 8 | *@author Mike Buland. | ||
| 9 | */ | ||
| 10 | class HashFunction | ||
| 11 | { | ||
| 12 | public: | ||
| 13 | /** | ||
| 14 | * Standard Constructor. | ||
| 15 | */ | ||
| 16 | HashFunction(); | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Standard Deconstructor. | ||
| 20 | */ | ||
| 21 | virtual ~HashFunction(); | ||
| 22 | |||
| 23 | /** Hashes the value represnted by id. This must return a fairly unique | ||
| 24 | * number in the range of 0-2^32 (or whatever the size of an unsigned long | ||
| 25 | * is on your system) based on the id given. The faster the number changes | ||
| 26 | * the better in a general sence. The return value will be the index | ||
| 27 | * (after probing takes place) to the data assosiated with an id, so this | ||
| 28 | * function should always produce the same number for any given id. | ||
| 29 | *@param id The identifier to use to create a unique numerical identifier. | ||
| 30 | *@returns A mostly unique numerical identifier generated using the given | ||
| 31 | * id. | ||
| 32 | */ | ||
| 33 | virtual unsigned long int hash( const void *id ) = 0; | ||
| 34 | |||
| 35 | /** This function must compare two ids in the format that this hashfunction | ||
| 36 | * accepts. For example, if the hash function hashes strings it should | ||
| 37 | * probably { return strcmp( id1, id2 ) == 0 }. | ||
| 38 | *@param id1 One value to use in the comparison | ||
| 39 | *@param id2 Another value to use in the comparison | ||
| 40 | *@returns True if the two values match, otherwise false. | ||
| 41 | */ | ||
| 42 | virtual bool cmpIDs( const void *id1, const void *id2 ) = 0; | ||
| 43 | |||
| 44 | // virtual void *createPersistantID( const void *id ) = 0; | ||
| 45 | // virtual void destroyPersistantID( const void *id ) = 0; | ||
| 46 | }; | ||
| 47 | |||
| 48 | #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 @@ | |||
| 1 | #include <stdlib.h> | ||
| 2 | #include <string.h> | ||
| 3 | #include <ctype.h> | ||
| 4 | #include "hashfunctioncasestring.h" | ||
| 5 | |||
| 6 | HashFunctionCaseString::HashFunctionCaseString() | ||
| 7 | { | ||
| 8 | } | ||
| 9 | |||
| 10 | HashFunctionCaseString::~HashFunctionCaseString() | ||
| 11 | { | ||
| 12 | } | ||
| 13 | |||
| 14 | unsigned long int HashFunctionCaseString::hash( const void *id ) | ||
| 15 | { | ||
| 16 | const char *str = (const char *)id; | ||
| 17 | unsigned long int nPos = 0; | ||
| 18 | for( int j = 0; str[j] != '\0'; j++ ) | ||
| 19 | { | ||
| 20 | nPos = tolower(str[j]) + (nPos << 6) + (nPos << 16) - nPos; | ||
| 21 | // nPos += nPos<<16|(((unsigned long int)tolower(str[j]))<<((j*7)%24)); | ||
| 22 | } | ||
| 23 | return nPos; | ||
| 24 | } | ||
| 25 | |||
| 26 | bool HashFunctionCaseString::cmpIDs( const void *id1, const void *id2 ) | ||
| 27 | { | ||
| 28 | const char *str1 = (const char *)id1; | ||
| 29 | const char *str2 = (const char *)id2; | ||
| 30 | |||
| 31 | int j; | ||
| 32 | for( j = 0; str1[j] != '\0' && str2[j] != '\0'; j++ ) | ||
| 33 | { | ||
| 34 | if( tolower(str1[j]) != tolower(str2[j]) ) | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | return (str1[j]==str2[j]); | ||
| 38 | } | ||
| 39 | |||
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 @@ | |||
| 1 | #ifndef HASH_FUNCTION_CASE_STRING | ||
| 2 | #define HASH_FUNCTION_CASE_STRING | ||
| 3 | |||
| 4 | #include "hashfunction.h" | ||
| 5 | |||
| 6 | /** A hash function for string data. This hash function does strings, but is | ||
| 7 | * actually generalized to handle any binary stream of characters terminated | ||
| 8 | * by a null character. This is different than HashFunctionString in that | ||
| 9 | * this does comparisons without regaurd to case. | ||
| 10 | *@author Mike Buland. | ||
| 11 | */ | ||
| 12 | class HashFunctionCaseString : public HashFunction | ||
| 13 | { | ||
| 14 | public: | ||
| 15 | /** | ||
| 16 | * Standard Constructor. | ||
| 17 | */ | ||
| 18 | HashFunctionCaseString(); | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Standard Deconstructor. | ||
| 22 | */ | ||
| 23 | ~HashFunctionCaseString(); | ||
| 24 | unsigned long int hash( const void *id ); | ||
| 25 | bool cmpIDs( const void *id1, const void *id2 ); | ||
| 26 | }; | ||
| 27 | |||
| 28 | #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 @@ | |||
| 1 | #include "hashfunctionint.h" | ||
| 2 | |||
| 3 | HashFunctionInt::HashFunctionInt() | ||
| 4 | { | ||
| 5 | } | ||
| 6 | |||
| 7 | HashFunctionInt::~HashFunctionInt() | ||
| 8 | { | ||
| 9 | } | ||
| 10 | |||
| 11 | unsigned long int HashFunctionInt::hash( const void *id ) | ||
| 12 | { | ||
| 13 | return (unsigned long)(id); | ||
| 14 | } | ||
| 15 | |||
| 16 | bool HashFunctionInt::cmpIDs( const void *id1, const void *id2 ) | ||
| 17 | { | ||
| 18 | return (unsigned long)(id1) == (unsigned long)(id2); | ||
| 19 | } | ||
| 20 | |||
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 @@ | |||
| 1 | #ifndef HASH_FUNCTION_INT | ||
| 2 | #define HASH_FUNCTION_INT | ||
| 3 | |||
| 4 | #include "hashfunction.h" | ||
| 5 | |||
| 6 | /** A hash function for integer data. Really, this does almost nothing except | ||
| 7 | * ensure we're dealing with positive indicies. | ||
| 8 | *@author Mike Buland. | ||
| 9 | */ | ||
| 10 | class HashFunctionInt : public HashFunction | ||
| 11 | { | ||
| 12 | public: | ||
| 13 | /** | ||
| 14 | * Standard Constructor. | ||
| 15 | */ | ||
| 16 | HashFunctionInt(); | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Standard Deconstructor. | ||
| 20 | */ | ||
| 21 | ~HashFunctionInt(); | ||
| 22 | unsigned long int hash( const void *id ); | ||
| 23 | bool cmpIDs( const void *id1, const void *id2 ); | ||
| 24 | }; | ||
| 25 | |||
| 26 | #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 @@ | |||
| 1 | #include "hashfunctionstring.h" | ||
| 2 | |||
| 3 | HashFunctionString::HashFunctionString() | ||
| 4 | { | ||
| 5 | } | ||
| 6 | |||
| 7 | HashFunctionString::~HashFunctionString() | ||
| 8 | { | ||
| 9 | } | ||
| 10 | |||
| 11 | unsigned long int HashFunctionString::hash( const void *id ) | ||
| 12 | { | ||
| 13 | const char *str = (const char *)id; | ||
| 14 | unsigned long int nPos = 0; | ||
| 15 | for( int j = 0; str[j] != '\0'; j++ ) | ||
| 16 | { | ||
| 17 | nPos = str[j] + (nPos << 6) + (nPos << 16) - nPos; | ||
| 18 | // nPos += nPos<<16|(((unsigned long int)str[j])<<((j*7)%24)); | ||
| 19 | } | ||
| 20 | return nPos; | ||
| 21 | } | ||
| 22 | |||
| 23 | bool HashFunctionString::cmpIDs( const void *id1, const void *id2 ) | ||
| 24 | { | ||
| 25 | const char *str1 = (const char *)id1; | ||
| 26 | const char *str2 = (const char *)id2; | ||
| 27 | |||
| 28 | int j; | ||
| 29 | for( j = 0; str1[j] != '\0' && str2[j] != '\0'; j++ ) | ||
| 30 | { | ||
| 31 | if( str1[j] != str2[j] ) | ||
| 32 | return false; | ||
| 33 | } | ||
| 34 | return (str1[j]==str2[j]); | ||
| 35 | } | ||
| 36 | |||
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 @@ | |||
| 1 | #ifndef HASH_FUNCTION_STRING | ||
| 2 | #define HASH_FUNCTION_STRING | ||
| 3 | |||
| 4 | #include "hashfunction.h" | ||
| 5 | |||
| 6 | /** A hash function for string data. This hash function does strings, but is | ||
| 7 | * actually generalized to handle any binary stream of characters terminated | ||
| 8 | * by a null character. | ||
| 9 | *@author Mike Buland. | ||
| 10 | */ | ||
| 11 | class HashFunctionString : public HashFunction | ||
| 12 | { | ||
| 13 | public: | ||
| 14 | /** | ||
| 15 | * Standard Constructor. | ||
| 16 | */ | ||
| 17 | HashFunctionString(); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Standard Deconstructor. | ||
| 21 | */ | ||
| 22 | ~HashFunctionString(); | ||
| 23 | unsigned long int hash( const void *id ); | ||
| 24 | bool cmpIDs( const void *id1, const void *id2 ); | ||
| 25 | }; | ||
| 26 | |||
| 27 | #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 @@ | |||
| 1 | #include <string.h> | ||
| 2 | #include <stdio.h> | ||
| 3 | #include <math.h> | ||
| 4 | |||
| 5 | #include "hashtable.h" | ||
| 6 | |||
| 7 | HashTable::HashTable( HashFunction *hNewFunc, unsigned long int nInitSize, bool bAllowDupes ) | ||
| 8 | { | ||
| 9 | hFunc = hNewFunc; | ||
| 10 | nTableSize = nextPrime( nInitSize ); | ||
| 11 | aTable = new HashNode[nTableSize]; | ||
| 12 | //for( int j = 0; j < nTableSize; j++ ) if( aTable[j].id || aTable[j].data || aTable[j].bDeleted ) printf("Unclean entry\n"); | ||
| 13 | nSize = 0; | ||
| 14 | nFilled = 0; | ||
| 15 | this->bAllowDupes = bAllowDupes; | ||
| 16 | } | ||
| 17 | |||
| 18 | HashTable::~HashTable() | ||
| 19 | { | ||
| 20 | delete[] aTable; | ||
| 21 | delete hFunc; | ||
| 22 | } | ||
| 23 | |||
| 24 | void HashTable::set( int j, const void *newID, const void *newData ) | ||
| 25 | { | ||
| 26 | if( newData == NULL ) | ||
| 27 | { | ||
| 28 | printf("Inserting NULL data is indestinguishable from uninserted data!\n"); | ||
| 29 | } | ||
| 30 | aTable[j].id = newID; | ||
| 31 | aTable[j].data = newData; | ||
| 32 | } | ||
| 33 | |||
| 34 | bool HashTable::isFilled( int j ) | ||
| 35 | { | ||
| 36 | return (aTable[j].id != NULL)||(aTable[j].bDeleted); | ||
| 37 | } | ||
| 38 | |||
| 39 | bool HashTable::reHash( unsigned long int nNewSize ) | ||
| 40 | { | ||
| 41 | HashNode *aOldTable = aTable; | ||
| 42 | unsigned long int oldSize = nTableSize; | ||
| 43 | |||
| 44 | // If the table can still be used if we just get rid of deleted items, don't | ||
| 45 | // change the size of the table, otherwise, go ahead and use the number | ||
| 46 | // passed in. | ||
| 47 | if( nSize > nTableSize>>1 ) | ||
| 48 | { | ||
| 49 | nTableSize = nextPrime( nNewSize ); | ||
| 50 | } | ||
| 51 | |||
| 52 | aTable = newTable( nTableSize ); | ||
| 53 | //for( int j = 0; j < nTableSize; j++ ) if( aTable[j].id || aTable[j].data || aTable[j].bDeleted ) printf("Unclean entry\n"); | ||
| 54 | |||
| 55 | nSize = 0; | ||
| 56 | nFilled = 0; | ||
| 57 | |||
| 58 | for( unsigned long int j = 0; j < oldSize; j++ ) | ||
| 59 | { | ||
| 60 | if( aOldTable[j].id != NULL && aOldTable[j].bDeleted == false ) | ||
| 61 | { | ||
| 62 | insert( aOldTable[j].id, aOldTable[j].data ); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | delete[] aOldTable; | ||
| 67 | } | ||
| 68 | |||
| 69 | unsigned long int HashTable::probe( unsigned long int nStart, const void *id ) | ||
| 70 | { | ||
| 71 | int nHash = nStart; | ||
| 72 | nStart = nStart%nTableSize; | ||
| 73 | if( bAllowDupes == true ) | ||
| 74 | { | ||
| 75 | for( | ||
| 76 | unsigned long int j=0; | ||
| 77 | isFilled( nStart ) && j < 32; | ||
| 78 | nStart = (nStart+(1<<j))%nTableSize, j++ | ||
| 79 | ); | ||
| 80 | |||
| 81 | /** | ||
| 82 | * This is an ugly little hack. If the hash table is too full in allow- | ||
| 83 | * dups mode we have to fall back on a linear search, otherwise you can | ||
| 84 | * only get up to 32 entries with the same name. | ||
| 85 | */ | ||
| 86 | if( isFilled( nStart ) ) | ||
| 87 | { | ||
| 88 | int nOldStart = nStart; | ||
| 89 | for( | ||
| 90 | nStart++; | ||
| 91 | isFilled( nStart ) && nStart != nOldStart; | ||
| 92 | nStart = (nStart+1)%nTableSize | ||
| 93 | ); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | else | ||
| 97 | { | ||
| 98 | for( | ||
| 99 | unsigned long int j=0; | ||
| 100 | isFilled( nStart ) && j < 32; | ||
| 101 | nStart = (nStart+(1<<j))%nTableSize, j++ | ||
| 102 | ) | ||
| 103 | { | ||
| 104 | if( isFilled( nStart ) ) | ||
| 105 | { | ||
| 106 | if( hFunc->cmpIDs( aTable[nStart].id, id ) == true && | ||
| 107 | aTable[nStart].bDeleted == false ) | ||
| 108 | { | ||
| 109 | return nStart; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
| 114 | // This is our insurance, if the table is full, then go ahead and rehash, | ||
| 115 | // then try again. | ||
| 116 | if( isFilled( nStart ) ) | ||
| 117 | { | ||
| 118 | reHash( getCapacity()*2 ); | ||
| 119 | return probe( nHash, id ); | ||
| 120 | } | ||
| 121 | return nStart; | ||
| 122 | } | ||
| 123 | |||
| 124 | HashTable::HashNode *HashTable::newTable( unsigned long int nNewSize ) | ||
| 125 | { | ||
| 126 | return new HashNode[nNewSize]; | ||
| 127 | } | ||
| 128 | |||
| 129 | #ifdef HASH_DEBUG_VIS | ||
| 130 | void HashTable::printDebugLine( const char *exData ) | ||
| 131 | { | ||
| 132 | char *buf = new char[getCapacity()+3]; | ||
| 133 | int j; | ||
| 134 | buf[0] = '['; | ||
| 135 | for( j = 0; j < getCapacity(); j++ ) | ||
| 136 | { | ||
| 137 | buf[j+1] = (aTable[j].bDeleted)?('X'):((isFilled( j ))?('#'):('-')); | ||
| 138 | } | ||
| 139 | buf[j+1] = ']'; | ||
| 140 | buf[j+2] = '\0'; | ||
| 141 | printf("%s %s\n", buf, exData ); | ||
| 142 | delete[] buf; | ||
| 143 | } | ||
| 144 | #endif | ||
| 145 | |||
| 146 | bool HashTable::insert( const void *id, const void *data ) | ||
| 147 | { | ||
| 148 | unsigned long int nPos = probe( hFunc->hash( id ), id )%nTableSize; | ||
| 149 | |||
| 150 | if( bAllowDupes == true ) | ||
| 151 | { | ||
| 152 | if( aTable[nPos].id == NULL && aTable[nPos].bDeleted == false ) | ||
| 153 | { | ||
| 154 | set( nPos, id, data ); | ||
| 155 | #ifdef HASH_DEBUG_VIS | ||
| 156 | printDebugLine( (const char *)id ); | ||
| 157 | #endif | ||
| 158 | nSize++; | ||
| 159 | nFilled++; | ||
| 160 | return true; | ||
| 161 | } | ||
| 162 | else | ||
| 163 | { | ||
| 164 | return false; | ||
| 165 | } | ||
| 166 | } | ||
| 167 | else | ||
| 168 | { | ||
| 169 | if( aTable[nPos].id == NULL && aTable[nPos].bDeleted == false ) | ||
| 170 | { | ||
| 171 | set( nPos, id, data ); | ||
| 172 | #ifdef HASH_DEBUG_VIS | ||
| 173 | printDebugLine( (const char *)id ); | ||
| 174 | #endif | ||
| 175 | nSize++; | ||
| 176 | nFilled++; | ||
| 177 | return true; | ||
| 178 | } | ||
| 179 | else if( hFunc->cmpIDs( aTable[nPos].id, id ) == true ) | ||
| 180 | { | ||
| 181 | set( nPos, id, data ); | ||
| 182 | #ifdef HASH_DEBUG_VIS | ||
| 183 | printDebugLine( (const char *)id ); | ||
| 184 | #endif | ||
| 185 | return true; | ||
| 186 | } | ||
| 187 | else | ||
| 188 | { | ||
| 189 | return false; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | const void *HashTable::get( const void *id, unsigned long int nSkip ) | ||
| 195 | { | ||
| 196 | unsigned long int nPos = hFunc->hash( id )%nTableSize; | ||
| 197 | |||
| 198 | for( unsigned long int j=0; j < 32; nPos = (nPos+(1<<j))%nTableSize, j++ ) | ||
| 199 | { | ||
| 200 | if( !isFilled( nPos ) ) return NULL; | ||
| 201 | if( hFunc->cmpIDs( id, aTable[nPos].id ) && | ||
| 202 | aTable[nPos].bDeleted == false ) | ||
| 203 | { | ||
| 204 | if( nSkip == 0 ) | ||
| 205 | { | ||
| 206 | return aTable[nPos].data; | ||
| 207 | } | ||
| 208 | else | ||
| 209 | { | ||
| 210 | nSkip--; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | return NULL; | ||
| 216 | } | ||
| 217 | |||
| 218 | void *HashTable::getFirstItemPos() | ||
| 219 | { | ||
| 220 | HashPos *pos = new HashPos; | ||
| 221 | return pos; | ||
| 222 | } | ||
| 223 | |||
| 224 | const void *HashTable::getItemData( void *xPos ) | ||
| 225 | { | ||
| 226 | return aTable[((HashPos *)xPos)->nPos].data; | ||
| 227 | } | ||
| 228 | |||
| 229 | const void *HashTable::getItemID( void *xPos ) | ||
| 230 | { | ||
| 231 | return aTable[((HashPos *)xPos)->nPos].id; | ||
| 232 | } | ||
| 233 | |||
| 234 | void *HashTable::getNextItemPos( void *xPos ) | ||
| 235 | { | ||
| 236 | HashPos *pos = (HashPos *)xPos; | ||
| 237 | if( pos->bStarted == false ) | ||
| 238 | { | ||
| 239 | pos->bStarted = true; | ||
| 240 | pos->nPos = 0; | ||
| 241 | } | ||
| 242 | else | ||
| 243 | { | ||
| 244 | pos->nPos++; | ||
| 245 | } | ||
| 246 | if( pos->nPos < nTableSize ) | ||
| 247 | { | ||
| 248 | for( ; pos->nPos < nTableSize; pos->nPos++ ) | ||
| 249 | { | ||
| 250 | if( isFilled( pos->nPos ) && | ||
| 251 | aTable[pos->nPos].bDeleted == false ) | ||
| 252 | { | ||
| 253 | return xPos; | ||
| 254 | } | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | delete pos; | ||
| 259 | |||
| 260 | return NULL; | ||
| 261 | } | ||
| 262 | |||
| 263 | // Big-O sqrt(n) | ||
| 264 | // Change this to be erethpothynies table with a storage | ||
| 265 | // lookup later on. | ||
| 266 | bool HashTable::isPrime (int num) | ||
| 267 | { | ||
| 268 | if (num == 2) // the only even prime | ||
| 269 | return true; | ||
| 270 | else if (num % 2 == 0) // other even numbers are composite | ||
| 271 | return false; | ||
| 272 | else | ||
| 273 | { | ||
| 274 | //bool prime = true; | ||
| 275 | int divisor = 3; | ||
| 276 | int upperLimit = static_cast<int>(sqrt(num) + 1); | ||
| 277 | while (divisor <= upperLimit) | ||
| 278 | { | ||
| 279 | if (num % divisor == 0) | ||
| 280 | return false; | ||
| 281 | // prime = false; | ||
| 282 | divisor +=2; | ||
| 283 | } | ||
| 284 | return true; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | // Big-O n^(3/2) | ||
| 289 | int HashTable::nextPrime( int base ) | ||
| 290 | { | ||
| 291 | int nPrime; | ||
| 292 | for( nPrime = base; isPrime( nPrime ) == false; nPrime++ ); | ||
| 293 | return nPrime; | ||
| 294 | } | ||
| 295 | |||
| 296 | unsigned long int HashTable::getCapacity() | ||
| 297 | { | ||
| 298 | return nTableSize; | ||
| 299 | } | ||
| 300 | |||
| 301 | unsigned long int HashTable::getSize() | ||
| 302 | { | ||
| 303 | return nSize; | ||
| 304 | } | ||
| 305 | |||
| 306 | double HashTable::getLoad() | ||
| 307 | { | ||
| 308 | return (double)(nFilled)/(double)(nTableSize); | ||
| 309 | } | ||
| 310 | |||
| 311 | const void *HashTable::operator[](const void *id) | ||
| 312 | { | ||
| 313 | return get( id ); | ||
| 314 | } | ||
| 315 | |||
| 316 | bool HashTable::del( const void *id, int nSkip ) | ||
| 317 | { | ||
| 318 | unsigned long int nPos = hFunc->hash( id )%nTableSize; | ||
| 319 | |||
| 320 | for( unsigned long int j=0; j < 32; nPos = (nPos+(1<<j))%nTableSize, j++ ) | ||
| 321 | { | ||
| 322 | if( !isFilled( nPos ) ) return false; | ||
| 323 | if( hFunc->cmpIDs( id, aTable[nPos].id ) && | ||
| 324 | aTable[nPos].bDeleted == false ) | ||
| 325 | { | ||
| 326 | if( nSkip == 0 ) | ||
| 327 | { | ||
| 328 | aTable[nPos].bDeleted = true; | ||
| 329 | // aTable[nPos]. | ||
| 330 | nSize--; | ||
| 331 | #ifdef HASH_DEBUG_VIS | ||
| 332 | printDebugLine( (const char *)id ); | ||
| 333 | #endif | ||
| 334 | return true; | ||
| 335 | } | ||
| 336 | else | ||
| 337 | { | ||
| 338 | nSkip--; | ||
| 339 | } | ||
| 340 | } | ||
| 341 | } | ||
| 342 | |||
| 343 | return false; | ||
| 344 | } | ||
| 345 | |||
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 @@ | |||
| 1 | /**\hashtable.h | ||
| 2 | * Describes the HashFunction, HashFunctionString, and HashTable classes. It | ||
| 3 | * was just easier to put them all in one set of files. | ||
| 4 | *@author Mike Buland | ||
| 5 | */ | ||
| 6 | |||
| 7 | #ifndef HASH_TABLE_H | ||
| 8 | #define HASH_TABLE_H | ||
| 9 | |||
| 10 | //Uncomment this line to see a cool text-mode visualization of what's going on | ||
| 11 | //#define HASH_DEBUG_VIS 1 | ||
| 12 | |||
| 13 | #include <stdlib.h> | ||
| 14 | #include <string.h> | ||
| 15 | #include <ctype.h> | ||
| 16 | |||
| 17 | #include "hashfunction.h" | ||
| 18 | |||
| 19 | /** | ||
| 20 | * A simple yet flexable hash-table. This uses several tricks to help ensure | ||
| 21 | * that the table is always running at maximum efficiency. You no longer have | ||
| 22 | * to specify a "danger fill level" when more space is needed a rehash is | ||
| 23 | * automatically trigered. Deleting elements is fully supported, as well as | ||
| 24 | * duplicate elements. To work with and allow duplicates simple construct your | ||
| 25 | * HashTable the way you normally would, but when deleting or getting elements | ||
| 26 | * you can specify a skip value. This effectively allows you to treat elements | ||
| 27 | * with duplicate ID's as though they were in a zero-based array. The first | ||
| 28 | * element inserted with a given ID would be at skip zero, the next at skip 1 | ||
| 29 | * and so on. This allows you to quickly search for elements with duplicate | ||
| 30 | * names, just stop when you get a null for a skip number, i.e. | ||
| 31 | * <pre> | ||
| 32 | * for( int j = 0;; j++ ) | ||
| 33 | * { | ||
| 34 | * void *pData = hash.get( myID, j ); | ||
| 35 | * if( !pData ) break; | ||
| 36 | * // Do something interesting with pData | ||
| 37 | * } | ||
| 38 | * </pre> | ||
| 39 | * There are new features in this HashTable that also allow for memory saving | ||
| 40 | * when dealing with systems where many elements are being deleted from the | ||
| 41 | * table. In those cases the elements deleted cannot be simply deleted, instead | ||
| 42 | * they have to be marked as deleted and hidden from the user, but maintained in | ||
| 43 | * the table so that future hashing operations don't fail. When rehashing | ||
| 44 | * occurs all elements marked as deleted are quietly removed. In these cases, | ||
| 45 | * if the number of deleted items would free enough space in the table for the | ||
| 46 | * table to be used efficiently without resizing, it is left the same size and | ||
| 47 | * rehashing is performed effectively in place, allowing the deleted items to | ||
| 48 | * be removed. | ||
| 49 | * <br> | ||
| 50 | * For info on adding new hashing algorithms, please see the HashFunction class. | ||
| 51 | *@author Mike Buland | ||
| 52 | *@todo Fix probing for tables that allow duplicates, and delete an item, then | ||
| 53 | * insert an item with the same name. | ||
| 54 | */ | ||
| 55 | class HashTable | ||
| 56 | { | ||
| 57 | public: | ||
| 58 | /** Constructs a hash table. | ||
| 59 | *@param hNewFunc A pointer to a hashfunction class to use. If this is | ||
| 60 | * null the default general string type will be used. | ||
| 61 | *@param nInitSize The initial size of the hashtable. | ||
| 62 | *@param bAllowDupes Setting this value to true allows the system to | ||
| 63 | * insert more than one copy of any given key. This can be tricky, and | ||
| 64 | * will require you to use the nSkip parameter on the get function. | ||
| 65 | */ | ||
| 66 | HashTable( HashFunction *hNewFunc, unsigned long int nInitSize, bool bAllowDupes=false ); | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Destroys the hashtable, cleaning up all internal storage, but not stored | ||
| 70 | * elements. Also deletes the HashFunction passed in in the constructor. | ||
| 71 | */ | ||
| 72 | ~HashTable(); | ||
| 73 | |||
| 74 | /** Inserts an item into the hashtable. This function will trigger a | ||
| 75 | * rehash if adding another item would force the table's load factor over | ||
| 76 | * the danger level. | ||
| 77 | *@param id used to find the data later. | ||
| 78 | *@param data The data item to insert into the table with the identifier | ||
| 79 | * id | ||
| 80 | *@returns True if insertion was successfull, and false if it failed. | ||
| 81 | */ | ||
| 82 | bool insert( const void *id, const void *data ); | ||
| 83 | |||
| 84 | /** Gets an item in the hashtable based on the id of that item. If there | ||
| 85 | * is more than one item with the same id you can use the nSkip parameter | ||
| 86 | * to access all of them. | ||
| 87 | *@param id The id of the item you're trying to find. | ||
| 88 | *@param nSkip The number of items with that id to skip before returning | ||
| 89 | * with the requested item. | ||
| 90 | *@returns A pointer to the data stored at the given id. | ||
| 91 | */ | ||
| 92 | const void *get( const void *id, unsigned long int nSkip=0 ); | ||
| 93 | |||
| 94 | /** Gets the total capacity of the hashtable. This is actually the number | ||
| 95 | * of total positions available inside the hashtable at the moment. This | ||
| 96 | * will change when the hashtable's load exceeds the danger level. | ||
| 97 | * Please note that this is NOT the actual amount of space available. | ||
| 98 | * In reality you can only access about 45-50 percent of that space. | ||
| 99 | *@returns The total capacity. | ||
| 100 | */ | ||
| 101 | unsigned long int getCapacity(); | ||
| 102 | |||
| 103 | /** Gets the number of filled in items in the hash table. This is roughly | ||
| 104 | * equivelent to the getSize function assosiated with the Lists. | ||
| 105 | *@returns The number of filled in items in the hash table. | ||
| 106 | */ | ||
| 107 | unsigned long int getSize(); | ||
| 108 | |||
| 109 | /** Gets the load (percentage) of filled in items in the table. This is | ||
| 110 | * technically the size divided by the capacity, but is definately usefull | ||
| 111 | * since it's required to check if it's time to rehash. | ||
| 112 | *@returns The table load in the range 0.0 to 1.0 | ||
| 113 | */ | ||
| 114 | double getLoad(); | ||
| 115 | |||
| 116 | /** Sets up an xPos object for use indexing the items in the table. Call | ||
| 117 | * this first and follow the directions for getNextItemPos below to | ||
| 118 | * iterate through every item in the table, while avoiding the empty | ||
| 119 | * spaces. | ||
| 120 | *@returns A pointer to a xPos object telling the hashtable where to find | ||
| 121 | * the item you're looking at. | ||
| 122 | */ | ||
| 123 | void *getFirstItemPos(); | ||
| 124 | |||
| 125 | /** Get the item's data that is being pointed to by xPos. This is only | ||
| 126 | * valid after xPos was created using getFirstItemPos and getNextItemPos | ||
| 127 | * was called at least once. | ||
| 128 | *@param xPos supplied by getFirstItemPos. | ||
| 129 | *@returns The key value that was used to insert the data into the table. | ||
| 130 | */ | ||
| 131 | const void *getItemData( void *xPos ); | ||
| 132 | |||
| 133 | /** Get the item's ID that is being pointed to by xPos. This is only | ||
| 134 | * valid after xPos was created using getFirstItemPos and getNextItemPos | ||
| 135 | * was called at least once. | ||
| 136 | *@param xPos supplied by getFirstItemPos. | ||
| 137 | *@returns The key value that was used to insert the data into the table. | ||
| 138 | */ | ||
| 139 | const void *getItemID( void *xPos ); | ||
| 140 | |||
| 141 | /** Used for iterating through a hash table sequentially. This will | ||
| 142 | * update the xPos pointer to point to the next time, all ready to | ||
| 143 | * be accessed with getItemID and getItemData. This must be called at | ||
| 144 | * least once before xPos is meaningful, and will return a NULL when it | ||
| 145 | * has reached the last item. | ||
| 146 | *@param xPos This must be an object created by a call to the function | ||
| 147 | * getFirstItemPos, and is only meaningful to the internal routines. | ||
| 148 | * Aborting a call in the middle (not running to the end of the table) | ||
| 149 | * may result in a memory leak at the moment. | ||
| 150 | *@returns xPos if still iterating through the list, otherwise it will | ||
| 151 | * return NULL when the end has been reached and the xPos variable has | ||
| 152 | * been deleted. | ||
| 153 | */ | ||
| 154 | void *getNextItemPos( void *xPos ); | ||
| 155 | |||
| 156 | /** A helpful operator to make accessing items easier. Please note that | ||
| 157 | * this simply returns a pointer to the data stored internally, and cannot | ||
| 158 | * be used like the STL operator to store new data, use insert for that. | ||
| 159 | *@param id The identifier used to store the requested item. | ||
| 160 | *@returns The data value assosiated with the given id, or NULL if it | ||
| 161 | * wasn't found in the table. | ||
| 162 | */ | ||
| 163 | const void *operator[](const void *id); | ||
| 164 | |||
| 165 | /** | ||
| 166 | * Delete the specified item from the hashtable. This actually keeps the | ||
| 167 | * data and marks it deleted. For all intents and purposes to the user it | ||
| 168 | * is deleted, except that the space is still used until a rehash is forced. | ||
| 169 | * This means that in hashtables where elements are being inserted and | ||
| 170 | * deleted frequently you may run into a higher rate of expansion. | ||
| 171 | *@param id The ID to delete. | ||
| 172 | *@param nSkip The number of similar id's to skip before deleting in a | ||
| 173 | * hashtable that allows duplicates. | ||
| 174 | *@returns True if the element was found and deleted, false otherwise. | ||
| 175 | */ | ||
| 176 | bool del( const void *id, int nSkip=0 ); | ||
| 177 | |||
| 178 | private: | ||
| 179 | /** | ||
| 180 | * Contains info related to a position in the hashtable. Used for | ||
| 181 | * searching through hashtables one item at a time, in order. This class | ||
| 182 | * should never be created by anything but a HashTable, and should never | ||
| 183 | * be referenced directly. Instead the hashtable returns a void pointer, | ||
| 184 | * which is what should be passed back in next time you use a search | ||
| 185 | * function. Always finish a search, since the object is deleted at the | ||
| 186 | * end of the search. | ||
| 187 | *@author Mike Buland | ||
| 188 | */ | ||
| 189 | class HashPos | ||
| 190 | { | ||
| 191 | public: | ||
| 192 | /** Create a blank HashPos. */ | ||
| 193 | HashPos() { bStarted=false; nPos = 0; }; | ||
| 194 | /** Has the search been started? */ | ||
| 195 | bool bStarted; | ||
| 196 | /** The position (index) into the backend storage structure. */ | ||
| 197 | unsigned long int nPos; | ||
| 198 | }; | ||
| 199 | |||
| 200 | /** | ||
| 201 | * All data related to a single element in the hashtable. This should | ||
| 202 | * really only be used and manipulated by the HashTable itself. | ||
| 203 | *@author Mike Buland | ||
| 204 | */ | ||
| 205 | typedef struct HashNode | ||
| 206 | { | ||
| 207 | public: | ||
| 208 | /** Create a new, empty HashNode. */ | ||
| 209 | HashNode() { id = NULL; data = NULL; bDeleted = false; }; | ||
| 210 | /** A pointer to the original ID that was used to key the data. */ | ||
| 211 | const void *id; | ||
| 212 | /** A pointer to the data stored along with the above ID. */ | ||
| 213 | const void *data; | ||
| 214 | /** Weather or not this data should really...exist */ | ||
| 215 | bool bDeleted; | ||
| 216 | } HashNode; | ||
| 217 | |||
| 218 | private: | ||
| 219 | /** | ||
| 220 | * Just sets the values in the element to some friendly values. | ||
| 221 | *@param newID The new ID to store. | ||
| 222 | *@param newData The new Data to store. | ||
| 223 | */ | ||
| 224 | void set( int j, const void *newID, const void *newData ); | ||
| 225 | /** | ||
| 226 | * Tells you if the node is filled or not. | ||
| 227 | *@returns True=an ID has been stored here, False=no ID. | ||
| 228 | */ | ||
| 229 | bool isFilled( int j ); | ||
| 230 | /** | ||
| 231 | * This actually resizes, but since every resize requires a reHash to go | ||
| 232 | * along with it, that's the name. This actually creates a new buffer for | ||
| 233 | * all of the contained data and then pulls every old element that was in | ||
| 234 | * the old table out and performs the hashing placement calculations again. | ||
| 235 | * This function skips all data that was marked as deleted, so at this | ||
| 236 | * point it really will be. | ||
| 237 | *@param nNewSize The new size to set the table to while re-hashing. | ||
| 238 | *@returns True if the operation was successful, false otherwise. | ||
| 239 | */ | ||
| 240 | bool reHash( unsigned long int nNewSize ); | ||
| 241 | |||
| 242 | /** | ||
| 243 | * Helper function to allocate a new table. Really just does the memory | ||
| 244 | * allocation. | ||
| 245 | *@param nNewSize The size of the table to generate. | ||
| 246 | *@returns A new, blank array of HashNode objects the size you specified. | ||
| 247 | */ | ||
| 248 | HashNode *newTable( unsigned long int nNewSize ); | ||
| 249 | |||
| 250 | /** | ||
| 251 | * This function is used once an actual hash code is obtained. nStart is | ||
| 252 | * the given hash code, which is then wrapped to the size of the table. If | ||
| 253 | * there is data at that location, tests are performed to see if it's the | ||
| 254 | * right one. If it is, then it is returned, otherwise a series of further | ||
| 255 | * tests based on a 2^n search pattern is performed. The position of the | ||
| 256 | * requested data in the back-end storage is returned if found, otherwise | ||
| 257 | * another less useful value is returned... | ||
| 258 | *@param nStart The initial hashcode of the ID testing for. | ||
| 259 | *@param id A pointer to the id that is being searched for. | ||
| 260 | *@returns The real location of the data requested. | ||
| 261 | */ | ||
| 262 | unsigned long int probe( unsigned long int nStart, const void *id ); | ||
| 263 | |||
| 264 | /** | ||
| 265 | * Simple helper function to determine if a number is prime or not. | ||
| 266 | * This function runs in sqrt(n) time. | ||
| 267 | *@param num Number to test for prime-hood. | ||
| 268 | *@returns True if the number is prime, false otherwise. | ||
| 269 | */ | ||
| 270 | bool isPrime( int num ); | ||
| 271 | |||
| 272 | /** | ||
| 273 | * Given any number, this function finds the first number after it that is | ||
| 274 | * prime. Since this number is a multiple internally it's rare that the | ||
| 275 | * starting number would be prime. | ||
| 276 | *@param base The number to start the prime search on. | ||
| 277 | *@returns The first prime after the number given. | ||
| 278 | */ | ||
| 279 | int nextPrime( int base ); | ||
| 280 | |||
| 281 | #ifdef HASH_DEBUG_VIS | ||
| 282 | void printDebugLine( const char *exData ); | ||
| 283 | #endif | ||
| 284 | |||
| 285 | /** A pointer to the HashFunction subclass instance to use. */ | ||
| 286 | HashFunction *hFunc; | ||
| 287 | /** The complete array of HashNode objects to store data in. */ | ||
| 288 | HashNode *aTable; | ||
| 289 | /** The actual size of the table, not how many elements are in it. */ | ||
| 290 | unsigned long int nTableSize; | ||
| 291 | /** The number of elements that are in the table. */ | ||
| 292 | unsigned long int nSize; | ||
| 293 | /** The number of elements that are unavailable now. */ | ||
| 294 | unsigned long int nFilled; | ||
| 295 | /** Allow duplicate ID's in the table. */ | ||
| 296 | bool bAllowDupes; | ||
| 297 | }; | ||
| 298 | |||
| 299 | #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 @@ | |||
| 1 | #include <string.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include "http.h" | ||
| 4 | #include "hashfunctionstring.h" | ||
| 5 | |||
| 6 | Http::Http( Connection *pConnection ) : hReqHeader( new HashFunctionString(), 100 ) | ||
| 7 | { | ||
| 8 | pCon = pConnection; | ||
| 9 | nParseState = parseInit; | ||
| 10 | } | ||
| 11 | |||
| 12 | Http::~Http() | ||
| 13 | { | ||
| 14 | for( int j = 0; j < lStrings.getSize(); j++ ) | ||
| 15 | { | ||
| 16 | delete (std::string *)lStrings[j]; | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | bool Http::parseRequest() | ||
| 21 | { | ||
| 22 | for(;;) | ||
| 23 | { | ||
| 24 | pCon->readInput(); | ||
| 25 | switch( nParseState ) | ||
| 26 | { | ||
| 27 | case parseInit: | ||
| 28 | { | ||
| 29 | int nLen = pCon->scanInputFor( CR ); | ||
| 30 | if( nLen == -1 ) | ||
| 31 | { | ||
| 32 | return false; | ||
| 33 | } | ||
| 34 | else | ||
| 35 | { | ||
| 36 | nReqType = getRequestType( pCon->getInput() ); | ||
| 37 | pCon->usedInput( pCon->scanInputFor(' ')+1 ); | ||
| 38 | |||
| 39 | nLen = pCon->scanInputFor(' '); | ||
| 40 | sReqURI.append( pCon->getInput(), nLen ); | ||
| 41 | pCon->usedInput( nLen+1 ); | ||
| 42 | |||
| 43 | if( !strncmp( pCon->getInput(), "HTTP/", 5 ) ) | ||
| 44 | { | ||
| 45 | char mbuf[2]={'\0','\0'}; | ||
| 46 | unsigned char major, minor; | ||
| 47 | |||
| 48 | pCon->usedInput( 5 ); | ||
| 49 | mbuf[0] = pCon->getInput()[0]; | ||
| 50 | major = (unsigned char)atoi(mbuf); | ||
| 51 | mbuf[0] = pCon->getInput()[2]; | ||
| 52 | minor = (unsigned char)atoi(mbuf); | ||
| 53 | setRequestVersion( major, minor ); | ||
| 54 | if( checkRequestVer() ) | ||
| 55 | { | ||
| 56 | nParseState = parseHeader; | ||
| 57 | } | ||
| 58 | else | ||
| 59 | { | ||
| 60 | setResponseStatus( statusHTTPVersionNotSupported ); | ||
| 61 | printf("Verson not supported.\n"); | ||
| 62 | return true; | ||
| 63 | } | ||
| 64 | |||
| 65 | pCon->usedInput( 5 ); | ||
| 66 | } | ||
| 67 | else | ||
| 68 | { | ||
| 69 | setResponseStatus( statusBadRequest ); | ||
| 70 | } | ||
| 71 | |||
| 72 | //return false; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | break; | ||
| 76 | |||
| 77 | case parseHeader: | ||
| 78 | { | ||
| 79 | int nLen = pCon->scanInputFor( CR ); | ||
| 80 | if( nLen == -1 ) | ||
| 81 | { | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | else if( nLen == 0 ) | ||
| 85 | { | ||
| 86 | // We've got our double-newline, time for content. | ||
| 87 | pCon->usedInput( 2 ); | ||
| 88 | setResponseStatus( statusOK ); | ||
| 89 | return true; | ||
| 90 | } | ||
| 91 | else | ||
| 92 | { | ||
| 93 | nLen = pCon->scanInputFor(':'); | ||
| 94 | if( nLen == -1 ) | ||
| 95 | { | ||
| 96 | printf("No colon? what are you trying to pull?\n"); | ||
| 97 | } | ||
| 98 | else | ||
| 99 | { | ||
| 100 | std::string *pName = new std::string( pCon->getInput(), nLen ); | ||
| 101 | lStrings.append( pName ); | ||
| 102 | pCon->usedInput( nLen+1 ); | ||
| 103 | |||
| 104 | nLen = pCon->scanInputFor( CR ); | ||
| 105 | std::string *pValue = convSpaceString( pCon->getInput(), nLen ); | ||
| 106 | lStrings.append( pValue ); | ||
| 107 | pCon->usedInput( nLen+2 ); | ||
| 108 | |||
| 109 | hReqHeader.insert( | ||
| 110 | pName->c_str(), | ||
| 111 | pValue->c_str() | ||
| 112 | ); | ||
| 113 | |||
| 114 | printf("::%s = \"%s\"\n", | ||
| 115 | pName->c_str(), | ||
| 116 | pValue->c_str() | ||
| 117 | ); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | break; | ||
| 122 | |||
| 123 | case parseFinished: | ||
| 124 | break; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | bool Http::buildResponse( short nResponseCode, const char *sResponse ) | ||
| 130 | { | ||
| 131 | if( nResponseCode > 0 ) | ||
| 132 | { | ||
| 133 | nResStatus = nResponseCode; | ||
| 134 | } | ||
| 135 | |||
| 136 | if( sResponse == NULL ) | ||
| 137 | { | ||
| 138 | sResStatusStr = "uh yeah"; | ||
| 139 | } | ||
| 140 | else | ||
| 141 | { | ||
| 142 | sResStatusStr = sResponse; | ||
| 143 | } | ||
| 144 | |||
| 145 | time_t curTime; | ||
| 146 | time( &curTime ); | ||
| 147 | gmtime_r( &curTime, &tResTime ); | ||
| 148 | |||
| 149 | sServerStr = "YFHttp/0.0.1"; | ||
| 150 | bResPersistant = false; | ||
| 151 | |||
| 152 | //char buf[30]; | ||
| 153 | //strftime( buf, 30, "%a, %d %b %Y %H:%M:%S GMT", &tResponseTime ); | ||
| 154 | |||
| 155 | return true; | ||
| 156 | } | ||
| 157 | |||
| 158 | bool Http::sendResponse() | ||
| 159 | { | ||
| 160 | char buf[256]; | ||
| 161 | |||
| 162 | sprintf( buf, "HTTP/1.1 %d %s\r\n", nResStatus, sResStatusStr.c_str() ); | ||
| 163 | pCon->appendOutput( buf ); | ||
| 164 | |||
| 165 | strftime( buf, 256, "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", &tResTime ); | ||
| 166 | pCon->appendOutput( buf ); | ||
| 167 | |||
| 168 | sprintf( buf, "Server: %s\r\n", sServerStr.c_str() ); | ||
| 169 | pCon->appendOutput( buf ); | ||
| 170 | |||
| 171 | if( bResPersistant ) | ||
| 172 | { | ||
| 173 | } | ||
| 174 | else | ||
| 175 | { | ||
| 176 | pCon->appendOutput("Connection: close\r\n"); | ||
| 177 | } | ||
| 178 | |||
| 179 | sprintf( buf, "Content-Type: %s\r\n", sResMime.c_str() ); | ||
| 180 | pCon->appendOutput( buf ); | ||
| 181 | |||
| 182 | sprintf( buf, "Content-Length: %d\r\n", sResContent.size() ); | ||
| 183 | pCon->appendOutput( buf ); | ||
| 184 | |||
| 185 | pCon->appendOutput("\r\n"); | ||
| 186 | |||
| 187 | pCon->appendOutput( sResContent.c_str(), sResContent.size() ); | ||
| 188 | |||
| 189 | return true; | ||
| 190 | } | ||
| 191 | |||
| 192 | void Http::setResponsePersistant( bool bPersistant ) | ||
| 193 | { | ||
| 194 | bResPersistant = bPersistant; | ||
| 195 | } | ||
| 196 | |||
| 197 | void Http::setResponseContent( const char *sMime, const char *sContent, int nLen ) | ||
| 198 | { | ||
| 199 | sResMime = sMime; | ||
| 200 | sResContent.erase(); | ||
| 201 | sResContent.append( sContent, nLen ); | ||
| 202 | } | ||
| 203 | |||
| 204 | std::string *Http::convSpaceString( const char *sStr, int nLen ) | ||
| 205 | { | ||
| 206 | int nNewLen = 0; | ||
| 207 | bool bStart = true; | ||
| 208 | bool bSpace = false; | ||
| 209 | |||
| 210 | for( int j = 0; j < nLen; j++ ) | ||
| 211 | { | ||
| 212 | if( sStr[j] == ' ' || sStr[j] == '\t' ) | ||
| 213 | { | ||
| 214 | if( bStart ) | ||
| 215 | { | ||
| 216 | } | ||
| 217 | else if( bSpace == false ) | ||
| 218 | { | ||
| 219 | bSpace = true; | ||
| 220 | nNewLen++; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | else | ||
| 224 | { | ||
| 225 | bStart = false; | ||
| 226 | bSpace = false; | ||
| 227 | nNewLen++; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | if( bSpace ) | ||
| 231 | { | ||
| 232 | nNewLen--; | ||
| 233 | } | ||
| 234 | |||
| 235 | std::string *pSStr = new std::string; | ||
| 236 | //char *pStr = pSStr->c_str(); | ||
| 237 | nNewLen = 0; | ||
| 238 | bStart = true; | ||
| 239 | bSpace = false; | ||
| 240 | |||
| 241 | for( int j = 0; j < nLen; j++ ) | ||
| 242 | { | ||
| 243 | if( sStr[j] == ' ' || sStr[j] == '\t' ) | ||
| 244 | { | ||
| 245 | if( bStart ) | ||
| 246 | { | ||
| 247 | } | ||
| 248 | else if( bSpace == false ) | ||
| 249 | { | ||
| 250 | bSpace = true; | ||
| 251 | *pSStr += ' '; | ||
| 252 | //pStr[nNewLen++] = ' '; | ||
| 253 | } | ||
| 254 | } | ||
| 255 | else | ||
| 256 | { | ||
| 257 | bStart = false; | ||
| 258 | bSpace = false; | ||
| 259 | *pSStr += sStr[j]; | ||
| 260 | //pStr[nNewLen++] = sStr[j]; | ||
| 261 | } | ||
| 262 | } | ||
| 263 | if( bSpace == true ) | ||
| 264 | { | ||
| 265 | nNewLen--; | ||
| 266 | // pStr[nNewLen] = '\0'; | ||
| 267 | } | ||
| 268 | |||
| 269 | return pSStr; | ||
| 270 | } | ||
| 271 | |||
| 272 | const char *Http::getRequestURI() | ||
| 273 | { | ||
| 274 | return sReqURI.c_str(); | ||
| 275 | } | ||
| 276 | |||
| 277 | short Http::getRequestType( const char *sType ) | ||
| 278 | { | ||
| 279 | if( !strncmp( sType, "OPTIONS", 7 ) ) | ||
| 280 | { | ||
| 281 | return reqOptions; | ||
| 282 | } | ||
| 283 | else if( !strncmp( sType, "GET", 3 ) ) | ||
| 284 | { | ||
| 285 | return reqGet; | ||
| 286 | } | ||
| 287 | else if( !strncmp( sType, "HEAD", 4 ) ) | ||
| 288 | { | ||
| 289 | return reqHead; | ||
| 290 | } | ||
| 291 | else if( !strncmp( sType, "POST", 4 ) ) | ||
| 292 | { | ||
| 293 | return reqPost; | ||
| 294 | } | ||
| 295 | else if( !strncmp( sType, "PUT", 3 ) ) | ||
| 296 | { | ||
| 297 | return reqPut; | ||
| 298 | } | ||
| 299 | else if( !strncmp( sType, "DELETE", 6 ) ) | ||
| 300 | { | ||
| 301 | return reqDelete; | ||
| 302 | } | ||
| 303 | else if( !strncmp( sType, "TRACE", 5 ) ) | ||
| 304 | { | ||
| 305 | return reqTrace; | ||
| 306 | } | ||
| 307 | else if( !strncmp( sType, "CONNECT", 7 ) ) | ||
| 308 | { | ||
| 309 | return reqConnect; | ||
| 310 | } | ||
| 311 | else | ||
| 312 | { | ||
| 313 | printf(" Uh oh, extension!\n"); | ||
| 314 | return reqExtension; | ||
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | const char *Http::getRequestType( short nType ) | ||
| 319 | { | ||
| 320 | switch( nType ) | ||
| 321 | { | ||
| 322 | case reqOptions: return "OPTIONS"; | ||
| 323 | case reqGet: return "GET"; | ||
| 324 | case reqHead: return "HEAD"; | ||
| 325 | case reqPost: return "POST"; | ||
| 326 | case reqPut: return "PUT"; | ||
| 327 | case reqDelete: return "DELETE"; | ||
| 328 | case reqTrace: return "TRACE"; | ||
| 329 | case reqConnect: return "CONNECT"; | ||
| 330 | case reqExtension: return "EXTENSION"; | ||
| 331 | default: return "INVALID VALUE"; | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | short Http::getRequestType() | ||
| 336 | { | ||
| 337 | return nReqType; | ||
| 338 | } | ||
| 339 | |||
| 340 | const char *Http::getRequestTypeStr() | ||
| 341 | { | ||
| 342 | return getRequestType( nReqType ); | ||
| 343 | } | ||
| 344 | |||
| 345 | void Http::setResponseStatus( short nStatus ) | ||
| 346 | { | ||
| 347 | nResStatus = nStatus; | ||
| 348 | } | ||
| 349 | |||
| 350 | void Http::setRequestVersion( unsigned char nMajor, unsigned char nMinor ) | ||
| 351 | { | ||
| 352 | cReqVersion = (nMajor<<4)|nMinor; | ||
| 353 | } | ||
| 354 | |||
| 355 | unsigned char Http::getRequestMinorVer() | ||
| 356 | { | ||
| 357 | return cReqVersion&0x0F; | ||
| 358 | } | ||
| 359 | |||
| 360 | unsigned char Http::getRequestMajorVer() | ||
| 361 | { | ||
| 362 | return cReqVersion>>4; | ||
| 363 | } | ||
| 364 | |||
| 365 | bool Http::checkRequestVer() | ||
| 366 | { | ||
| 367 | if( cReqVersion == HTTP11 ) | ||
| 368 | return true; | ||
| 369 | return false; | ||
| 370 | } | ||
| 371 | |||
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 @@ | |||
| 1 | /**\file http.h | ||
| 2 | * Describe a Hyper Text Transfer Protocol processor. This class will allow | ||
| 3 | * any program to act as either an HTTP server, client, or both. It contains | ||
| 4 | * a number of additional helpers and subclasses. | ||
| 5 | *@author Mike Buland | ||
| 6 | */ | ||
| 7 | |||
| 8 | #ifndef HTTP_H | ||
| 9 | #define HTTP_H | ||
| 10 | |||
| 11 | #include <iostream> | ||
| 12 | #include "connection.h" | ||
| 13 | #include "linkedlist.h" | ||
| 14 | #include "hashtable.h" | ||
| 15 | |||
| 16 | #define CR '\r' /**< The ASCII value of a Carrage Return */ | ||
| 17 | #define LF '\n' /**< The ASCII value of a Line Feed */ | ||
| 18 | #define CRLF CR LF /**< Combo of CR+LF for use in http */ | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Macro to create combined http version codes. This just makes processing a | ||
| 22 | * little bit faster for the most part. | ||
| 23 | *@param maj Major version number, between 0 and 15 | ||
| 24 | *@param min Minor version number, between 0 and 15 | ||
| 25 | *@returns A one byte combined version number suitable for use in switches. | ||
| 26 | */ | ||
| 27 | #define HTTPVER( maj, min ) ((maj<<4)|(min)) | ||
| 28 | |||
| 29 | #define HTTP10 HTTPVER( 1, 0 ) /**< Combined version code for http 1.0 */ | ||
| 30 | #define HTTP11 HTTPVER( 1, 1 ) /**< Combined version code for http 1.1 */ | ||
| 31 | |||
| 32 | /** | ||
| 33 | * This is the master HTTP processing class. One instance handles one | ||
| 34 | * transaction, in the future a different mechanism may be thought up, but for | ||
| 35 | * now this means that you must create multiple objects to handle a single | ||
| 36 | * connection that contains multiple requests. | ||
| 37 | * In the constructor the Http class is given a connection object. This object | ||
| 38 | * should already be initialized and connected to whatever socket it wants to | ||
| 39 | * be sending and receiving data to and from. Once that's done you can call | ||
| 40 | * parseRequest if you're acting as a server, or a variety of buildRequest | ||
| 41 | * functions to create and send a request if you're a client. | ||
| 42 | * Please note that this class does not provide any HTTP or extended format | ||
| 43 | * processing systems, but will allow for mime types tables to be registered. | ||
| 44 | *@author Mike Buland | ||
| 45 | */ | ||
| 46 | class Http | ||
| 47 | { | ||
| 48 | public: | ||
| 49 | /** | ||
| 50 | * Create an Http object tied to an existing connection object. | ||
| 51 | *@param pConnection The live connection object to deal with. | ||
| 52 | */ | ||
| 53 | Http( Connection *pConnection ); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Standard Deconstructor. | ||
| 57 | */ | ||
| 58 | ~Http(); | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Perform all parsing needed to figure out what an HTTP client wants from | ||
| 62 | * us. This will setup a number of properties in the Http object itself | ||
| 63 | * and has the possibility of setting one or more response states initially. | ||
| 64 | * These states should be checked for immediately after parsing to see if | ||
| 65 | * an appropriate error message should be generated. These errors can | ||
| 66 | * include issues with protocol, data formats, or unknown versions of the | ||
| 67 | * protocol. | ||
| 68 | *@returns True means that all processing is finished, false means that | ||
| 69 | * the parseRequest function should be called again when more data is | ||
| 70 | * ready. A return value of true does not indicate success, only that | ||
| 71 | * processing is finished, the getResponseStatus function should be called | ||
| 72 | * to see what status was set in the parse routine. A 200 indicates that | ||
| 73 | * as far as the parser is concerned, everything when smoothly. Otherwise | ||
| 74 | * it's your responsibility to build the appropriate error response body | ||
| 75 | * (like an html file) and send it as the response. | ||
| 76 | */ | ||
| 77 | bool parseRequest(); | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Get a request type's internal Http object id based on the string | ||
| 81 | * representation. These can be any HTTP/1.1 standard request type. | ||
| 82 | *@param sType The string that should be checked for type. This is in all | ||
| 83 | * caps, just like if it came from the HTTP client, which is most often | ||
| 84 | * the case. | ||
| 85 | *@returns The numerical ID of the given request type. Please note that | ||
| 86 | * HTTP/1.1 standard specifies that any string is valid here as long as | ||
| 87 | * the non-basic string is a request type understood by the serving | ||
| 88 | * software. This means that anything that is non-standard will return | ||
| 89 | * a type reqExtension and not an error. This is not a mistake. | ||
| 90 | */ | ||
| 91 | short getRequestType( const char *sType ); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Get the string representation of an Http object request type integer ID. | ||
| 95 | * This is used mainly for debugging to be sure the system has what we | ||
| 96 | * think it has. | ||
| 97 | *@param nType The integer ID of the request type to process. | ||
| 98 | *@returns The HTTP/1.1 string representation of that Http object ID code. | ||
| 99 | */ | ||
| 100 | const char *getRequestType( short nType ); | ||
| 101 | |||
| 102 | /** | ||
| 103 | * Returns the Http object request type ID code that is stored in the | ||
| 104 | * object by either the parseRequest function or use of the buildRequest | ||
| 105 | * functions. | ||
| 106 | *@returns The ID of the request type stored in the object. | ||
| 107 | */ | ||
| 108 | short getRequestType(); | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Same as getRequestType, only returns the string representation. | ||
| 112 | *@returns The string representation of the request type ID stored in the | ||
| 113 | * object. | ||
| 114 | */ | ||
| 115 | const char *getRequestTypeStr(); | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Sets the version of the request used by the system. This will be used | ||
| 119 | * by parse request, but is also part of the buildRequest tool functions. | ||
| 120 | *@param nMajor The major version number. | ||
| 121 | *@param nMinor The minor version number. | ||
| 122 | */ | ||
| 123 | void setRequestVersion( unsigned char nMajor, unsigned char nMinor ); | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Gets the major version number of the protocol used/to be used in this | ||
| 127 | * request. | ||
| 128 | *@returns The major version number of the request protocol. | ||
| 129 | */ | ||
| 130 | unsigned char getRequestMinorVer(); | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Gets the minor version number of the protocol used/to be used in this | ||
| 134 | * request. | ||
| 135 | *@returns The minor version number of the request protocol. | ||
| 136 | */ | ||
| 137 | unsigned char getRequestMajorVer(); | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Checks the stored request version against an internal table of supported | ||
| 141 | * protocol versions. | ||
| 142 | *@returns True if the protocol version is supported, false otherwise. | ||
| 143 | */ | ||
| 144 | bool checkRequestVer(); | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Converts an arbitrary string to a new string object with space saving | ||
| 148 | * operations performed ala the HTTP/1.1 specs. All leading and trailing | ||
| 149 | * whitespace is stripped, and all whitespace within the string is reduced | ||
| 150 | * to a single space char. | ||
| 151 | *@param sStr A pointer to the string data to process. | ||
| 152 | *@param nLen The length of the string to process. Since this function is | ||
| 153 | * often called on stream data, there is no null terminator where we need | ||
| 154 | * one. This is here for convinience so the data doesn't need to be hacked | ||
| 155 | * up or moved to an intermediate buffer. | ||
| 156 | *@returns A new string that may well be shorter than the original but that | ||
| 157 | * will have the same value as far as the HTTP/1.1 specs are concerned. | ||
| 158 | */ | ||
| 159 | std::string *convSpaceString( const char *sStr, int nLen ); | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Gets a string pointer to the URI that was/is being requested. This can | ||
| 163 | * be any RFC standard URI, with or without protocol and domain. | ||
| 164 | *@returns A pointer to the URI that was/is being requested. | ||
| 165 | */ | ||
| 166 | const char *getRequestURI(); | ||
| 167 | |||
| 168 | /** | ||
| 169 | * Set a new response status. This status can be anything that the HTTP | ||
| 170 | * specs allow. Other values are allowed as well, but beware, not all | ||
| 171 | * servers/clients will accept values that are not in the tables in this | ||
| 172 | * class. | ||
| 173 | *@param nStatus The status to set. | ||
| 174 | */ | ||
| 175 | void setResponseStatus( short nStatus ); | ||
| 176 | |||
| 177 | bool buildResponse( short nResponseCode=-1, const char *sResponse=NULL ); | ||
| 178 | void setResponseContent( const char *sMime, const char *sContent, int nLen ); | ||
| 179 | void setResponsePersistant( bool bPersistant ); | ||
| 180 | bool sendResponse(); | ||
| 181 | |||
| 182 | enum | ||
| 183 | { | ||
| 184 | reqOptions, | ||
| 185 | reqGet, | ||
| 186 | reqHead, | ||
| 187 | reqPost, | ||
| 188 | reqPut, | ||
| 189 | reqDelete, | ||
| 190 | reqTrace, | ||
| 191 | reqConnect, | ||
| 192 | reqExtension | ||
| 193 | }; | ||
| 194 | |||
| 195 | enum | ||
| 196 | { | ||
| 197 | statusContinue = 100, | ||
| 198 | statusSwitchProto = 101, | ||
| 199 | |||
| 200 | statusOK = 200, | ||
| 201 | statusCreated = 201, | ||
| 202 | statusAccepted = 202, | ||
| 203 | statusNonAuthInfo = 203, | ||
| 204 | statusNoContent = 204, | ||
| 205 | statusResetContent = 205, | ||
| 206 | statusPartialContent = 206, | ||
| 207 | |||
| 208 | statusMultiChoices = 300, | ||
| 209 | statusMovedPermanently = 301, | ||
| 210 | statusFound = 302, | ||
| 211 | statusSeeOther = 303, | ||
| 212 | statusNotModified = 304, | ||
| 213 | statusUseProxy = 305, | ||
| 214 | statusUnused = 306, | ||
| 215 | statusTempRedirect = 307, | ||
| 216 | |||
| 217 | statusBadRequest = 400, | ||
| 218 | statusUnauthorized = 401, | ||
| 219 | statusPaymentRequired = 402, | ||
| 220 | statusForbidden = 403, | ||
| 221 | statusNotFound = 404, | ||
| 222 | statusMethodNotAllowed = 405, | ||
| 223 | statusNotAcceptable = 406, | ||
| 224 | statusProxyAuthRequired = 407, | ||
| 225 | statusRequestTimeout = 408, | ||
| 226 | statusConflict = 409, | ||
| 227 | statusGone = 410, | ||
| 228 | statusLengthRequired = 411, | ||
| 229 | statusPreconditionFailed = 412, | ||
| 230 | statusRequestEntityTooLarge = 413, | ||
| 231 | statusRequestURITooLong = 414, | ||
| 232 | statusUnsupportedMediaType = 415, | ||
| 233 | statusRequestedRangeNotSatisfiable = 416, | ||
| 234 | statusExpectationFailed = 417, | ||
| 235 | |||
| 236 | statusInternalServerError = 500, | ||
| 237 | statusNotImplemented = 501, | ||
| 238 | statusBadGateway = 502, | ||
| 239 | statusServiceUnavailable = 503, | ||
| 240 | statusGatewayTimeout = 504, | ||
| 241 | statusHTTPVersionNotSupported = 505 | ||
| 242 | }; | ||
| 243 | |||
| 244 | private: | ||
| 245 | Connection *pCon; | ||
| 246 | unsigned char nParseState; | ||
| 247 | |||
| 248 | short nReqType; | ||
| 249 | std::string *pReqStr; | ||
| 250 | std::string sReqURI; | ||
| 251 | unsigned char cReqVersion; | ||
| 252 | HashTable hReqHeader; | ||
| 253 | LinkedList lStrings; | ||
| 254 | |||
| 255 | std::string sServerStr; | ||
| 256 | std::string sResMime; | ||
| 257 | std::string sResContent; | ||
| 258 | std::string sResStatusStr; | ||
| 259 | bool bResPersistant; | ||
| 260 | struct tm tResTime; | ||
| 261 | short nResStatus; | ||
| 262 | |||
| 263 | enum | ||
| 264 | { | ||
| 265 | parseInit, | ||
| 266 | parseHeader, | ||
| 267 | parseFinished | ||
| 268 | }; | ||
| 269 | }; | ||
| 270 | |||
| 271 | #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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | linkedlist.cpp - description | ||
| 3 | ------------------- | ||
| 4 | begin : Sun Oct 19 2003 | ||
| 5 | copyright : (C) 2003 by Mike Buland | ||
| 6 | email : eichlan@yf-soft.com | ||
| 7 | ***************************************************************************/ | ||
| 8 | |||
| 9 | /*************************************************************************** | ||
| 10 | * * | ||
| 11 | * This program is free software; you can redistribute it and/or modify * | ||
| 12 | * it under the terms of the GNU General Public License as published by * | ||
| 13 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 14 | * (at your option) any later version. * | ||
| 15 | * * | ||
| 16 | ***************************************************************************/ | ||
| 17 | |||
| 18 | #include "linkedlist.h" | ||
| 19 | |||
| 20 | LinkedList::LinkedList( ) | ||
| 21 | { | ||
| 22 | pBase = NULL; | ||
| 23 | pTop = NULL; | ||
| 24 | pLast = NULL; | ||
| 25 | nSize = 0; | ||
| 26 | nLast = -1; | ||
| 27 | } | ||
| 28 | |||
| 29 | LinkedList::~LinkedList( ) | ||
| 30 | { | ||
| 31 | /* | ||
| 32 | Link *pCur = pBase; | ||
| 33 | while( pCur ) | ||
| 34 | { | ||
| 35 | Link *pLast = pCur; | ||
| 36 | pCur = pCur->pNext; | ||
| 37 | delete pLast; | ||
| 38 | } | ||
| 39 | */ | ||
| 40 | empty(); | ||
| 41 | } | ||
| 42 | |||
| 43 | void *LinkedList::getAt( int index ) | ||
| 44 | { | ||
| 45 | if( index < 0 || index >= nSize ) | ||
| 46 | return NULL; | ||
| 47 | |||
| 48 | return getPtrTo( index )->pData; | ||
| 49 | } | ||
| 50 | |||
| 51 | void LinkedList::append( void *data ) | ||
| 52 | { | ||
| 53 | if( pBase == NULL ) | ||
| 54 | { | ||
| 55 | pBase = new Link( data ); | ||
| 56 | pTop = pBase; | ||
| 57 | nSize++; | ||
| 58 | } | ||
| 59 | else | ||
| 60 | { | ||
| 61 | pTop->pNext = new Link( data ); | ||
| 62 | pTop = pTop->pNext; | ||
| 63 | nSize++; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | void LinkedList::insertBefore( void *data, int pos ) | ||
| 68 | { | ||
| 69 | if( pos < 0 || pos > nSize ) | ||
| 70 | return; | ||
| 71 | |||
| 72 | if( pos == 0 ) | ||
| 73 | { | ||
| 74 | Link *pTmp = new Link( data, pBase ); | ||
| 75 | if( pBase == NULL ) | ||
| 76 | { | ||
| 77 | pTop = pTmp; | ||
| 78 | } | ||
| 79 | pBase = pTmp; | ||
| 80 | if( nLast >= 0 ) nLast++; | ||
| 81 | nSize++; | ||
| 82 | } | ||
| 83 | else | ||
| 84 | { | ||
| 85 | Link *pCur; | ||
| 86 | if( (pCur = getPtrTo( pos-1 )) == NULL ) | ||
| 87 | { | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | Link *pNew = new Link( data, pCur->pNext ); | ||
| 91 | pCur->pNext = pNew; | ||
| 92 | if( pNew->pNext == NULL ) | ||
| 93 | { | ||
| 94 | pTop = pNew; | ||
| 95 | } | ||
| 96 | if( nLast >= pos ) nLast++; | ||
| 97 | nSize++; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | int LinkedList::getSize( ) | ||
| 102 | { | ||
| 103 | return nSize; | ||
| 104 | } | ||
| 105 | |||
| 106 | bool LinkedList::isEmpty( ) | ||
| 107 | { | ||
| 108 | if( nSize == 0 ) | ||
| 109 | return true; | ||
| 110 | return false; | ||
| 111 | } | ||
| 112 | |||
| 113 | void LinkedList::deleteAt( int index ) | ||
| 114 | { | ||
| 115 | if( index >= nSize || | ||
| 116 | pBase == NULL ) | ||
| 117 | return; | ||
| 118 | |||
| 119 | if( index == 0 ) | ||
| 120 | { | ||
| 121 | Link *pTmp = pBase->pNext; | ||
| 122 | delete pBase; | ||
| 123 | pBase = pTmp; | ||
| 124 | if( nLast >= 0 ) nLast--; | ||
| 125 | nSize--; | ||
| 126 | if( pBase == NULL ) | ||
| 127 | { | ||
| 128 | pTop = NULL; | ||
| 129 | } | ||
| 130 | else if( pBase->pNext == NULL ) | ||
| 131 | { | ||
| 132 | pTop = pBase; | ||
| 133 | } | ||
| 134 | } | ||
| 135 | else | ||
| 136 | { | ||
| 137 | Link *pCur = getPtrTo( index-1 ); | ||
| 138 | if( pCur->pNext == pTop ) | ||
| 139 | { | ||
| 140 | pTop = pCur; | ||
| 141 | } | ||
| 142 | Link *pTmp; | ||
| 143 | if( pCur->pNext == NULL ) | ||
| 144 | { | ||
| 145 | pTmp = NULL; | ||
| 146 | } | ||
| 147 | else | ||
| 148 | { | ||
| 149 | pTmp = pCur->pNext->pNext; | ||
| 150 | } | ||
| 151 | delete pCur->pNext; | ||
| 152 | pCur->pNext = pTmp; | ||
| 153 | if( nLast == index ) nLast = -1; | ||
| 154 | else if( index < nLast ) nLast--; | ||
| 155 | nSize--; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | void LinkedList::empty() | ||
| 160 | { | ||
| 161 | while( nSize > 0 ) | ||
| 162 | { | ||
| 163 | deleteAt( 0 ); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | void LinkedList::setSize( int newSize ) | ||
| 168 | { | ||
| 169 | if( newSize < nSize ) | ||
| 170 | { | ||
| 171 | // Delete items off of the end of the list. | ||
| 172 | while( nSize > newSize ) | ||
| 173 | { | ||
| 174 | deleteAt( nSize-1 ); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | else | ||
| 178 | { | ||
| 179 | // Add null items to the end of the list. | ||
| 180 | while( nSize < newSize ) | ||
| 181 | { | ||
| 182 | append( NULL ); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | void LinkedList::setAt( int index, void *data ) | ||
| 188 | { | ||
| 189 | if( index >= nSize || index < 0 ) | ||
| 190 | return; | ||
| 191 | |||
| 192 | getPtrTo( index )->pData = data; | ||
| 193 | } | ||
| 194 | |||
| 195 | LinkedList::Link *LinkedList::getPtrTo( int index ) | ||
| 196 | { | ||
| 197 | if( index < 0 || index >= nSize ) | ||
| 198 | return NULL; | ||
| 199 | if( index == nLast ) | ||
| 200 | { | ||
| 201 | return pLast; | ||
| 202 | } | ||
| 203 | if( index == 0 ) | ||
| 204 | { | ||
| 205 | pLast = pBase; | ||
| 206 | nLast = 0; | ||
| 207 | return pBase; | ||
| 208 | } | ||
| 209 | else | ||
| 210 | { | ||
| 211 | Link *pCur = pBase; | ||
| 212 | int nCur = 0; | ||
| 213 | if( nLast < index && nLast >= 0 ) | ||
| 214 | { | ||
| 215 | pCur = pLast; | ||
| 216 | nCur = nLast; | ||
| 217 | } | ||
| 218 | while( nCur != index ) | ||
| 219 | { | ||
| 220 | pCur = pCur->pNext; | ||
| 221 | nCur++; | ||
| 222 | } | ||
| 223 | nLast = index; | ||
| 224 | pLast = pCur; | ||
| 225 | return pCur; | ||
| 226 | } | ||
| 227 | } | ||
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 @@ | |||
| 1 | /**@file | ||
| 2 | * Describes the LinkedList implementation of the List ADT. | ||
| 3 | *@author Mike Buland | ||
| 4 | */ | ||
| 5 | |||
| 6 | #ifndef LINKEDLIST_H | ||
| 7 | #define LINKEDLIST_H | ||
| 8 | |||
| 9 | #include <stdio.h> | ||
| 10 | #include "list.h" | ||
| 11 | |||
| 12 | /** A linked-item implementation of the List ADT. Since the data is linked | ||
| 13 | * sequentially this is a great choice for lists that will grow and shrink | ||
| 14 | * a lot, but don't require as much random access. This implementation | ||
| 15 | * includes optomizations that make iterating through data, and appending | ||
| 16 | * items to the list take O(1) time. | ||
| 17 | *@author Mike Buland | ||
| 18 | */ | ||
| 19 | class LinkedList : public List | ||
| 20 | { | ||
| 21 | public: | ||
| 22 | /** | ||
| 23 | * Construct a blank LinkedList. | ||
| 24 | */ | ||
| 25 | LinkedList(); | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Delete all list data, but do not delete any of the contained elements. | ||
| 29 | */ | ||
| 30 | ~LinkedList(); | ||
| 31 | |||
| 32 | void *getAt( int nIndex ); | ||
| 33 | void append( void *pData ); | ||
| 34 | void insertBefore( void *pData, int nPos = 0 ); | ||
| 35 | int getSize( ); | ||
| 36 | bool isEmpty( ); | ||
| 37 | void deleteAt( int nIndex ); | ||
| 38 | void empty(); | ||
| 39 | void setSize( int nNewSize ); | ||
| 40 | void setAt( int nIndex, void *pData ); | ||
| 41 | |||
| 42 | private: | ||
| 43 | /** | ||
| 44 | * A link in the linked list. | ||
| 45 | */ | ||
| 46 | class Link | ||
| 47 | { | ||
| 48 | public: | ||
| 49 | /** | ||
| 50 | * Construct an empty link. | ||
| 51 | */ | ||
| 52 | Link() | ||
| 53 | { | ||
| 54 | pData = NULL; | ||
| 55 | pNext = NULL; | ||
| 56 | } | ||
| 57 | /** | ||
| 58 | * Construct a link filled in with useful data. | ||
| 59 | *@param newData The data this link should hold. | ||
| 60 | *@param newNext The next link that this link should point to. | ||
| 61 | */ | ||
| 62 | Link( void *newData = NULL, Link * newNext = NULL ) | ||
| 63 | { | ||
| 64 | pData = newData; | ||
| 65 | pNext = newNext; | ||
| 66 | } | ||
| 67 | void *pData; /**< A pointer to the contained data. */ | ||
| 68 | Link *pNext; /**< A pointer to the next link in the chain */ | ||
| 69 | }; | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Finds a pointer to the link at index index. This is the core function | ||
| 73 | * called for all seek operations, and has been optimized as heavily as | ||
| 74 | * possible. | ||
| 75 | *@param index The zero-based index of the desired element. | ||
| 76 | *@returns A pointer to the requested Link, or NULL if it isn't found. | ||
| 77 | */ | ||
| 78 | Link *getPtrTo( int index ); | ||
| 79 | Link *pBase; /**< The first link in the list. */ | ||
| 80 | Link *pTop; /**< The Last link in the list. */ | ||
| 81 | Link *pLast; /**< The previously requested link. */ | ||
| 82 | int nSize; /**< The number of contained links. */ | ||
| 83 | int nLast; /**< The index of the previously requested link. */ | ||
| 84 | }; | ||
| 85 | |||
| 86 | #endif | ||
| 87 | |||
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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | * Copyright (C) 2003 by Mike Buland * | ||
| 3 | * eichlan@yf-soft.com * | ||
| 4 | * * | ||
| 5 | * This program is free software; you can redistribute it and/or modify * | ||
| 6 | * it under the terms of the GNU General Public License as published by * | ||
| 7 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 8 | * (at your option) any later version. * | ||
| 9 | ***************************************************************************/ | ||
| 10 | #include "linkmessage.h" | ||
| 11 | #include <string.h> | ||
| 12 | |||
| 13 | LinkMessage::LinkMessage( int nNewMsg ) | ||
| 14 | { | ||
| 15 | nMsg = nNewMsg; | ||
| 16 | } | ||
| 17 | |||
| 18 | LinkMessage::~LinkMessage() | ||
| 19 | { | ||
| 20 | } | ||
| 21 | |||
| 22 | /* | ||
| 23 | void LinkMessage::setBroadcast( bool bOn ) | ||
| 24 | { | ||
| 25 | bBroadcast = bOn; | ||
| 26 | } | ||
| 27 | |||
| 28 | bool LinkMessage::isBroadcast() | ||
| 29 | { | ||
| 30 | return bBroadcast; | ||
| 31 | } | ||
| 32 | |||
| 33 | |||
| 34 | void LinkMessage::setFromID( int id ) | ||
| 35 | { | ||
| 36 | nFromLinkID = id; | ||
| 37 | } | ||
| 38 | |||
| 39 | int LinkMessage::getFromID() | ||
| 40 | { | ||
| 41 | return nFromLinkID; | ||
| 42 | } | ||
| 43 | |||
| 44 | void LinkMessage::setToID( int id ) | ||
| 45 | { | ||
| 46 | nTargetLinkID = id; | ||
| 47 | } | ||
| 48 | |||
| 49 | int LinkMessage::getToID() | ||
| 50 | { | ||
| 51 | return nTargetLinkID; | ||
| 52 | } | ||
| 53 | */ | ||
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 @@ | |||
| 1 | /**\file linkmessage.h | ||
| 2 | */ | ||
| 3 | |||
| 4 | #ifndef LINKMESSAGE_H | ||
| 5 | #define LINKMESSAGE_H | ||
| 6 | |||
| 7 | /** | ||
| 8 | * A message to be broadcast accross ProgramLinks in a ProgramChain. Generally | ||
| 9 | * one would make a subclass of this in order to transmit more useful | ||
| 10 | * information, but sometimes it isn't necesarry. | ||
| 11 | *@author Mike Buland | ||
| 12 | */ | ||
| 13 | class LinkMessage | ||
| 14 | { | ||
| 15 | public: | ||
| 16 | /** | ||
| 17 | * Construct a blank LinkMessage. | ||
| 18 | */ | ||
| 19 | LinkMessage() {}; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Deconstruct a LinkMessage. | ||
| 23 | */ | ||
| 24 | virtual ~LinkMessage(); | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Create a LinkMessage object with a specific message assosiated with it | ||
| 28 | * to start with. | ||
| 29 | *@param nNewMsg The message to use in the Message object. | ||
| 30 | */ | ||
| 31 | LinkMessage( int nNewMsg ); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * The message contained in the Message object. | ||
| 35 | */ | ||
| 36 | int nMsg; | ||
| 37 | }; | ||
| 38 | |||
| 39 | #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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | list.cpp - description | ||
| 3 | ------------------- | ||
| 4 | begin : Sun Oct 19 2003 | ||
| 5 | copyright : (C) 2003 by Mike Buland | ||
| 6 | email : eichlan@yf-soft.com | ||
| 7 | ***************************************************************************/ | ||
| 8 | |||
| 9 | /*************************************************************************** | ||
| 10 | * * | ||
| 11 | * This program is free software; you can redistribute it and/or modify * | ||
| 12 | * it under the terms of the GNU General Public License as published by * | ||
| 13 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 14 | * (at your option) any later version. * | ||
| 15 | * * | ||
| 16 | ***************************************************************************/ | ||
| 17 | |||
| 18 | #include "list.h" | ||
| 19 | |||
| 20 | List::List( ) | ||
| 21 | { | ||
| 22 | } | ||
| 23 | |||
| 24 | List::~List( ) | ||
| 25 | { | ||
| 26 | } | ||
| 27 | |||
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 @@ | |||
| 1 | #ifndef LIST_H | ||
| 2 | #define LIST_H | ||
| 3 | |||
| 4 | |||
| 5 | /** The basic List class ADT. This, on it's own, does absolutely nothing, but | ||
| 6 | * does define all standard interface functions to access a list. | ||
| 7 | *@author Mike Buland | ||
| 8 | */ | ||
| 9 | class List | ||
| 10 | { | ||
| 11 | public: | ||
| 12 | /** | ||
| 13 | * Construct a list. | ||
| 14 | */ | ||
| 15 | List(); | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Desconstruct a list. | ||
| 19 | */ | ||
| 20 | virtual ~List(); | ||
| 21 | |||
| 22 | /** Gets the value at a specified index. | ||
| 23 | *@param nIndex The index of the item to return. | ||
| 24 | *@returns The specified item, or NULL if the index was beyond the range | ||
| 25 | * of the list. | ||
| 26 | *@author Mike Buland | ||
| 27 | */ | ||
| 28 | virtual void *getAt( int nIndex ) = 0; | ||
| 29 | |||
| 30 | /** Append the given data to the end of the list. This increases the | ||
| 31 | * size of the list by one. | ||
| 32 | *@param pData The data to append to the list. | ||
| 33 | *@author Mike Buland | ||
| 34 | */ | ||
| 35 | virtual void append( void *pData ) = 0; | ||
| 36 | |||
| 37 | /** Inserts an item at the specified position in the list. The | ||
| 38 | * new item takes the index that you specify, and all other items | ||
| 39 | * are moved up one position. The size of the list is increased by | ||
| 40 | * one. | ||
| 41 | *@param pData The value to insert into the list. | ||
| 42 | *@param nPos Where to insert the data into the list. | ||
| 43 | *@author Mike Buland | ||
| 44 | */ | ||
| 45 | virtual void insertBefore( void *pData, int nPos = 0 ) = 0; | ||
| 46 | |||
| 47 | /** Determines the size of the list, in elements. | ||
| 48 | *@returns The size of the list. | ||
| 49 | *@author Mike Buland | ||
| 50 | */ | ||
| 51 | virtual int getSize( ) = 0; | ||
| 52 | |||
| 53 | /** Determines if the list is empty or not. | ||
| 54 | *@returns True if the list is empty, or false if the list has | ||
| 55 | * data in it (if the size is greater than zero). | ||
| 56 | *@author Mike Buland | ||
| 57 | */ | ||
| 58 | virtual bool isEmpty( ) = 0; | ||
| 59 | |||
| 60 | /** Deletes an item at the specified index and moves all other | ||
| 61 | * values down one index. The size of the list is decreased by one. | ||
| 62 | *@param nIndex The index of the item to delete. | ||
| 63 | *@author Mike Buland | ||
| 64 | */ | ||
| 65 | virtual void deleteAt( int nIndex ) = 0; | ||
| 66 | |||
| 67 | /** Completely empties the list, and sets the effective size to | ||
| 68 | * zero. | ||
| 69 | *@author Mike Buland | ||
| 70 | */ | ||
| 71 | virtual void empty() = 0; | ||
| 72 | |||
| 73 | /** Sets the size of the list. This can be larger or smaller | ||
| 74 | * than what it was previously. If larger, new blank items will | ||
| 75 | * be added to the end of the list. If smaller than the old list | ||
| 76 | * items will be deleted from the end. | ||
| 77 | *@param nNewSize The new size of the list. | ||
| 78 | *@author Mike Buland | ||
| 79 | */ | ||
| 80 | virtual void setSize( int nNewSize ) = 0; | ||
| 81 | |||
| 82 | /** Sets a member at a specified location to a new value. | ||
| 83 | * If the member being set is outside of the range of the | ||
| 84 | * current list it should be expanded. | ||
| 85 | *@param nIndex The zero-based index of the item to change. | ||
| 86 | *@param pData The new value for that index. | ||
| 87 | *@author Mike Buland | ||
| 88 | */ | ||
| 89 | virtual void setAt( int nIndex, void *pData ) = 0; | ||
| 90 | |||
| 91 | /** Makes the List work like an array. Just say listObj[2] to get | ||
| 92 | * the third element. | ||
| 93 | *@param nIndex The index to access in the list. | ||
| 94 | *@returns A pointer to the data at element index. | ||
| 95 | *@author Mike Buland | ||
| 96 | */ | ||
| 97 | void *operator[]( int nIndex ) { return getAt( nIndex ); }; | ||
| 98 | }; | ||
| 99 | |||
| 100 | #endif | ||
| 101 | |||
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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include <string.h> | ||
| 4 | #include "md5.h" | ||
| 5 | |||
| 6 | // This is a fun macro that tells us where the length char goes after the data | ||
| 7 | // section in the padded data segment. It's short for OBfuscation LOCaction. | ||
| 8 | #define OBLOC(len) ((((len + 64) >> 9) << 4) + 14) | ||
| 9 | // This performs a wrapping bitwise shift, kinda' fun! | ||
| 10 | |||
| 11 | #define bit_roll( num, cnt ) \ | ||
| 12 | (((num) << (cnt)) | (((num) >> (32 - (cnt))) & ~(-1<<(cnt)))) | ||
| 13 | |||
| 14 | //#define md5_cmn( q, a, b, x, s, t ) (bit_roll((a + q + x + t), s) + b) | ||
| 15 | |||
| 16 | // The following are handy wrappers for the cmn function | ||
| 17 | #define md5_ff( a, b, c, d, x, s, t ) \ | ||
| 18 | (md5_cmn((b & c) | ((~b) & d), a, b, x, s, t)) | ||
| 19 | |||
| 20 | #define md5_gg( a, b, c, d, x, s, t ) \ | ||
| 21 | (md5_cmn((b & d) | (c & (~d)), a, b, x, s, t)) | ||
| 22 | |||
| 23 | #define md5_hh( a, b, c, d, x, s, t ) \ | ||
| 24 | (md5_cmn(b ^ c ^ d, a, b, x, s, t)) | ||
| 25 | |||
| 26 | #define md5_ii( a, b, c, d, x, s, t ) \ | ||
| 27 | (md5_cmn(c ^ (b | (~d)), a, b, x, s, t)) | ||
| 28 | |||
| 29 | inline long md5_cmn( long q, long a, long b, long x, long s, long t ) | ||
| 30 | { | ||
| 31 | return bit_roll((a + q + x + t), s) + b; | ||
| 32 | } | ||
| 33 | |||
| 34 | md5::md5() | ||
| 35 | { | ||
| 36 | } | ||
| 37 | |||
| 38 | md5::~md5() | ||
| 39 | { | ||
| 40 | } | ||
| 41 | |||
| 42 | /* | ||
| 43 | * Calculate the MD5 of an array of little-endian words, and a bit length | ||
| 44 | */ | ||
| 45 | void md5::core_md5( long *x, long len, md5sum *output ) | ||
| 46 | { | ||
| 47 | long a = 1732584193, olda; | ||
| 48 | long b = -271733879, oldb; | ||
| 49 | long c = -1732584194, oldc; | ||
| 50 | long d = 271733878, oldd; | ||
| 51 | |||
| 52 | for( long i = 0; i < len; i += 16 ) | ||
| 53 | { | ||
| 54 | olda = a; | ||
| 55 | oldb = b; | ||
| 56 | oldc = c; | ||
| 57 | oldd = d; | ||
| 58 | |||
| 59 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); | ||
| 60 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); | ||
| 61 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); | ||
| 62 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); | ||
| 63 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); | ||
| 64 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); | ||
| 65 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); | ||
| 66 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); | ||
| 67 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); | ||
| 68 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); | ||
| 69 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063); | ||
| 70 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); | ||
| 71 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); | ||
| 72 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); | ||
| 73 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); | ||
| 74 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); | ||
| 75 | |||
| 76 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); | ||
| 77 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); | ||
| 78 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); | ||
| 79 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); | ||
| 80 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); | ||
| 81 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); | ||
| 82 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); | ||
| 83 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); | ||
| 84 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); | ||
| 85 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); | ||
| 86 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); | ||
| 87 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); | ||
| 88 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); | ||
| 89 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); | ||
| 90 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); | ||
| 91 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); | ||
| 92 | |||
| 93 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); | ||
| 94 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); | ||
| 95 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); | ||
| 96 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); | ||
| 97 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); | ||
| 98 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); | ||
| 99 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); | ||
| 100 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); | ||
| 101 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); | ||
| 102 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); | ||
| 103 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); | ||
| 104 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); | ||
| 105 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); | ||
| 106 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); | ||
| 107 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); | ||
| 108 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); | ||
| 109 | |||
| 110 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); | ||
| 111 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); | ||
| 112 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); | ||
| 113 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); | ||
| 114 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); | ||
| 115 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); | ||
| 116 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); | ||
| 117 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); | ||
| 118 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); | ||
| 119 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); | ||
| 120 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); | ||
| 121 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); | ||
| 122 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); | ||
| 123 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); | ||
| 124 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); | ||
| 125 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); | ||
| 126 | |||
| 127 | a = a + olda; | ||
| 128 | b = b + oldb; | ||
| 129 | c = c + oldc; | ||
| 130 | d = d + oldd; | ||
| 131 | } | ||
| 132 | |||
| 133 | output->data[0] = a; | ||
| 134 | output->data[1] = b; | ||
| 135 | output->data[2] = c; | ||
| 136 | output->data[3] = d; | ||
| 137 | delete[] x; | ||
| 138 | } | ||
| 139 | |||
| 140 | long *md5::c2l( const char *str, long len, long *nNewLen ) | ||
| 141 | { | ||
| 142 | long len8 = len*8; | ||
| 143 | long mlen = OBLOC( len8 ); | ||
| 144 | long flen = (((mlen/16)+((mlen%16)?(1):(0))))*16; | ||
| 145 | long *aBin = new long[flen]; | ||
| 146 | memset( aBin, 0, flen ); | ||
| 147 | |||
| 148 | for( long i = 0; i < len8; i+=8 ) | ||
| 149 | { | ||
| 150 | aBin[i>>5] |= ((long)str[i/8]) << (i%32); | ||
| 151 | } | ||
| 152 | |||
| 153 | aBin[len8 >> 5] |= 0x80 << ((len8) % 32); | ||
| 154 | aBin[OBLOC( len8 )] = len8; | ||
| 155 | |||
| 156 | (*nNewLen) = flen; | ||
| 157 | |||
| 158 | return aBin; | ||
| 159 | } | ||
| 160 | |||
| 161 | void md5::l2hexstr( long *binarray, char *str ) | ||
| 162 | { | ||
| 163 | static const char hex_tab[] = {"0123456789abcdef"}; | ||
| 164 | //static char str[33]; | ||
| 165 | |||
| 166 | int k = 0; | ||
| 167 | for( int i = 0; i < 16; i++) | ||
| 168 | { | ||
| 169 | str[k++] = hex_tab[(binarray[i>>2] >> ((i%4)*8+4)) & 0xF]; | ||
| 170 | str[k++] = hex_tab[(binarray[i>>2] >> ((i%4)*8 )) & 0xF]; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | void md5::sumString( md5sum *pSum, const char *sStr ) | ||
| 175 | { | ||
| 176 | sumData( pSum, sStr, strlen( sStr ) ); | ||
| 177 | } | ||
| 178 | |||
| 179 | void md5::sumData( md5sum *pSum, const char *aData, long nLen ) | ||
| 180 | { | ||
| 181 | long nNewLen; | ||
| 182 | long *aOb = c2l( aData, nLen, &nNewLen ); | ||
| 183 | core_md5( aOb, nNewLen, pSum ); | ||
| 184 | } | ||
| 185 | |||
| 186 | void md5::sumToHex( md5sum *pSum, char *sHex ) | ||
| 187 | { | ||
| 188 | l2hexstr( pSum->data, sHex ); | ||
| 189 | } | ||
| 190 | |||
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 @@ | |||
| 1 | #ifndef MD5_H | ||
| 2 | #define MD5_H | ||
| 3 | |||
| 4 | /** | ||
| 5 | * Used to store an MD5 sum in a handy container. | ||
| 6 | */ | ||
| 7 | typedef struct | ||
| 8 | { | ||
| 9 | /** The actual data-storage for an MD5 sum. */ | ||
| 10 | long data[4]; | ||
| 11 | } md5sum; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Class for easily calculating MD5 sums of just about any data. | ||
| 15 | *@author Mike Buland | ||
| 16 | */ | ||
| 17 | class md5 | ||
| 18 | { | ||
| 19 | public: | ||
| 20 | /** Build an MD5 sum builder. */ | ||
| 21 | md5(); | ||
| 22 | |||
| 23 | /** Deconstruct */ | ||
| 24 | ~md5(); | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Create a sum of a standard c string, null terminated. This is probably | ||
| 28 | * the easiest function to use. | ||
| 29 | *@param pSum The MD5 sum structure to fill up. | ||
| 30 | *@param sStr The null-terminated string to turn into an MD5 sum. | ||
| 31 | */ | ||
| 32 | void sumString( md5sum *pSum, const char *sStr ); | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Create a sum of an array of arbitrary data. This is the most handy for | ||
| 36 | * dealing with files and so on. | ||
| 37 | *@param pSum The MD5 sum structure to fill up. | ||
| 38 | *@param aData A pointer to the base of the data to sum. | ||
| 39 | *@param nLen The number of bytes to use in the sum. | ||
| 40 | */ | ||
| 41 | void sumData( md5sum *pSum, const char *aData, long nLen ); | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Convert an md5sum to standard hex representation. Make sure that sHex | ||
| 45 | * contains at least 17 characters of space. | ||
| 46 | *@param pSum The sum structure to convert to hex. | ||
| 47 | *@param sHex The string to store the hex value in. | ||
| 48 | */ | ||
| 49 | void sumToHex( md5sum *pSum, char *sHex ); | ||
| 50 | |||
| 51 | private: | ||
| 52 | /** | ||
| 53 | * Do the bulk of the work of the md5 algorithm. | ||
| 54 | *@param x I'm not sure. I'll need to look it up. | ||
| 55 | *@param len The length of the data. | ||
| 56 | *@param output The sum structure to put the output in. | ||
| 57 | */ | ||
| 58 | void core_md5( long *x, long len, md5sum *output ); | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Convert an array of charaters to an array of longs in a very crafty way. | ||
| 62 | * This also applies standard MD5 obfuscation to the resulting array, and | ||
| 63 | * makes it fit within MD5 size constraints. | ||
| 64 | *@param str The data to convert. | ||
| 65 | *@param len The length of the data. | ||
| 66 | *@param nNewLen A pointer to a variable that will hold the new length of | ||
| 67 | * the resulting array of longs. | ||
| 68 | *@returns The newly obfuscated and resized long array. | ||
| 69 | */ | ||
| 70 | long *c2l( const char *str, long len, long *nNewLen ); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Backend helper to convert an array of longs into a hex string. | ||
| 74 | *@param binarray The binary data to convert. | ||
| 75 | *@param str The string to store the hex string in. | ||
| 76 | */ | ||
| 77 | void l2hexstr( long *binarray, char *str ); | ||
| 78 | |||
| 79 | }; | ||
| 80 | |||
| 81 | #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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | multilog.cpp - description | ||
| 3 | ------------------- | ||
| 4 | begin : Sat Sep 6 2003 | ||
| 5 | copyright : (C) 2003 by Mike Buland | ||
| 6 | email : eichlan@yf-soft.com | ||
| 7 | ***************************************************************************/ | ||
| 8 | |||
| 9 | /*************************************************************************** | ||
| 10 | * * | ||
| 11 | * This program is free software; you can redistribute it and/or modify * | ||
| 12 | * it under the terms of the GNU General Public License as published by * | ||
| 13 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 14 | * (at your option) any later version. * | ||
| 15 | * * | ||
| 16 | ***************************************************************************/ | ||
| 17 | |||
| 18 | #include "multilog.h" | ||
| 19 | #include <stdio.h> | ||
| 20 | #include <time.h> | ||
| 21 | #include <string.h> | ||
| 22 | #include <stdlib.h> | ||
| 23 | |||
| 24 | #include "multilogchannel.h" | ||
| 25 | |||
| 26 | // This section is what we need to make this a singleton | ||
| 27 | // this makes this class easy to use from anywhere, without | ||
| 28 | // worrying about re-creating every output form and all of that crazy jazz | ||
| 29 | MultiLog *MultiLog::singleLog = NULL; | ||
| 30 | |||
| 31 | MultiLog *MultiLog::getLog() | ||
| 32 | { | ||
| 33 | if( singleLog == NULL ) | ||
| 34 | { | ||
| 35 | singleLog = new MultiLog; | ||
| 36 | atexit( cleanup ); | ||
| 37 | } | ||
| 38 | return singleLog; | ||
| 39 | } | ||
| 40 | |||
| 41 | void MultiLog::cleanup() | ||
| 42 | { | ||
| 43 | if( singleLog != NULL ) | ||
| 44 | { | ||
| 45 | delete singleLog; | ||
| 46 | singleLog = NULL; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | MultiLog::MultiLog() | ||
| 51 | { | ||
| 52 | lChannel = new LinkedList(); | ||
| 53 | rEntry = new RingList( 150 ); | ||
| 54 | nEntriesLost = 0; | ||
| 55 | } | ||
| 56 | |||
| 57 | MultiLog::~MultiLog() | ||
| 58 | { | ||
| 59 | int nMax = lChannel->getSize(); | ||
| 60 | for( int j = 0; j < nMax; j++ ) | ||
| 61 | { | ||
| 62 | ((MultiLogChannel *)lChannel->getAt(j))->closeLog(); | ||
| 63 | delete ((MultiLogChannel *)lChannel->getAt(j)); | ||
| 64 | } | ||
| 65 | delete lChannel; | ||
| 66 | |||
| 67 | for( int j = 0; j < rEntry->getSize(); j++ ) | ||
| 68 | { | ||
| 69 | delete (LogEntry *)rEntry->getAt( j ); | ||
| 70 | } | ||
| 71 | delete rEntry; | ||
| 72 | } | ||
| 73 | /* | ||
| 74 | void MultiLog::Log( int nLevel, const char *lpFormat, ...) | ||
| 75 | { | ||
| 76 | switch( nLevel ) | ||
| 77 | { | ||
| 78 | default: | ||
| 79 | break; | ||
| 80 | } | ||
| 81 | va_list ap; | ||
| 82 | va_start(ap, lpFormat); | ||
| 83 | |||
| 84 | vprintf( lpFormat, ap ); | ||
| 85 | |||
| 86 | va_end(ap); | ||
| 87 | }*/ | ||
| 88 | |||
| 89 | void MultiLog::DetailLog( int nLevel, const char *lpFile, int nLine, const char *lpFunction, const char *lpFormat, ...) | ||
| 90 | { | ||
| 91 | LogEntry *e = new LogEntry(); | ||
| 92 | |||
| 93 | va_list ap; | ||
| 94 | va_start(ap, lpFormat); | ||
| 95 | char *text; | ||
| 96 | vasprintf( &text, lpFormat, ap ); | ||
| 97 | va_end(ap); | ||
| 98 | |||
| 99 | time( &e->xTime ); | ||
| 100 | e->nLevel = nLevel; | ||
| 101 | e->nLine = nLine; | ||
| 102 | e->lpFile = new char[strlen(lpFile)+1]; | ||
| 103 | strcpy( e->lpFile, lpFile ); | ||
| 104 | e->lpText = new char[strlen(text)+1]; | ||
| 105 | strcpy( e->lpText, text ); | ||
| 106 | free( text ); | ||
| 107 | |||
| 108 | append( e ); | ||
| 109 | } | ||
| 110 | |||
| 111 | void MultiLog::append( LogEntry *pEntry ) | ||
| 112 | { | ||
| 113 | rEntry->append( pEntry ); | ||
| 114 | if( rEntry->getPushBuf() ) | ||
| 115 | { | ||
| 116 | delete (LogEntry *)rEntry->getPushBuf(); | ||
| 117 | nEntriesLost++; | ||
| 118 | } | ||
| 119 | |||
| 120 | for( int j = 0; j < lChannel->getSize(); j++ ) | ||
| 121 | { | ||
| 122 | ((MultiLogChannel *)lChannel->getAt( j ))->append( pEntry ); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | void MultiLog::addChannel( MultiLogChannel *pChannel ) | ||
| 127 | { | ||
| 128 | lChannel->append( pChannel ); | ||
| 129 | |||
| 130 | pChannel->openLog(); | ||
| 131 | |||
| 132 | for( int j = 0; j < rEntry->getSize(); j++ ) | ||
| 133 | { | ||
| 134 | pChannel->append( (LogEntry *)rEntry->getAt( j ) ); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | MultiLog::LogEntry::~LogEntry() | ||
| 139 | { | ||
| 140 | delete[] lpFile; | ||
| 141 | delete[] lpText; | ||
| 142 | } | ||
| 143 | |||
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 @@ | |||
| 1 | #ifndef MULTILOG_H | ||
| 2 | #define MULTILOG_H | ||
| 3 | |||
| 4 | #include <stdio.h> | ||
| 5 | #include <stdarg.h> | ||
| 6 | #include <time.h> | ||
| 7 | |||
| 8 | #include "ringlist.h" | ||
| 9 | #include "linkedlist.h" | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Calls the DetailLog function but includes pre-processor macros to fill in | ||
| 13 | * most of the fields for you. This makes your life a lot easier, and makes the | ||
| 14 | * log useful for system debugging as well as just letting people know what's | ||
| 15 | * going on. | ||
| 16 | *@param LEVEL The log level, comes from an enum in the MultiLog class. | ||
| 17 | *@param FORMAT The text to store in the log, using printf style formatting. | ||
| 18 | *@param ... Parameters to help format the text in the FROMAT param. | ||
| 19 | */ | ||
| 20 | #define LineLog( LEVEL, FORMAT, ...) DetailLog( LEVEL, __FILE__, __LINE__, __PRETTY_FUNCTION__, FORMAT, ##__VA_ARGS__ ) | ||
| 21 | |||
| 22 | /** MultiLog keeps track of logfile info in a myriad of varieties, and is | ||
| 23 | * easily configurable between them all. It allows output to the standard | ||
| 24 | * output, error output, files, networks, and streams, which includes memory | ||
| 25 | * buffers. | ||
| 26 | * MultiLog uses the singleton pattern to keep only a single instance of | ||
| 27 | * the log. Instead of instantiating a new copy, call the getLog method. | ||
| 28 | *@author Mike Buland | ||
| 29 | */ | ||
| 30 | class MultiLog | ||
| 31 | { | ||
| 32 | public: | ||
| 33 | /** | ||
| 34 | * Keeps track of a single log entry, in a standard format, that can be | ||
| 35 | * processed by any MultiLogChannel derrived class. | ||
| 36 | *@author Mike Buland | ||
| 37 | */ | ||
| 38 | typedef struct LogEntry | ||
| 39 | { | ||
| 40 | /** Safely delete a log entry. */ | ||
| 41 | ~LogEntry(); | ||
| 42 | time_t xTime; /**< The time the log entry was made. */ | ||
| 43 | int nLevel; /**< The log-level of the entry. */ | ||
| 44 | char *lpFile; /**< The name of the file this entry came from. */ | ||
| 45 | int nLine; /**< The line number that this log came from. */ | ||
| 46 | char *lpText; /**< The text content of this log entry. */ | ||
| 47 | } LogEntry; | ||
| 48 | |||
| 49 | private: | ||
| 50 | /** | ||
| 51 | * Private constructor, this ensures that this is a singleton. | ||
| 52 | */ | ||
| 53 | MultiLog(); | ||
| 54 | |||
| 55 | /** | ||
| 56 | * The only instance of MultiLog ever. | ||
| 57 | */ | ||
| 58 | static MultiLog *singleLog; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Append a new logentry to the log list, possibly pushing an old entry off. | ||
| 62 | *@param pEntry The new entry to append. | ||
| 63 | */ | ||
| 64 | void append( LogEntry *pEntry ); | ||
| 65 | |||
| 66 | /** | ||
| 67 | * The actual log entry storage mechanism. | ||
| 68 | */ | ||
| 69 | RingList *rEntry; | ||
| 70 | |||
| 71 | /** | ||
| 72 | * The number of entries that have rolled off the end of the RingList. | ||
| 73 | */ | ||
| 74 | unsigned long nEntriesLost; | ||
| 75 | |||
| 76 | /** | ||
| 77 | * A list of all channels that are registered with the MultiLog. | ||
| 78 | */ | ||
| 79 | LinkedList *lChannel; | ||
| 80 | |||
| 81 | public: | ||
| 82 | /** | ||
| 83 | * Destroy the multilog. | ||
| 84 | *@todo Why is this public? Does it need to be? | ||
| 85 | */ | ||
| 86 | ~MultiLog(); | ||
| 87 | |||
| 88 | /** Sends info to the logfile. | ||
| 89 | *@param nLevel The type of data being logged (error, info, etc.) | ||
| 90 | *@param lpFormat The data to send to the log. | ||
| 91 | *@author Mike Buland | ||
| 92 | */ | ||
| 93 | //void Log( int nLevel, const char *lpFormat, ...); | ||
| 94 | |||
| 95 | /** Sends info to the logfile with extra information, including the files | ||
| 96 | * that it was called from and the line in the code. Besides that, it's | ||
| 97 | * exactly the same as Log. Please use the LineLog macro to make DetailLog | ||
| 98 | * really easy to use. It operates exacly like Log, but inserts the | ||
| 99 | * builtin macros as the lpFile and nLine parameters. | ||
| 100 | *@param nLevel The type of data being logged (error, info, etc.) | ||
| 101 | *@param lpFile The name of the file that called the log function. | ||
| 102 | *@param nLine The line in the file that this log function was called from. | ||
| 103 | *@param lpFunction The name of the function that called the log function. | ||
| 104 | *@param lpFormat The data to send to the log. | ||
| 105 | *@author Mike Buland | ||
| 106 | */ | ||
| 107 | void DetailLog( int nLevel, const char *lpFile, int nLine, const char *lpFunction, const char *lpFormat, ...); | ||
| 108 | |||
| 109 | /** Gets a pointer to the only instantion of the MultiLog that can exist. | ||
| 110 | * If there is no instantion in existance, it creates one, so it's | ||
| 111 | * foolproof. | ||
| 112 | *@returns A pointer to the only MultiLog instantion. | ||
| 113 | *@author Mike Buland | ||
| 114 | */ | ||
| 115 | static MultiLog *getLog(); | ||
| 116 | |||
| 117 | /** Performs standard cleanup and deletes the only instantiation of MultiLog | ||
| 118 | * that can exist. This is just the same as delete and will nicely close | ||
| 119 | * all open logs. always call this when you are done with your MultiLog. | ||
| 120 | */ | ||
| 121 | static void cleanup(); | ||
| 122 | |||
| 123 | /** | ||
| 124 | * Adds a logging channel to the MultiLog channel chain. Every added | ||
| 125 | * channel will automatically receive a complete log of everything that | ||
| 126 | * happened before the channel was added as well as all future messages. | ||
| 127 | *@param pChannel A pointer to the pre-contructed channel object to add. | ||
| 128 | */ | ||
| 129 | void addChannel( class MultiLogChannel *pChannel ); | ||
| 130 | |||
| 131 | /** The various pre-defined levels available to use when logging. | ||
| 132 | * The person logging can make up their own, just make sure to remember | ||
| 133 | * which value is which (all levels are integers). | ||
| 134 | *@author Mike Buland | ||
| 135 | */ | ||
| 136 | enum Levels | ||
| 137 | { | ||
| 138 | LError, | ||
| 139 | LWarning, | ||
| 140 | LInfo, | ||
| 141 | LStatus | ||
| 142 | }; | ||
| 143 | }; | ||
| 144 | |||
| 145 | #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 @@ | |||
| 1 | // | ||
| 2 | // C++ Implementation: multilogchannel | ||
| 3 | // | ||
| 4 | // Description: | ||
| 5 | // | ||
| 6 | // | ||
| 7 | // Author: Mike Buland <eichlan@yf-soft.com>, (C) 2005 | ||
| 8 | // | ||
| 9 | // Copyright: See COPYING file that comes with this distribution | ||
| 10 | // | ||
| 11 | // | ||
| 12 | #include "multilogchannel.h" | ||
| 13 | |||
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 @@ | |||
| 1 | #ifndef MULTILOGCHANNEL_H | ||
| 2 | #define MULTILOGCHANNEL_H | ||
| 3 | |||
| 4 | #include "multilog.h" | ||
| 5 | |||
| 6 | /** | ||
| 7 | * The baseclass for any MultiLog output channel. Any class that implements | ||
| 8 | * all of these functions can be put in the log chain and will be sent | ||
| 9 | * messages from active MultiLoggers. | ||
| 10 | *@author Mike Buland | ||
| 11 | */ | ||
| 12 | class MultiLogChannel | ||
| 13 | { | ||
| 14 | public: | ||
| 15 | /** | ||
| 16 | * Deconstruct a MultiLogChannel. | ||
| 17 | */ | ||
| 18 | virtual ~MultiLogChannel() {}; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Should perform any operations that need to take place in order to start | ||
| 22 | * the output of data into this channel. This will be called once by the | ||
| 23 | * MultiLog when the MultiLogChannel is registered. | ||
| 24 | *@returns True means that everything can go as planned. False means that | ||
| 25 | * the MultiLog should remove this channel from the list and delete it. | ||
| 26 | */ | ||
| 27 | virtual bool openLog() = 0; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Should append a log entry to the long, by whatever means are necesarry. | ||
| 31 | *@param pEntry The LogEntry to append. | ||
| 32 | *@returns True means that everything can go as planned. False means that | ||
| 33 | * the MultiLog should remove this channel from the list and delete it. | ||
| 34 | */ | ||
| 35 | virtual bool append( MultiLog::LogEntry *pEntry ) = 0; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Should perform any operations that need to take place in order to safely | ||
| 39 | * close and cleanup the log. | ||
| 40 | *@returns True means that everything can go as planned. False means that | ||
| 41 | * the MultiLog should remove this channel from the list and delete it. | ||
| 42 | */ | ||
| 43 | virtual bool closeLog() = 0; | ||
| 44 | }; | ||
| 45 | |||
| 46 | #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 @@ | |||
| 1 | |||
| 2 | #include <stdio.h> | ||
| 3 | #include <stdlib.h> | ||
| 4 | #include <fcntl.h> | ||
| 5 | #include <unistd.h> | ||
| 6 | #include <time.h> | ||
| 7 | #include <string.h> | ||
| 8 | #include "multilogtext.h" | ||
| 9 | |||
| 10 | MultiLogText::MultiLogText( const char *sFileName, const char *lpFormat ) | ||
| 11 | { | ||
| 12 | this->lpFormat = NULL; | ||
| 13 | nFD = open( sFileName, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH ); | ||
| 14 | setLogFormat( lpFormat ); | ||
| 15 | } | ||
| 16 | |||
| 17 | MultiLogText::MultiLogText( int nFileDesc, const char *lpFormat ) | ||
| 18 | { | ||
| 19 | this->lpFormat = NULL; | ||
| 20 | nFD = nFileDesc; | ||
| 21 | setLogFormat( lpFormat ); | ||
| 22 | } | ||
| 23 | |||
| 24 | MultiLogText::~MultiLogText() | ||
| 25 | { | ||
| 26 | if( nFD != -1 ) | ||
| 27 | { | ||
| 28 | close( nFD ); | ||
| 29 | } | ||
| 30 | |||
| 31 | delete[] lpFormat; | ||
| 32 | } | ||
| 33 | |||
| 34 | bool MultiLogText::setLogFormat( const char *lpFormat ) | ||
| 35 | { | ||
| 36 | char buf[200]; | ||
| 37 | int k = 0; | ||
| 38 | static char fmts[10][4]={ | ||
| 39 | {'y', 'd', '0', '1'}, | ||
| 40 | {'m', 'd', '0', '2'}, | ||
| 41 | {'d', 'd', '0', '3'}, | ||
| 42 | {'h', 'd', '0', '4'}, | ||
| 43 | {'M', 'd', '0', '5'}, | ||
| 44 | {'s', 'd', '0', '6'}, | ||
| 45 | {'l', 'd', '0', '7'}, | ||
| 46 | {'f', 's', '0', '8'}, | ||
| 47 | {'L', 'd', '0', '9'}, | ||
| 48 | {'t', 's', '1', '0'}, | ||
| 49 | }; | ||
| 50 | |||
| 51 | for( int j = 0; lpFormat[j] != '\0'; j++ ) | ||
| 52 | { | ||
| 53 | if( lpFormat[j] == '%' ) | ||
| 54 | { | ||
| 55 | buf[k++] = '%'; | ||
| 56 | int nPlace = k++; | ||
| 57 | k++; | ||
| 58 | buf[k++] = '$'; | ||
| 59 | bool bDone = false; | ||
| 60 | for( j++; bDone == false; j++ ) | ||
| 61 | { | ||
| 62 | int l; | ||
| 63 | for( l = 0; l < 10; l++ ) | ||
| 64 | { | ||
| 65 | if( lpFormat[j] == fmts[l][0] ) | ||
| 66 | { | ||
| 67 | buf[nPlace] = fmts[l][2]; | ||
| 68 | buf[nPlace+1] = fmts[l][3]; | ||
| 69 | buf[k++] = fmts[l][1]; | ||
| 70 | bDone = true; | ||
| 71 | break; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | if( l == 10 ) | ||
| 75 | { | ||
| 76 | buf[k++] = lpFormat[j]; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | j--; | ||
| 80 | } | ||
| 81 | else | ||
| 82 | { | ||
| 83 | buf[k++] = lpFormat[j]; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | buf[k++] = '\n'; | ||
| 87 | buf[k] = '\0'; | ||
| 88 | |||
| 89 | if( this->lpFormat != NULL ) | ||
| 90 | { | ||
| 91 | delete[] this->lpFormat; | ||
| 92 | } | ||
| 93 | this->lpFormat = new char[k+1]; | ||
| 94 | strcpy( this->lpFormat, buf ); | ||
| 95 | |||
| 96 | return true; | ||
| 97 | } | ||
| 98 | |||
| 99 | bool MultiLogText::openLog() | ||
| 100 | { | ||
| 101 | if( nFD == -1 ) | ||
| 102 | { | ||
| 103 | return false; | ||
| 104 | } | ||
| 105 | return true; | ||
| 106 | } | ||
| 107 | |||
| 108 | bool MultiLogText::append( MultiLog::LogEntry *pEntry ) | ||
| 109 | { | ||
| 110 | if( nFD == -1 ) | ||
| 111 | { | ||
| 112 | return false; | ||
| 113 | } | ||
| 114 | |||
| 115 | char *line = NULL; | ||
| 116 | struct tm *pTime; | ||
| 117 | pTime = localtime( &pEntry->xTime ); | ||
| 118 | asprintf( | ||
| 119 | &line, | ||
| 120 | lpFormat, | ||
| 121 | pTime->tm_year+1900, | ||
| 122 | pTime->tm_mon+1, | ||
| 123 | pTime->tm_mday, | ||
| 124 | pTime->tm_hour, | ||
| 125 | pTime->tm_min, | ||
| 126 | pTime->tm_sec, | ||
| 127 | pEntry->nLevel, | ||
| 128 | pEntry->lpFile, | ||
| 129 | pEntry->nLine, | ||
| 130 | pEntry->lpText | ||
| 131 | ); | ||
| 132 | write( nFD, line, strlen(line) ); | ||
| 133 | free( line ); | ||
| 134 | |||
| 135 | return true; | ||
| 136 | } | ||
| 137 | |||
| 138 | bool MultiLogText::closeLog() | ||
| 139 | { | ||
| 140 | if( nFD == -1 ) | ||
| 141 | { | ||
| 142 | return false; | ||
| 143 | } | ||
| 144 | // Don't close it if it's sdtout or errorout | ||
| 145 | if( nFD > 2 ) | ||
| 146 | { | ||
| 147 | close( nFD ); | ||
| 148 | } | ||
| 149 | nFD = -1; | ||
| 150 | return true; | ||
| 151 | } | ||
| 152 | |||
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 @@ | |||
| 1 | #ifndef MULTILOGTEXT_H | ||
| 2 | #define MULTILOGTEXT_H | ||
| 3 | |||
| 4 | #include "multilogchannel.h" | ||
| 5 | |||
| 6 | /** | ||
| 7 | * Simple MultiLogChannel that takes the logdata, formats it textually, and | ||
| 8 | * writes it to a text device, either a file or the screen, yay! This takes | ||
| 9 | * the place of the old standard logging facility. | ||
| 10 | * The entries in the format follow the standard printf % style, and are as | ||
| 11 | * follows: | ||
| 12 | * <ul> | ||
| 13 | * <li>%y - current year</li> | ||
| 14 | * <li>%m - current month</li> | ||
| 15 | * <li>%d - current day</li> | ||
| 16 | * <li>%h - current hour (24-hour format)</li> | ||
| 17 | * <li>%M - current minute</li> | ||
| 18 | * <li>%s - current seccond</li> | ||
| 19 | * <li>%l - Loglevel (numerical)</li> | ||
| 20 | * <li>%f - Filename</li> | ||
| 21 | * <li>%L - Line number</li> | ||
| 22 | * <li>%t - Full text of the log entry</li> | ||
| 23 | * </ul> | ||
| 24 | *@author Mike Buland | ||
| 25 | */ | ||
| 26 | class MultiLogText : public MultiLogChannel | ||
| 27 | { | ||
| 28 | public: | ||
| 29 | /** | ||
| 30 | * Construct a MultiLogText object around a specific filename and format. | ||
| 31 | * The file named by sFileName will be opened for writting in text+append | ||
| 32 | * mode. No existing data will be destroyed. | ||
| 33 | *@param sFileName The file to output log-data to. | ||
| 34 | *@param lpFormat The format using the above specifications to be used for | ||
| 35 | * every log entry. | ||
| 36 | */ | ||
| 37 | MultiLogText( const char *sFileName, const char *lpFormat ); | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Construct a MultiLogText object around a specific file and format. | ||
| 41 | * The file descriptor passed in should describe an already opened and set- | ||
| 42 | * up file or device. This could easily be a socket or stdout or stderr. | ||
| 43 | *@param nFileDesc The already opened descriptor to send data to. | ||
| 44 | *@param lpFormat The format using the above specifications to be used for | ||
| 45 | * every log entry. | ||
| 46 | */ | ||
| 47 | MultiLogText( int nFileDesc, const char *lpFormat ); | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Destroy the object. | ||
| 51 | */ | ||
| 52 | ~MultiLogText(); | ||
| 53 | |||
| 54 | bool openLog(); | ||
| 55 | bool append( MultiLog::LogEntry *pEntry ); | ||
| 56 | bool closeLog(); | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Change the log format on the fly. | ||
| 60 | *@param lpFormat The new format to use for all future log entries. | ||
| 61 | *@returns True if everything was fine, false for catastrophic failure. | ||
| 62 | */ | ||
| 63 | bool setLogFormat( const char *lpFormat ); | ||
| 64 | |||
| 65 | private: | ||
| 66 | int nFD; /**< The file descriptor we're writing to. */ | ||
| 67 | char *lpFormat; /**< The format that we're using, converted for printf. */ | ||
| 68 | }; | ||
| 69 | |||
| 70 | #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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <string.h> | ||
| 3 | #include "pproc.h" | ||
| 4 | |||
| 5 | void processParams( int argc, char *argv[], PPROC *pproc ) | ||
| 6 | { | ||
| 7 | // Loop over all the params except the first, no params, no looping! | ||
| 8 | for( int j = 1; j < argc; j++ ) | ||
| 9 | { | ||
| 10 | //printf("Param[%d]: \"%s\"\n", j, argv[j] ); | ||
| 11 | if( argv[j][0] == '-' ) | ||
| 12 | { | ||
| 13 | if( argv[j][1] == '-' ) | ||
| 14 | { | ||
| 15 | // Proccess a long-word param string | ||
| 16 | for( int k = 0; | ||
| 17 | pproc[k].proc != NULL || pproc[k].stateVar != NULL; | ||
| 18 | k++ ) | ||
| 19 | { | ||
| 20 | if( !strcmp( pproc[k].lpWord, &argv[j][2] ) ) | ||
| 21 | { | ||
| 22 | if( pproc[k].proc != NULL ) | ||
| 23 | { | ||
| 24 | j += pproc[k].proc( argc-j, &argv[j] ); | ||
| 25 | } | ||
| 26 | if( pproc[k].stateVar != NULL ) | ||
| 27 | { | ||
| 28 | (*(pproc[k].stateVar)) = pproc[k].bSetState; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | } | ||
| 32 | } | ||
| 33 | else | ||
| 34 | { | ||
| 35 | // Process a one char param string | ||
| 36 | for( int k = 0; | ||
| 37 | pproc[k].proc != NULL || pproc[k].stateVar != NULL; | ||
| 38 | k++ ) | ||
| 39 | { | ||
| 40 | if( pproc[k].cChar == argv[j][1] ) | ||
| 41 | { | ||
| 42 | if( pproc[k].proc != NULL ) | ||
| 43 | { | ||
| 44 | j += pproc[k].proc( argc-j, &argv[j] ); | ||
| 45 | } | ||
| 46 | if( pproc[k].stateVar != NULL ) | ||
| 47 | { | ||
| 48 | (*(pproc[k].stateVar)) = pproc[k].bSetState; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | else | ||
| 55 | { | ||
| 56 | // Handle generic params here. | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
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 @@ | |||
| 1 | #ifndef PPROC_H_ | ||
| 2 | #define PPROC_H_ | ||
| 3 | |||
| 4 | /** | ||
| 5 | * Contains all required info to handle a single program parameter. | ||
| 6 | *@author Mike Buland | ||
| 7 | */ | ||
| 8 | typedef struct PPROC | ||
| 9 | { | ||
| 10 | const char *lpWord; /**< The full text-word to use as a param. */ | ||
| 11 | const char cChar; /**< The short char version of the param. */ | ||
| 12 | /** | ||
| 13 | * Pointer to the function to call when this param is triggered. | ||
| 14 | *@param argc The number of params after and including the one that | ||
| 15 | * triggered this call. | ||
| 16 | *@param argv The array of commandline tokens to use as parameters. | ||
| 17 | *@returns 0 for everything is ok. A number greater than zero signals that | ||
| 18 | * this parameter function used n parameters and they should be skipped by | ||
| 19 | * the processParams function. | ||
| 20 | */ | ||
| 21 | int (*proc)( int argc, char *argv[] ); | ||
| 22 | bool *stateVar; /**< A pointer to a bool to be setwhen this is triggered */ | ||
| 23 | bool bSetState; /**< The state to set the above bool to. */ | ||
| 24 | } PPROC; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Process command line parameters based on a null-terminated array of PPROC | ||
| 28 | * structures. | ||
| 29 | *@param argc Should come straight from your main function's argc. | ||
| 30 | *@param argv Should come straight from your main function's argv. | ||
| 31 | *@param pproc The array of params that this function can respond to. | ||
| 32 | */ | ||
| 33 | void processParams( int argc, char *argv[], PPROC *pproc ); | ||
| 34 | |||
| 35 | #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 @@ | |||
| 1 | #include "pqueue.h" | ||
| 2 | |||
| 3 | PQueue::PQueue( int nNewNumQueues ) | ||
| 4 | { | ||
| 5 | nNumQueues = nNewNumQueues; | ||
| 6 | aQueue = new Queue[nNumQueues]; | ||
| 7 | } | ||
| 8 | |||
| 9 | PQueue::~PQueue() | ||
| 10 | { | ||
| 11 | delete[] aQueue; | ||
| 12 | } | ||
| 13 | |||
| 14 | void PQueue::enqueue( void *pData, int nQueueLevel ) | ||
| 15 | { | ||
| 16 | if( nQueueLevel < 0 || nQueueLevel >= nNumQueues ) | ||
| 17 | return; | ||
| 18 | |||
| 19 | aQueue[nQueueLevel].enqueue( pData ); | ||
| 20 | } | ||
| 21 | |||
| 22 | void *PQueue::dequeue() | ||
| 23 | { | ||
| 24 | for( int j = 0; j < nNumQueues; j++ ) | ||
| 25 | { | ||
| 26 | if( aQueue[j].isEmpty() == false ) | ||
| 27 | { | ||
| 28 | return aQueue[j].dequeue(); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | return NULL; | ||
| 33 | } | ||
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 @@ | |||
| 1 | #ifndef PQUEUE_H | ||
| 2 | #define PQUEUE_H | ||
| 3 | |||
| 4 | #include "queue.h" | ||
| 5 | |||
| 6 | /** Priority queue. This is just like a queue, but something with a higher | ||
| 7 | * priority will always come off the queue before something with a lower | ||
| 8 | * priority, even if it's added after. Otherwise works just like a queue. | ||
| 9 | *@author Mike Buland | ||
| 10 | */ | ||
| 11 | class PQueue | ||
| 12 | { | ||
| 13 | public: | ||
| 14 | /** Create a queue with any number of different priorities. | ||
| 15 | *@param nNewNumQueues The number of queues, the default is 3 | ||
| 16 | */ | ||
| 17 | PQueue( int nNewNumQueues=3 ); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Cleanup all contained queues. | ||
| 21 | */ | ||
| 22 | ~PQueue(); | ||
| 23 | |||
| 24 | /** Add a new item to the queue at the specified priority. A lower | ||
| 25 | * number means a higher priority! | ||
| 26 | *@param pData A pointer to the data to add to the queue | ||
| 27 | *@param nQueueLevel The priority to set the new data to | ||
| 28 | */ | ||
| 29 | void enqueue( void *pData, int nQueueLevel ); | ||
| 30 | |||
| 31 | /** Pull the next item off the queue, high priority first. | ||
| 32 | *@returns A pointer to the data that was next in the priority queue | ||
| 33 | */ | ||
| 34 | void *dequeue(); | ||
| 35 | |||
| 36 | private: | ||
| 37 | /** | ||
| 38 | * The queues we use for real data storage. | ||
| 39 | */ | ||
| 40 | Queue *aQueue; | ||
| 41 | |||
| 42 | /** | ||
| 43 | * The number of priorities or queus that we need. | ||
| 44 | */ | ||
| 45 | int nNumQueues; | ||
| 46 | }; | ||
| 47 | |||
| 48 | #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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | programchain.cpp - description | ||
| 3 | ------------------- | ||
| 4 | begin : Sat Sep 6 2003 | ||
| 5 | copyright : (C) 2003 by Mike Buland | ||
| 6 | email : eichlan@yf-soft.com | ||
| 7 | ***************************************************************************/ | ||
| 8 | |||
| 9 | /*************************************************************************** | ||
| 10 | * * | ||
| 11 | * This program is free software; you can redistribute it and/or modify * | ||
| 12 | * it under the terms of the GNU General Public License as published by * | ||
| 13 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 14 | * (at your option) any later version. * | ||
| 15 | * * | ||
| 16 | ***************************************************************************/ | ||
| 17 | |||
| 18 | #include <stdlib.h> | ||
| 19 | #include "programchain.h" | ||
| 20 | |||
| 21 | ProgramChain::ProgramChain() | ||
| 22 | { | ||
| 23 | pLog = MultiLog::getLog(); | ||
| 24 | pLog->LineLog( MultiLog::LStatus, "Program Chain Initialized." ); | ||
| 25 | } | ||
| 26 | |||
| 27 | ProgramChain::~ProgramChain() | ||
| 28 | { | ||
| 29 | } | ||
| 30 | |||
| 31 | bool ProgramChain::addLink( ProgramLink *pLink ) | ||
| 32 | { | ||
| 33 | if( pLink->init() == false ) | ||
| 34 | { | ||
| 35 | emergencyShutdown(); | ||
| 36 | return false; | ||
| 37 | } | ||
| 38 | |||
| 39 | lLink.append( pLink ); | ||
| 40 | |||
| 41 | pLink->setChain( this ); | ||
| 42 | |||
| 43 | return true; | ||
| 44 | } | ||
| 45 | |||
| 46 | ProgramLink *ProgramChain::getLink( const char *lpName ) | ||
| 47 | { | ||
| 48 | char a; | ||
| 49 | a = lpName[0]; | ||
| 50 | return NULL; | ||
| 51 | } | ||
| 52 | |||
| 53 | ProgramLink *ProgramChain::getBaseLink() | ||
| 54 | { | ||
| 55 | return NULL; | ||
| 56 | } | ||
| 57 | |||
| 58 | bool ProgramChain::execChainOnce() | ||
| 59 | { | ||
| 60 | int nLen = lLink.getSize(); | ||
| 61 | for( int j = 0; j < nLen; j++ ) | ||
| 62 | { | ||
| 63 | if( ((ProgramLink *)lLink[j])->timeSlice() == false ) | ||
| 64 | { | ||
| 65 | pLog->LineLog( MultiLog::LInfo, "Shutting down due to signal from link #%d", j ); | ||
| 66 | emergencyShutdown(); | ||
| 67 | return false; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | return true; | ||
| 72 | } | ||
| 73 | |||
| 74 | bool ProgramChain::enterChainLoop() | ||
| 75 | { | ||
| 76 | for(;;) | ||
| 77 | { | ||
| 78 | if( execChainOnce() == false ) | ||
| 79 | { | ||
| 80 | return false; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | return true; | ||
| 85 | } | ||
| 86 | |||
| 87 | void ProgramChain::emergencyShutdown() | ||
| 88 | { | ||
| 89 | int nLen = lLink.getSize(); | ||
| 90 | for( int j = 0; j < nLen; j++ ) | ||
| 91 | { | ||
| 92 | ((ProgramLink *)lLink[j])->deInit(); | ||
| 93 | delete (ProgramLink *)lLink[j]; | ||
| 94 | } | ||
| 95 | lLink.empty(); | ||
| 96 | } | ||
| 97 | |||
| 98 | LinkMessage *ProgramChain::broadcastIRM( LinkMessage *pMsgOut, ProgramLink *pSender ) | ||
| 99 | { | ||
| 100 | int nLen = lLink.getSize(); | ||
| 101 | for( int j = 0; j < nLen; j++ ) | ||
| 102 | { | ||
| 103 | LinkMessage *pMsg = ((ProgramLink *)lLink[j])->processIRM( pMsgOut ); | ||
| 104 | if( pMsg != NULL ) | ||
| 105 | { | ||
| 106 | delete pMsgOut; | ||
| 107 | return pMsg; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | delete pMsgOut; | ||
| 112 | return NULL; | ||
| 113 | } | ||
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 @@ | |||
| 1 | #ifndef PROGRAMCHAIN_H | ||
| 2 | #define PROGRAMCHAIN_H | ||
| 3 | |||
| 4 | #include "linkedlist.h" | ||
| 5 | #include "multilog.h" | ||
| 6 | #include "programlink.h" | ||
| 7 | |||
| 8 | /** The Program Chain links together program "chunks" to more easily facilitate | ||
| 9 | * a generalized program loop with modular extensions. | ||
| 10 | *@author Mike Buland | ||
| 11 | */ | ||
| 12 | class ProgramChain | ||
| 13 | { | ||
| 14 | public: | ||
| 15 | /** | ||
| 16 | * Construct an empty chain. | ||
| 17 | */ | ||
| 18 | ProgramChain(); | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Destroy your chain. | ||
| 22 | */ | ||
| 23 | ~ProgramChain(); | ||
| 24 | |||
| 25 | /** Adds a link to the end of the chain. | ||
| 26 | *@param pLink A pointer to the link to add to the chain. | ||
| 27 | *@returns True if adding the link was successful, otherwise false | ||
| 28 | *@author Mike Buland | ||
| 29 | */ | ||
| 30 | bool addLink( ProgramLink *pLink ); | ||
| 31 | |||
| 32 | /** Gets a link by name. | ||
| 33 | *@param lpName The name of the link you're looking for. Every link has a | ||
| 34 | * name, apparently. | ||
| 35 | *@returns A pointer to the specified ProgramLink, or NULL if none were found | ||
| 36 | * matching your criteria. | ||
| 37 | *@author Mike Buland | ||
| 38 | */ | ||
| 39 | class ProgramLink *getLink( const char *lpName ); | ||
| 40 | |||
| 41 | /** Gets the very first link in the chain. | ||
| 42 | *@returns A pointer to the first link in the chain. | ||
| 43 | *@author Mike Buland | ||
| 44 | */ | ||
| 45 | class ProgramLink *getBaseLink(); | ||
| 46 | |||
| 47 | /** Runs through the chain once. Useful if you want to have more control over | ||
| 48 | * the operation of the chain. | ||
| 49 | *@returns true if every link returned true. If at least one link returns false, | ||
| 50 | * then returns false. | ||
| 51 | *@author Mike Buland | ||
| 52 | */ | ||
| 53 | bool execChainOnce(); | ||
| 54 | |||
| 55 | /** Enters the master chain loop, looping over the entire chain and executing | ||
| 56 | * every link's TimeSlice routine in order, over and over, until a link returns | ||
| 57 | * a false value. | ||
| 58 | *@returns False, always. It returns true unless a link returned false, but loops | ||
| 59 | * until a link does return false. | ||
| 60 | *@author Mike Buland | ||
| 61 | **/ | ||
| 62 | bool enterChainLoop(); | ||
| 63 | |||
| 64 | /** Broadcasts an Immediate Response Message to all active links, save the | ||
| 65 | * sender. Whatever link first responds with a non-null response message | ||
| 66 | * will have it's messages sent back to the broadcasting link as the returns | ||
| 67 | * of this function call. Therefore it is very important that all message | ||
| 68 | * processing code is handled in a fairly timely fasion. | ||
| 69 | *@param pMsgOut The message to broadcast in hopes of a response. | ||
| 70 | *@param pSender The message that sent out the message and doesn't want to | ||
| 71 | * receive it's own message. This should always just be "this". | ||
| 72 | *@returns The message that was returned by the first link to return a | ||
| 73 | * non-null response. If all messages return null responses then this also | ||
| 74 | * returns null. Please note that whoever calls this will be responsible | ||
| 75 | * for deleting the message returned by it, if non-null. | ||
| 76 | */ | ||
| 77 | class LinkMessage *broadcastIRM( LinkMessage *pMsgOut, ProgramLink *pSender ); | ||
| 78 | |||
| 79 | private: | ||
| 80 | /** | ||
| 81 | * Shuts down all operation no matter what point in the operation we were. | ||
| 82 | */ | ||
| 83 | void emergencyShutdown(); | ||
| 84 | MultiLog *pLog; /**< A pointer to a log. */ | ||
| 85 | LinkedList lLink; /**< The linked list that contains all of the links. */ | ||
| 86 | }; | ||
| 87 | |||
| 88 | #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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | programlink.cpp - description | ||
| 3 | ------------------- | ||
| 4 | begin : Sat Sep 6 2003 | ||
| 5 | copyright : (C) 2003 by Mike Buland | ||
| 6 | email : eichlan@yf-soft.com | ||
| 7 | ***************************************************************************/ | ||
| 8 | |||
| 9 | /*************************************************************************** | ||
| 10 | * * | ||
| 11 | * This program is free software; you can redistribute it and/or modify * | ||
| 12 | * it under the terms of the GNU General Public License as published by * | ||
| 13 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 14 | * (at your option) any later version. * | ||
| 15 | * * | ||
| 16 | ***************************************************************************/ | ||
| 17 | |||
| 18 | #include "programlink.h" | ||
| 19 | #include "programchain.h" | ||
| 20 | |||
| 21 | ProgramLink::ProgramLink() | ||
| 22 | { | ||
| 23 | } | ||
| 24 | |||
| 25 | ProgramLink::~ProgramLink() | ||
| 26 | { | ||
| 27 | } | ||
| 28 | |||
| 29 | LinkMessage *ProgramLink::sendIRM( LinkMessage *pMsgOut ) | ||
| 30 | { | ||
| 31 | return pChain->broadcastIRM( pMsgOut, this ); | ||
| 32 | } | ||
| 33 | |||
| 34 | void ProgramLink::setChain( ProgramChain *pNewChain ) | ||
| 35 | { | ||
| 36 | pChain = pNewChain; | ||
| 37 | } | ||
| 38 | |||
| 39 | /* | ||
| 40 | void ProgramLink::postMessage( LinkMessage *pMsg, int nLvl ) | ||
| 41 | { | ||
| 42 | if( nLvl == msgToChain ) | ||
| 43 | { | ||
| 44 | qMsgToChain.enqueue( pMsg ); | ||
| 45 | } | ||
| 46 | else if( nLvl == msgToLink ) | ||
| 47 | { | ||
| 48 | qMsgToLink.enqueue( pMsg ); | ||
| 49 | } | ||
| 50 | else | ||
| 51 | { | ||
| 52 | // ERROR! | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | LinkMessage *ProgramLink::getMessage( int nLvl ) | ||
| 57 | { | ||
| 58 | if( nLvl == msgToChain ) | ||
| 59 | { | ||
| 60 | return (LinkMessage *)qMsgToChain.dequeue(); | ||
| 61 | } | ||
| 62 | else if( nLvl == msgToLink ) | ||
| 63 | { | ||
| 64 | return (LinkMessage *)qMsgToLink.dequeue(); | ||
| 65 | } | ||
| 66 | else | ||
| 67 | { | ||
| 68 | // ERROR! | ||
| 69 | } | ||
| 70 | } | ||
| 71 | */ | ||
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 @@ | |||
| 1 | #ifndef PROGRAMLINK_H | ||
| 2 | #define PROGRAMLINK_H | ||
| 3 | |||
| 4 | class ProgramLink; | ||
| 5 | #include "queue.h" | ||
| 6 | #include "linkmessage.h" | ||
| 7 | #include "programchain.h" | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Program Link is the base class for any object that will be a piece of the | ||
| 11 | * main program chain loop. | ||
| 12 | *@author Mike Buland | ||
| 13 | */ | ||
| 14 | class ProgramLink | ||
| 15 | { | ||
| 16 | friend class ProgramChain; | ||
| 17 | public: | ||
| 18 | /** | ||
| 19 | * Construct a program link. | ||
| 20 | */ | ||
| 21 | ProgramLink(); | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Deconstruct. | ||
| 25 | */ | ||
| 26 | virtual ~ProgramLink(); | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Initialization code required for a link that wasn't performed in the | ||
| 30 | * constructor. | ||
| 31 | *@returns true if initialization was successful. A false value will halt | ||
| 32 | * the chain. | ||
| 33 | */ | ||
| 34 | virtual bool init()=0; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * DeInitialization code that should happen, but doesn't belong in the | ||
| 38 | * destructor. | ||
| 39 | *@returns true means everything worked, false means failure, but is | ||
| 40 | * meaningless. | ||
| 41 | */ | ||
| 42 | virtual bool deInit()=0; | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Executed once per link per chain pass. Contains the guts of the program. | ||
| 46 | *@returns true if everything went well. A false value will halt the chain. | ||
| 47 | */ | ||
| 48 | virtual bool timeSlice()=0; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * This must be handled in order to process Instant Response Messages. | ||
| 52 | * This function should return null on all messages that it doesn't | ||
| 53 | * understand how to handle, and construct new messages to return to sender | ||
| 54 | * in the cases where it does understand. | ||
| 55 | *@param pMsgIn The message that must be processed. | ||
| 56 | *@returns Either a new message in cases where a response is required, | ||
| 57 | * or null if nothing needs to be done by this link. | ||
| 58 | */ | ||
| 59 | virtual LinkMessage *processIRM( LinkMessage *pMsgIn ) = 0; | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Broadcast a LinkMessage to all other links in the system. Each other | ||
| 63 | * link will get a call of their processIRM function. If the message gets | ||
| 64 | * a response then you will regain control immediately, otherwise the system | ||
| 65 | * will give all other Links a chance to respond before returning NULL. | ||
| 66 | *@param pMsgOut The message to broadcast. | ||
| 67 | *@returns The message response, or NULL if no Link understood your message. | ||
| 68 | */ | ||
| 69 | LinkMessage *sendIRM( LinkMessage *pMsgOut ); | ||
| 70 | |||
| 71 | private: | ||
| 72 | /** | ||
| 73 | * Set which chain we're assosiated with. This is hope IRM messages make | ||
| 74 | * it out to the rest of the world. | ||
| 75 | *@param pNewChain A pointer to the containing program chain. | ||
| 76 | */ | ||
| 77 | void setChain( class ProgramChain *pNewChain ); | ||
| 78 | |||
| 79 | /** | ||
| 80 | * The pointer to the containing chain. | ||
| 81 | */ | ||
| 82 | class ProgramChain *pChain; | ||
| 83 | /* | ||
| 84 | void postMessage( LinkMessage *pMsg, int nLvl ); | ||
| 85 | LinkMessage *getMessage( int nLvl ); | ||
| 86 | |||
| 87 | enum | ||
| 88 | { | ||
| 89 | msgToChain, | ||
| 90 | msgToLink | ||
| 91 | }; | ||
| 92 | |||
| 93 | private: | ||
| 94 | Queue qMsgToChain; | ||
| 95 | Queue qMsgToLink; | ||
| 96 | */ | ||
| 97 | }; | ||
| 98 | |||
| 99 | #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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | * Copyright (C) 2003 by Mike Buland * | ||
| 3 | * eichlan@yf-soft.com * | ||
| 4 | * * | ||
| 5 | * This program is free software; you can redistribute it and/or modify * | ||
| 6 | * it under the terms of the GNU General Public License as published by * | ||
| 7 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 8 | * (at your option) any later version. * | ||
| 9 | ***************************************************************************/ | ||
| 10 | #include "protocol.h" | ||
| 11 | |||
| 12 | Protocol::Protocol() | ||
| 13 | { | ||
| 14 | pConnection = NULL; | ||
| 15 | } | ||
| 16 | |||
| 17 | Protocol::~Protocol() | ||
| 18 | { | ||
| 19 | } | ||
| 20 | |||
| 21 | void Protocol::setConnection( Connection *pNewConnection ) | ||
| 22 | { | ||
| 23 | pConnection = pNewConnection; | ||
| 24 | |||
| 25 | onNewConnection(); | ||
| 26 | } | ||
| 27 | |||
| 28 | Connection *Protocol::getConnection() | ||
| 29 | { | ||
| 30 | return pConnection; | ||
| 31 | } | ||
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 @@ | |||
| 1 | #ifndef PROTOCOL_H | ||
| 2 | #define PROTOCOL_H | ||
| 3 | |||
| 4 | #include "connection.h" | ||
| 5 | |||
| 6 | /** This is the template for a class that handles specialized input and output | ||
| 7 | * to connections of different types with different protocols. | ||
| 8 | *@author Mike Buland | ||
| 9 | */ | ||
| 10 | class Protocol | ||
| 11 | { | ||
| 12 | public: | ||
| 13 | /** Constructor */ | ||
| 14 | Protocol(); | ||
| 15 | /** Deconstructor */ | ||
| 16 | virtual ~Protocol(); | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Function is called every time there is new data on the line. This is | ||
| 20 | * called directly from the Connection class to process data. This is not | ||
| 21 | * called whever there is pending data on the input, but every time new data | ||
| 22 | * is added to the input buffer. | ||
| 23 | *@returns True if processing went alright, false if something went wrong, | ||
| 24 | * I suppose. In truth this value is thrown away right now. | ||
| 25 | *@todo Either make a return value of false mean something, or make these | ||
| 26 | * void. | ||
| 27 | */ | ||
| 28 | virtual bool onNewData()=0; | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Function is called when there is a new connection. This should only | ||
| 32 | * happen once per Protocol object, but gives each protocol object a | ||
| 33 | * chance to perform connection handshaking and initialization at a point | ||
| 34 | * where they know that they have a handle to an active Connection. | ||
| 35 | *@returns See onNewData | ||
| 36 | */ | ||
| 37 | virtual bool onNewConnection()=0; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Sets the Protocol's Connection object. This is rather important, and | ||
| 41 | * handled usually by the ConnectionManager. | ||
| 42 | *@param pNewConnection The Connection object that this protocol will use to | ||
| 43 | * deal with the outside world. | ||
| 44 | */ | ||
| 45 | void setConnection( class Connection *pNewConnection ); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Get a pointer to this object's Connection object, or NULL if one was | ||
| 49 | * never set. If used with the ConnectionManager that should never happen. | ||
| 50 | *@returns A pointer to the active Connection. | ||
| 51 | */ | ||
| 52 | Connection *getConnection(); | ||
| 53 | |||
| 54 | private: | ||
| 55 | class Connection *pConnection; /**< The pointer to the Connection. */ | ||
| 56 | }; | ||
| 57 | |||
| 58 | #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 @@ | |||
| 1 | #include "protocoltelnet.h" | ||
| 2 | #include <string.h> | ||
| 3 | |||
| 4 | ProtocolTelnet::ProtocolTelnet() | ||
| 5 | { | ||
| 6 | nTermType = termUnInited; | ||
| 7 | bEchoOn = true; | ||
| 8 | } | ||
| 9 | |||
| 10 | ProtocolTelnet::~ProtocolTelnet() | ||
| 11 | { | ||
| 12 | } | ||
| 13 | |||
| 14 | bool ProtocolTelnet::onNewConnection() | ||
| 15 | { | ||
| 16 | Connection *pCon = getConnection(); | ||
| 17 | |||
| 18 | pCon->appendOutput( (char)IAC ); | ||
| 19 | pCon->appendOutput( (char)WILL ); | ||
| 20 | pCon->appendOutput( (char)SUPPRESSGA ); | ||
| 21 | |||
| 22 | pCon->appendOutput( (char)IAC ); | ||
| 23 | pCon->appendOutput( (char)DO ); | ||
| 24 | pCon->appendOutput( (char)SUPPRESSGA ); | ||
| 25 | |||
| 26 | pCon->appendOutput( (char)IAC ); | ||
| 27 | pCon->appendOutput( (char)DONT ); | ||
| 28 | pCon->appendOutput( (char)TERMTYPE ); | ||
| 29 | |||
| 30 | // pCon->appendOutput( IAC ); | ||
| 31 | // pCon->appendOutput( SB ); | ||
| 32 | // pCon->appendOutput( TERMTYPE ); | ||
| 33 | // pCon->appendOutput( 1 ); | ||
| 34 | // pCon->appendOutput( IAC ); | ||
| 35 | // pCon->appendOutput( SE ); | ||
| 36 | |||
| 37 | pCon->appendOutput( (char)IAC ); | ||
| 38 | pCon->appendOutput( (char)DONT ); | ||
| 39 | pCon->appendOutput( (char)ECHO ); | ||
| 40 | |||
| 41 | pCon->appendOutput( (char)IAC ); | ||
| 42 | pCon->appendOutput( (char)WILL ); | ||
| 43 | pCon->appendOutput( (char)ECHO ); | ||
| 44 | |||
| 45 | // 255(IAC),251(WILL),3 | ||
| 46 | } | ||
| 47 | |||
| 48 | bool ProtocolTelnet::onNewData() | ||
| 49 | { | ||
| 50 | Connection *pCon = getConnection(); | ||
| 51 | if( !pCon->hasInput() ) | ||
| 52 | { | ||
| 53 | return true; | ||
| 54 | } | ||
| 55 | |||
| 56 | int nInSize = pCon->getInputAmnt(); | ||
| 57 | char *lpInStr = (char *)pCon->getInput(); | ||
| 58 | |||
| 59 | // Here we interpret the basic commands and un-encapsulate them, so to | ||
| 60 | // speak. We'll allow this, even if the terminal is in raw mode, we | ||
| 61 | // just won't send anything in response... | ||
| 62 | for( int j = 0; j < nInSize; j++ ) | ||
| 63 | { | ||
| 64 | switch( (unsigned char)lpInStr[j] ) | ||
| 65 | { | ||
| 66 | case '\r': | ||
| 67 | fbEdited.appendData('\n'); | ||
| 68 | if( bEchoOn ) pCon->appendOutput("\n\r"); | ||
| 69 | break; | ||
| 70 | |||
| 71 | case '\n': | ||
| 72 | break; | ||
| 73 | |||
| 74 | case '\177': // backspace | ||
| 75 | if( fbEdited.getLength() > 0 ) | ||
| 76 | { | ||
| 77 | fbEdited.usedData( -1 ); // Delete one char from the end | ||
| 78 | if( bEchoOn ) pCon->appendOutput(ESC "[D"); // Move the cursor back one | ||
| 79 | if( bEchoOn ) pCon->appendOutput(ESC "[P"); // Delete one character | ||
| 80 | } | ||
| 81 | break; | ||
| 82 | |||
| 83 | case '\x1B': // escape sequence | ||
| 84 | if( (unsigned char)lpInStr[j+1] == '[' ) | ||
| 85 | { | ||
| 86 | switch( (unsigned char)lpInStr[j+2] ) | ||
| 87 | { | ||
| 88 | case 'A': // Up | ||
| 89 | break; | ||
| 90 | |||
| 91 | case 'B': // Down | ||
| 92 | break; | ||
| 93 | |||
| 94 | case 'C': // Right | ||
| 95 | break; | ||
| 96 | |||
| 97 | case 'D': // Left | ||
| 98 | break; | ||
| 99 | } | ||
| 100 | j+=2; | ||
| 101 | } | ||
| 102 | break; | ||
| 103 | |||
| 104 | case 0: // NOP: No operation | ||
| 105 | break; | ||
| 106 | |||
| 107 | case IAC: // IAC: Interpret as command | ||
| 108 | switch( lpInStr[j+1] ) | ||
| 109 | { | ||
| 110 | case SE: // SE: End of subnegotiation parameters. | ||
| 111 | break; | ||
| 112 | |||
| 113 | case NOP: // NOP: No operation | ||
| 114 | break; | ||
| 115 | |||
| 116 | 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. | ||
| 117 | break; | ||
| 118 | |||
| 119 | case BRK: // BRK: Break. Indicates that the "break" or "attention" key was hit. | ||
| 120 | break; | ||
| 121 | |||
| 122 | case IP: // IP: Suspend, interrupt or abort the process to which the NVT is connected. | ||
| 123 | break; | ||
| 124 | |||
| 125 | case AO: // AO: Abort output. Allows the current process to run to completion but do not send its output to the user. | ||
| 126 | break; | ||
| 127 | |||
| 128 | case AYT: // AYT: Are you there. Send back to the NVT some visible evidence that the AYT was received. | ||
| 129 | break; | ||
| 130 | |||
| 131 | case EC: // EC: Erase character. The receiver should delete the last preceding undeleted character from the data stream. | ||
| 132 | break; | ||
| 133 | |||
| 134 | case EL: // EL: Erase line. Delete characters from the data stream back to but not including the previous CRLF. | ||
| 135 | break; | ||
| 136 | |||
| 137 | case GA: // GA: Go ahead. Used, under certain circumstances, to tell the other end that it can transmit. | ||
| 138 | break; | ||
| 139 | |||
| 140 | case SB: // SB: Subnegotiation of the indicated option follows. | ||
| 141 | switch( lpInStr[j+2] ) | ||
| 142 | { | ||
| 143 | case TERMTYPE: | ||
| 144 | if( lpInStr[j+3] == 0 ) | ||
| 145 | { | ||
| 146 | for( int k = 0; j+4+k < nInSize; k++ ) | ||
| 147 | { | ||
| 148 | if( (unsigned char)lpInStr[j+4+k] == IAC && | ||
| 149 | (unsigned char)lpInStr[j+5+k] == SE ) | ||
| 150 | { | ||
| 151 | lpInStr[j+4+k] = 0; | ||
| 152 | //@TODO: Do something with the term type... | ||
| 153 | printf("Term type: %s\n", &lpInStr[j+4] ); | ||
| 154 | j += 5+k; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | else | ||
| 159 | { | ||
| 160 | } | ||
| 161 | break; | ||
| 162 | |||
| 163 | default: | ||
| 164 | //printf("unknown subnegotiation parameters! (%d)\n", lpInStr[j+2] ); | ||
| 165 | break; | ||
| 166 | } | ||
| 167 | break; | ||
| 168 | |||
| 169 | case WILL: // WILL: Indicates the desire to begin performing | ||
| 170 | switch( lpInStr[j+2] ) | ||
| 171 | { | ||
| 172 | case SUPPRESSGA: | ||
| 173 | j += 2; | ||
| 174 | // pCon->usedInput( 3 ); | ||
| 175 | break; | ||
| 176 | |||
| 177 | case TERMTYPE: | ||
| 178 | j += 2; | ||
| 179 | // pCon->usedInput( 3 ); | ||
| 180 | break; | ||
| 181 | |||
| 182 | case ECHO: | ||
| 183 | j += 2; | ||
| 184 | // pCon->usedInput( 3 ); | ||
| 185 | break; | ||
| 186 | |||
| 187 | case NAWS: | ||
| 188 | default: | ||
| 189 | pCon->appendOutput( (char)ESC[0] ); | ||
| 190 | pCon->appendOutput( (char)DONT ); | ||
| 191 | pCon->appendOutput( lpInStr[j+2] ); | ||
| 192 | //printf("unknown will command used! (%d)\n", lpInStr[j+2] ); | ||
| 193 | j += 2; | ||
| 194 | break; | ||
| 195 | } | ||
| 196 | break; | ||
| 197 | |||
| 198 | case WONT: // WONT: Indicates the refusal to perform | ||
| 199 | switch( lpInStr[j+2] ) | ||
| 200 | { | ||
| 201 | case ECHO: | ||
| 202 | j += 2; | ||
| 203 | // pCon->usedInput( 3 ); | ||
| 204 | break; | ||
| 205 | |||
| 206 | default: | ||
| 207 | //printf("unknown wont command used! (%d)\n", lpInStr[j+2] ); | ||
| 208 | j += 2; | ||
| 209 | break; | ||
| 210 | } | ||
| 211 | break; | ||
| 212 | |||
| 213 | case DO: // DO: Indicates the request that the other party perform | ||
| 214 | switch( lpInStr[j+2] ) | ||
| 215 | { | ||
| 216 | case ECHO: | ||
| 217 | j += 2; | ||
| 218 | break; | ||
| 219 | |||
| 220 | case SUPPRESSGA: | ||
| 221 | j += 2; | ||
| 222 | break; | ||
| 223 | |||
| 224 | default: | ||
| 225 | pCon->appendOutput( (char)ESC[0] ); | ||
| 226 | pCon->appendOutput( (char)DONT ); | ||
| 227 | pCon->appendOutput( lpInStr[j+2] ); | ||
| 228 | //printf("unknown do command used! (%d)\n", lpInStr[j+2] ); | ||
| 229 | j += 2; | ||
| 230 | break; | ||
| 231 | } | ||
| 232 | // pCon->usedInput( 3 ); | ||
| 233 | break; | ||
| 234 | |||
| 235 | case DONT: // DONT: Indicates the demand that the other party stop performing | ||
| 236 | switch( lpInStr[j+2] ) | ||
| 237 | { | ||
| 238 | case ECHO: | ||
| 239 | j += 2; | ||
| 240 | // pCon->usedInput( 3 ); | ||
| 241 | break; | ||
| 242 | |||
| 243 | default: | ||
| 244 | printf("unknown dont command used! (%d)\n", lpInStr[j+2] ); | ||
| 245 | j += 2; | ||
| 246 | break; | ||
| 247 | } | ||
| 248 | break; | ||
| 249 | } | ||
| 250 | break; | ||
| 251 | |||
| 252 | default: | ||
| 253 | fbEdited.appendData( lpInStr[j] ); | ||
| 254 | if( bEchoOn ) pCon->appendOutput( lpInStr[j] ); | ||
| 255 | break; | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | pCon->usedInput( pCon->getInputAmnt() ); | ||
| 260 | |||
| 261 | return true; | ||
| 262 | } | ||
| 263 | |||
| 264 | char *ProtocolTelnet::getLine( bool bFullOnly ) | ||
| 265 | { | ||
| 266 | int i = fbEdited.findChar('\n'); | ||
| 267 | |||
| 268 | if( i < 0 ) | ||
| 269 | { | ||
| 270 | if( bFullOnly == false ) | ||
| 271 | { | ||
| 272 | i = fbEdited.getLength(); | ||
| 273 | } | ||
| 274 | else | ||
| 275 | { | ||
| 276 | return NULL; | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | char *lpStr = new char[i+1]; | ||
| 281 | strncpy( lpStr, fbEdited.getData(), i ); | ||
| 282 | lpStr[i] = '\0'; | ||
| 283 | |||
| 284 | fbEdited.usedData( i+1 ); | ||
| 285 | |||
| 286 | return lpStr; | ||
| 287 | } | ||
| 288 | |||
| 289 | char *ProtocolTelnet::peekLine( bool bFullOnly ) | ||
| 290 | { | ||
| 291 | int i = fbEdited.findChar('\n'); | ||
| 292 | |||
| 293 | if( i < 0 ) | ||
| 294 | { | ||
| 295 | if( bFullOnly == false ) | ||
| 296 | { | ||
| 297 | i = fbEdited.getLength(); | ||
| 298 | } | ||
| 299 | else | ||
| 300 | { | ||
| 301 | return NULL; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | char *lpStr = new char[i+1]; | ||
| 306 | strncpy( lpStr, fbEdited.getData(), i ); | ||
| 307 | lpStr[i] = '\0'; | ||
| 308 | |||
| 309 | return lpStr; | ||
| 310 | } | ||
| 311 | |||
| 312 | void ProtocolTelnet::setEcho( bool bEchoOn ) | ||
| 313 | { | ||
| 314 | this->bEchoOn = bEchoOn; | ||
| 315 | } | ||
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 @@ | |||
| 1 | #ifndef PROTOCOLTELNET_H | ||
| 2 | #define PROTOCOLTELNET_H | ||
| 3 | |||
| 4 | #include "protocol.h" | ||
| 5 | #include "flexbuf.h" | ||
| 6 | |||
| 7 | #define ESC "\x1B" /**< A telnet escape code. */ | ||
| 8 | |||
| 9 | /** Handles all specialized protocol actions related to the telnet protocol. | ||
| 10 | * This includes setting modes, non-scrollable regions, and so on. | ||
| 11 | *@author Mike Buland | ||
| 12 | */ | ||
| 13 | class ProtocolTelnet : public Protocol | ||
| 14 | { | ||
| 15 | public: | ||
| 16 | ProtocolTelnet(); | ||
| 17 | ~ProtocolTelnet(); | ||
| 18 | |||
| 19 | bool onNewData(); | ||
| 20 | bool onNewConnection(); | ||
| 21 | |||
| 22 | char *getLine( bool bFullOnly = true ); | ||
| 23 | char *peekLine( bool bFullOnly = true ); | ||
| 24 | |||
| 25 | void setEcho( bool bEchoOn = true ); | ||
| 26 | |||
| 27 | enum | ||
| 28 | { | ||
| 29 | termUnInited, | ||
| 30 | termRaw, | ||
| 31 | termUnknown, | ||
| 32 | termVT220, | ||
| 33 | termXTerm | ||
| 34 | }; | ||
| 35 | |||
| 36 | enum | ||
| 37 | { | ||
| 38 | SE = 240, // SE: End of subnegotiation parameters. | ||
| 39 | NOP = 241, // NOP: No operation | ||
| 40 | 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. | ||
| 41 | BRK = 243, // BRK: Break. Indicates that the "break" or "attention" key was hit. | ||
| 42 | IP = 244, // IP: Suspend, interrupt or abort the process to which the NVT is connected. | ||
| 43 | AO = 245, // AO: Abort output. Allows the current process to run to completion but do not send its output to the user. | ||
| 44 | AYT = 246, // AYT: Are you there. Send back to the NVT some visible evidence that the AYT was received. | ||
| 45 | EC = 247, // EC: Erase character. The receiver should delete the last preceding undeleted character from the data stream. | ||
| 46 | EL = 248, // EL: Erase line. Delete characters from the data stream back to but not including the previous CRLF. | ||
| 47 | GA = 249, // GA: Go ahead. Used, under certain circumstances, to tell the other end that it can transmit. | ||
| 48 | SB = 250, // SB: Subnegotiation of the indicated option follows. | ||
| 49 | WILL = 251, // WILL: Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. | ||
| 50 | WONT = 252, // WONT: Indicates the refusal to perform, or continue performing, the indicated option. | ||
| 51 | 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. | ||
| 52 | 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. | ||
| 53 | IAC = 255 // IAC: Interpret as command | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum | ||
| 57 | { | ||
| 58 | ECHO = 1, // Explain who'll echo | ||
| 59 | SUPPRESSGA = 3, // Suppress Go Ahead | ||
| 60 | TERMTYPE = 24, // Terminal Type | ||
| 61 | NAWS = 31, // Window size | ||
| 62 | TERMSPEED = 32, // Terminal Speed | ||
| 63 | LINEMODE = 34 // Linemode | ||
| 64 | }; | ||
| 65 | |||
| 66 | private: | ||
| 67 | int nTermType; | ||
| 68 | |||
| 69 | int nTermWidth; | ||
| 70 | int nTermHeight; | ||
| 71 | |||
| 72 | FlexBuf fbEdited; | ||
| 73 | |||
| 74 | bool bEchoOn; | ||
| 75 | }; | ||
| 76 | |||
| 77 | #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 @@ | |||
| 1 | #include "queue.h" | ||
| 2 | |||
| 3 | void Queue::enqueue( void *data ) | ||
| 4 | { | ||
| 5 | lQueueData.append( data ); | ||
| 6 | } | ||
| 7 | |||
| 8 | void *Queue::dequeue() | ||
| 9 | { | ||
| 10 | void *dat = lQueueData[0]; | ||
| 11 | if( dat != NULL ) | ||
| 12 | { | ||
| 13 | lQueueData.deleteAt( 0 ); | ||
| 14 | } | ||
| 15 | return dat; | ||
| 16 | } | ||
| 17 | |||
| 18 | bool Queue::isEmpty() | ||
| 19 | { | ||
| 20 | return lQueueData.isEmpty(); | ||
| 21 | } | ||
| 22 | |||
| 23 | void Queue::empty() | ||
| 24 | { | ||
| 25 | lQueueData.empty(); | ||
| 26 | } | ||
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 @@ | |||
| 1 | #ifndef QUEUE_H | ||
| 2 | #define QUEUE_H | ||
| 3 | #include "linkedlist.h" | ||
| 4 | |||
| 5 | /** | ||
| 6 | * An ultra-simple queue implementation. It just uses a linked list as it's | ||
| 7 | * container so we don't have to worry about anything! | ||
| 8 | *@author Mike Buland | ||
| 9 | */ | ||
| 10 | class Queue | ||
| 11 | { | ||
| 12 | public: | ||
| 13 | /** | ||
| 14 | * Puts a new item at the end of the queue. | ||
| 15 | *@param data A new value to put at the end of the queue. | ||
| 16 | */ | ||
| 17 | void enqueue( void *data ); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Gets the begining item of the queue off and returns it. | ||
| 21 | *@returns The value at the front of the queue. | ||
| 22 | */ | ||
| 23 | void *dequeue(); | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Checks if the queueu is empty. | ||
| 27 | *@returns True if the queueu is empty, and false if it has things in it. | ||
| 28 | */ | ||
| 29 | bool isEmpty(); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Empty the queue. | ||
| 33 | */ | ||
| 34 | void empty(); | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Get a pointer to the internal list object. | ||
| 38 | *@returns A pointer to the internal list object. | ||
| 39 | */ | ||
| 40 | LinkedList *getList() { return &lQueueData; }; | ||
| 41 | |||
| 42 | private: | ||
| 43 | LinkedList lQueueData; /**< Where all of the real data is stored. */ | ||
| 44 | }; | ||
| 45 | #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 @@ | |||
| 1 | // | ||
| 2 | // C++ Implementation: ringlist | ||
| 3 | // | ||
| 4 | // Description: | ||
| 5 | // | ||
| 6 | // | ||
| 7 | // Author: Mike Buland <eichlan@yf-soft.com>, (C) 2005 | ||
| 8 | // | ||
| 9 | // Copyright: See COPYING file that comes with this distribution | ||
| 10 | // | ||
| 11 | // | ||
| 12 | #include <stdlib.h> | ||
| 13 | |||
| 14 | #include "ringlist.h" | ||
| 15 | |||
| 16 | RingList::RingList( int nInitSize ) | ||
| 17 | : List() | ||
| 18 | { | ||
| 19 | nFirstIndex = 0; | ||
| 20 | nRealLength = nInitSize; | ||
| 21 | nDataLength = 0; | ||
| 22 | apData = new void*[nInitSize]; | ||
| 23 | pPushBuf = NULL; | ||
| 24 | } | ||
| 25 | |||
| 26 | RingList::~RingList() | ||
| 27 | { | ||
| 28 | delete[] apData; | ||
| 29 | } | ||
| 30 | |||
| 31 | void *RingList::getAt( int nIndex ) | ||
| 32 | { | ||
| 33 | if( nIndex < 0 || nIndex >= nDataLength ) | ||
| 34 | { | ||
| 35 | return NULL; | ||
| 36 | } | ||
| 37 | |||
| 38 | return apData[(nFirstIndex+nIndex)%nRealLength]; | ||
| 39 | } | ||
| 40 | |||
| 41 | void RingList::append( void *pData ) | ||
| 42 | { | ||
| 43 | int nIndex = (nFirstIndex+nDataLength)%nRealLength; | ||
| 44 | |||
| 45 | pPushBuf = apData[nIndex]; | ||
| 46 | apData[nIndex] = pData; | ||
| 47 | |||
| 48 | if( nDataLength == nRealLength ) | ||
| 49 | { | ||
| 50 | nFirstIndex = (nFirstIndex+1)%nRealLength; | ||
| 51 | } | ||
| 52 | else | ||
| 53 | { | ||
| 54 | nDataLength++; | ||
| 55 | // We really didn't need it this time... | ||
| 56 | pPushBuf = NULL; | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | void RingList::insertBefore( void *pData, int nPos ) | ||
| 61 | { | ||
| 62 | // Not implemented right now, don't even try it! | ||
| 63 | } | ||
| 64 | |||
| 65 | int RingList::getSize() | ||
| 66 | { | ||
| 67 | return nDataLength; | ||
| 68 | } | ||
| 69 | |||
| 70 | bool RingList::isEmpty() | ||
| 71 | { | ||
| 72 | return nDataLength==0; | ||
| 73 | } | ||
| 74 | |||
| 75 | void RingList::deleteAt( int nIndex ) | ||
| 76 | { | ||
| 77 | // Also not implemented yet | ||
| 78 | } | ||
| 79 | |||
| 80 | void RingList::empty() | ||
| 81 | { | ||
| 82 | nFirstIndex = 0; | ||
| 83 | nDataLength = 0; | ||
| 84 | } | ||
| 85 | |||
| 86 | void RingList::setSize( int nNewSize ) | ||
| 87 | { | ||
| 88 | if( apData ) | ||
| 89 | { | ||
| 90 | delete[] apData; | ||
| 91 | } | ||
| 92 | nFirstIndex = 0; | ||
| 93 | nRealLength = nNewSize; | ||
| 94 | nDataLength = 0; | ||
| 95 | apData = new void*[nNewSize]; | ||
| 96 | } | ||
| 97 | |||
| 98 | void RingList::setAt( int nIndex, void *pData ) | ||
| 99 | { | ||
| 100 | apData[(nIndex+nFirstIndex)%nRealLength] = pData; | ||
| 101 | } | ||
| 102 | |||
| 103 | void *RingList::getPushBuf() | ||
| 104 | { | ||
| 105 | return pPushBuf; | ||
| 106 | } | ||
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 @@ | |||
| 1 | #ifndef RINGLIST_H | ||
| 2 | #define RINGLIST_H | ||
| 3 | |||
| 4 | #include "list.h" | ||
| 5 | |||
| 6 | /** | ||
| 7 | * A RingList or Ring Buffer implementation. This is a list that never grows in | ||
| 8 | * size once it is created, but instead once it is full new items added to the | ||
| 9 | * RingList replace the oldest items and the zero-index is virtually shifted. | ||
| 10 | * Since no data is actually moved when zero-index moves, this is very | ||
| 11 | * efficient. | ||
| 12 | * <br> | ||
| 13 | * The items removed are not actually deleted by the RingList, so instead they | ||
| 14 | * are first moved into a temporary "Push Buffer" that can be accessed so that | ||
| 15 | * elements pushed off the edge of the RingList can be accessed for cleanup. | ||
| 16 | *@author Mike Buland | ||
| 17 | */ | ||
| 18 | class RingList : public List | ||
| 19 | { | ||
| 20 | public: | ||
| 21 | /** | ||
| 22 | * Construct a RingList with a fixed initial size. This size never changes | ||
| 23 | * unless setSize is called later during normal operation. | ||
| 24 | *@param nInitSize The number of elements to allocate. | ||
| 25 | */ | ||
| 26 | RingList( int nInitSize ); | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Clean up the data structures, but not the contained elements. | ||
| 30 | */ | ||
| 31 | ~RingList(); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Get an element at the specified index. | ||
| 35 | *@param nIndex The index of the element to retreive. | ||
| 36 | *@returns A pointer to the requested element, or NULL if the element is | ||
| 37 | * not found or not initialized yet. | ||
| 38 | */ | ||
| 39 | void *getAt( int nIndex ); | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Append an element to the end of the list, overwriting the begining if | ||
| 43 | * necesarry. | ||
| 44 | *@param pData The pointer to append to the RingList. | ||
| 45 | */ | ||
| 46 | void append( void *pData ); | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Insert an element before another in the RingList, pushing all after it | ||
| 50 | * down the list. | ||
| 51 | *@param pData The data to insert. | ||
| 52 | *@param nPos The position that new the element should occupy in the list. | ||
| 53 | */ | ||
| 54 | void insertBefore( void *pData, int nPos = 0 ); | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Get the size of the array. | ||
| 58 | */ | ||
| 59 | int getSize(); | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Is the RingList empty? | ||
| 63 | *@returns True if it is empty, false otherwise. | ||
| 64 | */ | ||
| 65 | bool isEmpty(); | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Delete an element in the list, moving all later elements down one index. | ||
| 69 | *@param nIndex The index of the element to delete. | ||
| 70 | */ | ||
| 71 | void deleteAt( int nIndex ); | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Remove all elements from the RingList. | ||
| 75 | */ | ||
| 76 | void empty(); | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Set a new size for the RingList. Be careful with this one, if shrinking | ||
| 80 | * this may quietly create a memory leak. | ||
| 81 | *@param nNewSize The new size to set the RingList to. | ||
| 82 | *@todo Either fix this memory leak somehow or remove this function. | ||
| 83 | */ | ||
| 84 | void setSize( int nNewSize ); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Set a specific element to a new value. | ||
| 88 | *@param nIndex The zero-based index to change the value of. | ||
| 89 | *@param pData The data to put at the location specified by nIndex. | ||
| 90 | */ | ||
| 91 | void setAt( int nIndex, void *pData ); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Retrieve the contents of the push buffer. This is the data that is | ||
| 95 | * pushed off the end of the array if you append data and the list is full. | ||
| 96 | * This should be checked after every append operation to be sure there | ||
| 97 | * isn't anything that needs deleting. | ||
| 98 | *@returns The last value pushed off the RingList, or NULL if nothing was | ||
| 99 | * pushed off. | ||
| 100 | */ | ||
| 101 | void *getPushBuf(); | ||
| 102 | |||
| 103 | private: | ||
| 104 | int nFirstIndex; /**< The index to be translated as zero. */ | ||
| 105 | int nRealLength; /**< The Amount of storage space available. */ | ||
| 106 | int nDataLength; /**< The number of elements filled in. */ | ||
| 107 | void **apData; /**< The actual data storage. */ | ||
| 108 | void *pPushBuf; /**< The push buffer. */ | ||
| 109 | |||
| 110 | }; | ||
| 111 | |||
| 112 | #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 @@ | |||
| 1 | #include "stack.h" | ||
| 2 | |||
| 3 | void Stack::push( void *data ) | ||
| 4 | { | ||
| 5 | lStackData.append( data ); | ||
| 6 | } | ||
| 7 | |||
| 8 | void *Stack::top() | ||
| 9 | { | ||
| 10 | return lStackData.getAt( lStackData.getSize()-1 ); | ||
| 11 | } | ||
| 12 | |||
| 13 | void Stack::pop() | ||
| 14 | { | ||
| 15 | lStackData.deleteAt( lStackData.getSize()-1 ); | ||
| 16 | } | ||
| 17 | |||
| 18 | void *Stack::poptop() | ||
| 19 | { | ||
| 20 | void *dat = top(); | ||
| 21 | pop(); | ||
| 22 | return dat; | ||
| 23 | } | ||
| 24 | |||
| 25 | bool Stack::isEmpty() | ||
| 26 | { | ||
| 27 | return lStackData.isEmpty(); | ||
| 28 | } | ||
| 29 | |||
| 30 | void Stack::empty() | ||
| 31 | { | ||
| 32 | lStackData.empty(); | ||
| 33 | } | ||
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 @@ | |||
| 1 | #ifndef STACK_H | ||
| 2 | #define STACK_H | ||
| 3 | #include "linkedlist.h" | ||
| 4 | |||
| 5 | /** An ultra-simple stack implementation that just uses a linked list. | ||
| 6 | *@author Mike Buland | ||
| 7 | */ | ||
| 8 | class Stack | ||
| 9 | { | ||
| 10 | public: | ||
| 11 | /** Pushes a new value onto the top of the stack. | ||
| 12 | *@param data A new value for the stack. | ||
| 13 | *@author Mike Buland | ||
| 14 | */ | ||
| 15 | void push( void *data ); | ||
| 16 | |||
| 17 | /** Returns the top value off of the stack, but doesn't remove it from the | ||
| 18 | * stack. | ||
| 19 | *@returns The value at the top of the stack. | ||
| 20 | *@author Mike Buland | ||
| 21 | */ | ||
| 22 | void *top(); | ||
| 23 | |||
| 24 | /** Pops the top item off of the stack. | ||
| 25 | *@author Mike Buland | ||
| 26 | */ | ||
| 27 | void pop(); | ||
| 28 | |||
| 29 | /** Gets the top item off of the stack, pops it off the stack, and returns | ||
| 30 | * it. | ||
| 31 | *@returns The value at the top of the stack. | ||
| 32 | *@author Mike Buland | ||
| 33 | */ | ||
| 34 | void *poptop(); | ||
| 35 | |||
| 36 | /** Checks if the stack is empty. | ||
| 37 | *@returns True if the stack is empty, and false if it has things in it. | ||
| 38 | *@author Mike Buland | ||
| 39 | */ | ||
| 40 | bool isEmpty(); | ||
| 41 | |||
| 42 | /** Empty the stack. | ||
| 43 | *@author Mike Buland | ||
| 44 | */ | ||
| 45 | void empty(); | ||
| 46 | |||
| 47 | private: | ||
| 48 | LinkedList lStackData; /**< The actual stack data. */ | ||
| 49 | }; | ||
| 50 | #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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <iostream> | ||
| 3 | #include "hashtable.h" | ||
| 4 | #include "hashfunctioncasestring.h" | ||
| 5 | |||
| 6 | int main() | ||
| 7 | { | ||
| 8 | const char *names[]={ | ||
| 9 | "Homer the Great", | ||
| 10 | "And Maggie Makes Three", | ||
| 11 | "Bart's Comet", | ||
| 12 | "Homie The Clown", | ||
| 13 | "Bart Vs Australia", | ||
| 14 | "Homer vs Patty and Selma", | ||
| 15 | "A star is burns", | ||
| 16 | "Lisa's Wedding", | ||
| 17 | "Two Dozen and One Greyhounds", | ||
| 18 | "The PTA Disbands", | ||
| 19 | "Round Springfield", | ||
| 20 | "The Springfield connection", | ||
| 21 | "Lemon of Troy", | ||
| 22 | "Who Shot Mr. Burns (Pt. 1)", | ||
| 23 | "Who Shot Mr. Burns (pt. 2)", | ||
| 24 | "Radioactive Man", | ||
| 25 | "Home Sweet Homediddly-dum-doodly", | ||
| 26 | "Bart Sells His Soul", | ||
| 27 | "Lisa the Vegetarian", | ||
| 28 | "Treehouse of horror VI", | ||
| 29 | "King Size Homer", | ||
| 30 | "Mother Simpson", | ||
| 31 | "Sideshow Bob's Last Gleaming", | ||
| 32 | "The Simpson's 138th Show Spectacular", | ||
| 33 | "Marge Be Not Proud", | ||
| 34 | "Team Homer", | ||
| 35 | "Two Bad Neighbors", | ||
| 36 | "Scenes From the Class Struggle in Springfield", | ||
| 37 | "Bart the Fink", | ||
| 38 | "Lisa the Iconoclast", | ||
| 39 | "Homer the Smithers", | ||
| 40 | "The Day the Violence Died", | ||
| 41 | "A Fish Called Selma", | ||
| 42 | "Bart on the road", | ||
| 43 | "22 Short Films about Springfield", | ||
| 44 | "The Curse of the Flying Hellfish", | ||
| 45 | "Much Apu about Nothing", | ||
| 46 | "Homerpalooza", | ||
| 47 | "The Summer of 4 Ft 2", | ||
| 48 | "Treehouse of Horror VII", | ||
| 49 | "You Only Move Twice", | ||
| 50 | "The Homer They Fall", | ||
| 51 | "Burns Baby Burns", | ||
| 52 | "Bart After Dark", | ||
| 53 | "A Millhouse Divided", | ||
| 54 | "Lisas Date With Destiny", | ||
| 55 | "Hurricane Neddy", | ||
| 56 | "The Mysterious Voyage of Our Homer", | ||
| 57 | "The Springfield Files", | ||
| 58 | "The Twisted World of Marge Simpson", | ||
| 59 | "Mountain of Madness", | ||
| 60 | NULL | ||
| 61 | }; | ||
| 62 | |||
| 63 | HashTable h( new HashFunctionCaseString(), 5, false ); | ||
| 64 | |||
| 65 | int j; | ||
| 66 | printf("Inserting...\n"); | ||
| 67 | for( j = 0; j < 10; j++ ) | ||
| 68 | { | ||
| 69 | h.insert( names[j], (void *)(j+1) ); | ||
| 70 | h.insert( names[j], (void *)(j+1) ); | ||
| 71 | printf("Capacity: %d, Size: %d, Load: %f\n", | ||
| 72 | h.getCapacity(), | ||
| 73 | h.getSize(), | ||
| 74 | h.getLoad() | ||
| 75 | ); | ||
| 76 | } | ||
| 77 | |||
| 78 | for( j = 0; j < 10; j++ ) | ||
| 79 | { | ||
| 80 | printf("\"%s\" = %d\n", names[j], (int)h[names[j]] ); | ||
| 81 | } | ||
| 82 | |||
| 83 | printf("\nDeleting some...\n"); | ||
| 84 | |||
| 85 | for( int k = 0; k < 7; k++ ) | ||
| 86 | { | ||
| 87 | h.del( names[k] ); | ||
| 88 | //h.insert( names[j], (void *)(j+1) ); | ||
| 89 | printf("Capacity: %d, Size: %d, Load: %f\n", | ||
| 90 | h.getCapacity(), | ||
| 91 | h.getSize(), | ||
| 92 | h.getLoad() | ||
| 93 | ); | ||
| 94 | } | ||
| 95 | |||
| 96 | printf("\nInserting more...\n"); | ||
| 97 | |||
| 98 | for( ; names[j] != NULL; j++ ) | ||
| 99 | { | ||
| 100 | h.insert( names[j], (void *)(j+1) ); | ||
| 101 | printf("Capacity: %d, Size: %d, Load: %f\n", | ||
| 102 | h.getCapacity(), | ||
| 103 | h.getSize(), | ||
| 104 | h.getLoad() | ||
| 105 | ); | ||
| 106 | } | ||
| 107 | } | ||
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 @@ | |||
| 1 | #include "httpconnectionmonitor.h" | ||
| 2 | #include "http.h" | ||
| 3 | #include <sys/stat.h> | ||
| 4 | |||
| 5 | HttpConnectionMonitor::HttpConnectionMonitor() | ||
| 6 | { | ||
| 7 | } | ||
| 8 | |||
| 9 | HttpConnectionMonitor::~HttpConnectionMonitor() | ||
| 10 | { | ||
| 11 | } | ||
| 12 | |||
| 13 | bool HttpConnectionMonitor::onNewConnection( Connection *pCon ) | ||
| 14 | { | ||
| 15 | Http hp( pCon ); | ||
| 16 | |||
| 17 | pCon->readInput( 60, 0 ); | ||
| 18 | printf("#######################\n%s\n#######################\n", pCon->getInput() ); | ||
| 19 | |||
| 20 | while( hp.parseRequest() == false ); | ||
| 21 | printf("Done parsing.\n\n"); | ||
| 22 | |||
| 23 | if( hp.getRequestType() == Http::reqGet ) | ||
| 24 | { | ||
| 25 | printf("\"\"\"%s\"\"\"\n", hp.getRequestURI() ); | ||
| 26 | if( !strcmp( hp.getRequestURI(), "/" ) ) | ||
| 27 | { | ||
| 28 | std::string content("<html><head><title>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>"); | ||
| 29 | hp.buildResponse(); | ||
| 30 | hp.setResponseContent( | ||
| 31 | "text/html", | ||
| 32 | content.c_str(), | ||
| 33 | content.size() | ||
| 34 | ); | ||
| 35 | hp.sendResponse(); | ||
| 36 | } | ||
| 37 | else | ||
| 38 | { | ||
| 39 | 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>"); | ||
| 40 | hp.buildResponse( 404, "File not found."); | ||
| 41 | hp.setResponseContent( | ||
| 42 | "text/html", | ||
| 43 | content.c_str(), | ||
| 44 | content.size() | ||
| 45 | ); | ||
| 46 | hp.sendResponse(); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | else | ||
| 50 | { | ||
| 51 | printf("Non get: %s\n", hp.getRequestTypeStr() ); | ||
| 52 | } | ||
| 53 | pCon->writeOutput(); | ||
| 54 | |||
| 55 | if( pCon->hasInput() ) | ||
| 56 | { | ||
| 57 | std::string s( pCon->getInput(), pCon->getInputAmnt() ); | ||
| 58 | |||
| 59 | printf("Reamining data\n==============\n%s\n==============\n", | ||
| 60 | s.c_str() ); | ||
| 61 | } | ||
| 62 | |||
| 63 | pCon->disconnect(); | ||
| 64 | |||
| 65 | return true; | ||
| 66 | } | ||
| 67 | |||
| 68 | bool HttpConnectionMonitor::onClosedConnection( Connection *pCon ) | ||
| 69 | { | ||
| 70 | return true; | ||
| 71 | } | ||
| 72 | |||
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 @@ | |||
| 1 | #ifndef HTTPCONNECTIONMONITOR_H | ||
| 2 | #define HTTPCONNECTIONMONITOR_H | ||
| 3 | |||
| 4 | #include "connectionmonitor.h" | ||
| 5 | |||
| 6 | class HttpConnectionMonitor : public ConnectionMonitor | ||
| 7 | { | ||
| 8 | public: | ||
| 9 | HttpConnectionMonitor(); | ||
| 10 | ~HttpConnectionMonitor(); | ||
| 11 | |||
| 12 | bool onNewConnection( Connection *pCon ); | ||
| 13 | bool onClosedConnection( Connection *pCon ); | ||
| 14 | }; | ||
| 15 | |||
| 16 | #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 @@ | |||
| 1 | #include "connectionmanager.h" | ||
| 2 | #include "httpconnectionmonitor.h" | ||
| 3 | |||
| 4 | int main() | ||
| 5 | { | ||
| 6 | printf("Starting server...\n"); | ||
| 7 | |||
| 8 | ConnectionManager srv; | ||
| 9 | HttpConnectionMonitor http; | ||
| 10 | |||
| 11 | srv.setConnectionMonitor( &http ); | ||
| 12 | |||
| 13 | srv.startServer( 7331, 40 ); | ||
| 14 | |||
| 15 | for(;;) | ||
| 16 | { | ||
| 17 | srv.scanConnections( 5000, false ); | ||
| 18 | } | ||
| 19 | |||
| 20 | return 0; | ||
| 21 | } | ||
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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <string.h> | ||
| 3 | #include "md5.h" | ||
| 4 | |||
| 5 | int main() | ||
| 6 | { | ||
| 7 | md5 mproc; | ||
| 8 | md5sum sum; | ||
| 9 | char hexstr[33]; | ||
| 10 | |||
| 11 | memset( hexstr, 0, 33 ); | ||
| 12 | |||
| 13 | mproc.sumString( &sum, "qwertyuiopasdfgh" ); | ||
| 14 | mproc.sumToHex( &sum, hexstr ); | ||
| 15 | printf("sum: %s\n", hexstr ); | ||
| 16 | printf("chk: 1ebfc043d8880b758b13ddc8aa1638ef\n"); | ||
| 17 | |||
| 18 | return 0; | ||
| 19 | } | ||
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 @@ | |||
| 1 | #include "connectionmanager.h" | ||
| 2 | #include "telnetmonitor.h" | ||
| 3 | |||
| 4 | int main() | ||
| 5 | { | ||
| 6 | printf("Starting server...\n"); | ||
| 7 | |||
| 8 | ConnectionManager srv; | ||
| 9 | TelnetMonitor telnet; | ||
| 10 | |||
| 11 | srv.setConnectionMonitor( &telnet ); | ||
| 12 | |||
| 13 | srv.startServer( 4001, 40 ); | ||
| 14 | |||
| 15 | for(;;) | ||
| 16 | { | ||
| 17 | srv.scanConnections( 5000, false ); | ||
| 18 | } | ||
| 19 | |||
| 20 | return 0; | ||
| 21 | } | ||
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 @@ | |||
| 1 | #include "telnetmonitor.h" | ||
| 2 | #include "protocoltelnet.h" | ||
| 3 | #include <sys/stat.h> | ||
| 4 | |||
| 5 | TelnetMonitor::TelnetMonitor() | ||
| 6 | { | ||
| 7 | } | ||
| 8 | |||
| 9 | TelnetMonitor::~TelnetMonitor() | ||
| 10 | { | ||
| 11 | } | ||
| 12 | |||
| 13 | bool TelnetMonitor::init() | ||
| 14 | { | ||
| 15 | return true; | ||
| 16 | } | ||
| 17 | |||
| 18 | bool TelnetMonitor::deInit() | ||
| 19 | { | ||
| 20 | return true; | ||
| 21 | } | ||
| 22 | |||
| 23 | bool TelnetMonitor::timeSlice() | ||
| 24 | { | ||
| 25 | for( int j = 0; j < lCon.getSize(); j++ ) | ||
| 26 | { | ||
| 27 | if( ((Connection *)lCon[j])->hasInput() ) | ||
| 28 | { | ||
| 29 | printf("%s\n", ((Connection *)lCon[j])->getInput() ); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | return true; | ||
| 33 | } | ||
| 34 | |||
| 35 | LinkMessage* TelnetMonitor::processIRM( LinkMessage *pMsg ) | ||
| 36 | { | ||
| 37 | } | ||
| 38 | |||
| 39 | bool TelnetMonitor::onNewConnection( Connection *pCon ) | ||
| 40 | { | ||
| 41 | ProtocolTelnet *pt = new ProtocolTelnet(); | ||
| 42 | pCon->setProtocol( pt ); | ||
| 43 | |||
| 44 | lCon.append( pt ); | ||
| 45 | |||
| 46 | return true; | ||
| 47 | } | ||
| 48 | |||
| 49 | bool TelnetMonitor::onClosedConnection( Connection *pCon ) | ||
| 50 | { | ||
| 51 | return true; | ||
| 52 | } | ||
| 53 | |||
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 @@ | |||
| 1 | #ifndef HTTPCONNECTIONMONITOR_H | ||
| 2 | #define HTTPCONNECTIONMONITOR_H | ||
| 3 | |||
| 4 | #include "connectionmonitor.h" | ||
| 5 | #include "programlink.h" | ||
| 6 | #include "linkedlist.h" | ||
| 7 | |||
| 8 | class TelnetMonitor : public ConnectionMonitor, public ProgramLink | ||
| 9 | { | ||
| 10 | public: | ||
| 11 | TelnetMonitor(); | ||
| 12 | ~TelnetMonitor(); | ||
| 13 | |||
| 14 | bool init(); | ||
| 15 | bool deInit(); | ||
| 16 | bool timeSlice(); | ||
| 17 | LinkMessage* processIRM( LinkMessage *pMsgIn ); | ||
| 18 | |||
| 19 | bool onNewConnection( Connection *pCon ); | ||
| 20 | bool onClosedConnection( Connection *pCon ); | ||
| 21 | |||
| 22 | private: | ||
| 23 | LinkedList lCon; | ||
| 24 | }; | ||
| 25 | |||
| 26 | #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 @@ | |||
| 1 | #include "../xmlfilereader.h" | ||
| 2 | #include "../xmlstringreader.h" | ||
| 3 | #include "../xmlfilewriter.h" | ||
| 4 | |||
| 5 | int main( int argc, char *argv[] ) | ||
| 6 | { | ||
| 7 | if( argc < 4 ) | ||
| 8 | { | ||
| 9 | printf("Usage: %s f <file in> <file out>\n", argv[0] ); | ||
| 10 | printf(" %s s <xml string> <file out>\n\n", argv[0] ); | ||
| 11 | return 0; | ||
| 12 | } | ||
| 13 | |||
| 14 | if( argv[1][0] == 'f' ) | ||
| 15 | { | ||
| 16 | XmlFileReader r( argv[2], true ); | ||
| 17 | XmlFileWriter w( argv[3], "\t", r.getRoot() ); | ||
| 18 | w.write(); | ||
| 19 | //XmlWriter::write( argv[3], r.getRoot(), "\t" ); | ||
| 20 | } | ||
| 21 | else if( argv[1][0] == 's' ) | ||
| 22 | { | ||
| 23 | XmlStringReader r( argv[2], true ); | ||
| 24 | //XmlWriter::write( argv[3], r.getRoot(), "\t" ); | ||
| 25 | } | ||
| 26 | |||
| 27 | return 0; | ||
| 28 | } | ||
| 29 | |||
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 @@ | |||
| 1 | #include "xmlwriter.h" | ||
| 2 | |||
| 3 | int main() | ||
| 4 | { | ||
| 5 | printf("Testing Xml Replacement...\n"); | ||
| 6 | XmlDocument w; | ||
| 7 | |||
| 8 | w.addNode("text"); | ||
| 9 | w.setContent("this text is before the node. "); | ||
| 10 | w.addNode("keepme", "This one we keep...", true ); | ||
| 11 | w.setContent("this text is after."); | ||
| 12 | w.addNode("deleteme", "This one we don't...", true ); | ||
| 13 | w.setContent("this is last..." ); | ||
| 14 | w.closeNode(); | ||
| 15 | |||
| 16 | //XmlWriter::writeNode( stdout, w.getRoot(), 0, NULL ); | ||
| 17 | |||
| 18 | printf("\n\n"); | ||
| 19 | |||
| 20 | XmlNode *xNode = w.getRoot()->detatchNode( 1 ); | ||
| 21 | |||
| 22 | //XmlWriter::writeNode( stdout, w.getRoot(), 0, NULL ); | ||
| 23 | |||
| 24 | printf("\n\n"); | ||
| 25 | |||
| 26 | //XmlWriter::writeNode( stdout, xNode, 0, NULL ); | ||
| 27 | |||
| 28 | printf("\n\n"); | ||
| 29 | |||
| 30 | return 0; | ||
| 31 | } | ||
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 @@ | |||
| 1 | #include "xmlfilewriter.h" | ||
| 2 | #include "xmlstringwriter.h" | ||
| 3 | |||
| 4 | void fillItIn( XmlWriter &w ) | ||
| 5 | { | ||
| 6 | w.addNode("thinglist"); | ||
| 7 | |||
| 8 | w.addNode("thing"); | ||
| 9 | w.addProperty("type", "Weapon"); | ||
| 10 | |||
| 11 | w.addNode("id", "Klophin Staff", true ); | ||
| 12 | w.addNode("name", "Klophin Staff", true ); | ||
| 13 | w.addNode("durability", "0.01", true ); | ||
| 14 | w.addNode("size", "0.1", true ); | ||
| 15 | |||
| 16 | w.addNode("config"); | ||
| 17 | w.addNode("damage", "3d6+4", true ); | ||
| 18 | w.addNode("class", "melee", true ); | ||
| 19 | w.addNode("type", "bludgeon", true ); | ||
| 20 | w.addNode("damagedesc", "club/clubs", true ); | ||
| 21 | w.closeNode(); | ||
| 22 | |||
| 23 | w.closeNode(); | ||
| 24 | |||
| 25 | w.closeNode(); | ||
| 26 | } | ||
| 27 | |||
| 28 | int main() | ||
| 29 | { | ||
| 30 | printf("Testing XmlWriter...\n"); | ||
| 31 | XmlFileWriter wf("test.xml", "\t"); | ||
| 32 | |||
| 33 | fillItIn( wf ); | ||
| 34 | |||
| 35 | XmlStringWriter ws("\t"); | ||
| 36 | fillItIn( ws ); | ||
| 37 | |||
| 38 | printf("Now the string version:\n\n%s\n", ws.getString().c_str() ); | ||
| 39 | |||
| 40 | return 0; | ||
| 41 | } | ||
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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | * Copyright (C) 2003 by Mike Buland * | ||
| 3 | * eichlan@Xagafinelle * | ||
| 4 | * * | ||
| 5 | * This program is free software; you can redistribute it and/or modify * | ||
| 6 | * it under the terms of the GNU General Public License as published by * | ||
| 7 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 8 | * (at your option) any later version. * | ||
| 9 | ***************************************************************************/ | ||
| 10 | #include "tokenstring.h" | ||
| 11 | #include <string.h> | ||
| 12 | |||
| 13 | TokenString::TokenString( const char *lpNewTokenString ) | ||
| 14 | { | ||
| 15 | lpTokenString = NULL; | ||
| 16 | if( lpNewTokenString ) | ||
| 17 | { | ||
| 18 | parseLine( lpNewTokenString ); | ||
| 19 | } | ||
| 20 | } | ||
| 21 | |||
| 22 | TokenString::~TokenString() | ||
| 23 | { | ||
| 24 | delete[] lpTokenString; | ||
| 25 | for( int j = 0; j < lToken.getSize(); j++ ) | ||
| 26 | { | ||
| 27 | delete[] (((Token *)lToken[j])->lpToken); | ||
| 28 | delete ((Token *)lToken[j]); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | void TokenString::parseLine( const char *lpNewTokenString ) | ||
| 33 | { | ||
| 34 | if( lpTokenString != NULL ) | ||
| 35 | { | ||
| 36 | delete[] lpTokenString; | ||
| 37 | lpTokenString = NULL; | ||
| 38 | for( int j = 0; j < lToken.getSize(); j++ ) | ||
| 39 | { | ||
| 40 | delete[] (((Token *)lToken[j])->lpToken); | ||
| 41 | delete ((Token *)lToken[j]); | ||
| 42 | } | ||
| 43 | lToken.empty(); | ||
| 44 | } | ||
| 45 | if( lpNewTokenString == NULL ) | ||
| 46 | { | ||
| 47 | lpTokenString = new char[1]; | ||
| 48 | lpTokenString[0] = '\0'; | ||
| 49 | lToken.setSize(0); | ||
| 50 | return; | ||
| 51 | } | ||
| 52 | // First order of business, make an internal copy so someone can get it | ||
| 53 | // if they want to. | ||
| 54 | int nLen = strlen(lpNewTokenString); | ||
| 55 | lpTokenString = new char[nLen+1]; | ||
| 56 | strcpy( lpTokenString, lpNewTokenString ); | ||
| 57 | |||
| 58 | // Now we do a preliminary parse. This could be effected by later | ||
| 59 | // editing and aliasing, but we'll see... | ||
| 60 | int nTkStart, nTkEnd; | ||
| 61 | int mode=0; // 0 = startSearch, 1=endSearch | ||
| 62 | for( int j = 0; j <= nLen; j++ ) | ||
| 63 | { | ||
| 64 | if( mode == 0 ) | ||
| 65 | { | ||
| 66 | if( lpTokenString[j] != ' ' && | ||
| 67 | lpTokenString[j] != '\t' ) | ||
| 68 | { | ||
| 69 | nTkStart = j; | ||
| 70 | mode = 1; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | else | ||
| 74 | { | ||
| 75 | if( lpTokenString[j] == ' ' || | ||
| 76 | lpTokenString[j] == '\t' || | ||
| 77 | lpTokenString[j] == '\0' ) | ||
| 78 | { | ||
| 79 | nTkEnd = j-1; | ||
| 80 | mode = 0; | ||
| 81 | |||
| 82 | appendToken( nTkStart, nTkEnd ); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | void TokenString::appendToken( int nStart, int nEnd ) | ||
| 89 | { | ||
| 90 | Token *pToken = new Token; | ||
| 91 | pToken->lpOrig = &lpTokenString[nStart]; | ||
| 92 | |||
| 93 | // nStart and nEnd are inclusive, we must add two for the end, and the null | ||
| 94 | pToken->lpToken = new char[nEnd-nStart+2]; | ||
| 95 | memcpy( pToken->lpToken, &lpTokenString[nStart], nEnd-nStart+1 ); | ||
| 96 | pToken->lpToken[nEnd-nStart+1] = '\0'; | ||
| 97 | |||
| 98 | // printf("%s\n", pToken->lpToken ); | ||
| 99 | lToken.append( pToken ); | ||
| 100 | } | ||
| 101 | |||
| 102 | void TokenString::insertToken( int nStart, int nEnd, char *lpOldOrig, const char *lpNewToken, int nIndex ) | ||
| 103 | { | ||
| 104 | Token *pToken = new Token; | ||
| 105 | pToken->lpOrig = lpOldOrig; | ||
| 106 | |||
| 107 | // nStart and nEnd are inclusive, we must add two for the end, and the null | ||
| 108 | pToken->lpToken = new char[nEnd-nStart+2]; | ||
| 109 | memcpy( pToken->lpToken, &lpNewToken[nStart], nEnd-nStart+1 ); | ||
| 110 | pToken->lpToken[nEnd-nStart+1] = '\0'; | ||
| 111 | |||
| 112 | lToken.insertBefore( pToken, nIndex ); | ||
| 113 | } | ||
| 114 | |||
| 115 | int TokenString::getNumTokens() | ||
| 116 | { | ||
| 117 | return lToken.getSize(); | ||
| 118 | } | ||
| 119 | |||
| 120 | char *TokenString::getToken( int nIndex ) | ||
| 121 | { | ||
| 122 | if( nIndex >= lToken.getSize() ) return NULL; | ||
| 123 | return (char *)(((Token *)lToken[nIndex])->lpToken); | ||
| 124 | } | ||
| 125 | |||
| 126 | char *TokenString::getTokenString( int nIndex ) | ||
| 127 | { | ||
| 128 | if( nIndex >= lToken.getSize() ) return NULL; | ||
| 129 | return (char *)(((Token *)lToken[nIndex])->lpOrig); | ||
| 130 | } | ||
| 131 | |||
| 132 | void TokenString::expandTokenTo( int nIndex, char *lpNewToken ) | ||
| 133 | { | ||
| 134 | // First, we delete the token at nIndex, then we keep inserting | ||
| 135 | // at that position... | ||
| 136 | // We also have to remember the index to the original string, | ||
| 137 | // since most of what we're expanding to won't be in the origingal | ||
| 138 | // we need to keep these indexes updated in order to make other parts | ||
| 139 | // of the system happy. | ||
| 140 | char *lpOldOrig = ((Token *)lToken[nIndex])->lpOrig; | ||
| 141 | delete[] ((Token *)lToken[nIndex])->lpToken; | ||
| 142 | delete ((Token *)lToken[nIndex]); | ||
| 143 | lToken.deleteAt( nIndex ); | ||
| 144 | |||
| 145 | // We'll do this just like we did above, but instead we'll | ||
| 146 | // do tricky things when we find tokens... | ||
| 147 | int nLen = strlen(lpNewToken); | ||
| 148 | int nTkStart, nTkEnd, nNewIndex=nIndex; | ||
| 149 | int mode=0; // 0 = startSearch, 1=endSearch | ||
| 150 | for( int j = 0; j <= nLen; j++ ) | ||
| 151 | { | ||
| 152 | if( mode == 0 ) | ||
| 153 | { | ||
| 154 | if( lpNewToken[j] != ' ' && lpNewToken[j] != '\t' ) | ||
| 155 | { | ||
| 156 | nTkStart = j; | ||
| 157 | mode = 1; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | else | ||
| 161 | { | ||
| 162 | if( lpNewToken[j] == ' ' || lpNewToken[j] == '\t' || lpNewToken[j] == '\0' ) | ||
| 163 | { | ||
| 164 | nTkEnd = j-1; | ||
| 165 | mode = 0; | ||
| 166 | |||
| 167 | insertToken( nTkStart, nTkEnd, lpOldOrig, lpNewToken, nNewIndex ); | ||
| 168 | nNewIndex++; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
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 @@ | |||
| 1 | /*************************************************************************** | ||
| 2 | * Copyright (C) 2003 by Mike Buland * | ||
| 3 | * eichlan@Xagafinelle * | ||
| 4 | * * | ||
| 5 | * This program is free software; you can redistribute it and/or modify * | ||
| 6 | * it under the terms of the GNU General Public License as published by * | ||
| 7 | * the Free Software Foundation; either version 2 of the License, or * | ||
| 8 | * (at your option) any later version. * | ||
| 9 | ***************************************************************************/ | ||
| 10 | #ifndef TOKENSTRING_H | ||
| 11 | #define TOKENSTRING_H | ||
| 12 | |||
| 13 | #include "linkedlist.h" | ||
| 14 | |||
| 15 | /** A single tokenized command line. Contains all information necesarry to | ||
| 16 | * nicely access a stand-alone command line and to perform alias expansion | ||
| 17 | * inside of that command line. | ||
| 18 | * When expanding a token, the original command line is left intact, so any | ||
| 19 | * command usng a command line verbatum (getTokenString not getToken) will get | ||
| 20 | * the original, and not the expanded version. | ||
| 21 | * Since indexing into the original command line is also done by token, it | ||
| 22 | * means that using getTokenString( 0 ) will not always get you the first | ||
| 23 | * character of the command line, it will get you the first non-whitespace | ||
| 24 | * character. | ||
| 25 | * Furthermore, when expanding the expantion string is tokenized as well, | ||
| 26 | * but since the original string is unchanged, all tokens that expand any | ||
| 27 | * given index will all retain the same index into the original command line. | ||
| 28 | *@todo Update this to allow it to break on different types of token | ||
| 29 | * delimiters. | ||
| 30 | *@author Mike Buland | ||
| 31 | */ | ||
| 32 | class TokenString{ | ||
| 33 | public: | ||
| 34 | /** Automatically call parseLine when created. | ||
| 35 | *@param lpNewTokenString The command line to tokenize | ||
| 36 | *@author Mike Buland | ||
| 37 | */ | ||
| 38 | TokenString( const char *lpNewTokenString=NULL ); | ||
| 39 | ~TokenString(); | ||
| 40 | |||
| 41 | /** Performs a tokenizing parse on the given command line, setting it as | ||
| 42 | * the internal command line for all future tokenizing (excluding | ||
| 43 | * expansion) | ||
| 44 | *@param lpNewTokenString The new command line to set to this object. | ||
| 45 | *@author Mike Buland | ||
| 46 | */ | ||
| 47 | void parseLine( const char *lpNewTokenString ); | ||
| 48 | |||
| 49 | /** Appends a token to the list of available tokens. This references the | ||
| 50 | * internal pointer to the command line, so no token string must be | ||
| 51 | * specified. | ||
| 52 | *@param nStart The first character of the token to insert. | ||
| 53 | *@param nEnd The last character of the token to insert. | ||
| 54 | *@author Mike Buland | ||
| 55 | */ | ||
| 56 | void appendToken( int nStart, int nEnd ); | ||
| 57 | |||
| 58 | /** Gets the number of tokens. This is particularly useful post-aliasing | ||
| 59 | * since the number of tokens may not match what is percieved from the | ||
| 60 | * original command line. | ||
| 61 | *@returns The number of available tokens. | ||
| 62 | *@author Mike Buland | ||
| 63 | */ | ||
| 64 | int getNumTokens(); | ||
| 65 | |||
| 66 | /** Gets a processed token specified by index. | ||
| 67 | *@param nIndex The index of the token to retrieve. | ||
| 68 | *@returns A pointer to the requested token. Please note that these tokens | ||
| 69 | * may not match the original command line. | ||
| 70 | *@author Mike Buland | ||
| 71 | */ | ||
| 72 | char *getToken( int nIndex ); | ||
| 73 | |||
| 74 | /** Gets the original command line based on tokens. Use this if you want | ||
| 75 | * to perform your own processing on parts of the command line, without | ||
| 76 | * resorting to tokens. | ||
| 77 | * The first character in the returned string will always be | ||
| 78 | * non-whitespace. | ||
| 79 | *@param nIndex The index of the token to start at, zero gets you the whole | ||
| 80 | * command line. | ||
| 81 | *@returns A pointer to the internal original command line string, starting | ||
| 82 | * at the position of the first non-whitespace character of the token | ||
| 83 | * specified. | ||
| 84 | *@author Mike Buland | ||
| 85 | */ | ||
| 86 | char *getTokenString( int nIndex=0 ); | ||
| 87 | |||
| 88 | /** Expands a token, replacing it with the string lpNewToken, but | ||
| 89 | * processing the new string for tokens before performing the replacement | ||
| 90 | *@param nIndex Which token should be replaced. | ||
| 91 | *@param lpNewToken The string to replace the token with. | ||
| 92 | *@author Mike Buland | ||
| 93 | */ | ||
| 94 | void expandTokenTo( int nIndex, char *lpNewToken ); | ||
| 95 | |||
| 96 | /** Inserts a token at any position in the command line. This does not | ||
| 97 | * effect the original command line. | ||
| 98 | *@param nStart The start of the token in the string lpNewToken. (inclusive) | ||
| 99 | *@param nEnd The end of the token in the string lpToken. (inclusive) | ||
| 100 | *@param lpOldOrig The pointer to the position in the orginal command | ||
| 101 | * line where this new token should point. | ||
| 102 | *@param lpNewToken The string containing the new token. May contain more | ||
| 103 | * than just one token. | ||
| 104 | *@param nIndex The position to insert the token to. | ||
| 105 | *@author Mike Buland | ||
| 106 | */ | ||
| 107 | void insertToken( int nStart, int nEnd, char *lpOldOrig, const char *lpNewToken, int nIndex ); | ||
| 108 | |||
| 109 | private: | ||
| 110 | char *lpTokenString; | ||
| 111 | LinkedList lToken; | ||
| 112 | |||
| 113 | typedef struct Token | ||
| 114 | { | ||
| 115 | char *lpOrig; // This is just a pointer back to lpTokenString | ||
| 116 | char *lpToken; // This is really a whole token | ||
| 117 | } Token; | ||
| 118 | }; | ||
| 119 | |||
| 120 | #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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include "xmlwriter.h" | ||
| 4 | |||
| 5 | XmlDocument::XmlDocument( XmlNode *pRoot ) | ||
| 6 | { | ||
| 7 | this->pRoot = pRoot; | ||
| 8 | pCurrent = NULL; | ||
| 9 | bCompleted = (pRoot!=NULL); | ||
| 10 | } | ||
| 11 | |||
| 12 | XmlDocument::~XmlDocument() | ||
| 13 | { | ||
| 14 | if( pRoot ) | ||
| 15 | { | ||
| 16 | delete pRoot; | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | void XmlDocument::addNode( const char *sName, const char *sContent, bool bClose ) | ||
| 21 | { | ||
| 22 | if( pRoot == NULL ) | ||
| 23 | { | ||
| 24 | // This is the first node, so ignore position and just insert it. | ||
| 25 | pCurrent = pRoot = new XmlNode( sName, NULL, sContent ); | ||
| 26 | } | ||
| 27 | else | ||
| 28 | { | ||
| 29 | pCurrent = pCurrent->addChild( sName, sContent ); | ||
| 30 | } | ||
| 31 | |||
| 32 | if( bClose ) | ||
| 33 | { | ||
| 34 | closeNode(); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | void XmlDocument::setName( const char *sName ) | ||
| 39 | { | ||
| 40 | pCurrent->setName( sName ); | ||
| 41 | } | ||
| 42 | |||
| 43 | bool XmlDocument::isCompleted() | ||
| 44 | { | ||
| 45 | return bCompleted; | ||
| 46 | } | ||
| 47 | |||
| 48 | XmlNode *XmlDocument::getRoot() | ||
| 49 | { | ||
| 50 | return pRoot; | ||
| 51 | } | ||
| 52 | |||
| 53 | XmlNode *XmlDocument::getCurrent() | ||
| 54 | { | ||
| 55 | return pCurrent; | ||
| 56 | } | ||
| 57 | |||
| 58 | void XmlDocument::closeNode() | ||
| 59 | { | ||
| 60 | if( pCurrent != NULL ) | ||
| 61 | { | ||
| 62 | pCurrent = pCurrent->getParent(); | ||
| 63 | |||
| 64 | if( pCurrent == NULL ) | ||
| 65 | { | ||
| 66 | bCompleted = true; | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | void XmlDocument::addProperty( const char *sName, const char *sValue ) | ||
| 72 | { | ||
| 73 | if( pCurrent ) | ||
| 74 | { | ||
| 75 | pCurrent->addProperty( sName, sValue ); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | void XmlDocument::addProperty( const char *sName, const unsigned char nValue ) | ||
| 80 | { | ||
| 81 | char buf[12]; | ||
| 82 | sprintf( buf, "%hhi", nValue ); | ||
| 83 | addProperty( sName, buf ); | ||
| 84 | } | ||
| 85 | |||
| 86 | void XmlDocument::addProperty( const char *sName, const char nValue ) | ||
| 87 | { | ||
| 88 | char buf[12]; | ||
| 89 | sprintf( buf, "%hhi", nValue ); | ||
| 90 | addProperty( sName, buf ); | ||
| 91 | } | ||
| 92 | |||
| 93 | void XmlDocument::addProperty( const char *sName, const unsigned short nValue ) | ||
| 94 | { | ||
| 95 | char buf[12]; | ||
| 96 | sprintf( buf, "%hi", nValue ); | ||
| 97 | addProperty( sName, buf ); | ||
| 98 | } | ||
| 99 | |||
| 100 | void XmlDocument::addProperty( const char *sName, const short nValue ) | ||
| 101 | { | ||
| 102 | char buf[12]; | ||
| 103 | sprintf( buf, "%hi", nValue ); | ||
| 104 | addProperty( sName, buf ); | ||
| 105 | } | ||
| 106 | |||
| 107 | void XmlDocument::addProperty( const char *sName, const int nValue ) | ||
| 108 | { | ||
| 109 | char buf[12]; | ||
| 110 | sprintf( buf, "%li", nValue ); | ||
| 111 | addProperty( sName, buf ); | ||
| 112 | } | ||
| 113 | |||
| 114 | void XmlDocument::addProperty( const char *sName, const unsigned long nValue ) | ||
| 115 | { | ||
| 116 | char buf[12]; | ||
| 117 | sprintf( buf, "%li", nValue ); | ||
| 118 | addProperty( sName, buf ); | ||
| 119 | } | ||
| 120 | |||
| 121 | void XmlDocument::addProperty( const char *sName, const long nValue ) | ||
| 122 | { | ||
| 123 | char buf[12]; | ||
| 124 | sprintf( buf, "%li", nValue ); | ||
| 125 | addProperty( sName, buf ); | ||
| 126 | } | ||
| 127 | |||
| 128 | void XmlDocument::addProperty( const char *sName, const double dValue ) | ||
| 129 | { | ||
| 130 | char buf[40]; | ||
| 131 | sprintf( buf, "%f", dValue ); | ||
| 132 | addProperty( sName, buf ); | ||
| 133 | } | ||
| 134 | |||
| 135 | void XmlDocument::setContent( const char *sContent ) | ||
| 136 | { | ||
| 137 | if( pCurrent ) | ||
| 138 | { | ||
| 139 | pCurrent->setContent( sContent ); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
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 @@ | |||
| 1 | #ifndef XMLDOCUMENT | ||
| 2 | #define XMLDOCUMENT | ||
| 3 | |||
| 4 | #include "xmlnode.h" | ||
| 5 | |||
| 6 | /** | ||
| 7 | * Keeps track of an easily managed set of XmlNode information. Allows simple | ||
| 8 | * operations for logical writing to and reading from XML structures. Using | ||
| 9 | * already formed structures is simply done through the XmlNode structures, | ||
| 10 | * and the getRoot function here. Creation is performed through a simple set | ||
| 11 | * of operations that creates the data in a stream type format. | ||
| 12 | *@author Mike Buland | ||
| 13 | */ | ||
| 14 | class XmlDocument | ||
| 15 | { | ||
| 16 | public: | ||
| 17 | /** | ||
| 18 | * Construct either a blank XmlDocuemnt or construct a document around an | ||
| 19 | * existing XmlNode. Be careful, once an XmlNode is passed into a document | ||
| 20 | * the document takes over ownership and will delete it when the XmlDocument | ||
| 21 | * is deleted. | ||
| 22 | *@param pRoot The XmlNode to use as the root of this document, or NULL if | ||
| 23 | * you want to start a new document. | ||
| 24 | */ | ||
| 25 | XmlDocument( XmlNode *pRoot=NULL ); | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Destroy all contained nodes. | ||
| 29 | */ | ||
| 30 | ~XmlDocument(); | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Add a new node to the document. The new node is appended to the end of | ||
| 34 | * the current context, i.e. XmlNode, and the new node, provided it isn't | ||
| 35 | * close as part of this operation, will become the current context. | ||
| 36 | *@param sName The name of the new node to add. | ||
| 37 | *@param sContent A content string to be placed inside of the new node. | ||
| 38 | *@param bClose Set this to true to close the node immediately after adding | ||
| 39 | * the node and setting the content and name. If this is set to true the | ||
| 40 | * node is appended, but the context node doesn't change. | ||
| 41 | */ | ||
| 42 | void addNode( const char *sName=NULL, const char *sContent=NULL, bool bClose=false ); | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Set the name of the current node context. | ||
| 46 | *@param sName The new name of the node. | ||
| 47 | */ | ||
| 48 | void setName( const char *sName ); | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Close the current node context. This will move the current context to | ||
| 52 | * the parent node of the former current node. If the current node was the | ||
| 53 | * root then the "completed" flag is set and no more operations are allowed. | ||
| 54 | */ | ||
| 55 | void closeNode(); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Change the content of the current node at the current position between | ||
| 59 | * nodes. | ||
| 60 | *@param sContent The new content of the current node. | ||
| 61 | */ | ||
| 62 | void setContent( const char *sContent ); | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Add a named property to the current context node. | ||
| 66 | *@param sName The name of the property to add. | ||
| 67 | *@param sValue The string value of the property. | ||
| 68 | */ | ||
| 69 | void addProperty( const char *sName, const char *sValue ); | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Add a named property to the current context node, converting the | ||
| 73 | * numerical parameter to text using standrd printf style conversion. | ||
| 74 | *@param sName The name of the property to add. | ||
| 75 | *@param nValue The numerical value to add. | ||
| 76 | */ | ||
| 77 | void addProperty( const char *sName, const unsigned char nValue ); | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Add a named property to the current context node, converting the | ||
| 81 | * numerical parameter to text using standrd printf style conversion. | ||
| 82 | *@param sName The name of the property to add. | ||
| 83 | *@param nValue The numerical value to add. | ||
| 84 | */ | ||
| 85 | void addProperty( const char *sName, const char nValue ); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Add a named property to the current context node, converting the | ||
| 89 | * numerical parameter to text using standrd printf style conversion. | ||
| 90 | *@param sName The name of the property to add. | ||
| 91 | *@param nValue The numerical value to add. | ||
| 92 | */ | ||
| 93 | void addProperty( const char *sName, const unsigned short nValue ); | ||
| 94 | |||
| 95 | /** | ||
| 96 | * Add a named property to the current context node, converting the | ||
| 97 | * numerical parameter to text using standrd printf style conversion. | ||
| 98 | *@param sName The name of the property to add. | ||
| 99 | *@param nValue The numerical value to add. | ||
| 100 | */ | ||
| 101 | void addProperty( const char *sName, const short nValue ); | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Add a named property to the current context node, converting the | ||
| 105 | * numerical parameter to text using standrd printf style conversion. | ||
| 106 | *@param sName The name of the property to add. | ||
| 107 | *@param nValue The numerical value to add. | ||
| 108 | */ | ||
| 109 | void addProperty( const char *sName, const unsigned long nValue ); | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Add a named property to the current context node, converting the | ||
| 113 | * numerical parameter to text using standrd printf style conversion. | ||
| 114 | *@param sName The name of the property to add. | ||
| 115 | *@param nValue The numerical value to add. | ||
| 116 | */ | ||
| 117 | void addProperty( const char *sName, const long nValue ); | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Add a named property to the current context node, converting the | ||
| 121 | * numerical parameter to text using standrd printf style conversion. | ||
| 122 | *@param sName The name of the property to add. | ||
| 123 | *@param nValue The numerical value to add. | ||
| 124 | */ | ||
| 125 | void addProperty( const char *sName, const int nValue ); | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Add a named property to the current context node, converting the | ||
| 129 | * numerical parameter to text using standrd printf style conversion. | ||
| 130 | *@param sName The name of the property to add. | ||
| 131 | *@param dValue The numerical value to add. | ||
| 132 | */ | ||
| 133 | void addProperty( const char *sName, const double dValue ); | ||
| 134 | |||
| 135 | /** | ||
| 136 | * The XmlDocuemnt is considered completed if the root node has been closed. | ||
| 137 | * Once an XmlDocument has been completed, you can no longer perform | ||
| 138 | * operations on it. | ||
| 139 | *@return True if completed, false if still in progress. | ||
| 140 | */ | ||
| 141 | bool isCompleted(); | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Get a pointer to the root object of this XmlDocument. | ||
| 145 | *@returns A pointer to an internally owned XmlNode. Do not delete this | ||
| 146 | * XmlNode. | ||
| 147 | */ | ||
| 148 | XmlNode *getRoot(); | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Get the current context node, which could be the same as the root node. | ||
| 152 | *@returns A pointer to an internally owned XmlNode. Do not delete this | ||
| 153 | * XmlNode. | ||
| 154 | */ | ||
| 155 | XmlNode *getCurrent(); | ||
| 156 | |||
| 157 | private: | ||
| 158 | XmlNode *pRoot; /**< The root node. */ | ||
| 159 | XmlNode *pCurrent; /**< The current node. */ | ||
| 160 | bool bCompleted; /**< Is it completed? */ | ||
| 161 | }; | ||
| 162 | |||
| 163 | #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 @@ | |||
| 1 | #include "xmlfilereader.h" | ||
| 2 | #include <string.h> | ||
| 3 | |||
| 4 | XmlFileReader::XmlFileReader( const char *sFile, bool bStrip ) | ||
| 5 | : XmlReader( bStrip ) | ||
| 6 | { | ||
| 7 | fh = fopen( sFile, "rt" ); | ||
| 8 | |||
| 9 | if( fh == NULL ) | ||
| 10 | { | ||
| 11 | reportError("Couldn't open file."); | ||
| 12 | //nError = 1; | ||
| 13 | } | ||
| 14 | else | ||
| 15 | { | ||
| 16 | char buf[50]; | ||
| 17 | fgets( buf, 50, fh ); | ||
| 18 | |||
| 19 | if( !strcmp( buf, "<?xml version=\"1.0\"?>\n" ) ) | ||
| 20 | { | ||
| 21 | buildDoc(); | ||
| 22 | } | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | XmlFileReader::~XmlFileReader() | ||
| 27 | { | ||
| 28 | } | ||
| 29 | |||
| 30 | char XmlFileReader::getChar( int nIndex ) | ||
| 31 | { | ||
| 32 | // Make sure we always have a little data left in the buffer | ||
| 33 | if( fbDataIn.getLength() <= nIndex+1 && fh ) | ||
| 34 | { | ||
| 35 | int nBytes = fbDataIn.getCapacity()-1; | ||
| 36 | char *buf = new char[nBytes]; | ||
| 37 | int nRead = fread( buf, 1, nBytes, fh ); | ||
| 38 | fbDataIn.appendData( buf, nRead ); | ||
| 39 | delete[] buf; | ||
| 40 | |||
| 41 | if( nRead < nBytes ) | ||
| 42 | { | ||
| 43 | fclose( fh ); | ||
| 44 | fh = NULL; | ||
| 45 | } | ||
| 46 | } | ||
| 47 | if( fbDataIn.getLength() >= nIndex+1 ) | ||
| 48 | { | ||
| 49 | return fbDataIn.getData()[nIndex]; | ||
| 50 | } | ||
| 51 | else | ||
| 52 | { | ||
| 53 | return '\0'; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | void XmlFileReader::usedChar() | ||
| 58 | { | ||
| 59 | if( fbDataIn.getLength() > 0 ) | ||
| 60 | { | ||
| 61 | fbDataIn.usedData( 1 ); | ||
| 62 | } | ||
| 63 | } | ||
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 @@ | |||
| 1 | #ifndef XMLFILEREADER | ||
| 2 | #define XMLFILEREADER | ||
| 3 | |||
| 4 | #include <stdio.h> | ||
| 5 | #include "xmlreader.h" | ||
| 6 | #include "flexbuf.h" | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Takes care of reading in xml formatted data from a file. This could/should | ||
| 10 | * be made more arbitrary in the future so that we can read the data from any | ||
| 11 | * source. This is actually made quite simple already since all data read in | ||
| 12 | * is handled by one single helper function and then palced into a FlexBuf for | ||
| 13 | * easy access by the other functions. The FlexBuf also allows for block | ||
| 14 | * reading from disk, which improves speed by a noticable amount. | ||
| 15 | * <br> | ||
| 16 | * There are also some extra features implemented that allow you to break the | ||
| 17 | * standard XML reader specs and eliminate leading and trailing whitespace in | ||
| 18 | * all read content. This is useful in situations where you allow additional | ||
| 19 | * whitespace in the files to make them easily human readable. The resturned | ||
| 20 | * content will be NULL in sitautions where all content between nodes was | ||
| 21 | * stripped. | ||
| 22 | *@author Mike Buland | ||
| 23 | */ | ||
| 24 | class XmlFileReader : public XmlReader | ||
| 25 | { | ||
| 26 | public: | ||
| 27 | /** | ||
| 28 | * Construct an XmlReader around an xml file on your file system. | ||
| 29 | *@param sFile The file to read. | ||
| 30 | *@param bStrip Set to true to strip out leading and trailing whitespace in | ||
| 31 | * node contents. | ||
| 32 | */ | ||
| 33 | XmlFileReader( const char *sFile, bool bStrip=false ); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Destroy the reader and cleanup. | ||
| 37 | */ | ||
| 38 | ~XmlFileReader(); | ||
| 39 | |||
| 40 | private: | ||
| 41 | char getChar( int nIndex = 0 ); | ||
| 42 | void usedChar(); | ||
| 43 | FILE *fh; /**< The file handle. */ | ||
| 44 | FlexBuf fbDataIn; /**< The input buffer. */ | ||
| 45 | }; | ||
| 46 | |||
| 47 | #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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include "xmlfilewriter.h" | ||
| 4 | |||
| 5 | XmlFileWriter::XmlFileWriter( const char *sFileName, const char *sIndent, XmlNode *pRoot ) : | ||
| 6 | XmlWriter( sIndent, pRoot ) | ||
| 7 | { | ||
| 8 | this->sFileName = sFileName; | ||
| 9 | fh = fopen( sFileName, "wt"); | ||
| 10 | fprintf( fh, "<?xml version=\"1.0\"?>\n"); | ||
| 11 | } | ||
| 12 | |||
| 13 | XmlFileWriter::~XmlFileWriter() | ||
| 14 | { | ||
| 15 | fclose( fh ); | ||
| 16 | } | ||
| 17 | |||
| 18 | void XmlFileWriter::writeString( const char *sString ) | ||
| 19 | { | ||
| 20 | fprintf( fh, sString ); | ||
| 21 | } | ||
| 22 | |||
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 @@ | |||
| 1 | #ifndef XML_FILE_WRITER | ||
| 2 | #define XML_FILE_WRITER | ||
| 3 | |||
| 4 | #include "xmlnode.h" | ||
| 5 | #include "xmlwriter.h" | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Implements xml writing in the XML standard format. Also allows you to | ||
| 9 | * break that format and auto-indent your exported xml data for ease of | ||
| 10 | * reading. The auto-indenting will only be applied to sections that | ||
| 11 | * have no content of their own already. This means that except for | ||
| 12 | * whitespace all of your data will be preserved perfectly. | ||
| 13 | * You can create an XmlWriter object around a file, or access the static | ||
| 14 | * write function directly and just hand it a filename and a root XmlNode. | ||
| 15 | * When using an XmlWriter object the interface is identicle to that of | ||
| 16 | * the XmlDocument class, so reference that class for API info. However | ||
| 17 | * when the initial (or root) node is closed, and the document is finished | ||
| 18 | * the file will be created and written to automatically. The user can | ||
| 19 | * check to see if this is actually true by calling the isFinished | ||
| 20 | * function in the XmlDocument class. | ||
| 21 | *@author Mike Buland | ||
| 22 | */ | ||
| 23 | class XmlFileWriter : public XmlWriter | ||
| 24 | { | ||
| 25 | public: | ||
| 26 | /** | ||
| 27 | * Construct a file writer around a given file. | ||
| 28 | *@param sFileName The file to create or overwrite and write XML into. | ||
| 29 | *@param sIndent The indent text to use, if any. | ||
| 30 | */ | ||
| 31 | XmlFileWriter( const char *sFileName, const char *sIndent=NULL, XmlNode *pRoot=NULL ); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Destroy the writer. | ||
| 35 | */ | ||
| 36 | ~XmlFileWriter(); | ||
| 37 | |||
| 38 | private: | ||
| 39 | void writeString( const char *sString ); | ||
| 40 | std::string sFileName; /**< The filename to write to. */ | ||
| 41 | FILE *fh; /**< The file handle to the open file. */ | ||
| 42 | }; | ||
| 43 | |||
| 44 | #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 @@ | |||
| 1 | #include "xmlnode.h" | ||
| 2 | #include "hashfunctionstring.h" | ||
| 3 | |||
| 4 | XmlNode::XmlNode( const char *sName, XmlNode *pParent, const char *sContent ) : | ||
| 5 | hProperties( new HashFunctionString(), 53, false ), | ||
| 6 | hChildren( new HashFunctionString(), 53, true ) | ||
| 7 | { | ||
| 8 | this->pParent = pParent; | ||
| 9 | if( sName != NULL ) | ||
| 10 | { | ||
| 11 | setName( sName ); | ||
| 12 | } | ||
| 13 | if( sContent != NULL ) | ||
| 14 | { | ||
| 15 | this->sPreContent = new std::string( sContent ); | ||
| 16 | } | ||
| 17 | else | ||
| 18 | { | ||
| 19 | this->sPreContent = NULL; | ||
| 20 | } | ||
| 21 | nCurContent = 0; | ||
| 22 | } | ||
| 23 | |||
| 24 | XmlNode::~XmlNode() | ||
| 25 | { | ||
| 26 | for( int j = 0; j < lChildren.getSize(); j++ ) | ||
| 27 | { | ||
| 28 | delete (XmlNode *)lChildren[j]; | ||
| 29 | } | ||
| 30 | for( int j = 0; j < lPropNames.getSize(); j++ ) | ||
| 31 | { | ||
| 32 | delete (std::string *)lPropNames[j]; | ||
| 33 | } | ||
| 34 | for( int j = 0; j < lPropValues.getSize(); j++ ) | ||
| 35 | { | ||
| 36 | delete (std::string *)lPropValues[j]; | ||
| 37 | } | ||
| 38 | for( int j = 0; j < lPostContent.getSize(); j++ ) | ||
| 39 | { | ||
| 40 | if( lPostContent[j] != NULL ) | ||
| 41 | { | ||
| 42 | delete (std::string *)lPostContent[j]; | ||
| 43 | } | ||
| 44 | } | ||
| 45 | if( sPreContent ) | ||
| 46 | { | ||
| 47 | delete sPreContent; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | void XmlNode::setName( const char *sName ) | ||
| 52 | { | ||
| 53 | if( pParent ) | ||
| 54 | { | ||
| 55 | if( this->sName.size() == 0 ) | ||
| 56 | { | ||
| 57 | // We're not in the hash yet, so add us | ||
| 58 | this->sName = sName; | ||
| 59 | pParent->hChildren.insert( this->sName.c_str(), this ); | ||
| 60 | } | ||
| 61 | else | ||
| 62 | { | ||
| 63 | // Slightly more tricky, delete us, then add us... | ||
| 64 | pParent->hChildren.del( this->sName.c_str() ); | ||
| 65 | this->sName = sName; | ||
| 66 | pParent->hChildren.insert( this->sName.c_str(), this ); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | else | ||
| 70 | { | ||
| 71 | // If we have no parent, then just set the name string, we don't need | ||
| 72 | // to worry about hashing. | ||
| 73 | this->sName = sName; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | void XmlNode::setContent( const char *sContent, int nIndex ) | ||
| 78 | { | ||
| 79 | if( nIndex == -1 ) | ||
| 80 | { | ||
| 81 | nIndex = nCurContent; | ||
| 82 | } | ||
| 83 | if( nIndex == 0 ) | ||
| 84 | { | ||
| 85 | if( this->sPreContent ) | ||
| 86 | { | ||
| 87 | delete this->sPreContent; | ||
| 88 | } | ||
| 89 | |||
| 90 | this->sPreContent = new std::string( sContent ); | ||
| 91 | } | ||
| 92 | else | ||
| 93 | { | ||
| 94 | nIndex--; | ||
| 95 | if( lPostContent[nIndex] ) | ||
| 96 | { | ||
| 97 | delete (std::string *)lPostContent[nIndex]; | ||
| 98 | } | ||
| 99 | |||
| 100 | lPostContent.setAt( nIndex, new std::string( sContent ) ); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | const char *XmlNode::getContent( int nIndex ) | ||
| 105 | { | ||
| 106 | if( nIndex == 0 ) | ||
| 107 | { | ||
| 108 | if( sPreContent ) | ||
| 109 | { | ||
| 110 | return sPreContent->c_str(); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | else | ||
| 114 | { | ||
| 115 | nIndex--; | ||
| 116 | if( lPostContent[nIndex] ) | ||
| 117 | { | ||
| 118 | return ((std::string *)lPostContent[nIndex])->c_str(); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | return NULL; | ||
| 123 | } | ||
| 124 | |||
| 125 | XmlNode *XmlNode::addChild( const char *sName, const char *sContent ) | ||
| 126 | { | ||
| 127 | return addChild( new XmlNode( sName, this, sContent ) ); | ||
| 128 | } | ||
| 129 | |||
| 130 | XmlNode *XmlNode::addChild( XmlNode *pNode ) | ||
| 131 | { | ||
| 132 | lChildren.append( pNode ); | ||
| 133 | lPostContent.append( NULL ); | ||
| 134 | nCurContent++; | ||
| 135 | pNode->pParent = this; | ||
| 136 | |||
| 137 | return pNode; | ||
| 138 | } | ||
| 139 | |||
| 140 | XmlNode *XmlNode::getParent() | ||
| 141 | { | ||
| 142 | return pParent; | ||
| 143 | } | ||
| 144 | |||
| 145 | void XmlNode::addProperty( const char *sName, const char *sValue ) | ||
| 146 | { | ||
| 147 | std::string *pName = new std::string( sName ); | ||
| 148 | std::string *pValue = new std::string( sValue ); | ||
| 149 | |||
| 150 | hProperties.insert( pName->c_str(), pValue->c_str() ); | ||
| 151 | lPropNames.append( pName ); | ||
| 152 | lPropValues.append( pValue ); | ||
| 153 | } | ||
| 154 | |||
| 155 | int XmlNode::getNumProperties() | ||
| 156 | { | ||
| 157 | return lPropNames.getSize(); | ||
| 158 | } | ||
| 159 | |||
| 160 | const char *XmlNode::getPropertyName( int nIndex ) | ||
| 161 | { | ||
| 162 | std::string *tmp = ((std::string *)lPropNames[nIndex]); | ||
| 163 | if( tmp == NULL ) | ||
| 164 | return NULL; | ||
| 165 | return tmp->c_str(); | ||
| 166 | } | ||
| 167 | |||
| 168 | const char *XmlNode::getProperty( int nIndex ) | ||
| 169 | { | ||
| 170 | std::string *tmp = ((std::string *)lPropValues[nIndex]); | ||
| 171 | if( tmp == NULL ) | ||
| 172 | return NULL; | ||
| 173 | return tmp->c_str(); | ||
| 174 | } | ||
| 175 | |||
| 176 | const char *XmlNode::getProperty( const char *sName ) | ||
| 177 | { | ||
| 178 | const char *tmp = (const char *)hProperties[sName]; | ||
| 179 | if( tmp == NULL ) | ||
| 180 | return NULL; | ||
| 181 | return tmp; | ||
| 182 | } | ||
| 183 | |||
| 184 | bool XmlNode::deleteProperty( int nIndex ) | ||
| 185 | { | ||
| 186 | hProperties.del( ((std::string *)lPropNames[nIndex])->c_str() ); | ||
| 187 | |||
| 188 | delete (std::string *)lPropNames[nIndex]; | ||
| 189 | delete (std::string *)lPropValues[nIndex]; | ||
| 190 | |||
| 191 | lPropNames.deleteAt( nIndex ); | ||
| 192 | lPropValues.deleteAt( nIndex ); | ||
| 193 | } | ||
| 194 | |||
| 195 | bool XmlNode::hasChildren() | ||
| 196 | { | ||
| 197 | return lChildren.getSize()>0; | ||
| 198 | } | ||
| 199 | |||
| 200 | int XmlNode::getNumChildren() | ||
| 201 | { | ||
| 202 | return lChildren.getSize(); | ||
| 203 | } | ||
| 204 | |||
| 205 | XmlNode *XmlNode::getChild( int nIndex ) | ||
| 206 | { | ||
| 207 | return (XmlNode *)lChildren[nIndex]; | ||
| 208 | } | ||
| 209 | |||
| 210 | XmlNode *XmlNode::getChild( const char *sName, int nSkip ) | ||
| 211 | { | ||
| 212 | return (XmlNode *)hChildren.get( sName, nSkip ); | ||
| 213 | } | ||
| 214 | |||
| 215 | const char *XmlNode::getName() | ||
| 216 | { | ||
| 217 | return sName.c_str(); | ||
| 218 | } | ||
| 219 | |||
| 220 | bool XmlNode::deleteNode( int nIndex, const char *sReplacementText ) | ||
| 221 | { | ||
| 222 | XmlNode *xRet = detatchNode( nIndex, sReplacementText ); | ||
| 223 | |||
| 224 | if( xRet == NULL ) | ||
| 225 | { | ||
| 226 | return false; | ||
| 227 | } | ||
| 228 | else | ||
| 229 | { | ||
| 230 | delete xRet; | ||
| 231 | return true; | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | XmlNode *XmlNode::detatchNode( int nIndex, const char *sReplacementText ) | ||
| 236 | { | ||
| 237 | if( nIndex < 0 || nIndex >= lChildren.getSize() ) | ||
| 238 | return NULL; | ||
| 239 | |||
| 240 | // The real trick when deleteing a node isn't actually deleting it, it's | ||
| 241 | // reforming the content around the node that's now missing...hmmm... | ||
| 242 | |||
| 243 | if( nIndex == 0 ) | ||
| 244 | { | ||
| 245 | // If the index is zero we have to deal with the pre-content | ||
| 246 | if( sReplacementText ) | ||
| 247 | { | ||
| 248 | if( sPreContent == NULL ) | ||
| 249 | { | ||
| 250 | sPreContent = new std::string( sReplacementText ); | ||
| 251 | } | ||
| 252 | else | ||
| 253 | { | ||
| 254 | *sPreContent += sReplacementText; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | if( lPostContent.getSize() > 0 ) | ||
| 258 | { | ||
| 259 | if( lPostContent[0] != NULL ) | ||
| 260 | { | ||
| 261 | if( sPreContent == NULL ) | ||
| 262 | { | ||
| 263 | sPreContent = new std::string( | ||
| 264 | ((std::string *)lPostContent[0])->c_str() | ||
| 265 | ); | ||
| 266 | } | ||
| 267 | else | ||
| 268 | { | ||
| 269 | *sPreContent += | ||
| 270 | ((std::string *)lPostContent[0])->c_str(); | ||
| 271 | } | ||
| 272 | } | ||
| 273 | delete (std::string *)lPostContent[0]; | ||
| 274 | lPostContent.deleteAt( 0 ); | ||
| 275 | } | ||
| 276 | } | ||
| 277 | else | ||
| 278 | { | ||
| 279 | int nCont = nIndex-1; | ||
| 280 | // If it's above zero we deal with the post-content only | ||
| 281 | if( sReplacementText ) | ||
| 282 | { | ||
| 283 | if( lPostContent[nCont] == NULL ) | ||
| 284 | { | ||
| 285 | lPostContent.setAt( nCont, new std::string( sReplacementText ) ); | ||
| 286 | } | ||
| 287 | else | ||
| 288 | { | ||
| 289 | *((std::string *)lPostContent[nCont]) += sReplacementText; | ||
| 290 | } | ||
| 291 | } | ||
| 292 | if( lPostContent.getSize() > nIndex ) | ||
| 293 | { | ||
| 294 | if( lPostContent[nIndex] != NULL ) | ||
| 295 | { | ||
| 296 | if( lPostContent[nCont] == NULL ) | ||
| 297 | { | ||
| 298 | lPostContent.setAt( nCont, new std::string( | ||
| 299 | ((std::string *)lPostContent[nIndex])->c_str() | ||
| 300 | ) ); | ||
| 301 | } | ||
| 302 | else | ||
| 303 | { | ||
| 304 | *((std::string *)lPostContent[nCont]) += | ||
| 305 | ((std::string *)lPostContent[nIndex])->c_str(); | ||
| 306 | } | ||
| 307 | } | ||
| 308 | delete (std::string *)lPostContent[nIndex]; | ||
| 309 | lPostContent.deleteAt( nIndex ); | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | XmlNode *xRet = (XmlNode *)lChildren[nIndex]; | ||
| 314 | hChildren.del( ((XmlNode *)lChildren[nIndex])->getName() ); | ||
| 315 | lChildren.deleteAt( nIndex ); | ||
| 316 | |||
| 317 | return xRet; | ||
| 318 | } | ||
| 319 | |||
| 320 | bool XmlNode::replaceNode( int nIndex, XmlNode *pNewNode ) | ||
| 321 | { | ||
| 322 | if( nIndex < 0 || nIndex >= lChildren.getSize() ) | ||
| 323 | return false; | ||
| 324 | |||
| 325 | delete (XmlNode *)lChildren[nIndex]; | ||
| 326 | lChildren.setAt( nIndex, pNewNode ); | ||
| 327 | pNewNode->pParent = this; | ||
| 328 | |||
| 329 | return true; | ||
| 330 | } | ||
| 331 | |||
| 332 | XmlNode *XmlNode::getCopy() | ||
| 333 | { | ||
| 334 | XmlNode *pNew = new XmlNode(); | ||
| 335 | |||
| 336 | pNew->sName = sName; | ||
| 337 | if( sPreContent ) | ||
| 338 | { | ||
| 339 | pNew->sPreContent = new std::string( sPreContent->c_str() ); | ||
| 340 | } | ||
| 341 | else | ||
| 342 | { | ||
| 343 | pNew->sPreContent = NULL; | ||
| 344 | } | ||
| 345 | pNew->nCurContent = 0; | ||
| 346 | |||
| 347 | int nSize = lPostContent.getSize(); | ||
| 348 | pNew->lPostContent.setSize( nSize ); | ||
| 349 | for( int j = 0; j < nSize; j++ ) | ||
| 350 | { | ||
| 351 | if( lPostContent[j] ) | ||
| 352 | { | ||
| 353 | pNew->lPostContent.setAt( | ||
| 354 | j, new std::string( | ||
| 355 | ((std::string *)lPostContent[j])->c_str() | ||
| 356 | ) | ||
| 357 | ); | ||
| 358 | } | ||
| 359 | else | ||
| 360 | { | ||
| 361 | pNew->lPostContent.setAt( j, NULL ); | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | nSize = lChildren.getSize(); | ||
| 366 | pNew->lChildren.setSize( nSize ); | ||
| 367 | for( int j = 0; j < nSize; j++ ) | ||
| 368 | { | ||
| 369 | XmlNode *pChild = ((XmlNode *)lChildren[j])->getCopy(); | ||
| 370 | pNew->lChildren.setAt( j, pChild ); | ||
| 371 | pChild->pParent = this; | ||
| 372 | hChildren.insert( pChild->getName(), pChild ); | ||
| 373 | } | ||
| 374 | |||
| 375 | nSize = lPropNames.getSize(); | ||
| 376 | pNew->lPropNames.setSize( nSize ); | ||
| 377 | pNew->lPropValues.setSize( nSize ); | ||
| 378 | for( int j = 0; j < nSize; j++ ) | ||
| 379 | { | ||
| 380 | std::string *pProp = new std::string( ((std::string *)lPropNames[j])->c_str() ); | ||
| 381 | std::string *pVal = new std::string( ((std::string *)lPropValues[j])->c_str() ); | ||
| 382 | pNew->lPropNames.setAt( j, pProp ); | ||
| 383 | pNew->lPropValues.setAt( j, pVal ); | ||
| 384 | pNew->hProperties.insert( pProp->c_str(), pVal->c_str() ); | ||
| 385 | pNew->nCurContent++; | ||
| 386 | } | ||
| 387 | |||
| 388 | return pNew; | ||
| 389 | } | ||
| 390 | |||
| 391 | bool XmlNode::deleteNodeKeepChildren( int nIndex ) | ||
| 392 | { | ||
| 393 | // This is a tricky one...we need to do some patching to keep things all | ||
| 394 | // even... | ||
| 395 | XmlNode *xRet = (XmlNode *)lChildren[nIndex]; | ||
| 396 | |||
| 397 | if( xRet == NULL ) | ||
| 398 | { | ||
| 399 | return false; | ||
| 400 | } | ||
| 401 | else | ||
| 402 | { | ||
| 403 | if( getContent( nIndex ) ) | ||
| 404 | { | ||
| 405 | std::string sBuf( getContent( nIndex ) ); | ||
| 406 | sBuf += xRet->getContent( 0 ); | ||
| 407 | setContent( sBuf.c_str(), nIndex ); | ||
| 408 | } | ||
| 409 | else | ||
| 410 | { | ||
| 411 | setContent( xRet->getContent( 0 ), nIndex ); | ||
| 412 | } | ||
| 413 | |||
| 414 | int nSize = xRet->lChildren.getSize(); | ||
| 415 | for( int j = 0; j < nSize; j++ ) | ||
| 416 | { | ||
| 417 | XmlNode *pCopy = ((XmlNode *)xRet->lChildren[j])->getCopy(); | ||
| 418 | pCopy->pParent = this; | ||
| 419 | lChildren.insertBefore( pCopy, nIndex+j ); | ||
| 420 | |||
| 421 | if( xRet->lPostContent[j] ) | ||
| 422 | { | ||
| 423 | lPostContent.insertBefore( | ||
| 424 | new std::string( ((std::string *)xRet->lPostContent[j])->c_str() ), | ||
| 425 | nIndex+j | ||
| 426 | ); | ||
| 427 | } | ||
| 428 | else | ||
| 429 | { | ||
| 430 | lPostContent.insertBefore( NULL, nIndex+j ); | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | if( getContent( nIndex+nSize ) ) | ||
| 435 | { | ||
| 436 | //SString sBuf( getContent( nIndex+nSize ) ); | ||
| 437 | //sBuf.catfrom( xRet->getContent( nSize ) ); | ||
| 438 | //setContent( sBuf, nIndex+nSize ); | ||
| 439 | } | ||
| 440 | else | ||
| 441 | { | ||
| 442 | setContent( xRet->getContent( nSize ), nIndex+nSize ); | ||
| 443 | } | ||
| 444 | |||
| 445 | deleteNode( nIndex+nSize ); | ||
| 446 | return true; | ||
| 447 | } | ||
| 448 | |||
| 449 | } | ||
| 450 | |||
| 451 | bool XmlNode::replaceNodeWithChildren( int nIndex, XmlNode *pNewNode ) | ||
| 452 | { | ||
| 453 | } | ||
| 454 | |||
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 @@ | |||
| 1 | #ifndef XMLNODE | ||
| 2 | #define XMLNODE | ||
| 3 | |||
| 4 | #include <iostream> | ||
| 5 | #include "linkedlist.h" | ||
| 6 | #include "hashtable.h" | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Maintains all data pertient to an XML node, including sub-nodes and content. | ||
| 10 | * All child nodes can be accessed through index and through name via a hash | ||
| 11 | * table. This makes it very easy to gain simple and fast access to all of | ||
| 12 | * your data. For most applications, the memory footprint is also rather | ||
| 13 | * small. While XmlNode objects can be used directly to create XML structures | ||
| 14 | * it is highly reccomended that all operations be performed through the | ||
| 15 | * XmlDocument class. | ||
| 16 | *@author Mike Buland | ||
| 17 | */ | ||
| 18 | class XmlNode | ||
| 19 | { | ||
| 20 | public: | ||
| 21 | /** | ||
| 22 | * Construct a new XmlNode. | ||
| 23 | *@param sName The name of the node. | ||
| 24 | *@param pParent The parent node. | ||
| 25 | *@param sContent The initial content string. | ||
| 26 | */ | ||
| 27 | XmlNode( | ||
| 28 | const char *sName=NULL, | ||
| 29 | XmlNode *pParent = NULL, | ||
| 30 | const char *sContent=NULL | ||
| 31 | ); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Delete the node and cleanup all memory. | ||
| 35 | */ | ||
| 36 | ~XmlNode(); | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Change the name of the node. | ||
| 40 | *@param sName The new name of the node. | ||
| 41 | */ | ||
| 42 | void setName( const char *sName ); | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Construct a new node and add it as a child to this node, also return a | ||
| 46 | * pointer to the newly constructed node. | ||
| 47 | *@param sName The name of the new node. | ||
| 48 | *@param sContent The initial content of the new node. | ||
| 49 | *@returns A pointer to the newly created child node. | ||
| 50 | */ | ||
| 51 | XmlNode *addChild( const char *sName, const char *sContent=NULL ); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Add an already created XmlNode as a child to this node. The new child | ||
| 55 | * XmlNode's parent will be changed appropriately and the parent XmlNode | ||
| 56 | * will take ownership of the child. | ||
| 57 | *@param pChild The child XmlNode to add to this XmlNode. | ||
| 58 | *@returns A pointer to the child node that was just added. | ||
| 59 | */ | ||
| 60 | XmlNode *addChild( XmlNode *pChild ); | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Add a new property to the XmlNode. Properties are name/value pairs. | ||
| 64 | *@param sName The name of the property. Specifying a name that's already | ||
| 65 | * in use will overwrite that property. | ||
| 66 | *@param sValue The textual value of the property. | ||
| 67 | */ | ||
| 68 | void addProperty( const char *sName, const char *sValue ); | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Get a pointer to the parent node, if any. | ||
| 72 | *@returns A pointer to the node's parent, or NULL if there isn't one. | ||
| 73 | */ | ||
| 74 | XmlNode *getParent(); | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Tells you if this node has children. | ||
| 78 | *@returns True if this node has at least one child, false otherwise. | ||
| 79 | */ | ||
| 80 | bool hasChildren(); | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Tells you how many children this node has. | ||
| 84 | *@returns The number of children this node has. | ||
| 85 | */ | ||
| 86 | int getNumChildren(); | ||
| 87 | |||
| 88 | /** | ||
| 89 | * Get a child node at a specific index. | ||
| 90 | *@param nIndex The zero-based index of the child to retreive. | ||
| 91 | *@returns A pointer to the child, or NULL if you requested an invalid | ||
| 92 | * index. | ||
| 93 | */ | ||
| 94 | XmlNode *getChild( int nIndex ); | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Get a child with the specified name, and possibly skip value. For an | ||
| 98 | * explination of skip values see the HashTable. | ||
| 99 | *@param sName The name of the child to find. | ||
| 100 | *@param nSkip The number of nodes with that name to skip. | ||
| 101 | *@returns A pointer to the child, or NULL if no child with that name was | ||
| 102 | * found. | ||
| 103 | */ | ||
| 104 | XmlNode *getChild( const char *sName, int nSkip=0 ); | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Get a pointer to the name of this node. Do not change this, use setName | ||
| 108 | * instead. | ||
| 109 | *@returns A pointer to the name of this node. | ||
| 110 | */ | ||
| 111 | const char *getName(); | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Set the content of this node, optionally at a specific index. Using the | ||
| 115 | * default of -1 will set the content after the last added node. | ||
| 116 | *@param sContent The content string to use. | ||
| 117 | *@param nIndex The index of the content. | ||
| 118 | */ | ||
| 119 | void setContent( const char *sContent, int nIndex=-1 ); | ||
| 120 | |||
| 121 | /** | ||
| 122 | * Get the content string at a given index, or zero for initial content. | ||
| 123 | *@param nIndex The index of the content. | ||
| 124 | *@returns A pointer to the content at that location. | ||
| 125 | */ | ||
| 126 | const char *getContent( int nIndex = 0 ); | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Get the number of properties in this node. | ||
| 130 | *@returns The number of properties in this node. | ||
| 131 | */ | ||
| 132 | int getNumProperties(); | ||
| 133 | |||
| 134 | /** | ||
| 135 | * Get a property's name by index. | ||
| 136 | *@param nIndex The index of the property to examine. | ||
| 137 | *@returns A pointer to the name of the property specified, or NULL if none | ||
| 138 | * found. | ||
| 139 | */ | ||
| 140 | const char *getPropertyName( int nIndex ); | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Get a proprty's value by index. | ||
| 144 | *@param nIndex The index of the property to examine. | ||
| 145 | *@returns A pointer to the value of the property specified, or NULL if none | ||
| 146 | * found. | ||
| 147 | */ | ||
| 148 | const char *getProperty( int nIndex ); | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Get a propery's value by name. | ||
| 152 | *@param sName The name of the property to examine. | ||
| 153 | *@returns A pointer to the value of the property specified, or NULL if none | ||
| 154 | * found. | ||
| 155 | */ | ||
| 156 | const char *getProperty( const char *sName ); | ||
| 157 | |||
| 158 | /** | ||
| 159 | * Delete a property by index. | ||
| 160 | *@param nIndex The index of the property to delete. | ||
| 161 | *@returns True if the property was found and deleted, false if it wasn't | ||
| 162 | * found. | ||
| 163 | */ | ||
| 164 | bool deleteProperty( int nIndex ); | ||
| 165 | |||
| 166 | /** | ||
| 167 | * Delete a child node, possibly replacing it with some text. This actually | ||
| 168 | * fixes all content strings around the newly deleted child node. | ||
| 169 | *@param nIndex The index of the node to delete. | ||
| 170 | *@param sReplacementText The optional text to replace the node with. | ||
| 171 | *@returns True of the node was found, and deleted, false if it wasn't | ||
| 172 | * found. | ||
| 173 | */ | ||
| 174 | bool deleteNode( int nIndex, const char *sReplacementText = NULL ); | ||
| 175 | |||
| 176 | /** | ||
| 177 | * Delete a given node, but move all of it's children and content up to | ||
| 178 | * replace the deleted node. All of the content of the child node is | ||
| 179 | * spliced seamlessly into place with the parent node's content. | ||
| 180 | *@param nIndex The node to delete. | ||
| 181 | *@returns True if the node was found and deleted, false if it wasn't. | ||
| 182 | */ | ||
| 183 | bool deleteNodeKeepChildren( int nIndex ); | ||
| 184 | |||
| 185 | /** | ||
| 186 | * Detatch a given child node from this node. This effectively works just | ||
| 187 | * like a deleteNode, except that instead of deleting the node it is removed | ||
| 188 | * and returned, and all ownership is given up. | ||
| 189 | *@param nIndex The index of the node to detatch. | ||
| 190 | *@param sReplacementText The optional text to replace the detatched node | ||
| 191 | * with. | ||
| 192 | *@returns A pointer to the newly detatched node, which then passes | ||
| 193 | * ownership to the caller. | ||
| 194 | */ | ||
| 195 | XmlNode *detatchNode( int nIndex, const char *sReplacementText = NULL ); | ||
| 196 | |||
| 197 | /** | ||
| 198 | * Replace a given node with a different node that is not currently owned by | ||
| 199 | * this XmlNode or any ancestor. | ||
| 200 | *@param nIndex The index of the node to replace. | ||
| 201 | *@param pNewNode The new node to replace the old node with. | ||
| 202 | *@returns True if the node was found and replaced, false if it wasn't. | ||
| 203 | */ | ||
| 204 | bool replaceNode( int nIndex, XmlNode *pNewNode ); | ||
| 205 | |||
| 206 | /** | ||
| 207 | * Replace a given node with the children and content of a given node. | ||
| 208 | *@param nIndex The index of the node to replace. | ||
| 209 | *@param pNewNode The node that contains the children and content that will | ||
| 210 | * replace the node specified by nIndex. | ||
| 211 | *@returns True if the node was found and replaced, false if it wasn't. | ||
| 212 | */ | ||
| 213 | bool replaceNodeWithChildren( int nIndex, XmlNode *pNewNode ); | ||
| 214 | |||
| 215 | /** | ||
| 216 | * Get a copy of this node and all children. getCopy is recursive, so | ||
| 217 | * beware copying large trees of xml. | ||
| 218 | *@returns A newly created copy of this node and all of it's children. | ||
| 219 | */ | ||
| 220 | XmlNode *getCopy(); | ||
| 221 | |||
| 222 | private: | ||
| 223 | std::string sName; /**< The name of the node. */ | ||
| 224 | std::string *sPreContent; /**< The content that goes before any node. */ | ||
| 225 | LinkedList lChildren; /**< The children. */ | ||
| 226 | LinkedList lPostContent; /**< The content that comes after children. */ | ||
| 227 | HashTable hProperties; /**< Property hashtable. */ | ||
| 228 | HashTable hChildren; /**< Children hashtable. */ | ||
| 229 | LinkedList lPropNames; /**< List of property names. */ | ||
| 230 | LinkedList lPropValues; /**< List of property values. */ | ||
| 231 | XmlNode *pParent; /**< A pointer to the parent of this node. */ | ||
| 232 | int nCurContent; /**< The current content we're on, for using the -1 on | ||
| 233 | setContent. */ | ||
| 234 | }; | ||
| 235 | |||
| 236 | #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 @@ | |||
| 1 | #include "xmlreader.h" | ||
| 2 | #include <string.h> | ||
| 3 | |||
| 4 | XmlReader::XmlReader( bool bStrip ) | ||
| 5 | { | ||
| 6 | nError = 0; | ||
| 7 | this->bStrip = bStrip; | ||
| 8 | } | ||
| 9 | |||
| 10 | XmlReader::~XmlReader() | ||
| 11 | { | ||
| 12 | } | ||
| 13 | |||
| 14 | #define gcall( x ) if( x == false ) return false; | ||
| 15 | |||
| 16 | bool XmlReader::isws( char chr ) | ||
| 17 | { | ||
| 18 | return ( chr == ' ' || chr == '\t' || chr == '\n' || chr == '\r' ); | ||
| 19 | } | ||
| 20 | |||
| 21 | bool XmlReader::ws() | ||
| 22 | { | ||
| 23 | while( true ) | ||
| 24 | { | ||
| 25 | char chr = getChar(); | ||
| 26 | if( isws( chr ) ) | ||
| 27 | { | ||
| 28 | usedChar(); | ||
| 29 | } | ||
| 30 | else | ||
| 31 | { | ||
| 32 | return true; | ||
| 33 | } | ||
| 34 | } | ||
| 35 | return true; | ||
| 36 | } | ||
| 37 | |||
| 38 | bool XmlReader::buildDoc() | ||
| 39 | { | ||
| 40 | // take care of initial whitespace | ||
| 41 | gcall( ws() ); | ||
| 42 | gcall( node() ); | ||
| 43 | |||
| 44 | return true; | ||
| 45 | } | ||
| 46 | |||
| 47 | bool XmlReader::node() | ||
| 48 | { | ||
| 49 | gcall( startNode() ) | ||
| 50 | |||
| 51 | // At this point, we are closing the startNode | ||
| 52 | char chr = getChar(); | ||
| 53 | if( chr == '>' ) | ||
| 54 | { | ||
| 55 | usedChar(); | ||
| 56 | |||
| 57 | // Now we process the guts of the node. | ||
| 58 | gcall( content() ); | ||
| 59 | } | ||
| 60 | else if( chr == '/' ) | ||
| 61 | { | ||
| 62 | // This is the tricky one, one more validation, then we close the node. | ||
| 63 | usedChar(); | ||
| 64 | if( getChar() == '>' ) | ||
| 65 | { | ||
| 66 | closeNode(); | ||
| 67 | usedChar(); | ||
| 68 | } | ||
| 69 | else | ||
| 70 | { | ||
| 71 | reportError("Close node in singleNode malformed!"); | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | else | ||
| 76 | { | ||
| 77 | reportError("Close node expected, but not found."); | ||
| 78 | return false; | ||
| 79 | } | ||
| 80 | |||
| 81 | return true; | ||
| 82 | } | ||
| 83 | |||
| 84 | bool XmlReader::startNode() | ||
| 85 | { | ||
| 86 | if( getChar() == '<' ) | ||
| 87 | { | ||
| 88 | usedChar(); | ||
| 89 | |||
| 90 | if( getChar() == '/' ) | ||
| 91 | { | ||
| 92 | // Heh, it's actually a close node, go figure | ||
| 93 | FlexBuf fbName; | ||
| 94 | usedChar(); | ||
| 95 | gcall( ws() ); | ||
| 96 | |||
| 97 | while( true ) | ||
| 98 | { | ||
| 99 | char chr = getChar(); | ||
| 100 | if( isws( chr ) || chr == '>' ) | ||
| 101 | { | ||
| 102 | // Here we actually compare the name we got to the name | ||
| 103 | // we already set, they have to match exactly. | ||
| 104 | if( !strcasecmp( getCurrent()->getName(), fbName.getData() ) ) | ||
| 105 | { | ||
| 106 | closeNode(); | ||
| 107 | break; | ||
| 108 | } | ||
| 109 | else | ||
| 110 | { | ||
| 111 | reportError("Got a mismatched node close tag."); | ||
| 112 | return false; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | else | ||
| 116 | { | ||
| 117 | fbName.appendData( chr ); | ||
| 118 | usedChar(); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | gcall( ws() ); | ||
| 123 | if( getChar() == '>' ) | ||
| 124 | { | ||
| 125 | // Everything is cool. | ||
| 126 | usedChar(); | ||
| 127 | } | ||
| 128 | else | ||
| 129 | { | ||
| 130 | reportError("Got extra junk data instead of node close tag."); | ||
| 131 | return false; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | else | ||
| 135 | { | ||
| 136 | // We're good, format is consistant | ||
| 137 | addNode(); | ||
| 138 | |||
| 139 | // Skip extra whitespace | ||
| 140 | gcall( ws() ); | ||
| 141 | gcall( name() ); | ||
| 142 | gcall( ws() ); | ||
| 143 | gcall( paramlist() ); | ||
| 144 | gcall( ws() ); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | else | ||
| 148 | { | ||
| 149 | reportError("Expected to find node opening char, '<'.\n"); | ||
| 150 | return false; | ||
| 151 | } | ||
| 152 | |||
| 153 | return true; | ||
| 154 | } | ||
| 155 | |||
| 156 | bool XmlReader::name() | ||
| 157 | { | ||
| 158 | FlexBuf fbName; | ||
| 159 | |||
| 160 | while( true ) | ||
| 161 | { | ||
| 162 | char chr = getChar(); | ||
| 163 | if( isws( chr ) || chr == '>' || chr == '/' ) | ||
| 164 | { | ||
| 165 | setName( fbName.getData() ); | ||
| 166 | return true; | ||
| 167 | } | ||
| 168 | else | ||
| 169 | { | ||
| 170 | fbName.appendData( chr ); | ||
| 171 | usedChar(); | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | return true; | ||
| 176 | } | ||
| 177 | |||
| 178 | bool XmlReader::paramlist() | ||
| 179 | { | ||
| 180 | while( true ) | ||
| 181 | { | ||
| 182 | char chr = getChar(); | ||
| 183 | if( chr == '/' || chr == '>' ) | ||
| 184 | { | ||
| 185 | return true; | ||
| 186 | } | ||
| 187 | else | ||
| 188 | { | ||
| 189 | gcall( param() ); | ||
| 190 | gcall( ws() ); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | return true; | ||
| 195 | } | ||
| 196 | |||
| 197 | char XmlReader::getEscape() | ||
| 198 | { | ||
| 199 | // Right now, we just do # escapes... | ||
| 200 | if( getChar( 1 ) == '#' ) | ||
| 201 | { | ||
| 202 | usedChar(); | ||
| 203 | usedChar(); | ||
| 204 | char buf[4]; | ||
| 205 | int j = 0; | ||
| 206 | for( j = 0; getChar() != ';'; j++ ) | ||
| 207 | { | ||
| 208 | buf[j] = getChar(); | ||
| 209 | usedChar(); | ||
| 210 | } | ||
| 211 | usedChar(); | ||
| 212 | buf[j] = '\0'; | ||
| 213 | return (char)atoi( buf ); | ||
| 214 | } | ||
| 215 | else | ||
| 216 | { | ||
| 217 | return '\0'; | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | bool XmlReader::param() | ||
| 222 | { | ||
| 223 | FlexBuf fbName; | ||
| 224 | FlexBuf fbValue; | ||
| 225 | |||
| 226 | while( true ) | ||
| 227 | { | ||
| 228 | char chr = getChar(); | ||
| 229 | if( isws( chr ) || chr == '=' ) | ||
| 230 | { | ||
| 231 | break; | ||
| 232 | } | ||
| 233 | else | ||
| 234 | { | ||
| 235 | fbName.appendData( chr ); | ||
| 236 | usedChar(); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | gcall( ws() ); | ||
| 241 | |||
| 242 | if( getChar() == '=' ) | ||
| 243 | { | ||
| 244 | usedChar(); | ||
| 245 | |||
| 246 | gcall( ws() ); | ||
| 247 | |||
| 248 | char chr = getChar(); | ||
| 249 | if( chr == '"' ) | ||
| 250 | { | ||
| 251 | // Better quoted rhs | ||
| 252 | usedChar(); | ||
| 253 | |||
| 254 | while( true ) | ||
| 255 | { | ||
| 256 | chr = getChar(); | ||
| 257 | if( chr == '"' ) | ||
| 258 | { | ||
| 259 | usedChar(); | ||
| 260 | addProperty( fbName.getData(), fbValue.getData() ); | ||
| 261 | return true; | ||
| 262 | } | ||
| 263 | else | ||
| 264 | { | ||
| 265 | if( chr == '&' ) | ||
| 266 | { | ||
| 267 | chr = getEscape(); | ||
| 268 | if( chr == '\0' ) return false; | ||
| 269 | fbValue.appendData( chr ); | ||
| 270 | } | ||
| 271 | else | ||
| 272 | { | ||
| 273 | fbValue.appendData( chr ); | ||
| 274 | usedChar(); | ||
| 275 | } | ||
| 276 | } | ||
| 277 | } | ||
| 278 | } | ||
| 279 | else | ||
| 280 | { | ||
| 281 | // Simple one-word rhs | ||
| 282 | while( true ) | ||
| 283 | { | ||
| 284 | chr = getChar(); | ||
| 285 | if( isws( chr ) || chr == '/' || chr == '>' ) | ||
| 286 | { | ||
| 287 | addProperty( fbName.getData(), fbValue.getData() ); | ||
| 288 | return true; | ||
| 289 | } | ||
| 290 | else | ||
| 291 | { | ||
| 292 | if( chr == '&' ) | ||
| 293 | { | ||
| 294 | chr = getEscape(); | ||
| 295 | if( chr == '\0' ) return false; | ||
| 296 | fbValue.appendData( chr ); | ||
| 297 | } | ||
| 298 | else | ||
| 299 | { | ||
| 300 | fbValue.appendData( chr ); | ||
| 301 | usedChar(); | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
| 305 | } | ||
| 306 | } | ||
| 307 | else | ||
| 308 | { | ||
| 309 | reportError("Expected an equals to seperate the params."); | ||
| 310 | return false; | ||
| 311 | } | ||
| 312 | |||
| 313 | return true; | ||
| 314 | } | ||
| 315 | |||
| 316 | bool XmlReader::content() | ||
| 317 | { | ||
| 318 | FlexBuf fbContent; | ||
| 319 | |||
| 320 | if( bStrip ) gcall( ws() ); | ||
| 321 | |||
| 322 | while( true ) | ||
| 323 | { | ||
| 324 | char chr = getChar(); | ||
| 325 | if( chr == '<' ) | ||
| 326 | { | ||
| 327 | if( getChar(1) == '/' ) | ||
| 328 | { | ||
| 329 | if( fbContent.getLength() > 0 ) | ||
| 330 | { | ||
| 331 | if( bStrip ) | ||
| 332 | { | ||
| 333 | int j; | ||
| 334 | for( j = fbContent.getLength()-1; isws(fbContent.getData()[j]); j-- ); | ||
| 335 | ((char *)fbContent.getData())[j+1] = '\0'; | ||
| 336 | } | ||
| 337 | setContent( fbContent.getData() ); | ||
| 338 | } | ||
| 339 | usedChar(); | ||
| 340 | usedChar(); | ||
| 341 | gcall( ws() ); | ||
| 342 | FlexBuf fbName; | ||
| 343 | while( true ) | ||
| 344 | { | ||
| 345 | chr = getChar(); | ||
| 346 | if( isws( chr ) || chr == '>' ) | ||
| 347 | { | ||
| 348 | if( !strcasecmp( getCurrent()->getName(), fbName.getData() ) ) | ||
| 349 | { | ||
| 350 | closeNode(); | ||
| 351 | break; | ||
| 352 | } | ||
| 353 | else | ||
| 354 | { | ||
| 355 | reportError("Mismatched close tag found."); | ||
| 356 | return false; | ||
| 357 | } | ||
| 358 | } | ||
| 359 | else | ||
| 360 | { | ||
| 361 | fbName.appendData( chr ); | ||
| 362 | usedChar(); | ||
| 363 | } | ||
| 364 | } | ||
| 365 | gcall( ws() ); | ||
| 366 | if( getChar() == '>' ) | ||
| 367 | { | ||
| 368 | usedChar(); | ||
| 369 | return true; | ||
| 370 | } | ||
| 371 | else | ||
| 372 | { | ||
| 373 | reportError("Malformed close tag."); | ||
| 374 | return false; | ||
| 375 | } | ||
| 376 | } | ||
| 377 | else | ||
| 378 | { | ||
| 379 | if( fbContent.getLength() > 0 ) | ||
| 380 | { | ||
| 381 | if( bStrip ) | ||
| 382 | { | ||
| 383 | int j; | ||
| 384 | for( j = fbContent.getLength()-1; isws(fbContent.getData()[j]); j-- ); | ||
| 385 | ((char *)fbContent.getData())[j+1] = '\0'; | ||
| 386 | } | ||
| 387 | setContent( fbContent.getData() ); | ||
| 388 | fbContent.clearData(); | ||
| 389 | } | ||
| 390 | gcall( node() ); | ||
| 391 | } | ||
| 392 | |||
| 393 | if( bStrip ) gcall( ws() ); | ||
| 394 | } | ||
| 395 | else | ||
| 396 | { | ||
| 397 | fbContent.appendData( chr ); | ||
| 398 | usedChar(); | ||
| 399 | } | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | void XmlReader::reportError( const char *sError ) | ||
| 404 | { | ||
| 405 | printf("XmlReader error: %s\n", sError ); | ||
| 406 | } | ||
| 407 | |||
| 408 | int XmlReader::getError() | ||
| 409 | { | ||
| 410 | return nError; | ||
| 411 | } | ||
| 412 | |||
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 @@ | |||
| 1 | #ifndef XMLREADER | ||
| 2 | #define XMLREADER | ||
| 3 | |||
| 4 | #include <stdio.h> | ||
| 5 | #include "xmldocument.h" | ||
| 6 | #include "flexbuf.h" | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Takes care of reading in xml formatted data from a file. This could/should | ||
| 10 | * be made more arbitrary in the future so that we can read the data from any | ||
| 11 | * source. This is actually made quite simple already since all data read in | ||
| 12 | * is handled by one single helper function and then palced into a FlexBuf for | ||
| 13 | * easy access by the other functions. The FlexBuf also allows for block | ||
| 14 | * reading from disk, which improves speed by a noticable amount. | ||
| 15 | * <br> | ||
| 16 | * There are also some extra features implemented that allow you to break the | ||
| 17 | * standard XML reader specs and eliminate leading and trailing whitespace in | ||
| 18 | * all read content. This is useful in situations where you allow additional | ||
| 19 | * whitespace in the files to make them easily human readable. The resturned | ||
| 20 | * content will be NULL in sitautions where all content between nodes was | ||
| 21 | * stripped. | ||
| 22 | *@author Mike Buland | ||
| 23 | */ | ||
| 24 | class XmlReader : public XmlDocument | ||
| 25 | { | ||
| 26 | public: | ||
| 27 | /** | ||
| 28 | * Create a standard XmlReader. The optional parameter bStrip allows you to | ||
| 29 | * create a reader that will strip out all leading and trailing whitespace | ||
| 30 | * in content, a-la html. | ||
| 31 | *@param bStrip Strip out leading and trailing whitespace? | ||
| 32 | */ | ||
| 33 | XmlReader( bool bStrip=false ); | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Destroy this XmlReader. | ||
| 37 | */ | ||
| 38 | ~XmlReader(); | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Get the error code if an error happened. | ||
| 42 | *@returns The error code (I don't know what they are either) | ||
| 43 | */ | ||
| 44 | int getError(); | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Report an error to something, this is a really strange mechanism and | ||
| 48 | * should probably just be replaced with the multi-log system. | ||
| 49 | *@param sError The error to report. | ||
| 50 | */ | ||
| 51 | void reportError( const char *sError ); | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Build a document based on some kind of input. This is called | ||
| 55 | * automatically by the constructor. | ||
| 56 | */ | ||
| 57 | bool buildDoc(); | ||
| 58 | |||
| 59 | private: | ||
| 60 | /** | ||
| 61 | * This is called by the low level automoton in order to get the next | ||
| 62 | * character. This function should return a character at the current | ||
| 63 | * position plus nIndex, but does not increment the current character. | ||
| 64 | *@param nIndex The index of the character from the current stream position. | ||
| 65 | *@returns A single character at the requested position, or 0 for end of | ||
| 66 | * stream. | ||
| 67 | */ | ||
| 68 | virtual char getChar( int nIndex = 0 ) = 0; | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Called to increment the current stream position by a single character. | ||
| 72 | */ | ||
| 73 | virtual void usedChar() = 0; | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Automoton function: is whitespace. | ||
| 77 | *@param chr A character | ||
| 78 | *@returns True if chr is whitespace, false otherwise. | ||
| 79 | */ | ||
| 80 | bool isws( char chr ); | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Automoton function: ws. Skips sections of whitespace. | ||
| 84 | *@returns True if everything was ok, False for end of stream. | ||
| 85 | */ | ||
| 86 | bool ws(); | ||
| 87 | |||
| 88 | /** | ||
| 89 | * Automoton function: node. Processes an XmlNode | ||
| 90 | *@returns True if everything was ok, False for end of stream. | ||
| 91 | */ | ||
| 92 | bool node(); | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Automoton function: startNode. Processes the begining of a node. | ||
| 96 | *@returns True if everything was ok, False for end of stream. | ||
| 97 | */ | ||
| 98 | bool startNode(); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Automoton function: name. Processes the name of a node. | ||
| 102 | *@returns True if everything was ok, False for end of stream. | ||
| 103 | */ | ||
| 104 | bool name(); | ||
| 105 | |||
| 106 | char getEscape(); | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Automoton function: paramlist. Processes a list of node params. | ||
| 110 | *@returns True if everything was ok, False for end of stream. | ||
| 111 | */ | ||
| 112 | bool paramlist(); | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Automoton function: param. Processes a single parameter. | ||
| 116 | *@returns True if everything was ok, False for end of stream. | ||
| 117 | */ | ||
| 118 | bool param(); | ||
| 119 | |||
| 120 | /** | ||
| 121 | * Automoton function: content. Processes node content. | ||
| 122 | *@returns True if everything was ok, False for end of stream. | ||
| 123 | */ | ||
| 124 | bool content(); | ||
| 125 | |||
| 126 | FlexBuf fbContent; /**< buffer for the current node's content. */ | ||
| 127 | FlexBuf fbParamName; /**< buffer for the current param's name. */ | ||
| 128 | FlexBuf fbParamValue; /**< buffer for the current param's value. */ | ||
| 129 | bool bStrip; /**< Are we stripping whitespace? */ | ||
| 130 | int nError; /**< Is there an error? */ | ||
| 131 | }; | ||
| 132 | |||
| 133 | #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 @@ | |||
| 1 | #include "xmlstringreader.h" | ||
| 2 | #include <string.h> | ||
| 3 | |||
| 4 | XmlStringReader::XmlStringReader( const char *sString, bool bStrip ) | ||
| 5 | : XmlReader( bStrip ) | ||
| 6 | { | ||
| 7 | this->sString = sString; | ||
| 8 | |||
| 9 | nIndex = 0; | ||
| 10 | nLength = strlen( sString ); | ||
| 11 | |||
| 12 | buildDoc(); | ||
| 13 | } | ||
| 14 | |||
| 15 | XmlStringReader::~XmlStringReader() | ||
| 16 | { | ||
| 17 | } | ||
| 18 | |||
| 19 | char XmlStringReader::getChar( int nAdd ) | ||
| 20 | { | ||
| 21 | if( nLength >= nIndex+nAdd+1 ) | ||
| 22 | { | ||
| 23 | return sString[nIndex+nAdd]; | ||
| 24 | } | ||
| 25 | else | ||
| 26 | { | ||
| 27 | return '\0'; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | void XmlStringReader::usedChar() | ||
| 32 | { | ||
| 33 | if( nLength >= nIndex+1 ) | ||
| 34 | { | ||
| 35 | nIndex++; | ||
| 36 | } | ||
| 37 | } | ||
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 @@ | |||
| 1 | #ifndef XMLSTRINGREADER | ||
| 2 | #define XMLSTRINGREADER | ||
| 3 | |||
| 4 | #include <stdio.h> | ||
| 5 | #include "xmlreader.h" | ||
| 6 | #include "flexbuf.h" | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Takes care of reading in xml formatted data from a file. This could/should | ||
| 10 | * be made more arbitrary in the future so that we can read the data from any | ||
| 11 | * source. This is actually made quite simple already since all data read in | ||
| 12 | * is handled by one single helper function and then palced into a FlexBuf for | ||
| 13 | * easy access by the other functions. The FlexBuf also allows for block | ||
| 14 | * reading from disk, which improves speed by a noticable amount. | ||
| 15 | * <br> | ||
| 16 | * There are also some extra features implemented that allow you to break the | ||
| 17 | * standard XML reader specs and eliminate leading and trailing whitespace in | ||
| 18 | * all read content. This is useful in situations where you allow additional | ||
| 19 | * whitespace in the files to make them easily human readable. The resturned | ||
| 20 | * content will be NULL in sitautions where all content between nodes was | ||
| 21 | * stripped. | ||
| 22 | *@author Mike Buland | ||
| 23 | */ | ||
| 24 | class XmlStringReader : public XmlReader | ||
| 25 | { | ||
| 26 | public: | ||
| 27 | /** | ||
| 28 | * Create a new string reader around an already created and formatted | ||
| 29 | * null-terminated string. | ||
| 30 | *@param sString A pointer to the string data that will be used. This data | ||
| 31 | * is not changed during processing. | ||
| 32 | *@param bStrip Strip out leading and trailing whitespace. | ||
| 33 | */ | ||
| 34 | XmlStringReader( const char *sString, bool bStrip=false ); | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Destroy this string reader. | ||
| 38 | */ | ||
| 39 | ~XmlStringReader(); | ||
| 40 | |||
| 41 | private: | ||
| 42 | char getChar( int nIndex = 0 ); | ||
| 43 | void usedChar(); | ||
| 44 | const char *sString; /**< Internal pointer to the input string. */ | ||
| 45 | int nIndex; /**< Our index into the string */ | ||
| 46 | int nLength; /**< The computed length of the string */ | ||
| 47 | }; | ||
| 48 | |||
| 49 | #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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include "xmlstringwriter.h" | ||
| 4 | |||
| 5 | XmlStringWriter::XmlStringWriter( const char *sIndent ) : | ||
| 6 | XmlWriter( sIndent ) | ||
| 7 | { | ||
| 8 | } | ||
| 9 | |||
| 10 | XmlStringWriter::~XmlStringWriter() | ||
| 11 | { | ||
| 12 | } | ||
| 13 | |||
| 14 | void XmlStringWriter::writeString( const char *sString ) | ||
| 15 | { | ||
| 16 | sXml += sString; | ||
| 17 | } | ||
| 18 | |||
| 19 | std::string &XmlStringWriter::getString() | ||
| 20 | { | ||
| 21 | return sXml; | ||
| 22 | } | ||
| 23 | |||
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 @@ | |||
| 1 | #ifndef XML_STRING_WRITER | ||
| 2 | #define XML_STRING_WRITER | ||
| 3 | |||
| 4 | #include "xmlnode.h" | ||
| 5 | #include "xmlwriter.h" | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Implements xml writing in the XML standard format. Also allows you to | ||
| 9 | * break that format and auto-indent your exported xml data for ease of | ||
| 10 | * reading. The auto-indenting will only be applied to sections that | ||
| 11 | * have no content of their own already. This means that except for | ||
| 12 | * whitespace all of your data will be preserved perfectly. | ||
| 13 | * You can create an XmlWriter object around a file, or access the static | ||
| 14 | * write function directly and just hand it a filename and a root XmlNode. | ||
| 15 | * When using an XmlWriter object the interface is identicle to that of | ||
| 16 | * the XmlDocument class, so reference that class for API info. However | ||
| 17 | * when the initial (or root) node is closed, and the document is finished | ||
| 18 | * the file will be created and written to automatically. The user can | ||
| 19 | * check to see if this is actually true by calling the isFinished | ||
| 20 | * function in the XmlDocument class. | ||
| 21 | *@author Mike Buland | ||
| 22 | */ | ||
| 23 | class XmlStringWriter : public XmlWriter | ||
| 24 | { | ||
| 25 | public: | ||
| 26 | /** | ||
| 27 | * Construct a string writer using an internal string buffer. | ||
| 28 | *@param sIndent Optional indent to add to each line. | ||
| 29 | */ | ||
| 30 | XmlStringWriter( const char *sIndent=NULL ); | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Destroy the string writer and the internal string. | ||
| 34 | */ | ||
| 35 | ~XmlStringWriter(); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Get the string that was built. This is only valid after the document has | ||
| 39 | * been completed, so check isCompleted or be sure your addNode and | ||
| 40 | * closeNode calls match up. | ||
| 41 | *@returns A reference to the internal string object. | ||
| 42 | */ | ||
| 43 | std::string &getString(); | ||
| 44 | |||
| 45 | private: | ||
| 46 | void writeString( const char *sString ); | ||
| 47 | std::string sXml; /**< The string object we "write" to. */ | ||
| 48 | }; | ||
| 49 | |||
| 50 | #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 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | #include "xmlwriter.h" | ||
| 4 | |||
| 5 | XmlWriter::XmlWriter( const char *sIndent, XmlNode *pRoot ) : | ||
| 6 | XmlDocument( pRoot ) | ||
| 7 | { | ||
| 8 | if( sIndent == NULL ) | ||
| 9 | { | ||
| 10 | this->sIndent = ""; | ||
| 11 | } | ||
| 12 | else | ||
| 13 | { | ||
| 14 | this->sIndent = sIndent; | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | XmlWriter::~XmlWriter() | ||
| 19 | { | ||
| 20 | } | ||
| 21 | |||
| 22 | void XmlWriter::write() | ||
| 23 | { | ||
| 24 | write( getRoot(), sIndent.c_str() ); | ||
| 25 | } | ||
| 26 | |||
| 27 | void XmlWriter::write( XmlNode *pRoot, const char *sIndent ) | ||
| 28 | { | ||
| 29 | writeNode( pRoot, 0, sIndent ); | ||
| 30 | } | ||
| 31 | |||
| 32 | void XmlWriter::closeNode() | ||
| 33 | { | ||
| 34 | XmlDocument::closeNode(); | ||
| 35 | |||
| 36 | if( isCompleted() ) | ||
| 37 | { | ||
| 38 | write( getRoot(), sIndent.c_str() ); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | void XmlWriter::writeIndent( int nIndent, const char *sIndent ) | ||
| 43 | { | ||
| 44 | if( sIndent == NULL ) return; | ||
| 45 | for( int j = 0; j < nIndent; j++ ) | ||
| 46 | { | ||
| 47 | writeString( sIndent ); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | std::string XmlWriter::escape( std::string sIn ) | ||
| 52 | { | ||
| 53 | std::string sOut; | ||
| 54 | |||
| 55 | std::string::const_iterator i; | ||
| 56 | for( i = sIn.begin(); i != sIn.end(); i++ ) | ||
| 57 | { | ||
| 58 | if( ((*i >= ' ' && *i <= '9') || | ||
| 59 | (*i >= 'a' && *i <= 'z') || | ||
| 60 | (*i >= 'A' && *i <= 'Z') ) && | ||
| 61 | (*i != '\"' && *i != '\'' ) | ||
| 62 | ) | ||
| 63 | { | ||
| 64 | sOut += *i; | ||
| 65 | } | ||
| 66 | else | ||
| 67 | { | ||
| 68 | sOut += "&#"; | ||
| 69 | char buf[4]; | ||
| 70 | sprintf( buf, "%d", *i ); | ||
| 71 | sOut += buf; | ||
| 72 | sOut += ';'; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | return sOut; | ||
| 77 | } | ||
| 78 | |||
| 79 | void XmlWriter::writeNodeProps( XmlNode *pNode, int nIndent, const char *sIndent ) | ||
| 80 | { | ||
| 81 | for( int j = 0; j < pNode->getNumProperties(); j++ ) | ||
| 82 | { | ||
| 83 | writeString(" "); | ||
| 84 | writeString( pNode->getPropertyName( j ) ); | ||
| 85 | writeString("=\""); | ||
| 86 | writeString( escape( pNode->getProperty( j ) ).c_str() ); | ||
| 87 | writeString("\""); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | void XmlWriter::writeNode( XmlNode *pNode, int nIndent, const char *sIndent ) | ||
| 92 | { | ||
| 93 | if( pNode->hasChildren() ) | ||
| 94 | { | ||
| 95 | writeIndent( nIndent, sIndent ); | ||
| 96 | writeString("<"); | ||
| 97 | writeString( pNode->getName() ); | ||
| 98 | writeNodeProps( pNode, nIndent, sIndent ); | ||
| 99 | if( sIndent ) | ||
| 100 | writeString(">\n"); | ||
| 101 | else | ||
| 102 | writeString(">"); | ||
| 103 | |||
| 104 | if( pNode->getContent( 0 ) ) | ||
| 105 | { | ||
| 106 | writeIndent( nIndent+1, sIndent ); | ||
| 107 | if( sIndent ) | ||
| 108 | { | ||
| 109 | writeString( pNode->getContent( 0 ) ); | ||
| 110 | writeString("\n"); | ||
| 111 | } | ||
| 112 | else | ||
| 113 | writeString( pNode->getContent( 0 ) ); | ||
| 114 | } | ||
| 115 | |||
| 116 | int nNumChildren = pNode->getNumChildren(); | ||
| 117 | for( int j = 0; j < nNumChildren; j++ ) | ||
| 118 | { | ||
| 119 | writeNode( pNode->getChild( j ), nIndent+1, sIndent ); | ||
| 120 | if( pNode->getContent( j+1 ) ) | ||
| 121 | { | ||
| 122 | writeIndent( nIndent+1, sIndent ); | ||
| 123 | if( sIndent ) | ||
| 124 | { | ||
| 125 | writeString( pNode->getContent( j+1 ) ); | ||
| 126 | writeString("\n"); | ||
| 127 | } | ||
| 128 | else | ||
| 129 | writeString( pNode->getContent( j+1 ) ); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | writeIndent( nIndent, sIndent ); | ||
| 134 | if( sIndent ) | ||
| 135 | { | ||
| 136 | writeString("</"); | ||
| 137 | writeString( pNode->getName() ); | ||
| 138 | writeString(">\n"); | ||
| 139 | } | ||
| 140 | else | ||
| 141 | { | ||
| 142 | writeString("</"); | ||
| 143 | writeString( pNode->getName() ); | ||
| 144 | writeString(">"); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | else if( pNode->getContent() ) | ||
| 148 | { | ||
| 149 | writeIndent( nIndent, sIndent ); | ||
| 150 | writeString("<"); | ||
| 151 | writeString( pNode->getName() ); | ||
| 152 | writeNodeProps( pNode, nIndent, sIndent ); | ||
| 153 | writeString(">"); | ||
| 154 | writeString( pNode->getContent() ); | ||
| 155 | writeString("</"); | ||
| 156 | writeString( pNode->getName() ); | ||
| 157 | writeString(">"); | ||
| 158 | if( sIndent ) | ||
| 159 | writeString("\n"); | ||
| 160 | } | ||
| 161 | else | ||
| 162 | { | ||
| 163 | writeIndent( nIndent, sIndent ); | ||
| 164 | writeString("<"); | ||
| 165 | writeString( pNode->getName() ); | ||
| 166 | writeNodeProps( pNode, nIndent, sIndent ); | ||
| 167 | if( sIndent ) | ||
| 168 | writeString("/>\n"); | ||
| 169 | else | ||
| 170 | writeString("/>"); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
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 @@ | |||
| 1 | #ifndef XMLWRITER | ||
| 2 | #define XMLWRITER | ||
| 3 | |||
| 4 | #include "xmlnode.h" | ||
| 5 | #include "xmldocument.h" | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Implements xml writing in the XML standard format. Also allows you to | ||
| 9 | * break that format and auto-indent your exported xml data for ease of | ||
| 10 | * reading. The auto-indenting will only be applied to sections that | ||
| 11 | * have no content of their own already. This means that except for | ||
| 12 | * whitespace all of your data will be preserved perfectly. | ||
| 13 | * You can create an XmlWriter object around a file, or access the static | ||
| 14 | * write function directly and just hand it a filename and a root XmlNode. | ||
| 15 | * When using an XmlWriter object the interface is identicle to that of | ||
| 16 | * the XmlDocument class, so reference that class for API info. However | ||
| 17 | * when the initial (or root) node is closed, and the document is finished | ||
| 18 | * the file will be created and written to automatically. The user can | ||
| 19 | * check to see if this is actually true by calling the isFinished | ||
| 20 | * function in the XmlDocument class. | ||
| 21 | *@author Mike Buland | ||
| 22 | */ | ||
| 23 | class XmlWriter : public XmlDocument | ||
| 24 | { | ||
| 25 | public: | ||
| 26 | /** | ||
| 27 | * Construct a standard XmlWriter. | ||
| 28 | *@param sIndent Set this to something other than NULL to include it as an | ||
| 29 | * indent before each node in the output that doesn't already have content. | ||
| 30 | * If you are using the whitespace stripping option in the XmlReader and set | ||
| 31 | * this to a tab or some spaces it will never effect the content of your | ||
| 32 | * file. | ||
| 33 | */ | ||
| 34 | XmlWriter( const char *sIndent=NULL, XmlNode *pRoot=NULL ); | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Destroy the writer. | ||
| 38 | */ | ||
| 39 | ~XmlWriter(); | ||
| 40 | |||
| 41 | /** | ||
| 42 | * This override of the parent class closeNode function calls the parent | ||
| 43 | * class, but also triggers a write operation when the final node is closed. | ||
| 44 | * This means that by checking the isCompleted() function the user may also | ||
| 45 | * check to see if their file has been written or not. | ||
| 46 | */ | ||
| 47 | void closeNode(); | ||
| 48 | |||
| 49 | void write(); | ||
| 50 | |||
| 51 | private: | ||
| 52 | std::string sIndent; /**< The indent string */ | ||
| 53 | |||
| 54 | std::string escape( std::string sIn ); | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Write the file. | ||
| 58 | *@param pNode The root node | ||
| 59 | *@param sIndent The indent text. | ||
| 60 | */ | ||
| 61 | void write( XmlNode *pNode, const char *sIndent=NULL ); | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Write a node in the file, including children. | ||
| 65 | *@param pNode The node to write. | ||
| 66 | *@param nIndent The indent level (the number of times to include sIndent) | ||
| 67 | *@param sIndent The indent text. | ||
| 68 | */ | ||
| 69 | void writeNode( XmlNode *pNode, int nIndent, const char *sIndent ); | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Write the properties of a node. | ||
| 73 | *@param pNode The node who's properties to write. | ||
| 74 | *@param nIndent The indent level of the containing node | ||
| 75 | *@param sIndent The indent text. | ||
| 76 | */ | ||
| 77 | void writeNodeProps( XmlNode *pNode, int nIndent, const char *sIndent ); | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Called to write the actual indent. | ||
| 81 | *@param nIndent The indent level. | ||
| 82 | *@param sIndent The indent text. | ||
| 83 | */ | ||
| 84 | void writeIndent( int nIndent, const char *sIndent ); | ||
| 85 | |||
| 86 | /** | ||
| 87 | * This is the function that must be overridden in order to use this class. | ||
| 88 | * It must write the null-terminated string sString, minus the mull, | ||
| 89 | * verbatum to it's output device. Adding extra characters for any reason | ||
| 90 | * will break the XML formatting. | ||
| 91 | *@param sString The string data to write to the output. | ||
| 92 | */ | ||
| 93 | virtual void writeString( const char *sString ) = 0; | ||
| 94 | }; | ||
| 95 | |||
| 96 | #endif | ||
