mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-02-11 00:00:07 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34884f273a | ||
|
|
87745c0fde | ||
|
|
ec800b43d5 | ||
|
|
79d9fb389c | ||
|
|
33d14f22e0 | ||
|
|
b060305d6d | ||
|
|
561f56e7fb | ||
|
|
e9fdd19830 | ||
|
|
03fae468d1 | ||
|
|
0a7d185417 | ||
|
|
1e29a0195b | ||
|
|
4e29da53a2 | ||
|
|
acc54ceac3 | ||
|
|
c46a2ddb22 | ||
|
|
71e972084c | ||
|
|
e8c872b286 | ||
|
|
fc928d0e2b | ||
|
|
e65f0eeaf1 | ||
|
|
79ae63a4c0 | ||
|
|
21cc6bfb35 | ||
|
|
faf28fe0f9 | ||
|
|
bac5b680df | ||
|
|
7df465336e | ||
|
|
a554893844 | ||
|
|
06569fe2cc | ||
|
|
e812d3f43c | ||
|
|
d30c88729d |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
.DS_Store
|
||||
xcuserdata
|
||||
project.xcworkspace
|
||||
|
||||
Tests/Payload
|
||||
Carthage/Build
|
||||
/build
|
||||
/Carthage/Build
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
language: objective-c
|
||||
script: ./Run-Tests.sh
|
||||
osx_image: xcode8.2
|
||||
osx_image: xcode10.1
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#import <GCDWebServers/GCDWebServers.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#pragma clang diagnostic ignored "-Weverything" // Prevent "messaging to unqualified id" warnings
|
||||
|
||||
@interface Tests : XCTestCase
|
||||
@end
|
||||
|
||||
@@ -21,4 +23,21 @@
|
||||
XCTAssertNotNil(server);
|
||||
}
|
||||
|
||||
- (void)testPaths {
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@""), @"");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/foo/"), @"/foo");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/bar"), @"foo/bar");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo//bar"), @"foo/bar");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/bar//"), @"foo/bar");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/./bar"), @"foo/bar");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/bar/."), @"foo/bar");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"foo/../bar"), @"bar");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/foo/../bar"), @"/bar");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/foo/.."), @"/");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"/.."), @"/");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"."), @"");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@".."), @"");
|
||||
XCTAssertEqualObjects(GCDWebServerNormalizePath(@"../.."), @"");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -95,7 +95,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* The default value is nil i.e. all file extensions are allowed.
|
||||
*/
|
||||
@property(nonatomic, copy) NSArray* allowedFileExtensions;
|
||||
@property(nonatomic, copy) NSArray<NSString*>* allowedFileExtensions;
|
||||
|
||||
/**
|
||||
* Sets if files and directories whose name start with a period are allowed to
|
||||
|
||||
@@ -77,7 +77,7 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
||||
if ((self = [super init])) {
|
||||
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
||||
_uploadDirectory = [path copy];
|
||||
GCDWebDAVServer* __unsafe_unretained server = self;
|
||||
|
||||
// 9.1 PROPFIND method
|
||||
@@ -157,11 +157,6 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
@implementation GCDWebDAVServer (Methods)
|
||||
|
||||
// Must match implementation in GCDWebUploader
|
||||
- (BOOL)_checkSandboxedPath:(NSString*)path {
|
||||
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
|
||||
}
|
||||
|
||||
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
||||
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
||||
return NO;
|
||||
@@ -186,9 +181,9 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
||||
|
||||
- (GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request {
|
||||
NSString* relativePath = request.path;
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
|
||||
@@ -221,10 +216,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
||||
}
|
||||
|
||||
NSString* relativePath = request.path;
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
if (![self _checkSandboxedPath:absolutePath]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
|
||||
@@ -265,9 +257,9 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
||||
}
|
||||
|
||||
NSString* relativePath = request.path;
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
|
||||
@@ -299,10 +291,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
||||
}
|
||||
|
||||
NSString* relativePath = request.path;
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
if (![self _checkSandboxedPath:absolutePath]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
|
||||
@@ -348,10 +337,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
||||
}
|
||||
|
||||
NSString* srcRelativePath = request.path;
|
||||
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:srcRelativePath];
|
||||
if (![self _checkSandboxedPath:srcAbsolutePath]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
|
||||
}
|
||||
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(srcRelativePath)];
|
||||
|
||||
NSString* dstRelativePath = [request.headers objectForKey:@"Destination"];
|
||||
NSRange range = [dstRelativePath rangeOfString:(NSString*)[request.headers objectForKey:@"Host"]];
|
||||
@@ -362,8 +348,8 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
dstRelativePath = [[dstRelativePath substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
#pragma clang diagnostic pop
|
||||
NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:dstRelativePath];
|
||||
if (![self _checkSandboxedPath:dstAbsolutePath]) {
|
||||
NSString* dstAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(dstRelativePath)];
|
||||
if (!dstAbsolutePath) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
|
||||
}
|
||||
|
||||
@@ -532,9 +518,9 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
||||
}
|
||||
|
||||
NSString* relativePath = request.path;
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
|
||||
@@ -546,7 +532,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
||||
NSArray* items = nil;
|
||||
if (isDirectory) {
|
||||
NSError* error = nil;
|
||||
items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
|
||||
items = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||
if (items == nil) {
|
||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
||||
}
|
||||
@@ -582,9 +568,9 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
||||
}
|
||||
|
||||
NSString* relativePath = request.path;
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
|
||||
@@ -679,9 +665,9 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
||||
}
|
||||
|
||||
NSString* relativePath = request.path;
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'GCDWebServer'
|
||||
s.version = '3.4.1'
|
||||
s.version = '3.5.1'
|
||||
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
|
||||
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
||||
s.homepage = 'https://github.com/swisspol/GCDWebServer'
|
||||
@@ -34,23 +34,19 @@ Pod::Spec.new do |s|
|
||||
end
|
||||
|
||||
s.subspec 'WebDAV' do |cs|
|
||||
cs.subspec "Core" do |ccs|
|
||||
ccs.dependency 'GCDWebServer/Core'
|
||||
ccs.source_files = 'GCDWebDAVServer/*.{h,m}'
|
||||
ccs.requires_arc = true
|
||||
ccs.ios.library = 'xml2'
|
||||
ccs.tvos.library = 'xml2'
|
||||
ccs.osx.library = 'xml2'
|
||||
ccs.compiler_flags = '-I$(SDKROOT)/usr/include/libxml2'
|
||||
end
|
||||
cs.dependency 'GCDWebServer/Core'
|
||||
cs.source_files = 'GCDWebDAVServer/*.{h,m}'
|
||||
cs.requires_arc = true
|
||||
cs.ios.library = 'xml2'
|
||||
cs.tvos.library = 'xml2'
|
||||
cs.osx.library = 'xml2'
|
||||
cs.compiler_flags = '-I$(SDKROOT)/usr/include/libxml2'
|
||||
end
|
||||
|
||||
s.subspec 'WebUploader' do |cs|
|
||||
cs.subspec "Core" do |ccs|
|
||||
ccs.dependency 'GCDWebServer/Core'
|
||||
ccs.source_files = 'GCDWebUploader/*.{h,m}'
|
||||
ccs.requires_arc = true
|
||||
ccs.resource = "GCDWebUploader/GCDWebUploader.bundle"
|
||||
end
|
||||
cs.dependency 'GCDWebServer/Core'
|
||||
cs.source_files = 'GCDWebUploader/*.{h,m}'
|
||||
cs.requires_arc = true
|
||||
cs.resource = "GCDWebUploader/GCDWebUploader.bundle"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -856,7 +856,7 @@
|
||||
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0900;
|
||||
LastUpgradeCheck = 1010;
|
||||
TargetAttributes = {
|
||||
CEE28CD01AE004D800F4023C = {
|
||||
CreatedOnToolsVersion = 6.3;
|
||||
@@ -1208,6 +1208,7 @@
|
||||
1DEB928A08733DD80010E9CD /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BUNDLE_VERSION_STRING = 3.3.4;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -1222,11 +1223,12 @@
|
||||
"-Weverything",
|
||||
"-Wshadow",
|
||||
"-Wshorten-64-to-32",
|
||||
"-Wstrict-prototypes",
|
||||
"-Wdeprecated-declarations",
|
||||
"-Wno-vla",
|
||||
"-Wno-explicit-ownership-type",
|
||||
"-Wno-gnu-statement-expression",
|
||||
"-Wno-direct-ivar-access",
|
||||
"-Wno-implicit-retain-self",
|
||||
"-Wno-assign-enum",
|
||||
"-Wno-format-nonliteral",
|
||||
"-Wno-cast-align",
|
||||
@@ -1237,7 +1239,6 @@
|
||||
"-Wno-cstring-format-directive",
|
||||
"-Wno-reserved-id-macro",
|
||||
"-Wno-cast-qual",
|
||||
"-Wno-partial-availability",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1245,6 +1246,7 @@
|
||||
1DEB928B08733DD80010E9CD /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BUNDLE_VERSION_STRING = 3.3.4;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -27,7 +27,6 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableAddressSanitizer = "YES"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
@@ -48,7 +47,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@@ -46,7 +45,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@@ -46,7 +45,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -42,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* GCDWebServerRequest instance created with the same basic info.
|
||||
* Otherwise, it simply returns nil.
|
||||
*/
|
||||
typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
|
||||
typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery);
|
||||
|
||||
/**
|
||||
* The GCDWebServerProcessBlock is called after the HTTP request has been fully
|
||||
@@ -365,7 +365,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
||||
*
|
||||
* Returns NO if the server failed to start and sets "error" argument if not NULL.
|
||||
*/
|
||||
- (BOOL)startWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error;
|
||||
- (BOOL)startWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
|
||||
|
||||
/**
|
||||
* Stops the server and prevents it to accepts new HTTP requests.
|
||||
@@ -444,7 +444,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
||||
*
|
||||
* @warning This method must be used from the main thread only.
|
||||
*/
|
||||
- (BOOL)runWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error;
|
||||
- (BOOL)runWithOptions:(nullable NSDictionary<NSString*, id>*)options error:(NSError** _Nullable)error;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -613,7 +613,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
||||
*
|
||||
* Returns the number of failed tests or -1 if server failed to start.
|
||||
*/
|
||||
- (NSInteger)runTestsWithOptions:(nullable NSDictionary*)options inDirectory:(NSString*)path;
|
||||
- (NSInteger)runTestsWithOptions:(nullable NSDictionary<NSString*, id>*)options inDirectory:(NSString*)path;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -141,14 +141,14 @@ static void _ExecuteMainThreadRunLoopSources() {
|
||||
@implementation GCDWebServer {
|
||||
dispatch_queue_t _syncQueue;
|
||||
dispatch_group_t _sourceGroup;
|
||||
NSMutableArray* _handlers;
|
||||
NSMutableArray<GCDWebServerHandler*>* _handlers;
|
||||
NSInteger _activeConnections; // Accessed through _syncQueue only
|
||||
BOOL _connected; // Accessed on main thread only
|
||||
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
|
||||
|
||||
NSDictionary* _options;
|
||||
NSMutableDictionary* _authenticationBasicAccounts;
|
||||
NSMutableDictionary* _authenticationDigestAccounts;
|
||||
NSDictionary<NSString*, id>* _options;
|
||||
NSMutableDictionary<NSString*, NSString*>* _authenticationBasicAccounts;
|
||||
NSMutableDictionary<NSString*, NSString*>* _authenticationDigestAccounts;
|
||||
Class _connectionClass;
|
||||
CFTimeInterval _disconnectDelay;
|
||||
dispatch_source_t _source4;
|
||||
@@ -206,10 +206,8 @@ static void _ExecuteMainThreadRunLoopSources() {
|
||||
if (_backgroundTask == UIBackgroundTaskInvalid) {
|
||||
GWS_LOG_DEBUG(@"Did start background task");
|
||||
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
||||
|
||||
GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
|
||||
[self _endBackgroundTask];
|
||||
|
||||
}];
|
||||
} else {
|
||||
GWS_DNOT_REACHED();
|
||||
@@ -238,22 +236,20 @@ static void _ExecuteMainThreadRunLoopSources() {
|
||||
|
||||
- (void)willStartConnection:(GCDWebServerConnection*)connection {
|
||||
dispatch_sync(_syncQueue, ^{
|
||||
|
||||
GWS_DCHECK(_activeConnections >= 0);
|
||||
if (_activeConnections == 0) {
|
||||
GWS_DCHECK(self->_activeConnections >= 0);
|
||||
if (self->_activeConnections == 0) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
||||
CFRelease(_disconnectTimer);
|
||||
_disconnectTimer = NULL;
|
||||
if (self->_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(self->_disconnectTimer);
|
||||
CFRelease(self->_disconnectTimer);
|
||||
self->_disconnectTimer = NULL;
|
||||
}
|
||||
if (_connected == NO) {
|
||||
if (self->_connected == NO) {
|
||||
[self _didConnect];
|
||||
}
|
||||
});
|
||||
}
|
||||
_activeConnections += 1;
|
||||
|
||||
self->_activeConnections += 1;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -292,22 +288,22 @@ static void _ExecuteMainThreadRunLoopSources() {
|
||||
|
||||
- (void)didEndConnection:(GCDWebServerConnection*)connection {
|
||||
dispatch_sync(_syncQueue, ^{
|
||||
GWS_DCHECK(_activeConnections > 0);
|
||||
_activeConnections -= 1;
|
||||
if (_activeConnections == 0) {
|
||||
GWS_DCHECK(self->_activeConnections > 0);
|
||||
self->_activeConnections -= 1;
|
||||
if (self->_activeConnections == 0) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ((_disconnectDelay > 0.0) && (_source4 != NULL)) {
|
||||
if (_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
||||
CFRelease(_disconnectTimer);
|
||||
if ((self->_disconnectDelay > 0.0) && (self->_source4 != NULL)) {
|
||||
if (self->_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(self->_disconnectTimer);
|
||||
CFRelease(self->_disconnectTimer);
|
||||
}
|
||||
_disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + _disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) {
|
||||
self->_disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + self->_disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) {
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
[self _didDisconnect];
|
||||
CFRelease(_disconnectTimer);
|
||||
_disconnectTimer = NULL;
|
||||
CFRelease(self->_disconnectTimer);
|
||||
self->_disconnectTimer = NULL;
|
||||
});
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes);
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), self->_disconnectTimer, kCFRunLoopCommonModes);
|
||||
} else {
|
||||
[self _didDisconnect];
|
||||
}
|
||||
@@ -412,19 +408,21 @@ static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef
|
||||
}
|
||||
}
|
||||
|
||||
static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) {
|
||||
static inline id _GetOption(NSDictionary<NSString*, id>* options, NSString* key, id defaultValue) {
|
||||
id value = [options objectForKey:key];
|
||||
return value ? value : defaultValue;
|
||||
}
|
||||
|
||||
static inline NSString* _EncodeBase64(NSString* string) {
|
||||
NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||
#if (TARGET_OS_IPHONE && !(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)) || (!TARGET_OS_IPHONE && !(__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9))
|
||||
if (![data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) {
|
||||
return [data base64Encoding];
|
||||
}
|
||||
#endif
|
||||
#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9)
|
||||
return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding];
|
||||
#else
|
||||
if (@available(macOS 10.9, *)) {
|
||||
return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding];
|
||||
}
|
||||
return [data base64Encoding];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (int)_createListeningSocket:(BOOL)useIPv6
|
||||
@@ -469,7 +467,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
dispatch_group_enter(_sourceGroup);
|
||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0));
|
||||
dispatch_source_set_cancel_handler(source, ^{
|
||||
|
||||
@autoreleasepool {
|
||||
int result = close(listeningSocket);
|
||||
if (result != 0) {
|
||||
@@ -478,11 +475,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(_sourceGroup);
|
||||
|
||||
dispatch_group_leave(self->_sourceGroup);
|
||||
});
|
||||
dispatch_source_set_event_handler(source, ^{
|
||||
|
||||
@autoreleasepool {
|
||||
struct sockaddr_storage remoteSockAddr;
|
||||
socklen_t remoteAddrLen = sizeof(remoteSockAddr);
|
||||
@@ -503,13 +498,12 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
int noSigPipe = 1;
|
||||
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
|
||||
|
||||
GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
|
||||
GCDWebServerConnection* connection = [(GCDWebServerConnection*)[self->_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
|
||||
[connection self]; // Prevent compiler from complaining about unused variable / useless statement
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return source;
|
||||
}
|
||||
@@ -517,9 +511,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
- (BOOL)_start:(NSError**)error {
|
||||
GWS_DCHECK(_source4 == NULL);
|
||||
|
||||
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
||||
BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
|
||||
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
|
||||
NSUInteger port = [(NSNumber*)_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
||||
BOOL bindToLocalhost = [(NSNumber*)_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
|
||||
NSUInteger maxPendingConnections = [(NSNumber*)_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
|
||||
|
||||
struct sockaddr_in addr4;
|
||||
bzero(&addr4, sizeof(addr4));
|
||||
@@ -553,27 +547,27 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
||||
_serverName = [(NSString*)_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
||||
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
||||
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||
[_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
|
||||
[self->_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
|
||||
}];
|
||||
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||
[_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
|
||||
[self->_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, self->_authenticationRealm, password) forKey:username];
|
||||
}];
|
||||
}
|
||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||
_shouldAutomaticallyMapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
||||
_dispatchQueuePriority = [_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];
|
||||
_shouldAutomaticallyMapHEADToGET = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||
_disconnectDelay = [(NSNumber*)_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
||||
_dispatchQueuePriority = [(NSNumber*)_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];
|
||||
|
||||
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
|
||||
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
|
||||
@@ -604,7 +598,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
}
|
||||
}
|
||||
|
||||
if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
|
||||
if ([(NSNumber*)_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
|
||||
DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
|
||||
if (status == kDNSServiceErr_NoError) {
|
||||
CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
|
||||
@@ -632,7 +626,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
|
||||
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_delegate webServerDidStart:self];
|
||||
[self->_delegate webServerDidStart:self];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -693,10 +687,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
_authenticationDigestAccounts = nil;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
||||
CFRelease(_disconnectTimer);
|
||||
_disconnectTimer = NULL;
|
||||
if (self->_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(self->_disconnectTimer);
|
||||
CFRelease(self->_disconnectTimer);
|
||||
self->_disconnectTimer = NULL;
|
||||
[self _didDisconnect];
|
||||
}
|
||||
});
|
||||
@@ -704,7 +698,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
GWS_LOG_INFO(@"%@ stopped", [self class]);
|
||||
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_delegate webServerDidStop:self];
|
||||
[self->_delegate webServerDidStop:self];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -729,11 +723,11 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
|
||||
#endif
|
||||
|
||||
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
|
||||
- (BOOL)startWithOptions:(NSDictionary<NSString*, id>*)options error:(NSError**)error {
|
||||
if (_options == nil) {
|
||||
_options = options ? [options copy] : @{};
|
||||
#if TARGET_OS_IPHONE
|
||||
_suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
|
||||
_suspendInBackground = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
|
||||
if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
|
||||
#else
|
||||
if (![self _start:error])
|
||||
@@ -840,7 +834,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
return [self runWithOptions:options error:NULL];
|
||||
}
|
||||
|
||||
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error {
|
||||
- (BOOL)runWithOptions:(NSDictionary<NSString*, id>*)options error:(NSError**)error {
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
BOOL success = NO;
|
||||
_run = YES;
|
||||
@@ -876,13 +870,11 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
}
|
||||
|
||||
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
||||
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||
if (![requestMethod isEqualToString:method]) {
|
||||
return nil;
|
||||
}
|
||||
return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
|
||||
return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
}
|
||||
asyncProcessBlock:block];
|
||||
}
|
||||
@@ -898,16 +890,14 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
|
||||
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
||||
if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
||||
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||
if (![requestMethod isEqualToString:method]) {
|
||||
return nil;
|
||||
}
|
||||
if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
|
||||
return nil;
|
||||
}
|
||||
return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
|
||||
return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
}
|
||||
asyncProcessBlock:block];
|
||||
} else {
|
||||
@@ -927,8 +917,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
|
||||
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
|
||||
if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
||||
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||
if (![requestMethod isEqualToString:method]) {
|
||||
return nil;
|
||||
}
|
||||
@@ -951,10 +940,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
}
|
||||
}
|
||||
|
||||
GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
GCDWebServerRequest* request = [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
[request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
|
||||
return request;
|
||||
|
||||
}
|
||||
asyncProcessBlock:block];
|
||||
} else {
|
||||
@@ -971,11 +959,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
path:path
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
|
||||
response.cacheControlMaxAge = cacheAge;
|
||||
return response;
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -984,7 +970,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
path:path
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
GCDWebServerResponse* response = nil;
|
||||
if (allowRangeRequests) {
|
||||
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
|
||||
@@ -994,34 +979,33 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
}
|
||||
response.cacheControlMaxAge = cacheAge;
|
||||
return response;
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
|
||||
NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
if (enumerator == nil) {
|
||||
NSArray* contents = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||
if (contents == nil) {
|
||||
return nil;
|
||||
}
|
||||
NSMutableString* html = [NSMutableString string];
|
||||
[html appendString:@"<!DOCTYPE html>\n"];
|
||||
[html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
|
||||
[html appendString:@"<ul>\n"];
|
||||
for (NSString* file in enumerator) {
|
||||
if (![file hasPrefix:@"."]) {
|
||||
NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
|
||||
for (NSString* entry in contents) {
|
||||
if (![entry hasPrefix:@"."]) {
|
||||
NSString* type = [[[NSFileManager defaultManager] attributesOfItemAtPath:[path stringByAppendingPathComponent:entry] error:NULL] objectForKey:NSFileType];
|
||||
GWS_DCHECK(type);
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString* escapedFile = [entry stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
#pragma clang diagnostic pop
|
||||
GWS_DCHECK(escapedFile);
|
||||
if ([type isEqualToString:NSFileTypeRegular]) {
|
||||
[html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
|
||||
[html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, entry];
|
||||
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
||||
[html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, file];
|
||||
[html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, entry];
|
||||
}
|
||||
}
|
||||
[enumerator skipDescendents];
|
||||
}
|
||||
[html appendString:@"</ul>\n"];
|
||||
[html appendString:@"</body></html>\n"];
|
||||
@@ -1031,8 +1015,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
|
||||
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
|
||||
GCDWebServer* __unsafe_unretained server = self;
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
||||
|
||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary<NSString*, NSString*>* requestHeaders, NSString* urlPath, NSDictionary<NSString*, NSString*>* urlQuery) {
|
||||
if (![requestMethod isEqualToString:@"GET"]) {
|
||||
return nil;
|
||||
}
|
||||
@@ -1040,12 +1023,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
return nil;
|
||||
}
|
||||
return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
|
||||
}
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
GCDWebServerResponse* response = nil;
|
||||
NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
|
||||
NSString* filePath = [directoryPath stringByAppendingPathComponent:GCDWebServerNormalizePath([request.path substringFromIndex:basePath.length])];
|
||||
NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType];
|
||||
if (fileType) {
|
||||
if ([fileType isEqualToString:NSFileTypeDirectory]) {
|
||||
@@ -1072,7 +1053,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
|
||||
}
|
||||
return response;
|
||||
|
||||
}];
|
||||
} else {
|
||||
GWS_DNOT_REACHED();
|
||||
@@ -1190,7 +1170,7 @@ static void _LogResult(NSString* format, ...) {
|
||||
fprintf(stdout, "%s\n", [message UTF8String]);
|
||||
}
|
||||
|
||||
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
|
||||
- (NSInteger)runTestsWithOptions:(NSDictionary<NSString*, id>*)options inDirectory:(NSString*)path {
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
NSArray* ignoredHeaders = @[ @"Date", @"Etag" ]; // Dates are always different by definition and ETags depend on file system node IDs
|
||||
NSInteger result = -1;
|
||||
@@ -1198,7 +1178,7 @@ static void _LogResult(NSString* format, ...) {
|
||||
_ExecuteMainThreadRunLoopSources();
|
||||
|
||||
result = 0;
|
||||
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
|
||||
NSArray* files = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||
for (NSString* requestFile in files) {
|
||||
if (![requestFile hasSuffix:@".request"]) {
|
||||
continue;
|
||||
@@ -1258,7 +1238,7 @@ static void _LogResult(NSString* format, ...) {
|
||||
if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file)
|
||||
actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)];
|
||||
}
|
||||
if (![actualBody isEqualToData:expectedBody]) {
|
||||
if ((actualBody && expectedBody && ![actualBody isEqualToData:expectedBody]) || (actualBody && !expectedBody) || (!actualBody && expectedBody)) {
|
||||
_LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
|
||||
success = NO;
|
||||
#if !TARGET_OS_IPHONE
|
||||
|
||||
@@ -130,7 +130,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* The default implementation returns the original URL.
|
||||
*/
|
||||
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers;
|
||||
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary<NSString*, NSString*>*)headers;
|
||||
|
||||
/**
|
||||
* Assuming a valid HTTP request was received, this method is called before
|
||||
|
||||
@@ -193,22 +193,18 @@ NS_ASSUME_NONNULL_END
|
||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
|
||||
}
|
||||
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
|
||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
|
||||
CFHTTPMessageSetHeaderFieldValue(self->_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
|
||||
}];
|
||||
[self writeHeadersWithCompletionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
if (hasBody) {
|
||||
[self writeBodyWithCompletionBlock:^(BOOL successInner) {
|
||||
|
||||
[_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent
|
||||
|
||||
[self->_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent
|
||||
}];
|
||||
}
|
||||
} else if (hasBody) {
|
||||
[_response performClose];
|
||||
[self->_response performClose];
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
@@ -238,15 +234,13 @@ NS_ASSUME_NONNULL_END
|
||||
if (length) {
|
||||
[self readBodyWithRemainingLength:length
|
||||
completionBlock:^(BOOL success) {
|
||||
|
||||
NSError* localError = nil;
|
||||
if ([_request performClose:&localError]) {
|
||||
if ([self->_request performClose:&localError]) {
|
||||
[self _startProcessingRequest];
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error);
|
||||
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
if ([_request performClose:&error]) {
|
||||
@@ -269,15 +263,13 @@ NS_ASSUME_NONNULL_END
|
||||
NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
|
||||
[self readNextBodyChunk:chunkData
|
||||
completionBlock:^(BOOL success) {
|
||||
|
||||
NSError* localError = nil;
|
||||
if ([_request performClose:&localError]) {
|
||||
if ([self->_request performClose:&localError]) {
|
||||
[self _startProcessingRequest];
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error);
|
||||
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -286,15 +278,14 @@ NS_ASSUME_NONNULL_END
|
||||
NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
|
||||
[self readHeaders:headersData
|
||||
withCompletionBlock:^(NSData* extraData) {
|
||||
|
||||
if (extraData) {
|
||||
NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase
|
||||
if (_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
|
||||
NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(self->_requestMessage)); // Method verbs are case-sensitive and uppercase
|
||||
if (self->_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) {
|
||||
requestMethod = @"GET";
|
||||
_virtualHEAD = YES;
|
||||
self->_virtualHEAD = YES;
|
||||
}
|
||||
NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
|
||||
NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(_requestMessage));
|
||||
NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self->_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones
|
||||
NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(self->_requestMessage));
|
||||
if (requestURL) {
|
||||
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
|
||||
GWS_DCHECK(requestURL);
|
||||
@@ -307,55 +298,53 @@ NS_ASSUME_NONNULL_END
|
||||
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) : @{};
|
||||
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
|
||||
for (_handler in _server.handlers) {
|
||||
_request = _handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery);
|
||||
if (_request) {
|
||||
for (self->_handler in self->_server.handlers) {
|
||||
self->_request = self->_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery);
|
||||
if (self->_request) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_request) {
|
||||
_request.localAddressData = self.localAddressData;
|
||||
_request.remoteAddressData = self.remoteAddressData;
|
||||
if ([_request hasBody]) {
|
||||
[_request prepareForWriting];
|
||||
if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) {
|
||||
if (self->_request) {
|
||||
self->_request.localAddressData = self.localAddressData;
|
||||
self->_request.remoteAddressData = self.remoteAddressData;
|
||||
if ([self->_request hasBody]) {
|
||||
[self->_request prepareForWriting];
|
||||
if (self->_request.usesChunkedTransferEncoding || (extraData.length <= self->_request.contentLength)) {
|
||||
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
|
||||
if (expectHeader) {
|
||||
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
|
||||
[self writeData:_continueData
|
||||
withCompletionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
if (_request.usesChunkedTransferEncoding) {
|
||||
if (self->_request.usesChunkedTransferEncoding) {
|
||||
[self _readChunkedBodyWithInitialData:extraData];
|
||||
} else {
|
||||
[self _readBodyWithLength:_request.contentLength initialData:extraData];
|
||||
[self _readBodyWithLength:self->_request.contentLength initialData:extraData];
|
||||
}
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
|
||||
GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", self->_socket);
|
||||
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
|
||||
}
|
||||
} else {
|
||||
if (_request.usesChunkedTransferEncoding) {
|
||||
if (self->_request.usesChunkedTransferEncoding) {
|
||||
[self _readChunkedBodyWithInitialData:extraData];
|
||||
} else {
|
||||
[self _readBodyWithLength:_request.contentLength initialData:extraData];
|
||||
[self _readBodyWithLength:self->_request.contentLength initialData:extraData];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
|
||||
GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", self->_socket);
|
||||
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
|
||||
}
|
||||
} else {
|
||||
[self _startProcessingRequest];
|
||||
}
|
||||
} else {
|
||||
_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
|
||||
GWS_DCHECK(_request);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_NotImplemented];
|
||||
self->_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
|
||||
GWS_DCHECK(self->_request);
|
||||
[self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_NotImplemented];
|
||||
}
|
||||
} else {
|
||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
@@ -364,7 +353,6 @@ NS_ASSUME_NONNULL_END
|
||||
} else {
|
||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -426,7 +414,6 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
|
||||
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
|
||||
|
||||
@autoreleasepool {
|
||||
if (error == 0) {
|
||||
size_t size = dispatch_data_get_size(buffer);
|
||||
@@ -439,19 +426,18 @@ NS_ASSUME_NONNULL_END
|
||||
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
|
||||
block(YES);
|
||||
} else {
|
||||
if (_totalBytesRead > 0) {
|
||||
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
|
||||
if (self->_totalBytesRead > 0) {
|
||||
GWS_LOG_ERROR(@"No more data available on socket %i", self->_socket);
|
||||
} else {
|
||||
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
|
||||
GWS_LOG_WARNING(@"No data received from socket %i", self->_socket);
|
||||
}
|
||||
block(NO);
|
||||
}
|
||||
} else {
|
||||
GWS_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)", self->_socket, strerror(error), error);
|
||||
block(NO);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -460,29 +446,27 @@ NS_ASSUME_NONNULL_END
|
||||
[self readData:headersData
|
||||
withLength:NSUIntegerMax
|
||||
completionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
|
||||
if (range.location == NSNotFound) {
|
||||
[self readHeaders:headersData withCompletionBlock:block];
|
||||
} else {
|
||||
NSUInteger length = range.location + range.length;
|
||||
if (CFHTTPMessageAppendBytes(_requestMessage, headersData.bytes, length)) {
|
||||
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
|
||||
if (CFHTTPMessageAppendBytes(self->_requestMessage, headersData.bytes, length)) {
|
||||
if (CFHTTPMessageIsHeaderComplete(self->_requestMessage)) {
|
||||
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", self->_socket);
|
||||
block(nil);
|
||||
}
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", self->_socket);
|
||||
block(nil);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
block(nil);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -492,11 +476,10 @@ NS_ASSUME_NONNULL_END
|
||||
[self readData:bodyData
|
||||
withLength:length
|
||||
completionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
if (bodyData.length <= length) {
|
||||
NSError* error = nil;
|
||||
if ([_request performWriteData:bodyData error:&error]) {
|
||||
if ([self->_request performWriteData:bodyData error:&error]) {
|
||||
NSUInteger remainingLength = length - bodyData.length;
|
||||
if (remainingLength) {
|
||||
[self readBodyWithRemainingLength:remainingLength completionBlock:block];
|
||||
@@ -504,18 +487,17 @@ NS_ASSUME_NONNULL_END
|
||||
block(YES);
|
||||
}
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", self->_socket, error);
|
||||
block(NO);
|
||||
}
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", self->_socket);
|
||||
block(NO);
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
} else {
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -575,13 +557,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
[self readData:chunkData
|
||||
withLength:NSUIntegerMax
|
||||
completionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
[self readNextBodyChunk:chunkData completionBlock:block];
|
||||
} else {
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -594,18 +574,16 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
[data self]; // Keeps ARC from releasing data too early
|
||||
});
|
||||
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
|
||||
|
||||
@autoreleasepool {
|
||||
if (error == 0) {
|
||||
GWS_DCHECK(remainingData == NULL);
|
||||
[self didWriteBytes:data.bytes length:data.length];
|
||||
block(YES);
|
||||
} else {
|
||||
GWS_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)", self->_socket, strerror(error), error);
|
||||
block(NO);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
||||
dispatch_release(buffer);
|
||||
@@ -622,15 +600,14 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
||||
GWS_DCHECK([_response hasBody]);
|
||||
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
|
||||
|
||||
if (data) {
|
||||
if (data.length) {
|
||||
if (_response.usesChunkedTransferEncoding) {
|
||||
if (self->_response.usesChunkedTransferEncoding) {
|
||||
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
||||
size_t hexLength = strlen(hexString);
|
||||
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
||||
if (chunk == nil) {
|
||||
GWS_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: %@", self->_socket, error);
|
||||
block(NO);
|
||||
return;
|
||||
}
|
||||
@@ -647,31 +624,26 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
}
|
||||
[self writeData:data
|
||||
withCompletionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
[self writeBodyWithCompletionBlock:block];
|
||||
} else {
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
if (_response.usesChunkedTransferEncoding) {
|
||||
if (self->_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);
|
||||
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", self->_socket, error);
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -726,7 +698,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers {
|
||||
- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary<NSString*, NSString*>*)headers {
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ extern "C" {
|
||||
* types. Keys of the dictionary must be lowercased file extensions without
|
||||
* the period, and the values must be the corresponding MIME types.
|
||||
*/
|
||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides);
|
||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* _Nullable overrides);
|
||||
|
||||
/**
|
||||
* Add percent-escapes to a string so it can be used in a URL.
|
||||
@@ -60,7 +60,7 @@ NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string);
|
||||
* "application/x-www-form-urlencoded" form.
|
||||
* http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
|
||||
*/
|
||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
||||
NSDictionary<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form);
|
||||
|
||||
/**
|
||||
* On OS X, returns the IPv4 or IPv6 address as a string of the primary
|
||||
@@ -102,6 +102,11 @@ NSString* GCDWebServerFormatISO8601(NSDate* date);
|
||||
*/
|
||||
NSDate* _Nullable GCDWebServerParseISO8601(NSString* string);
|
||||
|
||||
/**
|
||||
* Removes "//", "/./" and "/../" components from path as well as any trailing slash.
|
||||
*/
|
||||
NSString* GCDWebServerNormalizePath(NSString* path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -166,8 +166,8 @@ NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
|
||||
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
|
||||
}
|
||||
|
||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* overrides) {
|
||||
NSDictionary* builtInOverrides = @{ @"css" : @"text/css" };
|
||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary<NSString*, NSString*>* overrides) {
|
||||
NSDictionary* builtInOverrides = @{@"css" : @"text/css"};
|
||||
NSString* mimeType = nil;
|
||||
extension = [extension lowercaseString];
|
||||
if (extension.length) {
|
||||
@@ -200,7 +200,7 @@ NSString* GCDWebServerUnescapeURLString(NSString* string) {
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
||||
NSDictionary<NSString*, NSString*>* GCDWebServerParseURLEncodedForm(NSString* form) {
|
||||
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
|
||||
NSScanner* scanner = [[NSScanner alloc] initWithString:form];
|
||||
[scanner setCharactersToBeSkipped:nil];
|
||||
@@ -314,3 +314,18 @@ NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
|
||||
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
|
||||
return (NSString*)[NSString stringWithUTF8String:buffer];
|
||||
}
|
||||
|
||||
NSString* GCDWebServerNormalizePath(NSString* path) {
|
||||
NSMutableArray* components = [[NSMutableArray alloc] init];
|
||||
for (NSString* component in [path componentsSeparatedByString:@"/"]) {
|
||||
if ([component isEqualToString:@".."]) {
|
||||
[components removeLastObject];
|
||||
} else if (component.length && ![component isEqualToString:@"."]) {
|
||||
[components addObject:component];
|
||||
}
|
||||
}
|
||||
if (path.length && ([path characterAtIndex:0] == '/')) {
|
||||
return [@"/" stringByAppendingString:[components componentsJoinedByString:@"/"]]; // Preserve initial slash
|
||||
}
|
||||
return [components componentsJoinedByString:@"/"];
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
#import "GCDWebServerFileResponse.h"
|
||||
#import "GCDWebServerStreamedResponse.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Check if a custom logging facility should be used instead.
|
||||
*/
|
||||
@@ -101,7 +99,7 @@ typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
|
||||
};
|
||||
|
||||
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
|
||||
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
|
||||
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* _Nonnull format, ...) NS_FORMAT_FUNCTION(2, 3);
|
||||
|
||||
#if DEBUG
|
||||
#define GWS_LOG_DEBUG(...) \
|
||||
@@ -155,6 +153,8 @@ extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* for
|
||||
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* GCDWebServer internal constants and APIs.
|
||||
*/
|
||||
@@ -170,7 +170,7 @@ static inline NSError* GCDWebServerMakePosixError(int code) {
|
||||
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : (NSString*)[NSString stringWithUTF8String:strerror(code)]}];
|
||||
}
|
||||
|
||||
extern void GCDWebServerInitializeFunctions();
|
||||
extern void GCDWebServerInitializeFunctions(void);
|
||||
extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
|
||||
extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
|
||||
extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
|
||||
@@ -185,11 +185,11 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
|
||||
@end
|
||||
|
||||
@interface GCDWebServer ()
|
||||
@property(nonatomic, readonly) NSMutableArray* handlers;
|
||||
@property(nonatomic, readonly) NSMutableArray<GCDWebServerHandler*>* handlers;
|
||||
@property(nonatomic, readonly, nullable) NSString* serverName;
|
||||
@property(nonatomic, readonly, nullable) NSString* authenticationRealm;
|
||||
@property(nonatomic, readonly, nullable) NSMutableDictionary* authenticationBasicAccounts;
|
||||
@property(nonatomic, readonly, nullable) NSMutableDictionary* authenticationDigestAccounts;
|
||||
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationBasicAccounts;
|
||||
@property(nonatomic, readonly, nullable) NSMutableDictionary<NSString*, NSString*>* authenticationDigestAccounts;
|
||||
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
||||
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
|
||||
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
||||
@@ -213,7 +213,7 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
|
||||
@end
|
||||
|
||||
@interface GCDWebServerResponse ()
|
||||
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
||||
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* additionalHeaders;
|
||||
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
||||
- (void)prepareForReading;
|
||||
- (BOOL)performOpen:(NSError**)error;
|
||||
|
||||
@@ -102,7 +102,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
||||
/**
|
||||
* Returns the HTTP headers for the request.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSDictionary* headers;
|
||||
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* headers;
|
||||
|
||||
/**
|
||||
* Returns the path component of the URL for the request.
|
||||
@@ -114,7 +114,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
||||
*
|
||||
* @warning This property will be nil if there is no query in the URL.
|
||||
*/
|
||||
@property(nonatomic, readonly, nullable) NSDictionary* query;
|
||||
@property(nonatomic, readonly, nullable) NSDictionary<NSString*, NSString*>* query;
|
||||
|
||||
/**
|
||||
* Returns the content type for the body of the request parsed from the
|
||||
@@ -186,7 +186,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
||||
/**
|
||||
* This method is the designated initializer for the class.
|
||||
*/
|
||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(nullable NSDictionary*)query;
|
||||
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary<NSString*, NSString*>*)headers path:(NSString*)path query:(nullable NSDictionary<NSString*, NSString*>*)query;
|
||||
|
||||
/**
|
||||
* Convenience method that checks if the contentType property is defined.
|
||||
|
||||
@@ -136,12 +136,12 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
||||
|
||||
@implementation GCDWebServerRequest {
|
||||
BOOL _opened;
|
||||
NSMutableArray* _decoders;
|
||||
NSMutableArray<GCDWebServerBodyDecoder*>* _decoders;
|
||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
||||
NSMutableDictionary* _attributes;
|
||||
NSMutableDictionary<NSString*, id>* _attributes;
|
||||
}
|
||||
|
||||
- (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<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
|
||||
if ((self = [super init])) {
|
||||
_method = [method copy];
|
||||
_URL = url;
|
||||
@@ -188,7 +188,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
|
||||
if ([rangeHeader hasPrefix:@"bytes="]) {
|
||||
NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
|
||||
if (components.count == 1) {
|
||||
components = [[components firstObject] componentsSeparatedByString:@"-"];
|
||||
components = [(NSString*)[components firstObject] componentsSeparatedByString:@"-"];
|
||||
if (components.count == 2) {
|
||||
NSString* startString = [components objectAtIndex:0];
|
||||
NSInteger startValue = [startString integerValue];
|
||||
|
||||
@@ -150,12 +150,12 @@
|
||||
|
||||
@implementation GCDWebServerResponse {
|
||||
BOOL _opened;
|
||||
NSMutableArray* _encoders;
|
||||
NSMutableArray<GCDWebServerBodyEncoder*>* _encoders;
|
||||
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
||||
}
|
||||
|
||||
+ (instancetype)response {
|
||||
return [[[self class] alloc] init];
|
||||
return [(GCDWebServerResponse*)[[self class] alloc] init];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
@@ -259,11 +259,11 @@
|
||||
@implementation GCDWebServerResponse (Extensions)
|
||||
|
||||
+ (instancetype)responseWithStatusCode:(NSInteger)statusCode {
|
||||
return [[self alloc] initWithStatusCode:statusCode];
|
||||
return [(GCDWebServerResponse*)[self alloc] initWithStatusCode:statusCode];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent {
|
||||
return [[self alloc] initWithRedirect:location permanent:permanent];
|
||||
return [(GCDWebServerResponse*)[self alloc] initWithRedirect:location permanent:permanent];
|
||||
}
|
||||
|
||||
- (instancetype)initWithStatusCode:(NSInteger)statusCode {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
}
|
||||
if (_data == nil) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Failed allocating memory" }];
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed allocating memory"}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
int _file;
|
||||
}
|
||||
|
||||
- (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<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
|
||||
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
||||
_temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||
}
|
||||
|
||||
@@ -107,13 +107,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* Returns the argument parts from the multipart encoded form as
|
||||
* name / GCDWebServerMultiPartArgument pairs.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSArray* arguments;
|
||||
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartArgument*>* arguments;
|
||||
|
||||
/**
|
||||
* Returns the files parts from the multipart encoded form as
|
||||
* name / GCDWebServerMultiPartFile pairs.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSArray* files;
|
||||
@property(nonatomic, readonly) NSArray<GCDWebServerMultiPartFile*>* files;
|
||||
|
||||
/**
|
||||
* Returns the MIME type for multipart encoded forms
|
||||
|
||||
@@ -106,8 +106,8 @@ static NSData* _dashNewlineData = nil;
|
||||
NSString* _defaultcontrolName;
|
||||
ParserState _state;
|
||||
NSMutableData* _data;
|
||||
NSMutableArray* _arguments;
|
||||
NSMutableArray* _files;
|
||||
NSMutableArray<GCDWebServerMultiPartArgument*>* _arguments;
|
||||
NSMutableArray<GCDWebServerMultiPartFile*>* _files;
|
||||
|
||||
NSString* _controlName;
|
||||
NSString* _fileName;
|
||||
@@ -132,7 +132,7 @@ static NSData* _dashNewlineData = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray* _Nonnull)arguments files:(NSMutableArray* _Nonnull)files {
|
||||
- (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray<GCDWebServerMultiPartArgument*>* _Nonnull)arguments files:(NSMutableArray<GCDWebServerMultiPartFile*>* _Nonnull)files {
|
||||
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
|
||||
if (data == nil) {
|
||||
GWS_DNOT_REACHED();
|
||||
@@ -312,8 +312,8 @@ static NSData* _dashNewlineData = nil;
|
||||
@end
|
||||
|
||||
@interface GCDWebServerMultiPartFormRequest ()
|
||||
@property(nonatomic) NSMutableArray* arguments;
|
||||
@property(nonatomic) NSMutableArray* files;
|
||||
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartArgument*>* arguments;
|
||||
@property(nonatomic) NSMutableArray<GCDWebServerMultiPartFile*>* files;
|
||||
@end
|
||||
|
||||
@implementation GCDWebServerMultiPartFormRequest {
|
||||
@@ -324,7 +324,7 @@ static NSData* _dashNewlineData = nil;
|
||||
return @"multipart/form-data";
|
||||
}
|
||||
|
||||
- (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<NSString*, NSString*>*)headers path:(NSString*)path query:(NSDictionary<NSString*, NSString*>*)query {
|
||||
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
|
||||
_arguments = [[NSMutableArray alloc] init];
|
||||
_files = [[NSMutableArray alloc] init];
|
||||
@@ -337,7 +337,7 @@ static NSData* _dashNewlineData = nil;
|
||||
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
|
||||
if (_parser == nil) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Failed starting to parse multipart form data" }];
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed starting to parse multipart form data"}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@@ -347,7 +347,7 @@ static NSData* _dashNewlineData = nil;
|
||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
||||
if (![_parser appendBytes:data.bytes length:data.length]) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Failed continuing to parse multipart form data" }];
|
||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed continuing to parse multipart form data"}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@@ -359,7 +359,7 @@ static NSData* _dashNewlineData = nil;
|
||||
_parser = nil;
|
||||
if (!atEnd) {
|
||||
if (error) {
|
||||
*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"}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* The text encoding used to interpret the data is extracted from the
|
||||
* "Content-Type" header or defaults to UTF-8.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSDictionary* arguments;
|
||||
@property(nonatomic, readonly) NSDictionary<NSString*, NSString*>* arguments;
|
||||
|
||||
/**
|
||||
* Returns the MIME type for URL encoded forms
|
||||
|
||||
@@ -64,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* Creates a data response from an HTML template encoded using UTF-8.
|
||||
* See -initWithHTMLTemplate:variables: for details.
|
||||
*/
|
||||
+ (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
|
||||
+ (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables;
|
||||
|
||||
/**
|
||||
* Creates a data response from a serialized JSON object and the default
|
||||
@@ -94,7 +94,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* All occurences of "%variable%" within the HTML template are replaced with
|
||||
* their corresponding values.
|
||||
*/
|
||||
- (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
|
||||
- (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables;
|
||||
|
||||
/**
|
||||
* Initializes a data response from a serialized JSON object and the default
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
@dynamic contentType;
|
||||
|
||||
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
|
||||
return [[[self class] alloc] initWithData:data contentType:type];
|
||||
return [(GCDWebServerDataResponse*)[[self class] alloc] initWithData:data contentType:type];
|
||||
}
|
||||
|
||||
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
|
||||
@@ -75,23 +75,23 @@
|
||||
@implementation GCDWebServerDataResponse (Extensions)
|
||||
|
||||
+ (instancetype)responseWithText:(NSString*)text {
|
||||
return [[self alloc] initWithText:text];
|
||||
return [(GCDWebServerDataResponse*)[self alloc] initWithText:text];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithHTML:(NSString*)html {
|
||||
return [[self alloc] initWithHTML:html];
|
||||
return [(GCDWebServerDataResponse*)[self alloc] initWithHTML:html];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
|
||||
return [[self alloc] initWithHTMLTemplate:path variables:variables];
|
||||
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables {
|
||||
return [(GCDWebServerDataResponse*)[self alloc] initWithHTMLTemplate:path variables:variables];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithJSONObject:(id)object {
|
||||
return [[self alloc] initWithJSONObject:object];
|
||||
return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type {
|
||||
return [[self alloc] initWithJSONObject:object contentType:type];
|
||||
return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object contentType:type];
|
||||
}
|
||||
|
||||
- (instancetype)initWithText:(NSString*)text {
|
||||
@@ -112,7 +112,7 @@
|
||||
return [self initWithData:data contentType:@"text/html; charset=utf-8"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
|
||||
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary<NSString*, NSString*>*)variables {
|
||||
NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
|
||||
[variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
|
||||
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||
va_end(arguments);
|
||||
return response;
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||
va_end(arguments);
|
||||
return response;
|
||||
}
|
||||
@@ -55,7 +55,7 @@
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||
va_end(arguments);
|
||||
return response;
|
||||
}
|
||||
@@ -64,7 +64,7 @@
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||
GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||
va_end(arguments);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* file extensions without the period, and the values must be the corresponding
|
||||
* MIME types.
|
||||
*/
|
||||
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides;
|
||||
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary<NSString*, NSString*>*)overrides;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -45,19 +45,19 @@
|
||||
@dynamic contentType, lastModifiedDate, eTag;
|
||||
|
||||
+ (instancetype)responseWithFile:(NSString*)path {
|
||||
return [[[self class] alloc] initWithFile:path];
|
||||
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
||||
return [[[self class] alloc] initWithFile:path isAttachment:attachment];
|
||||
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path isAttachment:attachment];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range {
|
||||
return [[[self class] alloc] initWithFile:path byteRange:range];
|
||||
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
||||
return [[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment mimeTypeOverrides:nil];
|
||||
return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment mimeTypeOverrides:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFile:(NSString*)path {
|
||||
@@ -76,7 +76,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
||||
return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(NSDictionary*)overrides {
|
||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(NSDictionary<NSString*, NSString*>*)overrides {
|
||||
struct stat info;
|
||||
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
||||
GWS_DNOT_REACHED();
|
||||
|
||||
@@ -38,21 +38,19 @@
|
||||
@dynamic contentType;
|
||||
|
||||
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
||||
return [[[self class] alloc] initWithContentType:type streamBlock:block];
|
||||
return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type streamBlock:block];
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
||||
return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block];
|
||||
return [(GCDWebServerStreamedResponse*)[[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);
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* The default value is nil i.e. all file extensions are allowed.
|
||||
*/
|
||||
@property(nonatomic, copy) NSArray* allowedFileExtensions;
|
||||
@property(nonatomic, copy) NSArray<NSString*>* allowedFileExtensions;
|
||||
|
||||
/**
|
||||
* Sets if files and directories whose name start with a period are allowed to
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#endif
|
||||
|
||||
#import "GCDWebUploader.h"
|
||||
#import "GCDWebServerFunctions.h"
|
||||
|
||||
#import "GCDWebServerDataRequest.h"
|
||||
#import "GCDWebServerMultiPartFormRequest.h"
|
||||
@@ -73,7 +74,7 @@ NS_ASSUME_NONNULL_END
|
||||
if (siteBundle == nil) {
|
||||
return nil;
|
||||
}
|
||||
_uploadDirectory = [[path stringByStandardizingPath] copy];
|
||||
_uploadDirectory = [path copy];
|
||||
GCDWebUploader* __unsafe_unretained server = self;
|
||||
|
||||
// Resource files
|
||||
@@ -135,7 +136,6 @@ NS_ASSUME_NONNULL_END
|
||||
@"epilogue" : epilogue,
|
||||
@"footer" : footer
|
||||
}];
|
||||
|
||||
}];
|
||||
|
||||
// File listing
|
||||
@@ -193,11 +193,6 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
@implementation GCDWebUploader (Methods)
|
||||
|
||||
// Must match implementation in GCDWebDAVServer
|
||||
- (BOOL)_checkSandboxedPath:(NSString*)path {
|
||||
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
|
||||
}
|
||||
|
||||
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
||||
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
||||
return NO;
|
||||
@@ -225,9 +220,9 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
|
||||
NSString* relativePath = [[request query] objectForKey:@"path"];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (!absolutePath || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
if (!isDirectory) {
|
||||
@@ -240,7 +235,7 @@ NS_ASSUME_NONNULL_END
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
|
||||
NSArray* contents = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
|
||||
if (contents == nil) {
|
||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
||||
}
|
||||
@@ -269,9 +264,9 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
|
||||
NSString* relativePath = [[request query] objectForKey:@"path"];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
if (isDirectory) {
|
||||
@@ -300,10 +295,7 @@ NS_ASSUME_NONNULL_END
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
|
||||
}
|
||||
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
|
||||
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
|
||||
if (![self _checkSandboxedPath:absolutePath]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)] stringByAppendingPathComponent:file.fileName]];
|
||||
|
||||
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
|
||||
@@ -324,17 +316,14 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
|
||||
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
|
||||
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
|
||||
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(oldRelativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
|
||||
}
|
||||
|
||||
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
|
||||
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
|
||||
if (![self _checkSandboxedPath:newAbsolutePath]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
|
||||
}
|
||||
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(newRelativePath)]];
|
||||
|
||||
NSString* itemName = [newAbsolutePath lastPathComponent];
|
||||
if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||
@@ -360,9 +349,9 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
|
||||
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)];
|
||||
BOOL isDirectory = NO;
|
||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
|
||||
@@ -390,10 +379,7 @@ NS_ASSUME_NONNULL_END
|
||||
|
||||
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
|
||||
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
||||
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
|
||||
if (![self _checkSandboxedPath:absolutePath]) {
|
||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||
}
|
||||
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:GCDWebServerNormalizePath(relativePath)]];
|
||||
|
||||
NSString* directoryName = [absolutePath lastPathComponent];
|
||||
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
|
||||
|
||||
50
Mac/main.m
50
Mac/main.m
@@ -178,10 +178,10 @@ int main(int argc, const char* argv[]) {
|
||||
recording = YES;
|
||||
} else if (!strcmp(argv[i], "-root") && (i + 1 < argc)) {
|
||||
++i;
|
||||
rootDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
|
||||
rootDirectory = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])];
|
||||
} else if (!strcmp(argv[i], "-tests") && (i + 1 < argc)) {
|
||||
++i;
|
||||
testDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])] stringByStandardizingPath];
|
||||
testDirectory = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:argv[i] length:strlen(argv[i])];
|
||||
} else if (!strcmp(argv[i], "-authenticationMethod") && (i + 1 < argc)) {
|
||||
++i;
|
||||
authenticationMethod = [NSString stringWithUTF8String:argv[i]];
|
||||
@@ -206,7 +206,7 @@ int main(int argc, const char* argv[]) {
|
||||
switch (mode) {
|
||||
// Simply serve contents of home directory
|
||||
case kMode_WebServer: {
|
||||
fprintf(stdout, "Running in Web Server mode from \"%s\"", [rootDirectory UTF8String]);
|
||||
fprintf(stdout, "Running in Web Server mode from \"%s\"\n", [rootDirectory UTF8String]);
|
||||
webServer = [[GCDWebServer alloc] init];
|
||||
[webServer addGETHandlerForBasePath:@"/" directoryPath:rootDirectory indexFilename:nil cacheAge:0 allowRangeRequests:YES];
|
||||
break;
|
||||
@@ -214,27 +214,24 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
// Renders a HTML page
|
||||
case kMode_HTMLPage: {
|
||||
fprintf(stdout, "Running in HTML Page mode");
|
||||
fprintf(stdout, "Running in HTML Page mode\n");
|
||||
webServer = [[GCDWebServer alloc] init];
|
||||
[webServer addDefaultHandlerForMethod:@"GET"
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
|
||||
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
// Implements an HTML form
|
||||
case kMode_HTMLForm: {
|
||||
fprintf(stdout, "Running in HTML Form mode");
|
||||
fprintf(stdout, "Running in HTML Form mode\n");
|
||||
webServer = [[GCDWebServer alloc] init];
|
||||
[webServer addHandlerForMethod:@"GET"
|
||||
path:@"/"
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
NSString* html = @" \
|
||||
<html><body> \
|
||||
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
|
||||
@@ -244,24 +241,21 @@ int main(int argc, const char* argv[]) {
|
||||
</body></html> \
|
||||
";
|
||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||
|
||||
}];
|
||||
[webServer addHandlerForMethod:@"POST"
|
||||
path:@"/"
|
||||
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
|
||||
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
|
||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
// Implements HTML file upload
|
||||
case kMode_HTMLFileUpload: {
|
||||
fprintf(stdout, "Running in HTML File Upload mode");
|
||||
fprintf(stdout, "Running in HTML File Upload mode\n");
|
||||
webServer = [[GCDWebServer alloc] init];
|
||||
NSString* formHTML = @" \
|
||||
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"multipart/form-data\"> \
|
||||
@@ -274,16 +268,13 @@ int main(int argc, const char* argv[]) {
|
||||
path:@"/"
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML];
|
||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||
|
||||
}];
|
||||
[webServer addHandlerForMethod:@"POST"
|
||||
path:@"/"
|
||||
requestClass:[GCDWebServerMultiPartFormRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
NSMutableString* string = [NSMutableString string];
|
||||
for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) {
|
||||
[string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string];
|
||||
@@ -296,108 +287,87 @@ int main(int argc, const char* argv[]) {
|
||||
};
|
||||
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML];
|
||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
// Serve home directory through WebDAV
|
||||
case kMode_WebDAV: {
|
||||
fprintf(stdout, "Running in WebDAV mode from \"%s\"", [rootDirectory UTF8String]);
|
||||
fprintf(stdout, "Running in WebDAV mode from \"%s\"\n", [rootDirectory UTF8String]);
|
||||
webServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:rootDirectory];
|
||||
break;
|
||||
}
|
||||
|
||||
// Serve home directory through web uploader
|
||||
case kMode_WebUploader: {
|
||||
fprintf(stdout, "Running in Web Uploader mode from \"%s\"", [rootDirectory UTF8String]);
|
||||
fprintf(stdout, "Running in Web Uploader mode from \"%s\"\n", [rootDirectory UTF8String]);
|
||||
webServer = [[GCDWebUploader alloc] initWithUploadDirectory:rootDirectory];
|
||||
break;
|
||||
}
|
||||
|
||||
// Test streaming responses
|
||||
case kMode_StreamingResponse: {
|
||||
fprintf(stdout, "Running in Streaming Response mode");
|
||||
fprintf(stdout, "Running in Streaming Response mode\n");
|
||||
webServer = [[GCDWebServer alloc] init];
|
||||
[webServer addHandlerForMethod:@"GET"
|
||||
path:@"/sync"
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||
|
||||
__block int countDown = 10;
|
||||
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain"
|
||||
streamBlock:^NSData*(NSError** error) {
|
||||
|
||||
usleep(100 * 1000);
|
||||
if (countDown) {
|
||||
return [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
} else {
|
||||
return [NSData data];
|
||||
}
|
||||
|
||||
}];
|
||||
|
||||
}];
|
||||
[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");
|
||||
fprintf(stdout, "Running in Async Response mode\n");
|
||||
webServer = [[GCDWebServer alloc] init];
|
||||
[webServer addHandlerForMethod:@"GET"
|
||||
path:@"/async"
|
||||
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:(NSData*)[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
|
||||
completionBlock(response);
|
||||
});
|
||||
|
||||
}];
|
||||
[webServer addHandlerForMethod:@"GET"
|
||||
path:@"/async2"
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock handlerCompletionBlock) {
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
__block int countDown = 10;
|
||||
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/plain"
|
||||
asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock readerCompletionBlock) {
|
||||
|
||||
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];
|
||||
readerCompletionBlock(data, nil);
|
||||
|
||||
});
|
||||
|
||||
}];
|
||||
handlerCompletionBlock(response);
|
||||
|
||||
});
|
||||
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -350,8 +350,8 @@ GCDWebServer & Background Mode for iOS Apps
|
||||
When doing networking operations in iOS apps, you must handle carefully [what happens when iOS puts the app in the background](https://developer.apple.com/library/ios/technotes/tn2277/_index.html). Typically you must stop any network servers while the app is in the background and restart them when the app comes back to the foreground. This can become quite complex considering servers might have ongoing connections when they need to be stopped.
|
||||
|
||||
Fortunately, GCDWebServer does all of this automatically for you:
|
||||
- GCDWebServer begins a [background task](https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html) whenever the first HTTP connection is opened and ends it only when the last one is closed. This prevents iOS from suspending the app after it goes in the background, which would immediately kill HTTP connections to the client.
|
||||
- While the app is in the background, as long as new HTTP connections keep being initiated, the background task will continue to exist and iOS will not suspend the app (unless under sudden and unexpected memory pressure).
|
||||
- GCDWebServer begins a [background task](https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html) whenever the first HTTP connection is opened and ends it only when the last one is closed. This prevents iOS from suspending the app after it goes in the background, which would immediately kill HTTP connections to the client.
|
||||
- While the app is in the background, as long as new HTTP connections keep being initiated, the background task will continue to exist and iOS will not suspend the app **for up to 10 minutes** (unless under sudden and unexpected memory pressure).
|
||||
- If the app is still in the background when the last HTTP connection is closed, GCDWebServer will suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option).
|
||||
- If the app goes in the background while no HTTP connections are opened, GCDWebServer will immediately suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option).
|
||||
- If the app comes back to the foreground and GCDWebServer had been suspended, it will automatically resume itself and start accepting again new HTTP connections as if you had called ```-start```.
|
||||
|
||||
33
Run-Tests.sh
33
Run-Tests.sh
@@ -1,4 +1,10 @@
|
||||
#!/bin/bash -ex
|
||||
#!/bin/bash -exu -o pipefail
|
||||
|
||||
if [[ -f "/usr/local/bin/xcpretty" ]]; then
|
||||
PRETTYFIER="xcpretty"
|
||||
else
|
||||
PRETTYFIER="tee" # Passthrough stdout
|
||||
fi
|
||||
|
||||
OSX_SDK="macosx"
|
||||
IOS_SDK="iphonesimulator"
|
||||
@@ -15,23 +21,28 @@ CONFIGURATION="Release"
|
||||
|
||||
OSX_TEST_SCHEME="GCDWebServers (Mac)"
|
||||
|
||||
BUILD_DIR="/tmp/GCDWebServer-Build"
|
||||
BUILD_DIR="`pwd`/build"
|
||||
PRODUCT="$BUILD_DIR/$CONFIGURATION/GCDWebServer"
|
||||
|
||||
PAYLOAD_ZIP="Tests/Payload.zip"
|
||||
PAYLOAD_DIR="/tmp/GCDWebServer-Payload"
|
||||
PAYLOAD_DIR="`pwd`/build/Payload"
|
||||
|
||||
function runTests {
|
||||
EXECUTABLE="$1"
|
||||
MODE="$2"
|
||||
TESTS="$3"
|
||||
FILE="${4:-}"
|
||||
|
||||
rm -rf "$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
|
||||
if [ "$4" != "" ]; then
|
||||
if [ "$FILE" != "" ]; 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"`
|
||||
TZ=GMT SetFile -d "1/1/2014 00:00:00" -m "1/1/2014 00:00:00" `basename "$FILE"`
|
||||
popd
|
||||
fi
|
||||
logLevel=2 $1 -mode "$2" -root "$PAYLOAD_DIR/Payload" -tests "$3"
|
||||
logLevel=2 $EXECUTABLE -mode "$MODE" -root "$PAYLOAD_DIR/Payload" -tests "$TESTS"
|
||||
}
|
||||
|
||||
# Run built-in OS X tests
|
||||
@@ -40,7 +51,7 @@ xcodebuild test -scheme "$OSX_TEST_SCHEME" "SYMROOT=$BUILD_DIR"
|
||||
|
||||
# Build for OS X for oldest supported deployment target
|
||||
rm -rf "$BUILD_DIR"
|
||||
xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=10.7" > /dev/null
|
||||
xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=10.7" | $PRETTYFIER
|
||||
|
||||
# Run tests
|
||||
runTests $PRODUCT "htmlForm" "Tests/HTMLForm"
|
||||
@@ -54,19 +65,19 @@ runTests $PRODUCT "webServer" "Tests/WebServer-Sample-Movie" "Tests/Sample-Movie
|
||||
|
||||
# Build for OS X for current deployment target
|
||||
rm -rf "$BUILD_DIR"
|
||||
xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=$OSX_SDK_VERSION" > /dev/null
|
||||
xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "MACOSX_DEPLOYMENT_TARGET=$OSX_SDK_VERSION" | $PRETTYFIER
|
||||
|
||||
# Build for iOS for oldest supported deployment target
|
||||
rm -rf "$BUILD_DIR"
|
||||
xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=8.0" > /dev/null
|
||||
xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=8.0" | $PRETTYFIER
|
||||
|
||||
# Build for iOS for current deployment target
|
||||
rm -rf "$BUILD_DIR"
|
||||
xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=$IOS_SDK_VERSION" > /dev/null
|
||||
xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=$IOS_SDK_VERSION" | $PRETTYFIER
|
||||
|
||||
# Build for tvOS for current deployment target
|
||||
rm -rf "$BUILD_DIR"
|
||||
xcodebuild build -sdk "$TVOS_SDK" -target "$TVOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "TVOS_DEPLOYMENT_TARGET=$TVOS_SDK_VERSION" > /dev/null
|
||||
xcodebuild build -sdk "$TVOS_SDK" -target "$TVOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "TVOS_DEPLOYMENT_TARGET=$TVOS_SDK_VERSION" | $PRETTYFIER
|
||||
|
||||
# Done
|
||||
echo "\nAll tests completed successfully!"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# brew install clang-format
|
||||
|
||||
CLANG_FORMAT_VERSION=`clang-format -version | awk '{ print $3 }'`
|
||||
if [[ "$CLANG_FORMAT_VERSION" != "5.0.0" ]]; then
|
||||
if [[ "$CLANG_FORMAT_VERSION" != "7.0.0" ]]; then
|
||||
echo "Unsupported clang-format version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user