26 Commits
2.3.1 ... 2.4

Author SHA1 Message Date
Pierre-Olivier Latour
f01307b2a7 Bumped version 2014-04-27 19:25:51 -07:00
Pierre-Olivier Latour
1f5e650423 Fix 2014-04-27 19:20:38 -07:00
Pierre-Olivier Latour
d404112a88 #46 Added "error" argument to -startWithOptions: 2014-04-27 19:19:55 -07:00
Pierre-Olivier Latour
dd3f539f74 #47 Ensure listening socket is closed when -stop returns 2014-04-27 19:05:34 -07:00
Pierre-Olivier Latour
0c51d09b69 Update README.md 2014-04-27 18:46:45 -07:00
Pierre-Olivier Latour
0c53c52dd4 Fix 2014-04-27 12:50:36 -07:00
Pierre-Olivier Latour
a687b52563 #39 Added support for "multipart/mixed" parts inside "multipart/form-data" 2014-04-26 19:31:11 -07:00
Pierre-Olivier Latour
c8c34aa61f Fix 2014-04-26 19:30:18 -07:00
Pierre-Olivier Latour
ed709d1476 Added HTMLFileUpload unit tests 2014-04-25 07:17:34 -07:00
Pierre-Olivier Latour
3dc7cb0ec4 Added HTMLForm unit tests 2014-04-25 07:14:40 -07:00
Pierre-Olivier Latour
142f007e58 Added "htmlFileUpload" mode 2014-04-24 19:18:13 -07:00
Pierre-Olivier Latour
c8d2b225ba Modified GCDWebServerMultiPart to allow duplicate control names 2014-04-24 19:01:46 -07:00
Pierre-Olivier Latour
f7d6da55cd Added GCDWebServerMIMEStreamParser class 2014-04-24 18:45:34 -07:00
Pierre-Olivier Latour
5a0c274807 Fix 2014-04-23 10:40:43 -07:00
Pierre-Olivier Latour
591da12aa3 Exclude GCDWebServerPrivate.h from Podspec 2014-04-23 10:39:42 -07:00
Pierre-Olivier Latour
72475429e4 Bumped version 2014-04-23 10:33:50 -07:00
Pierre-Olivier Latour
01da5969e4 Update README.md 2014-04-22 22:47:07 -07:00
Pierre-Olivier Latour
46890a0642 Merge pull request #43 from iosphere/pullrequest/unusedVar
Fixes a static analyzer warning about unused variables for disabled logging
2014-04-21 21:15:35 -03:00
Pierre-Olivier Latour
143ca5b99f Fix 2014-04-20 10:34:45 -03:00
Pierre-Olivier Latour
519866bc03 Fix 2014-04-19 23:05:13 -03:00
Pierre-Olivier Latour
a93cac5ea6 Fix 2014-04-19 22:56:45 -03:00
Pierre-Olivier Latour
43b578677f Fix 2014-04-19 22:19:47 -03:00
Pierre-Olivier Latour
10cbe27e50 Fixed open() calls 2014-04-19 22:17:17 -03:00
Pierre-Olivier Latour
b1169ce7d1 Bumped version 2014-04-19 17:48:53 -03:00
Felix Lamouroux
3e5fe3f956 Fixes logging for non-arc builds 2014-04-19 14:09:55 +02:00
Felix Lamouroux
a5208bd60f Fixes a static analyzer warning about unused variables when logging is disabled 2014-04-18 21:26:18 +02:00
31 changed files with 563 additions and 312 deletions

View File

@@ -71,8 +71,8 @@
/**
* The GCDWebDAVServer subclass of GCDWebServer implements a class 1 compliant
* WebDAV server. It is also partially class 2 compliant (but only when the
* client is the OS X WebDAV implementation), so it can work with the OS X Finder.
* WebDAV server. It is also partially class 2 compliant but only when the
* client is the OS X WebDAV implementation (so it can work with the OS X Finder).
*
* See the README.md file for more information about the features of GCDWebDAVServer.
*/
@@ -89,16 +89,15 @@
@property(nonatomic, assign) id<GCDWebDAVServerDelegate> delegate;
/**
* Restricts which files should be listed and allowed to be uploaded, downloaded,
* moved, copied or deleted depending on their extensions.
* Sets which files are allowed to be operated on depending on their extension.
*
* The default value is nil i.e. all file extensions are allowed.
*/
@property(nonatomic, copy) NSArray* allowedFileExtensions;
/**
* Sets if files and directories whose name start with a period should be
* listed and allowed to be uploaded, downloaded, moved, copied or deleted.
* Sets if files and directories whose name start with a period are allowed to
* be operated on.
*
* The default value is NO.
*/
@@ -119,7 +118,7 @@
@interface GCDWebDAVServer (Subclassing)
/**
* This method is called to check if a file is allowed to be uploaded.
* This method is called to check if a file upload is allowed to complete.
* The uploaded file is available for inspection at "tempPath".
*
* The default implementation returns YES.

View File

@@ -7,7 +7,7 @@
Pod::Spec.new do |s|
s.name = 'GCDWebServer'
s.version = '2.3'
s.version = '2.4'
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.homepage = 'https://github.com/swisspol/GCDWebServer'
@@ -22,6 +22,7 @@ Pod::Spec.new do |s|
s.subspec 'Core' do |cs|
cs.source_files = 'GCDWebServer/**/*.{h,m}'
cs.private_header_files = "GCDWebServer/Core/GCDWebServerPrivate.h"
cs.requires_arc = true
cs.ios.library = 'z'
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'

View File

@@ -59,7 +59,7 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
/**
* The GCDWebServerProcessBlock is called after the HTTP request has been fully
* received (i.e. the entire HTTP body has been read). THe block is passed the
* received (i.e. the entire HTTP body has been read). The block is passed the
* GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock.
*
* The block must return a GCDWebServerResponse or nil on error, which will
@@ -72,14 +72,14 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
/**
* The port used by the GCDWebServer (NSNumber / NSUInteger).
*
* Default value is 0 i.e. let the OS pick a random port.
* The default value is 0 i.e. let the OS pick a random port.
*/
extern NSString* const GCDWebServerOption_Port;
/**
* The Bonjour name used by the GCDWebServer (NSString).
*
* Default value is an empty string i.e. use the computer / device name.
* The default value is an empty string i.e. use the computer / device name.
*/
extern NSString* const GCDWebServerOption_BonjourName;
@@ -87,14 +87,14 @@ extern NSString* const GCDWebServerOption_BonjourName;
* The maximum number of incoming HTTP requests that can be queued waiting to
* be handled before new ones are dropped (NSNumber / NSUInteger).
*
* Default value is 16.
* The default value is 16.
*/
extern NSString* const GCDWebServerOption_MaxPendingConnections;
/**
* The value for "Server" HTTP header used by the GCDWebServer (NSString).
*
* Default value is the GCDWebServer class name.
* The default value is the GCDWebServer class name.
*/
extern NSString* const GCDWebServerOption_ServerName;
@@ -102,14 +102,14 @@ extern NSString* const GCDWebServerOption_ServerName;
* The authentication method used by the GCDWebServer
* (one of "GCDWebServerAuthenticationMethod_...").
*
* Default value is nil i.e. authentication disabled.
* The default value is nil i.e. authentication is disabled.
*/
extern NSString* const GCDWebServerOption_AuthenticationMethod;
/**
* The authentication realm used by the GCDWebServer (NSString).
*
* Default value is the same as GCDWebServerOption_ServerName.
* The default value is the same as the GCDWebServerOption_ServerName option.
*/
extern NSString* const GCDWebServerOption_AuthenticationRealm;
@@ -117,7 +117,7 @@ extern NSString* const GCDWebServerOption_AuthenticationRealm;
* The authentication accounts used by the GCDWebServer
* (NSDictionary of username / password pairs).
*
* Default value is nil i.e. no accounts.
* The default value is nil i.e. no accounts.
*/
extern NSString* const GCDWebServerOption_AuthenticationAccounts;
@@ -125,7 +125,7 @@ extern NSString* const GCDWebServerOption_AuthenticationAccounts;
* The class used by the GCDWebServer when instantiating GCDWebServerConnection
* (subclass of GCDWebServerConnection).
*
* Default value is GCDWebServerConnection class.
* The default value is the GCDWebServerConnection class.
*/
extern NSString* const GCDWebServerOption_ConnectionClass;
@@ -133,7 +133,7 @@ extern NSString* const GCDWebServerOption_ConnectionClass;
* Allow the GCDWebServer to pretend "HEAD" requests are actually "GET" ones
* and automatically discard the HTTP body of the response (NSNumber / BOOL).
*
* Default value is YES.
* The default value is YES.
*/
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
@@ -142,7 +142,7 @@ extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
* coalesce calls to -webServerDidConnect: and -webServerDidDisconnect:
* (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0.
*
* Default value is 1.0 second.
* The default value is 1.0 second.
*/
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;
@@ -156,7 +156,7 @@ extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;
*
* See the README.md file for more information about this option.
*
* Default value is YES.
* The default value is YES.
*
* @warning The running property will be NO while the GCDWebServer is suspended.
*/
@@ -167,7 +167,8 @@ extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground;
/**
* HTTP Basic Authentication scheme (see https://tools.ietf.org/html/rfc2617).
*
* @warning Use of this method is not recommended as the passwords are sent in clear.
* @warning Use of this authentication scheme is not recommended as the
* passwords are sent in clear.
*/
extern NSString* const GCDWebServerAuthenticationMethod_Basic;
@@ -187,7 +188,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
@optional
/**
* This method is called after the server has succesfully started.
* This method is called after the server has successfully started.
*/
- (void)webServerDidStart:(GCDWebServer*)server;
@@ -199,9 +200,11 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* This method is called when the first GCDWebServerConnection is opened by the
* server to serve a series of HTTP requests. A series is ongoing as long as
* new HTTP requests keep coming (and new GCDWebServerConnection instances keep
* being opened), before the last HTTP request has been responded to (and the
* server to serve a series of HTTP requests.
*
* A series of HTTP requests is considered ongoing as long as new HTTP requests
* keep coming (and new GCDWebServerConnection instances keep being opened),
* until before the last HTTP request has been responded to (and the
* corresponding last GCDWebServerConnection closed).
*/
- (void)webServerDidConnect:(GCDWebServer*)server;
@@ -226,8 +229,9 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
@end
/**
* The GCDWebServer class manages the socket that listens for HTTP requests and
* the list of handlers used to respond to them.
* The GCDWebServer class listens for incoming HTTP requests on a given port,
* then passes each one to a "handler" capable of generating an HTTP response
* for it, which is then sent back to the client.
*
* See the README.md file for more information about the architecture of GCDWebServer.
*/
@@ -239,7 +243,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
@property(nonatomic, assign) id<GCDWebServerDelegate> delegate;
/**
* Indicates if the server is currently running.
* Returns YES if the server is currently running.
*/
@property(nonatomic, readonly, getter=isRunning) BOOL running;
@@ -251,7 +255,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
@property(nonatomic, readonly) NSUInteger port;
/**
* Returns the Bonjour name in used by the server.
* Returns the Bonjour name used by the server.
*
* @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds.
@@ -265,51 +269,35 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* Adds a handler to the server to handle incoming HTTP requests.
* Handlers are called in a LIFO queue, so the latest added handler overrides
* any previously added ones.
*
* @warning Addling handlers while the GCDWebServer is running is not allowed.
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
*
* @warning Addling handlers while the server is running is not allowed.
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
/**
* Removes all handlers previously added to the server.
*
* @warning Removing handlers while the GCDWebServer is running is not allowed.
* @warning Removing handlers while the server is running is not allowed.
*/
- (void)removeAllHandlers;
/**
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
* using the computer / device name for as the Bonjour name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)start;
/**
* Starts the server on a given port and with a specific Bonjour name.
* Pass a nil Bonjour name to disable Bonjour entirely or an empty string to
* use the computer / device name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name;
/**
* Starts the server with explicit options. This method is the designated way
* to start the server.
*
* Returns NO if the server failed to start.
* Returns NO if the server failed to start and sets "error" argument if not NULL.
*/
- (BOOL)startWithOptions:(NSDictionary*)options;
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error;
/**
* Stops the server and prevents it to accepts new HTTP requests.
*
* @warning Stopping the server does not abort GCDWebServerConnection instances
* handling already received HTTP requests. These connections will continue to
* execute until the corresponding requests and responses are completed.
* currently handling already received HTTP requests. These connections will
* continue to execute normally until completion.
*/
- (void)stop;
@@ -332,6 +320,23 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*/
@property(nonatomic, readonly) NSURL* bonjourServerURL;
/**
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
* using the computer / device name for as the Bonjour name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)start;
/**
* Starts the server on a given port and with a specific Bonjour name.
* Pass a nil Bonjour name to disable Bonjour entirely or an empty string to
* use the computer / device name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name;
#if !TARGET_OS_IPHONE
/**
@@ -350,11 +355,11 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* is received i.e. Ctrl-C. This method is intended to be used by command line
* tools.
*
* Returns NO if the server failed to start.
* Returns NO if the server failed to start and sets "error" argument if not NULL.
*
* @warning This method must be used from the main thread only.
*/
- (BOOL)runWithOptions:(NSDictionary*)options;
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error;
#endif

View File

@@ -43,43 +43,6 @@
#define kDefaultPort 8080
#endif
@interface GCDWebServer () {
@private
id<GCDWebServerDelegate> __unsafe_unretained _delegate;
dispatch_queue_t _syncQueue;
NSMutableArray* _handlers;
NSInteger _activeConnections; // Accessed only with _syncQueue
BOOL _connected;
CFRunLoopTimerRef _connectedTimer;
NSDictionary* _options;
NSString* _serverName;
NSString* _authenticationRealm;
NSMutableDictionary* _authenticationBasicAccounts;
NSMutableDictionary* _authenticationDigestAccounts;
Class _connectionClass;
BOOL _mapHEADToGET;
CFTimeInterval _disconnectDelay;
NSUInteger _port;
dispatch_source_t _source;
CFNetServiceRef _service;
#if TARGET_OS_IPHONE
BOOL _suspendInBackground;
UIBackgroundTaskIdentifier _backgroundTask;
#endif
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
BOOL _recording;
#endif
}
@end
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerProcessBlock _processBlock;
}
@end
NSString* const GCDWebServerOption_Port = @"Port";
NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
@@ -132,6 +95,13 @@ static void _SignalHandler(int signal) {
#endif
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerProcessBlock _processBlock;
}
@end
@implementation GCDWebServerHandler
@synthesize matchBlock=_matchBlock, processBlock=_processBlock;
@@ -153,6 +123,37 @@ static void _SignalHandler(int signal) {
@end
@interface GCDWebServer () {
@private
id<GCDWebServerDelegate> __unsafe_unretained _delegate;
dispatch_queue_t _syncQueue;
dispatch_semaphore_t _sourceSemaphore;
NSMutableArray* _handlers;
NSInteger _activeConnections; // Accessed only with _syncQueue
BOOL _connected;
CFRunLoopTimerRef _connectedTimer;
NSDictionary* _options;
NSString* _serverName;
NSString* _authenticationRealm;
NSMutableDictionary* _authenticationBasicAccounts;
NSMutableDictionary* _authenticationDigestAccounts;
Class _connectionClass;
BOOL _mapHEADToGET;
CFTimeInterval _disconnectDelay;
NSUInteger _port;
dispatch_source_t _source;
CFNetServiceRef _service;
#if TARGET_OS_IPHONE
BOOL _suspendInBackground;
UIBackgroundTaskIdentifier _backgroundTask;
#endif
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
BOOL _recording;
#endif
}
@end
@implementation GCDWebServer
@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
@@ -183,6 +184,7 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
- (instancetype)init {
if ((self = [super init])) {
_syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL);
_sourceSemaphore = dispatch_semaphore_create(0);
_handlers = [[NSMutableArray alloc] init];
CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
_connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context);
@@ -206,6 +208,7 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
CFRunLoopTimerInvalidate(_connectedTimer);
CFRelease(_connectedTimer);
ARC_RELEASE(_handlers);
ARC_DISPATCH_RELEASE(_sourceSemaphore);
ARC_DISPATCH_RELEASE(_syncQueue);
ARC_DEALLOC(super);
@@ -363,7 +366,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
#endif
return ARC_AUTORELEASE([[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]);
}
- (BOOL)_start {
- (BOOL)_start:(NSError**)error {
DCHECK(_source == NULL);
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
NSString* name = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
@@ -413,6 +417,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
LOG_DEBUG(@"Did close listening socket %i", listeningSocket);
}
}
dispatch_semaphore_signal(_sourceSemaphore);
});
dispatch_source_set_event_handler(_source, ^{
@@ -468,8 +473,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
CFNetServiceScheduleWithRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFStreamError error = {0};
CFNetServiceRegisterWithOptions(_service, 0, &error);
CFStreamError streamError = {0};
CFNetServiceRegisterWithOptions(_service, 0, &streamError);
} else {
LOG_ERROR(@"Failed creating CFNetService");
}
@@ -483,15 +488,24 @@ static inline NSString* _EncodeBase64(NSString* string) {
});
}
} else {
LOG_ERROR(@"Failed listening on socket: %s (%i)", strerror(errno), errno);
LOG_ERROR(@"Failed starting listening socket: %s (%i)", strerror(errno), errno);
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
close(listeningSocket);
}
} else {
LOG_ERROR(@"Failed binding socket: %s (%i)", strerror(errno), errno);
LOG_ERROR(@"Failed binding listening socket: %s (%i)", strerror(errno), errno);
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
close(listeningSocket);
}
} else {
LOG_ERROR(@"Failed creating socket: %s (%i)", strerror(errno), errno);
LOG_ERROR(@"Failed creating listening socket: %s (%i)", strerror(errno), errno);
if (error) {
*error = GCDWebServerMakePosixError(errno);
}
}
return (_source ? YES : NO);
}
@@ -506,7 +520,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
_service = NULL;
}
dispatch_source_cancel(_source); // This will close the socket
dispatch_source_cancel(_source);
dispatch_semaphore_wait(_sourceSemaphore, DISPATCH_TIME_FOREVER); // Wait until the cancellation handler has been called which guarantees the listening socket is closed
ARC_DISPATCH_RELEASE(_source);
_source = NULL;
_port = 0;
@@ -528,17 +543,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
}
}
- (BOOL)start {
return [self startWithPort:kDefaultPort bonjourName:@""];
}
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
[options setValue:name forKey:GCDWebServerOption_BonjourName];
return [self startWithOptions:options];
}
#if TARGET_OS_IPHONE
- (void)_didEnterBackground:(NSNotification*)notification {
@@ -553,20 +557,20 @@ static inline NSString* _EncodeBase64(NSString* string) {
DCHECK([NSThread isMainThread]);
LOG_DEBUG(@"Will enter foreground");
if (!_source) {
[self _start]; // TODO: There's probably nothing we can do on failure
[self _start:NULL]; // TODO: There's probably nothing we can do on failure
}
}
#endif
- (BOOL)startWithOptions:(NSDictionary*)options {
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
if (_options == nil) {
_options = [options copy];
#if TARGET_OS_IPHONE
_suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start])
if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
#else
if (![self _start])
if (![self _start:error])
#endif
{
ARC_RELEASE(_options);
@@ -640,22 +644,33 @@ static inline NSString* _EncodeBase64(NSString* string) {
return nil;
}
- (BOOL)start {
return [self startWithPort:kDefaultPort bonjourName:@""];
}
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
[options setValue:name forKey:GCDWebServerOption_BonjourName];
return [self startWithOptions:options error:NULL];
}
#if !TARGET_OS_IPHONE
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name {
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
[options setValue:name forKey:GCDWebServerOption_BonjourName];
return [self runWithOptions:options];
return [self runWithOptions:options error:NULL];
}
- (BOOL)runWithOptions:(NSDictionary*)options {
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error {
DCHECK([NSThread isMainThread]);
BOOL success = NO;
_run = YES;
void (*handler)(int) = signal(SIGINT, _SignalHandler);
if (handler != SIG_ERR) {
if ([self startWithOptions:options]) {
if ([self startWithOptions:options error:error]) {
while (_run) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
}
@@ -846,37 +861,29 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (void)logVerbose:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_VERBOSE(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_VERBOSE(@"%@", message);
ARC_RELEASE(message);
}
- (void)logInfo:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_INFO(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_INFO(@"%@", message);
ARC_RELEASE(message);
}
- (void)logWarning:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_WARNING(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_WARNING(@"%@", message);
ARC_RELEASE(message);
}
- (void)logError:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_ERROR(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_ERROR(@"%@", message);
ARC_RELEASE(message);
}
- (void)logException:(NSException*)exception {
@@ -959,7 +966,7 @@ static void _LogResult(NSString* format, ...) {
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs
NSInteger result = -1;
if ([self startWithOptions:options]) {
if ([self startWithOptions:options error:NULL]) {
result = 0;
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];

View File

@@ -38,8 +38,8 @@
* subclass it to override some hooks. Use the GCDWebServerOption_ConnectionClass
* option for GCDWebServer to install your custom subclass.
*
* @warning The GCDWebServerConnection retains the GCDWebServer
* until the connection is closed.
* @warning The GCDWebServerConnection retains the GCDWebServer until the
* connection is closed.
*/
@interface GCDWebServerConnection : NSObject
@@ -95,6 +95,7 @@
/**
* This method is called when the connection is opened.
*
* Return NO to reject the connection e.g. after validating the local
* or remote address.
*/
@@ -103,17 +104,21 @@
/**
* This method is called whenever data has been received
* from the remote peer (i.e. client).
*
* @warning Do not attempt to modify this data.
*/
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length;
/**
* This method is called whenever data has been sent
* to the remote peer (i.e. client).
*
* @warning Do not attempt to modify this data.
*/
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length;
/**
* Assuming a valid request was received, this method is called before
* Assuming a valid HTTP request was received, this method is called before
* the request is processed.
*
* Return a non-nil GCDWebServerResponse to bypass the request processing entirely.
@@ -124,18 +129,18 @@
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;
/**
* Assuming a valid request was received and -preflightRequest: returned nil,
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
* this method is called to process the request.
*/
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
/**
* Assuming a valid request was received and either -preflightRequest:
* Assuming a valid HTTP request was received and either -preflightRequest:
* or -processRequest:withBlock: returned a non-nil GCDWebServerResponse,
* this method is called to override the response.
*
* You can either modify the current response and return it, or return a
* completely different one.
* completely new one.
*
* The default implementation replaces any response matching the "ETag" or
* "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304)
@@ -145,7 +150,7 @@
/**
* This method is called if any error happens while validing or processing
* the request or no GCDWebServerResponse is generated.
* the request or if no GCDWebServerResponse was generated during processing.
*
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
* the "request" argument will be nil.

View File

@@ -675,11 +675,11 @@ static NSString* _StringFromAddressData(NSData* data) {
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
_requestPath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY);
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
DCHECK(_requestFD > 0);
_responsePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY);
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
DCHECK(_responseFD > 0);
}
#endif

View File

@@ -50,17 +50,18 @@ NSString* GCDWebServerEscapeURLString(NSString* string);
NSString* GCDWebServerUnescapeURLString(NSString* string);
/**
* Extracts the unescaped names and values
* from a "application/x-www-form-urlencoded" form.
* Extracts the unescaped names and values from an
* "application/x-www-form-urlencoded" form.
* http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
*/
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
/**
* OS X: Returns the IPv4 address as a dotted string of the primary connected
* service or nil if not available.
* iOS: Returns the IPv4 address as a dotted string of the WiFi interface
* if connected or nil otherwise.
* On OS X, returns the IPv4 address as a dotted string of the primary connected
* service or nil if not available.
*
* On iOS, returns the IPv4 address as a dotted string of the WiFi interface
* if connected or nil otherwise.
*/
NSString* GCDWebServerGetPrimaryIPv4Address();
@@ -76,7 +77,7 @@ NSString* GCDWebServerFormatRFC822(NSDate* date);
* https://tools.ietf.org/html/rfc822#section-5
* https://tools.ietf.org/html/rfc1123#section-5.2.14
*
* @warning Timezones are not supported at this time.
* @warning Timezones other than GMT are not supported by this function.
*/
NSDate* GCDWebServerParseRFC822(NSString* string);
@@ -91,7 +92,7 @@ NSString* GCDWebServerFormatISO8601(NSDate* date);
* http://tools.ietf.org/html/rfc3339#section-5.6
*
* @warning Only "calendar" variant is supported at this time and timezones
* are not supported either.
* other than GMT are not supported either.
*/
NSDate* GCDWebServerParseISO8601(NSString* string);

View File

@@ -114,6 +114,10 @@ static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
return ((range.location != NSNotFound) || (range.length > 0));
}
static inline NSError* GCDWebServerMakePosixError(int code) {
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithUTF8String:strerror(code)]}];
}
extern void GCDWebServerInitializeFunctions();
extern NSString* GCDWebServerNormalizeHeaderValue(NSString* value);
extern NSString* GCDWebServerTruncateHeaderValue(NSString* value);

View File

@@ -106,8 +106,8 @@
@property(nonatomic, readonly) NSDictionary* query;
/**
* Returns the content type for the body of the request (this property is
* automatically parsed from the HTTP headers).
* Returns the content type for the body of the request parsed from the
* "Content-Type" header.
*
* This property will be nil if the request has no body or set to
* "application/octet-stream" if a body is present but there was no
@@ -116,8 +116,8 @@
@property(nonatomic, readonly) NSString* contentType;
/**
* Returns the content length for the body of the request (this property is
* automatically parsed from the HTTP headers).
* Returns the content length for the body of the request parsed from the
* "Content-Length" header.
*
* This property will be set to "NSNotFound" if the request has no body or
* if there is a body but no "Content-Length" header, typically because
@@ -138,13 +138,13 @@
/**
* Returns the parsed "Range" header or (NSNotFound, 0) if absent or malformed.
* The range will be set to (offset, length) if expressed from the beginning
* of the body, or (NSNotFound, -length) if expressed from the end of the body.
* of the entity body, or (NSNotFound, -length) if expressed from its end.
*/
@property(nonatomic, readonly) NSRange byteRange;
/**
* Indicates if the client supports gzip content encoding (this property is
* automatically parsed from the HTTP headers).
* Returns YES if the client supports gzip content encoding according to the
* "Accept-Encoding" header.
*/
@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding;

View File

@@ -29,7 +29,7 @@
/**
* This protocol is used by the GCDWebServerConnection to communicate with
* the GCDWebServerResponse and read the sent HTTP body data.
* the GCDWebServerResponse and read the HTTP body data to send.
*
* Note that multiple GCDWebServerBodyReader objects can be chained together
* internally e.g. to automatically apply gzip encoding to the content before
@@ -48,7 +48,7 @@
- (BOOL)open:(NSError**)error;
/**
* This method is called whenever body data is ready to be sent.
* This method is called whenever body data is sent.
*
* It should return a non-empty NSData if there is body data available,
* or an empty NSData there is no more body data, or nil on error and set
@@ -67,7 +67,7 @@
* The GCDWebServerResponse class is used to wrap a single HTTP response.
* It is instantiated by the handler of the GCDWebServer that handled the request.
* If a body is present, the methods from the GCDWebServerBodyReader protocol
* will be called by the GCDWebServerConnection to retrieve it.
* will be called by the GCDWebServerConnection to send it.
*
* The default implementation of the GCDWebServerBodyReader protocol
* on the class simply returns an empty body.
@@ -78,16 +78,17 @@
/**
* Sets the content type for the body of the response.
* This property must be set if a body is present.
*
* The default value is nil i.e. the response has no body.
*
* @warning This property must be set if a body is present.
*/
@property(nonatomic, copy) NSString* contentType;
/**
* Sets the content length for the body of the response. If a body is present
* but this property is set to "NSNotFound", this means the length of the body
* cannot be known ahead of time and chunked transfer encoding will be
* cannot be known ahead of time. Chunked transfer encoding will be
* automatically enabled by the GCDWebServerConnection to comply with HTTP/1.1
* specifications.
*
@@ -152,7 +153,7 @@
* Pass a nil value to remove an additional header.
*
* @warning Do not attempt to override the primary headers used
* by GCDWebServerResponse e.g. "Content-Type" or "ETag".
* by GCDWebServerResponse like "Content-Type", "ETag", etc...
*/
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header;

View File

@@ -34,10 +34,6 @@
}
@end
static inline NSError* _MakePosixError(int code) {
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%s", strerror(code)]}];
}
@implementation GCDWebServerFileRequest
@synthesize temporaryPath=_temporaryPath;
@@ -59,7 +55,7 @@ static inline NSError* _MakePosixError(int code) {
- (BOOL)open:(NSError**)error {
_file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_file <= 0) {
*error = _MakePosixError(errno);
*error = GCDWebServerMakePosixError(errno);
return NO;
}
return YES;
@@ -67,7 +63,7 @@ static inline NSError* _MakePosixError(int code) {
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (write(_file, data.bytes, data.length) != (ssize_t)data.length) {
*error = _MakePosixError(errno);
*error = GCDWebServerMakePosixError(errno);
return NO;
}
return YES;
@@ -75,7 +71,7 @@ static inline NSError* _MakePosixError(int code) {
- (BOOL)close:(NSError**)error {
if (close(_file) < 0) {
*error = _MakePosixError(errno);
*error = GCDWebServerMakePosixError(errno);
return NO;
}
#ifdef __GCDWEBSERVER_ENABLE_TESTING__

View File

@@ -33,6 +33,11 @@
*/
@interface GCDWebServerMultiPart : NSObject
/**
* Returns the control name retrieved from the part headers.
*/
@property(nonatomic, readonly) NSString* controlName;
/**
* Returns the content type retrieved from the part headers or "text/plain"
* if not available (per HTTP specifications).
@@ -100,13 +105,13 @@
* Returns the argument parts from the multipart encoded form as
* name / GCDWebServerMultiPartArgument pairs.
*/
@property(nonatomic, readonly) NSDictionary* arguments;
@property(nonatomic, readonly) NSArray* arguments;
/**
* Returns the files parts from the multipart encoded form as
* name / GCDWebServerMultiPartFile pairs.
*/
@property(nonatomic, readonly) NSDictionary* files;
@property(nonatomic, readonly) NSArray* files;
/**
* Returns the MIME type for multipart encoded forms
@@ -114,4 +119,14 @@
*/
+ (NSString*)mimeType;
/**
* Returns the first argument for a given control name or nil if not found.
*/
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
/**
* Returns the first file for a given control name or nil if not found.
*/
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
@end

View File

@@ -29,13 +29,19 @@
#define kMultiPartBufferSize (256 * 1024)
enum {
typedef enum {
kParserState_Undefined = 0,
kParserState_Start,
kParserState_Headers,
kParserState_Content,
kParserState_End
};
} ParserState;
@interface GCDWebServerMIMEStreamParser : NSObject
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files;
- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length;
- (BOOL)isAtEnd;
@end
static NSData* _newlineData = nil;
static NSData* _newlinesData = nil;
@@ -43,6 +49,7 @@ static NSData* _dashNewlineData = nil;
@interface GCDWebServerMultiPart () {
@private
NSString* _controlName;
NSString* _contentType;
NSString* _mimeType;
}
@@ -50,17 +57,19 @@ static NSData* _dashNewlineData = nil;
@implementation GCDWebServerMultiPart
@synthesize contentType=_contentType, mimeType=_mimeType;
@synthesize controlName=_controlName, contentType=_contentType, mimeType=_mimeType;
- (id)initWithContentType:(NSString*)contentType {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type {
if ((self = [super init])) {
_contentType = [contentType copy];
_controlName = [name copy];
_contentType = [type copy];
_mimeType = ARC_RETAIN(GCDWebServerTruncateHeaderValue(_contentType));
}
return self;
}
- (void)dealloc {
ARC_RELEASE(_controlName);
ARC_RELEASE(_contentType);
ARC_RELEASE(_mimeType);
@@ -80,8 +89,8 @@ static NSData* _dashNewlineData = nil;
@synthesize data=_data, string=_string;
- (id)initWithContentType:(NSString*)contentType data:(NSData*)data {
if ((self = [super initWithContentType:contentType])) {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
if ((self = [super initWithControlName:name contentType:type])) {
_data = ARC_RETAIN(data);
if ([self.contentType hasPrefix:@"text/"]) {
@@ -116,8 +125,8 @@ static NSData* _dashNewlineData = nil;
@synthesize fileName=_fileName, temporaryPath=_temporaryPath;
- (id)initWithContentType:(NSString*)contentType fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
if ((self = [super initWithContentType:contentType])) {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
if ((self = [super initWithControlName:name contentType:type])) {
_fileName = [fileName copy];
_temporaryPath = [temporaryPath copy];
}
@@ -139,26 +148,25 @@ static NSData* _dashNewlineData = nil;
@end
@interface GCDWebServerMultiPartFormRequest () {
@interface GCDWebServerMIMEStreamParser () {
@private
NSData* _boundary;
NSString* _defaultcontrolName;
ParserState _state;
NSMutableData* _data;
NSMutableArray* _arguments;
NSMutableArray* _files;
NSUInteger _parserState;
NSMutableData* _parserData;
NSString* _controlName;
NSString* _fileName;
NSString* _contentType;
NSString* _tmpPath;
int _tmpFile;
NSMutableDictionary* _arguments;
NSMutableDictionary* _files;
GCDWebServerMIMEStreamParser* _subParser;
}
@end
@implementation GCDWebServerMultiPartFormRequest
@synthesize arguments=_arguments, files=_files;
@implementation GCDWebServerMIMEStreamParser
+ (void)initialize {
if (_newlineData == nil) {
@@ -175,49 +183,50 @@ static NSData* _dashNewlineData = nil;
}
}
+ (NSString*)mimeType {
return @"multipart/form-data";
}
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
if (boundary) {
NSData* data = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding];
_boundary = ARC_RETAIN(data);
}
if (_boundary == nil) {
DNOT_REACHED();
ARC_RELEASE(self);
return nil;
}
_arguments = [[NSMutableDictionary alloc] init];
_files = [[NSMutableDictionary alloc] init];
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files {
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
if (data == nil) {
DNOT_REACHED();
ARC_RELEASE(self);
return nil;
}
if ((self = [super init])) {
_boundary = ARC_RETAIN(data);
_defaultcontrolName = ARC_RETAIN(name);
_arguments = ARC_RETAIN(arguments);
_files = ARC_RETAIN(files);
_data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
_state = kParserState_Start;
}
return self;
}
- (void)dealloc {
ARC_RELEASE(_boundary);
ARC_RELEASE(_defaultcontrolName);
ARC_RELEASE(_data);
ARC_RELEASE(_arguments);
ARC_RELEASE(_files);
ARC_RELEASE(_boundary);
ARC_RELEASE(_controlName);
ARC_RELEASE(_fileName);
ARC_RELEASE(_contentType);
if (_tmpFile > 0) {
close(_tmpFile);
unlink([_tmpPath fileSystemRepresentation]);
}
ARC_RELEASE(_tmpPath);
ARC_RELEASE(_subParser);
ARC_DEALLOC(super);
}
- (BOOL)open:(NSError**)error {
_parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
_parserState = kParserState_Start;
return YES;
}
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
- (BOOL)_parseData {
BOOL success = YES;
if (_parserState == kParserState_Headers) {
NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)];
if (_state == kParserState_Headers) {
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) {
ARC_RELEASE(_controlName);
@@ -228,7 +237,9 @@ static NSData* _dashNewlineData = nil;
_contentType = nil;
ARC_RELEASE(_tmpPath);
_tmpPath = nil;
NSString* headers = [[NSString alloc] initWithData:[_parserData subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding];
ARC_RELEASE(_subParser);
_subParser = nil;
NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding];
if (headers) {
for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) {
NSRange subRange = [header rangeOfString:@":"];
@@ -242,6 +253,9 @@ static NSData* _dashNewlineData = nil;
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
_controlName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"));
_fileName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"));
} else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) {
_controlName = ARC_RETAIN(_defaultcontrolName);
_fileName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"));
}
}
} else {
@@ -257,7 +271,14 @@ static NSData* _dashNewlineData = nil;
DNOT_REACHED();
}
if (_controlName) {
if (_fileName) {
if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) {
NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary");
_subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files];
if (_subParser == nil) {
DNOT_REACHED();
success = NO;
}
} else if (_fileName) {
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
_tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_tmpFile > 0) {
@@ -272,29 +293,36 @@ static NSData* _dashNewlineData = nil;
success = NO;
}
[_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
_parserState = kParserState_Content;
[_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
_state = kParserState_Content;
}
}
if ((_parserState == kParserState_Start) || (_parserState == kParserState_Content)) {
NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)];
if ((_state == kParserState_Start) || (_state == kParserState_Content)) {
NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) {
NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length);
NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length);
NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
if (_parserState == kParserState_Content) {
const void* dataBytes = _parserData.bytes;
if (_state == kParserState_Content) {
const void* dataBytes = _data.bytes;
NSUInteger dataLength = range.location - 2;
if (_tmpPath) {
if (_subParser) {
if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) {
DNOT_REACHED();
success = NO;
}
ARC_RELEASE(_subParser);
_subParser = nil;
} else if (_tmpPath) {
ssize_t result = write(_tmpFile, dataBytes, dataLength);
if (result == (ssize_t)dataLength) {
if (close(_tmpFile) == 0) {
_tmpFile = 0;
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
[_files setObject:file forKey:_controlName];
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
[_files addObject:file];
ARC_RELEASE(file);
} else {
DNOT_REACHED();
@@ -308,85 +336,150 @@ static NSData* _dashNewlineData = nil;
_tmpPath = nil;
} else {
NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength];
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithContentType:_contentType data:data];
[_arguments setObject:argument forKey:_controlName];
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data];
[_arguments addObject:argument];
ARC_RELEASE(argument);
ARC_RELEASE(data);
}
}
if (subRange1.location != NSNotFound) {
[_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
_parserState = kParserState_Headers;
[_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
_state = kParserState_Headers;
success = [self _parseData];
} else {
_parserState = kParserState_End;
_state = kParserState_End;
}
}
} else {
NSUInteger margin = 2 * _boundary.length;
if (_tmpPath && (_parserData.length > margin)) {
NSUInteger length = _parserData.length - margin;
ssize_t result = write(_tmpFile, _parserData.bytes, length);
if (result == (ssize_t)length) {
[_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
DNOT_REACHED();
success = NO;
if (_data.length > margin) {
NSUInteger length = _data.length - margin;
if (_subParser) {
if ([_subParser appendBytes:_data.bytes length:length]) {
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
DNOT_REACHED();
success = NO;
}
} else if (_tmpPath) {
ssize_t result = write(_tmpFile, _data.bytes, length);
if (result == (ssize_t)length) {
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else {
DNOT_REACHED();
success = NO;
}
}
}
}
}
return success;
}
- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length {
[_data appendBytes:bytes length:length];
return [self _parseData];
}
- (BOOL)isAtEnd {
return (_state == kParserState_End);
}
@end
@interface GCDWebServerMultiPartFormRequest () {
@private
GCDWebServerMIMEStreamParser* _parser;
NSMutableArray* _arguments;
NSMutableArray* _files;
}
@end
@implementation GCDWebServerMultiPartFormRequest
@synthesize arguments=_arguments, files=_files;
+ (NSString*)mimeType {
return @"multipart/form-data";
}
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
_arguments = [[NSMutableArray alloc] init];
_files = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
ARC_RELEASE(_arguments);
ARC_RELEASE(_files);
ARC_DEALLOC(super);
}
- (BOOL)open:(NSError**)error {
NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
if (_parser == nil) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}];
return NO;
}
return YES;
}
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
[_parserData appendBytes:data.bytes length:data.length];
if (![self _parseData]) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}];
if (![_parser appendBytes:data.bytes length:data.length]) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}];
return NO;
}
return YES;
}
- (BOOL)close:(NSError**)error {
ARC_RELEASE(_parserData);
_parserData = nil;
ARC_RELEASE(_controlName);
_controlName = nil;
ARC_RELEASE(_fileName);
_fileName = nil;
ARC_RELEASE(_contentType);
_contentType = nil;
if (_tmpFile > 0) {
close(_tmpFile);
unlink([_tmpPath fileSystemRepresentation]);
_tmpFile = 0;
}
ARC_RELEASE(_tmpPath);
_tmpPath = nil;
if (_parserState != kParserState_End) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}];
BOOL atEnd = [_parser isAtEnd];
ARC_RELEASE(_parser);
_parser = nil;
if (!atEnd) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}];
return NO;
}
return YES;
}
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name {
for (GCDWebServerMultiPartArgument* argument in _arguments) {
if ([argument.controlName isEqualToString:name]) {
return argument;
}
}
return nil;
}
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name {
for (GCDWebServerMultiPartFile* file in _files) {
if ([file.controlName isEqualToString:name]) {
return file;
}
}
return nil;
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_arguments.count) {
[description appendString:@"\n"];
for (NSString* key in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
GCDWebServerMultiPartArgument* argument = [_arguments objectForKey:key];
[description appendFormat:@"\n%@ (%@)\n", key, argument.contentType];
for (GCDWebServerMultiPartArgument* argument in _arguments) {
[description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType];
[description appendString:GCDWebServerDescribeData(argument.data, argument.contentType)];
}
}
if (_files.count) {
[description appendString:@"\n"];
for (NSString* key in [[_files allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
GCDWebServerMultiPartFile* file = [_files objectForKey:key];
[description appendFormat:@"\n%@ (%@): %@\n{%@}", key, file.contentType, file.fileName, file.temporaryPath];
for (GCDWebServerMultiPartFile* file in _files) {
[description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath];
}
}
return description;

View File

@@ -35,7 +35,7 @@
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
/**
* Returns the unescaped names and values for the URL encoded form.
* Returns the unescaped control names and values for the URL encoded form.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.

View File

@@ -78,7 +78,7 @@
/**
* Initializes a data response from text encoded using UTF-8.
*/
- (instancetype)initWithText:(NSString*)text; // Encodes using UTF-8
- (instancetype)initWithText:(NSString*)text;
/**
* Initializes a data response from HTML encoded using UTF-8.
@@ -87,6 +87,7 @@
/**
* Initializes a data response from an HTML template encoded using UTF-8.
*
* All occurences of "%variable%" within the HTML template are replaced with
* their corresponding values.
*/

View File

@@ -46,13 +46,13 @@
/**
* Creates a client error response with the corresponding HTTP status code
* and an optional underlying NSError.
* and an underlying NSError.
*/
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
/**
* Creates a server error response with the corresponding HTTP status code
* and an optional underlying NSError.
* and an underlying NSError.
*/
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
@@ -68,13 +68,13 @@
/**
* Initializes a client error response with the corresponding HTTP status code
* and an optional underlying NSError.
* and an underlying NSError.
*/
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
/**
* Initializes a server error response with the corresponding HTTP status code
* and an optional underlying NSError.
* and an underlying NSError.
*/
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);

View File

@@ -30,18 +30,21 @@
/**
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from a file on disk.
*
* It will automatically set the contentType, lastModifiedDate and eTag
* properties of the GCDWebServerResponse according to the file extension and
* metadata.
*/
@interface GCDWebServerFileResponse : GCDWebServerResponse
/**
* Creates a response with the contents of a file and the content type
* automatically set from the file extension.
* Creates a response with the contents of a file.
*/
+ (instancetype)responseWithFile:(NSString*)path;
/**
* Creates a response like +responseWithFile: but sets the "Content-Disposition"
* HTTP header appropriately for a download if the "attachment" argument is YES.
* Creates a response like +responseWithFile: and sets the "Content-Disposition"
* HTTP header for a download if the "attachment" argument is YES.
*/
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
@@ -54,21 +57,21 @@
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range;
/**
* Creates a response like +responseWithFile:byteRange: but also sets the
* "Content-Disposition" HTTP header appropriately for a download if the
* "attachment" argument is YES.
* Creates a response like +responseWithFile:byteRange: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment"
* argument is YES.
*/
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
/**
* Initializes a response with the contents of a file and the content type
* automatically set from the file extension.
* Initializes a response with the contents of a file.
*/
- (instancetype)initWithFile:(NSString*)path;
/**
* Initializes a response like -initWithFile: but sets the "Content-Disposition"
* HTTP header appropriately for a download if the "attachment" argument is YES.
* Initializes a response like +responseWithFile: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment"
* argument is YES.
*/
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;

View File

@@ -40,10 +40,6 @@
}
@end
static inline NSError* _MakePosixError(int code) {
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%s", strerror(code)]}];
}
@implementation GCDWebServerFileResponse
+ (instancetype)responseWithFile:(NSString*)path {
@@ -142,11 +138,11 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
- (BOOL)open:(NSError**)error {
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
if (_file <= 0) {
*error = _MakePosixError(errno);
*error = GCDWebServerMakePosixError(errno);
return NO;
}
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
*error = _MakePosixError(errno);
*error = GCDWebServerMakePosixError(errno);
close(_file);
return NO;
}
@@ -158,7 +154,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
NSMutableData* data = [[NSMutableData alloc] initWithLength:length];
ssize_t result = read(_file, data.mutableBytes, length);
if (result < 0) {
*error = _MakePosixError(errno);
*error = GCDWebServerMakePosixError(errno);
return nil;
}
if (result > 0) {

View File

@@ -30,7 +30,7 @@
@class GCDWebUploader;
/**
* Delegate methods for GCDWebUploaderDelegate.
* Delegate methods for GCDWebUploader.
*
* @warning These methods are always called on the main thread in a serialized way.
*/
@@ -70,37 +70,39 @@
* or directories.
*
* See the README.md file for more information about the features of GCDWebUploader.
*
* @warning For GCDWebUploader to work, "GCDWebUploader.bundle" must be added
* to the resources of the Xcode target.
*/
@interface GCDWebUploader : GCDWebServer
/**
* Returns the upload directory as specified when the server was initialized.
* Returns the upload directory as specified when the uploader was initialized.
*/
@property(nonatomic, readonly) NSString* uploadDirectory;
/**
* Sets the delegate for the server.
* Sets the delegate for the uploader.
*/
@property(nonatomic, assign) id<GCDWebUploaderDelegate> delegate;
/**
* Restricts which files should be listed and allowed to be uploaded, downloaded,
* moved or deleted depending on their extensions.
* Sets which files are allowed to be operated on depending on their extension.
*
* The default value is nil i.e. all file extensions are allowed.
*/
@property(nonatomic, copy) NSArray* allowedFileExtensions;
/**
* Sets if files and directories whose name start with a period should be
* listed and allowed to be uploaded, downloaded, moved or deleted.
* Sets if files and directories whose name start with a period are allowed to
* be operated on.
*
* The default value is NO.
*/
@property(nonatomic) BOOL allowHiddenItems;
/**
* Sets the title for the uploader interface.
* Sets the title for the uploader web interface.
*
* The default value is the application name.
*
@@ -110,7 +112,7 @@
@property(nonatomic, copy) NSString* title;
/**
* Sets the header for the uploader interface.
* Sets the header for the uploader web interface.
*
* The default value is the same as the title property.
*
@@ -120,7 +122,7 @@
@property(nonatomic, copy) NSString* header;
/**
* Sets the prologue for the uploader interface.
* Sets the prologue for the uploader web interface.
*
* The default value is a short help text.
*
@@ -130,7 +132,7 @@
@property(nonatomic, copy) NSString* prologue;
/**
* Sets the epilogue for the uploader interface.
* Sets the epilogue for the uploader web interface.
*
* The default value is nil i.e. no epilogue.
*
@@ -140,7 +142,7 @@
@property(nonatomic, copy) NSString* epilogue;
/**
* Sets the footer for the uploader interface.
* Sets the footer for the uploader web interface.
*
* The default value is the application name and version.
*
@@ -164,7 +166,7 @@
@interface GCDWebUploader (Subclassing)
/**
* This method is called to check if a file is allowed to be uploaded.
* This method is called to check if a file upload is allowed to complete.
* The uploaded file is available for inspection at "tempPath".
*
* The default implementation returns YES.

View File

@@ -154,11 +154,11 @@
NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch];
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
GCDWebServerMultiPartFile* file = [request.files objectForKey:@"files[]"];
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
if ((!_allowHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
}
NSString* relativePath = [(GCDWebServerMultiPartArgument*)[request.arguments objectForKey:@"path"] string];
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {

View File

@@ -31,6 +31,7 @@
#import "GCDWebServerDataRequest.h"
#import "GCDWebServerURLEncodedFormRequest.h"
#import "GCDWebServerMultiPartFormRequest.h"
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerStreamedResponse.h"
@@ -47,9 +48,10 @@ typedef enum {
kMode_WebServer = 0,
kMode_HTMLPage,
kMode_HTMLForm,
kMode_HTMLFileUpload,
kMode_WebDAV,
kMode_WebUploader,
kMode_StreamingResponse
kMode_StreamingResponse,
} Mode;
@interface Delegate : NSObject <GCDWebServerDelegate, GCDWebDAVServerDelegate, GCDWebUploaderDelegate>
@@ -140,7 +142,7 @@ int main(int argc, const char* argv[]) {
NSString* authenticationPassword = nil;
if (argc == 1) {
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
} else {
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
@@ -154,6 +156,8 @@ int main(int argc, const char* argv[]) {
mode = kMode_HTMLPage;
} else if (!strcmp(argv[i], "htmlForm")) {
mode = kMode_HTMLForm;
} else if (!strcmp(argv[i], "htmlFileUpload")) {
mode = kMode_HTMLFileUpload;
} else if (!strcmp(argv[i], "webDAV")) {
mode = kMode_WebDAV;
} else if (!strcmp(argv[i], "webUploader")) {
@@ -243,6 +247,48 @@ int main(int argc, const char* argv[]) {
break;
}
// Implements HTML file upload
case kMode_HTMLFileUpload: {
fprintf(stdout, "Running in HTML File Upload mode");
webServer = [[GCDWebServer alloc] init];
NSString* formHTML = @" \
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"multipart/form-data\"> \
<input type=\"hidden\" name=\"secret\" value=\"42\"> \
<input type=\"file\" name=\"files\" multiple><br/> \
<input type=\"submit\" value=\"Submit\"> \
</form> \
";
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
[webServer addHandlerForMethod:@"POST"
path:@"/"
requestClass:[GCDWebServerMultiPartFormRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSMutableString* string = [NSMutableString string];
for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) {
[string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string];
}
for (GCDWebServerMultiPartFile* file in [(GCDWebServerMultiPartFormRequest*)request files]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:file.temporaryPath error:NULL];
[string appendFormat:@"%@ = &quot;%@&quot; (%@ | %llu %@)<br>", file.controlName, file.fileName, file.mimeType,
attributes.fileSize >= 1000 ? attributes.fileSize / 1000 : attributes.fileSize,
attributes.fileSize >= 1000 ? @"KB" : @"Bytes"];
};
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
break;
}
// Serve home directory through WebDAV
case kMode_WebDAV: {
fprintf(stdout, "Running in WebDAV mode from \"%s\"", [rootDirectory UTF8String]);
@@ -313,7 +359,7 @@ int main(int argc, const char* argv[]) {
[options setObject:GCDWebServerAuthenticationMethod_DigestAccess forKey:GCDWebServerOption_AuthenticationMethod];
}
}
if ([webServer runWithOptions:options]) {
if ([webServer runWithOptions:options error:NULL]) {
result = 0;
}
}

View File

@@ -2,6 +2,8 @@ Overview
========
[![Build Status](https://travis-ci.org/swisspol/GCDWebServer.svg?branch=master)](https://travis-ci.org/swisspol/GCDWebServer)
[![Version](http://cocoapod-badges.herokuapp.com/v/GCDWebServer/badge.png)](http://cocoadocs.org/docsets/GCDWebServer)
[![Platform](http://cocoapod-badges.herokuapp.com/p/GCDWebServer/badge.png)](http://cocoadocs.org/docsets/GCDWebServer)
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in OS X & iOS apps. It was written from scratch with the following goals in mind:
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
@@ -169,7 +171,6 @@ int main(int argc, const char* argv[]) {
GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[webServer runWithPort:8080];
[webServer release]; // Remove if using ARC
}
return 0;

View File

@@ -43,6 +43,10 @@ rm -rf "$ARC_BUILD_DIR"
xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$ARC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=YES" > /dev/null
# Run tests
runTests $MRC_PRODUCT "htmlForm" "Tests/HTMLForm"
runTests $ARC_PRODUCT "htmlForm" "Tests/HTMLForm"
runTests $MRC_PRODUCT "htmlFileUpload" "Tests/HTMLFileUpload"
runTests $ARC_PRODUCT "htmlFileUpload" "Tests/HTMLFileUpload"
runTests $MRC_PRODUCT "webServer" "Tests/WebServer"
runTests $ARC_PRODUCT "webServer" "Tests/WebServer"
runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Transmit"

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 299
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:15:11 GMT
<html><body> <form name="input" action="/" method="post" enctype="multipart/form-data"> <input type="hidden" name="secret" value="42"> <input type="file" name="files" multiple><br/> <input type="submit" value="Submit"> </form> </body></html>

View File

@@ -0,0 +1,10 @@
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36
DNT: 1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 447
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:15:21 GMT
<html><body><p>secret = 42<br>files = &quot;hero_mba_11.jpg&quot; (image/jpeg | 106 KB)<br>files = &quot;Test File.txt&quot; (text/plain | 21 Bytes)<br></p><hr> <form name="input" action="/" method="post" enctype="multipart/form-data"> <input type="hidden" name="secret" value="42"> <input type="file" name="files" multiple><br/> <input type="submit" value="Submit"> </form> </body></html>

Binary file not shown.

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 293
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:12:09 GMT
<html><body> <form name="input" action="/" method="post" enctype="application/x-www-form-urlencoded"> Value: <input type="text" name="value"> <input type="submit" value="Submit"> </form> </body></html>

View File

@@ -0,0 +1,10 @@
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36
DNT: 1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6

View File

@@ -0,0 +1,9 @@
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 47
Content-Type: text/html; charset=utf-8
Connection: Close
Server: GCDWebServer
Date: Fri, 25 Apr 2014 14:12:20 GMT
<html><body><p>Hellø Wörld!</p></body></html>

View File

@@ -0,0 +1,15 @@
POST / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 30
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36
Content-Type: application/x-www-form-urlencoded
DNT: 1
Referer: http://localhost:8080/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6
value=Hell%C3%B8+W%C3%B6rld%21