46 Commits
3.3.1 ... 3.4

Author SHA1 Message Date
Pierre-Olivier Latour
ac788ca906 Increased CocoaLumberjack dependency to 3.x 2017-07-03 16:43:22 -05:00
Pierre-Olivier Latour
9f9509a05e Bumped version
[ci skip]
2017-07-03 16:27:16 -05:00
Pierre-Olivier Latour
48777cc151 Change minimal iOS version to 8.0
[ci skip]
2017-07-03 16:27:09 -05:00
Pierre-Olivier Latour
c35d514b08 Allow to customiz mime types used by GCDWebServerFileResponse 2017-06-24 09:54:09 -07:00
Pierre-Olivier Latour
a013f9cebb Fixed data race issue inside GCDWebServerGetMimeTypeForExtension()
Fix #312
2017-06-24 09:30:39 -07:00
Pierre-Olivier Latour
1e28aef262 Enable address sanitizer by default for Mac unit tests 2017-06-24 09:25:52 -07:00
Pierre-Olivier Latour
09851dd71b Modernize ObjC 2017-06-24 09:25:04 -07:00
Pierre-Olivier Latour
23f77b5765 Set default Xcode indentation to 2 spaces 2017-06-22 08:27:18 -07:00
Pierre-Olivier Latour
7de8b0c643 Updated to Xcode 8.3
[ci skip]
2017-06-22 08:11:02 -07:00
Pierre-Olivier Latour
2685819b61 Updated to latest clang-format
[ci skip]
2017-06-22 08:10:16 -07:00
Pierre-Olivier Latour
fbcf3fa96c Fix build warning 2017-01-01 20:55:36 +09:00
Pierre-Olivier Latour
0f8e4f57e0 Increased minimal iOS deployment target to 8.0 2017-01-01 20:51:42 +09:00
Pierre-Olivier Latour
750214b5d5 Updated Travis CI to Xcode 8.2 2017-01-01 20:42:22 +09:00
Amir Abbas Mousavian
ce5310f2ce Resolves signing and building issue of dynamic frameworks
Fixes #277
2017-01-01 20:34:18 +09:00
Pierre-Olivier Latour
83f1c062b5 Use clang-formatter to format source code 2016-12-27 11:02:32 +09:00
Pierre-Olivier Latour
0c51a9b071 Updated Xcode project format to 8.0 2016-12-27 10:57:00 +09:00
Pierre-Olivier Latour
3b5bc40e5e Updated to Xcode 8.2 2016-12-27 10:56:43 +09:00
Pierre-Olivier Latour
3178baa86e Update README.md 2016-11-16 08:08:28 -08:00
Pierre-Olivier Latour
9a97e44d97 Made "GCDWebUploader.bundle" non-flat to improve code-signing on OS X 2016-09-19 18:12:01 -07:00
Pierre-Olivier Latour
6c2b11e7b6 Updated for Xcode 8 2016-09-19 18:00:46 -07:00
Pierre-Olivier Latour
56b8e1befe Removed executable bit from test files 2016-09-19 17:42:17 -07:00
Pierre-Olivier Latour
4231465c58 Removed executable bit from GCDWebUploader.bundle files 2016-09-19 17:42:17 -07:00
Pierre-Olivier Latour
4958eb8c72 Bumped version 2016-07-15 12:05:17 -07:00
Pierre-Olivier Latour
43555c6662 Merge pull request #272 from bizz84/master
Removed cs.default_subspec from subspecs as this is now disallowed
2016-07-15 11:46:05 -07:00
Andrea Bizzotto
7c08c54156 Removed cs.default_subspec from subspecs as this is now disallowed in Cocoapods 2016-07-15 19:23:27 +01:00
Pierre-Olivier Latour
5834770e08 Removed exception handling which was only partial anyway 2016-06-29 21:38:57 -07:00
Pierre-Olivier Latour
52406a12c2 Merge pull request #267 from hsin919/master
Fix the typo in swift samples
2016-06-29 09:15:10 -07:00
Pierre-Olivier Latour
e83814b0fa Merge pull request #269 from opsGavin/patch-1
Typo in GCDWebServerGZipDecoder:open
2016-06-29 09:11:43 -07:00
Gavin MacLean
fba593a602 Typo in GCDWebServerGZipDecoder:open
I haven't tested or dug into this, as I just happened to spot it while looking through a colleagues code.

It looks like `inflateInit2` is called and then `deflateEnd` on error. I'm wondering if this is a copy and paste issue from `GCDWebServerGZipEncoder` and really it should be `inflateEnd` here instead?
2016-06-29 15:36:40 +01:00
hsin
eec17d5c7b Fix the typo in swift samples 2016-06-08 09:25:04 +08:00
Pierre-Olivier Latour
32bc72113a Merge pull request #266 from DD-P/read-me-typo-fix
Minor documentation fix: "owns" -> "own"
2016-05-08 08:00:48 -07:00
David Davies-Payne
200d21c3fa Minor documentation fix: "owns" -> "own" 2016-05-08 19:32:34 +12:00
Pierre-Olivier Latour
3042b853bc Fix 2016-04-17 20:07:54 -07:00
Pierre-Olivier Latour
50eb0437c1 Updated for Xcode 7.3 2016-04-17 20:07:49 -07:00
Pierre-Olivier Latour
00cc560b8e Merge pull request #264 from AmpMe/refacor/add-queue-priorty-as-an-option
Add an option to set the priority of the dispatch queue
2016-04-03 17:53:13 -07:00
Martin Gagnon
fa41f26b30 Add an option to set the priority of the dispatch queue 2016-04-03 20:11:35 -04:00
Pierre-Olivier Latour
845969ec0d Merge pull request #262 from pra85/patch-1
Fix a typo in Readme
2016-03-24 06:36:57 -07:00
Prayag Verma
629df7895d Fix a typo in Readme
`asynchronously` → `asynchronously`
2016-02-18 13:32:09 +05:30
Pierre-Olivier Latour
55104e5b1e Update README.md 2016-02-08 01:00:25 -08:00
Pierre-Olivier Latour
fcc95fdc11 Merge pull request #260 from jjrscott/master
Added __kindof keyword where appropriate
2016-02-07 17:18:39 -08:00
John Scott
10a94e36fd Removed guards around __kindof usage as the project is Xcode 7 only. 2016-02-06 12:18:49 +00:00
John Scott
010ef9b8bc Added __kindof keyword where appropriate to avoid errors of the form "incompatible block pointer types sending 'GCDWebServerResponse *(^)(GCDWebServerDataRequest *__strong)' to parameter of type 'GCDWebServerProcessBlock' (aka 'GCDWebServerResponse *(^)(GCDWebServerRequest *__strong)')" 2016-02-05 20:45:46 +00:00
Pierre-Olivier Latour
5ca7c27a07 Bumped version 2016-01-20 07:24:59 -08:00
Pierre-Olivier Latour
c6632633f8 Disabled address sanitizer 2016-01-20 07:21:37 -08:00
Pierre-Olivier Latour
5b6eebbb9e Fixed build warning 2016-01-20 07:21:18 -08:00
Pierre-Olivier Latour
a4c61bd071 Bumped version 2016-01-16 06:29:35 -08:00
827 changed files with 1577 additions and 1478 deletions

16
.clang-format Normal file
View File

@@ -0,0 +1,16 @@
---
BasedOnStyle: Google
Standard: Cpp11
ColumnLimit: 0
AlignTrailingComments: false
NamespaceIndentation: All
DerivePointerAlignment: false
AlwaysBreakBeforeMultilineStrings: false
AccessModifierOffset: -2
ObjCSpaceBeforeProtocolList: true
SortIncludes: false
---
Language: Cpp
---
Language: ObjC
...

View File

@@ -1,3 +1,3 @@
language: objective-c language: objective-c
script: ./Run-Tests.sh script: ./Run-Tests.sh
osx_image: xcode7.1 osx_image: xcode8.2

View File

@@ -27,6 +27,8 @@
#import "GCDWebServer.h" #import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebDAVServer; @class GCDWebDAVServer;
/** /**
@@ -86,7 +88,7 @@
/** /**
* Sets the delegate for the server. * Sets the delegate for the server.
*/ */
@property(nonatomic, assign) id<GCDWebDAVServerDelegate> delegate; @property(nonatomic, weak, nullable) id<GCDWebDAVServerDelegate> delegate;
/** /**
* Sets which files are allowed to be operated on depending on their extension. * Sets which files are allowed to be operated on depending on their extension.
@@ -154,3 +156,5 @@
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path; - (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -55,12 +55,104 @@ typedef NS_ENUM(NSInteger, DAVProperties) {
kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength
}; };
@interface GCDWebDAVServer () { NS_ASSUME_NONNULL_BEGIN
@private
NSString* _uploadDirectory; @interface GCDWebDAVServer (Methods)
NSArray* _allowedExtensions; - (nullable GCDWebServerResponse*)performOPTIONS:(GCDWebServerRequest*)request;
BOOL _allowHidden; - (nullable GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request;
- (nullable GCDWebServerResponse*)performPUT:(GCDWebServerFileRequest*)request;
- (nullable GCDWebServerResponse*)performDELETE:(GCDWebServerRequest*)request;
- (nullable GCDWebServerResponse*)performMKCOL:(GCDWebServerDataRequest*)request;
- (nullable GCDWebServerResponse*)performCOPY:(GCDWebServerRequest*)request isMove:(BOOL)isMove;
- (nullable GCDWebServerResponse*)performPROPFIND:(GCDWebServerDataRequest*)request;
- (nullable GCDWebServerResponse*)performLOCK:(GCDWebServerDataRequest*)request;
- (nullable GCDWebServerResponse*)performUNLOCK:(GCDWebServerRequest*)request;
@end
NS_ASSUME_NONNULL_END
@implementation GCDWebDAVServer
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {
_uploadDirectory = [[path stringByStandardizingPath] copy];
GCDWebDAVServer* __unsafe_unretained server = self;
// 9.1 PROPFIND method
[self addDefaultHandlerForMethod:@"PROPFIND"
requestClass:[GCDWebServerDataRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performPROPFIND:(GCDWebServerDataRequest*)request];
}];
// 9.3 MKCOL Method
[self addDefaultHandlerForMethod:@"MKCOL"
requestClass:[GCDWebServerDataRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performMKCOL:(GCDWebServerDataRequest*)request];
}];
// 9.4 GET & HEAD methods
[self addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performGET:request];
}];
// 9.6 DELETE method
[self addDefaultHandlerForMethod:@"DELETE"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performDELETE:request];
}];
// 9.7 PUT method
[self addDefaultHandlerForMethod:@"PUT"
requestClass:[GCDWebServerFileRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performPUT:(GCDWebServerFileRequest*)request];
}];
// 9.8 COPY method
[self addDefaultHandlerForMethod:@"COPY"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performCOPY:request isMove:NO];
}];
// 9.9 MOVE method
[self addDefaultHandlerForMethod:@"MOVE"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performCOPY:request isMove:YES];
}];
// 9.10 LOCK method
[self addDefaultHandlerForMethod:@"LOCK"
requestClass:[GCDWebServerDataRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performLOCK:(GCDWebServerDataRequest*)request];
}];
// 9.11 UNLOCK method
[self addDefaultHandlerForMethod:@"UNLOCK"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performUNLOCK:request];
}];
// 10.1 OPTIONS method / DAV Header
[self addDefaultHandlerForMethod:@"OPTIONS"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server performOPTIONS:request];
}];
}
return self;
} }
@end @end
@implementation GCDWebDAVServer (Methods) @implementation GCDWebDAVServer (Methods)
@@ -71,7 +163,7 @@ typedef NS_ENUM(NSInteger, DAVProperties) {
} }
- (BOOL)_checkFileExtension:(NSString*)fileName { - (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) { if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO; return NO;
} }
return YES; return YES;
@@ -99,27 +191,27 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
NSString* itemName = [absolutePath lastPathComponent]; NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) { if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading item name \"%@\" is not allowed", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading item name \"%@\" is not allowed", itemName];
} }
// Because HEAD requests are mapped to GET ones, we need to handle directories but it's OK to return nothing per http://webdav.org/specs/rfc4918.html#rfc.section.9.4 // Because HEAD requests are mapped to GET ones, we need to handle directories but it's OK to return nothing per http://webdav.org/specs/rfc4918.html#rfc.section.9.4
if (isDirectory) { if (isDirectory) {
return [GCDWebServerResponse response]; return [GCDWebServerResponse response];
} }
if ([self.delegate respondsToSelector:@selector(davServer:didDownloadFileAtPath:)]) { if ([self.delegate respondsToSelector:@selector(davServer:didDownloadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didDownloadFileAtPath:absolutePath]; [self.delegate davServer:self didDownloadFileAtPath:absolutePath];
}); });
} }
if ([request hasByteRange]) { if ([request hasByteRange]) {
return [GCDWebServerFileResponse responseWithFile:absolutePath byteRange:request.byteRange]; return [GCDWebServerFileResponse responseWithFile:absolutePath byteRange:request.byteRange];
} }
return [GCDWebServerFileResponse responseWithFile:absolutePath]; return [GCDWebServerFileResponse responseWithFile:absolutePath];
} }
@@ -127,7 +219,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if ([request hasByteRange]) { if ([request hasByteRange]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Range uploads not supported"]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Range uploads not supported"];
} }
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
if (![self _checkSandboxedPath:absolutePath]) { if (![self _checkSandboxedPath:absolutePath]) {
@@ -137,27 +229,27 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) { if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
} }
BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]; BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory];
if (existing && isDirectory) { if (existing && isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"PUT not allowed on existing collection \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"PUT not allowed on existing collection \"%@\"", relativePath];
} }
NSString* fileName = [absolutePath lastPathComponent]; NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) { if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file name \"%@\" is not allowed", fileName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file name \"%@\" is not allowed", fileName];
} }
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:request.temporaryPath]) { if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:request.temporaryPath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file to \"%@\" is not permitted", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file to \"%@\" is not permitted", relativePath];
} }
[[NSFileManager defaultManager] removeItemAtPath:absolutePath error:NULL]; [[NSFileManager defaultManager] removeItemAtPath:absolutePath error:NULL];
NSError* error = nil; NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:request.temporaryPath toPath:absolutePath error:&error]) { if (![[NSFileManager defaultManager] moveItemAtPath:request.temporaryPath toPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
} }
if ([self.delegate respondsToSelector:@selector(davServer:didUploadFileAtPath:)]) { if ([self.delegate respondsToSelector:@selector(davServer:didUploadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didUploadFileAtPath:absolutePath]; [self.delegate davServer:self didUploadFileAtPath:absolutePath];
@@ -171,28 +263,28 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if (depthHeader && ![depthHeader isEqualToString:@"infinity"]) { if (depthHeader && ![depthHeader isEqualToString:@"infinity"]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader];
} }
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
NSString* itemName = [absolutePath lastPathComponent]; NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) { if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
} }
if (![self shouldDeleteItemAtPath:absolutePath]) { if (![self shouldDeleteItemAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
} }
NSError* error = nil; NSError* error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) { if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
} }
if ([self.delegate respondsToSelector:@selector(davServer:didDeleteItemAtPath:)]) { if ([self.delegate respondsToSelector:@selector(davServer:didDeleteItemAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didDeleteItemAtPath:absolutePath]; [self.delegate davServer:self didDeleteItemAtPath:absolutePath];
@@ -205,7 +297,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if ([request hasBody] && (request.contentLength > 0)) { if ([request hasBody] && (request.contentLength > 0)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_UnsupportedMediaType message:@"Unexpected request body for MKCOL method"]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_UnsupportedMediaType message:@"Unexpected request body for MKCOL method"];
} }
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
if (![self _checkSandboxedPath:absolutePath]) { if (![self _checkSandboxedPath:absolutePath]) {
@@ -215,16 +307,16 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) { if (![[NSFileManager defaultManager] fileExistsAtPath:[absolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Missing intermediate collection(s) for \"%@\"", relativePath];
} }
NSString* directoryName = [absolutePath lastPathComponent]; NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHidden && [directoryName hasPrefix:@"."]) { if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
} }
if (![self shouldCreateDirectoryAtPath:absolutePath]) { if (![self shouldCreateDirectoryAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
} }
NSError* error = nil; NSError* error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) { if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
@@ -233,12 +325,12 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
NSString* creationDateHeader = [request.headers objectForKey:@"X-GCDWebServer-CreationDate"]; NSString* creationDateHeader = [request.headers objectForKey:@"X-GCDWebServer-CreationDate"];
if (creationDateHeader) { if (creationDateHeader) {
NSDate* date = GCDWebServerParseISO8601(creationDateHeader); NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:absolutePath error:&error]) { if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed setting creation date for directory \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed setting creation date for directory \"%@\"", relativePath];
} }
} }
#endif #endif
if ([self.delegate respondsToSelector:@selector(davServer:didCreateDirectoryAtPath:)]) { if ([self.delegate respondsToSelector:@selector(davServer:didCreateDirectoryAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate davServer:self didCreateDirectoryAtPath:absolutePath]; [self.delegate davServer:self didCreateDirectoryAtPath:absolutePath];
@@ -254,15 +346,15 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader];
} }
} }
NSString* srcRelativePath = request.path; NSString* srcRelativePath = request.path;
NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:srcRelativePath]; NSString* srcAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:srcRelativePath];
if (![self _checkSandboxedPath:srcAbsolutePath]) { if (![self _checkSandboxedPath:srcAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
} }
NSString* dstRelativePath = [request.headers objectForKey:@"Destination"]; NSString* dstRelativePath = [request.headers objectForKey:@"Destination"];
NSRange range = [dstRelativePath rangeOfString:[request.headers objectForKey:@"Host"]]; NSRange range = [dstRelativePath rangeOfString:(NSString*)[request.headers objectForKey:@"Host"]];
if ((dstRelativePath == nil) || (range.location == NSNotFound)) { if ((dstRelativePath == nil) || (range.location == NSNotFound)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Malformed 'Destination' header: %@", dstRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Malformed 'Destination' header: %@", dstRelativePath];
} }
@@ -274,23 +366,23 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
if (![self _checkSandboxedPath:dstAbsolutePath]) { if (![self _checkSandboxedPath:dstAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", srcRelativePath];
} }
BOOL isDirectory; BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:[dstAbsolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) { if (![[NSFileManager defaultManager] fileExistsAtPath:[dstAbsolutePath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Invalid destination \"%@\"", dstRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Conflict message:@"Invalid destination \"%@\"", dstRelativePath];
} }
NSString* itemName = [dstAbsolutePath lastPathComponent]; NSString* itemName = [dstAbsolutePath lastPathComponent];
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) { if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", itemName];
} }
NSString* overwriteHeader = [request.headers objectForKey:@"Overwrite"]; NSString* overwriteHeader = [request.headers objectForKey:@"Overwrite"];
BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:dstAbsolutePath]; BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:dstAbsolutePath];
if (existing && ((isMove && ![overwriteHeader isEqualToString:@"T"]) || (!isMove && [overwriteHeader isEqualToString:@"F"]))) { if (existing && ((isMove && ![overwriteHeader isEqualToString:@"T"]) || (!isMove && [overwriteHeader isEqualToString:@"F"]))) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_PreconditionFailed message:@"Destination \"%@\" already exists", dstRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_PreconditionFailed message:@"Destination \"%@\" already exists", dstRelativePath];
} }
if (isMove) { if (isMove) {
if (![self shouldMoveItemFromPath:srcAbsolutePath toPath:dstAbsolutePath]) { if (![self shouldMoveItemFromPath:srcAbsolutePath toPath:dstAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", srcRelativePath, dstRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", srcRelativePath, dstRelativePath];
@@ -300,7 +392,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Copying \"%@\" to \"%@\" is not permitted", srcRelativePath, dstRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Copying \"%@\" to \"%@\" is not permitted", srcRelativePath, dstRelativePath];
} }
} }
NSError* error = nil; NSError* error = nil;
if (isMove) { if (isMove) {
[[NSFileManager defaultManager] removeItemAtPath:dstAbsolutePath error:NULL]; [[NSFileManager defaultManager] removeItemAtPath:dstAbsolutePath error:NULL];
@@ -312,7 +404,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden underlyingError:error message:@"Failed copying \"%@\" to \"%@\"", srcRelativePath, dstRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden underlyingError:error message:@"Failed copying \"%@\" to \"%@\"", srcRelativePath, dstRelativePath];
} }
} }
if (isMove) { if (isMove) {
if ([self.delegate respondsToSelector:@selector(davServer:didMoveItemFromPath:toPath:)]) { if ([self.delegate respondsToSelector:@selector(davServer:didMoveItemFromPath:toPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -326,7 +418,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
}); });
} }
} }
return [GCDWebServerResponse responseWithStatusCode:(existing ? kGCDWebServerHTTPStatusCode_NoContent : kGCDWebServerHTTPStatusCode_Created)]; return [GCDWebServerResponse responseWithStatusCode:(existing ? kGCDWebServerHTTPStatusCode_NoContent : kGCDWebServerHTTPStatusCode_Created)];
} }
@@ -355,7 +447,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
[xmlString appendFormat:@"<D:href>%@</D:href>", escapedPath]; [xmlString appendFormat:@"<D:href>%@</D:href>", escapedPath];
[xmlString appendString:@"<D:propstat>"]; [xmlString appendString:@"<D:propstat>"];
[xmlString appendString:@"<D:prop>"]; [xmlString appendString:@"<D:prop>"];
if (properties & kDAVProperty_ResourceType) { if (properties & kDAVProperty_ResourceType) {
if (isDirectory) { if (isDirectory) {
[xmlString appendString:@"<D:resourcetype><D:collection/></D:resourcetype>"]; [xmlString appendString:@"<D:resourcetype><D:collection/></D:resourcetype>"];
@@ -363,19 +455,19 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
[xmlString appendString:@"<D:resourcetype/>"]; [xmlString appendString:@"<D:resourcetype/>"];
} }
} }
if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) { if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) {
[xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", GCDWebServerFormatISO8601([attributes fileCreationDate])]; [xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", GCDWebServerFormatISO8601((NSDate*)[attributes fileCreationDate])];
} }
if ((properties & kDAVProperty_LastModified) && isFile && [attributes objectForKey:NSFileModificationDate]) { // Last modification date is not useful for directories as it changes implicitely and 'Last-Modified' header is not provided for directories anyway if ((properties & kDAVProperty_LastModified) && isFile && [attributes objectForKey:NSFileModificationDate]) { // Last modification date is not useful for directories as it changes implicitely and 'Last-Modified' header is not provided for directories anyway
[xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", GCDWebServerFormatRFC822([attributes fileModificationDate])]; [xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", GCDWebServerFormatRFC822((NSDate*)[attributes fileModificationDate])];
} }
if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) { if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) {
[xmlString appendFormat:@"<D:getcontentlength>%llu</D:getcontentlength>", [attributes fileSize]]; [xmlString appendFormat:@"<D:getcontentlength>%llu</D:getcontentlength>", [attributes fileSize]];
} }
[xmlString appendString:@"</D:prop>"]; [xmlString appendString:@"</D:prop>"];
[xmlString appendString:@"<D:status>HTTP/1.1 200 OK</D:status>"]; [xmlString appendString:@"<D:status>HTTP/1.1 200 OK</D:status>"];
[xmlString appendString:@"</D:propstat>"]; [xmlString appendString:@"</D:propstat>"];
@@ -397,7 +489,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
} else { } else {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader]; // TODO: Return 403 / propfind-finite-depth for "infinity" depth return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Unsupported 'Depth' header: %@", depthHeader]; // TODO: Return 403 / propfind-finite-depth for "infinity" depth
} }
DAVProperties properties = 0; DAVProperties properties = 0;
if (request.data.length) { if (request.data.length) {
BOOL success = YES; BOOL success = YES;
@@ -438,19 +530,19 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
} else { } else {
properties = kDAVAllProperties; properties = kDAVAllProperties;
} }
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
NSString* itemName = [absolutePath lastPathComponent]; NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) { if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Retrieving properties for item name \"%@\" is not allowed", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Retrieving properties for item name \"%@\" is not allowed", itemName];
} }
NSArray* items = nil; NSArray* items = nil;
if (isDirectory) { if (isDirectory) {
NSError* error = nil; NSError* error = nil;
@@ -459,7 +551,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
} }
} }
NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"]; NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];
[xmlString appendString:@"<D:multistatus xmlns:D=\"DAV:\">\n"]; [xmlString appendString:@"<D:multistatus xmlns:D=\"DAV:\">\n"];
if (![relativePath hasPrefix:@"/"]) { if (![relativePath hasPrefix:@"/"]) {
@@ -471,14 +563,14 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
relativePath = [relativePath stringByAppendingString:@"/"]; relativePath = [relativePath stringByAppendingString:@"/"];
} }
for (NSString* item in items) { for (NSString* item in items) {
if (_allowHidden || ![item hasPrefix:@"."]) { if (_allowHiddenItems || ![item hasPrefix:@"."]) {
[self _addPropertyResponseForItem:[absolutePath stringByAppendingPathComponent:item] resource:[relativePath stringByAppendingString:item] properties:properties xmlString:xmlString]; [self _addPropertyResponseForItem:[absolutePath stringByAppendingPathComponent:item] resource:[relativePath stringByAppendingString:item] properties:properties xmlString:xmlString];
} }
} }
} }
[xmlString appendString:@"</D:multistatus>"]; [xmlString appendString:@"</D:multistatus>"];
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[xmlString dataUsingEncoding:NSUTF8StringEncoding] GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
contentType:@"application/xml; charset=\"utf-8\""]; contentType:@"application/xml; charset=\"utf-8\""];
response.statusCode = kGCDWebServerHTTPStatusCode_MultiStatus; response.statusCode = kGCDWebServerHTTPStatusCode_MultiStatus;
return response; return response;
@@ -488,14 +580,14 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
if (!_IsMacFinder(request)) { if (!_IsMacFinder(request)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"LOCK method only allowed for Mac Finder"]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"LOCK method only allowed for Mac Finder"];
} }
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
NSString* depthHeader = [request.headers objectForKey:@"Depth"]; NSString* depthHeader = [request.headers objectForKey:@"Depth"];
NSString* timeoutHeader = [request.headers objectForKey:@"Timeout"]; NSString* timeoutHeader = [request.headers objectForKey:@"Timeout"];
NSString* scope = nil; NSString* scope = nil;
@@ -533,16 +625,16 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding]; NSString* string = [[NSString alloc] initWithData:request.data encoding:NSUTF8StringEncoding];
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Invalid DAV properties:\n%@", string];
} }
if (![scope isEqualToString:@"exclusive"] || ![type isEqualToString:@"write"] || ![depthHeader isEqualToString:@"0"]) { if (![scope isEqualToString:@"exclusive"] || ![type isEqualToString:@"write"] || ![depthHeader isEqualToString:@"0"]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking request \"%@/%@/%@\" for \"%@\" is not allowed", scope, type, depthHeader, relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking request \"%@/%@/%@\" for \"%@\" is not allowed", scope, type, depthHeader, relativePath];
} }
NSString* itemName = [absolutePath lastPathComponent]; NSString* itemName = [absolutePath lastPathComponent];
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) { if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking item name \"%@\" is not allowed", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking item name \"%@\" is not allowed", itemName];
} }
#ifdef __GCDWEBSERVER_ENABLE_TESTING__ #ifdef __GCDWEBSERVER_ENABLE_TESTING__
NSString* lockTokenHeader = [request.headers objectForKey:@"X-GCDWebServer-LockToken"]; NSString* lockTokenHeader = [request.headers objectForKey:@"X-GCDWebServer-LockToken"];
if (lockTokenHeader) { if (lockTokenHeader) {
@@ -556,7 +648,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
CFRelease(string); CFRelease(string);
CFRelease(uuid); CFRelease(uuid);
} }
NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"]; NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];
[xmlString appendString:@"<D:prop xmlns:D=\"DAV:\">\n"]; [xmlString appendString:@"<D:prop xmlns:D=\"DAV:\">\n"];
[xmlString appendString:@"<D:lockdiscovery>\n<D:activelock>\n"]; [xmlString appendString:@"<D:lockdiscovery>\n<D:activelock>\n"];
@@ -570,13 +662,13 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
[xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeoutHeader]; [xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeoutHeader];
} }
[xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token]; [xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token];
NSString* lockroot = [@"http://" stringByAppendingString:[[request.headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:relativePath]]]; NSString* lockroot = [@"http://" stringByAppendingString:[(NSString*)[request.headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:relativePath]]];
[xmlString appendFormat:@"<D:lockroot><D:href>%@</D:href></D:lockroot>\n", lockroot]; [xmlString appendFormat:@"<D:lockroot><D:href>%@</D:href></D:lockroot>\n", lockroot];
[xmlString appendString:@"</D:activelock>\n</D:lockdiscovery>\n"]; [xmlString appendString:@"</D:activelock>\n</D:lockdiscovery>\n"];
[xmlString appendString:@"</D:prop>"]; [xmlString appendString:@"</D:prop>"];
[self logVerbose:@"WebDAV pretending to lock \"%@\"", relativePath]; [self logVerbose:@"WebDAV pretending to lock \"%@\"", relativePath];
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[xmlString dataUsingEncoding:NSUTF8StringEncoding] GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
contentType:@"application/xml; charset=\"utf-8\""]; contentType:@"application/xml; charset=\"utf-8\""];
return response; return response;
} }
@@ -585,97 +677,30 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
if (!_IsMacFinder(request)) { if (!_IsMacFinder(request)) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"UNLOCK method only allowed for Mac Finder"]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_MethodNotAllowed message:@"UNLOCK method only allowed for Mac Finder"];
} }
NSString* relativePath = request.path; NSString* relativePath = request.path;
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath]; NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO; BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
NSString* tokenHeader = [request.headers objectForKey:@"Lock-Token"]; NSString* tokenHeader = [request.headers objectForKey:@"Lock-Token"];
if (!tokenHeader.length) { if (!tokenHeader.length) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Missing 'Lock-Token' header"]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Missing 'Lock-Token' header"];
} }
NSString* itemName = [absolutePath lastPathComponent]; NSString* itemName = [absolutePath lastPathComponent];
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) { if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Unlocking item name \"%@\" is not allowed", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Unlocking item name \"%@\" is not allowed", itemName];
} }
[self logVerbose:@"WebDAV pretending to unlock \"%@\"", relativePath]; [self logVerbose:@"WebDAV pretending to unlock \"%@\"", relativePath];
return [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NoContent]; return [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NoContent];
} }
@end @end
@implementation GCDWebDAVServer
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden;
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {
_uploadDirectory = [[path stringByStandardizingPath] copy];
GCDWebDAVServer* __unsafe_unretained server = self;
// 9.1 PROPFIND method
[self addDefaultHandlerForMethod:@"PROPFIND" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performPROPFIND:(GCDWebServerDataRequest*)request];
}];
// 9.3 MKCOL Method
[self addDefaultHandlerForMethod:@"MKCOL" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performMKCOL:(GCDWebServerDataRequest*)request];
}];
// 9.4 GET & HEAD methods
[self addDefaultHandlerForMethod:@"GET" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performGET:request];
}];
// 9.6 DELETE method
[self addDefaultHandlerForMethod:@"DELETE" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performDELETE:request];
}];
// 9.7 PUT method
[self addDefaultHandlerForMethod:@"PUT" requestClass:[GCDWebServerFileRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performPUT:(GCDWebServerFileRequest*)request];
}];
// 9.8 COPY method
[self addDefaultHandlerForMethod:@"COPY" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performCOPY:request isMove:NO];
}];
// 9.9 MOVE method
[self addDefaultHandlerForMethod:@"MOVE" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performCOPY:request isMove:YES];
}];
// 9.10 LOCK method
[self addDefaultHandlerForMethod:@"LOCK" requestClass:[GCDWebServerDataRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performLOCK:(GCDWebServerDataRequest*)request];
}];
// 9.11 UNLOCK method
[self addDefaultHandlerForMethod:@"UNLOCK" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performUNLOCK:request];
}];
// 10.1 OPTIONS method / DAV Header
[self addDefaultHandlerForMethod:@"OPTIONS" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server performOPTIONS:request];
}];
}
return self;
}
@end
@implementation GCDWebDAVServer (Subclassing) @implementation GCDWebDAVServer (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath { - (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {

View File

@@ -2,19 +2,19 @@
# http://guides.cocoapods.org/making/getting-setup-with-trunk.html # http://guides.cocoapods.org/making/getting-setup-with-trunk.html
# $ sudo gem update cocoapods # $ sudo gem update cocoapods
# (optional) $ pod trunk register {email} {name} --description={computer} # (optional) $ pod trunk register {email} {name} --description={computer}
# $ pod trunk push # $ pod trunk --verbose push
# DELETE THIS SECTION BEFORE PROCEEDING! # DELETE THIS SECTION BEFORE PROCEEDING!
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'GCDWebServer' s.name = 'GCDWebServer'
s.version = '3.3.1' s.version = '3.4'
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' } s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
s.license = { :type => 'BSD', :file => 'LICENSE' } s.license = { :type => 'BSD', :file => 'LICENSE' }
s.homepage = 'https://github.com/swisspol/GCDWebServer' s.homepage = 'https://github.com/swisspol/GCDWebServer'
s.summary = 'Lightweight GCD based HTTP server for OS X & iOS (includes web based uploader & WebDAV server)' s.summary = 'Lightweight GCD based HTTP server for OS X & iOS (includes web based uploader & WebDAV server)'
s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git', :tag => s.version.to_s } s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git', :tag => s.version.to_s }
s.ios.deployment_target = '5.0' s.ios.deployment_target = '8.0'
s.tvos.deployment_target = '9.0' s.tvos.deployment_target = '9.0'
s.osx.deployment_target = '10.7' s.osx.deployment_target = '10.7'
s.requires_arc = true s.requires_arc = true
@@ -35,12 +35,10 @@ Pod::Spec.new do |s|
s.subspec "CocoaLumberjack" do |cs| s.subspec "CocoaLumberjack" do |cs|
cs.dependency 'GCDWebServer/Core' cs.dependency 'GCDWebServer/Core'
cs.dependency 'CocoaLumberjack', '~> 2' cs.dependency 'CocoaLumberjack', '~> 3'
end end
s.subspec 'WebDAV' do |cs| s.subspec 'WebDAV' do |cs|
cs.default_subspec = 'Core'
cs.subspec "Core" do |ccs| cs.subspec "Core" do |ccs|
ccs.dependency 'GCDWebServer/Core' ccs.dependency 'GCDWebServer/Core'
ccs.source_files = 'GCDWebDAVServer/*.{h,m}' ccs.source_files = 'GCDWebDAVServer/*.{h,m}'
@@ -58,8 +56,6 @@ Pod::Spec.new do |s|
end end
s.subspec 'WebUploader' do |cs| s.subspec 'WebUploader' do |cs|
cs.default_subspec = 'Core'
cs.subspec "Core" do |ccs| cs.subspec "Core" do |ccs|
ccs.dependency 'GCDWebServer/Core' ccs.dependency 'GCDWebServer/Core'
ccs.source_files = 'GCDWebUploader/*.{h,m}' ccs.source_files = 'GCDWebUploader/*.{h,m}'

View File

@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 46; objectVersion = 48;
objects = { objects = {
/* Begin PBXAggregateTarget section */ /* Begin PBXAggregateTarget section */
@@ -465,8 +465,10 @@
E2DDD1B81BE6952F002CE867 /* tvOS Frameworks and Libraries */, E2DDD1B81BE6952F002CE867 /* tvOS Frameworks and Libraries */,
1AB674ADFE9D54B511CA2CBB /* Products */, 1AB674ADFE9D54B511CA2CBB /* Products */,
); );
indentWidth = 2;
name = LittleCMS; name = LittleCMS;
sourceTree = "<group>"; sourceTree = "<group>";
tabWidth = 2;
}; };
1AB674ADFE9D54B511CA2CBB /* Products */ = { 1AB674ADFE9D54B511CA2CBB /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
@@ -854,14 +856,13 @@
08FB7793FE84155DC02AAC07 /* Project object */ = { 08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0720; LastUpgradeCheck = 0830;
TargetAttributes = { TargetAttributes = {
CEE28CD01AE004D800F4023C = { CEE28CD01AE004D800F4023C = {
CreatedOnToolsVersion = 6.3; CreatedOnToolsVersion = 6.3;
}; };
CEE28CEE1AE0051F00F4023C = { CEE28CEE1AE0051F00F4023C = {
CreatedOnToolsVersion = 6.3; CreatedOnToolsVersion = 6.3;
DevelopmentTeam = 88W3E55T4B;
}; };
E24039241BA09207000B7089 = { E24039241BA09207000B7089 = {
CreatedOnToolsVersion = 6.4; CreatedOnToolsVersion = 6.4;
@@ -871,7 +872,6 @@
}; };
E2DDD1C61BE698A8002CE867 = { E2DDD1C61BE698A8002CE867 = {
CreatedOnToolsVersion = 7.1; CreatedOnToolsVersion = 7.1;
DevelopmentTeam = 88W3E55T4B;
}; };
E2DDD1F51BE69EE4002CE867 = { E2DDD1F51BE69EE4002CE867 = {
CreatedOnToolsVersion = 7.1; CreatedOnToolsVersion = 7.1;
@@ -879,7 +879,7 @@
}; };
}; };
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */; buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
compatibilityVersion = "Xcode 3.2"; compatibilityVersion = "Xcode 8.0";
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 1; hasScannedForEncodings = 1;
knownRegions = ( knownRegions = (
@@ -1208,7 +1208,7 @@
1DEB928A08733DD80010E9CD /* Debug */ = { 1DEB928A08733DD80010E9CD /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
BUNDLE_VERSION_STRING = 3.3.1; BUNDLE_VERSION_STRING = 3.3.4;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
@@ -1237,7 +1237,7 @@
"-Wno-cstring-format-directive", "-Wno-cstring-format-directive",
"-Wno-reserved-id-macro", "-Wno-reserved-id-macro",
"-Wno-cast-qual", "-Wno-cast-qual",
"-Wno-nullable-to-nonnull-conversion", "-Wno-partial-availability",
); );
}; };
name = Debug; name = Debug;
@@ -1245,7 +1245,7 @@
1DEB928B08733DD80010E9CD /* Release */ = { 1DEB928B08733DD80010E9CD /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
BUNDLE_VERSION_STRING = 3.3.1; BUNDLE_VERSION_STRING = 3.3.4;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__; GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
@@ -1268,6 +1268,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.7; MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = GCDWebServers; PRODUCT_NAME = GCDWebServers;
SDKROOT = macosx; SDKROOT = macosx;
SKIP_INSTALL = YES;
}; };
name = Debug; name = Debug;
}; };
@@ -1283,13 +1284,13 @@
MACOSX_DEPLOYMENT_TARGET = 10.7; MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = GCDWebServers; PRODUCT_NAME = GCDWebServers;
SDKROOT = macosx; SDKROOT = macosx;
SKIP_INSTALL = YES;
}; };
name = Release; name = Release;
}; };
CEE28D031AE0052000F4023C /* Debug */ = { CEE28D031AE0052000F4023C /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
@@ -1298,15 +1299,14 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = GCDWebServers; PRODUCT_NAME = GCDWebServers;
PROVISIONING_PROFILE = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES;
}; };
name = Debug; name = Debug;
}; };
CEE28D041AE0052000F4023C /* Release */ = { CEE28D041AE0052000F4023C /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
@@ -1315,8 +1315,8 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = GCDWebServers; PRODUCT_NAME = GCDWebServers;
PROVISIONING_PROFILE = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES;
}; };
name = Release; name = Release;
}; };
@@ -1373,7 +1373,6 @@
E2DDD1901BE69404002CE867 /* Debug */ = { E2DDD1901BE69404002CE867 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
@@ -1381,8 +1380,8 @@
INFOPLIST_FILE = Frameworks/Info.plist; INFOPLIST_FILE = Frameworks/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = GCDWebServers; PRODUCT_NAME = GCDWebServers;
PROVISIONING_PROFILE = "";
SDKROOT = appletvos; SDKROOT = appletvos;
SKIP_INSTALL = YES;
TVOS_DEPLOYMENT_TARGET = 9.0; TVOS_DEPLOYMENT_TARGET = 9.0;
}; };
name = Debug; name = Debug;
@@ -1390,7 +1389,6 @@
E2DDD1911BE69404002CE867 /* Release */ = { E2DDD1911BE69404002CE867 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
@@ -1398,8 +1396,8 @@
INFOPLIST_FILE = Frameworks/Info.plist; INFOPLIST_FILE = Frameworks/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_NAME = GCDWebServers; PRODUCT_NAME = GCDWebServers;
PROVISIONING_PROFILE = "";
SDKROOT = appletvos; SDKROOT = appletvos;
SKIP_INSTALL = YES;
TVOS_DEPLOYMENT_TARGET = 9.0; TVOS_DEPLOYMENT_TARGET = 9.0;
}; };
name = Release; name = Release;
@@ -1407,7 +1405,6 @@
E2DDD1D91BE698A8002CE867 /* Debug */ = { E2DDD1D91BE698A8002CE867 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = tvOS/Info.plist; INFOPLIST_FILE = tvOS/Info.plist;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
@@ -1419,7 +1416,6 @@
E2DDD1DA1BE698A8002CE867 /* Release */ = { E2DDD1DA1BE698A8002CE867 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = tvOS/Info.plist; INFOPLIST_FILE = tvOS/Info.plist;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
@@ -1431,10 +1427,9 @@
E2DDD20B1BE69EE5002CE867 /* Debug */ = { E2DDD20B1BE69EE5002CE867 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
ENABLE_BITCODE = YES; ENABLE_BITCODE = YES;
INFOPLIST_FILE = iOS/Info.plist; INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 6.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1445,10 +1440,9 @@
E2DDD20C1BE69EE5002CE867 /* Release */ = { E2DDD20C1BE69EE5002CE867 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "iPhone Developer";
ENABLE_BITCODE = YES; ENABLE_BITCODE = YES;
INFOPLIST_FILE = iOS/Info.plist; INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 6.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0720" LastUpgradeVersion = "0830"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -26,7 +26,8 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES"
enableAddressSanitizer = "YES">
<Testables> <Testables>
<TestableReference <TestableReference
skipped = "NO"> skipped = "NO">
@@ -50,7 +51,6 @@
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
enableAddressSanitizer = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion> <MacroExpansion>
@@ -63,11 +63,6 @@
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0720" LastUpgradeVersion = "0830"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -49,7 +49,6 @@
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
enableAddressSanitizer = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion> <MacroExpansion>
@@ -62,11 +61,6 @@
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0720" LastUpgradeVersion = "0830"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -49,7 +49,6 @@
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
enableAddressSanitizer = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion> <MacroExpansion>
@@ -62,11 +61,6 @@
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction

View File

@@ -30,6 +30,8 @@
#import "GCDWebServerRequest.h" #import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h" #import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerMatchBlock is called for every handler added to the * The GCDWebServerMatchBlock is called for every handler added to the
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have * GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
@@ -40,7 +42,7 @@
* GCDWebServerRequest instance created with the same basic info. * GCDWebServerRequest instance created with the same basic info.
* Otherwise, it simply returns nil. * Otherwise, it simply returns nil.
*/ */
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
/** /**
* The GCDWebServerProcessBlock is called after the HTTP request has been fully * The GCDWebServerProcessBlock is called after the HTTP request has been fully
@@ -52,7 +54,7 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
* recommended to return a GCDWebServerErrorResponse on error so more useful * recommended to return a GCDWebServerErrorResponse on error so more useful
* information can be returned to the client. * information can be returned to the client.
*/ */
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request); typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);
/** /**
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock * The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
@@ -64,8 +66,8 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
* It's however recommended to return a GCDWebServerErrorResponse on error so more * It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client. * useful information can be returned to the client.
*/ */
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response); typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response);
typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock); typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
/** /**
* The port used by the GCDWebServer (NSNumber / NSUInteger). * The port used by the GCDWebServer (NSNumber / NSUInteger).
@@ -176,6 +178,15 @@ extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
*/ */
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;
/**
* Set the dispatch queue priority on which server connection will be
* run (NSNumber / long).
*
*
* The default value is DISPATCH_QUEUE_PRIORITY_DEFAULT.
*/
extern NSString* const GCDWebServerOption_DispatchQueuePriority;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
/** /**
@@ -286,7 +297,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/** /**
* Sets the delegate for the server. * Sets the delegate for the server.
*/ */
@property(nonatomic, assign) id<GCDWebServerDelegate> delegate; @property(nonatomic, weak, nullable) id<GCDWebServerDelegate> delegate;
/** /**
* Returns YES if the server is currently running. * Returns YES if the server is currently running.
@@ -306,7 +317,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @warning This property is only valid if the server is running and Bonjour * @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds. * registration has successfully completed, which can take up to a few seconds.
*/ */
@property(nonatomic, readonly) NSString* bonjourName; @property(nonatomic, readonly, nullable) NSString* bonjourName;
/** /**
* Returns the Bonjour service type used by the server. * Returns the Bonjour service type used by the server.
@@ -314,7 +325,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @warning This property is only valid if the server is running and Bonjour * @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds. * registration has successfully completed, which can take up to a few seconds.
*/ */
@property(nonatomic, readonly) NSString* bonjourType; @property(nonatomic, readonly, nullable) NSString* bonjourType;
/** /**
* This method is the designated initializer for the class. * This method is the designated initializer for the class.
@@ -354,7 +365,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* Returns NO if the server failed to start and sets "error" argument if not NULL. * Returns NO if the server failed to start and sets "error" argument if not NULL.
*/ */
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error; - (BOOL)startWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error;
/** /**
* Stops the server and prevents it to accepts new HTTP requests. * Stops the server and prevents it to accepts new HTTP requests.
@@ -374,7 +385,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* @warning This property is only valid if the server is running. * @warning This property is only valid if the server is running.
*/ */
@property(nonatomic, readonly) NSURL* serverURL; @property(nonatomic, readonly, nullable) NSURL* serverURL;
/** /**
* Returns the server's Bonjour URL. * Returns the server's Bonjour URL.
@@ -384,7 +395,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* Also be aware this property will not automatically update if the Bonjour hostname * Also be aware this property will not automatically update if the Bonjour hostname
* has been dynamically changed after the server started running (this should be rare). * has been dynamically changed after the server started running (this should be rare).
*/ */
@property(nonatomic, readonly) NSURL* bonjourServerURL; @property(nonatomic, readonly, nullable) NSURL* bonjourServerURL;
/** /**
* Returns the server's public URL. * Returns the server's public URL.
@@ -392,7 +403,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @warning This property is only valid if the server is running and NAT port * @warning This property is only valid if the server is running and NAT port
* mapping is active. * mapping is active.
*/ */
@property(nonatomic, readonly) NSURL* publicServerURL; @property(nonatomic, readonly, nullable) NSURL* publicServerURL;
/** /**
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS) * Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
@@ -409,7 +420,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* Returns NO if the server failed to start. * Returns NO if the server failed to start.
*/ */
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; - (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
@@ -422,7 +433,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* @warning This method must be used from the main thread only. * @warning This method must be used from the main thread only.
*/ */
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name; - (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
/** /**
* Runs the server synchronously using -startWithOptions: until a SIGTERM or * Runs the server synchronously using -startWithOptions: until a SIGTERM or
@@ -433,7 +444,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* @warning This method must be used from the main thread only. * @warning This method must be used from the main thread only.
*/ */
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error; - (BOOL)runWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error;
#endif #endif
@@ -489,7 +500,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* Adds a handler to the server to respond to incoming "GET" HTTP requests * Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a specific case-insensitive path with in-memory data. * with a specific case-insensitive path with in-memory data.
*/ */
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge; - (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(nullable NSString*)contentType cacheAge:(NSUInteger)cacheAge;
/** /**
* Adds a handler to the server to respond to incoming "GET" HTTP requests * Adds a handler to the server to respond to incoming "GET" HTTP requests
@@ -506,7 +517,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* The "indexFilename" argument allows to specify an "index" file name to use * The "indexFilename" argument allows to specify an "index" file name to use
* when the request path corresponds to a directory. * when the request path corresponds to a directory.
*/ */
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; - (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(nullable NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
@end @end
@@ -537,12 +548,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* GWS_LOG_INFO(...) * GWS_LOG_INFO(...)
* GWS_LOG_WARNING(...) * GWS_LOG_WARNING(...)
* GWS_LOG_ERROR(...) * GWS_LOG_ERROR(...)
* GWS_LOG_EXCEPTION(__EXCEPTION__)
* *
* IMPORTANT: Except for GWS_LOG_EXCEPTION() which gets passed an NSException, * IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG()
* these macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() macro * macro should not do anything unless the preprocessor constant "DEBUG" evaluates
* should not do anything unless the preprocessor constant "DEBUG" evaluates to * to non-zero.
* non-zero.
* *
* The logging methods below send log messages to the same logging facility * The logging methods below send log messages to the same logging facility
* used by GCDWebServer. They can be used for consistency wherever you interact * used by GCDWebServer. They can be used for consistency wherever you interact
@@ -562,34 +571,28 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* INFO = 2 * INFO = 2
* WARNING = 3 * WARNING = 3
* ERROR = 4 * ERROR = 4
* EXCEPTION = 5
*/ */
+ (void)setLogLevel:(int)level; + (void)setLogLevel:(int)level;
/** /**
* Logs a message to the logging facility at the VERBOSE level. * Logs a message to the logging facility at the VERBOSE level.
*/ */
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/** /**
* Logs a message to the logging facility at the INFO level. * Logs a message to the logging facility at the INFO level.
*/ */
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/** /**
* Logs a message to the logging facility at the WARNING level. * Logs a message to the logging facility at the WARNING level.
*/ */
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/** /**
* Logs a message to the logging facility at the ERROR level. * Logs a message to the logging facility at the ERROR level.
*/ */
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2);
/**
* Logs an exception to the logging facility at the EXCEPTION level.
*/
- (void)logException:(NSException*)exception;
@end @end
@@ -611,8 +614,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* Returns the number of failed tests or -1 if server failed to start. * Returns the number of failed tests or -1 if server failed to start.
*/ */
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path; - (NSInteger)runTestsWithOptions:(nullable NSDictionary*)options inDirectory:(NSString*)path;
@end @end
#endif #endif
NS_ASSUME_NONNULL_END

View File

@@ -63,6 +63,7 @@ NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAcco
NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass"; NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET"; NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval"; NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
NSString* const GCDWebServerOption_DispatchQueuePriority = @"DispatchQueuePriority";
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground"; NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
#endif #endif
@@ -91,7 +92,7 @@ static BOOL _run;
#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ #ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) { void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) {
static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR", "EXCEPTION"}; static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR"};
static int enableLogging = -1; static int enableLogging = -1;
if (enableLogging < 0) { if (enableLogging < 0) {
enableLogging = (isatty(STDERR_FILENO) ? 1 : 0); enableLogging = (isatty(STDERR_FILENO) ? 1 : 0);
@@ -131,18 +132,9 @@ static void _ExecuteMainThreadRunLoopSources() {
#endif #endif
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerAsyncProcessBlock _asyncProcessBlock;
}
@end
@implementation GCDWebServerHandler @implementation GCDWebServerHandler
@synthesize matchBlock=_matchBlock, asyncProcessBlock=_asyncProcessBlock; - (instancetype)initWithMatchBlock:(GCDWebServerMatchBlock _Nonnull)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock _Nonnull)processBlock {
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
if ((self = [super init])) { if ((self = [super init])) {
_matchBlock = [matchBlock copy]; _matchBlock = [matchBlock copy];
_asyncProcessBlock = [processBlock copy]; _asyncProcessBlock = [processBlock copy];
@@ -152,25 +144,19 @@ static void _ExecuteMainThreadRunLoopSources() {
@end @end
@interface GCDWebServer () { @implementation GCDWebServer {
@private
id<GCDWebServerDelegate> __unsafe_unretained _delegate;
dispatch_queue_t _syncQueue; dispatch_queue_t _syncQueue;
dispatch_group_t _sourceGroup; dispatch_group_t _sourceGroup;
NSMutableArray* _handlers; NSMutableArray* _handlers;
NSInteger _activeConnections; // Accessed through _syncQueue only NSInteger _activeConnections; // Accessed through _syncQueue only
BOOL _connected; // Accessed on main thread only BOOL _connected; // Accessed on main thread only
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
NSDictionary* _options; NSDictionary* _options;
NSString* _serverName;
NSString* _authenticationRealm;
NSMutableDictionary* _authenticationBasicAccounts; NSMutableDictionary* _authenticationBasicAccounts;
NSMutableDictionary* _authenticationDigestAccounts; NSMutableDictionary* _authenticationDigestAccounts;
Class _connectionClass; Class _connectionClass;
BOOL _mapHEADToGET;
CFTimeInterval _disconnectDelay; CFTimeInterval _disconnectDelay;
NSUInteger _port;
dispatch_source_t _source4; dispatch_source_t _source4;
dispatch_source_t _source6; dispatch_source_t _source6;
CFNetServiceRef _registrationService; CFNetServiceRef _registrationService;
@@ -189,13 +175,6 @@ static void _ExecuteMainThreadRunLoopSources() {
BOOL _recording; BOOL _recording;
#endif #endif
} }
@end
@implementation GCDWebServer
@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts,
shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
+ (void)initialize { + (void)initialize {
GCDWebServerInitializeFunctions(); GCDWebServerInitializeFunctions();
@@ -218,7 +197,7 @@ static void _ExecuteMainThreadRunLoopSources() {
GWS_DCHECK(_activeConnections == 0); GWS_DCHECK(_activeConnections == 0);
GWS_DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source GWS_DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source
GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE #if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
dispatch_release(_sourceGroup); dispatch_release(_sourceGroup);
dispatch_release(_syncQueue); dispatch_release(_syncQueue);
@@ -233,10 +212,10 @@ static void _ExecuteMainThreadRunLoopSources() {
if (_backgroundTask == UIBackgroundTaskInvalid) { if (_backgroundTask == UIBackgroundTaskInvalid) {
GWS_LOG_DEBUG(@"Did start background task"); GWS_LOG_DEBUG(@"Did start background task");
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]); GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
[self _endBackgroundTask]; [self _endBackgroundTask];
}]; }];
} else { } else {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
@@ -251,13 +230,13 @@ static void _ExecuteMainThreadRunLoopSources() {
GWS_DCHECK(_connected == NO); GWS_DCHECK(_connected == NO);
_connected = YES; _connected = YES;
GWS_LOG_DEBUG(@"Did connect"); GWS_LOG_DEBUG(@"Did connect");
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) { if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
[self _startBackgroundTask]; [self _startBackgroundTask];
} }
#endif #endif
if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) { if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
[_delegate webServerDidConnect:self]; [_delegate webServerDidConnect:self];
} }
@@ -265,7 +244,7 @@ static void _ExecuteMainThreadRunLoopSources() {
- (void)willStartConnection:(GCDWebServerConnection*)connection { - (void)willStartConnection:(GCDWebServerConnection*)connection {
dispatch_sync(_syncQueue, ^{ dispatch_sync(_syncQueue, ^{
GWS_DCHECK(_activeConnections >= 0); GWS_DCHECK(_activeConnections >= 0);
if (_activeConnections == 0) { if (_activeConnections == 0) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -280,7 +259,7 @@ static void _ExecuteMainThreadRunLoopSources() {
}); });
} }
_activeConnections += 1; _activeConnections += 1;
}); });
} }
@@ -307,11 +286,11 @@ static void _ExecuteMainThreadRunLoopSources() {
GWS_DCHECK(_connected == YES); GWS_DCHECK(_connected == YES);
_connected = NO; _connected = NO;
GWS_LOG_DEBUG(@"Did disconnect"); GWS_LOG_DEBUG(@"Did disconnect");
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[self _endBackgroundTask]; [self _endBackgroundTask];
#endif #endif
if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) { if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) {
[_delegate webServerDidDisconnect:self]; [_delegate webServerDidDisconnect:self];
} }
@@ -354,9 +333,10 @@ static void _ExecuteMainThreadRunLoopSources() {
} }
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock { - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
[self addHandlerWithMatchBlock:matchBlock asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { [self addHandlerWithMatchBlock:matchBlock
completionBlock(processBlock(request)); asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
}]; completionBlock(processBlock(request));
}];
} }
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock { - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
@@ -462,7 +442,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
if (listeningSocket > 0) { if (listeningSocket > 0) {
int yes = 1; int yes = 1;
setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
if (bind(listeningSocket, address, length) == 0) { if (bind(listeningSocket, address, length) == 0) {
if (listen(listeningSocket, (int)maxPendingConnections) == 0) { if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket); GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket);
@@ -481,7 +461,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
close(listeningSocket); close(listeningSocket);
} }
} else { } else {
if (error) { if (error) {
*error = GCDWebServerMakePosixError(errno); *error = GCDWebServerMakePosixError(errno);
@@ -493,9 +473,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 { - (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
dispatch_group_enter(_sourceGroup); dispatch_group_enter(_sourceGroup);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue); 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, ^{ dispatch_source_set_cancel_handler(source, ^{
@autoreleasepool { @autoreleasepool {
int result = close(listeningSocket); int result = close(listeningSocket);
if (result != 0) { if (result != 0) {
@@ -505,17 +485,17 @@ static inline NSString* _EncodeBase64(NSString* string) {
} }
} }
dispatch_group_leave(_sourceGroup); dispatch_group_leave(_sourceGroup);
}); });
dispatch_source_set_event_handler(source, ^{ dispatch_source_set_event_handler(source, ^{
@autoreleasepool { @autoreleasepool {
struct sockaddr_storage remoteSockAddr; struct sockaddr_storage remoteSockAddr;
socklen_t remoteAddrLen = sizeof(remoteSockAddr); socklen_t remoteAddrLen = sizeof(remoteSockAddr);
int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen); int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen);
if (socket > 0) { if (socket > 0) {
NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen]; NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
struct sockaddr_storage localSockAddr; struct sockaddr_storage localSockAddr;
socklen_t localAddrLen = sizeof(localSockAddr); socklen_t localAddrLen = sizeof(localSockAddr);
NSData* localAddress = nil; NSData* localAddress = nil;
@@ -525,28 +505,28 @@ static inline NSString* _EncodeBase64(NSString* string) {
} else { } else {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
} }
int noSigPipe = 1; int noSigPipe = 1;
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE 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 = [[_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 [connection self]; // Prevent compiler from complaining about unused variable / useless statement
} else { } else {
GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
} }
} }
}); });
return source; return source;
} }
- (BOOL)_start:(NSError**)error { - (BOOL)_start:(NSError**)error {
GWS_DCHECK(_source4 == NULL); GWS_DCHECK(_source4 == NULL);
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue]; BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
struct sockaddr_in addr4; struct sockaddr_in addr4;
bzero(&addr4, sizeof(addr4)); bzero(&addr4, sizeof(addr4));
addr4.sin_len = sizeof(addr4); addr4.sin_len = sizeof(addr4);
@@ -566,7 +546,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno); GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
} }
} }
struct sockaddr_in6 addr6; struct sockaddr_in6 addr6;
bzero(&addr6, sizeof(addr6)); bzero(&addr6, sizeof(addr6));
addr6.sin6_len = sizeof(addr6); addr6.sin6_len = sizeof(addr6);
@@ -578,7 +558,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
close(listeningSocket4); close(listeningSocket4);
return NO; return NO;
} }
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; _serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil); NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) { if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
@@ -597,26 +577,27 @@ static inline NSString* _EncodeBase64(NSString* string) {
}]; }];
} }
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; _shouldAutomaticallyMapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue]; _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
_dispatchQueuePriority = [_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO]; _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES]; _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
_port = port; _port = port;
_bindToLocalhost = bindToLocalhost; _bindToLocalhost = bindToLocalhost;
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil); NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
if (bonjourName) { if (bonjourName) {
_registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port); _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
if (_registrationService) { if (_registrationService) {
CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context); CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFStreamError streamError = {0}; CFStreamError streamError = {0};
CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError); CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService); _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
if (_resolutionService) { if (_resolutionService) {
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context); CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
@@ -628,7 +609,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
GWS_LOG_ERROR(@"Failed creating CFNetService for registration"); GWS_LOG_ERROR(@"Failed creating CFNetService for registration");
} }
} }
if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) { if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self); DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
if (status == kDNSServiceErr_NoError) { if (status == kDNSServiceErr_NoError) {
@@ -651,7 +632,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status); GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status);
} }
} }
dispatch_resume(_source4); dispatch_resume(_source4);
dispatch_resume(_source6); dispatch_resume(_source6);
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL); GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
@@ -660,13 +641,13 @@ static inline NSString* _EncodeBase64(NSString* string) {
[_delegate webServerDidStart:self]; [_delegate webServerDidStart:self];
}); });
} }
return YES; return YES;
} }
- (void)_stop { - (void)_stop {
GWS_DCHECK(_source4 != NULL); GWS_DCHECK(_source4 != NULL);
if (_dnsService) { if (_dnsService) {
_dnsAddress = nil; _dnsAddress = nil;
_dnsPort = 0; _dnsPort = 0;
@@ -682,7 +663,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
DNSServiceRefDeallocate(_dnsService); DNSServiceRefDeallocate(_dnsService);
_dnsService = NULL; _dnsService = NULL;
} }
if (_registrationService) { if (_registrationService) {
if (_resolutionService) { if (_resolutionService) {
CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
@@ -697,7 +678,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
CFRelease(_registrationService); CFRelease(_registrationService);
_registrationService = NULL; _registrationService = NULL;
} }
dispatch_source_cancel(_source6); dispatch_source_cancel(_source6);
dispatch_source_cancel(_source4); dispatch_source_cancel(_source4);
dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed
@@ -711,12 +692,12 @@ static inline NSString* _EncodeBase64(NSString* string) {
_source4 = NULL; _source4 = NULL;
_port = 0; _port = 0;
_bindToLocalhost = NO; _bindToLocalhost = NO;
_serverName = nil; _serverName = nil;
_authenticationRealm = nil; _authenticationRealm = nil;
_authenticationBasicAccounts = nil; _authenticationBasicAccounts = nil;
_authenticationDigestAccounts = nil; _authenticationDigestAccounts = nil;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (_disconnectTimer) { if (_disconnectTimer) {
CFRunLoopTimerInvalidate(_disconnectTimer); CFRunLoopTimerInvalidate(_disconnectTimer);
@@ -725,7 +706,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
[self _didDisconnect]; [self _didDisconnect];
} }
}); });
GWS_LOG_INFO(@"%@ stopped", [self class]); GWS_LOG_INFO(@"%@ stopped", [self class]);
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -893,32 +874,38 @@ static inline NSString* _EncodeBase64(NSString* string) {
@implementation GCDWebServer (Handlers) @implementation GCDWebServer (Handlers)
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addDefaultHandlerForMethod:method requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { [self addDefaultHandlerForMethod:method
completionBlock(block(request)); requestClass:aClass
}]; asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
} }
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { - (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* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:method]) { if (![requestMethod isEqualToString:method]) {
return nil; return nil;
} }
return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
} asyncProcessBlock:block]; }
asyncProcessBlock:block];
} }
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerForMethod:method path:path requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { [self addHandlerForMethod:method
completionBlock(block(request)); path:path
}]; requestClass:aClass
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
} }
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) { 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* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:method]) { if (![requestMethod isEqualToString:method]) {
return nil; return nil;
} }
@@ -926,24 +913,28 @@ static inline NSString* _EncodeBase64(NSString* string) {
return nil; return nil;
} }
return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
} asyncProcessBlock:block]; }
asyncProcessBlock:block];
} else { } else {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
} }
} }
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerForMethod:method pathRegex:regex requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { [self addHandlerForMethod:method
completionBlock(block(request)); pathRegex:regex
}]; requestClass:aClass
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
} }
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL]; NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) { 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* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:method]) { if (![requestMethod isEqualToString:method]) {
return nil; return nil;
} }
@@ -969,8 +960,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
[request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures]; [request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
return request; return request;
} asyncProcessBlock:block]; }
asyncProcessBlock:block];
} else { } else {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
} }
@@ -981,29 +973,35 @@ static inline NSString* _EncodeBase64(NSString* string) {
@implementation GCDWebServer (GETHandlers) @implementation GCDWebServer (GETHandlers)
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge { - (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { [self addHandlerForMethod:@"GET"
path:path
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType]; requestClass:[GCDWebServerRequest class]
response.cacheControlMaxAge = cacheAge; processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return response;
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
}]; response.cacheControlMaxAge = cacheAge;
return response;
}];
} }
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { - (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { [self addHandlerForMethod:@"GET"
path:path
GCDWebServerResponse* response = nil; requestClass:[GCDWebServerRequest class]
if (allowRangeRequests) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; GCDWebServerResponse* response = nil;
} else { if (allowRangeRequests) {
response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment]; response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
} [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
response.cacheControlMaxAge = cacheAge; } else {
return response; response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment];
}
}]; response.cacheControlMaxAge = cacheAge;
return response;
}];
} }
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path { - (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
@@ -1039,8 +1037,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { - (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) { if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
GCDWebServer* __unsafe_unretained server = self; 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* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:@"GET"]) { if (![requestMethod isEqualToString:@"GET"]) {
return nil; return nil;
} }
@@ -1048,39 +1046,40 @@ static inline NSString* _EncodeBase64(NSString* string) {
return nil; return nil;
} }
return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
} processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { }
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
GCDWebServerResponse* response = nil;
NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]]; GCDWebServerResponse* response = nil;
NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType]; NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
if (fileType) { NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType];
if ([fileType isEqualToString:NSFileTypeDirectory]) { if (fileType) {
if (indexFilename) { if ([fileType isEqualToString:NSFileTypeDirectory]) {
NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename]; if (indexFilename) {
NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType]; NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
if ([indexType isEqualToString:NSFileTypeRegular]) { NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType];
return [GCDWebServerFileResponse responseWithFile:indexPath]; if ([indexType isEqualToString:NSFileTypeRegular]) {
return [GCDWebServerFileResponse responseWithFile:indexPath];
}
}
response = [server _responseWithContentsOfDirectory:filePath];
} else if ([fileType isEqualToString:NSFileTypeRegular]) {
if (allowRangeRequests) {
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
} else {
response = [GCDWebServerFileResponse responseWithFile:filePath];
}
} }
} }
response = [server _responseWithContentsOfDirectory:filePath]; if (response) {
} else if ([fileType isEqualToString:NSFileTypeRegular]) { response.cacheControlMaxAge = cacheAge;
if (allowRangeRequests) {
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
} else { } else {
response = [GCDWebServerFileResponse responseWithFile:filePath]; response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
} }
} return response;
}
if (response) { }];
response.cacheControlMaxAge = cacheAge;
} else {
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
}
return response;
}];
} else { } else {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
} }
@@ -1128,10 +1127,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
va_end(arguments); va_end(arguments);
} }
- (void)logException:(NSException*)exception {
GWS_LOG_EXCEPTION(exception);
}
@end @end
#ifdef __GCDWEBSERVER_ENABLE_TESTING__ #ifdef __GCDWEBSERVER_ENABLE_TESTING__
@@ -1205,11 +1200,11 @@ static void _LogResult(NSString* format, ...) {
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path { - (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
GWS_DCHECK([NSThread isMainThread]); GWS_DCHECK([NSThread isMainThread]);
NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs NSArray* ignoredHeaders = @[ @"Date", @"Etag" ]; // Dates are always different by definition and ETags depend on file system node IDs
NSInteger result = -1; NSInteger result = -1;
if ([self startWithOptions:options error:NULL]) { if ([self startWithOptions:options error:NULL]) {
_ExecuteMainThreadRunLoopSources(); _ExecuteMainThreadRunLoopSources();
result = 0; result = 0;
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL]; NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
for (NSString* requestFile in files) { for (NSString* requestFile in files) {
@@ -1231,19 +1226,19 @@ static void _LogResult(NSString* format, ...) {
if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) { if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) {
NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]]; NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]];
if (responseData) { if (responseData) {
CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO); CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO);
if (expectedResponse) { if (expectedResponse) {
CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port); CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port);
if (actualResponse) { if (actualResponse) {
success = YES; success = YES;
CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse); CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse);
CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse); CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse);
if (actualStatusCode != expectedStatusCode) { if (actualStatusCode != expectedStatusCode) {
_LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode); _LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode);
success = NO; success = NO;
} }
NSDictionary* expectedHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(expectedResponse)); NSDictionary* expectedHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(expectedResponse));
NSDictionary* actualHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(actualResponse)); NSDictionary* actualHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(actualResponse));
for (NSString* expectedHeader in expectedHeaders) { for (NSString* expectedHeader in expectedHeaders) {
@@ -1263,7 +1258,7 @@ static void _LogResult(NSString* format, ...) {
success = NO; success = NO;
} }
} }
NSString* expectedContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length"))); NSString* expectedContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length")));
NSData* expectedBody = CFBridgingRelease(CFHTTPMessageCopyBody(expectedResponse)); NSData* expectedBody = CFBridgingRelease(CFHTTPMessageCopyBody(expectedResponse));
NSString* actualContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length"))); NSString* actualContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length")));
@@ -1276,20 +1271,20 @@ static void _LogResult(NSString* format, ...) {
success = NO; success = NO;
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
#if DEBUG #if DEBUG
if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) { if (GCDWebServerIsTextContentType((NSString*)[expectedHeaders objectForKey:@"Content-Type"])) {
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) { if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
NSTask* task = [[NSTask alloc] init]; NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/opendiff"]; [task setLaunchPath:@"/usr/bin/opendiff"];
[task setArguments:@[expectedPath, actualPath]]; [task setArguments:@[ expectedPath, actualPath ]];
[task launch]; [task launch];
} }
} }
#endif #endif
#endif #endif
} }
CFRelease(actualResponse); CFRelease(actualResponse);
} }
CFRelease(expectedResponse); CFRelease(expectedResponse);
@@ -1312,9 +1307,9 @@ static void _LogResult(NSString* format, ...) {
} }
_ExecuteMainThreadRunLoopSources(); _ExecuteMainThreadRunLoopSources();
} }
[self stop]; [self stop];
_ExecuteMainThreadRunLoopSources(); _ExecuteMainThreadRunLoopSources();
} }
return result; return result;

View File

@@ -27,6 +27,8 @@
#import "GCDWebServer.h" #import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebServerHandler; @class GCDWebServerHandler;
/** /**
@@ -139,7 +141,7 @@
* The default implementation checks for HTTP authentication if applicable * The default implementation checks for HTTP authentication if applicable
* and returns a barebone 401 status code response if authentication failed. * and returns a barebone 401 status code response if authentication failed.
*/ */
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request; - (nullable GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;
/** /**
* Assuming a valid HTTP request was received and -preflightRequest: returned nil, * Assuming a valid HTTP request was received and -preflightRequest: returned nil,
@@ -169,7 +171,7 @@
* @warning If the request was invalid (e.g. the HTTP headers were malformed), * @warning If the request was invalid (e.g. the HTTP headers were malformed),
* the "request" argument will be nil. * the "request" argument will be nil.
*/ */
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; - (void)abortRequest:(nullable GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
/** /**
* Called when the connection is closed. * Called when the connection is closed.
@@ -177,3 +179,5 @@
- (void)close; - (void)close;
@end @end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@@ -34,20 +36,24 @@ extern "C" {
/** /**
* Converts a file extension to the corresponding MIME type. * Converts a file extension to the corresponding MIME type.
* If there is no match, "application/octet-stream" is returned. * If there is no match, "application/octet-stream" is returned.
*
* Overrides allow to customize the built-in mapping from extensions to MIME
* 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); NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides);
/** /**
* Add percent-escapes to a string so it can be used in a URL. * Add percent-escapes to a string so it can be used in a URL.
* The legal characters ":@/?&=+" are also escaped to ensure compatibility * The legal characters ":@/?&=+" are also escaped to ensure compatibility
* with URL encoded forms and URL queries. * with URL encoded forms and URL queries.
*/ */
NSString* GCDWebServerEscapeURLString(NSString* string); NSString* _Nullable GCDWebServerEscapeURLString(NSString* string);
/** /**
* Unescapes a URL percent-encoded string. * Unescapes a URL percent-encoded string.
*/ */
NSString* GCDWebServerUnescapeURLString(NSString* string); NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string);
/** /**
* Extracts the unescaped names and values from an * Extracts the unescaped names and values from an
@@ -63,7 +69,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
* interface if connected or nil otherwise. * interface if connected or nil otherwise.
*/ */
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6); NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
/** /**
* Converts a date into a string using RFC822 formatting. * Converts a date into a string using RFC822 formatting.
@@ -79,7 +85,7 @@ NSString* GCDWebServerFormatRFC822(NSDate* date);
* *
* @warning Timezones other than GMT are not supported by this function. * @warning Timezones other than GMT are not supported by this function.
*/ */
NSDate* GCDWebServerParseRFC822(NSString* string); NSDate* _Nullable GCDWebServerParseRFC822(NSString* string);
/** /**
* Converts a date into a string using IOS 8601 formatting. * Converts a date into a string using IOS 8601 formatting.
@@ -94,8 +100,10 @@ NSString* GCDWebServerFormatISO8601(NSDate* date);
* @warning Only "calendar" variant is supported at this time and timezones * @warning Only "calendar" variant is supported at this time and timezones
* other than GMT are not supported either. * other than GMT are not supported either.
*/ */
NSDate* GCDWebServerParseISO8601(NSString* string); NSDate* _Nullable GCDWebServerParseISO8601(NSString* string);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
NS_ASSUME_NONNULL_END

View File

@@ -83,21 +83,28 @@ NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
} }
NSString* GCDWebServerTruncateHeaderValue(NSString* value) { NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
NSRange range = [value rangeOfString:@";"]; if (value) {
return range.location != NSNotFound ? [value substringToIndex:range.location] : value; NSRange range = [value rangeOfString:@";"];
if (range.location != NSNotFound) {
return [value substringToIndex:range.location];
}
}
return value;
} }
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
NSString* parameter = nil; NSString* parameter = nil;
NSScanner* scanner = [[NSScanner alloc] initWithString:value]; if (value) {
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive NSScanner* scanner = [[NSScanner alloc] initWithString:value];
NSString* string = [NSString stringWithFormat:@"%@=", name]; [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
if ([scanner scanUpToString:string intoString:NULL]) { NSString* string = [NSString stringWithFormat:@"%@=", name];
[scanner scanString:string intoString:NULL]; if ([scanner scanUpToString:string intoString:NULL]) {
if ([scanner scanString:@"\"" intoString:NULL]) { [scanner scanString:string intoString:NULL];
[scanner scanUpToString:@"\"" intoString:&parameter]; if ([scanner scanString:@"\"" intoString:NULL]) {
} else { [scanner scanUpToString:@"\"" intoString:&parameter];
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter]; } else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
}
} }
} }
return parameter; return parameter;
@@ -159,17 +166,15 @@ NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length]; return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
} }
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) { NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* overrides) {
static NSDictionary* _overrides = nil; NSDictionary* builtInOverrides = @{@"css": @"text/css"};
if (_overrides == nil) {
_overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
@"text/css", @"css",
nil];
}
NSString* mimeType = nil; NSString* mimeType = nil;
extension = [extension lowercaseString]; extension = [extension lowercaseString];
if (extension.length) { if (extension.length) {
mimeType = [_overrides objectForKey:extension]; mimeType = [overrides objectForKey:extension];
if (mimeType == nil) {
mimeType = [builtInOverrides objectForKey:extension];
}
if (mimeType == nil) { if (mimeType == nil) {
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
if (uti) { if (uti) {
@@ -205,13 +210,13 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
break; break;
} }
[scanner setScanLocation:([scanner scanLocation] + 1)]; [scanner setScanLocation:([scanner scanLocation] + 1)];
NSString* value = nil; NSString* value = nil;
[scanner scanUpToString:@"&" intoString:&value]; [scanner scanUpToString:@"&" intoString:&value];
if (value == nil) { if (value == nil) {
value = @""; value = @"";
} }
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil; NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil;
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
@@ -222,7 +227,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value); GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
} }
if ([scanner isAtEnd]) { if ([scanner isAtEnd]) {
break; break;
} }
@@ -232,21 +237,22 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
} }
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) { NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
NSString* string = nil;
char hostBuffer[NI_MAXHOST]; char hostBuffer[NI_MAXHOST];
char serviceBuffer[NI_MAXSERV]; char serviceBuffer[NI_MAXSERV];
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) { if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) {
string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer]; #if DEBUG
} else {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
#else
return @"";
#endif
} }
return string; return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer];
} }
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) { NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
NSString* address = nil; NSString* address = nil;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#if !TARGET_IPHONE_SIMULATOR #if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV
const char* primaryInterface = "en0"; // WiFi interface on iOS const char* primaryInterface = "en0"; // WiFi interface on iOS
#endif #endif
#else #else
@@ -255,7 +261,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
if (store) { if (store) {
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
if (info) { if (info) {
primaryInterface = [[NSString stringWithString:[(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String]; NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"];
if (interface) {
primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool
}
CFRelease(info); CFRelease(info);
} }
CFRelease(store); CFRelease(store);
@@ -267,9 +276,9 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
struct ifaddrs* list; struct ifaddrs* list;
if (getifaddrs(&list) >= 0) { if (getifaddrs(&list) >= 0) {
for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) { for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV #if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV
// Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
// Assumption holds for Apple TV running tvOS // Assumption holds for Apple TV running tvOS
if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1")) if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))
#else #else
if (strcmp(ifap->ifa_name, primaryInterface)) if (strcmp(ifap->ifa_name, primaryInterface))
@@ -303,5 +312,5 @@ NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo; buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
} }
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0; buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
return [NSString stringWithUTF8String:buffer]; return (NSString*)[NSString stringWithUTF8String:buffer];
} }

View File

@@ -48,6 +48,8 @@
#import "GCDWebServerFileResponse.h" #import "GCDWebServerFileResponse.h"
#import "GCDWebServerStreamedResponse.h" #import "GCDWebServerStreamedResponse.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* Check if a custom logging facility should be used instead. * Check if a custom logging facility should be used instead.
*/ */
@@ -77,7 +79,6 @@
#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__) #define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__)
#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__) #define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__)
#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__) #define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__)
#define GWS_LOG_EXCEPTION(__EXCEPTION__) XLOG_EXCEPTION(__EXCEPTION__)
#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__) #define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__)
#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE() #define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE()
@@ -102,7 +103,6 @@ extern DDLogLevel GCDWebServerLogLevel;
#define GWS_LOG_INFO(...) DDLogInfo(__VA_ARGS__) #define GWS_LOG_INFO(...) DDLogInfo(__VA_ARGS__)
#define GWS_LOG_WARNING(...) DDLogWarn(__VA_ARGS__) #define GWS_LOG_WARNING(...) DDLogWarn(__VA_ARGS__)
#define GWS_LOG_ERROR(...) DDLogError(__VA_ARGS__) #define GWS_LOG_ERROR(...) DDLogError(__VA_ARGS__)
#define GWS_LOG_EXCEPTION(__EXCEPTION__) DDLogError(@"%@", __EXCEPTION__)
/** /**
* If all of the above fail, then use GCDWebServer built-in * If all of the above fail, then use GCDWebServer built-in
@@ -118,23 +118,36 @@ typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
kGCDWebServerLoggingLevel_Verbose, kGCDWebServerLoggingLevel_Verbose,
kGCDWebServerLoggingLevel_Info, kGCDWebServerLoggingLevel_Info,
kGCDWebServerLoggingLevel_Warning, kGCDWebServerLoggingLevel_Warning,
kGCDWebServerLoggingLevel_Error, kGCDWebServerLoggingLevel_Error
kGCDWebServerLoggingLevel_Exception
}; };
extern GCDWebServerLoggingLevel GCDWebServerLogLevel; extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3); extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
#if DEBUG #if DEBUG
#define GWS_LOG_DEBUG(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); } while (0) #define GWS_LOG_DEBUG(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); \
} while (0)
#else #else
#define GWS_LOG_DEBUG(...) #define GWS_LOG_DEBUG(...)
#endif #endif
#define GWS_LOG_VERBOSE(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); } while (0) #define GWS_LOG_VERBOSE(...) \
#define GWS_LOG_INFO(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); } while (0) do { \
#define GWS_LOG_WARNING(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); } while (0) if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); \
#define GWS_LOG_ERROR(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); } while (0) } while (0)
#define GWS_LOG_EXCEPTION(__EXCEPTION__) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Exception) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Exception, @"%@", __EXCEPTION__); } while (0) #define GWS_LOG_INFO(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); \
} while (0)
#define GWS_LOG_WARNING(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); \
} while (0)
#define GWS_LOG_ERROR(...) \
do { \
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); \
} while (0)
#endif #endif
@@ -147,10 +160,10 @@ extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* for
#if DEBUG #if DEBUG
#define GWS_DCHECK(__CONDITION__) \ #define GWS_DCHECK(__CONDITION__) \
do { \ do { \
if (!(__CONDITION__)) { \ if (!(__CONDITION__)) { \
abort(); \ abort(); \
} \ } \
} while (0) } while (0)
#define GWS_DNOT_REACHED() abort() #define GWS_DNOT_REACHED() abort()
@@ -168,7 +181,6 @@ extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* for
*/ */
#define kGCDWebServerDefaultMimeType @"application/octet-stream" #define kGCDWebServerDefaultMimeType @"application/octet-stream"
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain" #define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) { static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
@@ -176,30 +188,31 @@ static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
} }
static inline NSError* GCDWebServerMakePosixError(int code) { static inline NSError* GCDWebServerMakePosixError(int code) {
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithUTF8String:strerror(code)]}]; return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithUTF8String:strerror(code)]}];
} }
extern void GCDWebServerInitializeFunctions(); extern void GCDWebServerInitializeFunctions();
extern NSString* GCDWebServerNormalizeHeaderValue(NSString* value); extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
extern NSString* GCDWebServerTruncateHeaderValue(NSString* value); extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute); extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset); extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
extern BOOL GCDWebServerIsTextContentType(NSString* type); extern BOOL GCDWebServerIsTextContentType(NSString* type);
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType); extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2); extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2);
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService); extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
@interface GCDWebServerConnection () @interface GCDWebServerConnection ()
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; - (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
@end @end
@interface GCDWebServer () @interface GCDWebServer ()
@property(nonatomic, readonly) NSArray* handlers; @property(nonatomic, readonly) NSMutableArray* handlers;
@property(nonatomic, readonly) NSString* serverName; @property(nonatomic, readonly) NSString* serverName;
@property(nonatomic, readonly) NSString* authenticationRealm; @property(nonatomic, readonly) NSString* authenticationRealm;
@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts; @property(nonatomic, readonly) NSMutableDictionary* authenticationBasicAccounts;
@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts; @property(nonatomic, readonly) NSMutableDictionary* authenticationDigestAccounts;
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; @property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
- (void)willStartConnection:(GCDWebServerConnection*)connection; - (void)willStartConnection:(GCDWebServerConnection*)connection;
- (void)didEndConnection:(GCDWebServerConnection*)connection; - (void)didEndConnection:(GCDWebServerConnection*)connection;
@end @end
@@ -211,13 +224,13 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
@interface GCDWebServerRequest () @interface GCDWebServerRequest ()
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
@property(nonatomic, readwrite) NSData* localAddressData; @property(nonatomic) NSData* localAddressData;
@property(nonatomic, readwrite) NSData* remoteAddressData; @property(nonatomic) NSData* remoteAddressData;
- (void)prepareForWriting; - (void)prepareForWriting;
- (BOOL)performOpen:(NSError**)error; - (BOOL)performOpen:(NSError**)error;
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error; - (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
- (BOOL)performClose:(NSError**)error; - (BOOL)performClose:(NSError**)error;
- (void)setAttribute:(id)attribute forKey:(NSString*)key; - (void)setAttribute:(nullable id)attribute forKey:(NSString*)key;
@end @end
@interface GCDWebServerResponse () @interface GCDWebServerResponse ()
@@ -228,3 +241,5 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block; - (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
- (void)performClose; - (void)performClose;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -27,6 +27,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** /**
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest * Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
* with the contents of any regular expression captures done on the request path. * with the contents of any regular expression captures done on the request path.
@@ -112,7 +114,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
* *
* @warning This property will be nil if there is no query in the URL. * @warning This property will be nil if there is no query in the URL.
*/ */
@property(nonatomic, readonly) NSDictionary* query; @property(nonatomic, readonly, nullable) NSDictionary* query;
/** /**
* Returns the content type for the body of the request parsed from the * Returns the content type for the body of the request parsed from the
@@ -122,7 +124,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
* "application/octet-stream" if a body is present but there was no * "application/octet-stream" if a body is present but there was no
* "Content-Type" header. * "Content-Type" header.
*/ */
@property(nonatomic, readonly) NSString* contentType; @property(nonatomic, readonly, nullable) NSString* contentType;
/** /**
* Returns the content length for the body of the request parsed from the * Returns the content length for the body of the request parsed from the
@@ -137,12 +139,12 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
/** /**
* Returns the parsed "If-Modified-Since" header or nil if absent or malformed. * Returns the parsed "If-Modified-Since" header or nil if absent or malformed.
*/ */
@property(nonatomic, readonly) NSDate* ifModifiedSince; @property(nonatomic, readonly, nullable) NSDate* ifModifiedSince;
/** /**
* Returns the parsed "If-None-Match" header or nil if absent or malformed. * Returns the parsed "If-None-Match" header or nil if absent or malformed.
*/ */
@property(nonatomic, readonly) NSString* ifNoneMatch; @property(nonatomic, readonly, nullable) NSString* ifNoneMatch;
/** /**
* Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed. * Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed.
@@ -184,7 +186,7 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
/** /**
* This method is the designated initializer for the class. * This method is the designated initializer for the class.
*/ */
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query; - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(nullable NSDictionary*)query;
/** /**
* Convenience method that checks if the contentType property is defined. * Convenience method that checks if the contentType property is defined.
@@ -201,6 +203,8 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
* *
* @return The attribute value for the key. * @return The attribute value for the key.
*/ */
- (id)attributeForKey:(NSString*)key; - (nullable id)attributeForKey:(NSString*)key;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -39,22 +39,17 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
#define kGZipInitialBufferSize (256 * 1024) #define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter> @interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer;
@end @end
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder @interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
@end @end
@interface GCDWebServerBodyDecoder () { @implementation GCDWebServerBodyDecoder {
@private
GCDWebServerRequest* __unsafe_unretained _request; GCDWebServerRequest* __unsafe_unretained _request;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer; id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
} }
@end
@implementation GCDWebServerBodyDecoder - (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id<GCDWebServerBodyWriter> _Nonnull)writer {
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer {
if ((self = [super init])) { if ((self = [super init])) {
_request = request; _request = request;
_writer = writer; _writer = writer;
@@ -76,14 +71,10 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
@end @end
@interface GCDWebServerGZipDecoder () { @implementation GCDWebServerGZipDecoder {
@private
z_stream _stream; z_stream _stream;
BOOL _finished; BOOL _finished;
} }
@end
@implementation GCDWebServerGZipDecoder
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
int result = inflateInit2(&_stream, 15 + 16); int result = inflateInit2(&_stream, 15 + 16);
@@ -94,7 +85,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
return NO; return NO;
} }
if (![super open:error]) { if (![super open:error]) {
deflateEnd(&_stream); inflateEnd(&_stream);
return NO; return NO;
} }
return YES; return YES;
@@ -143,77 +134,55 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
@end @end
@interface GCDWebServerRequest () { @implementation GCDWebServerRequest {
@private
NSString* _method;
NSURL* _url;
NSDictionary* _headers;
NSString* _path;
NSDictionary* _query;
NSString* _type;
BOOL _chunked;
NSUInteger _length;
NSDate* _modifiedSince;
NSString* _noneMatch;
NSRange _range;
BOOL _gzipAccepted;
NSData* _localAddress;
NSData* _remoteAddress;
BOOL _opened; BOOL _opened;
NSMutableArray* _decoders; NSMutableArray* _decoders;
NSMutableDictionary* _attributes;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer; id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
NSMutableDictionary* _attributes;
} }
@end
@implementation GCDWebServerRequest : NSObject
@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch,
byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked, localAddressData=_localAddress, remoteAddressData=_remoteAddress;
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super init])) { if ((self = [super init])) {
_method = [method copy]; _method = [method copy];
_url = url; _URL = url;
_headers = headers; _headers = headers;
_path = [path copy]; _path = [path copy];
_query = query; _query = query;
_type = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]); _contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
_chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"]; _usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"]; NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
if (lengthHeader) { if (lengthHeader) {
NSInteger length = [lengthHeader integerValue]; NSInteger length = [lengthHeader integerValue];
if (_chunked || (length < 0)) { if (_usesChunkedTransferEncoding || (length < 0)) {
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _url); GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL);
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
return nil; return nil;
} }
_length = length; _contentLength = length;
if (_type == nil) { if (_contentType == nil) {
_type = kGCDWebServerDefaultMimeType; _contentType = kGCDWebServerDefaultMimeType;
} }
} else if (_chunked) { } else if (_usesChunkedTransferEncoding) {
if (_type == nil) { if (_contentType == nil) {
_type = kGCDWebServerDefaultMimeType; _contentType = kGCDWebServerDefaultMimeType;
} }
_length = NSUIntegerMax; _contentLength = NSUIntegerMax;
} else { } else {
if (_type) { if (_contentType) {
GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _url); GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL);
_type = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense _contentType = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense
} }
_length = NSUIntegerMax; _contentLength = NSUIntegerMax;
} }
NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"]; NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
if (modifiedHeader) { if (modifiedHeader) {
_modifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy]; _ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
} }
_noneMatch = [_headers objectForKey:@"If-None-Match"]; _ifNoneMatch = [_headers objectForKey:@"If-None-Match"];
_range = NSMakeRange(NSUIntegerMax, 0); _byteRange = NSMakeRange(NSUIntegerMax, 0);
NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]); NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]);
if (rangeHeader) { if (rangeHeader) {
if ([rangeHeader hasPrefix:@"bytes="]) { if ([rangeHeader hasPrefix:@"bytes="]) {
@@ -226,27 +195,27 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
NSString* endString = [components objectAtIndex:1]; NSString* endString = [components objectAtIndex:1];
NSInteger endValue = [endString integerValue]; NSInteger endValue = [endString integerValue];
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999" if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
_range.location = startValue; _byteRange.location = startValue;
_range.length = endValue - startValue + 1; _byteRange.length = endValue - startValue + 1;
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-" } else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
_range.location = startValue; _byteRange.location = startValue;
_range.length = NSUIntegerMax; _byteRange.length = NSUIntegerMax;
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500" } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
_range.location = NSUIntegerMax; _byteRange.location = NSUIntegerMax;
_range.length = endValue; _byteRange.length = endValue;
} }
} }
} }
} }
if ((_range.location == NSUIntegerMax) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid if ((_byteRange.location == NSUIntegerMax) && (_byteRange.length == 0)) { // Ignore "Range" header if syntactically invalid
GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url); GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
} }
} }
if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) { if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
_gzipAccepted = YES; _acceptsGzipContentEncoding = YES;
} }
_decoders = [[NSMutableArray alloc] init]; _decoders = [[NSMutableArray alloc] init];
_attributes = [[NSMutableDictionary alloc] init]; _attributes = [[NSMutableDictionary alloc] init];
} }
@@ -254,11 +223,11 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
} }
- (BOOL)hasBody { - (BOOL)hasBody {
return _type ? YES : NO; return _contentType ? YES : NO;
} }
- (BOOL)hasByteRange { - (BOOL)hasByteRange {
return GCDWebServerIsValidByteRange(_range); return GCDWebServerIsValidByteRange(_byteRange);
} }
- (id)attributeForKey:(NSString*)key { - (id)attributeForKey:(NSString*)key {
@@ -287,7 +256,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
} }
- (BOOL)performOpen:(NSError**)error { - (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_type); GWS_DCHECK(_contentType);
GWS_DCHECK(_writer); GWS_DCHECK(_writer);
if (_opened) { if (_opened) {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
@@ -312,11 +281,11 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
} }
- (NSString*)localAddressString { - (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES); return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
} }
- (NSString*)remoteAddressString { - (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES); return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
} }
- (NSString*)description { - (NSString*)description {

View File

@@ -27,11 +27,13 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the * The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
* GCDWebServerBodyReader object when reading data from it asynchronously. * GCDWebServerBodyReader object when reading data from it asynchronously.
*/ */
typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* error); typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* _Nullable error);
/** /**
* This protocol is used by the GCDWebServerConnection to communicate with * This protocol is used by the GCDWebServerConnection to communicate with
@@ -62,7 +64,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
* or an empty NSData there is no more body data, or nil on error and set * or an empty NSData there is no more body data, or nil on error and set
* the "error" argument which is guaranteed to be non-NULL. * the "error" argument which is guaranteed to be non-NULL.
*/ */
- (NSData*)readData:(NSError**)error; - (nullable NSData*)readData:(NSError**)error;
/** /**
* This method is called after all body data has been sent. * This method is called after all body data has been sent.
@@ -102,7 +104,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
* *
* @warning This property must be set if a body is present. * @warning This property must be set if a body is present.
*/ */
@property(nonatomic, copy) NSString* contentType; @property(nonatomic, copy, nullable) NSString* contentType;
/** /**
* Sets the content length for the body of the response. If a body is present * Sets the content length for the body of the response. If a body is present
@@ -136,14 +138,14 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
* *
* The default value is nil. * The default value is nil.
*/ */
@property(nonatomic, retain) NSDate* lastModifiedDate; @property(nonatomic, nullable) NSDate* lastModifiedDate;
/** /**
* Sets the ETag for the response using the "ETag" header. * Sets the ETag for the response using the "ETag" header.
* *
* The default value is nil. * The default value is nil.
*/ */
@property(nonatomic, copy) NSString* eTag; @property(nonatomic, copy, nullable) NSString* eTag;
/** /**
* Enables gzip encoding for the response body. * Enables gzip encoding for the response body.
@@ -174,7 +176,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
* @warning Do not attempt to override the primary headers used * @warning Do not attempt to override the primary headers used
* by GCDWebServerResponse like "Content-Type", "ETag", etc... * by GCDWebServerResponse like "Content-Type", "ETag", etc...
*/ */
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header; - (void)setValue:(nullable NSString*)value forAdditionalHeader:(NSString*)header;
/** /**
* Convenience method that checks if the contentType property is defined. * Convenience method that checks if the contentType property is defined.
@@ -206,3 +208,5 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent; - (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -37,22 +37,17 @@
#define kGZipInitialBufferSize (256 * 1024) #define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader> @interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
@end @end
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder @interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
@end @end
@interface GCDWebServerBodyEncoder () { @implementation GCDWebServerBodyEncoder {
@private
GCDWebServerResponse* __unsafe_unretained _response; GCDWebServerResponse* __unsafe_unretained _response;
id<GCDWebServerBodyReader> __unsafe_unretained _reader; id<GCDWebServerBodyReader> __unsafe_unretained _reader;
} }
@end
@implementation GCDWebServerBodyEncoder - (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
if ((self = [super init])) { if ((self = [super init])) {
_response = response; _response = response;
_reader = reader; _reader = reader;
@@ -74,16 +69,12 @@
@end @end
@interface GCDWebServerGZipEncoder () { @implementation GCDWebServerGZipEncoder {
@private
z_stream _stream; z_stream _stream;
BOOL _finished; BOOL _finished;
} }
@end
@implementation GCDWebServerGZipEncoder - (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
if ((self = [super initWithResponse:response reader:reader])) { if ((self = [super initWithResponse:response reader:reader])) {
response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"]; [response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
@@ -157,28 +148,11 @@
@end @end
@interface GCDWebServerResponse () { @implementation GCDWebServerResponse {
@private
NSString* _type;
NSUInteger _length;
NSInteger _status;
NSUInteger _maxAge;
NSDate* _lastModified;
NSString* _eTag;
NSMutableDictionary* _headers;
BOOL _chunked;
BOOL _gzipped;
BOOL _opened; BOOL _opened;
NSMutableArray* _encoders; NSMutableArray* _encoders;
id<GCDWebServerBodyReader> __unsafe_unretained _reader; id<GCDWebServerBodyReader> __unsafe_unretained _reader;
} }
@end
@implementation GCDWebServerResponse
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, lastModifiedDate=_lastModified, eTag=_eTag,
gzipContentEncodingEnabled=_gzipped, additionalHeaders=_headers;
+ (instancetype)response { + (instancetype)response {
return [[[self class] alloc] init]; return [[[self class] alloc] init];
@@ -186,26 +160,26 @@
- (instancetype)init { - (instancetype)init {
if ((self = [super init])) { if ((self = [super init])) {
_type = nil; _contentType = nil;
_length = NSUIntegerMax; _contentLength = NSUIntegerMax;
_status = kGCDWebServerHTTPStatusCode_OK; _statusCode = kGCDWebServerHTTPStatusCode_OK;
_maxAge = 0; _cacheControlMaxAge = 0;
_headers = [[NSMutableDictionary alloc] init]; _additionalHeaders = [[NSMutableDictionary alloc] init];
_encoders = [[NSMutableArray alloc] init]; _encoders = [[NSMutableArray alloc] init];
} }
return self; return self;
} }
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header { - (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
[_headers setValue:value forKey:header]; [_additionalHeaders setValue:value forKey:header];
} }
- (BOOL)hasBody { - (BOOL)hasBody {
return _type ? YES : NO; return _contentType ? YES : NO;
} }
- (BOOL)usesChunkedTransferEncoding { - (BOOL)usesChunkedTransferEncoding {
return (_type != nil) && (_length == NSUIntegerMax); return (_contentType != nil) && (_contentLength == NSUIntegerMax);
} }
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
@@ -222,7 +196,7 @@
- (void)prepareForReading { - (void)prepareForReading {
_reader = self; _reader = self;
if (_gzipped) { if (_gzipContentEncodingEnabled) {
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader]; GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
[_encoders addObject:encoder]; [_encoders addObject:encoder];
_reader = encoder; _reader = encoder;
@@ -230,7 +204,7 @@
} }
- (BOOL)performOpen:(NSError**)error { - (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_type); GWS_DCHECK(_contentType);
GWS_DCHECK(_reader); GWS_DCHECK(_reader);
if (_opened) { if (_opened) {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
@@ -241,6 +215,7 @@
} }
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block { - (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
GWS_DCHECK(_opened);
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) { if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
[_reader asyncReadDataWithCompletion:[block copy]]; [_reader asyncReadDataWithCompletion:[block copy]];
} else { } else {
@@ -256,24 +231,24 @@
} }
- (NSString*)description { - (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_status]; NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode];
if (_type) { if (_contentType) {
[description appendFormat:@"\nContent Type = %@", _type]; [description appendFormat:@"\nContent Type = %@", _contentType];
} }
if (_length != NSUIntegerMax) { if (_contentLength != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_length]; [description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength];
} }
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge]; [description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge];
if (_lastModified) { if (_lastModifiedDate) {
[description appendFormat:@"\nLast Modified Date = %@", _lastModified]; [description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate];
} }
if (_eTag) { if (_eTag) {
[description appendFormat:@"\nETag = %@", _eTag]; [description appendFormat:@"\nETag = %@", _eTag];
} }
if (_headers.count) { if (_additionalHeaders.count) {
[description appendString:@"\n"]; [description appendString:@"\n"];
for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) { for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]]; [description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]];
} }
} }
return description; return description;

View File

@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h" #import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body * The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request in memory. * of the HTTP request in memory.
@@ -49,12 +51,14 @@
* The text encoding used to interpret the data is extracted from the * The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8. * "Content-Type" header or defaults to UTF-8.
*/ */
@property(nonatomic, readonly) NSString* text; @property(nonatomic, readonly, nullable) NSString* text;
/** /**
* Returns the data for the request body interpreted as a JSON object. If the * Returns the data for the request body interpreted as a JSON object. If the
* content type of the body is not JSON, or if an error occurs, nil is returned. * content type of the body is not JSON, or if an error occurs, nil is returned.
*/ */
@property(nonatomic, readonly) id jsonObject; @property(nonatomic, readonly, nullable) id jsonObject;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -31,18 +31,14 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerDataRequest () { @interface GCDWebServerDataRequest ()
@private @property(nonatomic) NSMutableData* data;
NSMutableData* _data; @end
@implementation GCDWebServerDataRequest {
NSString* _text; NSString* _text;
id _jsonObject; id _jsonObject;
} }
@end
@implementation GCDWebServerDataRequest
@synthesize data=_data;
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
if (self.contentLength != NSUIntegerMax) { if (self.contentLength != NSUIntegerMax) {
@@ -52,7 +48,7 @@
} }
if (_data == nil) { if (_data == nil) {
if (error) { 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; return NO;
} }
@@ -72,7 +68,7 @@
NSMutableString* description = [NSMutableString stringWithString:[super description]]; NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_data) { if (_data) {
[description appendString:@"\n\n"]; [description appendString:@"\n\n"];
[description appendString:GCDWebServerDescribeData(_data, self.contentType)]; [description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)];
} }
return description; return description;
} }

View File

@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h" #import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body * The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request to a file on disk. * of the HTTP request to a file on disk.
@@ -43,3 +45,5 @@
@property(nonatomic, readonly) NSString* temporaryPath; @property(nonatomic, readonly) NSString* temporaryPath;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -31,16 +31,9 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerFileRequest () { @implementation GCDWebServerFileRequest {
@private
NSString* _temporaryPath;
int _file; int _file;
} }
@end
@implementation GCDWebServerFileRequest
@synthesize temporaryPath=_temporaryPath;
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {
@@ -85,14 +78,14 @@
NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"]; NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"];
if (creationDateHeader) { if (creationDateHeader) {
NSDate* date = GCDWebServerParseISO8601(creationDateHeader); NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:_temporaryPath error:error]) { if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) {
return NO; return NO;
} }
} }
NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"]; NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"];
if (modifiedDateHeader) { if (modifiedDateHeader) {
NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader); NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader);
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate: date} ofItemAtPath:_temporaryPath error:error]) { if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) {
return NO; return NO;
} }
} }

View File

@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h" #import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerMultiPart class is an abstract class that wraps the content * The GCDWebServerMultiPart class is an abstract class that wraps the content
* of a part. * of a part.
@@ -69,7 +71,7 @@
* The text encoding used to interpret the data is extracted from the * The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8. * "Content-Type" header or defaults to UTF-8.
*/ */
@property(nonatomic, readonly) NSString* string; @property(nonatomic, readonly, nullable) NSString* string;
@end @end
@@ -122,11 +124,13 @@
/** /**
* Returns the first argument for a given control name or nil if not found. * Returns the first argument for a given control name or nil if not found.
*/ */
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name; - (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
/** /**
* Returns the first file for a given control name or nil if not found. * Returns the first file for a given control name or nil if not found.
*/ */
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name; - (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -42,53 +42,31 @@ typedef enum {
} ParserState; } ParserState;
@interface GCDWebServerMIMEStreamParser : NSObject @interface GCDWebServerMIMEStreamParser : NSObject
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files;
- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length;
- (BOOL)isAtEnd;
@end @end
static NSData* _newlineData = nil; static NSData* _newlineData = nil;
static NSData* _newlinesData = nil; static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil; static NSData* _dashNewlineData = nil;
@interface GCDWebServerMultiPart () {
@private
NSString* _controlName;
NSString* _contentType;
NSString* _mimeType;
}
@end
@implementation GCDWebServerMultiPart @implementation GCDWebServerMultiPart
@synthesize controlName=_controlName, contentType=_contentType, mimeType=_mimeType; - (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type {
if ((self = [super init])) { if ((self = [super init])) {
_controlName = [name copy]; _controlName = [name copy];
_contentType = [type copy]; _contentType = [type copy];
_mimeType = GCDWebServerTruncateHeaderValue(_contentType); _mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
} }
return self; return self;
} }
@end @end
@interface GCDWebServerMultiPartArgument () {
@private
NSData* _data;
NSString* _string;
}
@end
@implementation GCDWebServerMultiPartArgument @implementation GCDWebServerMultiPartArgument
@synthesize data=_data, string=_string; - (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
if ((self = [super initWithControlName:name contentType:type])) { if ((self = [super initWithControlName:name contentType:type])) {
_data = data; _data = data;
if ([self.contentType hasPrefix:@"text/"]) { if ([self.contentType hasPrefix:@"text/"]) {
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
_string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)]; _string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)];
@@ -103,18 +81,9 @@ static NSData* _dashNewlineData = nil;
@end @end
@interface GCDWebServerMultiPartFile () {
@private
NSString* _fileName;
NSString* _temporaryPath;
}
@end
@implementation GCDWebServerMultiPartFile @implementation GCDWebServerMultiPartFile
@synthesize fileName=_fileName, temporaryPath=_temporaryPath; - (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
if ((self = [super initWithControlName:name contentType:type])) { if ((self = [super initWithControlName:name contentType:type])) {
_fileName = [fileName copy]; _fileName = [fileName copy];
_temporaryPath = [temporaryPath copy]; _temporaryPath = [temporaryPath copy];
@@ -132,15 +101,14 @@ static NSData* _dashNewlineData = nil;
@end @end
@interface GCDWebServerMIMEStreamParser () { @implementation GCDWebServerMIMEStreamParser {
@private
NSData* _boundary; NSData* _boundary;
NSString* _defaultcontrolName; NSString* _defaultcontrolName;
ParserState _state; ParserState _state;
NSMutableData* _data; NSMutableData* _data;
NSMutableArray* _arguments; NSMutableArray* _arguments;
NSMutableArray* _files; NSMutableArray* _files;
NSString* _controlName; NSString* _controlName;
NSString* _fileName; NSString* _fileName;
NSString* _contentType; NSString* _contentType;
@@ -148,9 +116,6 @@ static NSData* _dashNewlineData = nil;
int _tmpFile; int _tmpFile;
GCDWebServerMIMEStreamParser* _subParser; GCDWebServerMIMEStreamParser* _subParser;
} }
@end
@implementation GCDWebServerMIMEStreamParser
+ (void)initialize { + (void)initialize {
if (_newlineData == nil) { if (_newlineData == nil) {
@@ -167,7 +132,7 @@ static NSData* _dashNewlineData = nil;
} }
} }
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files { - (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray* _Nonnull)arguments files:(NSMutableArray* _Nonnull)files {
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil; NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
if (data == nil) { if (data == nil) {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
@@ -194,11 +159,10 @@ static NSData* _dashNewlineData = nil;
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
- (BOOL)_parseData { - (BOOL)_parseData {
BOOL success = YES; BOOL success = YES;
if (_state == kParserState_Headers) { if (_state == kParserState_Headers) {
NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)]; NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) { if (range.location != NSNotFound) {
_controlName = nil; _controlName = nil;
_fileName = nil; _fileName = nil;
_contentType = nil; _contentType = nil;
@@ -256,12 +220,12 @@ static NSData* _dashNewlineData = nil;
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
success = NO; success = NO;
} }
[_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; [_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
_state = kParserState_Content; _state = kParserState_Content;
} }
} }
if ((_state == kParserState_Start) || (_state == kParserState_Content)) { if ((_state == kParserState_Start) || (_state == kParserState_Content)) {
NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)]; NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)];
if (range.location != NSNotFound) { if (range.location != NSNotFound) {
@@ -269,7 +233,6 @@ static NSData* _dashNewlineData = nil;
NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) { if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
if (_state == kParserState_Content) { if (_state == kParserState_Content) {
const void* dataBytes = _data.bytes; const void* dataBytes = _data.bytes;
NSUInteger dataLength = range.location - 2; NSUInteger dataLength = range.location - 2;
@@ -301,7 +264,7 @@ static NSData* _dashNewlineData = nil;
[_arguments addObject:argument]; [_arguments addObject:argument];
} }
} }
if (subRange1.location != NSNotFound) { if (subRange1.location != NSNotFound) {
[_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; [_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
_state = kParserState_Headers; _state = kParserState_Headers;
@@ -333,7 +296,7 @@ static NSData* _dashNewlineData = nil;
} }
} }
} }
return success; return success;
} }
@@ -348,17 +311,14 @@ static NSData* _dashNewlineData = nil;
@end @end
@interface GCDWebServerMultiPartFormRequest () { @interface GCDWebServerMultiPartFormRequest ()
@private @property(nonatomic) NSMutableArray* arguments;
GCDWebServerMIMEStreamParser* _parser; @property(nonatomic) NSMutableArray* files;
NSMutableArray* _arguments;
NSMutableArray* _files;
}
@end @end
@implementation GCDWebServerMultiPartFormRequest @implementation GCDWebServerMultiPartFormRequest {
GCDWebServerMIMEStreamParser* _parser;
@synthesize arguments=_arguments, files=_files; }
+ (NSString*)mimeType { + (NSString*)mimeType {
return @"multipart/form-data"; return @"multipart/form-data";
@@ -377,7 +337,7 @@ static NSData* _dashNewlineData = nil;
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files]; _parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
if (_parser == nil) { if (_parser == nil) {
if (error) { 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; return NO;
} }
@@ -387,7 +347,7 @@ static NSData* _dashNewlineData = nil;
- (BOOL)writeData:(NSData*)data error:(NSError**)error { - (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (![_parser appendBytes:data.bytes length:data.length]) { if (![_parser appendBytes:data.bytes length:data.length]) {
if (error) { 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; return NO;
} }
@@ -399,7 +359,7 @@ static NSData* _dashNewlineData = nil;
_parser = nil; _parser = nil;
if (!atEnd) { if (!atEnd) {
if (error) { 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; return NO;
} }

View File

@@ -27,6 +27,8 @@
#import "GCDWebServerDataRequest.h" #import "GCDWebServerDataRequest.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest * The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest
* parses the body of the HTTP request as a URL encoded form using * parses the body of the HTTP request as a URL encoded form using
@@ -49,3 +51,5 @@
+ (NSString*)mimeType; + (NSString*)mimeType;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -31,16 +31,8 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerURLEncodedFormRequest () {
@private
NSDictionary* _arguments;
}
@end
@implementation GCDWebServerURLEncodedFormRequest @implementation GCDWebServerURLEncodedFormRequest
@synthesize arguments=_arguments;
+ (NSString*)mimeType { + (NSString*)mimeType {
return @"application/x-www-form-urlencoded"; return @"application/x-www-form-urlencoded";
} }
@@ -49,12 +41,10 @@
if (![super close:error]) { if (![super close:error]) {
return NO; return NO;
} }
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
_arguments = GCDWebServerParseURLEncodedForm(string); _arguments = GCDWebServerParseURLEncodedForm(string);
GWS_DCHECK(_arguments);
return YES; return YES;
} }

View File

@@ -27,11 +27,14 @@
#import "GCDWebServerResponse.h" #import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body * The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from memory. * of the HTTP response from memory.
*/ */
@interface GCDWebServerDataResponse : GCDWebServerResponse @interface GCDWebServerDataResponse : GCDWebServerResponse
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
/** /**
* Creates a response with data in memory and a given content type. * Creates a response with data in memory and a given content type.
@@ -50,40 +53,40 @@
/** /**
* Creates a data response from text encoded using UTF-8. * Creates a data response from text encoded using UTF-8.
*/ */
+ (instancetype)responseWithText:(NSString*)text; + (nullable instancetype)responseWithText:(NSString*)text;
/** /**
* Creates a data response from HTML encoded using UTF-8. * Creates a data response from HTML encoded using UTF-8.
*/ */
+ (instancetype)responseWithHTML:(NSString*)html; + (nullable instancetype)responseWithHTML:(NSString*)html;
/** /**
* Creates a data response from an HTML template encoded using UTF-8. * Creates a data response from an HTML template encoded using UTF-8.
* See -initWithHTMLTemplate:variables: for details. * See -initWithHTMLTemplate:variables: for details.
*/ */
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; + (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
/** /**
* Creates a data response from a serialized JSON object and the default * Creates a data response from a serialized JSON object and the default
* "application/json" content type. * "application/json" content type.
*/ */
+ (instancetype)responseWithJSONObject:(id)object; + (nullable instancetype)responseWithJSONObject:(id)object;
/** /**
* Creates a data response from a serialized JSON object and a custom * Creates a data response from a serialized JSON object and a custom
* content type. * content type.
*/ */
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; + (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type;
/** /**
* Initializes a data response from text encoded using UTF-8. * Initializes a data response from text encoded using UTF-8.
*/ */
- (instancetype)initWithText:(NSString*)text; - (nullable instancetype)initWithText:(NSString*)text;
/** /**
* Initializes a data response from HTML encoded using UTF-8. * Initializes a data response from HTML encoded using UTF-8.
*/ */
- (instancetype)initWithHTML:(NSString*)html; - (nullable instancetype)initWithHTML:(NSString*)html;
/** /**
* Initializes a data response from an HTML template encoded using UTF-8. * Initializes a data response from an HTML template encoded using UTF-8.
@@ -91,18 +94,20 @@
* All occurences of "%variable%" within the HTML template are replaced with * All occurences of "%variable%" within the HTML template are replaced with
* their corresponding values. * their corresponding values.
*/ */
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; - (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
/** /**
* Initializes a data response from a serialized JSON object and the default * Initializes a data response from a serialized JSON object and the default
* "application/json" content type. * "application/json" content type.
*/ */
- (instancetype)initWithJSONObject:(id)object; - (nullable instancetype)initWithJSONObject:(id)object;
/** /**
* Initializes a data response from a serialized JSON object and a custom * Initializes a data response from a serialized JSON object and a custom
* content type. * content type.
*/ */
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; - (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -31,28 +31,21 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerDataResponse () { @implementation GCDWebServerDataResponse {
@private
NSData* _data; NSData* _data;
BOOL _done; BOOL _done;
} }
@end
@implementation GCDWebServerDataResponse @dynamic contentType;
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
return [[[self class] alloc] initWithData:data contentType:type]; return [[[self class] alloc] initWithData:data contentType:type];
} }
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
if ((self = [super init])) { if ((self = [super init])) {
_data = data; _data = data;
self.contentType = type; self.contentType = type;
self.contentLength = data.length; self.contentLength = data.length;
} }
@@ -124,8 +117,7 @@
[variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
}]; }];
id response = [self initWithHTML:html]; return [self initWithHTML:html];
return response;
} }
- (instancetype)initWithJSONObject:(id)object { - (instancetype)initWithJSONObject:(id)object {
@@ -135,6 +127,7 @@
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type { - (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type {
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL]; NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
if (data == nil) { if (data == nil) {
GWS_DNOT_REACHED();
return nil; return nil;
} }
return [self initWithData:data contentType:type]; return [self initWithData:data contentType:type];

View File

@@ -28,6 +28,8 @@
#import "GCDWebServerDataResponse.h" #import "GCDWebServerDataResponse.h"
#import "GCDWebServerHTTPStatusCodes.h" #import "GCDWebServerHTTPStatusCodes.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates * The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates
* an HTML body from an HTTP status code and an error message. * an HTML body from an HTTP status code and an error message.
@@ -37,45 +39,47 @@
/** /**
* Creates a client error response with the corresponding HTTP status code. * Creates a client error response with the corresponding HTTP status code.
*/ */
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/** /**
* Creates a server error response with the corresponding HTTP status code. * Creates a server error response with the corresponding HTTP status code.
*/ */
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/** /**
* Creates a client error response with the corresponding HTTP status code * Creates a client error response with the corresponding HTTP status code
* and an underlying NSError. * and an underlying NSError.
*/ */
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
/** /**
* Creates a server error response with the corresponding HTTP status code * Creates a server error response with the corresponding HTTP status code
* and an underlying NSError. * and an underlying NSError.
*/ */
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
/** /**
* Initializes a client error response with the corresponding HTTP status code. * Initializes a client error response with the corresponding HTTP status code.
*/ */
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/** /**
* Initializes a server error response with the corresponding HTTP status code. * Initializes a server error response with the corresponding HTTP status code.
*/ */
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
/** /**
* Initializes a client error response with the corresponding HTTP status code * Initializes a client error response with the corresponding HTTP status code
* and an underlying NSError. * and an underlying NSError.
*/ */
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
/** /**
* Initializes a server error response with the corresponding HTTP status code * Initializes a server error response with the corresponding HTTP status code
* and an underlying NSError. * and an underlying NSError.
*/ */
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -31,10 +31,6 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerErrorResponse ()
- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments;
@end
@implementation GCDWebServerErrorResponse @implementation GCDWebServerErrorResponse
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {

View File

@@ -27,6 +27,8 @@
#import "GCDWebServerResponse.h" #import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body * The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from a file on disk. * of the HTTP response from a file on disk.
@@ -36,17 +38,20 @@
* metadata. * metadata.
*/ */
@interface GCDWebServerFileResponse : GCDWebServerResponse @interface GCDWebServerFileResponse : GCDWebServerResponse
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
@property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null
@property(nonatomic, copy) NSString* eTag; // Redeclare as non-null
/** /**
* Creates a response with the contents of a file. * Creates a response with the contents of a file.
*/ */
+ (instancetype)responseWithFile:(NSString*)path; + (nullable instancetype)responseWithFile:(NSString*)path;
/** /**
* Creates a response like +responseWithFile: and sets the "Content-Disposition" * Creates a response like +responseWithFile: and sets the "Content-Disposition"
* HTTP header for a download if the "attachment" argument is YES. * HTTP header for a download if the "attachment" argument is YES.
*/ */
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; + (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
/** /**
* Creates a response like +responseWithFile: but restricts the file contents * Creates a response like +responseWithFile: but restricts the file contents
@@ -54,26 +59,26 @@
* *
* See -initWithFile:byteRange: for details. * See -initWithFile:byteRange: for details.
*/ */
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range;
/** /**
* Creates a response like +responseWithFile:byteRange: and sets the * Creates a response like +responseWithFile:byteRange: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment" * "Content-Disposition" HTTP header for a download if the "attachment"
* argument is YES. * argument is YES.
*/ */
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
/** /**
* Initializes a response with the contents of a file. * Initializes a response with the contents of a file.
*/ */
- (instancetype)initWithFile:(NSString*)path; - (nullable instancetype)initWithFile:(NSString*)path;
/** /**
* Initializes a response like +responseWithFile: and sets the * Initializes a response like +responseWithFile: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment" * "Content-Disposition" HTTP header for a download if the "attachment"
* argument is YES. * argument is YES.
*/ */
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; - (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
/** /**
* Initializes a response like -initWithFile: but restricts the file contents * Initializes a response like -initWithFile: but restricts the file contents
@@ -86,11 +91,18 @@
* This argument would typically be set to the value of the byteRange property * This argument would typically be set to the value of the byteRange property
* of the current GCDWebServerRequest. * of the current GCDWebServerRequest.
*/ */
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range;
/** /**
* This method is the designated initializer for the class. * This method is the designated initializer for the class.
*
* If MIME type overrides are specified, they allow to customize the built-in
* mapping from extensions to MIME types. Keys of the dictionary must be lowercased
* file extensions without the period, and the values must be the corresponding
* MIME types.
*/ */
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -35,16 +35,14 @@
#define kFileReadBufferSize (32 * 1024) #define kFileReadBufferSize (32 * 1024)
@interface GCDWebServerFileResponse () { @implementation GCDWebServerFileResponse {
@private
NSString* _path; NSString* _path;
NSUInteger _offset; NSUInteger _offset;
NSUInteger _size; NSUInteger _size;
int _file; int _file;
} }
@end
@implementation GCDWebServerFileResponse @dynamic contentType, lastModifiedDate, eTag;
+ (instancetype)responseWithFile:(NSString*)path { + (instancetype)responseWithFile:(NSString*)path {
return [[[self class] alloc] initWithFile:path]; return [[[self class] alloc] initWithFile:path];
@@ -59,26 +57,26 @@
} }
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { + (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
return [[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]; return [[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment mimeTypeOverrides:nil];
} }
- (instancetype)initWithFile:(NSString*)path { - (instancetype)initWithFile:(NSString*)path {
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO]; return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO mimeTypeOverrides:nil];
} }
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { - (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment]; return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment mimeTypeOverrides:nil];
} }
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range { - (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range {
return [self initWithFile:path byteRange:range isAttachment:NO]; return [self initWithFile:path byteRange:range isAttachment:NO mimeTypeOverrides:nil];
} }
static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) { static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)]; return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)];
} }
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { - (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(NSDictionary*)overrides {
struct stat info; struct stat info;
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) { if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
@@ -91,7 +89,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
} }
#endif #endif
NSUInteger fileSize = (NSUInteger)info.st_size; NSUInteger fileSize = (NSUInteger)info.st_size;
BOOL hasByteRange = GCDWebServerIsValidByteRange(range); BOOL hasByteRange = GCDWebServerIsValidByteRange(range);
if (hasByteRange) { if (hasByteRange) {
if (range.location != NSUIntegerMax) { if (range.location != NSUIntegerMax) {
@@ -108,7 +106,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
range.location = 0; range.location = 0;
range.length = fileSize; range.length = fileSize;
} }
if ((self = [super init])) { if ((self = [super init])) {
_path = [path copy]; _path = [path copy];
_offset = range.location; _offset = range.location;
@@ -118,7 +116,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
[self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"]; [self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"];
GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path); GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path);
} }
if (attachment) { if (attachment) {
NSString* fileName = [path lastPathComponent]; NSString* fileName = [path lastPathComponent];
NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]; NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
@@ -130,8 +128,8 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
} }
} }
self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension]); self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension], overrides);
self.contentLength = _size; self.contentLength = _size;
self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec); self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec);
self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec]; self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec];

View File

@@ -27,12 +27,14 @@
#import "GCDWebServerResponse.h" #import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerStreamBlock is called to stream the data for the HTTP body. * The GCDWebServerStreamBlock is called to stream the data for the HTTP body.
* The block must return either a chunk of data, an empty NSData when done, or * The block must return either a chunk of data, an empty NSData when done, or
* nil on error and set the "error" argument which is guaranteed to be non-NULL. * nil on error and set the "error" argument which is guaranteed to be non-NULL.
*/ */
typedef NSData* (^GCDWebServerStreamBlock)(NSError** error); typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error);
/** /**
* The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock * The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock
@@ -51,6 +53,7 @@ typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlo
* the body of the HTTP response using a GCD block. * the body of the HTTP response using a GCD block.
*/ */
@interface GCDWebServerStreamedResponse : GCDWebServerResponse @interface GCDWebServerStreamedResponse : GCDWebServerResponse
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
/** /**
* Creates a response with streamed data and a given content type. * Creates a response with streamed data and a given content type.
@@ -73,3 +76,5 @@ typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlo
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -31,13 +31,11 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerStreamedResponse () { @implementation GCDWebServerStreamedResponse {
@private
GCDWebServerAsyncStreamBlock _block; GCDWebServerAsyncStreamBlock _block;
} }
@end
@implementation GCDWebServerStreamedResponse @dynamic contentType;
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { + (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return [[[self class] alloc] initWithContentType:type streamBlock:block]; return [[[self class] alloc] initWithContentType:type streamBlock:block];
@@ -48,19 +46,20 @@
} }
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { - (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return [self initWithContentType:type asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) { return [self initWithContentType:type
asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
NSError* error = nil;
NSData* data = block(&error); NSError* error = nil;
completionBlock(data, error); NSData* data = block(&error);
completionBlock(data, error);
}];
}];
} }
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block { - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
if ((self = [super init])) { if ((self = [super init])) {
_block = [block copy]; _block = [block copy];
self.contentType = type; self.contentType = type;
} }
return self; return self;

View File

@@ -27,6 +27,8 @@
#import "GCDWebServer.h" #import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebUploader; @class GCDWebUploader;
/** /**
@@ -84,7 +86,7 @@
/** /**
* Sets the delegate for the uploader. * Sets the delegate for the uploader.
*/ */
@property(nonatomic, assign) id<GCDWebUploaderDelegate> delegate; @property(nonatomic, weak, nullable) id<GCDWebUploaderDelegate> delegate;
/** /**
* Sets which files are allowed to be operated on depending on their extension. * Sets which files are allowed to be operated on depending on their extension.
@@ -195,3 +197,5 @@
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path; - (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end @end
NS_ASSUME_NONNULL_END

View File

@@ -46,17 +46,149 @@
#import "GCDWebServerErrorResponse.h" #import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h" #import "GCDWebServerFileResponse.h"
@interface GCDWebUploader () { NS_ASSUME_NONNULL_BEGIN
@private
NSString* _uploadDirectory; @interface GCDWebUploader (Methods)
NSArray* _allowedExtensions; - (nullable GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request;
BOOL _allowHidden; - (nullable GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request;
NSString* _title; - (nullable GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request;
NSString* _header; - (nullable GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request;
NSString* _prologue; - (nullable GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request;
NSString* _epilogue; - (nullable GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request;
NSString* _footer; @end
NS_ASSUME_NONNULL_END
@implementation GCDWebUploader
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {
NSString* bundlePath = [[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"];
if (bundlePath == nil) {
return nil;
}
NSBundle* siteBundle = [NSBundle bundleWithPath:bundlePath];
if (siteBundle == nil) {
return nil;
}
_uploadDirectory = [[path stringByStandardizingPath] copy];
GCDWebUploader* __unsafe_unretained server = self;
// Resource files
[self addGETHandlerForBasePath:@"/" directoryPath:(NSString*)[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
// Web page
[self addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
#if TARGET_OS_IPHONE
NSString* device = [[UIDevice currentDevice] name];
#else
NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
#endif
NSString* title = server.title;
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
}
#if !TARGET_OS_IPHONE
if (title == nil) {
title = [[NSProcessInfo processInfo] processName];
}
#endif
}
NSString* header = server.header;
if (header == nil) {
header = title;
}
NSString* prologue = server.prologue;
if (prologue == nil) {
prologue = [siteBundle localizedStringForKey:@"PROLOGUE" value:@"" table:nil];
}
NSString* epilogue = server.epilogue;
if (epilogue == nil) {
epilogue = [siteBundle localizedStringForKey:@"EPILOGUE" value:@"" table:nil];
}
NSString* footer = server.footer;
if (footer == nil) {
NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
#if !TARGET_OS_IPHONE
if (!name && !version) {
name = @"OS X";
version = [[NSProcessInfo processInfo] operatingSystemVersionString];
}
#endif
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
}
return [GCDWebServerDataResponse responseWithHTMLTemplate:(NSString*)[siteBundle pathForResource:@"index" ofType:@"html"]
variables:@{
@"device" : device,
@"title" : title,
@"header" : header,
@"prologue" : prologue,
@"epilogue" : epilogue,
@"footer" : footer
}];
}];
// File listing
[self addHandlerForMethod:@"GET"
path:@"/list"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server listDirectory:request];
}];
// File download
[self addHandlerForMethod:@"GET"
path:@"/download"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server downloadFile:request];
}];
// File upload
[self addHandlerForMethod:@"POST"
path:@"/upload"
requestClass:[GCDWebServerMultiPartFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
}];
// File and folder moving
[self addHandlerForMethod:@"POST"
path:@"/move"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// File and folder deletion
[self addHandlerForMethod:@"POST"
path:@"/delete"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// Directory creation
[self addHandlerForMethod:@"POST"
path:@"/create"
requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
}];
}
return self;
} }
@end @end
@implementation GCDWebUploader (Methods) @implementation GCDWebUploader (Methods)
@@ -67,13 +199,13 @@
} }
- (BOOL)_checkFileExtension:(NSString*)fileName { - (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) { if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO; return NO;
} }
return YES; return YES;
} }
- (NSString*) _uniquePathForPath:(NSString*)path { - (NSString*)_uniquePathForPath:(NSString*)path {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSString* directory = [path stringByDeletingLastPathComponent]; NSString* directory = [path stringByDeletingLastPathComponent];
NSString* file = [path lastPathComponent]; NSString* file = [path lastPathComponent];
@@ -82,7 +214,7 @@
int retries = 0; int retries = 0;
do { do {
if (extension.length) { if (extension.length) {
path = [directory stringByAppendingPathComponent:[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]]; path = [directory stringByAppendingPathComponent:(NSString*)[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
} else { } else {
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]]; path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
} }
@@ -101,34 +233,34 @@
if (!isDirectory) { if (!isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
} }
NSString* directoryName = [absolutePath lastPathComponent]; NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHidden && [directoryName hasPrefix:@"."]) { if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
} }
NSError* error = nil; NSError* error = nil;
NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error]; NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
if (contents == nil) { if (contents == nil) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
} }
NSMutableArray* array = [NSMutableArray array]; NSMutableArray* array = [NSMutableArray array];
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) { for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
if (_allowHidden || ![item hasPrefix:@"."]) { if (_allowHiddenItems || ![item hasPrefix:@"."]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL]; NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
NSString* type = [attributes objectForKey:NSFileType]; NSString* type = [attributes objectForKey:NSFileType];
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) { if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
[array addObject:@{ [array addObject:@{
@"path": [relativePath stringByAppendingPathComponent:item], @"path" : [relativePath stringByAppendingPathComponent:item],
@"name": item, @"name" : item,
@"size": [attributes objectForKey:NSFileSize] @"size" : [attributes objectForKey:NSFileSize]
}]; }];
} else if ([type isEqualToString:NSFileTypeDirectory]) { } else if ([type isEqualToString:NSFileTypeDirectory]) {
[array addObject:@{ [array addObject:@{
@"path": [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"], @"path" : [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
@"name": item @"name" : item
}]; }];
} }
} }
} }
@@ -145,13 +277,13 @@
if (isDirectory) { if (isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
} }
NSString* fileName = [absolutePath lastPathComponent]; NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) { if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
} }
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath: )]) { if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath]; [self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
}); });
@@ -162,9 +294,9 @@
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request { - (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch]; NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch];
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup) NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"]; GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
if ((!_allowHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) { if ((!_allowHiddenItems && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
} }
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string]; NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
@@ -172,16 +304,16 @@
if (![self _checkSandboxedPath:absolutePath]) { if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) { if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
} }
NSError* error = nil; NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) { if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
} }
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) { if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didUploadFileAtPath:absolutePath]; [self.delegate webUploader:self didUploadFileAtPath:absolutePath];
@@ -197,27 +329,27 @@
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
} }
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"]; NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]]; NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
if (![self _checkSandboxedPath:newAbsolutePath]) { if (![self _checkSandboxedPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
} }
NSString* itemName = [newAbsolutePath lastPathComponent]; NSString* itemName = [newAbsolutePath lastPathComponent];
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) { if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
} }
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) { if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
} }
NSError* error = nil; NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) { if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
} }
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) { if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]; [self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
@@ -233,21 +365,21 @@
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) { if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
NSString* itemName = [absolutePath lastPathComponent]; NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) { if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
} }
if (![self shouldDeleteItemAtPath:absolutePath]) { if (![self shouldDeleteItemAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
} }
NSError* error = nil; NSError* error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) { if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
} }
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) { if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath]; [self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
@@ -262,21 +394,21 @@
if (![self _checkSandboxedPath:absolutePath]) { if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
} }
NSString* directoryName = [absolutePath lastPathComponent]; NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHidden && [directoryName hasPrefix:@"."]) { if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
} }
if (![self shouldCreateDirectoryAtPath:absolutePath]) { if (![self shouldCreateDirectoryAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath]; return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
} }
NSError* error = nil; NSError* error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) { if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath]; return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
} }
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) { if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath]; [self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
@@ -287,117 +419,6 @@
@end @end
@implementation GCDWebUploader
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden,
title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer;
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
if (siteBundle == nil) {
return nil;
}
_uploadDirectory = [[path stringByStandardizingPath] copy];
GCDWebUploader* __unsafe_unretained server = self;
// Resource files
[self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
// Web page
[self addHandlerForMethod:@"GET" path:@"/" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
#if TARGET_OS_IPHONE
NSString* device = [[UIDevice currentDevice] name];
#else
NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
#endif
NSString* title = server.title;
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (title == nil) {
title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
}
#if !TARGET_OS_IPHONE
if (title == nil) {
title = [[NSProcessInfo processInfo] processName];
}
#endif
}
NSString* header = server.header;
if (header == nil) {
header = title;
}
NSString* prologue = server.prologue;
if (prologue == nil) {
prologue = [siteBundle localizedStringForKey:@"PROLOGUE" value:@"" table:nil];
}
NSString* epilogue = server.epilogue;
if (epilogue == nil) {
epilogue = [siteBundle localizedStringForKey:@"EPILOGUE" value:@"" table:nil];
}
NSString* footer = server.footer;
if (footer == nil) {
NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
#if !TARGET_OS_IPHONE
if (!name && !version) {
name = @"OS X";
version = [[NSProcessInfo processInfo] operatingSystemVersionString];
}
#endif
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
}
return [GCDWebServerDataResponse responseWithHTMLTemplate:[siteBundle pathForResource:@"index" ofType:@"html"]
variables:@{
@"device": device,
@"title": title,
@"header": header,
@"prologue": prologue,
@"epilogue": epilogue,
@"footer": footer
}];
}];
// File listing
[self addHandlerForMethod:@"GET" path:@"/list" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server listDirectory:request];
}];
// File download
[self addHandlerForMethod:@"GET" path:@"/download" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server downloadFile:request];
}];
// File upload
[self addHandlerForMethod:@"POST" path:@"/upload" requestClass:[GCDWebServerMultiPartFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
}];
// File and folder moving
[self addHandlerForMethod:@"POST" path:@"/move" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// File and folder deletion
[self addHandlerForMethod:@"POST" path:@"/delete" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
}];
// Directory creation
[self addHandlerForMethod:@"POST" path:@"/create" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
}];
}
return self;
}
@end
@implementation GCDWebUploader (Subclassing) @implementation GCDWebUploader (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath { - (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {

View File

@@ -147,7 +147,7 @@ int main(int argc, const char* argv[]) {
NSString* authenticationPassword = nil; NSString* authenticationPassword = nil;
BOOL bindToLocalhost = NO; BOOL bindToLocalhost = NO;
BOOL requestNATPortMapping = NO; BOOL requestNATPortMapping = NO;
if (argc == 1) { if (argc == 1) {
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password] [--localhost]\n\n", basename((char*)argv[0])); fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password] [--localhost]\n\n", basename((char*)argv[0]));
} else { } else {
@@ -201,10 +201,9 @@ int main(int argc, const char* argv[]) {
} }
} }
} }
GCDWebServer* webServer = nil; GCDWebServer* webServer = nil;
switch (mode) { switch (mode) {
// Simply serve contents of home directory // Simply serve contents of home directory
case kMode_WebServer: { case kMode_WebServer: {
fprintf(stdout, "Running in Web Server mode from \"%s\"", [rootDirectory UTF8String]); fprintf(stdout, "Running in Web Server mode from \"%s\"", [rootDirectory UTF8String]);
@@ -212,21 +211,21 @@ int main(int argc, const char* argv[]) {
[webServer addGETHandlerForBasePath:@"/" directoryPath:rootDirectory indexFilename:nil cacheAge:0 allowRangeRequests:YES]; [webServer addGETHandlerForBasePath:@"/" directoryPath:rootDirectory indexFilename:nil cacheAge:0 allowRangeRequests:YES];
break; break;
} }
// Renders a HTML page // Renders a HTML page
case kMode_HTMLPage: { case kMode_HTMLPage: {
fprintf(stdout, "Running in HTML Page mode"); fprintf(stdout, "Running in HTML Page mode");
webServer = [[GCDWebServer alloc] init]; webServer = [[GCDWebServer alloc] init];
[webServer addDefaultHandlerForMethod:@"GET" [webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"]; return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
}]; }];
break; break;
} }
// Implements an HTML form // Implements an HTML form
case kMode_HTMLForm: { case kMode_HTMLForm: {
fprintf(stdout, "Running in HTML Form mode"); fprintf(stdout, "Running in HTML Form mode");
@@ -234,9 +233,9 @@ int main(int argc, const char* argv[]) {
[webServer addHandlerForMethod:@"GET" [webServer addHandlerForMethod:@"GET"
path:@"/" path:@"/"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSString* html = @" \ NSString* html = @" \
<html><body> \ <html><body> \
<form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \ <form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
Value: <input type=\"text\" name=\"value\"> \ Value: <input type=\"text\" name=\"value\"> \
@@ -244,22 +243,22 @@ int main(int argc, const char* argv[]) {
</form> \ </form> \
</body></html> \ </body></html> \
"; ";
return [GCDWebServerDataResponse responseWithHTML:html]; return [GCDWebServerDataResponse responseWithHTML:html];
}]; }];
[webServer addHandlerForMethod:@"POST" [webServer addHandlerForMethod:@"POST"
path:@"/" path:@"/"
requestClass:[GCDWebServerURLEncodedFormRequest class] requestClass:[GCDWebServerURLEncodedFormRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"]; NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value]; NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
return [GCDWebServerDataResponse responseWithHTML:html]; return [GCDWebServerDataResponse responseWithHTML:html];
}]; }];
break; break;
} }
// Implements HTML file upload // Implements HTML file upload
case kMode_HTMLFileUpload: { case kMode_HTMLFileUpload: {
fprintf(stdout, "Running in HTML File Upload mode"); fprintf(stdout, "Running in HTML File Upload mode");
@@ -274,48 +273,48 @@ int main(int argc, const char* argv[]) {
[webServer addHandlerForMethod:@"GET" [webServer addHandlerForMethod:@"GET"
path:@"/" path:@"/"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML]; NSString* html = [NSString stringWithFormat:@"<html><body>%@</body></html>", formHTML];
return [GCDWebServerDataResponse responseWithHTML:html]; return [GCDWebServerDataResponse responseWithHTML:html];
}]; }];
[webServer addHandlerForMethod:@"POST" [webServer addHandlerForMethod:@"POST"
path:@"/" path:@"/"
requestClass:[GCDWebServerMultiPartFormRequest class] requestClass:[GCDWebServerMultiPartFormRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
NSMutableString* string = [NSMutableString string]; NSMutableString* string = [NSMutableString string];
for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) { for (GCDWebServerMultiPartArgument* argument in [(GCDWebServerMultiPartFormRequest*)request arguments]) {
[string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string]; [string appendFormat:@"%@ = %@<br>", argument.controlName, argument.string];
} }
for (GCDWebServerMultiPartFile* file in [(GCDWebServerMultiPartFormRequest*)request files]) { for (GCDWebServerMultiPartFile* file in [(GCDWebServerMultiPartFormRequest*)request files]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:file.temporaryPath error:NULL]; NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:file.temporaryPath error:NULL];
[string appendFormat:@"%@ = &quot;%@&quot; (%@ | %llu %@)<br>", file.controlName, file.fileName, file.mimeType, [string appendFormat:@"%@ = &quot;%@&quot; (%@ | %llu %@)<br>", file.controlName, file.fileName, file.mimeType,
attributes.fileSize >= 1000 ? attributes.fileSize / 1000 : attributes.fileSize, attributes.fileSize >= 1000 ? attributes.fileSize / 1000 : attributes.fileSize,
attributes.fileSize >= 1000 ? @"KB" : @"Bytes"]; attributes.fileSize >= 1000 ? @"KB" : @"Bytes"];
}; };
NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML]; NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p><hr>%@</body></html>", string, formHTML];
return [GCDWebServerDataResponse responseWithHTML:html]; return [GCDWebServerDataResponse responseWithHTML:html];
}]; }];
break; break;
} }
// Serve home directory through WebDAV // Serve home directory through WebDAV
case kMode_WebDAV: { case kMode_WebDAV: {
fprintf(stdout, "Running in WebDAV mode from \"%s\"", [rootDirectory UTF8String]); fprintf(stdout, "Running in WebDAV mode from \"%s\"", [rootDirectory UTF8String]);
webServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:rootDirectory]; webServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:rootDirectory];
break; break;
} }
// Serve home directory through web uploader // Serve home directory through web uploader
case kMode_WebUploader: { case kMode_WebUploader: {
fprintf(stdout, "Running in Web Uploader mode from \"%s\"", [rootDirectory UTF8String]); fprintf(stdout, "Running in Web Uploader mode from \"%s\"", [rootDirectory UTF8String]);
webServer = [[GCDWebUploader alloc] initWithUploadDirectory:rootDirectory]; webServer = [[GCDWebUploader alloc] initWithUploadDirectory:rootDirectory];
break; break;
} }
// Test streaming responses // Test streaming responses
case kMode_StreamingResponse: { case kMode_StreamingResponse: {
fprintf(stdout, "Running in Streaming Response mode"); fprintf(stdout, "Running in Streaming Response mode");
@@ -323,42 +322,44 @@ int main(int argc, const char* argv[]) {
[webServer addHandlerForMethod:@"GET" [webServer addHandlerForMethod:@"GET"
path:@"/sync" path:@"/sync"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
__block int countDown = 10; __block int countDown = 10;
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" streamBlock:^NSData *(NSError** error) { return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain"
streamBlock:^NSData*(NSError** error) {
usleep(100 * 1000);
if (countDown) { usleep(100 * 1000);
return [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding]; if (countDown) {
} else { return [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding];
return [NSData data]; } else {
} return [NSData data];
}
}];
}];
}];
}];
[webServer addHandlerForMethod:@"GET" [webServer addHandlerForMethod:@"GET"
path:@"/async" path:@"/async"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
__block int countDown = 10; __block int countDown = 10;
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) { 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(), ^{
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); NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
completionBlock(data, nil);
});
});
}];
}];
}];
}];
break; break;
} }
// Test async responses // Test async responses
case kMode_AsyncResponse: { case kMode_AsyncResponse: {
fprintf(stdout, "Running in Async Response mode"); fprintf(stdout, "Running in Async Response mode");
@@ -367,41 +368,41 @@ int main(int argc, const char* argv[]) {
path:@"/async" path:@"/async"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { 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), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"]; GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
completionBlock(response); completionBlock(response);
}); });
}]; }];
[webServer addHandlerForMethod:@"GET" [webServer addHandlerForMethod:@"GET"
path:@"/async2" path:@"/async2"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock handlerCompletionBlock) { 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), ^{ 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; __block int countDown = 10;
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock readerCompletionBlock) { 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(), ^{
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); NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
readerCompletionBlock(data, nil);
});
});
}];
handlerCompletionBlock(response); }];
handlerCompletionBlock(response);
});
});
}];
}];
break; break;
} }
} }
if (webServer) { if (webServer) {
Delegate* delegate = [[Delegate alloc] init]; Delegate* delegate = [[Delegate alloc] init];
if (testDirectory) { if (testDirectory) {
@@ -409,7 +410,7 @@ int main(int argc, const char* argv[]) {
webServer.delegate = delegate; webServer.delegate = delegate;
#endif #endif
fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]); fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]);
result = (int)[webServer runTestsWithOptions:@{GCDWebServerOption_Port: @8080} inDirectory:testDirectory]; result = (int)[webServer runTestsWithOptions:@{ GCDWebServerOption_Port : @8080 } inDirectory:testDirectory];
} else { } else {
webServer.delegate = delegate; webServer.delegate = delegate;
if (recording) { if (recording) {
@@ -424,7 +425,7 @@ int main(int argc, const char* argv[]) {
[options setObject:@"" forKey:GCDWebServerOption_BonjourName]; [options setObject:@"" forKey:GCDWebServerOption_BonjourName];
if (authenticationUser && authenticationPassword) { if (authenticationUser && authenticationPassword) {
[options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm]; [options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];
[options setObject:@{authenticationUser: authenticationPassword} forKey:GCDWebServerOption_AuthenticationAccounts]; [options setObject:@{authenticationUser : authenticationPassword} forKey:GCDWebServerOption_AuthenticationAccounts];
if ([authenticationMethod isEqualToString:@"Basic"]) { if ([authenticationMethod isEqualToString:@"Basic"]) {
[options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod]; [options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod];
} else if ([authenticationMethod isEqualToString:@"Digest"]) { } else if ([authenticationMethod isEqualToString:@"Digest"]) {

View File

@@ -36,7 +36,7 @@ What's not supported (but not really required from an embedded HTTP server):
Requirements: Requirements:
* OS X 10.7 or later (x86_64) * OS X 10.7 or later (x86_64)
* iOS 5.0 or later (armv7, armv7s or arm64) * iOS 8.0 or later (armv7, armv7s or arm64)
* ARC memory management only (if you need MRC support use GCDWebServer 3.1 and earlier) * ARC memory management only (if you need MRC support use GCDWebServer 3.1 and earlier)
Getting Started Getting Started
@@ -71,9 +71,9 @@ Then run `$ carthage update` and add the generated frameworks to your Xcode proj
Help & Support Help & Support
============== ==============
For help with using GCDWebServer, it's best to ask your question on Stack Overflow with the [`gcdwebserver`](http://stackoverflow.com/questions/tagged/gcdwebserver) tag. Be sure to read this entire README first though! For help with using GCDWebServer, it's best to ask your question on Stack Overflow with the [`gcdwebserver`](http://stackoverflow.com/questions/tagged/gcdwebserver) tag. For bug reports and enhancement requests you can use [issues](https://github.com/swisspol/GCDWebServer/issues) in this project.
For bug reports or enhancement requests, please use [GitHub issues](https://github.com/swisspol/GCDWebServer/issues) instead. Be sure to read this entire README first though!
Hello World Hello World
=========== ===========
@@ -153,7 +153,7 @@ int main(int argc, const char* argv[]) {
***webServer.swift*** ***webServer.swift***
```swift ```swift
import Foundation import Foundation
import GCDWebServers import GCDWebServer
func initWebServer() { func initWebServer() {
@@ -172,8 +172,8 @@ func initWebServer() {
***WebServer-Bridging-Header.h*** ***WebServer-Bridging-Header.h***
```objectivec ```objectivec
#import <GCDWebServers/GCDWebServer.h> #import <GCDWebServer/GCDWebServer.h>
#import <GCDWebServers/GCDWebServerDataResponse.h> #import <GCDWebServer/GCDWebServerDataResponse.h>
``` ```
Web Based Uploads in iOS Apps Web Based Uploads in iOS Apps
@@ -276,7 +276,7 @@ GCDWebServer's architecture consists of only 4 core classes:
Implementing Handlers Implementing Handlers
===================== =====================
GCDWebServer relies on "handlers" to process incoming web requests and generating responses. Handlers are implemented with GCD blocks which makes it very easy to provide your owns. However, they are executed on arbitrary threads within GCD so __special attention must be paid to thread-safety and re-entrancy__. GCDWebServer relies on "handlers" to process incoming web requests and generating responses. Handlers are implemented with GCD blocks which makes it very easy to provide your own. However, they are executed on arbitrary threads within GCD so __special attention must be paid to thread-safety and re-entrancy__.
Handlers require 2 GCD blocks: Handlers require 2 GCD blocks:
* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil. * The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil.
@@ -287,7 +287,7 @@ Note that most methods on ```GCDWebServer``` to add handlers only require the ``
Asynchronous HTTP Responses Asynchronous HTTP Responses
=========================== ===========================
New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example: New in GCDWebServer 3.0 is the ability to process HTTP requests asynchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
**(Synchronous version)** The handler blocks while generating the HTTP response: **(Synchronous version)** The handler blocks while generating the HTTP response:
```objectivec ```objectivec

View File

@@ -58,7 +58,7 @@ xcodebuild build -sdk "$OSX_SDK" -target "$OSX_TARGET" -configuration "$CONFIGUR
# Build for iOS for oldest supported deployment target # Build for iOS for oldest supported deployment target
rm -rf "$BUILD_DIR" rm -rf "$BUILD_DIR"
xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=6.0" > /dev/null xcodebuild build -sdk "$IOS_SDK" -target "$IOS_TARGET" -configuration "$CONFIGURATION" "SYMROOT=$BUILD_DIR" "IPHONEOS_DEPLOYMENT_TARGET=8.0" > /dev/null
# Build for iOS for current deployment target # Build for iOS for current deployment target
rm -rf "$BUILD_DIR" rm -rf "$BUILD_DIR"

0
Tests/WebDAV-Cyberduck/001-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/001-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/002-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/002-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/003-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/003-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/004-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/004-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/005-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/005-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/006-404.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/006-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/007-201.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/007-COPY.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/008-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/008-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/009-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/009-HEAD.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/010-200.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/010-GET.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/011-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/011-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/012-204.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/012-DELETE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/013-204.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/013-DELETE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/014-204.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/014-DELETE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/015-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/015-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/016-201.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/016-MOVE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/017-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/017-PROPFIND.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/018-201.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/018-MOVE.request Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/019-207.response Executable file → Normal file
View File

0
Tests/WebDAV-Cyberduck/019-PROPFIND.request Executable file → Normal file
View File

Some files were not shown because too many files have changed in this diff Show More