38 Commits
2.4 ... 2.5.2

Author SHA1 Message Date
Pierre-Olivier Latour
a5d83abdd0 Bumped version to 2.5.2 2014-09-21 10:35:46 -07:00
Pierre-Olivier Latour
f1e9f1a37c Improved handling of symbolic links in -addGETHandlerForBasePath:directoryPath:indexFilename:cacheAge:allowRangeRequests: 2014-09-19 08:19:49 -07:00
Pierre-Olivier Latour
00b5ec87ba #76 Fixed -bonjourServerURL to correctly return hostname instead of service name 2014-09-15 09:00:39 -07:00
Pierre-Olivier Latour
cf94e70a42 Fall back to CFBundleName if CFBundleDisplayName is not available 2014-09-15 08:05:07 -07:00
Pierre-Olivier Latour
d47409c776 Updated for Xcode6 2014-09-15 08:05:00 -07:00
Pierre-Olivier Latour
a9db13475b Bumped version 2014-08-24 12:28:51 -07:00
Pierre-Olivier Latour
17fad0f1b9 Improved automatic detection of when to use dispatch_retain() and dispatch_release() depending on compiler settings 2014-08-24 12:24:16 -07:00
Pierre-Olivier Latour
5493d9e803 Run test against default and oldest supported deployment targets 2014-08-24 12:20:55 -07:00
Pierre-Olivier Latour
12b1edb958 #70 Ensure -isRunning works as expected even if GCDWebServerOption_AutomaticallySuspendInBackground is enabled 2014-07-23 07:40:37 -07:00
Pierre-Olivier Latour
7544a6dc4e Update 2014-07-12 17:11:26 -07:00
Pierre-Olivier Latour
f9621c8aac Bumped version 2014-07-12 16:55:31 -07:00
Pierre-Olivier Latour
7a93b27478 Update README.md 2014-07-03 19:54:25 -07:00
Pierre-Olivier Latour
9d48f9ec12 #57 Validate paths passed to GCDWebDAVServer and GCDWebUploader to ensure they are within the upload directory 2014-06-22 15:53:03 -07:00
Pierre-Olivier Latour
7c6e85cf9a Merge pull request #60 from pvblivs/master
Adding instructions for Swift command line tool
2014-06-22 15:36:21 -07:00
pvblivs
24fbd161d8 Update README.md 2014-06-10 08:28:59 +02:00
pvblivs
0ae0d4175a Adding instructions for Swift command line tool 2014-06-06 16:34:26 +02:00
Pierre-Olivier Latour
6d550a02b7 Merge pull request #55 from mstarinteractive/fix-json-content-type
Fix content-types like "application/json; charset=utf-8"
2014-05-16 12:17:19 -07:00
Braden MacDonald
a7f46b762f Fix content-types like "application/json; charset=utf-8" 2014-05-16 11:55:20 -07:00
Pierre-Olivier Latour
d1c7f9a323 Merge pull request #56 from jaanus/customBonjourType
Can specify a custom Bonjour service type for the server.
2014-05-16 08:31:21 -07:00
jaanus
93bfe65211 Removed unneeded API. If custom Bonjour type is needed, startWithOptions should be used to specify an options dictionary with the custom type. 2014-05-16 17:12:52 +02:00
jaanus
8ab53f74d5 Can specify a custom Bonjour service type for the server. 2014-05-16 11:20:28 +02:00
Pierre-Olivier Latour
04a69787bf Update README.md 2014-05-05 13:43:38 -07:00
Pierre-Olivier Latour
dfd019de7d Update README.md 2014-05-05 13:18:04 -07:00
Pierre-Olivier Latour
4db631fa27 Fixed errno being corrupted by LOG_ERROR() 2014-05-02 22:17:25 -07:00
Pierre-Olivier Latour
ba03d756c6 Merge pull request #52 from tipbit/fix-empty-query-param
Fix GCDWebServerParseURLEncodedForm to allow empty values.
2014-04-30 17:22:56 -07:00
Ewan Mellor
04f59a9214 Fix GCDWebServerParseURLEncodedForm to allow empty values.
If the input string is something like foo=&bar= then both foo and bar
should be in the result, with the empty string as their corresponding value.

The [scanner scanUpToString:@"&" ...] call was returning failure, because
it was already positioned at the &.  In this situation, just set value to the
empty string.
2014-04-30 17:08:42 -07:00
Pierre-Olivier Latour
40ea252ad6 Ensure connected state is updated immediately after calling -stop 2014-04-30 14:04:46 -07:00
Pierre-Olivier Latour
c193860468 Fix 2014-04-30 13:59:26 -07:00
Pierre-Olivier Latour
94ad8c745e No need to call -stop from -dealloc 2014-04-30 13:36:06 -07:00
Pierre-Olivier Latour
56c096996f Fix 2014-04-30 13:06:02 -07:00
Pierre-Olivier Latour
8cbaf0f867 Added video streaming unit test 2014-04-30 11:31:20 -07:00
Pierre-Olivier Latour
295901c0b3 Reject files greater than 4 GiB in 32 bit mode 2014-04-30 09:57:12 -07:00
Pierre-Olivier Latour
2dda0c98ce #50 Use NSUIntegerMax instead of NSNotFound to indicate undefined length 2014-04-30 09:56:18 -07:00
Pierre-Olivier Latour
70a38c8b01 Fix 2014-04-30 09:15:30 -07:00
Pierre-Olivier Latour
1b12a7bd14 Ensure pending scheduled callbacks have executed in "run" APIs 2014-04-29 22:12:28 -07:00
Pierre-Olivier Latour
75e6332500 Don't delay disconnected state update if already stopped 2014-04-29 22:06:13 -07:00
Pierre-Olivier Latour
420ed719e8 Add SIGTERM support to -runWithOptions:error: 2014-04-29 16:20:03 -07:00
Pierre-Olivier Latour
3b75f9dd20 Added -rewriteRequestURL:withMethod:headers: hook 2014-04-28 14:09:15 -07:00
31 changed files with 445 additions and 142 deletions

View File

@@ -61,6 +61,11 @@ typedef NS_ENUM(NSInteger, DAVProperties) {
@implementation GCDWebDAVServer (Methods) @implementation GCDWebDAVServer (Methods)
// Must match implementation in GCDWebUploader
- (BOOL)_checkSandboxedPath:(NSString*)path {
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
}
- (BOOL)_checkFileExtension:(NSString*)fileName { - (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) { if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO; return NO;
@@ -87,7 +92,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![absolutePath hasPrefix:_uploadDirectory] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
@@ -116,7 +121,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
if (![absolutePath hasPrefix:_uploadDirectory]) { if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
BOOL isDirectory; BOOL isDirectory;
@@ -161,7 +166,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![absolutePath hasPrefix:_uploadDirectory] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
@@ -194,7 +199,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
if (![absolutePath hasPrefix:_uploadDirectory]) { if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
BOOL isDirectory; BOOL isDirectory;
@@ -243,7 +248,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* srcRelativePath = request.path; NSString* srcRelativePath = request.path;
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:srcRelativePath]; NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:srcRelativePath];
if (![srcAbsolutePath hasPrefix:_uploadDirectory]) { if (![self _checkSandboxedPath:srcAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
} }
@@ -254,7 +259,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
} }
dstRelativePath = [[dstRelativePath substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; dstRelativePath = [[dstRelativePath substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:dstRelativePath]; NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:dstRelativePath];
if (![dstAbsolutePath hasPrefix:_uploadDirectory]) { if (![self _checkSandboxedPath:dstAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
} }
@@ -425,7 +430,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![absolutePath hasPrefix:_uploadDirectory] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
@@ -475,7 +480,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![absolutePath hasPrefix:_uploadDirectory] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
@@ -575,7 +580,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![absolutePath hasPrefix:_uploadDirectory] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }

View File

@@ -1,19 +1,19 @@
# http://guides.cocoapods.org/syntax/podspec.html # http://guides.cocoapods.org/syntax/podspec.html
# Verify Podspec with: # http://guides.cocoapods.org/making/getting-setup-with-trunk.html
# sudo gem update cocoapods # $ sudo gem update cocoapods
# pod spec lint GCDWebServer.podspec --verbose # (optional) $ pod trunk register {email} {name} --description={computer}
# Add to source line: # $ pod trunk push
# :tag => s.version.to_s # DELETE THIS SECTION BEFORE PROCEEDING!
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'GCDWebServer' s.name = 'GCDWebServer'
s.version = '2.4' s.version = '2.5.2'
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'
s.summary = 'Lightweight GCD based HTTP server for OS X & iOS (includes web based uploader & WebDAV server)' s.summary = 'Lightweight GCD based HTTP server for OS X & iOS (includes web based uploader & WebDAV server)'
s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git' } s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git', :tag => s.version.to_s }
s.ios.deployment_target = '5.0' s.ios.deployment_target = '5.0'
s.osx.deployment_target = '10.7' s.osx.deployment_target = '10.7'
s.requires_arc = true s.requires_arc = true

View File

@@ -370,7 +370,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = { 08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0510; LastUpgradeCheck = 0600;
}; };
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */; buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
compatibilityVersion = "Xcode 3.2"; compatibilityVersion = "Xcode 3.2";
@@ -491,7 +491,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD)"; ARCHS = "$(ARCHS_STANDARD)";
MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
SDKROOT = macosx; SDKROOT = macosx;
}; };
@@ -501,7 +500,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD)"; ARCHS = "$(ARCHS_STANDARD)";
MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
SDKROOT = macosx; SDKROOT = macosx;
}; };
@@ -553,7 +551,6 @@
ARCHS = "$(ARCHS_STANDARD)"; ARCHS = "$(ARCHS_STANDARD)";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = iOS/Info.plist; INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 5.1.1;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -567,7 +564,6 @@
ARCHS = "$(ARCHS_STANDARD)"; ARCHS = "$(ARCHS_STANDARD)";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = iOS/Info.plist; INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 5.1.1;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;

View File

@@ -77,12 +77,21 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
extern NSString* const GCDWebServerOption_Port; extern NSString* const GCDWebServerOption_Port;
/** /**
* The Bonjour name used by the GCDWebServer (NSString). * The Bonjour name used by the GCDWebServer (NSString). If set to an empty string,
* the name will automatically take the value of the GCDWebServerOption_ServerName
* option. If this option is set to nil, Bonjour will be disabled.
* *
* The default value is an empty string i.e. use the computer / device name. * The default value is an empty string.
*/ */
extern NSString* const GCDWebServerOption_BonjourName; extern NSString* const GCDWebServerOption_BonjourName;
/**
* The Bonjour service type used by the GCDWebServer (NSString).
*
* The default value is "_http._tcp", the service type for HTTP web servers.
*/
extern NSString* const GCDWebServerOption_BonjourType;
/** /**
* The maximum number of incoming HTTP requests that can be queued waiting to * The maximum number of incoming HTTP requests that can be queued waiting to
* be handled before new ones are dropped (NSNumber / NSUInteger). * be handled before new ones are dropped (NSNumber / NSUInteger).
@@ -233,6 +242,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* then passes each one to a "handler" capable of generating an HTTP response * then passes each one to a "handler" capable of generating an HTTP response
* for it, which is then sent back to the client. * for it, which is then sent back to the client.
* *
* GCDWebServer instances can be created and used from any thread but it's
* recommended to have the main thread's runloop be running so internal callbacks
* can be handled e.g. for Bonjour registration.
*
* See the README.md file for more information about the architecture of GCDWebServer. * See the README.md file for more information about the architecture of GCDWebServer.
*/ */
@interface GCDWebServer : NSObject @interface GCDWebServer : NSObject
@@ -262,6 +275,14 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*/ */
@property(nonatomic, readonly) NSString* bonjourName; @property(nonatomic, readonly) NSString* bonjourName;
/**
* Returns the Bonjour service type 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.
*/
@property(nonatomic, readonly) NSString* bonjourType;
/** /**
* This method is the designated initializer for the class. * This method is the designated initializer for the class.
*/ */
@@ -317,12 +338,14 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* @warning This property is only valid if the server is running and Bonjour * @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. * registration has successfully completed, which can take up to a few seconds.
* Also be aware this property will not automatically update if the Bonjour hostname
* has been dynamically changed after the server started running (this should be rare).
*/ */
@property(nonatomic, readonly) NSURL* bonjourServerURL; @property(nonatomic, readonly) NSURL* bonjourServerURL;
/** /**
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS) * Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
* using the computer / device name for as the Bonjour name. * using the default Bonjour name.
* *
* Returns NO if the server failed to start. * Returns NO if the server failed to start.
*/ */
@@ -331,7 +354,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/** /**
* Starts the server on a given port and with a specific Bonjour name. * 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 * Pass a nil Bonjour name to disable Bonjour entirely or an empty string to
* use the computer / device name. * use the default name.
* *
* Returns NO if the server failed to start. * Returns NO if the server failed to start.
*/ */
@@ -351,9 +374,9 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name; - (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name;
/** /**
* Runs the server synchronously using -startWithOptions: until a SIGINT signal * Runs the server synchronously using -startWithOptions: until a SIGTERM or
* is received i.e. Ctrl-C. This method is intended to be used by command line * SIGINT signal is received i.e. Ctrl-C in Terminal. This method is intended to
* tools. * be used by command line tools.
* *
* Returns NO if the server failed to start and sets "error" argument if not NULL. * Returns NO if the server failed to start and sets "error" argument if not NULL.
* *

View File

@@ -45,6 +45,7 @@
NSString* const GCDWebServerOption_Port = @"Port"; NSString* const GCDWebServerOption_Port = @"Port";
NSString* const GCDWebServerOption_BonjourName = @"BonjourName"; NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
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_AuthenticationMethod = @"AuthenticationMethod";
@@ -95,6 +96,21 @@ static void _SignalHandler(int signal) {
#endif #endif
#if !TARGET_OS_IPHONE || defined(__GCDWEBSERVER_ENABLE_TESTING__)
// This utility function is used to ensure scheduled callbacks on the main thread are called when running the server synchronously
// https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
// The main queue works with the applications run loop to interleave the execution of queued tasks with the execution of other event sources attached to the run loop
// TODO: Ensure all scheduled blocks on the main queue are also executed
static void _ExecuteMainThreadRunLoopSources() {
SInt32 result;
do {
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
} while (result == kCFRunLoopRunHandledSource);
}
#endif
@interface GCDWebServerHandler () { @interface GCDWebServerHandler () {
@private @private
GCDWebServerMatchBlock _matchBlock; GCDWebServerMatchBlock _matchBlock;
@@ -129,9 +145,10 @@ static void _SignalHandler(int signal) {
dispatch_queue_t _syncQueue; dispatch_queue_t _syncQueue;
dispatch_semaphore_t _sourceSemaphore; dispatch_semaphore_t _sourceSemaphore;
NSMutableArray* _handlers; NSMutableArray* _handlers;
NSInteger _activeConnections; // Accessed only with _syncQueue NSInteger _activeConnections; // Accessed through _syncQueue only
BOOL _connected; BOOL _connected; // Accessed on main thread only
CFRunLoopTimerRef _connectedTimer; BOOL _disconnecting; // Accessed on main thread only
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
NSDictionary* _options; NSDictionary* _options;
NSString* _serverName; NSString* _serverName;
@@ -143,7 +160,8 @@ static void _SignalHandler(int signal) {
CFTimeInterval _disconnectDelay; CFTimeInterval _disconnectDelay;
NSUInteger _port; NSUInteger _port;
dispatch_source_t _source; dispatch_source_t _source;
CFNetServiceRef _service; CFNetServiceRef _registrationService;
CFNetServiceRef _resolutionService;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
BOOL _suspendInBackground; BOOL _suspendInBackground;
UIBackgroundTaskIdentifier _backgroundTask; UIBackgroundTaskIdentifier _backgroundTask;
@@ -175,10 +193,13 @@ static void _SignalHandler(int signal) {
GCDWebServerInitializeFunctions(); GCDWebServerInitializeFunctions();
} }
static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
DCHECK([NSThread isMainThread]);
GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info;
@autoreleasepool { @autoreleasepool {
[(ARC_BRIDGE GCDWebServer*)info _didDisconnect]; [server _didDisconnect];
} }
server->_disconnecting = NO;
} }
- (instancetype)init { - (instancetype)init {
@@ -187,8 +208,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
_sourceSemaphore = dispatch_semaphore_create(0); _sourceSemaphore = dispatch_semaphore_create(0);
_handlers = [[NSMutableArray alloc] init]; _handlers = [[NSMutableArray alloc] init];
CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
_connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context); _disconnectTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _DisconnectTimerCallBack, &context);
CFRunLoopAddTimer(CFRunLoopGetMain(), _connectedTimer, kCFRunLoopCommonModes); CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes);
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
_backgroundTask = UIBackgroundTaskInvalid; _backgroundTask = UIBackgroundTaskInvalid;
#endif #endif
@@ -199,14 +220,10 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
- (void)dealloc { - (void)dealloc {
DCHECK(_connected == NO); DCHECK(_connected == NO);
DCHECK(_activeConnections == 0); DCHECK(_activeConnections == 0);
DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source
_delegate = nil; CFRunLoopTimerInvalidate(_disconnectTimer);
if (_options) { CFRelease(_disconnectTimer);
[self stop];
}
CFRunLoopTimerInvalidate(_connectedTimer);
CFRelease(_connectedTimer);
ARC_RELEASE(_handlers); ARC_RELEASE(_handlers);
ARC_DISPATCH_RELEASE(_sourceSemaphore); ARC_DISPATCH_RELEASE(_sourceSemaphore);
ARC_DISPATCH_RELEASE(_syncQueue); ARC_DISPATCH_RELEASE(_syncQueue);
@@ -256,8 +273,9 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
DCHECK(_activeConnections >= 0); DCHECK(_activeConnections >= 0);
if (_activeConnections == 0) { if (_activeConnections == 0) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (_disconnectDelay > 0.0) { if (_disconnecting) {
CFRunLoopTimerSetNextFireDate(_connectedTimer, HUGE_VAL); CFRunLoopTimerSetNextFireDate(_disconnectTimer, HUGE_VAL);
_disconnecting = NO;
} }
if (_connected == NO) { if (_connected == NO) {
[self _didConnect]; [self _didConnect];
@@ -310,8 +328,9 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
_activeConnections -= 1; _activeConnections -= 1;
if (_activeConnections == 0) { if (_activeConnections == 0) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (_disconnectDelay > 0.0) { if ((_disconnectDelay > 0.0) && (_source != NULL)) {
CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay); CFRunLoopTimerSetNextFireDate(_disconnectTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay);
_disconnecting = YES;
} else { } else {
[self _didDisconnect]; [self _didDisconnect];
} }
@@ -321,10 +340,15 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
} }
- (NSString*)bonjourName { - (NSString*)bonjourName {
CFStringRef name = _service ? CFNetServiceGetName(_service) : NULL; CFStringRef name = _resolutionService ? CFNetServiceGetName(_resolutionService) : NULL;
return name && CFStringGetLength(name) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil; return name && CFStringGetLength(name) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil;
} }
- (NSString*)bonjourType {
CFStringRef type = _resolutionService ? CFNetServiceGetType(_resolutionService) : NULL;
return type && CFStringGetLength(type) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil;
}
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock { - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
DCHECK(_options == nil); DCHECK(_options == nil);
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock]; GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock];
@@ -337,11 +361,26 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
[_handlers removeAllObjects]; [_handlers removeAllObjects];
} }
static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
DCHECK([NSThread isMainThread]); DCHECK([NSThread isMainThread]);
@autoreleasepool { @autoreleasepool {
if (error->error) { if (error->error) {
LOG_ERROR(@"Bonjour error %i (domain %i)", (int)error->error, (int)error->domain); LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain);
} else {
GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info;
LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
CFNetServiceResolveWithTimeout(server->_resolutionService, 1.0, NULL);
}
}
}
static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
DCHECK([NSThread isMainThread]);
@autoreleasepool {
if (error->error) {
if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) {
LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain);
}
} else { } else {
GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info; GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info;
LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL); LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
@@ -369,8 +408,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (BOOL)_start:(NSError**)error { - (BOOL)_start:(NSError**)error {
DCHECK(_source == NULL); DCHECK(_source == NULL);
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
NSString* name = _GetOption(_options, GCDWebServerOption_BonjourName, @""); NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listeningSocket > 0) { if (listeningSocket > 0) {
@@ -467,14 +508,21 @@ static inline NSString* _EncodeBase64(NSString* string) {
_port = port; _port = port;
} }
if (name) { if (bonjourName) {
_service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (ARC_BRIDGE CFStringRef)name, (SInt32)_port); _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
if (_service) { if (_registrationService) {
CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
CFNetServiceScheduleWithRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes); CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFStreamError streamError = {0}; CFStreamError streamError = {0};
CFNetServiceRegisterWithOptions(_service, 0, &streamError); CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
if (_resolutionService) {
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
} else { } else {
LOG_ERROR(@"Failed creating CFNetService"); LOG_ERROR(@"Failed creating CFNetService");
} }
@@ -488,24 +536,24 @@ static inline NSString* _EncodeBase64(NSString* string) {
}); });
} }
} else { } else {
LOG_ERROR(@"Failed starting listening socket: %s (%i)", strerror(errno), errno);
if (error) { if (error) {
*error = GCDWebServerMakePosixError(errno); *error = GCDWebServerMakePosixError(errno);
} }
LOG_ERROR(@"Failed starting listening socket: %s (%i)", strerror(errno), errno);
close(listeningSocket); close(listeningSocket);
} }
} else { } else {
LOG_ERROR(@"Failed binding listening socket: %s (%i)", strerror(errno), errno);
if (error) { if (error) {
*error = GCDWebServerMakePosixError(errno); *error = GCDWebServerMakePosixError(errno);
} }
LOG_ERROR(@"Failed binding listening socket: %s (%i)", strerror(errno), errno);
close(listeningSocket); close(listeningSocket);
} }
} else { } else {
LOG_ERROR(@"Failed creating listening socket: %s (%i)", strerror(errno), errno);
if (error) { if (error) {
*error = GCDWebServerMakePosixError(errno); *error = GCDWebServerMakePosixError(errno);
} }
LOG_ERROR(@"Failed creating listening socket: %s (%i)", strerror(errno), errno);
} }
return (_source ? YES : NO); return (_source ? YES : NO);
} }
@@ -513,11 +561,19 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (void)_stop { - (void)_stop {
DCHECK(_source != NULL); DCHECK(_source != NULL);
if (_service) { if (_registrationService) {
CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes); if (_resolutionService) {
CFNetServiceSetClient(_service, NULL, NULL); CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFRelease(_service); CFNetServiceSetClient(_resolutionService, NULL, NULL);
_service = NULL; CFNetServiceCancel(_resolutionService);
CFRelease(_resolutionService);
_resolutionService = NULL;
}
CFNetServiceUnscheduleFromRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFNetServiceSetClient(_registrationService, NULL, NULL);
CFNetServiceCancel(_registrationService);
CFRelease(_registrationService);
_registrationService = NULL;
} }
dispatch_source_cancel(_source); dispatch_source_cancel(_source);
@@ -535,6 +591,14 @@ static inline NSString* _EncodeBase64(NSString* string) {
ARC_RELEASE(_authenticationDigestAccounts); ARC_RELEASE(_authenticationDigestAccounts);
_authenticationDigestAccounts = nil; _authenticationDigestAccounts = nil;
dispatch_async(dispatch_get_main_queue(), ^{
if (_disconnecting) {
CFRunLoopTimerSetNextFireDate(_disconnectTimer, HUGE_VAL);
_disconnecting = NO;
[self _didDisconnect];
}
});
LOG_INFO(@"%@ stopped", [self class]); LOG_INFO(@"%@ stopped", [self class]);
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -591,7 +655,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
} }
- (BOOL)isRunning { - (BOOL)isRunning {
return (_source ? YES : NO); return (_options ? YES : NO);
} }
- (void)stop { - (void)stop {
@@ -631,13 +695,14 @@ static inline NSString* _EncodeBase64(NSString* string) {
} }
- (NSURL*)bonjourServerURL { - (NSURL*)bonjourServerURL {
if (_source && _service) { if (_source && _resolutionService) {
CFStringRef name = CFNetServiceGetName(_service); NSString* name = (ARC_BRIDGE NSString*)CFNetServiceGetTargetHost(_resolutionService);
if (name && CFStringGetLength(name)) { if (name.length) {
name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain
if (_port != 80) { if (_port != 80) {
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@.local:%i/", name, (int)_port]]; return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", name, (int)_port]];
} else { } else {
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@.local/", name]]; return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", name]];
} }
} }
} }
@@ -668,8 +733,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
DCHECK([NSThread isMainThread]); DCHECK([NSThread isMainThread]);
BOOL success = NO; BOOL success = NO;
_run = YES; _run = YES;
void (*handler)(int) = signal(SIGINT, _SignalHandler); void (*termHandler)(int) = signal(SIGTERM, _SignalHandler);
if (handler != SIG_ERR) { void (*intHandler)(int) = signal(SIGINT, _SignalHandler);
if ((termHandler != SIG_ERR) && (intHandler != SIG_ERR)) {
if ([self startWithOptions:options error:error]) { if ([self startWithOptions:options error:error]) {
while (_run) { while (_run) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
@@ -677,7 +743,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
[self stop]; [self stop];
success = YES; success = YES;
} }
signal(SIGINT, handler); _ExecuteMainThreadRunLoopSources();
signal(SIGINT, intHandler);
signal(SIGTERM, termHandler);
} }
return success; return success;
} }
@@ -814,17 +882,18 @@ static inline NSString* _EncodeBase64(NSString* string) {
GCDWebServerResponse* response = nil; GCDWebServerResponse* response = nil;
NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]]; NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
BOOL isDirectory; NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) { if (fileType) {
if (isDirectory) { if ([fileType isEqualToString:NSFileTypeDirectory]) {
if (indexFilename) { if (indexFilename) {
NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename]; NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) { NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType];
if ([indexType isEqualToString:NSFileTypeRegular]) {
return [GCDWebServerFileResponse responseWithFile:indexPath]; return [GCDWebServerFileResponse responseWithFile:indexPath];
} }
} }
response = [server _responseWithContentsOfDirectory:filePath]; response = [server _responseWithContentsOfDirectory:filePath];
} else { } else if ([fileType isEqualToString:NSFileTypeRegular]) {
if (allowRangeRequests) { if (allowRangeRequests) {
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange]; response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
@@ -930,7 +999,7 @@ static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData,
while (1) { while (1) {
ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length); ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length);
if (result < 0) { if (result < 0) {
length = NSNotFound; length = NSUIntegerMax;
break; break;
} else if (result == 0) { } else if (result == 0) {
break; break;
@@ -940,7 +1009,7 @@ static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData,
outData.length = 2 * outData.length; outData.length = 2 * outData.length;
} }
} }
if (length != NSNotFound) { if (length != NSUIntegerMax) {
outData.length = length; outData.length = length;
response = _CreateHTTPMessageFromData(outData, NO); response = _CreateHTTPMessageFromData(outData, NO);
} else { } else {
@@ -964,9 +1033,11 @@ static void _LogResult(NSString* format, ...) {
} }
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path { - (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
DCHECK([NSThread isMainThread]);
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 startWithOptions:options error:NULL]) { if ([self startWithOptions:options error:NULL]) {
_ExecuteMainThreadRunLoopSources();
result = 0; result = 0;
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL]; NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
@@ -1022,8 +1093,13 @@ static void _LogResult(NSString* format, ...) {
} }
} }
NSString* expectedContentLength = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length")));
NSData* expectedBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(expectedResponse)); NSData* expectedBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(expectedResponse));
NSString* actualContentLength = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length")));
NSData* actualBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(actualResponse)); NSData* actualBody = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyBody(actualResponse));
if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file)
actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)];
}
if (![actualBody isEqualToData:expectedBody]) { if (![actualBody isEqualToData:expectedBody]) {
_LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length); _LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
success = NO; success = NO;
@@ -1064,9 +1140,12 @@ static void _LogResult(NSString* format, ...) {
++result; ++result;
} }
} }
_ExecuteMainThreadRunLoopSources();
} }
[self stop]; [self stop];
_ExecuteMainThreadRunLoopSources();
} }
return result; return result;
} }

View File

@@ -117,6 +117,14 @@
*/ */
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; - (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length;
/**
* This method is called after the HTTP headers have been received to
* allow replacing the request URL by another one.
*
* The default implementation returns the original URL.
*/
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers;
/** /**
* Assuming a valid HTTP request was received, this method is called before * Assuming a valid HTTP request was received, this method is called before
* the request is processed. * the request is processed.

View File

@@ -416,7 +416,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
if (_response.contentType != nil) { if (_response.contentType != nil) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (ARC_BRIDGE CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType)); CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (ARC_BRIDGE CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType));
} }
if (_response.contentLength != NSNotFound) { if (_response.contentLength != NSUIntegerMax) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]); CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
} }
if (_response.usesChunkedTransferEncoding) { if (_response.usesChunkedTransferEncoding) {
@@ -522,11 +522,15 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
requestMethod = @"GET"; requestMethod = @"GET";
_virtualHEAD = YES; _virtualHEAD = YES;
} }
NSDictionary* requestHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
NSURL* requestURL = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(_requestMessage)); NSURL* requestURL = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(_requestMessage));
if (requestURL) {
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
DCHECK(requestURL);
}
NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(ARC_BRIDGE_RELEASE(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(ARC_BRIDGE_RELEASE(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash
NSString* queryString = requestURL ? ARC_BRIDGE_RELEASE(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped; NSString* queryString = requestURL ? ARC_BRIDGE_RELEASE(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{}; NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
NSDictionary* requestHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) { if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
for (_handler in _server.handlers) { for (_handler in _server.handlers) {
_request = ARC_RETAIN(_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery)); _request = ARC_RETAIN(_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery));
@@ -713,6 +717,10 @@ static NSString* _StringFromAddressData(NSData* data) {
#endif #endif
} }
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers {
return url;
}
// https://tools.ietf.org/html/rfc2617 // https://tools.ietf.org/html/rfc2617
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request { - (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); LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);

View File

@@ -198,8 +198,9 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
[scanner setScanLocation:([scanner scanLocation] + 1)]; [scanner setScanLocation:([scanner scanLocation] + 1)];
NSString* value = nil; NSString* value = nil;
if (![scanner scanUpToString:@"&" intoString:&value]) { [scanner scanUpToString:@"&" intoString:&value];
break; if (value == nil) {
value = @"";
} }
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];

View File

@@ -25,8 +25,7 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#import <TargetConditionals.h> #import <os/object.h>
#import <AvailabilityMacros.h>
#if __has_feature(objc_arc) #if __has_feature(objc_arc)
#define ARC_BRIDGE __bridge #define ARC_BRIDGE __bridge
@@ -35,7 +34,7 @@
#define ARC_RELEASE(__OBJECT__) #define ARC_RELEASE(__OBJECT__)
#define ARC_AUTORELEASE(__OBJECT__) __OBJECT__ #define ARC_AUTORELEASE(__OBJECT__) __OBJECT__
#define ARC_DEALLOC(__OBJECT__) #define ARC_DEALLOC(__OBJECT__)
#if (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_6_0)) || (!TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8)) #if OS_OBJECT_USE_OBJC_RETAIN_RELEASE
#define ARC_DISPATCH_RETAIN(__OBJECT__) #define ARC_DISPATCH_RETAIN(__OBJECT__)
#define ARC_DISPATCH_RELEASE(__OBJECT__) #define ARC_DISPATCH_RELEASE(__OBJECT__)
#else #else
@@ -111,7 +110,7 @@ extern void GCDLogMessage(GCDWebServerLogLevel level, NSString* format, ...) NS_
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain" #define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) { static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
return ((range.location != NSNotFound) || (range.length > 0)); return ((range.location != NSUIntegerMax) || (range.length > 0));
} }
static inline NSError* GCDWebServerMakePosixError(int code) { static inline NSError* GCDWebServerMakePosixError(int code) {

View File

@@ -119,7 +119,7 @@
* Returns the content length for the body of the request parsed from the * Returns the content length for the body of the request parsed from the
* "Content-Length" header. * "Content-Length" header.
* *
* This property will be set to "NSNotFound" if the request has no body or * This property will be set to "NSUIntegerMax" if the request has no body or
* if there is a body but no "Content-Length" header, typically because * if there is a body but no "Content-Length" header, typically because
* chunked transfer encoding is used. * chunked transfer encoding is used.
*/ */
@@ -136,9 +136,9 @@
@property(nonatomic, readonly) NSString* ifNoneMatch; @property(nonatomic, readonly) NSString* ifNoneMatch;
/** /**
* Returns the parsed "Range" header or (NSNotFound, 0) if absent or malformed. * Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed.
* The range will be set to (offset, length) if expressed from the beginning * The range will be set to (offset, length) if expressed from the beginning
* of the entity body, or (NSNotFound, -length) if expressed from its end. * of the entity body, or (NSUIntegerMax, length) if expressed from its end.
*/ */
@property(nonatomic, readonly) NSRange byteRange; @property(nonatomic, readonly) NSRange byteRange;

View File

@@ -187,14 +187,14 @@
if (_type == nil) { if (_type == nil) {
_type = kGCDWebServerDefaultMimeType; _type = kGCDWebServerDefaultMimeType;
} }
_length = NSNotFound; _length = NSUIntegerMax;
} else { } else {
if (_type) { if (_type) {
DNOT_REACHED(); DNOT_REACHED();
ARC_RELEASE(self); ARC_RELEASE(self);
return nil; return nil;
} }
_length = NSNotFound; _length = NSUIntegerMax;
} }
NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"]; NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
@@ -203,7 +203,7 @@
} }
_noneMatch = ARC_RETAIN([_headers objectForKey:@"If-None-Match"]); _noneMatch = ARC_RETAIN([_headers objectForKey:@"If-None-Match"]);
_range = NSMakeRange(NSNotFound, 0); _range = NSMakeRange(NSUIntegerMax, 0);
NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]); NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]);
if (rangeHeader) { if (rangeHeader) {
if ([rangeHeader hasPrefix:@"bytes="]) { if ([rangeHeader hasPrefix:@"bytes="]) {
@@ -222,13 +222,13 @@
_range.location = startValue; _range.location = startValue;
_range.length = NSUIntegerMax; _range.length = NSUIntegerMax;
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500" } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
_range.location = NSNotFound; _range.location = NSUIntegerMax;
_range.length = endValue; _range.length = endValue;
} }
} }
} }
} }
if ((_range.location == NSNotFound) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid if ((_range.location == NSUIntegerMax) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid
LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url); LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
} }
} }

View File

@@ -87,12 +87,12 @@
/** /**
* Sets the content length for the body of the response. If a body is present * 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 * but this property is set to "NSUIntegerMax", this means the length of the body
* cannot be known ahead of time. 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 * automatically enabled by the GCDWebServerConnection to comply with HTTP/1.1
* specifications. * specifications.
* *
* The default value is "NSNotFound" i.e. the response has no body or its length * The default value is "NSUIntegerMax" i.e. the response has no body or its length
* is undefined. * is undefined.
*/ */
@property(nonatomic) NSUInteger contentLength; @property(nonatomic) NSUInteger contentLength;

View File

@@ -81,7 +81,7 @@
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader { - (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
if ((self = [super initWithResponse:response reader:reader])) { if ((self = [super initWithResponse:response reader:reader])) {
response.contentLength = NSNotFound; // Make sure "Content-Length" header is not set since we don't know it response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"]; [response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
} }
return self; return self;
@@ -180,7 +180,7 @@
- (instancetype)init { - (instancetype)init {
if ((self = [super init])) { if ((self = [super init])) {
_type = nil; _type = nil;
_length = NSNotFound; _length = NSUIntegerMax;
_status = kGCDWebServerHTTPStatusCode_OK; _status = kGCDWebServerHTTPStatusCode_OK;
_maxAge = 0; _maxAge = 0;
_headers = [[NSMutableDictionary alloc] init]; _headers = [[NSMutableDictionary alloc] init];
@@ -208,7 +208,7 @@
} }
- (BOOL)usesChunkedTransferEncoding { - (BOOL)usesChunkedTransferEncoding {
return (_type != nil) && (_length == NSNotFound); return (_type != nil) && (_length == NSUIntegerMax);
} }
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
@@ -259,7 +259,7 @@
if (_type) { if (_type) {
[description appendFormat:@"\nContent Type = %@", _type]; [description appendFormat:@"\nContent Type = %@", _type];
} }
if (_length != NSNotFound) { if (_length != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_length]; [description appendFormat:@"\nContent Length = %lu", (unsigned long)_length];
} }
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge]; [description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge];

View File

@@ -49,7 +49,7 @@
} }
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
if (self.contentLength != NSNotFound) { if (self.contentLength != NSUIntegerMax) {
_data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; _data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
} else { } else {
_data = [[NSMutableData alloc] init]; _data = [[NSMutableData alloc] init];
@@ -97,7 +97,8 @@
- (id)jsonObject { - (id)jsonObject {
if (_jsonObject == nil) { if (_jsonObject == nil) {
if ([self.contentType isEqualToString:@"application/json"] || [self.contentType isEqualToString:@"text/json"] || [self.contentType isEqualToString:@"text/javascript"]) { NSString* mimeType = GCDWebServerTruncateHeaderValue(self.contentType);
if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) {
_jsonObject = ARC_RETAIN([NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]); _jsonObject = ARC_RETAIN([NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]);
} else { } else {
DNOT_REACHED(); DNOT_REACHED();

View File

@@ -77,9 +77,9 @@
/** /**
* Initializes a response like -initWithFile: but restricts the file contents * Initializes a response like -initWithFile: but restricts the file contents
* to a specific byte range. This range should be set to (NSNotFound, 0) for * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for
* the full file, (offset, length) if expressed from the beginning of the file, * the full file, (offset, length) if expressed from the beginning of the file,
* or (NSNotFound, -length) if expressed from the end of the file. The "offset" * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset"
* and "length" values will be automatically adjusted to be compatible with the * and "length" values will be automatically adjusted to be compatible with the
* actual size of the file. * actual size of the file.
* *

View File

@@ -59,11 +59,11 @@
} }
- (instancetype)initWithFile:(NSString*)path { - (instancetype)initWithFile:(NSString*)path {
return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:NO]; return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO];
} }
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { - (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:attachment]; return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment];
} }
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range { - (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range {
@@ -81,31 +81,41 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
ARC_RELEASE(self); ARC_RELEASE(self);
return nil; return nil;
} }
if (GCDWebServerIsValidByteRange(range)) { #ifndef __LP64__
if (range.location != NSNotFound) { if (info.st_size >= (off_t)4294967295) { // In 32 bit mode, we can't handle files greater than 4 GiBs (don't use "NSUIntegerMax" here to avoid potential unsigned to signed conversion issues)
range.location = MIN(range.location, (NSUInteger)info.st_size); DNOT_REACHED();
range.length = MIN(range.length, (NSUInteger)info.st_size - range.location); ARC_RELEASE(self);
return nil;
}
#endif
NSUInteger fileSize = (NSUInteger)info.st_size;
BOOL hasByteRange = GCDWebServerIsValidByteRange(range);
if (hasByteRange) {
if (range.location != NSUIntegerMax) {
range.location = MIN(range.location, fileSize);
range.length = MIN(range.length, fileSize - range.location);
} else { } else {
range.length = MIN(range.length, (NSUInteger)info.st_size); range.length = MIN(range.length, fileSize);
range.location = (NSUInteger)info.st_size - range.length; range.location = fileSize - range.length;
} }
if (range.length == 0) { if (range.length == 0) {
ARC_RELEASE(self); ARC_RELEASE(self);
return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header
} }
} else {
range.location = 0;
range.length = fileSize;
} }
if ((self = [super init])) { if ((self = [super init])) {
_path = [path copy]; _path = [path copy];
if (range.location != NSNotFound) { _offset = range.location;
_offset = range.location; _size = range.length;
_size = range.length; if (hasByteRange) {
[self setStatusCode:kGCDWebServerHTTPStatusCode_PartialContent]; [self setStatusCode:kGCDWebServerHTTPStatusCode_PartialContent];
[self setValue:[NSString stringWithFormat:@"bytes %i-%i/%i", (int)range.location, (int)(range.location + range.length - 1), (int)info.st_size] forAdditionalHeader:@"Content-Range"]; [self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"];
LOG_DEBUG(@"Using content bytes range [%i-%i] for file \"%@\"", (int)range.location, (int)(range.location + range.length - 1), path); LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path);
} else {
_offset = 0;
_size = (NSUInteger)info.st_size;
} }
if (attachment) { if (attachment) {
@@ -121,8 +131,8 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
} }
} }
self.contentType = GCDWebServerGetMimeTypeForExtension([path pathExtension]); self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension]);
self.contentLength = (range.location != NSNotFound ? range.length : (NSUInteger)info.st_size); self.contentLength = _size;
self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec); self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec);
self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec]; self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec];
} }

View File

@@ -57,6 +57,11 @@
@implementation GCDWebUploader (Methods) @implementation GCDWebUploader (Methods)
// Must match implementation in GCDWebDAVServer
- (BOOL)_checkSandboxedPath:(NSString*)path {
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
}
- (BOOL)_checkFileExtension:(NSString*)fileName { - (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) { if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO; return NO;
@@ -85,8 +90,8 @@
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request { - (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"]; NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory; BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
if (!isDirectory) { if (!isDirectory) {
@@ -129,8 +134,8 @@
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request { - (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"]; NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory; BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
if (isDirectory) { if (isDirectory) {
@@ -160,6 +165,9 @@
} }
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string]; NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]]; NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) { if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
@@ -181,13 +189,16 @@
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request { - (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"]; NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath]; NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
BOOL isDirectory; BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
} }
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"]; NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]]; NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
if (![self _checkSandboxedPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
}
NSString* itemName = [newAbsolutePath lastPathComponent]; NSString* itemName = [newAbsolutePath lastPathComponent];
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) { if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
@@ -215,7 +226,7 @@
NSString* relativePath = [request.arguments objectForKey:@"path"]; NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
@@ -244,6 +255,9 @@
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request { - (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"]; NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]]; NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* directoryName = [absolutePath lastPathComponent]; NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHidden && [directoryName hasPrefix:@"."]) { if (!_allowHidden && [directoryName hasPrefix:@"."]) {
@@ -308,6 +322,9 @@
NSString* title = server.title; NSString* title = server.title;
if (title == nil) { if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
}
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
if (title == nil) { if (title == nil) {
title = [[NSProcessInfo processInfo] processName]; title = [[NSProcessInfo processInfo] processName];

View File

@@ -337,11 +337,14 @@ int main(int argc, const char* argv[]) {
if (webServer) { if (webServer) {
Delegate* delegate = [[Delegate alloc] init]; Delegate* delegate = [[Delegate alloc] init];
webServer.delegate = delegate;
if (testDirectory) { if (testDirectory) {
#ifndef NDEBUG
webServer.delegate = delegate;
#endif
fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]); fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]);
result = (int)[webServer runTestsWithOptions:@{GCDWebServerOption_Port: @8080} inDirectory:testDirectory]; result = (int)[webServer runTestsWithOptions:@{GCDWebServerOption_Port: @8080} inDirectory:testDirectory];
} else { } else {
webServer.delegate = delegate;
if (recording) { if (recording) {
fprintf(stdout, "<RECORDING ENABLED>\n"); fprintf(stdout, "<RECORDING ENABLED>\n");
webServer.recordingEnabled = YES; webServer.recordingEnabled = YES;
@@ -363,9 +366,10 @@ int main(int argc, const char* argv[]) {
result = 0; result = 0;
} }
} }
webServer.delegate = nil;
#if !__has_feature(objc_arc) #if !__has_feature(objc_arc)
[webServer release];
[delegate release]; [delegate release];
[webServer release];
#endif #endif
} }
} }

View File

@@ -77,8 +77,10 @@ int main(int argc, const char* argv[]) {
}]; }];
// Use convenience method that runs server on port 8080 until SIGINT received (i.e. Ctrl-C in Terminal) // Use convenience method that runs server on port 8080
// until SIGINT (Ctrl-C in Terminal) or SIGTERM is received
[webServer runWithPort:8080 bonjourName:nil]; [webServer runWithPort:8080 bonjourName:nil];
NSLog(@"Visit %@ in your web browser", webServer.serverURL);
} }
return 0; return 0;
@@ -90,7 +92,12 @@ int main(int argc, const char* argv[]) {
#import "GCDWebServer.h" #import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h" #import "GCDWebServerDataResponse.h"
static GCDWebServer* _webServer = nil; // This should really be an ivar of your application's delegate class @interface AppDelegate : NSObject <UIApplicationDelegate> {
GCDWebServer* _webServer;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
@@ -108,9 +115,35 @@ static GCDWebServer* _webServer = nil; // This should really be an ivar of your
// Start server on port 8080 // Start server on port 8080
[_webServer startWithPort:8080 bonjourName:nil]; [_webServer startWithPort:8080 bonjourName:nil];
NSLog(@"Visit %@ in your web browser", _webServer.serverURL);
return YES; return YES;
} }
@end
```
**OS X Swift version (command line tool):**
***webServer.swift***
```swift
import Foundation
let webServer = GCDWebServer()
webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self) { request in
return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>")
}
webServer.runWithPort(8080, bonjourName: nil)
println("Visit \(webServer.serverURL) in your web browser")
```
***WebServer-Bridging-Header.h***
```objectivec
#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"
``` ```
Web Based Uploads in iOS Apps Web Based Uploads in iOS Apps
@@ -123,7 +156,12 @@ Simply instantiate and run a ```GCDWebUploader``` instance then visit ```http://
```objectivec ```objectivec
#import "GCDWebUploader.h" #import "GCDWebUploader.h"
static GCDWebUploader* _webUploader = nil; // This should really be an ivar of your application's delegate class @interface AppDelegate : NSObject <UIApplicationDelegate> {
GCDWebUploader* _webUploader;
}
@end
@implementation AppDelegate
- (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];
@@ -132,6 +170,8 @@ static GCDWebUploader* _webUploader = nil; // This should really be an ivar of
NSLog(@"Visit %@ in your web browser", _webUploader.serverURL); NSLog(@"Visit %@ in your web browser", _webUploader.serverURL);
return YES; return YES;
} }
@end
``` ```
WebDAV Server in iOS Apps WebDAV Server in iOS Apps
@@ -146,7 +186,12 @@ Simply instantiate and run a ```GCDWebDAVServer``` instance then connect to ```h
```objectivec ```objectivec
#import "GCDWebDAVServer.h" #import "GCDWebDAVServer.h"
static GCDWebDAVServer* _davServer = nil; // This should really be an ivar of your application's delegate class @interface AppDelegate : NSObject <UIApplicationDelegate> {
GCDWebDAVServer* _davServer;
}
@end
@implementation AppDelegate
- (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];
@@ -155,6 +200,8 @@ static GCDWebDAVServer* _davServer = nil; // This should really be an ivar of y
NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL); NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL);
return YES; return YES;
} }
@end
``` ```
Serving a Static Website Serving a Static Website
@@ -220,6 +267,25 @@ Fortunately, GCDWebServer does all of this automatically for you:
HTTP connections are often initiated in batches (or bursts), for instance when loading a web page with multiple resources. This makes it difficult to accurately detect when the *very last* HTTP connection has been closed: it's possible 2 consecutive HTTP connections part of the same batch would be separated by a small delay instead of overlapping. It would be bad for the client if GCDWebServer suspended itself right in between. The ```GCDWebServerOption_ConnectedStateCoalescingInterval``` option solves this problem elegantly by forcing GCDWebServer to wait some extra delay before performing any action after the last HTTP connection has been closed, just in case a new HTTP connection is initiated within this delay. HTTP connections are often initiated in batches (or bursts), for instance when loading a web page with multiple resources. This makes it difficult to accurately detect when the *very last* HTTP connection has been closed: it's possible 2 consecutive HTTP connections part of the same batch would be separated by a small delay instead of overlapping. It would be bad for the client if GCDWebServer suspended itself right in between. The ```GCDWebServerOption_ConnectedStateCoalescingInterval``` option solves this problem elegantly by forcing GCDWebServer to wait some extra delay before performing any action after the last HTTP connection has been closed, just in case a new HTTP connection is initiated within this delay.
Debug Builds & Custom Logging
=============================
When building GCDWebServer in "Debug" mode versus "Release" mode, GCDWebServer logs a lot more information and also performs a number of internal consistency checks. To disable this behavior, make sure to define the preprocessor constant ```NDEBUG``` when compiling GCDWebServer. In Xcode target settings, this can be done by adding ```NDEBUG``` to the build setting ```GCC_PREPROCESSOR_DEFINITIONS``` when building in Release configuration (this is done automatically for you if you use CocoaPods).
It's also possible to replace the logging system used by GCDWebServer by a custom one. Simply define the preprocessor constant ```__GCDWEBSERVER_LOGGING_HEADER__``` to the name of a header file (e.g. "MyLogging.h") that defines these macros:
```
#define LOG_DEBUG(...) // Should not do anything if NDEBUG is defined
#define LOG_VERBOSE(...)
#define LOG_INFO(...)
#define LOG_WARNING(...)
#define LOG_ERROR(...)
#define LOG_EXCEPTION(__EXCEPTION__)
#define DCHECK(__CONDITION__) // Should not do anything if NDEBUG is defined or abort if __CONDITION__ is false
#define DNOT_REACHED() // Should not do anything if NDEBUG is defined
```
Advanced Example 1: Implementing HTTP Redirects Advanced Example 1: Implementing HTTP Redirects
=============================================== ===============================================

View File

@@ -23,22 +23,44 @@ function runTests {
rm -rf "$PAYLOAD_DIR" rm -rf "$PAYLOAD_DIR"
ditto -x -k "$PAYLOAD_ZIP" "$PAYLOAD_DIR" ditto -x -k "$PAYLOAD_ZIP" "$PAYLOAD_DIR"
TZ=GMT find "$PAYLOAD_DIR" -type d -exec SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" '{}' \; # ZIP archives do not preserve directories dates TZ=GMT find "$PAYLOAD_DIR" -type d -exec SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" '{}' \; # ZIP archives do not preserve directories dates
if [ "$4" != "" ]; then
cp -f "$4" "$PAYLOAD_DIR/Payload"
pushd "$PAYLOAD_DIR/Payload"
SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" `basename "$4"`
popd
fi
logLevel=2 $1 -mode "$2" -root "$PAYLOAD_DIR/Payload" -tests "$3" logLevel=2 $1 -mode "$2" -root "$PAYLOAD_DIR/Payload" -tests "$3"
} }
# Build for iOS in manual memory management mode (TODO: run tests on iOS) # Build for iOS in manual memory management mode and for oldest deployment target (TODO: run tests on iOS)
rm -rf "$MRC_BUILD_DIR"
xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$MRC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=NO" "IPHONEOS_DEPLOYMENT_TARGET=5.1.1" > /dev/null
# Build for iOS in manual memory management mode and for default deployment target (TODO: run tests on iOS)
rm -rf "$MRC_BUILD_DIR" rm -rf "$MRC_BUILD_DIR"
xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$MRC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=NO" > /dev/null xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$MRC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=NO" > /dev/null
# Build for iOS in ARC mode (TODO: run tests on iOS) # Build for iOS in ARC mode and for oldest deployment target (TODO: run tests on iOS)
rm -rf "$ARC_BUILD_DIR"
xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$ARC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=YES" "IPHONEOS_DEPLOYMENT_TARGET=5.1.1" > /dev/null
# Build for iOS in ARC mode and for default deployment target (TODO: run tests on iOS)
rm -rf "$ARC_BUILD_DIR" rm -rf "$ARC_BUILD_DIR"
xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$ARC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=YES" > /dev/null xcodebuild -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$ARC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=YES" > /dev/null
# Build for OS X in manual memory management mode # Build for OS X in manual memory management mode and for oldest deployment target
rm -rf "$MRC_BUILD_DIR"
xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$MRC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=NO" "MACOSX_DEPLOYMENT_TARGET=10.7" > /dev/null
# Build for OS X in manual memory management mode and for default deployment target
rm -rf "$MRC_BUILD_DIR" rm -rf "$MRC_BUILD_DIR"
xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$MRC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=NO" > /dev/null xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$MRC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=NO" > /dev/null
# Build for OS X in ARC mode # Build for OS X in ARC mode and for oldest deployment target
rm -rf "$ARC_BUILD_DIR"
xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$ARC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=YES" "MACOSX_DEPLOYMENT_TARGET=10.7" > /dev/null
# Build for OS X in ARC mode and for default deployment target
rm -rf "$ARC_BUILD_DIR" 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 xcodebuild -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" build "SYMROOT=$ARC_BUILD_DIR" "CLANG_ENABLE_OBJC_ARC=YES" > /dev/null
@@ -57,6 +79,8 @@ runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Finder"
runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Finder" runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Finder"
runTests $MRC_PRODUCT "webUploader" "Tests/WebUploader" runTests $MRC_PRODUCT "webUploader" "Tests/WebUploader"
runTests $ARC_PRODUCT "webUploader" "Tests/WebUploader" runTests $ARC_PRODUCT "webUploader" "Tests/WebUploader"
runTests $MRC_PRODUCT "webServer" "Tests/WebServer-Sample-Movie" "Tests/Sample-Movie.mp4"
runTests $ARC_PRODUCT "webServer" "Tests/WebServer-Sample-Movie" "Tests/Sample-Movie.mp4"
# Done # Done
echo "\nAll tests completed successfully!" echo "\nAll tests completed successfully!"

BIN
Tests/Sample-Movie.mp4 Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,12 @@
GET /Sample-Movie.mp4 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Pragma: no-cache
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
Referer: http://localhost:8080/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6

Binary file not shown.

View File

@@ -0,0 +1,13 @@
GET /Sample-Movie.mp4 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Accept-Encoding: identity;q=1, *;q=0
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
Accept: */*
DNT: 1
Referer: http://localhost:8080/Sample-Movie.mp4
Accept-Language: en-US,en;q=0.8,fr;q=0.6
Range: bytes=0-

Binary file not shown.

View File

@@ -0,0 +1,13 @@
GET /Sample-Movie.mp4 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Accept-Encoding: identity;q=1, *;q=0
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
Accept: */*
DNT: 1
Referer: http://localhost:8080/Sample-Movie.mp4
Accept-Language: en-US,en;q=0.8,fr;q=0.6
Range: bytes=3391326-

Binary file not shown.

View File

@@ -0,0 +1,12 @@
GET /Sample-Movie.mp4 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept-Encoding: identity;q=1, *;q=0
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
Accept: */*
DNT: 1
Referer: http://localhost:8080/Sample-Movie.mp4
Accept-Language: en-US,en;q=0.8,fr;q=0.6
Range: bytes=168-3391487
If-Range: 75279017/1388563200/0

Binary file not shown.

View File

@@ -0,0 +1,12 @@
GET /Sample-Movie.mp4 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept-Encoding: identity;q=1, *;q=0
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
Accept: */*
DNT: 1
Referer: http://localhost:8080/Sample-Movie.mp4
Accept-Language: en-US,en;q=0.8,fr;q=0.6
Range: bytes=168-1023
If-Range: 75279017/1388563200/0