mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-02-11 00:00:07 +08:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
628f6673b0 | ||
|
|
1944cd8a6e | ||
|
|
d2001e38ca | ||
|
|
18889793b7 | ||
|
|
14d538b0fb | ||
|
|
3b7198b4cc | ||
|
|
abb891334a | ||
|
|
059f5c8d01 | ||
|
|
9d9546bb6d | ||
|
|
2ff05b1aa0 | ||
|
|
bf2c9a170d | ||
|
|
15caa9cd20 | ||
|
|
32ba49ae34 | ||
|
|
8b87924776 | ||
|
|
5bda05c1f9 | ||
|
|
a8481af765 | ||
|
|
b5ad507a57 | ||
|
|
8c8e4847a5 | ||
|
|
514c09dc39 | ||
|
|
c4bf7b11e2 | ||
|
|
a933b2126e | ||
|
|
001df4ea39 | ||
|
|
75d018a375 | ||
|
|
4449e42601 | ||
|
|
c45053bc11 | ||
|
|
5070e4fc33 | ||
|
|
7c1e70a538 | ||
|
|
7102c7922e | ||
|
|
2de9418307 | ||
|
|
e59cf4b6df | ||
|
|
9e8f0e00f3 | ||
|
|
d7650a71e0 | ||
|
|
420ddc3eac | ||
|
|
143e38c968 | ||
|
|
8e9fe4c4c1 | ||
|
|
95bccff2f7 | ||
|
|
780a608d6c | ||
|
|
18d93bbf47 | ||
|
|
b35ebd7d58 | ||
|
|
a11b047233 | ||
|
|
4eac9d4f8e | ||
|
|
d1e2a1a12f | ||
|
|
54d5abd3a8 | ||
|
|
a9fee8d7e2 | ||
|
|
6b15bdaa4e | ||
|
|
3771cf4e92 | ||
|
|
a5d83abdd0 | ||
|
|
f1e9f1a37c | ||
|
|
00b5ec87ba | ||
|
|
cf94e70a42 | ||
|
|
d47409c776 | ||
|
|
a9db13475b | ||
|
|
17fad0f1b9 | ||
|
|
5493d9e803 | ||
|
|
12b1edb958 | ||
|
|
7544a6dc4e | ||
|
|
f9621c8aac | ||
|
|
7a93b27478 | ||
|
|
9d48f9ec12 | ||
|
|
7c6e85cf9a | ||
|
|
24fbd161d8 | ||
|
|
0ae0d4175a | ||
|
|
6d550a02b7 | ||
|
|
a7f46b762f | ||
|
|
d1c7f9a323 | ||
|
|
93bfe65211 | ||
|
|
8ab53f74d5 | ||
|
|
04a69787bf | ||
|
|
dfd019de7d | ||
|
|
4db631fa27 | ||
|
|
ba03d756c6 | ||
|
|
04f59a9214 | ||
|
|
40ea252ad6 | ||
|
|
c193860468 | ||
|
|
94ad8c745e | ||
|
|
56c096996f | ||
|
|
8cbaf0f867 | ||
|
|
295901c0b3 | ||
|
|
2dda0c98ce | ||
|
|
70a38c8b01 | ||
|
|
1b12a7bd14 | ||
|
|
75e6332500 | ||
|
|
420ed719e8 | ||
|
|
3b75f9dd20 |
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebDAVServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
// WebDAV specifications: http://webdav.org/specs/rfc4918.html
|
// WebDAV specifications: http://webdav.org/specs/rfc4918.html
|
||||||
|
|
||||||
// Requires "HEADER_SEARCH_PATHS = $(SDKROOT)/usr/include/libxml2" in Xcode build settings
|
// Requires "HEADER_SEARCH_PATHS = $(SDKROOT)/usr/include/libxml2" in Xcode build settings
|
||||||
@@ -61,6 +65,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 +96,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 +125,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 +170,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 +203,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 +252,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 +263,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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,9 +422,6 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
if (!success) {
|
if (!success) {
|
||||||
NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding];
|
NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding];
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
[string autorelease];
|
|
||||||
#endif
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -425,7 +431,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 +481,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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,9 +520,6 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
if (!success) {
|
if (!success) {
|
||||||
NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding];
|
NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding];
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
[string autorelease];
|
|
||||||
#endif
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string];
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,7 +578,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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,11 +605,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
||||||
#if __has_feature(objc_arc)
|
|
||||||
GCDWebDAVServer* __unsafe_unretained server = self;
|
GCDWebDAVServer* __unsafe_unretained server = self;
|
||||||
#else
|
|
||||||
__block GCDWebDAVServer* server = self;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 9.1 PROPFIND method
|
// 9.1 PROPFIND method
|
||||||
[self addDefaultHandlerForMethod:@"PROPFIND" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addDefaultHandlerForMethod:@"PROPFIND" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
@@ -662,17 +661,6 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
[_uploadDirectory release];
|
|
||||||
[_allowedExtensions release];
|
|
||||||
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebDAVServer (Subclassing)
|
@implementation GCDWebDAVServer (Subclassing)
|
||||||
|
|||||||
@@ -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 = '3.2.1'
|
||||||
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
|
||||||
@@ -28,7 +28,6 @@ Pod::Spec.new do |s|
|
|||||||
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'
|
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'
|
||||||
cs.osx.library = 'z'
|
cs.osx.library = 'z'
|
||||||
cs.osx.framework = 'SystemConfiguration'
|
cs.osx.framework = 'SystemConfiguration'
|
||||||
cs.compiler_flags = '-DNDEBUG' # TODO: Only set this for Release configuration
|
|
||||||
end
|
end
|
||||||
|
|
||||||
s.subspec 'WebDAV' do |cs|
|
s.subspec 'WebDAV' do |cs|
|
||||||
|
|||||||
@@ -112,6 +112,8 @@
|
|||||||
E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
|
E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
|
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
|
E26DC18719E84B2200C68DDC /* GCDWebServer.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = GCDWebServer.podspec; sourceTree = "<group>"; };
|
||||||
|
E26DC18819E84BC000C68DDC /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||||
E28BAE1618F99C810095C089 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = "<group>"; };
|
E28BAE1618F99C810095C089 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = "<group>"; };
|
||||||
E28BAE1718F99C810095C089 /* GCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServer.m; sourceTree = "<group>"; };
|
E28BAE1718F99C810095C089 /* GCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServer.m; sourceTree = "<group>"; };
|
||||||
E28BAE1818F99C810095C089 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerConnection.h; sourceTree = "<group>"; };
|
E28BAE1818F99C810095C089 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerConnection.h; sourceTree = "<group>"; };
|
||||||
@@ -183,6 +185,8 @@
|
|||||||
08FB7794FE84155DC02AAC07 /* LittleCMS */ = {
|
08FB7794FE84155DC02AAC07 /* LittleCMS */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E26DC18819E84BC000C68DDC /* README.md */,
|
||||||
|
E26DC18719E84B2200C68DDC /* GCDWebServer.podspec */,
|
||||||
E28BAE1418F99C810095C089 /* GCDWebServer */,
|
E28BAE1418F99C810095C089 /* GCDWebServer */,
|
||||||
E2A0E80718F3432600C580B1 /* GCDWebDAVServer */,
|
E2A0E80718F3432600C580B1 /* GCDWebDAVServer */,
|
||||||
E2BE850618E77ECA0061360B /* GCDWebUploader */,
|
E2BE850618E77ECA0061360B /* GCDWebUploader */,
|
||||||
@@ -370,7 +374,7 @@
|
|||||||
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 0510;
|
LastUpgradeCheck = 0610;
|
||||||
};
|
};
|
||||||
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
|
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
|
||||||
compatibilityVersion = "Xcode 3.2";
|
compatibilityVersion = "Xcode 3.2";
|
||||||
@@ -490,7 +494,6 @@
|
|||||||
1DEB928608733DD80010E9CD /* Debug */ = {
|
1DEB928608733DD80010E9CD /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ARCHS = "$(ARCHS_STANDARD)";
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
||||||
PRODUCT_NAME = GCDWebServer;
|
PRODUCT_NAME = GCDWebServer;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -500,7 +503,6 @@
|
|||||||
1DEB928708733DD80010E9CD /* Release */ = {
|
1DEB928708733DD80010E9CD /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ARCHS = "$(ARCHS_STANDARD)";
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
||||||
PRODUCT_NAME = GCDWebServer;
|
PRODUCT_NAME = GCDWebServer;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -511,7 +513,9 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
|
||||||
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
|
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
|
||||||
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
|
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
@@ -531,6 +535,7 @@
|
|||||||
"-Wno-padded",
|
"-Wno-padded",
|
||||||
"-Wno-documentation",
|
"-Wno-documentation",
|
||||||
"-Wno-documentation-unknown-command",
|
"-Wno-documentation-unknown-command",
|
||||||
|
"-Wno-objc-missing-property-synthesis",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -539,7 +544,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
|
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
|
||||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
|
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
|
||||||
@@ -550,10 +555,9 @@
|
|||||||
E22112761690B4DF0048D2B2 /* Debug */ = {
|
E22112761690B4DF0048D2B2 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
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;
|
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||||
PRODUCT_NAME = GCDWebServer;
|
PRODUCT_NAME = GCDWebServer;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -564,10 +568,9 @@
|
|||||||
E22112771690B4DF0048D2B2 /* Release */ = {
|
E22112771690B4DF0048D2B2 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
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;
|
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||||
PRODUCT_NAME = GCDWebServer;
|
PRODUCT_NAME = GCDWebServer;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|||||||
@@ -30,21 +30,6 @@
|
|||||||
#import "GCDWebServerRequest.h"
|
#import "GCDWebServerRequest.h"
|
||||||
#import "GCDWebServerResponse.h"
|
#import "GCDWebServerResponse.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* Log levels used by GCDWebServer.
|
|
||||||
*
|
|
||||||
* @warning kGCDWebServerLogLevel_Debug is only available if "NDEBUG" is not
|
|
||||||
* defined when building.
|
|
||||||
*/
|
|
||||||
typedef NS_ENUM(int, GCDWebServerLogLevel) {
|
|
||||||
kGCDWebServerLogLevel_Debug = 0,
|
|
||||||
kGCDWebServerLogLevel_Verbose,
|
|
||||||
kGCDWebServerLogLevel_Info,
|
|
||||||
kGCDWebServerLogLevel_Warning,
|
|
||||||
kGCDWebServerLogLevel_Error,
|
|
||||||
kGCDWebServerLogLevel_Exception,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerMatchBlock is called for every handler added to the
|
* The GCDWebServerMatchBlock is called for every handler added to the
|
||||||
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
|
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
|
||||||
@@ -69,6 +54,19 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
|
|||||||
*/
|
*/
|
||||||
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
|
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
|
||||||
|
* except the GCDWebServerResponse can be returned to the server at a later time
|
||||||
|
* allowing for asynchronous generation of the response.
|
||||||
|
*
|
||||||
|
* The block must eventually call "completionBlock" passing a GCDWebServerResponse
|
||||||
|
* or nil on error, which will result in a 500 HTTP status code returned to the client.
|
||||||
|
* It's however recommended to return a GCDWebServerErrorResponse on error so more
|
||||||
|
* useful information can be returned to the client.
|
||||||
|
*/
|
||||||
|
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
|
||||||
|
typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The port used by the GCDWebServer (NSNumber / NSUInteger).
|
* The port used by the GCDWebServer (NSNumber / NSUInteger).
|
||||||
*
|
*
|
||||||
@@ -77,12 +75,32 @@ 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 nil.
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only accept HTTP requests coming from localhost i.e. not from the outside
|
||||||
|
* network (NSNumber / BOOL).
|
||||||
|
*
|
||||||
|
* The default value is NO.
|
||||||
|
*
|
||||||
|
* @warning Bonjour should be disabled if using this option since the server
|
||||||
|
* will not be reachable from the outside network anyway.
|
||||||
|
*/
|
||||||
|
extern NSString* const GCDWebServerOption_BindToLocalhost;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 +251,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,13 +284,21 @@ 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.
|
||||||
*/
|
*/
|
||||||
- (instancetype)init;
|
- (instancetype)init;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a handler to the server to handle incoming HTTP requests.
|
* Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests.
|
||||||
*
|
*
|
||||||
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
|
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
|
||||||
* respond to a given request, the latest added one wins.
|
* respond to a given request, the latest added one wins.
|
||||||
@@ -277,6 +307,16 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
*/
|
*/
|
||||||
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests.
|
||||||
|
*
|
||||||
|
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
|
||||||
|
* respond to a given request, the latest added one wins.
|
||||||
|
*
|
||||||
|
* @warning Addling handlers while the server is running is not allowed.
|
||||||
|
*/
|
||||||
|
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all handlers previously added to the server.
|
* Removes all handlers previously added to the server.
|
||||||
*
|
*
|
||||||
@@ -317,12 +357,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 +373,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 +393,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.
|
||||||
*
|
*
|
||||||
@@ -369,22 +411,44 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a default handler to the server to handle all incoming HTTP requests
|
* Adds a default handler to the server to handle all incoming HTTP requests
|
||||||
* with a given HTTP method.
|
* with a given HTTP method and generate responses synchronously.
|
||||||
*/
|
*/
|
||||||
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
|
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a default handler to the server to handle all incoming HTTP requests
|
||||||
|
* with a given HTTP method and generate responses asynchronously.
|
||||||
|
*/
|
||||||
|
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a handler to the server to handle incoming HTTP requests with a given
|
* Adds a handler to the server to handle incoming HTTP requests with a given
|
||||||
* HTTP method and a specific case-insensitive path.
|
* HTTP method and a specific case-insensitive path and generate responses
|
||||||
|
* synchronously.
|
||||||
*/
|
*/
|
||||||
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
|
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a handler to the server to handle incoming HTTP requests with a given
|
* Adds a handler to the server to handle incoming HTTP requests with a given
|
||||||
* HTTP method and a path matching a case-insensitive regular expression.
|
* HTTP method and a specific case-insensitive path and generate responses
|
||||||
|
* asynchronously.
|
||||||
|
*/
|
||||||
|
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a handler to the server to handle incoming HTTP requests with a given
|
||||||
|
* HTTP method and a path matching a case-insensitive regular expression and
|
||||||
|
* generate responses synchronously.
|
||||||
*/
|
*/
|
||||||
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
|
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a handler to the server to handle incoming HTTP requests with a given
|
||||||
|
* HTTP method and a path matching a case-insensitive regular expression and
|
||||||
|
* generate responses asynchronously.
|
||||||
|
*/
|
||||||
|
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServer (GETHandlers)
|
@interface GCDWebServer (GETHandlers)
|
||||||
@@ -414,42 +478,84 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GCDWebServer provides its own built-in logging facility which is used by
|
||||||
|
* default. It simply sends log messages to stderr assuming it is connected
|
||||||
|
* to a terminal type device.
|
||||||
|
*
|
||||||
|
* GCDWebServer is also compatible with a limited set of third-party logging
|
||||||
|
* facilities. If one of them is available at compile time, GCDWebServer will
|
||||||
|
* automatically use it in place of the built-in one.
|
||||||
|
*
|
||||||
|
* Currently supported third-party logging facilities are:
|
||||||
|
* - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility
|
||||||
|
* - CocoaLumberjack: https://github.com/CocoaLumberjack/CocoaLumberjack
|
||||||
|
*
|
||||||
|
* For both the built-in logging facility and CocoaLumberjack, the default
|
||||||
|
* logging level is INFO (or DEBUG if the preprocessor constant "DEBUG"
|
||||||
|
* evaluates to non-zero at compile time).
|
||||||
|
*
|
||||||
|
* It's possible to have GCDWebServer use a custom logging facility by defining
|
||||||
|
* the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build
|
||||||
|
* settings to the name of a custom header file (escaped like \"MyLogging.h\").
|
||||||
|
* This header file must define the following set of macros:
|
||||||
|
*
|
||||||
|
* GWS_LOG_DEBUG(...)
|
||||||
|
* GWS_LOG_VERBOSE(...)
|
||||||
|
* GWS_LOG_INFO(...)
|
||||||
|
* GWS_LOG_WARNING(...)
|
||||||
|
* GWS_LOG_ERROR(...)
|
||||||
|
* GWS_LOG_EXCEPTION(__EXCEPTION__)
|
||||||
|
*
|
||||||
|
* IMPORTANT: Except for GWS_LOG_EXCEPTION() which gets passed an NSException,
|
||||||
|
* these macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() macro
|
||||||
|
* should not do anything unless the preprocessor constant "DEBUG" evaluates to
|
||||||
|
* non-zero.
|
||||||
|
*
|
||||||
|
* The logging methods below send log messages to the same logging facility
|
||||||
|
* used by GCDWebServer. They can be used for consistency wherever you interact
|
||||||
|
* with GCDWebServer in your code (e.g. in the implementation of handlers).
|
||||||
|
*/
|
||||||
@interface GCDWebServer (Logging)
|
@interface GCDWebServer (Logging)
|
||||||
|
|
||||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current log level below which logged messages are discarded.
|
* Sets the log level of the logging facility below which log messages are discarded.
|
||||||
*
|
*
|
||||||
* The default level is either DEBUG or INFO if "NDEBUG" is defined at build-time.
|
* @warning The interpretation of the "level" argument depends on the logging
|
||||||
* It can also be set at runtime with the "logLevel" environment variable.
|
* facility used at compile time.
|
||||||
|
*
|
||||||
|
* If using the built-in logging facility, the log levels are as follow:
|
||||||
|
* DEBUG = 0
|
||||||
|
* VERBOSE = 1
|
||||||
|
* INFO = 2
|
||||||
|
* WARNING = 3
|
||||||
|
* ERROR = 4
|
||||||
|
* EXCEPTION = 5
|
||||||
*/
|
*/
|
||||||
+ (void)setLogLevel:(GCDWebServerLogLevel)level;
|
+ (void)setLogLevel:(int)level;
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message with the kGCDWebServerLogLevel_Verbose level.
|
* Logs a message to the logging facility at the VERBOSE level.
|
||||||
*/
|
*/
|
||||||
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message with the kGCDWebServerLogLevel_Info level.
|
* Logs a message to the logging facility at the INFO level.
|
||||||
*/
|
*/
|
||||||
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message with the kGCDWebServerLogLevel_Warning level.
|
* Logs a message to the logging facility at the WARNING level.
|
||||||
*/
|
*/
|
||||||
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message with the kGCDWebServerLogLevel_Error level.
|
* Logs a message to the logging facility at the ERROR level.
|
||||||
*/
|
*/
|
||||||
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs an exception with the kGCDWebServerLogLevel_Exception level.
|
* Logs an exception to the logging facility at the EXCEPTION level.
|
||||||
*/
|
*/
|
||||||
- (void)logException:(NSException*)exception;
|
- (void)logException:(NSException*)exception;
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,11 @@
|
|||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) GCDWebServer* server;
|
@property(nonatomic, readonly) GCDWebServer* server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns YES if the connection is using IPv6.
|
||||||
|
*/
|
||||||
|
@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the address of the local peer (i.e. server) of the connection
|
* Returns the address of the local peer (i.e. server) of the connection
|
||||||
* as a raw "struct sockaddr".
|
* as a raw "struct sockaddr".
|
||||||
@@ -56,7 +61,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the address of the local peer (i.e. server) of the connection
|
* Returns the address of the local peer (i.e. server) of the connection
|
||||||
* as a dotted string.
|
* as a string.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* localAddressString;
|
@property(nonatomic, readonly) NSString* localAddressString;
|
||||||
|
|
||||||
@@ -68,7 +73,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the address of the remote peer (i.e. client) of the connection
|
* Returns the address of the remote peer (i.e. client) of the connection
|
||||||
* as a dotted string.
|
* as a string.
|
||||||
*/
|
*/
|
||||||
@property(nonatomic, readonly) NSString* remoteAddressString;
|
@property(nonatomic, readonly) NSString* remoteAddressString;
|
||||||
|
|
||||||
@@ -117,6 +122,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.
|
||||||
@@ -130,13 +143,14 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
|
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
|
||||||
* this method is called to process the request.
|
* this method is called to process the request by executing the handler's
|
||||||
|
* process block.
|
||||||
*/
|
*/
|
||||||
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
|
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assuming a valid HTTP request was received and either -preflightRequest:
|
* Assuming a valid HTTP request was received and either -preflightRequest:
|
||||||
* or -processRequest:withBlock: returned a non-nil GCDWebServerResponse,
|
* or -processRequest:completion: returned a non-nil GCDWebServerResponse,
|
||||||
* this method is called to override the response.
|
* this method is called to override the response.
|
||||||
*
|
*
|
||||||
* You can either modify the current response and return it, or return a
|
* You can either modify the current response and return it, or return a
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import <TargetConditionals.h>
|
#import <TargetConditionals.h>
|
||||||
#import <netdb.h>
|
#import <netdb.h>
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
@@ -99,14 +103,14 @@ static int32_t _connectionCounter = 0;
|
|||||||
block(YES);
|
block(YES);
|
||||||
} else {
|
} else {
|
||||||
if (_bytesRead > 0) {
|
if (_bytesRead > 0) {
|
||||||
LOG_ERROR(@"No more data available on socket %i", _socket);
|
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(@"No data received from socket %i", _socket);
|
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
|
||||||
}
|
}
|
||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
|
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
|
||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +119,7 @@ static int32_t _connectionCounter = 0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)_readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
|
- (void)_readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
|
||||||
DCHECK(_requestMessage);
|
GWS_DCHECK(_requestMessage);
|
||||||
[self _readData:headersData withLength:NSUIntegerMax completionBlock:^(BOOL success) {
|
[self _readData:headersData withLength:NSUIntegerMax completionBlock:^(BOOL success) {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -128,11 +132,11 @@ static int32_t _connectionCounter = 0;
|
|||||||
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
|
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
|
||||||
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
|
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
||||||
block(nil);
|
block(nil);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
||||||
block(nil);
|
block(nil);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +148,7 @@ static int32_t _connectionCounter = 0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
|
- (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
|
||||||
DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
|
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
|
||||||
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
|
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
|
||||||
[self _readData:bodyData withLength:length completionBlock:^(BOOL success) {
|
[self _readData:bodyData withLength:length completionBlock:^(BOOL success) {
|
||||||
|
|
||||||
@@ -159,20 +163,19 @@ static int32_t _connectionCounter = 0;
|
|||||||
block(YES);
|
block(YES);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
||||||
block(NO);
|
block(NO);
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
ARC_RELEASE(bodyData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||||
@@ -185,7 +188,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)_readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
|
- (void)_readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
|
||||||
DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
|
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
|
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
|
||||||
@@ -205,12 +208,12 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
|
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
|
||||||
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
|
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||||
block(NO);
|
block(NO);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
|
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
|
||||||
block(NO);
|
block(NO);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -222,7 +225,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
|
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
|
||||||
block(NO);
|
block(NO);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -244,90 +247,87 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
@implementation GCDWebServerConnection (Write)
|
@implementation GCDWebServerConnection (Write)
|
||||||
|
|
||||||
- (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
|
- (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
[data retain];
|
|
||||||
#endif
|
|
||||||
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, kGCDWebServerGCDQueue, ^{
|
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, kGCDWebServerGCDQueue, ^{
|
||||||
#if __has_feature(objc_arc)
|
|
||||||
[data self]; // Keeps ARC from releasing data too early
|
[data self]; // Keeps ARC from releasing data too early
|
||||||
#else
|
|
||||||
[data release];
|
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
dispatch_write(_socket, buffer, kGCDWebServerGCDQueue, ^(dispatch_data_t remainingData, int error) {
|
dispatch_write(_socket, buffer, kGCDWebServerGCDQueue, ^(dispatch_data_t remainingData, int error) {
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
if (error == 0) {
|
if (error == 0) {
|
||||||
DCHECK(remainingData == NULL);
|
GWS_DCHECK(remainingData == NULL);
|
||||||
[self didWriteBytes:data.bytes length:data.length];
|
[self didWriteBytes:data.bytes length:data.length];
|
||||||
block(YES);
|
block(YES);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
ARC_DISPATCH_RELEASE(buffer);
|
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
||||||
|
dispatch_release(buffer);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
|
- (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
|
||||||
DCHECK(_responseMessage);
|
GWS_DCHECK(_responseMessage);
|
||||||
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
|
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
|
||||||
[self _writeData:(ARC_BRIDGE NSData*)data withCompletionBlock:block];
|
[self _writeData:(__bridge NSData*)data withCompletionBlock:block];
|
||||||
CFRelease(data);
|
CFRelease(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
||||||
DCHECK([_response hasBody]);
|
GWS_DCHECK([_response hasBody]);
|
||||||
NSError* error = nil;
|
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
|
||||||
NSData* data = [_response performReadData:&error];
|
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
if (_response.usesChunkedTransferEncoding) {
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
||||||
size_t hexLength = strlen(hexString);
|
size_t hexLength = strlen(hexString);
|
||||||
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
||||||
if (chunk == nil) {
|
if (chunk == nil) {
|
||||||
LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
||||||
block(NO);
|
block(NO);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
|
||||||
|
bcopy(hexString, ptr, hexLength);
|
||||||
|
ptr += hexLength;
|
||||||
|
*ptr++ = '\r';
|
||||||
|
*ptr++ = '\n';
|
||||||
|
bcopy(data.bytes, ptr, data.length);
|
||||||
|
ptr += data.length;
|
||||||
|
*ptr++ = '\r';
|
||||||
|
*ptr = '\n';
|
||||||
|
data = chunk;
|
||||||
}
|
}
|
||||||
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
|
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
||||||
bcopy(hexString, ptr, hexLength);
|
|
||||||
ptr += hexLength;
|
|
||||||
*ptr++ = '\r';
|
|
||||||
*ptr++ = '\n';
|
|
||||||
bcopy(data.bytes, ptr, data.length);
|
|
||||||
ptr += data.length;
|
|
||||||
*ptr++ = '\r';
|
|
||||||
*ptr = '\n';
|
|
||||||
data = chunk;
|
|
||||||
}
|
|
||||||
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
[self _writeBodyWithCompletionBlock:block];
|
|
||||||
} else {
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
if (_response.usesChunkedTransferEncoding) {
|
|
||||||
[self _writeData:_lastChunkData withCompletionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
block(success);
|
if (success) {
|
||||||
|
[self _writeBodyWithCompletionBlock:block];
|
||||||
|
} else {
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
block(YES);
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
|
[self _writeData:_lastChunkData withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
|
block(success);
|
||||||
|
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
block(YES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
||||||
|
block(NO);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
}];
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -339,49 +339,59 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
if (_CRLFData == nil) {
|
if (_CRLFData == nil) {
|
||||||
_CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2];
|
_CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2];
|
||||||
DCHECK(_CRLFData);
|
GWS_DCHECK(_CRLFData);
|
||||||
}
|
}
|
||||||
if (_CRLFCRLFData == nil) {
|
if (_CRLFCRLFData == nil) {
|
||||||
_CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
|
_CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
|
||||||
DCHECK(_CRLFCRLFData);
|
GWS_DCHECK(_CRLFCRLFData);
|
||||||
}
|
}
|
||||||
if (_continueData == nil) {
|
if (_continueData == nil) {
|
||||||
CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
|
CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
|
||||||
#if __has_feature(objc_arc)
|
|
||||||
_continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message));
|
_continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message));
|
||||||
#else
|
|
||||||
_continueData = (NSData*)CFHTTPMessageCopySerializedMessage(message);
|
|
||||||
#endif
|
|
||||||
CFRelease(message);
|
CFRelease(message);
|
||||||
DCHECK(_continueData);
|
GWS_DCHECK(_continueData);
|
||||||
}
|
}
|
||||||
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) {
|
if (_digestAuthenticationNonce == nil) {
|
||||||
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
|
||||||
_digestAuthenticationNonce = ARC_RETAIN(GCDWebServerComputeMD5Digest(@"%@", ARC_BRIDGE_RELEASE(CFUUIDCreateString(kCFAllocatorDefault, uuid))));
|
_digestAuthenticationNonce = GCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)));
|
||||||
CFRelease(uuid);
|
CFRelease(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)isUsingIPv6 {
|
||||||
|
const struct sockaddr* localSockAddr = _localAddress.bytes;
|
||||||
|
return (localSockAddr->sa_family == AF_INET6);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
|
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
|
||||||
_statusCode = statusCode;
|
_statusCode = statusCode;
|
||||||
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
|
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close"));
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (ARC_BRIDGE CFStringRef)_server.serverName);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (__bridge CFStringRef)_server.serverName);
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (__bridge CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_startProcessingRequest {
|
||||||
|
GWS_DCHECK(_responseMessage == NULL);
|
||||||
|
|
||||||
|
GCDWebServerResponse* preflightResponse = [self preflightRequest:_request];
|
||||||
|
if (preflightResponse) {
|
||||||
|
[self _finishProcessingRequest:preflightResponse];
|
||||||
|
} else {
|
||||||
|
[self processRequest:_request completion:^(GCDWebServerResponse* processResponse) {
|
||||||
|
[self _finishProcessingRequest:processResponse];
|
||||||
|
}];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||||
- (void)_processRequest {
|
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
|
||||||
DCHECK(_responseMessage == NULL);
|
GWS_DCHECK(_responseMessage == NULL);
|
||||||
BOOL hasBody = NO;
|
BOOL hasBody = NO;
|
||||||
|
|
||||||
GCDWebServerResponse* response = [self preflightRequest:_request];
|
|
||||||
if (!response) {
|
|
||||||
response = [self processRequest:_request withBlock:_handler.processBlock];
|
|
||||||
}
|
|
||||||
if (response) {
|
if (response) {
|
||||||
response = [self overrideResponse:response forRequest:_request];
|
response = [self overrideResponse:response forRequest:_request];
|
||||||
}
|
}
|
||||||
@@ -392,38 +402,38 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
NSError* error = nil;
|
NSError* error = nil;
|
||||||
if (hasBody && ![response performOpen:&error]) {
|
if (hasBody && ![response performOpen:&error]) {
|
||||||
LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
|
||||||
} else {
|
} else {
|
||||||
_response = ARC_RETAIN(response);
|
_response = response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_response) {
|
if (_response) {
|
||||||
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
|
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
|
||||||
if (_response.lastModifiedDate) {
|
if (_response.lastModifiedDate) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822(_response.lastModifiedDate));
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822(_response.lastModifiedDate));
|
||||||
}
|
}
|
||||||
if (_response.eTag) {
|
if (_response.eTag) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (ARC_BRIDGE CFStringRef)_response.eTag);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
|
||||||
}
|
}
|
||||||
if ((_response.statusCode >= 200) && (_response.statusCode < 300)) {
|
if ((_response.statusCode >= 200) && (_response.statusCode < 300)) {
|
||||||
if (_response.cacheControlMaxAge > 0) {
|
if (_response.cacheControlMaxAge > 0) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]);
|
||||||
} else {
|
} else {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_response.contentType != nil) {
|
if (_response.contentType != nil) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (ARC_BRIDGE CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType));
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__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"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
|
||||||
}
|
}
|
||||||
if (_response.usesChunkedTransferEncoding) {
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
|
||||||
}
|
}
|
||||||
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
|
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (ARC_BRIDGE CFStringRef)key, (ARC_BRIDGE CFStringRef)obj);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
|
||||||
}];
|
}];
|
||||||
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
|
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
@@ -449,16 +459,16 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
|
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
|
||||||
NSError* error = nil;
|
NSError* error = nil;
|
||||||
if (![_request performOpen:&error]) {
|
if (![_request performOpen:&error]) {
|
||||||
LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initialData.length) {
|
if (initialData.length) {
|
||||||
if (![_request performWriteData:initialData error:&error]) {
|
if (![_request performWriteData:initialData error:&error]) {
|
||||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||||
if (![_request performClose:&error]) {
|
if (![_request performClose:&error]) {
|
||||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||||
}
|
}
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
return;
|
return;
|
||||||
@@ -471,18 +481,18 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
|
|
||||||
NSError* localError = nil;
|
NSError* localError = nil;
|
||||||
if ([_request performClose:&localError]) {
|
if ([_request performClose:&localError]) {
|
||||||
[self _processRequest];
|
[self _startProcessingRequest];
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
if ([_request performClose:&error]) {
|
if ([_request performClose:&error]) {
|
||||||
[self _processRequest];
|
[self _startProcessingRequest];
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,7 +501,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
- (void)_readChunkedBodyWithInitialData:(NSData*)initialData {
|
- (void)_readChunkedBodyWithInitialData:(NSData*)initialData {
|
||||||
NSError* error = nil;
|
NSError* error = nil;
|
||||||
if (![_request performOpen:&error]) {
|
if (![_request performOpen:&error]) {
|
||||||
LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -501,14 +511,13 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
|
|
||||||
NSError* localError = nil;
|
NSError* localError = nil;
|
||||||
if ([_request performClose:&localError]) {
|
if ([_request performClose:&localError]) {
|
||||||
[self _processRequest];
|
[self _startProcessingRequest];
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
ARC_RELEASE(chunkData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_readRequestHeaders {
|
- (void)_readRequestHeaders {
|
||||||
@@ -517,19 +526,23 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
[self _readHeaders:headersData withCompletionBlock:^(NSData* extraData) {
|
[self _readHeaders:headersData withCompletionBlock:^(NSData* extraData) {
|
||||||
|
|
||||||
if (extraData) {
|
if (extraData) {
|
||||||
NSString* requestMethod = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase
|
NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase
|
||||||
if (_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
|
if (_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
|
||||||
requestMethod = @"GET";
|
requestMethod = @"GET";
|
||||||
_virtualHEAD = YES;
|
_virtualHEAD = YES;
|
||||||
}
|
}
|
||||||
NSURL* requestURL = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(_requestMessage));
|
NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
|
||||||
NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(ARC_BRIDGE_RELEASE(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash
|
NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(_requestMessage));
|
||||||
NSString* queryString = requestURL ? ARC_BRIDGE_RELEASE(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
|
if (requestURL) {
|
||||||
|
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
|
||||||
|
GWS_DCHECK(requestURL);
|
||||||
|
}
|
||||||
|
NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash
|
||||||
|
NSString* queryString = requestURL ? CFBridgingRelease(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 = _handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery);
|
||||||
if (_request) {
|
if (_request) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -553,7 +566,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
|
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
|
GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -564,42 +577,40 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
|
GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
[self _processRequest];
|
[self _startProcessingRequest];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
|
_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
|
||||||
DCHECK(_request);
|
GWS_DCHECK(_request);
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_MethodNotAllowed];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_MethodNotAllowed];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
}
|
}
|
||||||
|
|
||||||
}];
|
}];
|
||||||
ARC_RELEASE(headersData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (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 {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_server = ARC_RETAIN(server);
|
_server = server;
|
||||||
_localAddress = ARC_RETAIN(localAddress);
|
_localAddress = localAddress;
|
||||||
_remoteAddress = ARC_RETAIN(remoteAddress);
|
_remoteAddress = remoteAddress;
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
LOG_DEBUG(@"Did open connection on socket %i", _socket);
|
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
|
||||||
|
|
||||||
[_server willStartConnection:self];
|
[_server willStartConnection:self];
|
||||||
|
|
||||||
if (![self open]) {
|
if (![self open]) {
|
||||||
close(_socket);
|
close(_socket);
|
||||||
ARC_RELEASE(self);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
_opened = YES;
|
_opened = YES;
|
||||||
@@ -609,33 +620,20 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSString* _StringFromAddressData(NSData* data) {
|
|
||||||
NSString* string = nil;
|
|
||||||
const struct sockaddr* addr = data.bytes;
|
|
||||||
char hostBuffer[NI_MAXHOST];
|
|
||||||
char serviceBuffer[NI_MAXSERV];
|
|
||||||
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
|
|
||||||
string = [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer];
|
|
||||||
} else {
|
|
||||||
DNOT_REACHED();
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)localAddressString {
|
- (NSString*)localAddressString {
|
||||||
return _StringFromAddressData(_localAddress);
|
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)remoteAddressString {
|
- (NSString*)remoteAddressString {
|
||||||
return _StringFromAddressData(_remoteAddress);
|
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
int result = close(_socket);
|
int result = close(_socket);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno);
|
GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno);
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG(@"Did close connection on socket %i", _socket);
|
GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_opened) {
|
if (_opened) {
|
||||||
@@ -643,26 +641,14 @@ static NSString* _StringFromAddressData(NSData* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[_server didEndConnection:self];
|
[_server didEndConnection:self];
|
||||||
ARC_RELEASE(_server);
|
|
||||||
ARC_RELEASE(_localAddress);
|
|
||||||
ARC_RELEASE(_remoteAddress);
|
|
||||||
|
|
||||||
if (_requestMessage) {
|
if (_requestMessage) {
|
||||||
CFRelease(_requestMessage);
|
CFRelease(_requestMessage);
|
||||||
}
|
}
|
||||||
ARC_RELEASE(_request);
|
|
||||||
|
|
||||||
if (_responseMessage) {
|
if (_responseMessage) {
|
||||||
CFRelease(_responseMessage);
|
CFRelease(_responseMessage);
|
||||||
}
|
}
|
||||||
ARC_RELEASE(_response);
|
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
|
||||||
ARC_RELEASE(_requestPath);
|
|
||||||
ARC_RELEASE(_responsePath);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -674,13 +660,13 @@ static NSString* _StringFromAddressData(NSData* data) {
|
|||||||
if (_server.recordingEnabled) {
|
if (_server.recordingEnabled) {
|
||||||
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
|
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
|
||||||
|
|
||||||
_requestPath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
|
_requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||||
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||||
DCHECK(_requestFD > 0);
|
GWS_DCHECK(_requestFD > 0);
|
||||||
|
|
||||||
_responsePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
|
_responsePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||||
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||||
DCHECK(_responseFD > 0);
|
GWS_DCHECK(_responseFD > 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -688,12 +674,12 @@ static NSString* _StringFromAddressData(NSData* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
|
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
|
||||||
LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
|
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||||
_bytesRead += length;
|
_bytesRead += length;
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
|
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
|
||||||
LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno);
|
GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno);
|
||||||
close(_requestFD);
|
close(_requestFD);
|
||||||
_requestFD = 0;
|
_requestFD = 0;
|
||||||
}
|
}
|
||||||
@@ -701,21 +687,25 @@ static NSString* _StringFromAddressData(NSData* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
|
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
|
||||||
LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
|
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||||
_bytesWritten += length;
|
_bytesWritten += length;
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
|
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
|
||||||
LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno);
|
GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno);
|
||||||
close(_responseFD);
|
close(_responseFD);
|
||||||
_responseFD = 0;
|
_responseFD = 0;
|
||||||
}
|
}
|
||||||
#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);
|
GWS_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;
|
GCDWebServerResponse* response = nil;
|
||||||
if (_server.authenticationBasicAccounts) {
|
if (_server.authenticationBasicAccounts) {
|
||||||
__block BOOL authenticated = NO;
|
__block BOOL authenticated = NO;
|
||||||
@@ -764,16 +754,14 @@ static NSString* _StringFromAddressData(NSData* data) {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
|
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
|
||||||
LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
GWS_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;
|
|
||||||
@try {
|
@try {
|
||||||
response = block(request);
|
_handler.asyncProcessBlock(request, completion);
|
||||||
}
|
}
|
||||||
@catch (NSException* exception) {
|
@catch (NSException* exception) {
|
||||||
LOG_EXCEPTION(exception);
|
GWS_LOG_EXCEPTION(exception);
|
||||||
}
|
}
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
|
||||||
@@ -798,20 +786,20 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
|||||||
newResponse.cacheControlMaxAge = response.cacheControlMaxAge;
|
newResponse.cacheControlMaxAge = response.cacheControlMaxAge;
|
||||||
newResponse.lastModifiedDate = response.lastModifiedDate;
|
newResponse.lastModifiedDate = response.lastModifiedDate;
|
||||||
newResponse.eTag = response.eTag;
|
newResponse.eTag = response.eTag;
|
||||||
DCHECK(newResponse);
|
GWS_DCHECK(newResponse);
|
||||||
return newResponse;
|
return newResponse;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode {
|
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode {
|
||||||
DCHECK(_responseMessage == NULL);
|
GWS_DCHECK(_responseMessage == NULL);
|
||||||
DCHECK((statusCode >= 400) && (statusCode < 600));
|
GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
|
||||||
[self _initializeResponseHeadersWithStatusCode:statusCode];
|
[self _initializeResponseHeadersWithStatusCode:statusCode];
|
||||||
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
|
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
|
||||||
; // Nothing more to do
|
; // Nothing more to do
|
||||||
}];
|
}];
|
||||||
LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
|
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)close {
|
- (void)close {
|
||||||
@@ -825,8 +813,8 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
|||||||
success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
|
success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
|
||||||
}
|
}
|
||||||
if (!success) {
|
if (!success) {
|
||||||
LOG_ERROR(@"Failed saving recorded request: %@", error);
|
GWS_LOG_ERROR(@"Failed saving recorded request: %@", error);
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
unlink([_requestPath fileSystemRepresentation]);
|
unlink([_requestPath fileSystemRepresentation]);
|
||||||
}
|
}
|
||||||
@@ -840,17 +828,17 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
|||||||
success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
|
success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
|
||||||
}
|
}
|
||||||
if (!success) {
|
if (!success) {
|
||||||
LOG_ERROR(@"Failed saving recorded response: %@", error);
|
GWS_LOG_ERROR(@"Failed saving recorded response: %@", error);
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
unlink([_responsePath fileSystemRepresentation]);
|
unlink([_responsePath fileSystemRepresentation]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (_request) {
|
if (_request) {
|
||||||
LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
||||||
} else {
|
} else {
|
||||||
LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ NSString* GCDWebServerUnescapeURLString(NSString* string);
|
|||||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On OS X, returns the IPv4 address as a dotted string of the primary connected
|
* On OS X, returns the IPv4 or IPv6 address as a string of the primary
|
||||||
* service or nil if not available.
|
* connected service or nil if not available.
|
||||||
*
|
*
|
||||||
* On iOS, returns the IPv4 address as a dotted string of the WiFi interface
|
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
|
||||||
* if connected or nil otherwise.
|
* interface if connected or nil otherwise.
|
||||||
*/
|
*/
|
||||||
NSString* GCDWebServerGetPrimaryIPv4Address();
|
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a date into a string using RFC822 formatting.
|
* Converts a date into a string using RFC822 formatting.
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import <TargetConditionals.h>
|
#import <TargetConditionals.h>
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import <MobileCoreServices/MobileCoreServices.h>
|
#import <MobileCoreServices/MobileCoreServices.h>
|
||||||
@@ -45,24 +49,24 @@ static dispatch_queue_t _dateFormatterQueue = NULL;
|
|||||||
|
|
||||||
// TODO: Handle RFC 850 and ANSI C's asctime() format
|
// TODO: Handle RFC 850 and ANSI C's asctime() format
|
||||||
void GCDWebServerInitializeFunctions() {
|
void GCDWebServerInitializeFunctions() {
|
||||||
DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
|
GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
|
||||||
if (_dateFormatterRFC822 == nil) {
|
if (_dateFormatterRFC822 == nil) {
|
||||||
_dateFormatterRFC822 = [[NSDateFormatter alloc] init];
|
_dateFormatterRFC822 = [[NSDateFormatter alloc] init];
|
||||||
_dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
_dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||||
_dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
|
_dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
|
||||||
_dateFormatterRFC822.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
|
_dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
|
||||||
DCHECK(_dateFormatterRFC822);
|
GWS_DCHECK(_dateFormatterRFC822);
|
||||||
}
|
}
|
||||||
if (_dateFormatterISO8601 == nil) {
|
if (_dateFormatterISO8601 == nil) {
|
||||||
_dateFormatterISO8601 = [[NSDateFormatter alloc] init];
|
_dateFormatterISO8601 = [[NSDateFormatter alloc] init];
|
||||||
_dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
_dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||||
_dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
|
_dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
|
||||||
_dateFormatterISO8601.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
|
_dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
|
||||||
DCHECK(_dateFormatterISO8601);
|
GWS_DCHECK(_dateFormatterISO8601);
|
||||||
}
|
}
|
||||||
if (_dateFormatterQueue == NULL) {
|
if (_dateFormatterQueue == NULL) {
|
||||||
_dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
|
_dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
|
||||||
DCHECK(_dateFormatterQueue);
|
GWS_DCHECK(_dateFormatterQueue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +100,6 @@ NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* nam
|
|||||||
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ARC_RELEASE(scanner);
|
|
||||||
return parameter;
|
return parameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +153,7 @@ NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
|
|||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
|
NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
|
||||||
NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
||||||
if (string) {
|
if (string) {
|
||||||
return ARC_AUTORELEASE(string);
|
return string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
|
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
|
||||||
@@ -168,9 +171,9 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
|
|||||||
if (extension.length) {
|
if (extension.length) {
|
||||||
mimeType = [_overrides objectForKey:extension];
|
mimeType = [_overrides objectForKey:extension];
|
||||||
if (mimeType == nil) {
|
if (mimeType == nil) {
|
||||||
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (ARC_BRIDGE CFStringRef)extension, NULL);
|
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
|
||||||
if (uti) {
|
if (uti) {
|
||||||
mimeType = ARC_BRIDGE_RELEASE(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
|
mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
|
||||||
CFRelease(uti);
|
CFRelease(uti);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,11 +182,11 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerEscapeURLString(NSString* string) {
|
NSString* GCDWebServerEscapeURLString(NSString* string) {
|
||||||
return ARC_BRIDGE_RELEASE(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
|
return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerUnescapeURLString(NSString* string) {
|
NSString* GCDWebServerUnescapeURLString(NSString* string) {
|
||||||
return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
|
return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
||||||
@@ -198,16 +201,20 @@ 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:@" "];
|
||||||
|
NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil;
|
||||||
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||||
if (key && value) {
|
NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil;
|
||||||
[parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
|
if (unescapedKey && unescapedValue) {
|
||||||
|
[parameters setObject:unescapedValue forKey:unescapedKey];
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
|
||||||
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([scanner isAtEnd]) {
|
if ([scanner isAtEnd]) {
|
||||||
@@ -215,11 +222,22 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
|||||||
}
|
}
|
||||||
[scanner setScanLocation:([scanner scanLocation] + 1)];
|
[scanner setScanLocation:([scanner scanLocation] + 1)];
|
||||||
}
|
}
|
||||||
ARC_RELEASE(scanner);
|
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerGetPrimaryIPv4Address() {
|
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
|
||||||
|
NSString* string = nil;
|
||||||
|
char hostBuffer[NI_MAXHOST];
|
||||||
|
char serviceBuffer[NI_MAXSERV];
|
||||||
|
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
|
||||||
|
string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer];
|
||||||
|
} else {
|
||||||
|
GWS_DNOT_REACHED();
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
|
||||||
NSString* address = nil;
|
NSString* address = nil;
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#if !TARGET_IPHONE_SIMULATOR
|
#if !TARGET_IPHONE_SIMULATOR
|
||||||
@@ -229,9 +247,9 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
|||||||
const char* primaryInterface = NULL;
|
const char* primaryInterface = NULL;
|
||||||
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
|
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
|
||||||
if (store) {
|
if (store) {
|
||||||
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4"));
|
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
|
||||||
if (info) {
|
if (info) {
|
||||||
primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
|
primaryInterface = [[NSString stringWithString:[(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
|
||||||
CFRelease(info);
|
CFRelease(info);
|
||||||
}
|
}
|
||||||
CFRelease(store);
|
CFRelease(store);
|
||||||
@@ -251,11 +269,8 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) {
|
if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) {
|
||||||
char buffer[NI_MAXHOST];
|
address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
|
||||||
if (getnameinfo(ifap->ifa_addr, ifap->ifa_addr->sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST | NI_NOFQDN) >= 0) {
|
|
||||||
address = [NSString stringWithUTF8String:buffer];
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,7 +282,7 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
|||||||
NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
const char* string = [ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]) UTF8String];
|
const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
unsigned char md5[CC_MD5_DIGEST_LENGTH];
|
unsigned char md5[CC_MD5_DIGEST_LENGTH];
|
||||||
CC_MD5(string, (CC_LONG)strlen(string), md5);
|
CC_MD5(string, (CC_LONG)strlen(string), md5);
|
||||||
|
|||||||
@@ -25,33 +25,12 @@
|
|||||||
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>
|
#import <sys/socket.h>
|
||||||
|
|
||||||
#if __has_feature(objc_arc)
|
/**
|
||||||
#define ARC_BRIDGE __bridge
|
* All GCDWebServer headers.
|
||||||
#define ARC_BRIDGE_RELEASE(__OBJECT__) CFBridgingRelease(__OBJECT__)
|
*/
|
||||||
#define ARC_RETAIN(__OBJECT__) __OBJECT__
|
|
||||||
#define ARC_RELEASE(__OBJECT__)
|
|
||||||
#define ARC_AUTORELEASE(__OBJECT__) __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))
|
|
||||||
#define ARC_DISPATCH_RETAIN(__OBJECT__)
|
|
||||||
#define ARC_DISPATCH_RELEASE(__OBJECT__)
|
|
||||||
#else
|
|
||||||
#define ARC_DISPATCH_RETAIN(__OBJECT__) dispatch_retain(__OBJECT__)
|
|
||||||
#define ARC_DISPATCH_RELEASE(__OBJECT__) dispatch_release(__OBJECT__)
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#define ARC_BRIDGE
|
|
||||||
#define ARC_BRIDGE_RELEASE(__OBJECT__) [(id)__OBJECT__ autorelease]
|
|
||||||
#define ARC_RETAIN(__OBJECT__) [__OBJECT__ retain]
|
|
||||||
#define ARC_RELEASE(__OBJECT__) [__OBJECT__ release]
|
|
||||||
#define ARC_AUTORELEASE(__OBJECT__) [__OBJECT__ autorelease]
|
|
||||||
#define ARC_DEALLOC(__OBJECT__) [__OBJECT__ dealloc]
|
|
||||||
#define ARC_DISPATCH_RETAIN(__OBJECT__) dispatch_retain(__OBJECT__)
|
|
||||||
#define ARC_DISPATCH_RELEASE(__OBJECT__) dispatch_release(__OBJECT__)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerHTTPStatusCodes.h"
|
#import "GCDWebServerHTTPStatusCodes.h"
|
||||||
#import "GCDWebServerFunctions.h"
|
#import "GCDWebServerFunctions.h"
|
||||||
@@ -69,49 +48,131 @@
|
|||||||
#import "GCDWebServerFileResponse.h"
|
#import "GCDWebServerFileResponse.h"
|
||||||
#import "GCDWebServerStreamedResponse.h"
|
#import "GCDWebServerStreamedResponse.h"
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_LOGGING_HEADER__
|
/**
|
||||||
|
* Check if a custom logging facility should be used instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(__GCDWEBSERVER_LOGGING_HEADER__)
|
||||||
|
|
||||||
|
#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__
|
||||||
|
|
||||||
// Define __GCDWEBSERVER_LOGGING_HEADER__ as a preprocessor constant to redirect GCDWebServer logging to your own system
|
|
||||||
#import __GCDWEBSERVER_LOGGING_HEADER__
|
#import __GCDWEBSERVER_LOGGING_HEADER__
|
||||||
|
|
||||||
#else
|
/**
|
||||||
|
* Automatically detect if XLFacility is available and if so use it as a
|
||||||
|
* logging facility.
|
||||||
|
*/
|
||||||
|
|
||||||
extern GCDWebServerLogLevel GCDLogLevel;
|
#elif defined(__has_include) && __has_include("XLFacilityMacros.h")
|
||||||
extern void GCDLogMessage(GCDWebServerLogLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
|
|
||||||
|
|
||||||
#define LOG_VERBOSE(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Verbose) GCDLogMessage(kGCDWebServerLogLevel_Verbose, __VA_ARGS__); } while (0)
|
#define __GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__
|
||||||
#define LOG_INFO(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Info) GCDLogMessage(kGCDWebServerLogLevel_Info, __VA_ARGS__); } while (0)
|
|
||||||
#define LOG_WARNING(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Warning) GCDLogMessage(kGCDWebServerLogLevel_Warning, __VA_ARGS__); } while (0)
|
|
||||||
#define LOG_ERROR(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Error) GCDLogMessage(kGCDWebServerLogLevel_Error, __VA_ARGS__); } while (0)
|
|
||||||
#define LOG_EXCEPTION(__EXCEPTION__) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Exception) GCDLogMessage(kGCDWebServerLogLevel_Exception, @"%@", __EXCEPTION__); } while (0)
|
|
||||||
|
|
||||||
#ifdef NDEBUG
|
#undef XLOG_TAG
|
||||||
|
#define XLOG_TAG @"gcdwebserver.internal"
|
||||||
|
|
||||||
#define DCHECK(__CONDITION__)
|
#import "XLFacilityMacros.h"
|
||||||
#define DNOT_REACHED()
|
|
||||||
#define LOG_DEBUG(...)
|
#define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_EXCEPTION(__EXCEPTION__) XLOG_EXCEPTION(__EXCEPTION__)
|
||||||
|
|
||||||
|
#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__)
|
||||||
|
#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically detect if CocoaLumberJack is available and if so use
|
||||||
|
* it as a logging facility.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#elif defined(__has_include) && __has_include("DDLogMacros.h")
|
||||||
|
|
||||||
|
#import "DDLogMacros.h"
|
||||||
|
|
||||||
|
#define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__
|
||||||
|
|
||||||
|
#undef LOG_LEVEL_DEF
|
||||||
|
#define LOG_LEVEL_DEF GCDWebServerLogLevel
|
||||||
|
extern int GCDWebServerLogLevel;
|
||||||
|
|
||||||
|
#define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_INFO(...) DDLogInfo(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_WARNING(...) DDLogWarn(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_ERROR(...) DDLogError(__VA_ARGS__)
|
||||||
|
#define GWS_LOG_EXCEPTION(__EXCEPTION__) DDLogError(@"%@", __EXCEPTION__)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If all of the above fail, then use GCDWebServer built-in
|
||||||
|
* logging facility.
|
||||||
|
*/
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#define DCHECK(__CONDITION__) \
|
#define __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
|
||||||
|
|
||||||
|
typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
|
||||||
|
kGCDWebServerLoggingLevel_Debug = 0,
|
||||||
|
kGCDWebServerLoggingLevel_Verbose,
|
||||||
|
kGCDWebServerLoggingLevel_Info,
|
||||||
|
kGCDWebServerLoggingLevel_Warning,
|
||||||
|
kGCDWebServerLoggingLevel_Error,
|
||||||
|
kGCDWebServerLoggingLevel_Exception
|
||||||
|
};
|
||||||
|
|
||||||
|
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
|
||||||
|
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
#define GWS_LOG_DEBUG(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); } while (0)
|
||||||
|
#else
|
||||||
|
#define GWS_LOG_DEBUG(...)
|
||||||
|
#endif
|
||||||
|
#define GWS_LOG_VERBOSE(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); } while (0)
|
||||||
|
#define GWS_LOG_INFO(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); } while (0)
|
||||||
|
#define GWS_LOG_WARNING(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); } while (0)
|
||||||
|
#define GWS_LOG_ERROR(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); } while (0)
|
||||||
|
#define GWS_LOG_EXCEPTION(__EXCEPTION__) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Exception) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Exception, @"%@", __EXCEPTION__); } while (0)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consistency check macros used when building Debug only.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
#define GWS_DCHECK(__CONDITION__) \
|
||||||
do { \
|
do { \
|
||||||
if (!(__CONDITION__)) { \
|
if (!(__CONDITION__)) { \
|
||||||
abort(); \
|
abort(); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
#define DNOT_REACHED() abort()
|
#define GWS_DNOT_REACHED() abort()
|
||||||
#define LOG_DEBUG(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Debug) GCDLogMessage(kGCDWebServerLogLevel_Debug, __VA_ARGS__); } while (0)
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define GWS_DCHECK(__CONDITION__)
|
||||||
|
#define GWS_DNOT_REACHED()
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GCDWebServer internal constants and APIs.
|
||||||
|
*/
|
||||||
|
|
||||||
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
||||||
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||||
#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) {
|
||||||
@@ -126,6 +187,7 @@ 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);
|
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||||
|
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
|
||||||
|
|
||||||
@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;
|
||||||
@@ -144,8 +206,7 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
|
|||||||
|
|
||||||
@interface GCDWebServerHandler : NSObject
|
@interface GCDWebServerHandler : NSObject
|
||||||
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
|
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
|
||||||
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
|
@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock;
|
||||||
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerRequest ()
|
@interface GCDWebServerRequest ()
|
||||||
@@ -154,6 +215,7 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
|
|||||||
- (BOOL)performOpen:(NSError**)error;
|
- (BOOL)performOpen:(NSError**)error;
|
||||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
|
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
|
||||||
- (BOOL)performClose:(NSError**)error;
|
- (BOOL)performClose:(NSError**)error;
|
||||||
|
- (void)setAttribute:(id)attribute forKey:(NSString*)key;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerResponse ()
|
@interface GCDWebServerResponse ()
|
||||||
@@ -161,6 +223,6 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
|
|||||||
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
||||||
- (void)prepareForReading;
|
- (void)prepareForReading;
|
||||||
- (BOOL)performOpen:(NSError**)error;
|
- (BOOL)performOpen:(NSError**)error;
|
||||||
- (NSData*)performReadData:(NSError**)error;
|
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
|
||||||
- (void)performClose;
|
- (void)performClose;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -27,6 +27,15 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
|
||||||
|
* with the contents of any regular expression captures done on the request path.
|
||||||
|
*
|
||||||
|
* @warning This attribute will only be set on the request if adding a handler using
|
||||||
|
* -addHandlerForMethod:pathRegex:requestClass:processBlock:.
|
||||||
|
*/
|
||||||
|
extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This protocol is used by the GCDWebServerConnection to communicate with
|
* This protocol is used by the GCDWebServerConnection to communicate with
|
||||||
* the GCDWebServerRequest and write the received HTTP body data.
|
* the GCDWebServerRequest and write the received HTTP body data.
|
||||||
@@ -119,7 +128,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 +145,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;
|
||||||
|
|
||||||
@@ -163,4 +172,11 @@
|
|||||||
*/
|
*/
|
||||||
- (BOOL)hasByteRange;
|
- (BOOL)hasByteRange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an attribute associated with this request using the given key.
|
||||||
|
*
|
||||||
|
* @return The attribute value for the key.
|
||||||
|
*/
|
||||||
|
- (id)attributeForKey:(NSString*)key;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -25,10 +25,16 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import <zlib.h>
|
#import <zlib.h>
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
|
NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerRequestAttribute_RegexCaptures";
|
||||||
|
|
||||||
#define kZlibErrorDomain @"ZlibErrorDomain"
|
#define kZlibErrorDomain @"ZlibErrorDomain"
|
||||||
#define kGZipInitialBufferSize (256 * 1024)
|
#define kGZipInitialBufferSize (256 * 1024)
|
||||||
|
|
||||||
@@ -93,12 +99,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
||||||
DCHECK(!_finished);
|
GWS_DCHECK(!_finished);
|
||||||
_stream.next_in = (Bytef*)data.bytes;
|
_stream.next_in = (Bytef*)data.bytes;
|
||||||
_stream.avail_in = (uInt)data.length;
|
_stream.avail_in = (uInt)data.length;
|
||||||
NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
||||||
if (decodedData == nil) {
|
if (decodedData == nil) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
NSUInteger length = 0;
|
NSUInteger length = 0;
|
||||||
@@ -108,7 +114,6 @@
|
|||||||
_stream.avail_out = (uInt)maxLength;
|
_stream.avail_out = (uInt)maxLength;
|
||||||
int result = inflate(&_stream, Z_NO_FLUSH);
|
int result = inflate(&_stream, Z_NO_FLUSH);
|
||||||
if ((result != Z_OK) && (result != Z_STREAM_END)) {
|
if ((result != Z_OK) && (result != Z_STREAM_END)) {
|
||||||
ARC_RELEASE(decodedData);
|
|
||||||
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
|
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
@@ -123,12 +128,11 @@
|
|||||||
}
|
}
|
||||||
decodedData.length = length;
|
decodedData.length = length;
|
||||||
BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet
|
BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet
|
||||||
ARC_RELEASE(decodedData);
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)close:(NSError**)error {
|
- (BOOL)close:(NSError**)error {
|
||||||
DCHECK(_finished);
|
GWS_DCHECK(_finished);
|
||||||
inflateEnd(&_stream);
|
inflateEnd(&_stream);
|
||||||
return [super close:error];
|
return [super close:error];
|
||||||
}
|
}
|
||||||
@@ -152,6 +156,7 @@
|
|||||||
|
|
||||||
BOOL _opened;
|
BOOL _opened;
|
||||||
NSMutableArray* _decoders;
|
NSMutableArray* _decoders;
|
||||||
|
NSMutableDictionary* _attributes;
|
||||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
@@ -164,19 +169,18 @@
|
|||||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_method = [method copy];
|
_method = [method copy];
|
||||||
_url = ARC_RETAIN(url);
|
_url = url;
|
||||||
_headers = ARC_RETAIN(headers);
|
_headers = headers;
|
||||||
_path = [path copy];
|
_path = [path copy];
|
||||||
_query = ARC_RETAIN(query);
|
_query = query;
|
||||||
|
|
||||||
_type = ARC_RETAIN(GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]));
|
_type = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
|
||||||
_chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
|
_chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
|
||||||
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
|
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
|
||||||
if (lengthHeader) {
|
if (lengthHeader) {
|
||||||
NSInteger length = [lengthHeader integerValue];
|
NSInteger length = [lengthHeader integerValue];
|
||||||
if (_chunked || (length < 0)) {
|
if (_chunked || (length < 0)) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
ARC_RELEASE(self);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
_length = length;
|
_length = length;
|
||||||
@@ -187,23 +191,22 @@
|
|||||||
if (_type == nil) {
|
if (_type == nil) {
|
||||||
_type = kGCDWebServerDefaultMimeType;
|
_type = kGCDWebServerDefaultMimeType;
|
||||||
}
|
}
|
||||||
_length = NSNotFound;
|
_length = NSUIntegerMax;
|
||||||
} else {
|
} else {
|
||||||
if (_type) {
|
if (_type) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
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"];
|
||||||
if (modifiedHeader) {
|
if (modifiedHeader) {
|
||||||
_modifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
|
_modifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
|
||||||
}
|
}
|
||||||
_noneMatch = ARC_RETAIN([_headers objectForKey:@"If-None-Match"]);
|
_noneMatch = [_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,14 +225,14 @@
|
|||||||
_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);
|
GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,24 +241,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
_decoders = [[NSMutableArray alloc] init];
|
_decoders = [[NSMutableArray alloc] init];
|
||||||
|
_attributes = [[NSMutableDictionary alloc] init];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_method);
|
|
||||||
ARC_RELEASE(_url);
|
|
||||||
ARC_RELEASE(_headers);
|
|
||||||
ARC_RELEASE(_path);
|
|
||||||
ARC_RELEASE(_query);
|
|
||||||
ARC_RELEASE(_type);
|
|
||||||
ARC_RELEASE(_modifiedSince);
|
|
||||||
ARC_RELEASE(_noneMatch);
|
|
||||||
ARC_RELEASE(_decoders);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)hasBody {
|
- (BOOL)hasBody {
|
||||||
return _type ? YES : NO;
|
return _type ? YES : NO;
|
||||||
}
|
}
|
||||||
@@ -264,6 +254,10 @@
|
|||||||
return GCDWebServerIsValidByteRange(_range);
|
return GCDWebServerIsValidByteRange(_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (id)attributeForKey:(NSString*)key {
|
||||||
|
return [_attributes objectForKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -281,16 +275,15 @@
|
|||||||
if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) {
|
if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) {
|
||||||
GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer];
|
GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer];
|
||||||
[_decoders addObject:decoder];
|
[_decoders addObject:decoder];
|
||||||
ARC_RELEASE(decoder);
|
|
||||||
_writer = decoder;
|
_writer = decoder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)performOpen:(NSError**)error {
|
- (BOOL)performOpen:(NSError**)error {
|
||||||
DCHECK(_type);
|
GWS_DCHECK(_type);
|
||||||
DCHECK(_writer);
|
GWS_DCHECK(_writer);
|
||||||
if (_opened) {
|
if (_opened) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
_opened = YES;
|
_opened = YES;
|
||||||
@@ -298,15 +291,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
|
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
|
||||||
DCHECK(_opened);
|
GWS_DCHECK(_opened);
|
||||||
return [_writer writeData:data error:error];
|
return [_writer writeData:data error:error];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)performClose:(NSError**)error {
|
- (BOOL)performClose:(NSError**)error {
|
||||||
DCHECK(_opened);
|
GWS_DCHECK(_opened);
|
||||||
return [_writer close:error];
|
return [_writer close:error];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setAttribute:(id)attribute forKey:(NSString*)key {
|
||||||
|
[_attributes setValue:attribute forKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString*)description {
|
- (NSString*)description {
|
||||||
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
|
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
|
||||||
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||||
|
|||||||
@@ -27,6 +27,12 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
|
||||||
|
* GCDWebServerBodyReader object when reading data from it asynchronously.
|
||||||
|
*/
|
||||||
|
typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This protocol is used by the GCDWebServerConnection to communicate with
|
* This protocol is used by the GCDWebServerConnection to communicate with
|
||||||
* the GCDWebServerResponse and read the HTTP body data to send.
|
* the GCDWebServerResponse and read the HTTP body data to send.
|
||||||
@@ -39,6 +45,8 @@
|
|||||||
*/
|
*/
|
||||||
@protocol GCDWebServerBodyReader <NSObject>
|
@protocol GCDWebServerBodyReader <NSObject>
|
||||||
|
|
||||||
|
@required
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called before any body data is sent.
|
* This method is called before any body data is sent.
|
||||||
*
|
*
|
||||||
@@ -61,6 +69,17 @@
|
|||||||
*/
|
*/
|
||||||
- (void)close;
|
- (void)close;
|
||||||
|
|
||||||
|
@optional
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this method is implemented, it will be preferred over -readData:.
|
||||||
|
*
|
||||||
|
* It must call the passed block when data is available, passing a non-empty
|
||||||
|
* NSData if there is body data available, or an empty NSData there is no more
|
||||||
|
* body data, or nil on error and pass an NSError along.
|
||||||
|
*/
|
||||||
|
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,12 +106,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;
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import <zlib.h>
|
#import <zlib.h>
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
@@ -81,7 +85,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;
|
||||||
@@ -107,7 +111,7 @@
|
|||||||
} else {
|
} else {
|
||||||
encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
||||||
if (encodedData == nil) {
|
if (encodedData == nil) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
NSUInteger length = 0;
|
NSUInteger length = 0;
|
||||||
@@ -126,7 +130,6 @@
|
|||||||
if (result == Z_STREAM_END) {
|
if (result == Z_STREAM_END) {
|
||||||
_finished = YES;
|
_finished = YES;
|
||||||
} else if (result != Z_OK) {
|
} else if (result != Z_OK) {
|
||||||
ARC_RELEASE(encodedData);
|
|
||||||
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
|
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@@ -136,11 +139,11 @@
|
|||||||
}
|
}
|
||||||
encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
|
encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
|
||||||
}
|
}
|
||||||
DCHECK(_stream.avail_in == 0);
|
GWS_DCHECK(_stream.avail_in == 0);
|
||||||
} while (length == 0); // Make sure we don't return an empty NSData if not in finished state
|
} while (length == 0); // Make sure we don't return an empty NSData if not in finished state
|
||||||
encodedData.length = length;
|
encodedData.length = length;
|
||||||
}
|
}
|
||||||
return ARC_AUTORELEASE(encodedData);
|
return encodedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)close {
|
- (void)close {
|
||||||
@@ -174,13 +177,13 @@
|
|||||||
gzipContentEncodingEnabled=_gzipped, additionalHeaders=_headers;
|
gzipContentEncodingEnabled=_gzipped, additionalHeaders=_headers;
|
||||||
|
|
||||||
+ (instancetype)response {
|
+ (instancetype)response {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] init]);
|
return [[[self class] alloc] init];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (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];
|
||||||
@@ -189,16 +192,6 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_type);
|
|
||||||
ARC_RELEASE(_lastModified);
|
|
||||||
ARC_RELEASE(_eTag);
|
|
||||||
ARC_RELEASE(_headers);
|
|
||||||
ARC_RELEASE(_encoders);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
|
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
|
||||||
[_headers setValue:value forKey:header];
|
[_headers setValue:value forKey:header];
|
||||||
}
|
}
|
||||||
@@ -208,7 +201,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)usesChunkedTransferEncoding {
|
- (BOOL)usesChunkedTransferEncoding {
|
||||||
return (_type != nil) && (_length == NSNotFound);
|
return (_type != nil) && (_length == NSUIntegerMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
@@ -228,29 +221,33 @@
|
|||||||
if (_gzipped) {
|
if (_gzipped) {
|
||||||
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
|
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
|
||||||
[_encoders addObject:encoder];
|
[_encoders addObject:encoder];
|
||||||
ARC_RELEASE(encoder);
|
|
||||||
_reader = encoder;
|
_reader = encoder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)performOpen:(NSError**)error {
|
- (BOOL)performOpen:(NSError**)error {
|
||||||
DCHECK(_type);
|
GWS_DCHECK(_type);
|
||||||
DCHECK(_reader);
|
GWS_DCHECK(_reader);
|
||||||
if (_opened) {
|
if (_opened) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
_opened = YES;
|
_opened = YES;
|
||||||
return [_reader open:error];
|
return [_reader open:error];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData*)performReadData:(NSError**)error {
|
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
|
||||||
DCHECK(_opened);
|
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
|
||||||
return [_reader readData:error];
|
[_reader asyncReadDataWithCompletion:block];
|
||||||
|
} else {
|
||||||
|
NSError* error = nil;
|
||||||
|
NSData* data = [_reader readData:&error];
|
||||||
|
block(data, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)performClose {
|
- (void)performClose {
|
||||||
DCHECK(_opened);
|
GWS_DCHECK(_opened);
|
||||||
[_reader close];
|
[_reader close];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +256,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];
|
||||||
@@ -283,11 +280,11 @@
|
|||||||
@implementation GCDWebServerResponse (Extensions)
|
@implementation GCDWebServerResponse (Extensions)
|
||||||
|
|
||||||
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
|
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
|
||||||
return ARC_AUTORELEASE([[self alloc] initWithStatusCode:statusCode]);
|
return [[self alloc] initWithStatusCode:statusCode];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
|
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
|
||||||
return ARC_AUTORELEASE([[self alloc] initWithRedirect:location permanent:permanent]);
|
return [[self alloc] initWithRedirect:location permanent:permanent];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithStatusCode:(NSInteger)statusCode {
|
- (instancetype)initWithStatusCode:(NSInteger)statusCode {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerDataRequest () {
|
@interface GCDWebServerDataRequest () {
|
||||||
@@ -40,16 +44,8 @@
|
|||||||
|
|
||||||
@synthesize data=_data;
|
@synthesize data=_data;
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_data);
|
|
||||||
ARC_RELEASE(_text);
|
|
||||||
ARC_RELEASE(_jsonObject);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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];
|
||||||
@@ -89,7 +85,7 @@
|
|||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
||||||
_text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
_text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _text;
|
return _text;
|
||||||
@@ -97,10 +93,11 @@
|
|||||||
|
|
||||||
- (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);
|
||||||
_jsonObject = ARC_RETAIN([NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]);
|
if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) {
|
||||||
|
_jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL];
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _jsonObject;
|
return _jsonObject;
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerFileRequest () {
|
@interface GCDWebServerFileRequest () {
|
||||||
@@ -40,16 +44,13 @@
|
|||||||
|
|
||||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
||||||
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
||||||
_temporaryPath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
|
_temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
unlink([_temporaryPath fileSystemRepresentation]);
|
unlink([_temporaryPath fileSystemRepresentation]);
|
||||||
ARC_RELEASE(_temporaryPath);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
#define kMultiPartBufferSize (256 * 1024)
|
#define kMultiPartBufferSize (256 * 1024)
|
||||||
@@ -63,19 +67,11 @@ static NSData* _dashNewlineData = nil;
|
|||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_controlName = [name copy];
|
_controlName = [name copy];
|
||||||
_contentType = [type copy];
|
_contentType = [type copy];
|
||||||
_mimeType = ARC_RETAIN(GCDWebServerTruncateHeaderValue(_contentType));
|
_mimeType = GCDWebServerTruncateHeaderValue(_contentType);
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_controlName);
|
|
||||||
ARC_RELEASE(_contentType);
|
|
||||||
ARC_RELEASE(_mimeType);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPartArgument () {
|
@interface GCDWebServerMultiPartArgument () {
|
||||||
@@ -91,7 +87,7 @@ static NSData* _dashNewlineData = nil;
|
|||||||
|
|
||||||
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
|
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
|
||||||
if ((self = [super initWithControlName:name contentType:type])) {
|
if ((self = [super initWithControlName:name contentType:type])) {
|
||||||
_data = ARC_RETAIN(data);
|
_data = data;
|
||||||
|
|
||||||
if ([self.contentType hasPrefix:@"text/"]) {
|
if ([self.contentType hasPrefix:@"text/"]) {
|
||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
||||||
@@ -101,13 +97,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_data);
|
|
||||||
ARC_RELEASE(_string);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)description {
|
- (NSString*)description {
|
||||||
return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length];
|
return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length];
|
||||||
}
|
}
|
||||||
@@ -135,11 +124,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
unlink([_temporaryPath fileSystemRepresentation]);
|
unlink([_temporaryPath fileSystemRepresentation]);
|
||||||
|
|
||||||
ARC_RELEASE(_fileName);
|
|
||||||
ARC_RELEASE(_temporaryPath);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)description {
|
- (NSString*)description {
|
||||||
@@ -171,30 +155,29 @@ static NSData* _dashNewlineData = nil;
|
|||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
if (_newlineData == nil) {
|
if (_newlineData == nil) {
|
||||||
_newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
|
_newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
|
||||||
DCHECK(_newlineData);
|
GWS_DCHECK(_newlineData);
|
||||||
}
|
}
|
||||||
if (_newlinesData == nil) {
|
if (_newlinesData == nil) {
|
||||||
_newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
|
_newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
|
||||||
DCHECK(_newlinesData);
|
GWS_DCHECK(_newlinesData);
|
||||||
}
|
}
|
||||||
if (_dashNewlineData == nil) {
|
if (_dashNewlineData == nil) {
|
||||||
_dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
|
_dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
|
||||||
DCHECK(_dashNewlineData);
|
GWS_DCHECK(_dashNewlineData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files {
|
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files {
|
||||||
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
|
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
|
||||||
if (data == nil) {
|
if (data == nil) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
ARC_RELEASE(self);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_boundary = ARC_RETAIN(data);
|
_boundary = data;
|
||||||
_defaultcontrolName = ARC_RETAIN(name);
|
_defaultcontrolName = name;
|
||||||
_arguments = ARC_RETAIN(arguments);
|
_arguments = arguments;
|
||||||
_files = ARC_RETAIN(files);
|
_files = files;
|
||||||
_data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
|
_data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
|
||||||
_state = kParserState_Start;
|
_state = kParserState_Start;
|
||||||
}
|
}
|
||||||
@@ -202,23 +185,10 @@ static NSData* _dashNewlineData = nil;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
ARC_RELEASE(_boundary);
|
|
||||||
ARC_RELEASE(_defaultcontrolName);
|
|
||||||
ARC_RELEASE(_data);
|
|
||||||
ARC_RELEASE(_arguments);
|
|
||||||
ARC_RELEASE(_files);
|
|
||||||
|
|
||||||
ARC_RELEASE(_controlName);
|
|
||||||
ARC_RELEASE(_fileName);
|
|
||||||
ARC_RELEASE(_contentType);
|
|
||||||
if (_tmpFile > 0) {
|
if (_tmpFile > 0) {
|
||||||
close(_tmpFile);
|
close(_tmpFile);
|
||||||
unlink([_tmpPath fileSystemRepresentation]);
|
unlink([_tmpPath fileSystemRepresentation]);
|
||||||
}
|
}
|
||||||
ARC_RELEASE(_tmpPath);
|
|
||||||
ARC_RELEASE(_subParser);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
|
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
|
||||||
@@ -229,15 +199,10 @@ static NSData* _dashNewlineData = nil;
|
|||||||
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
|
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
|
||||||
if (range.location != NSNotFound) {
|
if (range.location != NSNotFound) {
|
||||||
|
|
||||||
ARC_RELEASE(_controlName);
|
|
||||||
_controlName = nil;
|
_controlName = nil;
|
||||||
ARC_RELEASE(_fileName);
|
|
||||||
_fileName = nil;
|
_fileName = nil;
|
||||||
ARC_RELEASE(_contentType);
|
|
||||||
_contentType = nil;
|
_contentType = nil;
|
||||||
ARC_RELEASE(_tmpPath);
|
|
||||||
_tmpPath = nil;
|
_tmpPath = nil;
|
||||||
ARC_RELEASE(_subParser);
|
|
||||||
_subParser = nil;
|
_subParser = nil;
|
||||||
NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding];
|
NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding];
|
||||||
if (headers) {
|
if (headers) {
|
||||||
@@ -247,35 +212,34 @@ static NSData* _dashNewlineData = nil;
|
|||||||
NSString* name = [header substringToIndex:subRange.location];
|
NSString* name = [header substringToIndex:subRange.location];
|
||||||
NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) {
|
if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) {
|
||||||
_contentType = ARC_RETAIN(GCDWebServerNormalizeHeaderValue(value));
|
_contentType = GCDWebServerNormalizeHeaderValue(value);
|
||||||
} else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) {
|
} else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) {
|
||||||
NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value);
|
NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value);
|
||||||
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
|
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
|
||||||
_controlName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"));
|
_controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name");
|
||||||
_fileName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"));
|
_fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
|
||||||
} else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) {
|
} else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) {
|
||||||
_controlName = ARC_RETAIN(_defaultcontrolName);
|
_controlName = _defaultcontrolName;
|
||||||
_fileName = ARC_RETAIN(GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"));
|
_fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_contentType == nil) {
|
if (_contentType == nil) {
|
||||||
_contentType = @"text/plain";
|
_contentType = @"text/plain";
|
||||||
}
|
}
|
||||||
ARC_RELEASE(headers);
|
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'");
|
GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'");
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
if (_controlName) {
|
if (_controlName) {
|
||||||
if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) {
|
if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) {
|
||||||
NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary");
|
NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary");
|
||||||
_subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files];
|
_subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files];
|
||||||
if (_subParser == nil) {
|
if (_subParser == nil) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
} else if (_fileName) {
|
} else if (_fileName) {
|
||||||
@@ -284,12 +248,12 @@ static NSData* _dashNewlineData = nil;
|
|||||||
if (_tmpFile > 0) {
|
if (_tmpFile > 0) {
|
||||||
_tmpPath = [path copy];
|
_tmpPath = [path copy];
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,10 +275,9 @@ static NSData* _dashNewlineData = nil;
|
|||||||
NSUInteger dataLength = range.location - 2;
|
NSUInteger dataLength = range.location - 2;
|
||||||
if (_subParser) {
|
if (_subParser) {
|
||||||
if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) {
|
if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
ARC_RELEASE(_subParser);
|
|
||||||
_subParser = nil;
|
_subParser = nil;
|
||||||
} else if (_tmpPath) {
|
} else if (_tmpPath) {
|
||||||
ssize_t result = write(_tmpFile, dataBytes, dataLength);
|
ssize_t result = write(_tmpFile, dataBytes, dataLength);
|
||||||
@@ -323,23 +286,19 @@ static NSData* _dashNewlineData = nil;
|
|||||||
_tmpFile = 0;
|
_tmpFile = 0;
|
||||||
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
|
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
|
||||||
[_files addObject:file];
|
[_files addObject:file];
|
||||||
ARC_RELEASE(file);
|
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
ARC_RELEASE(_tmpPath);
|
|
||||||
_tmpPath = nil;
|
_tmpPath = nil;
|
||||||
} else {
|
} else {
|
||||||
NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength];
|
NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength];
|
||||||
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data];
|
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data];
|
||||||
[_arguments addObject:argument];
|
[_arguments addObject:argument];
|
||||||
ARC_RELEASE(argument);
|
|
||||||
ARC_RELEASE(data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +318,7 @@ static NSData* _dashNewlineData = nil;
|
|||||||
if ([_subParser appendBytes:_data.bytes length:length]) {
|
if ([_subParser appendBytes:_data.bytes length:length]) {
|
||||||
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
} else if (_tmpPath) {
|
} else if (_tmpPath) {
|
||||||
@@ -367,7 +326,7 @@ static NSData* _dashNewlineData = nil;
|
|||||||
if (result == (ssize_t)length) {
|
if (result == (ssize_t)length) {
|
||||||
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
success = NO;
|
success = NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,13 +372,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_arguments);
|
|
||||||
ARC_RELEASE(_files);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
|
NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
|
||||||
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
|
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
|
||||||
@@ -440,7 +392,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
|
|
||||||
- (BOOL)close:(NSError**)error {
|
- (BOOL)close:(NSError**)error {
|
||||||
BOOL atEnd = [_parser isAtEnd];
|
BOOL atEnd = [_parser isAtEnd];
|
||||||
ARC_RELEASE(_parser);
|
|
||||||
_parser = nil;
|
_parser = nil;
|
||||||
if (!atEnd) {
|
if (!atEnd) {
|
||||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}];
|
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}];
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerURLEncodedFormRequest () {
|
@interface GCDWebServerURLEncodedFormRequest () {
|
||||||
@@ -41,12 +45,6 @@
|
|||||||
return @"application/x-www-form-urlencoded";
|
return @"application/x-www-form-urlencoded";
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_arguments);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)close:(NSError**)error {
|
- (BOOL)close:(NSError**)error {
|
||||||
if (![super close:error]) {
|
if (![super close:error]) {
|
||||||
return NO;
|
return NO;
|
||||||
@@ -54,9 +52,8 @@
|
|||||||
|
|
||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
||||||
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
||||||
_arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
|
_arguments = GCDWebServerParseURLEncodedForm(string);
|
||||||
DCHECK(_arguments);
|
GWS_DCHECK(_arguments);
|
||||||
ARC_RELEASE(string);
|
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerDataResponse () {
|
@interface GCDWebServerDataResponse () {
|
||||||
@@ -37,18 +41,17 @@
|
|||||||
@implementation GCDWebServerDataResponse
|
@implementation GCDWebServerDataResponse
|
||||||
|
|
||||||
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
|
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithData:data contentType:type]);
|
return [[[self class] alloc] initWithData:data contentType:type];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
|
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
|
||||||
if (data == nil) {
|
if (data == nil) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
ARC_RELEASE(self);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_data = ARC_RETAIN(data);
|
_data = data;
|
||||||
|
|
||||||
self.contentType = type;
|
self.contentType = type;
|
||||||
self.contentLength = data.length;
|
self.contentLength = data.length;
|
||||||
@@ -56,12 +59,6 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_data);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSData*)readData:(NSError**)error {
|
- (NSData*)readData:(NSError**)error {
|
||||||
NSData* data;
|
NSData* data;
|
||||||
if (_done) {
|
if (_done) {
|
||||||
@@ -85,30 +82,29 @@
|
|||||||
@implementation GCDWebServerDataResponse (Extensions)
|
@implementation GCDWebServerDataResponse (Extensions)
|
||||||
|
|
||||||
+ (instancetype)responseWithText:(NSString*)text {
|
+ (instancetype)responseWithText:(NSString*)text {
|
||||||
return ARC_AUTORELEASE([[self alloc] initWithText:text]);
|
return [[self alloc] initWithText:text];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithHTML:(NSString*)html {
|
+ (instancetype)responseWithHTML:(NSString*)html {
|
||||||
return ARC_AUTORELEASE([[self alloc] initWithHTML:html]);
|
return [[self alloc] initWithHTML:html];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
|
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
|
||||||
return ARC_AUTORELEASE([[self alloc] initWithHTMLTemplate:path variables:variables]);
|
return [[self alloc] initWithHTMLTemplate:path variables:variables];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithJSONObject:(id)object {
|
+ (instancetype)responseWithJSONObject:(id)object {
|
||||||
return ARC_AUTORELEASE([[self alloc] initWithJSONObject:object]);
|
return [[self alloc] initWithJSONObject:object];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type {
|
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type {
|
||||||
return ARC_AUTORELEASE([[self alloc] initWithJSONObject:object contentType:type]);
|
return [[self alloc] initWithJSONObject:object contentType:type];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithText:(NSString*)text {
|
- (instancetype)initWithText:(NSString*)text {
|
||||||
NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding];
|
NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
if (data == nil) {
|
if (data == nil) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
ARC_RELEASE(self);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [self initWithData:data contentType:@"text/plain; charset=utf-8"];
|
return [self initWithData:data contentType:@"text/plain; charset=utf-8"];
|
||||||
@@ -117,8 +113,7 @@
|
|||||||
- (instancetype)initWithHTML:(NSString*)html {
|
- (instancetype)initWithHTML:(NSString*)html {
|
||||||
NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding];
|
NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
if (data == nil) {
|
if (data == nil) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
ARC_RELEASE(self);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [self initWithData:data contentType:@"text/html; charset=utf-8"];
|
return [self initWithData:data contentType:@"text/html; charset=utf-8"];
|
||||||
@@ -130,7 +125,6 @@
|
|||||||
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
|
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
|
||||||
}];
|
}];
|
||||||
id response = [self initWithHTML:html];
|
id response = [self initWithHTML:html];
|
||||||
ARC_RELEASE(html);
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +135,6 @@
|
|||||||
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type {
|
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type {
|
||||||
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
|
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
|
||||||
if (data == nil) {
|
if (data == nil) {
|
||||||
ARC_RELEASE(self);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
return [self initWithData:data contentType:type];
|
return [self initWithData:data contentType:type];
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerErrorResponse ()
|
@interface GCDWebServerErrorResponse ()
|
||||||
@@ -34,37 +38,37 @@
|
|||||||
@implementation GCDWebServerErrorResponse
|
@implementation GCDWebServerErrorResponse
|
||||||
|
|
||||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]);
|
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]);
|
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]);
|
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]);
|
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||||
va_end(arguments);
|
va_end(arguments);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -82,12 +86,11 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
|||||||
if ((self = [self initWithHTML:html])) {
|
if ((self = [self initWithHTML:html])) {
|
||||||
self.statusCode = statusCode;
|
self.statusCode = statusCode;
|
||||||
}
|
}
|
||||||
ARC_RELEASE(message);
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||||
@@ -96,7 +99,7 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||||
@@ -105,7 +108,7 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||||
@@ -114,7 +117,7 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||||
va_list arguments;
|
va_list arguments;
|
||||||
va_start(arguments, format);
|
va_start(arguments, format);
|
||||||
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import <sys/stat.h>
|
#import <sys/stat.h>
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
@@ -43,27 +47,27 @@
|
|||||||
@implementation GCDWebServerFileResponse
|
@implementation GCDWebServerFileResponse
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path {
|
+ (instancetype)responseWithFile:(NSString*)path {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path]);
|
return [[[self class] alloc] initWithFile:path];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path isAttachment:attachment]);
|
return [[[self class] alloc] initWithFile:path isAttachment:attachment];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range {
|
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range]);
|
return [[[self class] alloc] initWithFile:path byteRange:range];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]);
|
return [[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (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 {
|
||||||
@@ -77,35 +81,42 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
|||||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
||||||
struct stat info;
|
struct stat info;
|
||||||
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
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);
|
GWS_DNOT_REACHED();
|
||||||
range.length = MIN(range.length, (NSUInteger)info.st_size - range.location);
|
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);
|
|
||||||
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);
|
GWS_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) {
|
||||||
@@ -115,26 +126,19 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
|||||||
if (lossyFileName) {
|
if (lossyFileName) {
|
||||||
NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, GCDWebServerEscapeURLString(fileName)];
|
NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, GCDWebServerEscapeURLString(fileName)];
|
||||||
[self setValue:value forAdditionalHeader:@"Content-Disposition"];
|
[self setValue:value forAdditionalHeader:@"Content-Disposition"];
|
||||||
ARC_RELEASE(lossyFileName);
|
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
GWS_DNOT_REACHED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
ARC_RELEASE(_path);
|
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
- (BOOL)open:(NSError**)error {
|
||||||
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
|
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
|
||||||
if (_file <= 0) {
|
if (_file <= 0) {
|
||||||
@@ -161,7 +165,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
|||||||
[data setLength:result];
|
[data setLength:result];
|
||||||
_size -= result;
|
_size -= result;
|
||||||
}
|
}
|
||||||
return ARC_AUTORELEASE(data);
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)close {
|
- (void)close {
|
||||||
|
|||||||
@@ -25,14 +25,29 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "GCDWebServerStreamedResponse.h"
|
#import "GCDWebServerResponse.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerStreamingBlock is called to stream the data for the HTTP body.
|
* The GCDWebServerStreamBlock is called to stream the data for the HTTP body.
|
||||||
* The block must return empty NSData when done or nil on error and set the
|
* The block must return empty NSData when done or nil on error and set the
|
||||||
* "error" argument which is guaranteed to be non-NULL.
|
* "error" argument which is guaranteed to be non-NULL.
|
||||||
*/
|
*/
|
||||||
typedef NSData* (^GCDWebServerStreamingBlock)(NSError** error);
|
typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock
|
||||||
|
* except the streamed data can be returned at a later time allowing for
|
||||||
|
* truly asynchronous generation of the data.
|
||||||
|
*
|
||||||
|
* The block must return empty NSData when done or nil on error and set the
|
||||||
|
* "error" argument which is guaranteed to be non-NULL.
|
||||||
|
*
|
||||||
|
* The block must regularly call "completionBlock" passing new streamed data.
|
||||||
|
* Eventually it must call "completionBlock" passing an empty NSData indicating
|
||||||
|
* the end of the stream has been reached, or pass nil and an NSError in case of
|
||||||
|
* error.
|
||||||
|
*/
|
||||||
|
typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams
|
* The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams
|
||||||
@@ -43,11 +58,21 @@ typedef NSData* (^GCDWebServerStreamingBlock)(NSError** error);
|
|||||||
/**
|
/**
|
||||||
* Creates a response with streamed data and a given content type.
|
* Creates a response with streamed data and a given content type.
|
||||||
*/
|
*/
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block;
|
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a response with async streamed data and a given content type.
|
||||||
|
*/
|
||||||
|
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a response with streamed data and a given content type.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is the designated initializer for the class.
|
* This method is the designated initializer for the class.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block;
|
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -25,21 +25,39 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebServer requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
@interface GCDWebServerStreamedResponse () {
|
@interface GCDWebServerStreamedResponse () {
|
||||||
@private
|
@private
|
||||||
GCDWebServerStreamingBlock _block;
|
GCDWebServerAsyncStreamBlock _block;
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerStreamedResponse
|
@implementation GCDWebServerStreamedResponse
|
||||||
|
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block {
|
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type streamBlock:block]);
|
return [[[self class] alloc] initWithContentType:type streamBlock:block];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block {
|
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
||||||
|
return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
||||||
|
return [self initWithContentType:type asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
NSData* data = block(&error);
|
||||||
|
completionBlock(data, error);
|
||||||
|
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_block = [block copy];
|
_block = [block copy];
|
||||||
|
|
||||||
@@ -48,14 +66,8 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
|
||||||
ARC_RELEASE(_block);
|
_block(block);
|
||||||
|
|
||||||
ARC_DEALLOC(super);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSData*)readData:(NSError**)error {
|
|
||||||
return _block(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)description {
|
- (NSString*)description {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<div id="alerts"></div>
|
<div id="alerts"></div>
|
||||||
|
|
||||||
<div class="btn-toolbar">
|
<div class="btn-toolbar">
|
||||||
<button type="button" class="btn btn-primary fileinput-button">
|
<button type="button" class="btn btn-primary fileinput-button" id="upload-file">
|
||||||
<span class="glyphicon glyphicon-upload"></span> Upload Files…
|
<span class="glyphicon glyphicon-upload"></span> Upload Files…
|
||||||
<input id="fileupload" type="file" name="files[]" multiple>
|
<input id="fileupload" type="file" name="files[]" multiple>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -178,6 +178,17 @@ function _reload(path) {
|
|||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// Workaround Firefox and IE not showing file selection dialog when clicking on "upload-file" <button>
|
||||||
|
// Making it a <div> instead also works but then it the button doesn't work anymore with tab selection or accessibility
|
||||||
|
$("#upload-file").click(function(event) {
|
||||||
|
$("#fileupload").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent event bubbling when using workaround above
|
||||||
|
$("#fileupload").click(function(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
$("#fileupload").fileupload({
|
$("#fileupload").fileupload({
|
||||||
dropZone: $(document),
|
dropZone: $(document),
|
||||||
pasteZone: null,
|
pasteZone: null,
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if !__has_feature(objc_arc)
|
||||||
|
#error GCDWebUploader requires ARC
|
||||||
|
#endif
|
||||||
|
|
||||||
#import <TargetConditionals.h>
|
#import <TargetConditionals.h>
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
@@ -57,6 +61,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 +94,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 +138,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 +169,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 +193,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 +230,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 +259,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:@"."]) {
|
||||||
@@ -278,17 +296,10 @@
|
|||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
|
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
|
||||||
if (siteBundle == nil) {
|
if (siteBundle == nil) {
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
[self release];
|
|
||||||
#endif
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
||||||
#if __has_feature(objc_arc)
|
|
||||||
GCDWebUploader* __unsafe_unretained server = self;
|
GCDWebUploader* __unsafe_unretained server = self;
|
||||||
#else
|
|
||||||
__block GCDWebUploader* server = self;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Resource files
|
// Resource files
|
||||||
[self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
|
[self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
|
||||||
@@ -299,15 +310,14 @@
|
|||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
NSString* device = [[UIDevice currentDevice] name];
|
NSString* device = [[UIDevice currentDevice] name];
|
||||||
#else
|
#else
|
||||||
#if __has_feature(objc_arc)
|
|
||||||
NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
|
NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
|
||||||
#else
|
|
||||||
NSString* device = [(id)SCDynamicStoreCopyComputerName(NULL, NULL) autorelease];
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
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];
|
||||||
@@ -384,22 +394,6 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
[_uploadDirectory release];
|
|
||||||
[_allowedExtensions release];
|
|
||||||
[_title release];
|
|
||||||
[_header release];
|
|
||||||
[_prologue release];
|
|
||||||
[_epilogue release];
|
|
||||||
[_footer release];
|
|
||||||
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GCDWebUploader (Subclassing)
|
@implementation GCDWebUploader (Subclassing)
|
||||||
|
|||||||
62
Mac/main.m
62
Mac/main.m
@@ -52,6 +52,7 @@ typedef enum {
|
|||||||
kMode_WebDAV,
|
kMode_WebDAV,
|
||||||
kMode_WebUploader,
|
kMode_WebUploader,
|
||||||
kMode_StreamingResponse,
|
kMode_StreamingResponse,
|
||||||
|
kMode_AsyncResponse
|
||||||
} Mode;
|
} Mode;
|
||||||
|
|
||||||
@interface Delegate : NSObject <GCDWebServerDelegate, GCDWebDAVServerDelegate, GCDWebUploaderDelegate>
|
@interface Delegate : NSObject <GCDWebServerDelegate, GCDWebDAVServerDelegate, GCDWebUploaderDelegate>
|
||||||
@@ -140,9 +141,10 @@ int main(int argc, const char* argv[]) {
|
|||||||
NSString* authenticationRealm = nil;
|
NSString* authenticationRealm = nil;
|
||||||
NSString* authenticationUser = nil;
|
NSString* authenticationUser = nil;
|
||||||
NSString* authenticationPassword = nil;
|
NSString* authenticationPassword = nil;
|
||||||
|
BOOL bindToLocalhost = NO;
|
||||||
|
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
|
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password] [--localhost]\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] != '-') {
|
||||||
@@ -164,6 +166,8 @@ int main(int argc, const char* argv[]) {
|
|||||||
mode = kMode_WebUploader;
|
mode = kMode_WebUploader;
|
||||||
} else if (!strcmp(argv[i], "streamingResponse")) {
|
} else if (!strcmp(argv[i], "streamingResponse")) {
|
||||||
mode = kMode_StreamingResponse;
|
mode = kMode_StreamingResponse;
|
||||||
|
} else if (!strcmp(argv[i], "asyncResponse")) {
|
||||||
|
mode = kMode_AsyncResponse;
|
||||||
}
|
}
|
||||||
} else if (!strcmp(argv[i], "-record")) {
|
} else if (!strcmp(argv[i], "-record")) {
|
||||||
recording = YES;
|
recording = YES;
|
||||||
@@ -185,6 +189,8 @@ int main(int argc, const char* argv[]) {
|
|||||||
} else if (!strcmp(argv[i], "-authenticationPassword") && (i + 1 < argc)) {
|
} else if (!strcmp(argv[i], "-authenticationPassword") && (i + 1 < argc)) {
|
||||||
++i;
|
++i;
|
||||||
authenticationPassword = [NSString stringWithUTF8String:argv[i]];
|
authenticationPassword = [NSString stringWithUTF8String:argv[i]];
|
||||||
|
} else if (!strcmp(argv[i], "--localhost")) {
|
||||||
|
bindToLocalhost = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,7 +314,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
fprintf(stdout, "Running in Streaming Response mode");
|
fprintf(stdout, "Running in Streaming Response mode");
|
||||||
webServer = [[GCDWebServer alloc] init];
|
webServer = [[GCDWebServer alloc] init];
|
||||||
[webServer addHandlerForMethod:@"GET"
|
[webServer addHandlerForMethod:@"GET"
|
||||||
path:@"/"
|
path:@"/sync"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
@@ -324,24 +330,58 @@ int main(int argc, const char* argv[]) {
|
|||||||
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
}];
|
||||||
|
[webServer addHandlerForMethod:@"GET"
|
||||||
|
path:@"/async"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
|
__block int countDown = 10;
|
||||||
|
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
||||||
|
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
|
||||||
|
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
|
||||||
|
completionBlock(data, nil);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}];
|
||||||
|
|
||||||
|
}];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test async responses
|
||||||
|
case kMode_AsyncResponse: {
|
||||||
|
fprintf(stdout, "Running in Async Response mode");
|
||||||
|
webServer = [[GCDWebServer alloc] init];
|
||||||
|
[webServer addHandlerForMethod:@"GET"
|
||||||
|
path:@"/"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
|
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
|
||||||
|
completionBlock(response);
|
||||||
|
});
|
||||||
|
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#if __has_feature(objc_arc)
|
|
||||||
fprintf(stdout, " (ARC is ON)\n");
|
|
||||||
#else
|
|
||||||
fprintf(stdout, " (ARC is OFF)\n");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (webServer) {
|
if (webServer) {
|
||||||
Delegate* delegate = [[Delegate alloc] init];
|
Delegate* delegate = [[Delegate alloc] init];
|
||||||
webServer.delegate = delegate;
|
|
||||||
if (testDirectory) {
|
if (testDirectory) {
|
||||||
|
#if DEBUG
|
||||||
|
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;
|
||||||
@@ -349,6 +389,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
fprintf(stdout, "\n");
|
fprintf(stdout, "\n");
|
||||||
NSMutableDictionary* options = [NSMutableDictionary dictionary];
|
NSMutableDictionary* options = [NSMutableDictionary dictionary];
|
||||||
[options setObject:@8080 forKey:GCDWebServerOption_Port];
|
[options setObject:@8080 forKey:GCDWebServerOption_Port];
|
||||||
|
[options setObject:@(bindToLocalhost) forKey:GCDWebServerOption_BindToLocalhost];
|
||||||
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
|
[options setObject:@"" forKey:GCDWebServerOption_BonjourName];
|
||||||
if (authenticationUser && authenticationPassword) {
|
if (authenticationUser && authenticationPassword) {
|
||||||
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
|
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
|
||||||
@@ -363,10 +404,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
result = 0;
|
result = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !__has_feature(objc_arc)
|
webServer.delegate = nil;
|
||||||
[webServer release];
|
|
||||||
[delegate release];
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
143
README.md
143
README.md
@@ -3,7 +3,10 @@ Overview
|
|||||||
|
|
||||||
[](https://travis-ci.org/swisspol/GCDWebServer)
|
[](https://travis-ci.org/swisspol/GCDWebServer)
|
||||||
[](http://cocoadocs.org/docsets/GCDWebServer)
|
[](http://cocoadocs.org/docsets/GCDWebServer)
|
||||||
[](http://cocoadocs.org/docsets/GCDWebServer)
|
[](https://github.com/swisspol/GCDWebServer)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
*ANNOUNCEMENT: If you like GCDWebServer, check out [XLFacility](https://github.com/swisspol/XLFacility), an elegant and powerful logging facility for OS X & iOS by the same author and also open-source. XLFacility can be used seemlessly to handle logging from GCDWebServer (see "Logging in GCDWebServer" below).*
|
||||||
|
|
||||||
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in OS X & iOS apps. It was written from scratch with the following goals in mind:
|
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in OS X & iOS apps. It was written from scratch with the following goals in mind:
|
||||||
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
|
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
|
||||||
@@ -13,6 +16,7 @@ GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to b
|
|||||||
* Available under a friendly [New BSD License](LICENSE)
|
* Available under a friendly [New BSD License](LICENSE)
|
||||||
|
|
||||||
Extra built-in features:
|
Extra built-in features:
|
||||||
|
* Allow implementation of fully asynchronous handlers of incoming HTTP requests
|
||||||
* Minimize memory usage with disk streaming of large HTTP request or response bodies
|
* Minimize memory usage with disk streaming of large HTTP request or response bodies
|
||||||
* Parser for [web forms](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4) submitted using "application/x-www-form-urlencoded" or "multipart/form-data" encodings (including file uploads)
|
* Parser for [web forms](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4) submitted using "application/x-www-form-urlencoded" or "multipart/form-data" encodings (including file uploads)
|
||||||
* [JSON](http://www.json.org/) parsing and serialization for request and response HTTP bodies
|
* [JSON](http://www.json.org/) parsing and serialization for request and response HTTP bodies
|
||||||
@@ -21,6 +25,7 @@ Extra built-in features:
|
|||||||
* [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
|
* [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
|
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
|
||||||
|
* Full support for both IPv4 and IPv6
|
||||||
|
|
||||||
Included extensions:
|
Included extensions:
|
||||||
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
|
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
|
||||||
@@ -33,6 +38,7 @@ What's not supported (but not really required from an embedded HTTP server):
|
|||||||
Requirements:
|
Requirements:
|
||||||
* OS X 10.7 or later (x86_64)
|
* OS X 10.7 or later (x86_64)
|
||||||
* iOS 5.0 or later (armv7, armv7s or arm64)
|
* iOS 5.0 or later (armv7, armv7s or arm64)
|
||||||
|
* ARC memory management only (if you need MRC support use GCDWebServer 3.1 and earlier)
|
||||||
|
|
||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
@@ -41,15 +47,15 @@ Download or check out the [latest release](https://github.com/swisspol/GCDWebSer
|
|||||||
|
|
||||||
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:
|
||||||
```
|
```
|
||||||
pod "GCDWebServer", "~> 2.0"
|
pod "GCDWebServer", "~> 3.0"
|
||||||
```
|
```
|
||||||
If you want to use GCDWebUploader, use this line instead:
|
If you want to use GCDWebUploader, use this line instead:
|
||||||
```
|
```
|
||||||
pod "GCDWebServer/WebUploader", "~> 2.0"
|
pod "GCDWebServer/WebUploader", "~> 3.0"
|
||||||
```
|
```
|
||||||
Or this line for GCDWebDAVServer:
|
Or this line for GCDWebDAVServer:
|
||||||
```
|
```
|
||||||
pod "GCDWebServer/WebDAV", "~> 2.0"
|
pod "GCDWebServer/WebDAV", "~> 3.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
Hello World
|
Hello World
|
||||||
@@ -77,8 +83,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 +98,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,11 +121,96 @@ 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
Asynchronous HTTP Responses
|
||||||
|
===========================
|
||||||
|
|
||||||
|
New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
|
||||||
|
|
||||||
|
**(Synchronous version)** The handler blocks while generating the HTTP response:
|
||||||
|
```objectivec
|
||||||
|
[webServer addDefaultHandlerForMethod:@"GET"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
|
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
**(Asynchronous version)** The handler returns immediately and calls back GCDWebServer later with the generated HTTP response:
|
||||||
|
```objectivec
|
||||||
|
[webServer addDefaultHandlerForMethod:@"GET"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
|
|
||||||
|
// Do some async operation like network access or file I/O (simulated here using dispatch_after())
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
|
||||||
|
completionBlock(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
**(Advanced asynchronous version)** The handler returns immediately a streamed HTTP response which itself generates its contents asynchronously:
|
||||||
|
```objectivec
|
||||||
|
[webServer addDefaultHandlerForMethod:@"GET"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
|
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/html" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
||||||
|
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
completionBlock([@"<html><body><p>Hello" dataUsingEncoding:NSUTF8StringEncoding], nil); // Generate the 1st part of the stream data
|
||||||
|
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
completionBlock([@"World</p></body></html>" dataUsingEncoding:NSUTF8StringEncoding], nil); // Generate the 2nd part of the stream data
|
||||||
|
|
||||||
|
completionBlock([NSData data], nil); // Must pass an empty NSData to signal the end of the stream
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}];
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!*
|
||||||
|
|
||||||
Web Based Uploads in iOS Apps
|
Web Based Uploads in iOS Apps
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
@@ -123,7 +221,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 +235,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 +251,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 +265,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
|
||||||
@@ -202,9 +314,9 @@ GCDWebServer relies on "handlers" to process incoming web requests and generatin
|
|||||||
|
|
||||||
Handlers require 2 GCD blocks:
|
Handlers require 2 GCD blocks:
|
||||||
* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil.
|
* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil.
|
||||||
* The ```GCDWebServerProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client.
|
* The ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return synchronously (if using ```GCDWebServerProcessBlock```) or asynchronously (if using ```GCDWebServerAsyncProcessBlock```) a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client.
|
||||||
|
|
||||||
Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex.
|
Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex.
|
||||||
|
|
||||||
GCDWebServer & Background Mode for iOS Apps
|
GCDWebServer & Background Mode for iOS Apps
|
||||||
===========================================
|
===========================================
|
||||||
@@ -220,6 +332,17 @@ 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.
|
||||||
|
|
||||||
|
Logging in GCDWebServer
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Both for debugging and informational purpose, GCDWebServer logs messages extensively whenever something happens. Furthermore, when building GCDWebServer in "Debug" mode versus "Release" mode, it logs even more information but also performs a number of internal consistency checks. To enable this behavior, define the preprocessor constant ```DEBUG=1``` when compiling GCDWebServer. In Xcode target settings, this can be done by adding ```DEBUG=1``` to the build setting ```GCC_PREPROCESSOR_DEFINITIONS``` when building in "Debug" configuration. Finally, you can also control the logging verbosity at run time by calling ```+[GCDWebServer setLogLevel:]```.
|
||||||
|
|
||||||
|
By default, all messages logged by GCDWebServer are sent to its built-in logging facility, which simply outputs to ```stderr``` (assuming a terminal type device is connected). In order to better integrate with the rest of your app or because of the amount of information logged, you might want to use another logging facility.
|
||||||
|
|
||||||
|
GCDWebServer has automatic support for [XLFacility](https://github.com/swisspol/XLFacility) (by the same author as GCDWebServer and also open-source) and [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack). If either of them is in the same Xcode project, GCDWebServer should use it automatically instead of the built-in logging facility (see [GCDWebServerPrivate.h](GCDWebServer/Core/GCDWebServerPrivate.h) for the implementation details).
|
||||||
|
|
||||||
|
It's also possible to use a custom logging facility - see [GCDWebServer.h](GCDWebServer/Core/GCDWebServer.h) for more information.
|
||||||
|
|
||||||
Advanced Example 1: Implementing HTTP Redirects
|
Advanced Example 1: Implementing HTTP Redirects
|
||||||
===============================================
|
===============================================
|
||||||
|
|
||||||
|
|||||||
60
Run-Tests.sh
60
Run-Tests.sh
@@ -11,52 +11,50 @@ OSX_TARGET="GCDWebServer (Mac)"
|
|||||||
IOS_TARGET="GCDWebServer (iOS)"
|
IOS_TARGET="GCDWebServer (iOS)"
|
||||||
CONFIGURATION="Release"
|
CONFIGURATION="Release"
|
||||||
|
|
||||||
MRC_BUILD_DIR="/tmp/GCDWebServer-MRC"
|
BUILD_DIR="/tmp/GCDWebServer-Build"
|
||||||
MRC_PRODUCT="$MRC_BUILD_DIR/$CONFIGURATION/GCDWebServer"
|
PRODUCT="$BUILD_DIR/$CONFIGURATION/GCDWebServer"
|
||||||
ARC_BUILD_DIR="/tmp/GCDWebServer-ARC"
|
|
||||||
ARC_PRODUCT="$ARC_BUILD_DIR/$CONFIGURATION/GCDWebServer"
|
|
||||||
|
|
||||||
PAYLOAD_ZIP="Tests/Payload.zip"
|
PAYLOAD_ZIP="Tests/Payload.zip"
|
||||||
PAYLOAD_DIR="/tmp/GCDWebServer"
|
PAYLOAD_DIR="/tmp/GCDWebServer-Payload"
|
||||||
|
|
||||||
function runTests {
|
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"
|
||||||
|
TZ=GMT 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 for oldest deployment target (TODO: run tests on iOS)
|
||||||
rm -rf "$MRC_BUILD_DIR"
|
rm -rf "$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=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=5.1.1" > /dev/null
|
||||||
|
|
||||||
# Build for iOS in ARC mode (TODO: run tests on iOS)
|
# Build for iOS for default deployment target (TODO: run tests on iOS)
|
||||||
rm -rf "$ARC_BUILD_DIR"
|
rm -rf "$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=$BUILD_DIR" > /dev/null
|
||||||
|
|
||||||
# Build for OS X in manual memory management mode
|
# Build for OS X for oldest deployment target
|
||||||
rm -rf "$MRC_BUILD_DIR"
|
rm -rf "$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=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=10.7" > /dev/null
|
||||||
|
|
||||||
# Build for OS X in ARC mode
|
# Build for OS X for default deployment target
|
||||||
rm -rf "$ARC_BUILD_DIR"
|
rm -rf "$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=$BUILD_DIR" > /dev/null
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
runTests $MRC_PRODUCT "htmlForm" "Tests/HTMLForm"
|
runTests $PRODUCT "htmlForm" "Tests/HTMLForm"
|
||||||
runTests $ARC_PRODUCT "htmlForm" "Tests/HTMLForm"
|
runTests $PRODUCT "htmlFileUpload" "Tests/HTMLFileUpload"
|
||||||
runTests $MRC_PRODUCT "htmlFileUpload" "Tests/HTMLFileUpload"
|
runTests $PRODUCT "webServer" "Tests/WebServer"
|
||||||
runTests $ARC_PRODUCT "htmlFileUpload" "Tests/HTMLFileUpload"
|
runTests $PRODUCT "webDAV" "Tests/WebDAV-Transmit"
|
||||||
runTests $MRC_PRODUCT "webServer" "Tests/WebServer"
|
runTests $PRODUCT "webDAV" "Tests/WebDAV-Cyberduck"
|
||||||
runTests $ARC_PRODUCT "webServer" "Tests/WebServer"
|
runTests $PRODUCT "webDAV" "Tests/WebDAV-Finder"
|
||||||
runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Transmit"
|
runTests $PRODUCT "webUploader" "Tests/WebUploader"
|
||||||
runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Transmit"
|
runTests $PRODUCT "webServer" "Tests/WebServer-Sample-Movie" "Tests/Sample-Movie.mp4"
|
||||||
runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Cyberduck"
|
|
||||||
runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Cyberduck"
|
|
||||||
runTests $MRC_PRODUCT "webDAV" "Tests/WebDAV-Finder"
|
|
||||||
runTests $ARC_PRODUCT "webDAV" "Tests/WebDAV-Finder"
|
|
||||||
runTests $MRC_PRODUCT "webUploader" "Tests/WebUploader"
|
|
||||||
runTests $ARC_PRODUCT "webUploader" "Tests/WebUploader"
|
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
echo "\nAll tests completed successfully!"
|
echo "\nAll tests completed successfully!"
|
||||||
|
|||||||
BIN
Tests/Sample-Movie.mp4
Normal file
BIN
Tests/Sample-Movie.mp4
Normal file
Binary file not shown.
BIN
Tests/WebServer-Sample-Movie/001-200.response
Normal file
BIN
Tests/WebServer-Sample-Movie/001-200.response
Normal file
Binary file not shown.
12
Tests/WebServer-Sample-Movie/001-GET.request
Normal file
12
Tests/WebServer-Sample-Movie/001-GET.request
Normal 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
|
||||||
|
|
||||||
BIN
Tests/WebServer-Sample-Movie/002-206.response
Normal file
BIN
Tests/WebServer-Sample-Movie/002-206.response
Normal file
Binary file not shown.
13
Tests/WebServer-Sample-Movie/002-GET.request
Normal file
13
Tests/WebServer-Sample-Movie/002-GET.request
Normal 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-
|
||||||
|
|
||||||
BIN
Tests/WebServer-Sample-Movie/003-206.response
Normal file
BIN
Tests/WebServer-Sample-Movie/003-206.response
Normal file
Binary file not shown.
13
Tests/WebServer-Sample-Movie/003-GET.request
Normal file
13
Tests/WebServer-Sample-Movie/003-GET.request
Normal 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-
|
||||||
|
|
||||||
BIN
Tests/WebServer-Sample-Movie/004-206.response
Normal file
BIN
Tests/WebServer-Sample-Movie/004-206.response
Normal file
Binary file not shown.
12
Tests/WebServer-Sample-Movie/004-GET.request
Normal file
12
Tests/WebServer-Sample-Movie/004-GET.request
Normal 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
|
||||||
|
|
||||||
BIN
Tests/WebServer-Sample-Movie/005-206.response
Normal file
BIN
Tests/WebServer-Sample-Movie/005-206.response
Normal file
Binary file not shown.
12
Tests/WebServer-Sample-Movie/005-GET.request
Normal file
12
Tests/WebServer-Sample-Movie/005-GET.request
Normal 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
|
||||||
|
|
||||||
@@ -30,27 +30,15 @@
|
|||||||
|
|
||||||
@interface AppDelegate () <GCDWebUploaderDelegate> {
|
@interface AppDelegate () <GCDWebUploaderDelegate> {
|
||||||
@private
|
@private
|
||||||
UIWindow* _window;
|
|
||||||
GCDWebUploader* _webServer;
|
GCDWebUploader* _webServer;
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
|
|
||||||
@synthesize window=_window;
|
|
||||||
|
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
[_window release];
|
|
||||||
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
_window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
CGRect bounds = ([UIScreen instancesRespondToSelector:@selector(nativeBounds)] ? [[UIScreen mainScreen] nativeBounds] : [[UIScreen mainScreen] bounds]);
|
||||||
|
_window = [[UIWindow alloc] initWithFrame:bounds];
|
||||||
_window.backgroundColor = [UIColor whiteColor];
|
_window.backgroundColor = [UIColor whiteColor];
|
||||||
[_window makeKeyAndVisible];
|
[_window makeKeyAndVisible];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user