From c9ed8204191ea56e50fb2b51c7239dd8244bfd24 Mon Sep 17 00:00:00 2001
From: CaS
Date: Mon, 14 Nov 2005 20:37:33 +0000
Subject: [PATCH] Simplify library ... move portions into WebServer and
Performance libraries
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/sqlclient/trunk@22006 72102866-910b-0410-8b05-ffd578937521
---
ChangeLog | 6 +
GNUmakefile | 30 +-
Performance.import | 3 +
README | 32 -
SQLClient.h | 144 +---
SQLClient.jigs | 28 +-
SQLClient.m | 594 +-------------
WebServer.h | 611 --------------
WebServer.m | 1910 --------------------------------------------
WebServerBundles.m | 248 ------
testPostgres.m | 5 +-
testWebServer.m | 85 --
12 files changed, 36 insertions(+), 3660 deletions(-)
create mode 100644 Performance.import
delete mode 100644 WebServer.h
delete mode 100644 WebServer.m
delete mode 100644 WebServerBundles.m
delete mode 100644 testWebServer.m
diff --git a/ChangeLog b/ChangeLog
index e24e07b..9d1f46e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2005-11-14 Richard Frith-Macdonald
+
+ Factor out WebServer into separate library, and timer and caching
+ stuff into Performance library. Make this library depend on the
+ Performance library.
+
2005-10-27 Richard Frith-Macdonald
* WebServer.m: Add more accurate timestamps and implement request
diff --git a/GNUmakefile b/GNUmakefile
index 9478b3d..8a778cb 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -3,7 +3,7 @@ include $(GNUSTEP_MAKEFILES)/common.make
-include config.make
PACKAGE_NAME = SQLClient
-PACKAGE_VERSION = 1.1.0
+PACKAGE_VERSION = 1.2.0
CVS_MODULE_NAME = gnustep/dev-libs/SQLClient
CVS_TAG_NAME = SQLClient
@@ -12,35 +12,16 @@ TEST_TOOL_NAME=
LIBRARY_NAME=SQLClient
DOCUMENT_NAME=SQLClient
-SQLClient_INTERFACE_VERSION=1.1
+SQLClient_INTERFACE_VERSION=1.2
SQLClient_OBJC_FILES = SQLClient.m
-SQLClient_LIBRARIES_DEPEND_UPON =
+SQLClient_LIBRARIES_DEPEND_UPON = -lPerformance
SQLClient_HEADER_FILES = SQLClient.h
SQLClient_AGSDOC_FILES = SQLClient.h
# Optional Java wrappers for the library
JAVA_WRAPPER_NAME = SQLClient
-#
-# Assume that the use of the gnu runtime means we have the gnustep
-# base library and can use its extensions to build WebServer stuff.
-#
-ifeq ($(OBJC_RUNTIME_LIB),gnu)
-APPLE=0
-else
-APPLE=1
-endif
-
-ifeq ($(APPLE),1)
-ADDITIONAL_OBJC_LIBS += -lgnustep-baseadd
-SQLClient_LIBRARIES_DEPEND_UPON = -lgnustep-baseadd
-else
-SQLClient_OBJC_FILES += WebServer.m WebServerBundles.m
-SQLClient_HEADER_FILES += WebServer.h
-SQLClient_AGSDOC_FILES += WebServer.h
-endif
-
SQLClient_HEADER_FILES_INSTALL_DIR = SQLClient
BUNDLE_NAME=
@@ -148,11 +129,6 @@ Oracle_libs_BUNDLE_LIBS += -lclntsh \
Oracle_libs_PRINCIPAL_CLASS = SQLClientOracle_libs
endif
-TEST_TOOL_NAME+=testWebServer
-testWebServer_OBJC_FILES = testWebServer.m
-testWebServer_TOOL_LIBS += -lSQLClient
-testWebServer_LIB_DIRS += -L./obj
-
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/library.make
diff --git a/Performance.import b/Performance.import
new file mode 100644
index 0000000..a8b238b
--- /dev/null
+++ b/Performance.import
@@ -0,0 +1,3 @@
+
+import gnu.gnustep.Performance.*;
+
diff --git a/README b/README
index 236d20c..952e1df 100644
--- a/README
+++ b/README
@@ -80,35 +80,3 @@ should be entered on the GNUstep project page
-
-The WebServer class provides the framework for a GNUstep program to act as
-an HTTP or HTTPS server for simple applications.
-It does not attempt to be a general-purpose web server, but is rather
-intended to permit a program to easily handle requests from automated
-systems which are intended to control, monitor, or use the services
-provided by the program in which the class is embedded.
-The emphasis is on making it robust/reliable/simple, so you can rapidly
-develop software using it. It is a single-threaded, single-process
-system using asynchronous I/O, so you can easily run it under debug
-in gdb to fix any bugs in your delegate object.
-In particular of course, it may be used in conjunction with the
-SQLClient class to implement web-based database applications.
-
-The class is controlled by a few straightforward settings and basically
-operates by handing over requests to its delegate. The delegate must at
-least implement the -processRequest:response:for: method.
-
-Built-in facilities include -
-
-* Parsing of parameter string in request URL
-* Parsing of url encoded form data in a POST request
-* Parsing of form encoded data in a POST request
-* Substitution into template pages on output
-* SSL support
-* Limit access by IP address
-* Limit total number of simultaneous connections
-* Limit number of simultaneous connectionsform one address
-* Limit idle time permitted on a connection
-* Limit size of request headers permitted
-* Limit size of request body permitted
-
diff --git a/SQLClient.h b/SQLClient.h
index 78ae2be..44c8f65 100644
--- a/SQLClient.h
+++ b/SQLClient.h
@@ -38,12 +38,6 @@
faster, easier to use, and easier to add new database backends for
than JDBC).
-
- The library also provides a framework for making such simple database
- applications readily available as web applications using the
- [WebServer] class, which allows your applications to act as stand-alone
- web servers.
-
The major features of the SQLClient library are -
@@ -177,12 +171,12 @@
#include
+@class GSCache;
@class NSData;
@class NSDate;
@class NSMutableSet;
@class NSRecursiveLock;
@class NSString;
-@class SQLCache;
@class SQLTransaction;
/**
@@ -322,7 +316,7 @@ extern unsigned SQLClientTimeTick();
NSTimeInterval _lastOperation;
NSTimeInterval _duration;
unsigned int _debugging; /** The current debugging level */
- SQLCache *_cache; /** The cache for query results */
+ GSCache *_cache; /** The cache for query results */
}
/**
@@ -960,136 +954,6 @@ extern unsigned SQLClientTimeTick();
- (void) setDurationLogging: (NSTimeInterval)threshold;
@end
-/**
- * The SQLCache class is used to maintain a cache of objects.
- * When full, old objects are removed to make room for new ones
- * on a least-recently-used basis.
- * Cache sizes may be limited by the number of objects in the cache,
- * or by the memory used by the cache, or both. Calculation of the
- * size of items in the cache is relatively expensive, so caches are
- * only limited by number of objects in the default case.
- * Objects stored in the cache may be given a limited lifetime,
- * in which case an attempt to fetch an expired object
- * from the cache will cause it to be removed from the cache instead.
- */
-@interface SQLCache : NSObject
-
-/**
- * Return all the current cache instances... useful if you want to do
- * something to all cache instances in your process.
- */
-+ (NSArray*) allCaches;
-
-/**
- * Return a report on all SQLCache instances ... calls the -description
- * method of the individual cache instances to get a report on each one.
- */
-+ (NSString*) description;
-
-/**
- * Return the count of objects currently in the cache.
- */
-- (unsigned) currentObjects;
-
-/**
- * Return the total size of the objects currently in the cache.
- */
-- (unsigned) currentSize;
-
-/**
- * Return the default lifetime for items set in the cache.
- * A value of zero means that items are not purged based on lifetime.
- */
-- (unsigned) lifetime;
-
-/**
- * Return the maximum number of items in the cache.
- * A value of zero means there is no limit.
- */
-- (unsigned) maxObjects;
-
-/**
- * Return the maximum tital size of items in the cache.
- * A value of zero means there is no limit.
- */
-- (unsigned) maxSize;
-
-/**
- * Return the name of this instance (as set using -setName:)
- */
-- (NSString*) name;
-
-/**
- * Return the cached value for the specified key, or nil if there
- * is no value in the cache.
- */
-- (id) objectForKey: (NSString*)aKey;
-
-/**
- * Remove all items whose lifetimes have passed
- * (if lifetimes are in use for the cache).
- * The 'current' timestamp is that of the last call to SQLClientTimeNow(),
- * which is usually the timestmap of the most recent SQL query.
- */
-- (void) purge;
-
-/**
- * Sets the lifetime (seconds) for items added to the cache. If this
- * is set to zero then items are not removed from the cache based on
- * lifetimes when the cache is full and an object is added, though
- * expired items are still removed when an attempt to retrieve
- * them is made.
- */
-- (void) setLifetime: (unsigned)max;
-
-/**
- * Sets the maximum number of objects in the cache. If this is non-zero
- * then an attempt to set an object in a full cache will result in the
- * least recently used item in the cache being removed.
- */
-- (void) setMaxObjects: (unsigned)max;
-
-/**
- * Sets the maximum total size for objects in the cache. If this is non-zero
- * then an attempt to set an object whose size would exceed the cache limit
- * will result in the least recently used items in the cache being removed.
- */
-- (void) setMaxSize: (unsigned)max;
-
-/**
- * Sets the name of this instance.
- */
-- (void) setName: (NSString*)name;
-
-/**
- * Sets (or replaces)the cached value for the specified key.
- */
-- (void) setObject: (id)anObject forKey: (NSString*)aKey;
-
-/**
- * Sets (or replaces)the cached value for the specified key, giving
- * the value the specified lifetime (in seconds). A lifetime of zero
- * means that the item is not limited by lifetime.
- */
-- (void) setObject: (id)anObject
- forKey: (NSString*)aKey
- lifetime: (unsigned)lifetime;
-
-/**
- * Called by -setObject:forKey:lifetime: to make space for a new
- * object in the cache (also when the cache is resized).
- * This will, if a lifetime is set (see the -setLifetime: method)
- * first purge all expired objects from the cache, then
- * (if necessary) remove objects from the cache until the number
- * of objects and size of cache meet the limits specified.
- * If the objects argument is zero then all objects are removed from
- * the cache.
- * The size argument is used only if a maximum size is set
- * for the cache.
- */
-- (void) shrinkObjects: (unsigned)objects andSize: (unsigned)size;
-@end
-
/**
* This category porovides methods for caching the results of queries
* in order to reduce the number of client-server trips and the database
@@ -1102,7 +966,7 @@ extern unsigned SQLClientTimeTick();
* Returns the cache used by the receiver for storing the results of
* requests made through it.
*/
-- (SQLCache*) cache;
+- (GSCache*) cache;
/**
* If the result of the query is already cached and is still valid,
@@ -1148,7 +1012,7 @@ extern unsigned SQLClientTimeTick();
* If aCache is nil, the current cache is released, and a new cache will
* be automatically created as soon as there is a need to cache anything.
*/
-- (void) setCache: (SQLCache*)aCache;
+- (void) setCache: (GSCache*)aCache;
@end
diff --git a/SQLClient.jigs b/SQLClient.jigs
index e44e22c..0a59d08 100644
--- a/SQLClient.jigs
+++ b/SQLClient.jigs
@@ -1,36 +1,14 @@
{ /* -*-c-*- */
"prerequisite libraries" = (
- "gnustep-base"
+ "gnustep-base",
+ "Performance"
);
types = {
NSTimeInterval = double;
};
classes = (
{
- "java name" = "gnu.gnustep.SQLClient.SQLCache";
- "objective-c name" = "SQLCache";
- "class methods" = (
- "allCaches"
- );
- "instance methods" = (
- "currentObjects",
- "currentSize",
- "lifetime",
- "maxObjects",
- "maxSize",
- "name",
- "objectForKey:",
- "purge",
- "setLifetime:",
- "setMaxObjects:",
- "setMaxSize:",
- "setName:",
- "setObject:forKey:",
- "setObject:forKey:lifetime:",
- "shrinkObjects:andSize:"
- );
- },
- {
+ "file to include in preamble java code" = "Performance.import";
"java name" = "gnu.gnustep.SQLClient.SQLClient";
"objective-c name" = "SQLClient";
"class methods" = (
diff --git a/SQLClient.m b/SQLClient.m
index 231bcc5..f729f2e 100644
--- a/SQLClient.m
+++ b/SQLClient.m
@@ -48,6 +48,7 @@
#include
#include
+#include
#include "SQLClient.h"
@@ -59,126 +60,6 @@ static NSNull *null = nil;
static Class NSStringClass = 0;
static Class NSDateClass = 0;
-static SEL tiSel = 0;
-static NSTimeInterval (*tiImp)(Class,SEL) = 0;
-
-static NSTimeInterval baseTime = 0;
-static NSTimeInterval lastTime = 0;
-
-inline NSTimeInterval SQLClientTimeLast()
-{
- return lastTime;
-}
-
-inline NSTimeInterval SQLClientTimeNow()
-{
- if (baseTime == 0)
- {
- NSDateClass = [NSDate class];
- tiSel = @selector(timeIntervalSinceReferenceDate);
- tiImp
- = (NSTimeInterval (*)(Class,SEL))[NSDateClass methodForSelector: tiSel];
- baseTime = lastTime = (*tiImp)(NSDateClass, tiSel);
- return baseTime;
- }
- else
- {
- NSTimeInterval now = (*tiImp)(NSDateClass, tiSel);
-
- /*
- * If the clock has been reset so that time has gone backwards,
- * we adjust the baseTime so that lastTime >= baseTime is true.
- */
- if (now < lastTime)
- {
- baseTime -= (lastTime - now);
- }
- lastTime = now;
- return lastTime;
- }
-}
-
-inline NSTimeInterval SQLClientTimeStart()
-{
- if (baseTime == 0)
- {
- return SQLClientTimeNow();
- }
- return baseTime;
-}
-
-inline unsigned SQLClientTimeTick()
-{
- NSTimeInterval start = SQLClientTimeStart();
-
- return (SQLClientTimeLast() - start) + 1;
-}
-
-@interface NSArray (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude;
-@end
-
-@interface NSData (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude;
-@end
-
-@interface NSObject (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude;
-@end
-
-@interface NSString (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude;
-@end
-
-@implementation NSArray (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude
-{
- if ([exclude member: self] != nil)
- {
- return 0;
- }
- else
- {
- unsigned count = [self count];
- unsigned size = [super sizeInBytes: exclude] + count*sizeof(void*);
-
- if (exclude == nil)
- {
- exclude = [NSMutableSet setWithCapacity: 8];
- }
- [exclude addObject: self];
- while (count-- > 0)
- {
- size += [[self objectAtIndex: count] sizeInBytes: exclude];
- }
- return size;
- }
-}
-@end
-
-@implementation NSData (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude
-{
- if ([exclude member: self] != nil) return 0;
- return [super sizeInBytes: exclude] + [self length];
-}
-@end
-
-@implementation NSObject (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude
-{
- if ([exclude member: self] != nil) return 0;
- return isa->instance_size;
-}
-@end
-
-@implementation NSString (SizeInBytes)
-- (unsigned) sizeInBytes: (NSMutableSet*)exclude
-{
- if ([exclude member: self] != nil) return 0;
- return [super sizeInBytes: exclude] + sizeof(unichar) * [self length];
-}
-@end
@implementation SQLRecord
+ (id) allocWithZone: (NSZone*)aZone
@@ -189,7 +70,7 @@ inline unsigned SQLClientTimeTick()
+ (void) initialize
{
- SQLClientTimeNow();
+ GSTickerTimeNow();
if (null == nil)
{
null = [NSNull new];
@@ -542,7 +423,7 @@ static unsigned int maxConnections = 8;
+ (void) initialize
{
- SQLClientTimeNow();
+ GSTickerTimeNow();
if (null == nil)
{
null = [NSNull new];
@@ -1215,10 +1096,10 @@ static void quoteString(NSMutableString *s)
if (_duration >= 0)
{
- start = SQLClientTimeNow();
+ start = GSTickerTimeNow();
}
[self backendExecute: info];
- _lastOperation = SQLClientTimeNow();
+ _lastOperation = GSTickerTimeNow();
[_statements addObject: statement];
if (_duration >= 0)
{
@@ -1289,10 +1170,10 @@ static void quoteString(NSMutableString *s)
if (_duration >= 0)
{
- start = SQLClientTimeNow();
+ start = GSTickerTimeNow();
}
result = [self backendQuery: stmt];
- _lastOperation = SQLClientTimeNow();
+ _lastOperation = GSTickerTimeNow();
if (_duration >= 0)
{
NSTimeInterval d;
@@ -1787,7 +1668,7 @@ static void quoteString(NSMutableString *s)
*/
+ (void) _tick: (NSTimer*)t
{
- SQLClientTimeNow();
+ GSTickerTimeNow();
}
@end
@@ -1906,11 +1787,11 @@ static void quoteString(NSMutableString *s)
@implementation SQLClient (Caching)
-- (SQLCache*) cache
+- (GSCache*) cache
{
if (_cache == nil)
{
- _cache = [SQLCache new];
+ _cache = [GSCache new];
}
return _cache;
}
@@ -1942,8 +1823,8 @@ static void quoteString(NSMutableString *s)
[lock lock];
NS_DURING
{
- NSTimeInterval start = SQLClientTimeNow();
- SQLCache *c = [self cache];
+ NSTimeInterval start = GSTickerTimeNow();
+ GSCache *c = [self cache];
id toCache = nil;
if (seconds < 0)
@@ -1958,7 +1839,7 @@ static void quoteString(NSMutableString *s)
if (result == nil)
{
result = toCache = [self backendQuery: stmt];
- _lastOperation = SQLClientTimeNow();
+ _lastOperation = GSTickerTimeNow();
if (_duration >= 0)
{
NSTimeInterval d;
@@ -2002,7 +1883,7 @@ static void quoteString(NSMutableString *s)
return result;
}
-- (void) setCache: (SQLCache*)aCache
+- (void) setCache: (GSCache*)aCache
{
ASSIGN(_cache, aCache);
}
@@ -2125,450 +2006,3 @@ static void quoteString(NSMutableString *s)
}
@end
-
-@interface SQLCItem : NSObject
-{
-@public
- SQLCItem *next;
- SQLCItem *prev;
- unsigned when;
- unsigned size;
- NSString *key;
- id object;
-}
-+ (SQLCItem*) newWithObject: (id)anObject forKey: (NSString*)aKey;
-@end
-
-@implementation SQLCItem
-+ (SQLCItem*) newWithObject: (id)anObject forKey: (NSString*)aKey
-{
- SQLCItem *i;
-
- i = (SQLCItem*)NSAllocateObject(self, 0, NSDefaultMallocZone());
- i->object = RETAIN(anObject);
- i->key = [aKey copy];
- return i;
-}
-- (void) dealloc
-{
- RELEASE(key);
- RELEASE(object);
- NSDeallocateObject(self);
-}
-@end
-
-
-@implementation SQLCache
-
-static NSHashTable *SQLCacheInstances = 0;
-static NSLock *SQLCacheLock = nil;
-
-typedef struct {
- unsigned currentObjects;
- unsigned currentSize;
- unsigned lifetime;
- unsigned maxObjects;
- unsigned maxSize;
- unsigned hits;
- unsigned misses;
- NSMapTable *contents;
- SQLCItem *first;
- NSString *name;
- NSMutableSet *exclude;
-} SQLC;
-#define my ((SQLC*)&self[1])
-
-/*
- * Add item to linked list starting at *first
- */
-static void appendItem(SQLCItem *item, SQLCItem **first)
-{
- if (*first == nil)
- {
- item->next = item->prev = item;
- *first = item;
- }
- else
- {
- (*first)->prev->next = item;
- item->prev = (*first)->prev;
- (*first)->prev = item;
- item->next = *first;
- }
-}
-
-/*
- * Remove item from linked list starting at *first
- */
-static void removeItem(SQLCItem *item, SQLCItem **first)
-{
- if (*first == item)
- {
- if (item->next == item)
- {
- *first = nil;
- }
- else
- {
- *first = item->next;
- }
- }
- item->next->prev = item->prev;
- item->prev->next = item->next;
- item->prev = item->next = item;
-}
-
-+ (NSArray*) allCaches
-{
- NSArray *a;
-
- [SQLCacheLock lock];
- a = NSAllHashTableObjects(SQLCacheInstances);
- [SQLCacheLock unlock];
- return a;
-}
-
-+ (id) alloc
-{
- return [self allocWithZone: NSDefaultMallocZone()];
-}
-
-+ (id) allocWithZone: (NSZone*)z
-{
- SQLCache *c;
-
- c = (SQLCache*)NSAllocateObject(self, sizeof(SQLC), z);
- [SQLCacheLock lock];
- NSHashInsert(SQLCacheInstances, (void*)c);
- [SQLCacheLock unlock];
- return c;
-}
-
-+ (NSString*) description
-{
- NSMutableString *ms;
- NSHashEnumerator e;
- SQLCache *c;
-
- ms = [NSMutableString stringWithString: [super description]];
- [SQLCacheLock lock];
- e = NSEnumerateHashTable(SQLCacheInstances);
- while ((c = (SQLCache*)NSNextHashEnumeratorItem(&e)) != nil)
- {
- [ms appendFormat: @"\n%@", [c description]];
- }
- NSEndHashTableEnumeration(&e);
- [SQLCacheLock unlock];
- return ms;
-}
-
-+ (void) initialize
-{
- SQLClientTimeNow();
- if (null == nil)
- {
- null = [NSNull new];
- }
- if (SQLCacheInstances == 0)
- {
- SQLCacheLock = [NSLock new];
- SQLCacheInstances
- = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0);
- }
-}
-
-- (unsigned) currentObjects
-{
- return my->currentObjects;
-}
-
-- (unsigned) currentSize
-{
- return my->currentSize;
-}
-
-- (void) dealloc
-{
- [SQLCacheLock lock];
- if (my->contents != 0)
- {
- [self shrinkObjects: 0 andSize: 0];
- NSFreeMapTable(my->contents);
- }
- RELEASE(my->exclude);
- RELEASE(my->name);
- NSHashRemove(SQLCacheInstances, (void*)self);
- NSDeallocateObject(self);
- [SQLCacheLock unlock];
-}
-
-- (NSString*) description
-{
- NSString *n = my->name;
-
- if (n == nil)
- {
- n = [super description];
- }
- return [NSString stringWithFormat:
- @" %@\n"
- @" Items: %u(%u)\n"
- @" Size: %u(%u)\n"
- @" Life: %u\n"
- @" Hit: %u\n"
- @" Miss: %u\n",
- n,
- my->currentObjects, my->maxObjects,
- my->currentSize, my->maxSize,
- my->lifetime,
- my->hits,
- my->misses];
-}
-
-- (id) init
-{
- my->contents = NSCreateMapTable(NSObjectMapKeyCallBacks,
- NSObjectMapValueCallBacks, 0);
- return self;
-}
-
-- (unsigned) lifetime
-{
- return my->lifetime;
-}
-
-- (unsigned) maxObjects
-{
- return my->maxObjects;
-}
-
-- (unsigned) maxSize
-{
- return my->maxSize;
-}
-
-- (NSString*) name
-{
- return my->name;
-}
-
-- (id) objectForKey: (NSString*)aKey
-{
- SQLCItem *item;
- unsigned when = SQLClientTimeTick();
-
- item = (SQLCItem*)NSMapGet(my->contents, aKey);
- if (item == nil)
- {
- my->misses++;
- return nil;
- }
- removeItem(item, &my->first);
- if (item->when > 0 && item->when < when)
- {
- my->currentObjects--;
- if (my->maxSize > 0)
- {
- my->currentSize -= item->size;
- }
- NSMapRemove(my->contents, (void*)item->key);
- my->misses++;
- return nil; // Lifetime expired.
- }
-
- // Least recently used ... move to end of list.
- appendItem(item, &my->first);
- my->hits++;
- return item->object;
-}
-
-- (void) purge
-{
- unsigned when = SQLClientTimeTick();
-
- if (my->contents != 0)
- {
- NSMapEnumerator e;
- SQLCItem *i;
- NSString *k;
-
- e = NSEnumerateMapTable(my->contents);
- while (NSNextMapEnumeratorPair(&e, (void**)&k, (void**)&i) != 0)
- {
- if (i->when > 0 && i->when < when)
- {
- removeItem(i, &my->first);
- my->currentObjects--;
- if (my->maxSize > 0)
- {
- my->currentSize -= i->size;
- }
- NSMapRemove(my->contents, (void*)i->key);
- }
- }
- NSEndMapTableEnumeration(&e);
- }
-}
-
-- (void) setLifetime: (unsigned)max
-{
- my->lifetime = max;
-}
-
-- (void) setMaxObjects: (unsigned)max
-{
- my->maxObjects = max;
- if (my->currentObjects > my->maxObjects)
- {
- [self shrinkObjects: my->maxObjects
- andSize: my->maxSize];
- }
-}
-
-- (void) setMaxSize: (unsigned)max
-{
- if (max > 0 && my->maxSize == 0)
- {
- NSMapEnumerator e = NSEnumerateMapTable(my->contents);
- SQLCItem *i;
- NSString *k;
- unsigned size = 0;
-
- if (my->exclude == nil)
- {
- my->exclude = [NSMutableSet new];
- }
- while (NSNextMapEnumeratorPair(&e, (void**)&k, (void**)&i) != 0)
- {
- if (i->size == 0)
- {
- [my->exclude removeAllObjects];
- i->size = [i->object sizeInBytes: my->exclude];
- }
- if (i->size > max)
- {
- /*
- * Item in cache is too big for new size limit ...
- * Remove it.
- */
- removeItem(i, &my->first);
- NSMapRemove(my->contents, (void*)i->key);
- my->currentObjects--;
- continue;
- }
- size += i->size;
- }
- NSEndMapTableEnumeration(&e);
- my->currentSize = size;
- }
- else if (max == 0)
- {
- my->currentSize = 0;
- }
- my->maxSize = max;
- if (my->currentSize > my->maxSize)
- {
- [self shrinkObjects: my->maxObjects
- andSize: my->maxSize];
- }
-}
-
-- (void) setName: (NSString*)name
-{
- ASSIGN(my->name, name);
-}
-
-- (void) setObject: (id)anObject forKey: (NSString*)aKey
-{
- [self setObject: anObject forKey: aKey lifetime: my->lifetime];
-}
-
-- (void) setObject: (id)anObject
- forKey: (NSString*)aKey
- lifetime: (unsigned)lifetime
-{
- SQLCItem *item;
- unsigned maxObjects = my->maxObjects;
- unsigned maxSize = my->maxSize;
- unsigned addObjects = (anObject == nil ? 0 : 1);
- unsigned addSize = 0;
-
- item = (SQLCItem*)NSMapGet(my->contents, aKey);
- if (item != nil)
- {
- removeItem(item, &my->first);
- my->currentObjects--;
- if (my->maxSize > 0)
- {
- my->currentSize -= item->size;
- }
- NSMapRemove(my->contents, (void*)aKey);
- }
-
- if (maxSize > 0 || maxObjects > 0)
- {
- if (maxSize > 0)
- {
- if (my->exclude == nil)
- {
- my->exclude = [NSMutableSet new];
- }
- [my->exclude removeAllObjects];
- addSize = [anObject sizeInBytes: my->exclude];
- if (addSize > maxSize)
- {
- return; // Object too big to cache.
- }
- }
- }
-
- if (addObjects > 0)
- {
- /*
- * Make room for new object.
- */
- [self shrinkObjects: maxObjects - addObjects
- andSize: maxSize - addSize];
- item = [SQLCItem newWithObject: anObject forKey: aKey];
- if (lifetime > 0)
- {
- item->when = SQLClientTimeTick() + lifetime;
- }
- item->size = addSize;
- NSMapInsert(my->contents, (void*)item->key, (void*)item);
- appendItem(item, &my->first);
- my->currentObjects += addObjects;
- my->currentSize += addSize;
- RELEASE(item);
- }
-}
-
-- (void) shrinkObjects: (unsigned)objects andSize: (unsigned)size
-{
- unsigned newSize = [self currentSize];
- unsigned newObjects = [self currentObjects];
-
- if (newObjects > objects || (my->maxSize > 0 && newSize > size))
- {
- [self purge];
- newSize = [self currentSize];
- newObjects = [self currentObjects];
- while (newObjects > objects || (my->maxSize > 0 && newSize > size))
- {
- SQLCItem *item;
-
- item = my->first;
- removeItem(item, &my->first);
- newObjects--;
- if (my->maxSize > 0)
- {
- newSize -= item->size;
- }
- NSMapRemove(my->contents, (void*)item->key);
- }
- my->currentObjects = newObjects;
- my->currentSize = newSize;
- }
-}
-@end
-
diff --git a/WebServer.h b/WebServer.h
deleted file mode 100644
index cf1b91b..0000000
--- a/WebServer.h
+++ /dev/null
@@ -1,611 +0,0 @@
-/**
- Copyright (C) 2004 Free Software Foundation, Inc.
-
- Written by: Richard Frith-Macdonald
- Date: June 2004
-
- This file is part of the SQLClient Library.
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
-
-WebServer documentation
-
- The WebServer class
-
- What is the WebServer class?
-
- The WebServer class provides the framework for a GNUstep program to
- act as an HTTP or HTTPS server for simple applications.
- It does not attempt to be a general-purpose web server, but is rather
- intended to permit a program to easily handle requests from automated
- systems which are intended to control, monitor, or use the services
- provided by the program in which the class is embedded.
- The emphasis is on making it robust/reliable/simple, so you can rapidly
- develop software using it. It is a single-threaded, single-process
- system using asynchronous I/O, so you can easily run it under
- debug in gdb to fix any bugs in your delegate object.
- In particular of course, it may be used in conjunction with the
- [SQLClient] class to implement web-based database applications.
-
-
- The class is controlled by a few straightforward settings and
- basically operates by handing over requests to its delegate.
- The delegate must at least implement the
- [(WebServerDelegate)-processRequest:response:for:] method.
-
-
- Built-in facilities include -
-
-
- - Parsing of parameter string in request URL
- - Parsing of url encoded form data in a POST request
- - Parsing of form encoded data in a POST request
- - Substitution into template pages on output
- - SSL support
- - HTTP Basic authentication
- - Limit access by IP address
- - Limit total number of simultaneous connections
- - Limit number of simultaneous connectionsform one address
- - Limit idle time permitted on a connection
- - Limit size of request headers permitted
- - Limit size of request body permitted
-
-
-
-
- $Date$ $Revision$
- */
-
-#ifndef INCLUDED_WEBSERVER_H
-#define INCLUDED_WEBSERVER_H
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-@class WebServer;
-
-/**
- * This protocol is implemented by a delegate of a WebServer instance
- * in order to allow the delegate to process requests which arrive
- * at the server.
- */
-@protocol WebServerDelegate
-/**
- * Process the http request whose headers and data are provided in
- * a GSMimeDocument.
- * Extra headers are created as follows -
- *
- * x-http-method
- * The method from the HTTP request (eg. GET or POST)
- * x-http-path
- * The path from the HTTP request, or an empty string if
- * there was no path.
- * x-http-query
- * The query string from the HTTP request or an empty string
- * if there was no query.
- * x-http-version
- * The version from the HTTP request.
- * x-local-address
- * The IP address of the local host receiving the request.
- * x-local-port
- * The port of the local host receiving the request.
- * x-remote-address
- * The IP address of the host that the request came from.
- * x-remote-port
- * The port of the host that the request came from.
- * x-http-username
- * The username from the 'authorization' header if the request
- * supplied http basic authentication.
- * x-http-password
- * The password from the 'authorization' header if the request
- * supplied http basic authentication.
- *
- * On completion, the method must modify response to contain the data
- * and headers to be sent out.
- * The 'content-length' header need not be set in the response as it will
- * be overridden anyway.
- * The special 'http' header will be used as the response/status line.
- * If not supplied, 'HTTP/1.1 200 Success' or 'HTTP/1.1 204 No Content' will
- * be used as the response line, depending on whether the data is empty or
- * not.
- * If an exception is raised by this method, the response produced will
- * be set to 'HTTP/1.0 500 Internal Server Error' and the connection will
- * be closed.
- */
-- (BOOL) processRequest: (GSMimeDocument*)request
- response: (GSMimeDocument*)response
- for: (WebServer*)http;
-/**
- * Log an error or warning ... if the delegate does not implement this
- * method, the message is logged to stderr using the NSLog function.
- */
-- (void) webAlert: (NSString*)message for: (WebServer*)http;
-@end
-
-/**
- * You create an instance of the WebServer class in order to handle
- * incoming http or https requests on a single port.
- *
- * Before use, it must be configured using the -setPort:secure: method
- * to specify the port and if/how ssl is to be used.
- *
- * You must also set a delegate to handle incoming requests,
- * and may specify a maximum number of simultaneous connections
- * which may be in progress etc.
- *
- * In addition to the options which may be set directly in the class,
- * you can provide some configuration via the standard NSDefaults class.
- * This information is set at initialisation of an instance and the
- * class recognises the following defaults keys -
- *
- *
- * WebServerHosts
- * An array of host IP addresses to list the mhosts permitted to
- * send requests to the server. If defined, requests from other hosts
- * will be rejected. It's better to use firewalling to control this
- * sort of thing.
- *
- * WebServerQuiet
- * An array of host IP addresses to refrain from logging ...
- * this is useful if (for instance) you have a monitoring process which
- * sends requests to the server to be sure it's alive, and don't want
- * to log all the connections from this monitor.
- * Not only do we refrain from logging anything but exceptional events
- * about these hosts, connections and requests by these hosts are not
- * counted in statistics we generate.
- *
- *
- */
-@interface WebServer : NSObject
-{
-@private
- NSNotificationCenter *_nc;
- NSString *_port;
- BOOL _accepting;
- BOOL _verbose;
- BOOL _durations;
- NSDictionary *_sslConfig;
- NSArray *_quiet;
- NSArray *_hosts;
- unsigned int _substitutionLimit;
- unsigned int _maxBodySize;
- unsigned int _maxRequestSize;
- unsigned int _maxSessions;
- unsigned int _maxPerHost;
- id _delegate;
- NSFileHandle *_listener;
- NSMapTable *_sessions;
- unsigned _handled;
- unsigned _requests;
- NSString *_root;
- NSTimer *_ticker;
- NSTimeInterval _sessionTimeout;
- NSTimeInterval _ticked;
- NSCountedSet *_perHost;
-}
-
-/**
- * This method is called for each incoming request, and checks that the
- * requested resource is accessible (basic user/password access control).
- * The method returns YES if access is granted, or returns NO and sets the
- * appropriate response values if access is refused.
- * If access is refused by this method, the delegate is not informed of the
- * request at all ... so this forms an initial access control mechanism,
- * but if it is passed, the delegate is still free to implement its own
- * additional access control within the
- * [(WebServerDelegate)-processRequest:response:for:] method.
- * The access control is managed by the WebServerAccess
- * user default, which is a dictionary whose keys are paths, and whose
- * values are dictionaries specifying the access control for those paths.
- * Access control is done on the basis of the longest matching path.
- * Each access control dictionary contains an authentication realm string
- * (keyed on Realm) and a dictionary containing username/password
- * pairs (keyed on Users) or a dictionary containing information
- * to perform a database lookup of username and password
- * (keyed on UserDB).
- * eg.
- *
- * WebServerAccess = {
- * "" = {
- * Realm = "general";
- * Users = {
- * Fred = 1942;
- * };
- * };
- * "/private" = {
- * Realm = "private";
- * UserDB = {
- * // System will contact database using SQLClient and lookup password
- * // The SQLClient library must be linked in and used by the tool
- * // using WebServer ... it is not linked in by the WebServer library.
- * Name = databasename;
- * Table = tablename;
- * UsernameField = fielname1;
- * PasswordField = fielname2;
- * };
- * };
- * };
- *
- */
-- (BOOL) accessRequest: (GSMimeDocument*)request
- response: (GSMimeDocument*)response;
-
-/**
- * Decode an application/x-www-form-urlencoded form and store its
- * contents into the supplied dictionary.
- * The resulting dictionary keys are strings.
- * The resulting dictionary values are arrays of NSData objects.
- * You probably don't need to call this method yourself ... more likely
- * you will use the -parameters: method instead.
- * NB. For forms POSTed using multipart/form-data
you don't
- * need to perform any explicit decoding as this will already have been
- * done for you and the decoded form will be presented as the request
- * GSMimeDocument. The fields of the form will be the component parts
- * of the content of the request and can be accessed using the standard
- * GSMimeDocument methods.
- * This method returns the number of fields actually decoded.
- */
-- (unsigned) decodeURLEncodedForm: (NSData*)data
- into: (NSMutableDictionary*)dict;
-
-/**
- * Encode an application/x-www-form-urlencoded form and store its
- * representation in the supplied data object.
- * The dictionary contains the form, with keys as data objects or strings,
- * and values as arrays of values to be placed in the data.
- * Each value in the array may be a data object or a string.
- * As a special case, a value may be a data object or a string rather
- * than an array ... this is treated like an array of one value.
- * All non data keys and values are convertd to data using utf-8 encoding.
- * This method returns the number of values actually encoded.
- */
-- (unsigned) encodeURLEncodedForm: (NSDictionary*)dict
- into: (NSMutableData*)data;
-
-/**
- * Returns YES if the server is for HTTPS (encrypted connections),
- * NO otherwise.
- */
-- (BOOL) isSecure;
-
-/**
- * Extracts request parameters from the http query string and from the
- * request body (if it was application/x-www-form-urlencoded or
- * multipart/form-data) and return the extracted parameters as a
- * mutable dictionary whose keys are the parameter names and whose
- * values are arrays containing the data for each parameter.
- * You should call this no more than once per request, storing the result
- * and using it as an argument to the methods used to extract particular
- * parameters.
- * Parameters from the request data are added to any found in the
- * query string.
- * Values provided as multipart/form-data
are also available
- * in a more flexible format as the content of the request.
- */
-- (NSMutableDictionary*) parameters: (GSMimeDocument*)request;
-
-/**
- * Returns the index'th data parameter for the specified name.
- * Matching of names is case-insensitive
- * If there are no data items for the name, or if the index is
- * too large for the number of items which exist, this returns nil.
- */
-- (NSData*) parameter: (NSString*)name
- at: (unsigned)index
- from: (NSDictionary*)params;
-
-/**
- * Calls -parameter:at:from: with an index of zero.
- */
-- (NSData*) parameter: (NSString*)name from: (NSDictionary*)params;
-
-/**
- * Calls -parameterString:at:from:charset: with a nil charset so that
- * UTF-8 encoding is used for string conversion.
- */
-- (NSString*) parameterString: (NSString*)name
- at: (unsigned)index
- from: (NSDictionary*)params;
-/**
- * Calls -parameter:at:from: and, if the result is non-nil
- * converts the data to a string using the specified mime
- * characterset, (if charset is nil, UTF-8 is used).
- */
-- (NSString*) parameterString: (NSString*)name
- at: (unsigned)index
- from: (NSDictionary*)params
- charset: (NSString*)charset;
-/**
- * Calls -parameterString:at:from:charset: with an index of zero and
- * a nil value for charset (which causes data to be treated as UTF-8).
- */
-- (NSString*) parameterString: (NSString*)name
- from: (NSDictionary*)params;
-
-/**
- * Calls -parameterString:at:from:charset: with an index of zero.
- */
-- (NSString*) parameterString: (NSString*)name
- from: (NSDictionary*)params
- charset: (NSString*)charset;
-
-/**
- * Loads a template file from disk and places it in aResponse as content
- * whose mime type is determined from the file extension using the
- * provided mapping (or a simple built-in default mapping if map is nil).
- * If you have a dedicated web server for handling static pages (eg images)
- * it is better to use that rather than vending static pages using this
- * method. It's unlikley that this method can be as efficient as a dedicated
- * server. However this mechanism is adequate for moderate throughputs.
- */
-- (BOOL) produceResponse: (GSMimeDocument*)aResponse
- fromStaticPage: (NSString*)aPath
- using: (NSDictionary*)map;
-
-/**
- * Loads a template file from disk and places it in aResponse as content
- * of type 'text/html' with a charset of 'utf-8'.
- * The argument aPath is a path relative to the root path set using
- * the -setRoot: method.
- * Substitutes values into the template from map using the
- * -substituteFrom:using:into:depth: method.
- * Returns NO if them template could not be read or if any substitution
- * failed. In this case no value is set in the response.
- * If the response is actually text of another type, or you want another
- * characterset used, you can change the content type header in the
- * request after you call this method.
- */
-- (BOOL) produceResponse: (GSMimeDocument*)aResponse
- fromTemplate: (NSString*)aPath
- using: (NSDictionary*)map;
-
-/**
- * Sets the delegate object which processes requests for the receiver.
- */
-- (void) setDelegate: (id)anObject;
-
-/**
- * Sets a flag to determine whether logging of request and session
- * durations is to be performed.
- * If this is YES then the duration of requests and sessions will
- * be logged using the [(WebServerDelegate)-webAlert:for:] method.
- * The request duration is calculated from the point where the first byte
- * of data in the request is read to the point where the response has
- * been completely written.
- * This is useful for debugging and where a full audit trail is required.
- */
-- (void) setDurationLogging: (BOOL)aFlag;
-
-/**
- * Sets the maximum size of an uploaded request body.
- * The default is 4M bytes.
- */
-- (void) setMaxBodySize: (unsigned)max;
-
-/**
- * Sets the maximum size of an incoming request (including all headers,
- * but not the body).
- * The default is 8K bytes.
- */
-- (void) setMaxRequestSize: (unsigned)max;
-
-/**
- * Sets the maximum number of simultaneous sessions with clients.
- * The default is 32.
- * A value of zero permits unlimited connections.
- */
-- (void) setMaxSessions: (unsigned)max;
-
-/**
- * Sets the maximum number of simultaneous sessions with a particular
- * remote host.
- * The default is 8.
- * A value of zero permits unlimited connections.
- */
-- (void) setMaxSessionsPerHost: (unsigned)max;
-
-/**
- * Sets the port and security information for the receiver ... without
- * this the receiver will not listen for incoming requests.
- * If secure is nil then the receiver listens on aPort for HTTP requests.
- * If secure is not nil, the receiver listens for HTTPS instead.
- * If secure is a dictionary containing CertificateFile
,
- * KeyFile
and Password
then the server will
- * use the specified certificate and key files (which it will access
- * using the password).
- * The secure dictionary may also contain other dictionaries
- * keyed on IP addresses, and if the address that an incoming connection
- * arrived on matches the key of a dictionary, that dictionary is used
- * to provide the certificate information, with the top-level values
- * being used as a fallback.
- * This method returns YES on success, NO on failure ... if it returns NO
- * then the receiver will not be capable of handling incoming
- * web requests!
- * Typically a failure will be due to an invalid port being specified ...
- * a port may not already be in use and may not be in the range up to 1024
- * (unless running as the super-user).
- */
-- (BOOL) setPort: (NSString*)aPort secure: (NSDictionary*)secure;
-
-/**
- * Sets the maximum recursion depth allowed for subsititutions into
- * templates. This defaults to 4.
- */
-- (void) setSubstitutionLimit: (unsigned)depth;
-
-/**
- * Set root path for loading template files from.
- * Templates may only be loaded from within this directory.
- */
-- (void) setRoot: (NSString*)aPath;
-
-/**
- * Sets the time after which an idle session should be shut down.
- * Default is 30.0
- */
-- (void) setSessionTimeout: (NSTimeInterval)aDelay;
-
-/**
- * Sets a flag to determine whether verbose logging is to be performed.
- * If this is YES then all incoming requests and their responses will
- * be logged using the [(WebServerDelegate)-webAlert:for:] method.
- * Setting this to YES automatically sets duration logging to YES as well,
- * though you can then call -setDurationLogging: to set it back to NO.
- * This is useful for debugging and where a full audit trail is required.
- */
-- (void) setVerbose: (BOOL)aFlag;
-
-/**
- * Perform substituations replacing the markup in aTemplate with the
- * values supplied by map and appending the results to the result.
- * Substitutions are recursive, and the depth argument is used to
- * specify the current recursion depth (you should normally call this
- * method with a depth of zero at the start of processing a template).
- * Any value inside SGML comment delimiters ('<!--' and '-->') is
- * treated as a possible key in map and the entire comment is replaced
- * by the corresponding map value (unless it is nil). Recursive substitution
- * is done unless the mapped value starts with an SGML comment.
- * While the map is nominally a dictionary, in fact it may be any
- * object which responds to the objectForKey: method by returning
- * an NSString or nil.
- * The method returns YES on success, NO on failure (depth too great).
- * You don't normally need to use this method directly ... call the
- * -produceResponse:fromTemplate:using: method instead.
- */
-- (BOOL) substituteFrom: (NSString*)aTemplate
- using: (NSDictionary*)map
- into: (NSMutableString*)result
- depth: (unsigned)depth;
-
-@end
-
-/**
- * WebServerBundles is an example delegate for the WebServer class.
- * This is intended to act as a convenience for a scheme where the
- * WebServer instance in a program is configured by values obtained
- * from the user defaults system, and incoming requests may be handled
- * by different delegate objects depending on the path information
- * supplied in the request. The WebServerBundles intance is responsible
- * for loading the bundles (based on information in the WebServerBundles
- * dictionary in the user defaults system) and for forwarding requests
- * to the appropriate bundles for processing.
- * If a request comes in which is not an exact match for the path of any
- * handler, the request path is repeatedly shortened by chopping off the
- * last path component until a matching handler is found.
- * The paths in the dictionary must not end with a slash...
- * an empty string will match all requests which do not match a handler
- * with a longer path.
- *
- *
- */
-@interface WebServerBundles : NSObject
-{
- NSMutableDictionary *_handlers;
- WebServer *_http;
-}
-
-/**
- * Handle a notification that the defaults have been updated ... change
- * WebServer configuration if necessary.
- *
- * -
- * WebServerPort must be used to specify the port that the server
- * listens on. See [WebServer-setPort:secure:] for details.
- *
- * -
- * WebServerSecure may be supplied to make the server operate as an
- * HTTPS server rather than an HTTP server.
- * See [WebServer-setPort:secure:] for details.
- *
- * -
- * WebServerBundles is a dictionary keyed on path strings, whose
- * values are dictionaries, each containing per-handler configuration
- * information and the name of the bundle containing the code to handle
- * requests sent to the path. NB. the bundle name listed should
- * omit the
.bundle
extension.
- *
- *
- * Returns YES on success, NO on failure (if the port of the WebServer
- * cannot be set).
- */
-- (BOOL) defaultsUpdate: (NSNotification *)aNotification;
-
-/**
- * Returns the handler to be used for the specified path, or nil if there
- * is no handler available.
- * If the info argument is non-null, it is used to return additional
- * information, either the path actually matched, or an error string.
- */
-- (id) handlerForPath: (NSString*)path info: (NSString**)info;
-
-/**
- * Return dictionary of all handlers by name (path in request which maps
- * to that handler instance).
- */
-- (NSMutableDictionary*) handlers;
-
-/**
- * Return the WebServer instance that the receiver is acting as a
- * delegate for.
- */
-- (WebServer*) http;
-
-/**
- * Initialises the receiver as the delegate of http and configures
- * the WebServer based upon the settings found in the user defaults
- * system by using the -defaultsUpdate: method.
- */
-- (id) initAsDelegateOf: (WebServer*)http;
-
-/**
- * Handles an incoming request by forwarding it to another handler.
- * If a direct mapping is available from the path in the request to
- * an existing handler, that handler is used to process the request.
- * Otherwise, the WebServerBundles dictionary (obtained from the
- * defaults system) is used to map the request path to configuration
- * information listing the bundle containing the handler to be used.
- * The configuration information is a dictionary containing the name
- * of the bundle (keyed on 'Name'), and this is used to locate the
- * bundle in the applications resources.
- * Before a request is passed on to a handler, two extra headers are set
- * in it ... x-http-path-base
and x-http-path-info
- * being the actual path matched for the handler, and the remainder of the
- * path after that base part.
- */
-- (BOOL) processRequest: (GSMimeDocument*)request
- response: (GSMimeDocument*)response
- for: (WebServer*)http;
-
-/**
- * Registers an object as the handler for a particular path.
- * Registering a nil handler destroys any existing handler for the path.
- */
-- (void) registerHandler: (id)handler forPath: (NSString*)path;
-
-/**
- * Just write to stderr using NSLog.
- */
-- (void) webAlert: (NSString*)message for: (WebServer*)http;
-@end
-
-#endif
-
diff --git a/WebServer.m b/WebServer.m
deleted file mode 100644
index 61eff00..0000000
--- a/WebServer.m
+++ /dev/null
@@ -1,1910 +0,0 @@
-/**
- Copyright (C) 2004 Free Software Foundation, Inc.
-
- Written by: Richard Frith-Macdonald
- Date: June 2004
-
- This file is part of the SQLClient Library.
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
-
- $Date$ $Revision$
- */
-
-#include
-#include "WebServer.h"
-#include "SQLClient.h"
-
-@interface WebServerSession : NSObject
-{
- NSString *address;
- NSFileHandle *handle;
- GSMimeParser *parser;
- NSMutableData *buffer;
- unsigned byteCount;
- unsigned identity;
- NSTimeInterval ticked;
- NSTimeInterval requestStart;
- NSTimeInterval sessionStart;
- BOOL processing;
- BOOL shouldEnd;
- BOOL hasReset;
-}
-- (NSString*) address;
-- (NSMutableData*) buffer;
-- (NSFileHandle*) handle;
-- (BOOL) hasReset;
-- (unsigned) identity;
-- (unsigned) moreBytes: (unsigned)count;
-- (GSMimeParser*) parser;
-- (BOOL) processing;
-- (NSTimeInterval) requestDuration: (NSTimeInterval)now;
-- (void) reset;
-- (NSTimeInterval) sessionDuration: (NSTimeInterval)now;
-- (void) setAddress: (NSString*)aString;
-- (void) setBuffer: (NSMutableData*)aBuffer;
-- (void) setHandle: (NSFileHandle*)aHandle;
-- (void) setParser: (GSMimeParser*)aParser;
-- (void) setProcessing: (BOOL)aFlag;
-- (void) setRequestStart: (NSTimeInterval)when;
-- (void) setSessionStart: (NSTimeInterval)when;
-- (void) setShouldEnd: (BOOL)aFlag;
-- (void) setTicked: (NSTimeInterval)when;
-- (BOOL) shouldEnd;
-- (NSTimeInterval) ticked;
-@end
-
-@implementation WebServerSession
-- (NSString*) address
-{
- return address;
-}
-
-- (NSMutableData*) buffer
-{
- return buffer;
-}
-
-- (void) dealloc
-{
- [handle closeFile];
- DESTROY(address);
- DESTROY(buffer);
- DESTROY(handle);
- DESTROY(parser);
- [super dealloc];
-}
-
-- (NSString*) description
-{
- return [NSString stringWithFormat: @"WebServerSession: %08x [%@] ",
- [self identity], [self address]];
-}
-
-- (NSFileHandle*) handle
-{
- return handle;
-}
-
-- (BOOL) hasReset
-{
- return hasReset;
-}
-
-- (unsigned) identity
-{
- return identity;
-}
-
-- (id) init
-{
- static unsigned sessionIdentity = 0;
-
- identity = ++sessionIdentity;
- return self;
-}
-
-- (unsigned) moreBytes: (unsigned)count
-{
- byteCount += count;
- return byteCount;
-}
-
-- (GSMimeParser*) parser
-{
- return parser;
-}
-
-- (BOOL) processing
-{
- return processing;
-}
-
-- (NSTimeInterval) requestDuration: (NSTimeInterval)now
-{
- if (requestStart > 0.0)
- {
- return now - requestStart;
- }
- return 0.0;
-}
-
-- (void) reset
-{
- hasReset = YES;
- [self setRequestStart: 0.0];
- [self setBuffer: [NSMutableData dataWithCapacity: 1024]];
- [self setParser: nil];
- [self setProcessing: NO];
-}
-
-- (NSTimeInterval) sessionDuration: (NSTimeInterval)now
-{
- if (sessionStart > 0.0)
- {
- return now - sessionStart;
- }
- return 0.0;
-}
-
-- (void) setAddress: (NSString*)aString
-{
- ASSIGN(address, aString);
-}
-
-- (void) setBuffer: (NSMutableData*)aBuffer
-{
- ASSIGN(buffer, aBuffer);
-}
-
-- (void) setHandle: (NSFileHandle*)aHandle
-{
- ASSIGN(handle, aHandle);
-}
-
-- (void) setParser: (GSMimeParser*)aParser
-{
- ASSIGN(parser, aParser);
-}
-
-- (void) setProcessing: (BOOL)aFlag
-{
- processing = aFlag;
-}
-
-- (void) setRequestStart: (NSTimeInterval)when
-{
- requestStart = when;
-}
-
-- (void) setSessionStart: (NSTimeInterval)when
-{
- sessionStart = when;
-}
-
-- (void) setShouldEnd: (BOOL)aFlag
-{
- shouldEnd = aFlag;
-}
-
-- (void) setTicked: (NSTimeInterval)when
-{
- ticked = when;
-}
-
-- (BOOL) shouldEnd
-{
- return shouldEnd;
-}
-
-- (NSTimeInterval) ticked
-{
- return ticked;
-}
-@end
-
-@interface WebServer (Private)
-- (void) _alert: (NSString*)fmt, ...;
-- (void) _didConnect: (NSNotification*)notification;
-- (void) _didRead: (NSNotification*)notification;
-- (void) _didWrite: (NSNotification*)notification;
-- (void) _endSession: (WebServerSession*)session;
-- (void) _process: (WebServerSession*)session;
-- (void) _timeout: (NSTimer*)timer;
-@end
-
-@implementation WebServer
-
-- (BOOL) accessRequest: (GSMimeDocument*)request
- response: (GSMimeDocument*)response
-{
- NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
- NSDictionary *conf = [defs dictionaryForKey: @"WebServerAccess"];
- NSString *path = [[request headerNamed: @"x-http-path"] value];
- NSDictionary *access = nil;
- NSString *stored;
- NSString *username;
- NSString *password;
-
- while (access == nil)
- {
- access = [conf objectForKey: path];
- if ([access isKindOfClass: [NSDictionary class]] == NO)
- {
- NSRange r;
-
- r = [path rangeOfString: @"/" options: NSBackwardsSearch];
- if (r.length > 0)
- {
- path = [path substringToIndex: r.location];
- }
- else
- {
- return YES; // No access dictionary - permit access
- }
- }
- }
-
- username = [[request headerNamed: @"x-http-username"] value];
- password = [[request headerNamed: @"x-http-password"] value];
- if ([access objectForKey: @"Users"] != nil)
- {
- NSDictionary *users = [access objectForKey: @"Users"];
-
- stored = [users objectForKey: username];
- }
- else if ([access objectForKey: @"UserDB"] != nil)
- {
- static Class c = nil;
- static BOOL beenHere = NO;
-
- /*
- * We get the SQLClient class from thee runtime, so we don't have to
- * link the library directly ... which means that this class caan be
- * used without it as long as database accesss is not needed.
- */
- if (beenHere == NO)
- {
- beenHere = YES;
- c = NSClassFromString(@"SQLClient");
- if (c == nil)
- {
- [self _alert: @"SQLClient library has not been linked"];
- }
- }
-
- NS_DURING
- {
- NSDictionary *info = [access objectForKey: @"UserDB"];
- NSString *name = [info objectForKey: @"Name"];
- SQLClient *sql;
-
- /*
- * try to re-use an existing client if possible.
- */
- sql = [c existingClient: name];
- if (sql == nil)
- {
- sql = [c alloc];
- sql = [sql initWithConfiguration: nil name: name];
- }
- stored = [sql queryString: @"SELECT ",
- [info objectForKey: @"Password"],
- @" FROM ",
- [info objectForKey: @"Table"],
- @" WHERE ",
- [info objectForKey: @"Username"],
- @" = ",
- [sql quote: username],
- nil];
- }
- NS_HANDLER
- {
- [self _alert: @"Read from database failed - %@", localException];
- stored = nil;
- }
- NS_ENDHANDLER
- }
-
-
- if (username == nil || password == nil || [password isEqual: stored] == NO)
- {
- NSString *realm = [access objectForKey: @"Realm"];
- NSString *auth;
-
- auth = [NSString stringWithFormat: @"Basic realm=\"%@\"", realm];
-
- /*
- * Return status code 401 (Aunauthorised)
- */
- [response setHeader: @"http"
- value: @"HTTP/1.1 401 Unauthorised"
- parameters: nil];
- [response setHeader: @"WWW-authenticate"
- value: auth
- parameters: nil];
-
- [response setContent:
-@"\n"
-@"401 Authorization Required\n"
-@"Authorization Required
\n"
-@"This server could not verify that you "
-@"are authorized to access the resource "
-@"requested. Either you supplied the wrong "
-@"credentials (e.g., bad password), or your "
-@"browser doesn't understand how to supply "
-@"the credentials required.
\n"
-@"\n"
- type: @"text/html"];
-
- return NO;
- }
- else
- {
- return YES; // OK to access
- }
-}
-
-- (void) dealloc
-{
- if (_ticker != nil)
- {
- [_ticker invalidate];
- _ticker = nil;
- }
- [self setPort: nil secure: nil];
- DESTROY(_nc);
- DESTROY(_root);
- DESTROY(_quiet);
- DESTROY(_hosts);
- DESTROY(_perHost);
- if (_sessions != 0)
- {
- NSFreeMapTable(_sessions);
- _sessions = 0;
- }
- [super dealloc];
-}
-
-static unsigned
-unescapeData(const unsigned char* bytes, unsigned length, unsigned char *buf)
-{
- unsigned int to = 0;
- unsigned int from = 0;
-
- while (from < length)
- {
- unsigned char c = bytes[from++];
-
- if (c == '+')
- {
- c = ' ';
- }
- else if (c == '%' && from < length - 1)
- {
- unsigned char tmp;
-
- c = 0;
- tmp = bytes[from++];
- if (tmp <= '9' && tmp >= '0')
- {
- c = tmp - '0';
- }
- else if (tmp <= 'F' && tmp >= 'A')
- {
- c = tmp + 10 - 'A';
- }
- else if (tmp <= 'f' && tmp >= 'a')
- {
- c = tmp + 10 - 'a';
- }
- else
- {
- c = 0;
- }
- c <<= 4;
- tmp = bytes[from++];
- if (tmp <= '9' && tmp >= '0')
- {
- c += tmp - '0';
- }
- else if (tmp <= 'F' && tmp >= 'A')
- {
- c += tmp + 10 - 'A';
- }
- else if (tmp <= 'f' && tmp >= 'a')
- {
- c += tmp + 10 - 'a';
- }
- else
- {
- c = 0;
- }
- }
- buf[to++] = c;
- }
- return to;
-}
-
-- (unsigned) decodeURLEncodedForm: (NSData*)data
- into: (NSMutableDictionary*)dict
-{
- const unsigned char *bytes = (const unsigned char*)[data bytes];
- unsigned length = [data length];
- unsigned pos = 0;
- unsigned fields = 0;
-
- while (pos < length)
- {
- unsigned int keyStart = pos;
- unsigned int keyEnd;
- unsigned int valStart;
- unsigned int valEnd;
- unsigned char *buf;
- unsigned int buflen;
- BOOL escape = NO;
- NSData *d;
- NSString *k;
- NSMutableArray *a;
-
- while (pos < length && bytes[pos] != '&')
- {
- pos++;
- }
- valEnd = pos;
- if (pos < length)
- {
- pos++; // Step past '&'
- }
-
- keyEnd = keyStart;
- while (keyEnd < pos && bytes[keyEnd] != '=')
- {
- if (bytes[keyEnd] == '%' || bytes[keyEnd] == '+')
- {
- escape = YES;
- }
- keyEnd++;
- }
-
- if (escape == YES)
- {
- buf = NSZoneMalloc(NSDefaultMallocZone(), keyEnd - keyStart);
- buflen = unescapeData(&bytes[keyStart], keyEnd - keyStart, buf);
- d = [[NSData alloc] initWithBytesNoCopy: buf
- length: buflen
- freeWhenDone: YES];
- }
- else
- {
- d = [[NSData alloc] initWithBytesNoCopy: (void*)&bytes[keyStart]
- length: keyEnd - keyStart
- freeWhenDone: NO];
- }
- k = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding];
- if (k == nil)
- {
- [NSException raise: NSInvalidArgumentException
- format: @"Bad UTF-8 form data (key of field %d)", fields];
- }
- RELEASE(d);
-
- valStart = keyEnd;
- if (valStart < pos)
- {
- valStart++; // Step past '='
- }
- if (valStart < valEnd)
- {
- buf = NSZoneMalloc(NSDefaultMallocZone(), valEnd - valStart);
- buflen = unescapeData(&bytes[valStart], valEnd - valStart, buf);
- d = [[NSData alloc] initWithBytesNoCopy: buf
- length: buflen
- freeWhenDone: YES];
- }
- else
- {
- d = [NSData new];
- }
- a = [dict objectForKey: k];
- if (a == nil)
- {
- a = [[NSMutableArray alloc] initWithCapacity: 1];
- [dict setObject: a forKey: k];
- RELEASE(a);
- }
- [a addObject: d];
- RELEASE(d);
- RELEASE(k);
- fields++;
- }
- return fields;
-}
-
-static NSMutableData*
-escapeData(const unsigned char* bytes, unsigned length, NSMutableData *d)
-{
- unsigned char *dst;
- unsigned int spos = 0;
- unsigned int dpos = [d length];
-
- [d setLength: dpos + 3 * length];
- dst = (unsigned char*)[d mutableBytes];
- while (spos < length)
- {
- unsigned char c = bytes[spos++];
- unsigned int hi;
- unsigned int lo;
-
- switch (c)
- {
- case ',':
- case ';':
- case '"':
- case '\'':
- case '&':
- case '=':
- case '(':
- case ')':
- case '<':
- case '>':
- case '?':
- case '#':
- case '{':
- case '}':
- case '%':
- case ' ':
- case '+':
- dst[dpos++] = '%';
- hi = (c & 0xf0) >> 4;
- dst[dpos++] = (hi > 9) ? 'A' + hi - 10 : '0' + hi;
- lo = (c & 0x0f);
- dst[dpos++] = (lo > 9) ? 'A' + lo - 10 : '0' + lo;
- break;
-
- default:
- if (c < ' ' || c > 127)
- {
- dst[dpos++] = '%';
- hi = (c & 0xf0) >> 4;
- dst[dpos++] = (hi > 9) ? 'A' + hi - 10 : '0' + hi;
- lo = (c & 0x0f);
- dst[dpos++] = (lo > 9) ? 'A' + lo - 10 : '0' + lo;
- }
- else
- {
- dst[dpos++] = c;
- }
- break;
- }
- }
- [d setLength: dpos];
- return d;
-}
-
-- (unsigned) encodeURLEncodedForm: (NSDictionary*)dict
- into: (NSMutableData*)data
-{
- CREATE_AUTORELEASE_POOL(arp);
- NSEnumerator *keyEnumerator;
- id key;
- unsigned valueCount = 0;
- NSMutableData *md = [NSMutableData dataWithCapacity: 100];
-
- keyEnumerator = [dict keyEnumerator];
- while ((key = [keyEnumerator nextObject]) != nil)
- {
- id values = [dict objectForKey: key];
- NSData *keyData;
- NSEnumerator *valueEnumerator;
- id value;
-
- if ([key isKindOfClass: [NSData class]] == YES)
- {
- keyData = key;
- }
- else
- {
- key = [key description];
- keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
- }
- [md setLength: 0];
- escapeData([keyData bytes], [keyData length], md);
- keyData = md;
-
- if ([values isKindOfClass: [NSArray class]] == NO)
- {
- values = [NSArray arrayWithObject: values];
- }
-
- valueEnumerator = [values objectEnumerator];
-
- while ((value = [valueEnumerator nextObject]) != nil)
- {
- NSData *valueData;
-
- if ([data length] > 0)
- {
- [data appendBytes: "&" length: 1];
- }
- [data appendData: keyData];
- [data appendBytes: "=" length: 1];
- if ([value isKindOfClass: [NSData class]] == YES)
- {
- valueData = value;
- }
- else
- {
- value = [value description];
- valueData = [value dataUsingEncoding: NSUTF8StringEncoding];
- }
- escapeData([valueData bytes], [valueData length], data);
- valueCount++;
- }
- }
- RELEASE(arp);
- return valueCount;
-}
-
-- (NSString*) description
-{
- return [NSString stringWithFormat: @"%@ on %@(%@), %u of %u sessions active,"
- @" %u ended, %u requests, listening: %@",
- [super description], _port, ([self isSecure] ? @"https" : @"http"),
- NSCountMapTable(_sessions),
- _maxSessions, _handled, _requests, _accepting == YES ? @"yes" : @"no"];
-}
-
-- (id) init
-{
- NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
-
- _hosts = RETAIN([defs arrayForKey: @"WebServerHosts"]);
- _quiet = RETAIN([defs arrayForKey: @"WebServerQuiet"]);
- _nc = RETAIN([NSNotificationCenter defaultCenter]);
- _sessionTimeout = 30.0;
- _maxPerHost = 8;
- _maxSessions = 32;
- _maxBodySize = 8*1024;
- _maxRequestSize = 4*1024*1024;
- _substitutionLimit = 4;
- _sessions = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
- NSObjectMapValueCallBacks, 0);
- _perHost = [NSCountedSet new];
- _ticker = [NSTimer scheduledTimerWithTimeInterval: 0.8
- target: self
- selector: @selector(_timeout:)
- userInfo: 0
- repeats: YES];
- return self;
-}
-
-- (BOOL) isSecure
-{
- if (_sslConfig == nil)
- {
- return NO;
- }
- return YES;
-}
-
-- (BOOL) produceResponse: (GSMimeDocument*)aResponse
- fromStaticPage: (NSString*)aPath
- using: (NSDictionary*)map
-{
- CREATE_AUTORELEASE_POOL(arp);
- NSString *path = (_root == nil) ? (id)@"" : (id)_root;
- NSString *ext = [aPath pathExtension];
- NSString *type;
- NSString *str;
- id data;
- NSFileManager *mgr;
- BOOL string = NO;
- BOOL result = YES;
-
- if (map == nil)
- {
- static NSDictionary *defaultMap = nil;
-
- if (defaultMap == nil)
- {
- defaultMap = [[NSDictionary alloc] initWithObjectsAndKeys:
- @"image/gif", @"gif",
- @"image/png", @"png",
- @"image/jpeg", @"jpeg",
- @"text/html", @"html",
- @"text/plain", @"txt",
- @"text/xml", @"xml",
- nil];
- }
- map = defaultMap;
- }
-
- type = [map objectForKey: ext];
- if (type == nil)
- {
- type = [map objectForKey: [ext lowercaseString]];
- }
- if (type == nil)
- {
- type = @"application/octet-stream";
- }
- string = [type hasPrefix: @"text/"];
-
- path = [path stringByAppendingString: @"/"];
- str = [path stringByStandardizingPath];
- path = [path stringByAppendingPathComponent: aPath];
- path = [path stringByStandardizingPath];
- mgr = [NSFileManager defaultManager];
- if ([path hasPrefix: str] == NO)
- {
- [self _alert: @"Illegal static page '%@' ('%@')", aPath, path];
- result = NO;
- }
- else if ([mgr isReadableFileAtPath: path] == NO)
- {
- [self _alert: @"Can't read static page '%@' ('%@')", aPath, path];
- result = NO;
- }
- else if (string == YES
- && (data = [NSString stringWithContentsOfFile: path]) == nil)
- {
- [self _alert: @"Failed to load string '%@' ('%@')", aPath, path];
- result = NO;
- }
- else if (string == NO
- && (data = [NSData dataWithContentsOfFile: path]) == nil)
- {
- [self _alert: @"Failed to load data '%@' ('%@')", aPath, path];
- result = NO;
- }
- else
- {
- [aResponse setContent: data type: type name: nil];
- }
- DESTROY(arp);
- return result;
-}
-
-- (BOOL) produceResponse: (GSMimeDocument*)aResponse
- fromTemplate: (NSString*)aPath
- using: (NSDictionary*)map
-{
- CREATE_AUTORELEASE_POOL(arp);
- NSString *path = (_root == nil) ? (id)@"" : (id)_root;
- NSString *str;
- NSFileManager *mgr;
- BOOL result;
-
- path = [path stringByAppendingString: @"/"];
- str = [path stringByStandardizingPath];
- path = [path stringByAppendingPathComponent: aPath];
- path = [path stringByStandardizingPath];
- mgr = [NSFileManager defaultManager];
- if ([path hasPrefix: str] == NO)
- {
- [self _alert: @"Illegal template '%@' ('%@')", aPath, path];
- result = NO;
- }
- else if ([mgr isReadableFileAtPath: path] == NO)
- {
- [self _alert: @"Can't read template '%@' ('%@')", aPath, path];
- result = NO;
- }
- else if ((str = [NSString stringWithContentsOfFile: path]) == nil)
- {
- [self _alert: @"Failed to load template '%@' ('%@')", aPath, path];
- result = NO;
- }
- else
- {
- NSMutableString *m = [NSMutableString stringWithCapacity: [str length]];
-
- result = [self substituteFrom: str
- using: map
- into: m
- depth: 0];
- if (result == YES)
- {
- [aResponse setContent: m type: @"text/html" name: nil];
- [[aResponse headerNamed: @"content-type"] setParameter: @"utf-8"
- forKey: @"charset"];
- }
- }
- DESTROY(arp);
- return result;
-}
-
-- (NSMutableDictionary*) parameters: (GSMimeDocument*)request
-{
- NSMutableDictionary *params;
- NSString *str = [[request headerNamed: @"x-http-query"] value];
- NSData *data;
-
- params = [NSMutableDictionary dictionaryWithCapacity: 32];
- if ([str length] > 0)
- {
- data = [str dataUsingEncoding: NSASCIIStringEncoding];
- [self decodeURLEncodedForm: data into: params];
- }
-
- str = [[request headerNamed: @"content-type"] value];
- if ([str isEqualToString: @"application/x-www-form-urlencoded"] == YES)
- {
- data = [request convertToData];
- [self decodeURLEncodedForm: data into: params];
- }
- else if ([str isEqualToString: @"multipart/form-data"] == YES)
- {
- NSArray *contents = [request content];
- unsigned count = [contents count];
- unsigned i;
-
- for (i = 0; i < count; i++)
- {
- GSMimeDocument *doc = [contents objectAtIndex: i];
- GSMimeHeader *hdr = [doc headerNamed: @"content-type"];
- NSString *k = [hdr parameterForKey: @"name"];
-
- if (k == nil)
- {
- hdr = [doc headerNamed: @"content-disposition"];
- k = [hdr parameterForKey: @"name"];
- }
- if (k != nil)
- {
- NSMutableArray *a;
-
- a = [params objectForKey: k];
- if (a == nil)
- {
- a = [[NSMutableArray alloc] initWithCapacity: 1];
- [params setObject: a forKey: k];
- RELEASE(a);
- }
- [a addObject: [doc convertToData]];
- }
- }
- }
-
- return params;
-}
-
-- (NSData*) parameter: (NSString*)name
- at: (unsigned)index
- from: (NSDictionary*)params
-{
- NSArray *a = [params objectForKey: name];
-
- if (a == nil)
- {
- NSEnumerator *e = [params keyEnumerator];
- NSString *k;
-
- while ((k = [e nextObject]) != nil)
- {
- if ([k caseInsensitiveCompare: name] == NSOrderedSame)
- {
- a = [params objectForKey: k];
- break;
- }
- }
- }
- if (index >= [a count])
- {
- return nil;
- }
- return [a objectAtIndex: index];
-}
-
-- (NSData*) parameter: (NSString*)name from: (NSDictionary*)params
-{
- return [self parameter: name at: 0 from: params];
-}
-
-- (NSString*) parameterString: (NSString*)name
- at: (unsigned)index
- from: (NSDictionary*)params
-{
- return [self parameterString: name at: index from: params charset: nil];
-}
-
-- (NSString*) parameterString: (NSString*)name
- at: (unsigned)index
- from: (NSDictionary*)params
- charset: (NSString*)charset
-{
- NSData *d = [self parameter: name at: index from: params];
- NSString *s = nil;
-
- if (d != nil)
- {
- s = [NSString alloc];
- if (charset == nil || [charset length] == 0)
- {
- s = [s initWithData: d encoding: NSUTF8StringEncoding];
- }
- else
- {
- NSStringEncoding enc;
-
- enc = [GSMimeDocument encodingFromCharset: charset];
- s = [s initWithData: d encoding: enc];
- }
- }
- return AUTORELEASE(s);
-}
-
-- (NSString*) parameterString: (NSString*)name from: (NSDictionary*)params
-{
- return [self parameterString: name at: 0 from: params charset: nil];
-}
-
-- (NSString*) parameterString: (NSString*)name
- from: (NSDictionary*)params
- charset: (NSString*)charset
-{
- return [self parameterString: name at: 0 from: params charset: charset];
-}
-
-- (void) setDelegate: (id)anObject
-{
- _delegate = anObject;
-}
-
-- (void) setDurationLogging: (BOOL)aFlag
-{
- _durations = aFlag;
-}
-
-- (void) setMaxBodySize: (unsigned)max
-{
- _maxBodySize = max;
-}
-
-- (void) setMaxRequestSize: (unsigned)max
-{
- _maxRequestSize = max;
-}
-
-- (void) setMaxSessions: (unsigned)max
-{
- _maxSessions = max;
-}
-
-- (void) setMaxSessionsPerHost: (unsigned)max
-{
- _maxPerHost = max;
-}
-
-- (BOOL) setPort: (NSString*)aPort secure: (NSDictionary*)secure
-{
- BOOL ok = YES;
- BOOL update = NO;
-
- if (aPort == nil || [aPort isEqual: _port] == NO)
- {
- update = YES;
- }
- if ((secure == nil && _sslConfig != nil)
- || (secure != nil && [secure isEqual: _sslConfig] == NO))
- {
- update = YES;
- }
-
- if (update == YES)
- {
- ASSIGN(_sslConfig, secure);
- if (_listener != nil)
- {
- [_nc removeObserver: self
- name: NSFileHandleConnectionAcceptedNotification
- object: _listener];
- DESTROY(_listener);
- }
- _accepting = NO; // No longer listening for connections.
- DESTROY(_port);
- if (aPort != nil)
- {
- _port = [aPort copy];
- if (_sslConfig != nil)
- {
- _listener = [[NSFileHandle sslClass]
- fileHandleAsServerAtAddress: nil
- service: _port
- protocol: @"tcp"];
- }
- else
- {
- _listener = [NSFileHandle fileHandleAsServerAtAddress: nil
- service: _port
- protocol: @"tcp"];
- }
-
- if (_listener == nil)
- {
- [self _alert: @"Failed to listen on port %@", _port];
- DESTROY(_port);
- ok = NO;
- }
- else
- {
- RETAIN(_listener);
- [_nc addObserver: self
- selector: @selector(_didConnect:)
- name: NSFileHandleConnectionAcceptedNotification
- object: _listener];
- if (_accepting == NO && (_maxSessions <= 0
- || NSCountMapTable(_sessions) < _maxSessions))
- {
- [_listener acceptConnectionInBackgroundAndNotify];
- _accepting = YES;
- }
- }
- }
- }
- return ok;
-}
-
-- (void) setRoot: (NSString*)aPath
-{
- ASSIGN(_root, aPath);
-}
-
-- (void) setSessionTimeout: (NSTimeInterval)aDelay
-{
- _sessionTimeout = aDelay;
-}
-
-- (void) setSubstitutionLimit: (unsigned)depth
-{
- _substitutionLimit = depth;
-}
-
-- (void) setVerbose: (BOOL)aFlag
-{
- _verbose = aFlag;
- if (aFlag == YES)
- {
- [self setDurationLogging: YES];
- }
-}
-
-- (BOOL) substituteFrom: (NSString*)aTemplate
- using: (NSDictionary*)map
- into: (NSMutableString*)result
- depth: (unsigned)depth
-{
- unsigned length;
- unsigned pos = 0;
- NSRange r = NSMakeRange(pos, length);
-
- if (depth > _substitutionLimit)
- {
- [self _alert: @"Substitution exceeded limit (%u)", _substitutionLimit];
- return NO;
- }
-
- length = [aTemplate length];
- r = NSMakeRange(pos, length);
- r = [aTemplate rangeOfString: @""
- options: NSLiteralSearch
- range: r];
- if (r.length > 0)
- {
- unsigned end = NSMaxRange(r);
- NSString *subFrom;
- NSString *subTo;
-
- r = NSMakeRange(start + 4, r.location - start - 4);
- subFrom = [aTemplate substringWithRange: r];
- subTo = [map objectForKey: subFrom];
- if (subTo == nil)
- {
- [result appendString: @"