mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-02-11 00:00:07 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51e8dbe475 | ||
|
|
4a3d4537a3 | ||
|
|
2597d6da00 | ||
|
|
1ca5a56952 | ||
|
|
80cff01154 | ||
|
|
1e17d5c455 | ||
|
|
ce1eb3c29a | ||
|
|
f11647ee7f | ||
|
|
60f281fd30 | ||
|
|
181984ba6d | ||
|
|
4685fae29c | ||
|
|
8619799e62 | ||
|
|
b1061378fb | ||
|
|
9bc2bfa506 | ||
|
|
078c5e7bc3 | ||
|
|
3f304b3c14 | ||
|
|
80348079a6 | ||
|
|
099b2a03e6 | ||
|
|
f0c63f4776 | ||
|
|
4e3aa3bc5c | ||
|
|
c5d82b85ed | ||
|
|
b1181d2e40 | ||
|
|
d931e04d47 | ||
|
|
016c4da6d2 | ||
|
|
38dd39a789 | ||
|
|
748f6a8bc9 |
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'GCDWebServer'
|
s.name = 'GCDWebServer'
|
||||||
s.version = '2.2'
|
s.version = '2.3'
|
||||||
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
|
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
|
||||||
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
||||||
s.homepage = 'https://github.com/swisspol/GCDWebServer'
|
s.homepage = 'https://github.com/swisspol/GCDWebServer'
|
||||||
@@ -24,7 +24,9 @@ Pod::Spec.new do |s|
|
|||||||
cs.source_files = 'GCDWebServer/**/*.{h,m}'
|
cs.source_files = 'GCDWebServer/**/*.{h,m}'
|
||||||
cs.requires_arc = true
|
cs.requires_arc = true
|
||||||
cs.ios.library = 'z'
|
cs.ios.library = 'z'
|
||||||
|
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'
|
||||||
cs.osx.library = 'z'
|
cs.osx.library = 'z'
|
||||||
|
cs.osx.framework = 'SystemConfiguration'
|
||||||
cs.compiler_flags = '-DNDEBUG' # TODO: Only set this for Release configuration
|
cs.compiler_flags = '-DNDEBUG' # TODO: Only set this for Release configuration
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ extern NSString* const GCDWebServerOption_Port; // NSNumber / NSUInteger (defau
|
|||||||
extern NSString* const GCDWebServerOption_BonjourName; // NSString (default is empty string i.e. use computer name)
|
extern NSString* const GCDWebServerOption_BonjourName; // NSString (default is empty string i.e. use computer name)
|
||||||
extern NSString* const GCDWebServerOption_MaxPendingConnections; // NSNumber / NSUInteger (default is 16)
|
extern NSString* const GCDWebServerOption_MaxPendingConnections; // NSNumber / NSUInteger (default is 16)
|
||||||
extern NSString* const GCDWebServerOption_ServerName; // NSString (default is server class name)
|
extern NSString* const GCDWebServerOption_ServerName; // NSString (default is server class name)
|
||||||
|
extern NSString* const GCDWebServerOption_AuthenticationMethod; // One of "GCDWebServerAuthenticationMethod_..." (default is nil i.e. no authentication)
|
||||||
|
extern NSString* const GCDWebServerOption_AuthenticationRealm; // NSString (default is server name)
|
||||||
|
extern NSString* const GCDWebServerOption_AuthenticationAccounts; // NSDictionary of username / password (default is nil i.e. no accounts)
|
||||||
extern NSString* const GCDWebServerOption_ConnectionClass; // Subclass of GCDWebServerConnection (default is GCDWebServerConnection class)
|
extern NSString* const GCDWebServerOption_ConnectionClass; // Subclass of GCDWebServerConnection (default is GCDWebServerConnection class)
|
||||||
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; // NSNumber / BOOL (default is YES)
|
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; // NSNumber / BOOL (default is YES)
|
||||||
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; // NSNumber / double (default is 1.0 seconds - set to <=0.0 to disable coaslescing of -webServerDidConnect: / -webServerDidDisconnect:)
|
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; // NSNumber / double (default is 1.0 seconds - set to <=0.0 to disable coaslescing of -webServerDidConnect: / -webServerDidDisconnect:)
|
||||||
@@ -53,6 +56,9 @@ extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; //
|
|||||||
extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; // NSNumber / BOOL (default is YES)
|
extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; // NSNumber / BOOL (default is YES)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern NSString* const GCDWebServerAuthenticationMethod_Basic; // Not recommended as password is sent in clear
|
||||||
|
extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
||||||
|
|
||||||
@class GCDWebServer;
|
@class GCDWebServer;
|
||||||
|
|
||||||
// These methods are always called on main thread
|
// These methods are always called on main thread
|
||||||
@@ -83,7 +89,8 @@ extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; //
|
|||||||
@property(nonatomic, readonly) NSURL* serverURL; // Only non-nil if server is running
|
@property(nonatomic, readonly) NSURL* serverURL; // Only non-nil if server is running
|
||||||
@property(nonatomic, readonly) NSURL* bonjourServerURL; // Only non-nil if server is running and Bonjour registration is active
|
@property(nonatomic, readonly) NSURL* bonjourServerURL; // Only non-nil if server is running and Bonjour registration is active
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
- (BOOL)runWithPort:(NSUInteger)port; // Starts then automatically stops on SIGINT i.e. Ctrl-C (use on main thread only)
|
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name;
|
||||||
|
- (BOOL)runWithOptions:(NSDictionary*)options; // Starts then automatically stops on SIGINT i.e. Ctrl-C (use on main thread only)
|
||||||
#endif
|
#endif
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -113,7 +120,7 @@ extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; //
|
|||||||
|
|
||||||
@interface GCDWebServer (Testing)
|
@interface GCDWebServer (Testing)
|
||||||
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled; // Creates files in the current directory containing the raw data for all requests and responses (directory most NOT contain prior recordings)
|
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled; // Creates files in the current directory containing the raw data for all requests and responses (directory most NOT contain prior recordings)
|
||||||
- (NSInteger)runTestsInDirectory:(NSString*)path withPort:(NSUInteger)port; // Returns number of failed tests or -1 if server failed to start
|
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path; // Returns number of failed tests or -1 if server failed to start
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -54,6 +54,9 @@
|
|||||||
|
|
||||||
NSDictionary* _options;
|
NSDictionary* _options;
|
||||||
NSString* _serverName;
|
NSString* _serverName;
|
||||||
|
NSString* _authenticationRealm;
|
||||||
|
NSMutableDictionary* _authenticationBasicAccounts;
|
||||||
|
NSMutableDictionary* _authenticationDigestAccounts;
|
||||||
Class _connectionClass;
|
Class _connectionClass;
|
||||||
BOOL _mapHEADToGET;
|
BOOL _mapHEADToGET;
|
||||||
CFTimeInterval _disconnectDelay;
|
CFTimeInterval _disconnectDelay;
|
||||||
@@ -81,6 +84,9 @@ NSString* const GCDWebServerOption_Port = @"Port";
|
|||||||
NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
|
NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
|
||||||
NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
|
NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
|
||||||
NSString* const GCDWebServerOption_ServerName = @"ServerName";
|
NSString* const GCDWebServerOption_ServerName = @"ServerName";
|
||||||
|
NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod";
|
||||||
|
NSString* const GCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm";
|
||||||
|
NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts";
|
||||||
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
|
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
|
||||||
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
|
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
|
||||||
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
|
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
|
||||||
@@ -88,6 +94,9 @@ NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"Connecte
|
|||||||
NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
|
NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
|
||||||
|
NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";
|
||||||
|
|
||||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||||
#ifdef NDEBUG
|
#ifdef NDEBUG
|
||||||
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Info;
|
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Info;
|
||||||
@@ -146,7 +155,9 @@ static void _SignalHandler(int signal) {
|
|||||||
|
|
||||||
@implementation GCDWebServer
|
@implementation GCDWebServer
|
||||||
|
|
||||||
@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
|
||||||
|
authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts,
|
||||||
|
shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
||||||
|
|
||||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||||
|
|
||||||
@@ -339,6 +350,15 @@ static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValu
|
|||||||
return value ? value : defaultValue;
|
return value ? value : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline NSString* _EncodeBase64(NSString* string) {
|
||||||
|
NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
#if (TARGET_OS_IPHONE && !(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)) || (!TARGET_OS_IPHONE && !(__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9))
|
||||||
|
if (![data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) {
|
||||||
|
return [data base64Encoding];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return ARC_AUTORELEASE([[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]);
|
||||||
|
}
|
||||||
- (BOOL)_start {
|
- (BOOL)_start {
|
||||||
DCHECK(_source == NULL);
|
DCHECK(_source == NULL);
|
||||||
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
||||||
@@ -359,6 +379,22 @@ static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValu
|
|||||||
if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
|
if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
|
||||||
LOG_DEBUG(@"Did open listening socket %i", listeningSocket);
|
LOG_DEBUG(@"Did open listening socket %i", listeningSocket);
|
||||||
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
||||||
|
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
||||||
|
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
||||||
|
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||||
|
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
|
||||||
|
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||||
|
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||||
|
[_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
|
||||||
|
}];
|
||||||
|
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
||||||
|
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||||
|
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
|
||||||
|
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||||
|
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||||
|
[_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
|
||||||
|
}];
|
||||||
|
}
|
||||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||||
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||||
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
||||||
@@ -473,6 +509,12 @@ static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValu
|
|||||||
|
|
||||||
ARC_RELEASE(_serverName);
|
ARC_RELEASE(_serverName);
|
||||||
_serverName = nil;
|
_serverName = nil;
|
||||||
|
ARC_RELEASE(_authenticationRealm);
|
||||||
|
_authenticationRealm = nil;
|
||||||
|
ARC_RELEASE(_authenticationBasicAccounts);
|
||||||
|
_authenticationBasicAccounts = nil;
|
||||||
|
ARC_RELEASE(_authenticationDigestAccounts);
|
||||||
|
_authenticationDigestAccounts = nil;
|
||||||
|
|
||||||
LOG_INFO(@"%@ stopped", [self class]);
|
LOG_INFO(@"%@ stopped", [self class]);
|
||||||
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
||||||
@@ -596,13 +638,20 @@ static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValu
|
|||||||
|
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
|
|
||||||
- (BOOL)runWithPort:(NSUInteger)port {
|
- (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];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)runWithOptions:(NSDictionary*)options {
|
||||||
DCHECK([NSThread isMainThread]);
|
DCHECK([NSThread isMainThread]);
|
||||||
BOOL success = NO;
|
BOOL success = NO;
|
||||||
_run = YES;
|
_run = YES;
|
||||||
void (*handler)(int) = signal(SIGINT, _SignalHandler);
|
void (*handler)(int) = signal(SIGINT, _SignalHandler);
|
||||||
if (handler != SIG_ERR) {
|
if (handler != SIG_ERR) {
|
||||||
if ([self startWithPort:port bonjourName:@""]) {
|
if ([self startWithOptions:options]) {
|
||||||
while (_run) {
|
while (_run) {
|
||||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
|
||||||
}
|
}
|
||||||
@@ -899,10 +948,10 @@ static void _LogResult(NSString* format, ...) {
|
|||||||
ARC_RELEASE(message);
|
ARC_RELEASE(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)runTestsInDirectory:(NSString*)path withPort:(NSUInteger)port {
|
- (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
|
NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs
|
||||||
NSInteger result = -1;
|
NSInteger result = -1;
|
||||||
if ([self startWithPort:port bonjourName:nil]) {
|
if ([self startWithOptions:options]) {
|
||||||
|
|
||||||
result = 0;
|
result = 0;
|
||||||
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
|
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
|
||||||
@@ -927,7 +976,7 @@ static void _LogResult(NSString* format, ...) {
|
|||||||
if (responseData) {
|
if (responseData) {
|
||||||
CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO);
|
CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO);
|
||||||
if (expectedResponse) {
|
if (expectedResponse) {
|
||||||
CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, port);
|
CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port);
|
||||||
if (actualResponse) {
|
if (actualResponse) {
|
||||||
success = YES;
|
success = YES;
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,9 @@
|
|||||||
- (BOOL)open; // Return NO to reject connection e.g. after validating local or remote addresses
|
- (BOOL)open; // Return NO to reject connection e.g. after validating local or remote addresses
|
||||||
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; // Called after data has been read from the connection
|
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; // Called after data has been read from the connection
|
||||||
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; // Called after data has been written to the connection
|
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; // Called after data has been written to the connection
|
||||||
|
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request; // Called before request is processed to return an override response bypassing processing or nil to continue - Default implementation checks authentication if applicable
|
||||||
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block; // Only called if the request can be processed
|
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block; // Only called if the request can be processed
|
||||||
- (GCDWebServerResponse*)replaceResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request; // Default implementation replaces any response matching the "ETag" or "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) one
|
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request; // Default implementation replaces any response matching the "ETag" or "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) one
|
||||||
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; // If request headers was malformed, "request" will be nil
|
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; // If request headers were malformed, "request" will be nil
|
||||||
- (void)close;
|
- (void)close;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ static NSData* _CRLFData = nil;
|
|||||||
static NSData* _CRLFCRLFData = nil;
|
static NSData* _CRLFCRLFData = nil;
|
||||||
static NSData* _continueData = nil;
|
static NSData* _continueData = nil;
|
||||||
static NSData* _lastChunkData = nil;
|
static NSData* _lastChunkData = nil;
|
||||||
|
static NSString* _digestAuthenticationNonce = nil;
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
static int32_t _connectionCounter = 0;
|
static int32_t _connectionCounter = 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -357,6 +358,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
if (_lastChunkData == nil) {
|
if (_lastChunkData == nil) {
|
||||||
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
|
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
|
||||||
}
|
}
|
||||||
|
if (_digestAuthenticationNonce == nil) {
|
||||||
|
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
||||||
|
_digestAuthenticationNonce = ARC_RETAIN(GCDWebServerComputeMD5Digest(@"%@", ARC_BRIDGE_RELEASE(CFUUIDCreateString(kCFAllocatorDefault, uuid))));
|
||||||
|
CFRelease(uuid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
|
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
|
||||||
@@ -372,20 +378,23 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
DCHECK(_responseMessage == NULL);
|
DCHECK(_responseMessage == NULL);
|
||||||
BOOL hasBody = NO;
|
BOOL hasBody = NO;
|
||||||
|
|
||||||
GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock];
|
GCDWebServerResponse* response = [self preflightRequest:_request];
|
||||||
|
if (!response) {
|
||||||
|
response = [self processRequest:_request withBlock:_handler.processBlock];
|
||||||
|
}
|
||||||
if (response) {
|
if (response) {
|
||||||
response = [self replaceResponse:response forRequest:_request];
|
response = [self overrideResponse:response forRequest:_request];
|
||||||
if (response) {
|
}
|
||||||
if ([response hasBody]) {
|
if (response) {
|
||||||
[response prepareForReading];
|
if ([response hasBody]) {
|
||||||
hasBody = !_virtualHEAD;
|
[response prepareForReading];
|
||||||
}
|
hasBody = !_virtualHEAD;
|
||||||
NSError* error = nil;
|
}
|
||||||
if (hasBody && ![response performOpen:&error]) {
|
NSError* error = nil;
|
||||||
LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
|
if (hasBody && ![response performOpen:&error]) {
|
||||||
} else {
|
LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
|
||||||
_response = ARC_RETAIN(response);
|
} else {
|
||||||
}
|
_response = ARC_RETAIN(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,9 +538,9 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
if ([_request hasBody]) {
|
if ([_request hasBody]) {
|
||||||
[_request prepareForWriting];
|
[_request prepareForWriting];
|
||||||
if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) {
|
if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) {
|
||||||
NSString* expectHeader = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyHeaderFieldValue(_requestMessage, CFSTR("Expect")));
|
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
|
||||||
if (expectHeader) {
|
if (expectHeader) {
|
||||||
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) {
|
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
|
||||||
[self _writeData:_continueData withCompletionBlock:^(BOOL success) {
|
[self _writeData:_continueData withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -704,6 +713,57 @@ static NSString* _StringFromAddressData(NSData* data) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc2617
|
||||||
|
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
|
||||||
|
LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
||||||
|
GCDWebServerResponse* response = nil;
|
||||||
|
if (_server.authenticationBasicAccounts) {
|
||||||
|
__block BOOL authenticated = NO;
|
||||||
|
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
|
||||||
|
if ([authorizationHeader hasPrefix:@"Basic "]) {
|
||||||
|
NSString* basicAccount = [authorizationHeader substringFromIndex:6];
|
||||||
|
[_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) {
|
||||||
|
if ([basicAccount isEqualToString:digest]) {
|
||||||
|
authenticated = YES;
|
||||||
|
*stop = YES;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
if (!authenticated) {
|
||||||
|
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
|
||||||
|
[response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"];
|
||||||
|
}
|
||||||
|
} else if (_server.authenticationDigestAccounts) {
|
||||||
|
BOOL authenticated = NO;
|
||||||
|
BOOL isStaled = NO;
|
||||||
|
NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"];
|
||||||
|
if ([authorizationHeader hasPrefix:@"Digest "]) {
|
||||||
|
NSString* realm = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm");
|
||||||
|
if ([realm isEqualToString:_server.authenticationRealm]) {
|
||||||
|
NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce");
|
||||||
|
if ([nonce isEqualToString:_digestAuthenticationNonce]) {
|
||||||
|
NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username");
|
||||||
|
NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri");
|
||||||
|
NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response");
|
||||||
|
NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username];
|
||||||
|
NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required
|
||||||
|
NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2);
|
||||||
|
if ([actualResponse isEqualToString:expectedResponse]) {
|
||||||
|
authenticated = YES;
|
||||||
|
}
|
||||||
|
} else if (nonce.length) {
|
||||||
|
isStaled = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!authenticated) {
|
||||||
|
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized];
|
||||||
|
[response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
|
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
|
||||||
LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
@@ -731,7 +791,7 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
|||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (GCDWebServerResponse*)replaceResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request {
|
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request {
|
||||||
if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) {
|
if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) {
|
||||||
NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed;
|
NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed;
|
||||||
GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code];
|
GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code];
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#else
|
#else
|
||||||
#import <SystemConfiguration/SystemConfiguration.h>
|
#import <SystemConfiguration/SystemConfiguration.h>
|
||||||
#endif
|
#endif
|
||||||
|
#import <CommonCrypto/CommonDigest.h>
|
||||||
|
|
||||||
#import <ifaddrs.h>
|
#import <ifaddrs.h>
|
||||||
#import <net/if.h>
|
#import <net/if.h>
|
||||||
@@ -79,13 +80,11 @@ NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
|
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
|
||||||
DCHECK([value isEqualToString:GCDWebServerNormalizeHeaderValue(value)]);
|
|
||||||
NSRange range = [value rangeOfString:@";"];
|
NSRange range = [value rangeOfString:@";"];
|
||||||
return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
|
return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
|
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
|
||||||
DCHECK([value isEqualToString:GCDWebServerNormalizeHeaderValue(value)]);
|
|
||||||
NSString* parameter = nil;
|
NSString* parameter = nil;
|
||||||
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
|
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
|
||||||
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
|
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
|
||||||
@@ -266,3 +265,22 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
|||||||
}
|
}
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
||||||
|
va_list arguments;
|
||||||
|
va_start(arguments, format);
|
||||||
|
const char* string = [ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]) UTF8String];
|
||||||
|
va_end(arguments);
|
||||||
|
unsigned char md5[CC_MD5_DIGEST_LENGTH];
|
||||||
|
CC_MD5(string, (CC_LONG)strlen(string), md5);
|
||||||
|
char buffer[2 * CC_MD5_DIGEST_LENGTH + 1];
|
||||||
|
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
|
||||||
|
unsigned char byte = md5[i];
|
||||||
|
unsigned char byteHi = (byte & 0xF0) >> 4;
|
||||||
|
buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi;
|
||||||
|
unsigned char byteLo = byte & 0x0F;
|
||||||
|
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
|
||||||
|
}
|
||||||
|
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
|
||||||
|
return [NSString stringWithUTF8String:buffer];
|
||||||
|
}
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSStr
|
|||||||
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
||||||
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
||||||
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
||||||
|
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||||
|
|
||||||
@interface GCDWebServerConnection ()
|
@interface GCDWebServerConnection ()
|
||||||
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
||||||
@@ -129,6 +130,9 @@ extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
|||||||
@interface GCDWebServer ()
|
@interface GCDWebServer ()
|
||||||
@property(nonatomic, readonly) NSArray* handlers;
|
@property(nonatomic, readonly) NSArray* handlers;
|
||||||
@property(nonatomic, readonly) NSString* serverName;
|
@property(nonatomic, readonly) NSString* serverName;
|
||||||
|
@property(nonatomic, readonly) NSString* authenticationRealm;
|
||||||
|
@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts;
|
||||||
|
@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts;
|
||||||
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
||||||
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
||||||
- (void)didEndConnection:(GCDWebServerConnection*)connection;
|
- (void)didEndConnection:(GCDWebServerConnection*)connection;
|
||||||
|
|||||||
@@ -228,27 +228,34 @@ static NSData* _dashNewlineData = nil;
|
|||||||
_contentType = nil;
|
_contentType = nil;
|
||||||
ARC_RELEASE(_tmpPath);
|
ARC_RELEASE(_tmpPath);
|
||||||
_tmpPath = nil;
|
_tmpPath = nil;
|
||||||
CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
|
NSString* headers = [[NSString alloc] initWithData:[_parserData subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding];
|
||||||
const char* temp = "GET / HTTP/1.0\r\n";
|
if (headers) {
|
||||||
CFHTTPMessageAppendBytes(message, (const UInt8*)temp, strlen(temp));
|
for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) {
|
||||||
CFHTTPMessageAppendBytes(message, _parserData.bytes, range.location + range.length);
|
NSRange subRange = [header rangeOfString:@":"];
|
||||||
if (CFHTTPMessageIsHeaderComplete(message)) {
|
if (subRange.location != NSNotFound) {
|
||||||
NSString* controlName = nil;
|
NSString* name = [header substringToIndex:subRange.location];
|
||||||
NSString* fileName = nil;
|
NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
NSDictionary* headers = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(message));
|
if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) {
|
||||||
NSString* contentDisposition = GCDWebServerNormalizeHeaderValue([headers objectForKey:@"Content-Disposition"]);
|
_contentType = ARC_RETAIN(GCDWebServerNormalizeHeaderValue(value));
|
||||||
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
|
} else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) {
|
||||||
controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name");
|
NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value);
|
||||||
fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
|
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
|
||||||
|
_controlName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"));
|
||||||
|
_fileName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DNOT_REACHED();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_controlName = [controlName copy];
|
|
||||||
_fileName = [fileName copy];
|
|
||||||
_contentType = ARC_RETAIN(GCDWebServerNormalizeHeaderValue([headers objectForKey:@"Content-Type"]));
|
|
||||||
if (_contentType == nil) {
|
if (_contentType == nil) {
|
||||||
_contentType = @"text/plain";
|
_contentType = @"text/plain";
|
||||||
}
|
}
|
||||||
|
ARC_RELEASE(headers);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'");
|
||||||
|
DNOT_REACHED();
|
||||||
}
|
}
|
||||||
CFRelease(message);
|
|
||||||
if (_controlName) {
|
if (_controlName) {
|
||||||
if (_fileName) {
|
if (_fileName) {
|
||||||
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||||
|
|||||||
@@ -112,12 +112,14 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
|||||||
_size = (NSUInteger)info.st_size;
|
_size = (NSUInteger)info.st_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachment) { // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1
|
if (attachment) {
|
||||||
NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
|
NSString* fileName = [path lastPathComponent];
|
||||||
NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
|
NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
|
||||||
if (fileName) {
|
NSString* lossyFileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
|
||||||
[self setValue:[NSString stringWithFormat:@"attachment; filename=\"%@\"", fileName] forAdditionalHeader:@"Content-Disposition"];
|
if (lossyFileName) {
|
||||||
ARC_RELEASE(fileName);
|
NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, GCDWebServerEscapeURLString(fileName)];
|
||||||
|
[self setValue:value forAdditionalHeader:@"Content-Disposition"];
|
||||||
|
ARC_RELEASE(lossyFileName);
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
DNOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|||||||
34
Mac/main.m
34
Mac/main.m
@@ -130,9 +130,13 @@ int main(int argc, const char* argv[]) {
|
|||||||
BOOL recording = NO;
|
BOOL recording = NO;
|
||||||
NSString* rootDirectory = NSHomeDirectory();
|
NSString* rootDirectory = NSHomeDirectory();
|
||||||
NSString* testDirectory = nil;
|
NSString* testDirectory = nil;
|
||||||
|
NSString* authenticationMethod = nil;
|
||||||
|
NSString* authenticationRealm = nil;
|
||||||
|
NSString* authenticationUser = nil;
|
||||||
|
NSString* authenticationPassword = nil;
|
||||||
|
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory]\n\n", basename((char*)argv[0]));
|
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]));
|
||||||
} else {
|
} else {
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
if (argv[i][0] != '-') {
|
if (argv[i][0] != '-') {
|
||||||
@@ -161,6 +165,18 @@ int main(int argc, const char* argv[]) {
|
|||||||
} else if (!strcmp(argv[i], "-tests") && (i + 1 < argc)) {
|
} else if (!strcmp(argv[i], "-tests") && (i + 1 < argc)) {
|
||||||
++i;
|
++i;
|
||||||
testDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
|
testDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
|
||||||
|
} else if (!strcmp(argv[i], "-authenticationMethod") && (i + 1 < argc)) {
|
||||||
|
++i;
|
||||||
|
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
|
||||||
|
} else if (!strcmp(argv[i], "-authenticationRealm") && (i + 1 < argc)) {
|
||||||
|
++i;
|
||||||
|
authenticationRealm = [NSString stringWithUTF8String:argv[i]];
|
||||||
|
} else if (!strcmp(argv[i], "-authenticationUser") && (i + 1 < argc)) {
|
||||||
|
++i;
|
||||||
|
authenticationUser = [NSString stringWithUTF8String:argv[i]];
|
||||||
|
} else if (!strcmp(argv[i], "-authenticationPassword") && (i + 1 < argc)) {
|
||||||
|
++i;
|
||||||
|
authenticationPassword = [NSString stringWithUTF8String:argv[i]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,14 +290,26 @@ int main(int argc, const char* argv[]) {
|
|||||||
webServer.delegate = delegate;
|
webServer.delegate = delegate;
|
||||||
if (testDirectory) {
|
if (testDirectory) {
|
||||||
fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]);
|
fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]);
|
||||||
result = (int)[webServer runTestsInDirectory:testDirectory withPort:8080];
|
result = (int)[webServer runTestsWithOptions:@{GCDWebServerOption_Port: @8080} inDirectory:testDirectory];
|
||||||
} else {
|
} else {
|
||||||
if (recording) {
|
if (recording) {
|
||||||
fprintf(stdout, "<RECORDING ENABLED>\n");
|
fprintf(stdout, "<RECORDING ENABLED>\n");
|
||||||
webServer.recordingEnabled = YES;
|
webServer.recordingEnabled = YES;
|
||||||
}
|
}
|
||||||
fprintf(stdout, "\n");
|
fprintf(stdout, "\n");
|
||||||
if ([webServer runWithPort:8080]) {
|
NSMutableDictionary* options = [NSMutableDictionary dictionary];
|
||||||
|
[options setObject:@8080 forKey:GCDWebServerOption_Port];
|
||||||
|
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
|
||||||
|
if (authenticationUser && authenticationPassword) {
|
||||||
|
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
|
||||||
|
[options setObject:@{authenticationUser: authenticationPassword} forKey:GCDWebServerOption_AuthenticationAccounts];
|
||||||
|
if ([authenticationMethod isEqualToString:@"Basic"]) {
|
||||||
|
[options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod];
|
||||||
|
} else if ([authenticationMethod isEqualToString:@"Digest"]) {
|
||||||
|
[options setObject:GCDWebServerAuthenticationMethod_DigestAccess forKey:GCDWebServerOption_AuthenticationMethod];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ([webServer runWithOptions:options]) {
|
||||||
result = 0;
|
result = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -17,15 +17,13 @@ Extra built-in features:
|
|||||||
* [Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) for request and response HTTP bodies
|
* [Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) for request and response HTTP bodies
|
||||||
* [HTTP compression](https://en.wikipedia.org/wiki/HTTP_compression) with gzip for request and response HTTP bodies
|
* [HTTP compression](https://en.wikipedia.org/wiki/HTTP_compression) with gzip for request and response HTTP bodies
|
||||||
* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files
|
* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files
|
||||||
|
* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection
|
||||||
|
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
|
||||||
|
|
||||||
Included extensions:
|
Included extensions:
|
||||||
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files from an iOS app's sandbox using a web browser
|
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
|
||||||
* [GCDWebDAVServer](GCDWebDAVServer/GCDWebDAVServer.h): subclass of ```GCDWebServer``` that implements a class 1 [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server (with partial class 2 support for OS X Finder)
|
* [GCDWebDAVServer](GCDWebDAVServer/GCDWebDAVServer.h): subclass of ```GCDWebServer``` that implements a class 1 [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server (with partial class 2 support for OS X Finder)
|
||||||
|
|
||||||
What's not available out of the box but can be implemented on top of the API:
|
|
||||||
* Authentication like [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
|
||||||
* Web forms submitted using "multipart/mixed"
|
|
||||||
|
|
||||||
What's not supported (but not really required from an embedded HTTP server):
|
What's not supported (but not really required from an embedded HTTP server):
|
||||||
* Keep-alive connections
|
* Keep-alive connections
|
||||||
* HTTPS
|
* HTTPS
|
||||||
@@ -37,7 +35,7 @@ Requirements:
|
|||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Download or checkout the source for GCDWebServer then add the entire "GCDWebServer" subfolder to your Xcode project. If you intend to use one of the extensions like GCDWebDAVServer or GCDWebUploader, add these subfolders as well.
|
Download or check out the [latest release](https://github.com/swisspol/GCDWebServer/releases) of GCDWebServer then add the entire "GCDWebServer" subfolder to your Xcode project. If you intend to use one of the extensions like GCDWebDAVServer or GCDWebUploader, add these subfolders as well.
|
||||||
|
|
||||||
Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Xcode project's Podfile:
|
Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Xcode project's Podfile:
|
||||||
```
|
```
|
||||||
@@ -55,11 +53,12 @@ pod "GCDWebServer/WebDAV", "~> 2.0"
|
|||||||
Hello World
|
Hello World
|
||||||
===========
|
===========
|
||||||
|
|
||||||
These codes snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code.
|
These code snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code.
|
||||||
|
|
||||||
**OS X version (command line tool):**
|
**OS X version (command line tool):**
|
||||||
```objectivec
|
```objectivec
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
|
#import "GCDWebServerDataResponse.h"
|
||||||
|
|
||||||
int main(int argc, const char* argv[]) {
|
int main(int argc, const char* argv[]) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
@@ -87,11 +86,12 @@ int main(int argc, const char* argv[]) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**iOS Version:**
|
**iOS version:**
|
||||||
```objectivec
|
```objectivec
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
|
#import "GCDWebServerDataResponse.h"
|
||||||
|
|
||||||
static GCDWebServer* _webServer = nil; // This should really be an ivar of your application delegate class
|
static GCDWebServer* _webServer = nil; // This should really be an ivar of your application's delegate class
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
|
|
||||||
@@ -119,12 +119,12 @@ Web Based Uploads in iOS Apps
|
|||||||
|
|
||||||
GCDWebUploader is a subclass of ```GCDWebServer``` that provides a ready-to-use HTML 5 file uploader & downloader. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using a clean user interface in their web browser.
|
GCDWebUploader is a subclass of ```GCDWebServer``` that provides a ready-to-use HTML 5 file uploader & downloader. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using a clean user interface in their web browser.
|
||||||
|
|
||||||
Simply instantiate and run a GCDWebUploader instance then visit http://{YOUR-IOS-DEVICE-IP-ADDRESS}/ from your web browser:
|
Simply instantiate and run a ```GCDWebUploader``` instance then visit ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` from your web browser:
|
||||||
|
|
||||||
```objectivec
|
```objectivec
|
||||||
#import "GCDWebUploader.h"
|
#import "GCDWebUploader.h"
|
||||||
|
|
||||||
static GCDWebUploader* _webUploader = nil; // This should really be an ivar of your application delegate class
|
static GCDWebUploader* _webUploader = nil; // This should really be an ivar of your application's delegate class
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||||
@@ -142,12 +142,12 @@ GCDWebDAVServer is a subclass of ```GCDWebServer``` that provides a class 1 comp
|
|||||||
|
|
||||||
GCDWebDAVServer should also work with the [OS X Finder](http://support.apple.com/kb/PH13859) as it is partially class 2 compliant (but only when the client is the OS X WebDAV implementation).
|
GCDWebDAVServer should also work with the [OS X Finder](http://support.apple.com/kb/PH13859) as it is partially class 2 compliant (but only when the client is the OS X WebDAV implementation).
|
||||||
|
|
||||||
Simply instantiate and run a GCDWebDAVServer instance then connect to http://{YOUR-IOS-DEVICE-IP-ADDRESS}/ using a WebDAV client:
|
Simply instantiate and run a ```GCDWebDAVServer``` instance then connect to ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` using a WebDAV client:
|
||||||
|
|
||||||
```objectivec
|
```objectivec
|
||||||
#import "GCDWebDAVServer.h"
|
#import "GCDWebDAVServer.h"
|
||||||
|
|
||||||
static GCDWebDAVServer* _davServer = nil; // This should really be an ivar of your application delegate class
|
static GCDWebDAVServer* _davServer = nil; // This should really be an ivar of your application's delegate class
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||||
@@ -211,7 +211,7 @@ Note that most methods on ```GCDWebServer``` to add handlers only require the ``
|
|||||||
GCDWebServer & Background Mode for iOS Apps
|
GCDWebServer & Background Mode for iOS Apps
|
||||||
===========================================
|
===========================================
|
||||||
|
|
||||||
When doing networking operations in iOS apps, you must handle carefully [what happens when iOS puts the app in the background](https://developer.apple.com/library/ios/technotes/tn2277/_index.html). Typically you must stop any server while the app is in the background and restart them when it comes back to the foreground. This can become quite complex considering the server might have ongoing connections when it needs to be stopped.
|
When doing networking operations in iOS apps, you must handle carefully [what happens when iOS puts the app in the background](https://developer.apple.com/library/ios/technotes/tn2277/_index.html). Typically you must stop any network servers while the app is in the background and restart them when the app comes back to the foreground. This can become quite complex considering servers might have ongoing connections when they need to be stopped.
|
||||||
|
|
||||||
Fortunately, GCDWebServer does all of this automatically for you:
|
Fortunately, GCDWebServer does all of this automatically for you:
|
||||||
- GCDWebServer begins a [background task](https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html) whenever the first HTTP connection is opened and ends it only when the last one is closed. This prevents iOS from suspending the app after it goes in the background, which would immediately kill HTTP connections to the client.
|
- GCDWebServer begins a [background task](https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html) whenever the first HTTP connection is opened and ends it only when the last one is closed. This prevents iOS from suspending the app after it goes in the background, which would immediately kill HTTP connections to the client.
|
||||||
@@ -320,4 +320,4 @@ Final Example: File Downloads and Uploads From iOS App
|
|||||||
|
|
||||||
GCDWebServer was originally written for the [ComicFlow](http://itunes.apple.com/us/app/comicflow/id409290355?mt=8) comic reader app for iPad. It allow users to connect to their iPad with their web browser over WiFi and then upload, download and organize comic files inside the app.
|
GCDWebServer was originally written for the [ComicFlow](http://itunes.apple.com/us/app/comicflow/id409290355?mt=8) comic reader app for iPad. It allow users to connect to their iPad with their web browser over WiFi and then upload, download and organize comic files inside the app.
|
||||||
|
|
||||||
ComicFlow is [entirely open-source](https://github.com/swisspol/ComicFlow) and you can see how it uses GCDWebUploader in the [WebServer.m](https://github.com/swisspol/ComicFlow/blob/master/Classes/WebServer.m) file.
|
ComicFlow is [entirely open-source](https://github.com/swisspol/ComicFlow) and you can see how it uses GCDWebServer in the [WebServer.h](https://github.com/swisspol/ComicFlow/blob/master/Classes/WebServer.h) and [WebServer.m](https://github.com/swisspol/ComicFlow/blob/master/Classes/WebServer.m) files.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Server: GCDWebUploader
|
|||||||
Content-Type: text/plain
|
Content-Type: text/plain
|
||||||
Last-Modified: Thu, 10 Apr 2014 11:10:14 GMT
|
Last-Modified: Thu, 10 Apr 2014 11:10:14 GMT
|
||||||
Date: Sat, 12 Apr 2014 05:36:17 GMT
|
Date: Sat, 12 Apr 2014 05:36:17 GMT
|
||||||
Content-Disposition: attachment; filename="Copy.txt"
|
Content-Disposition: attachment; filename="Copy.txt"; filename*=UTF-8''Copy.txt
|
||||||
Content-Length: 271
|
Content-Length: 271
|
||||||
Cache-Control: no-cache
|
Cache-Control: no-cache
|
||||||
Etag: 73598983/1397128214/0
|
Etag: 73598983/1397128214/0
|
||||||
|
|||||||
Reference in New Issue
Block a user