mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-02-11 00:00:07 +08:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac788ca906 | ||
|
|
9f9509a05e | ||
|
|
48777cc151 | ||
|
|
c35d514b08 | ||
|
|
a013f9cebb | ||
|
|
1e28aef262 | ||
|
|
09851dd71b | ||
|
|
23f77b5765 | ||
|
|
7de8b0c643 | ||
|
|
2685819b61 | ||
|
|
fbcf3fa96c | ||
|
|
0f8e4f57e0 | ||
|
|
750214b5d5 | ||
|
|
ce5310f2ce | ||
|
|
83f1c062b5 | ||
|
|
0c51a9b071 | ||
|
|
3b5bc40e5e | ||
|
|
3178baa86e | ||
|
|
9a97e44d97 | ||
|
|
6c2b11e7b6 | ||
|
|
56b8e1befe | ||
|
|
4231465c58 | ||
|
|
4958eb8c72 |
16
.clang-format
Normal file
16
.clang-format
Normal 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
|
||||||
|
...
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'GCDWebServer'
|
s.name = 'GCDWebServer'
|
||||||
s.version = '3.3.3'
|
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,7 +35,7 @@ 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|
|
||||||
|
|||||||
@@ -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 = 0730;
|
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.3;
|
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,6 @@
|
|||||||
"-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",
|
"-Wno-partial-availability",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1246,7 +1245,7 @@
|
|||||||
1DEB928B08733DD80010E9CD /* Release */ = {
|
1DEB928B08733DD80010E9CD /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_VERSION_STRING = 3.3.3;
|
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__;
|
||||||
@@ -1269,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;
|
||||||
};
|
};
|
||||||
@@ -1284,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;
|
||||||
@@ -1299,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;
|
||||||
@@ -1316,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;
|
||||||
};
|
};
|
||||||
@@ -1374,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;
|
||||||
@@ -1382,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;
|
||||||
@@ -1391,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;
|
||||||
@@ -1399,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;
|
||||||
@@ -1408,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 = "";
|
||||||
@@ -1420,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 = "";
|
||||||
@@ -1432,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;
|
||||||
@@ -1446,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;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0730"
|
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">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0730"
|
LastUpgradeVersion = "0830"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0730"
|
LastUpgradeVersion = "0830"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -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)(__kindof GCDWebServerRequest* request);
|
typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
|
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
|
||||||
@@ -64,7 +66,7 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(__kindof GCDWebServerR
|
|||||||
* 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)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
|
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -295,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.
|
||||||
@@ -315,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.
|
||||||
@@ -323,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.
|
||||||
@@ -363,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.
|
||||||
@@ -383,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.
|
||||||
@@ -393,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.
|
||||||
@@ -401,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)
|
||||||
@@ -418,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
|
||||||
|
|
||||||
@@ -431,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
|
||||||
@@ -442,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
|
||||||
|
|
||||||
@@ -498,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
|
||||||
@@ -515,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
|
||||||
|
|
||||||
@@ -575,22 +577,22 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
|||||||
/**
|
/**
|
||||||
* 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);
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -612,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
|
||||||
|
|||||||
@@ -132,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];
|
||||||
@@ -153,26 +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;
|
||||||
dispatch_queue_priority_t _dispatchQueuePriority;
|
|
||||||
NSUInteger _port;
|
|
||||||
dispatch_source_t _source4;
|
dispatch_source_t _source4;
|
||||||
dispatch_source_t _source6;
|
dispatch_source_t _source6;
|
||||||
CFNetServiceRef _registrationService;
|
CFNetServiceRef _registrationService;
|
||||||
@@ -191,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, dispatchQueuePriority=_dispatchQueuePriority;
|
|
||||||
|
|
||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
GCDWebServerInitializeFunctions();
|
GCDWebServerInitializeFunctions();
|
||||||
@@ -220,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);
|
||||||
@@ -235,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();
|
||||||
@@ -253,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];
|
||||||
}
|
}
|
||||||
@@ -267,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(), ^{
|
||||||
@@ -282,7 +259,7 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
_activeConnections += 1;
|
_activeConnections += 1;
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,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];
|
||||||
}
|
}
|
||||||
@@ -356,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 {
|
||||||
@@ -464,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);
|
||||||
@@ -483,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);
|
||||||
@@ -497,7 +475,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
dispatch_group_enter(_sourceGroup);
|
dispatch_group_enter(_sourceGroup);
|
||||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0));
|
dispatch_source_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) {
|
||||||
@@ -507,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;
|
||||||
@@ -527,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);
|
||||||
@@ -568,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);
|
||||||
@@ -580,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]) {
|
||||||
@@ -599,27 +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];
|
_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);
|
||||||
@@ -631,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) {
|
||||||
@@ -654,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);
|
||||||
@@ -663,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;
|
||||||
@@ -685,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);
|
||||||
@@ -700,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
|
||||||
@@ -714,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);
|
||||||
@@ -728,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(), ^{
|
||||||
@@ -896,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;
|
||||||
}
|
}
|
||||||
@@ -929,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;
|
||||||
}
|
}
|
||||||
@@ -972,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();
|
||||||
}
|
}
|
||||||
@@ -984,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 {
|
||||||
@@ -1042,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;
|
||||||
}
|
}
|
||||||
@@ -1051,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();
|
||||||
}
|
}
|
||||||
@@ -1204,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) {
|
||||||
@@ -1230,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) {
|
||||||
@@ -1262,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")));
|
||||||
@@ -1275,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);
|
||||||
@@ -1311,9 +1307,9 @@ static void _LogResult(NSString* format, ...) {
|
|||||||
}
|
}
|
||||||
_ExecuteMainThreadRunLoopSources();
|
_ExecuteMainThreadRunLoopSources();
|
||||||
}
|
}
|
||||||
|
|
||||||
[self stop];
|
[self stop];
|
||||||
|
|
||||||
_ExecuteMainThreadRunLoopSources();
|
_ExecuteMainThreadRunLoopSources();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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:¶meter];
|
if ([scanner scanString:@"\"" intoString:NULL]) {
|
||||||
} else {
|
[scanner scanUpToString:@"\"" intoString:¶meter];
|
||||||
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
} else {
|
||||||
|
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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,15 +237,16 @@ 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) {
|
||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
@@ -123,14 +125,29 @@ 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_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
|
||||||
|
|
||||||
@@ -143,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()
|
||||||
|
|
||||||
@@ -171,29 +188,29 @@ 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;
|
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
|
||||||
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
||||||
@@ -207,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 ()
|
||||||
@@ -224,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -257,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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, ... {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
0
GCDWebUploader/GCDWebUploader.bundle/js/html5shiv.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/html5shiv.min.js
vendored
Executable file → Normal file
0
GCDWebUploader/GCDWebUploader.bundle/js/html5shiv.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/html5shiv.min.js
vendored
Executable file → Normal file
0
GCDWebUploader/GCDWebUploader.bundle/js/respond.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/respond.min.js
vendored
Executable file → Normal file
0
GCDWebUploader/GCDWebUploader.bundle/js/respond.min.js → GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/respond.min.js
vendored
Executable file → Normal 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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
221
Mac/main.m
221
Mac/main.m
@@ -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:@"%@ = "%@" (%@ | %llu %@)<br>", file.controlName, file.fileName, file.mimeType,
|
[string appendFormat:@"%@ = "%@" (%@ | %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"]) {
|
||||||
|
|||||||
@@ -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,7 +71,7 @@ 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.
|
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.
|
||||||
|
|
||||||
Be sure to read this entire README first though!
|
Be sure to read this entire README first though!
|
||||||
|
|
||||||
|
|||||||
@@ -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
0
Tests/WebDAV-Cyberduck/001-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/001-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/001-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/002-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/003-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/004-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/005-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-404.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-404.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/006-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-COPY.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/007-COPY.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/008-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/009-HEAD.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-200.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-GET.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/010-GET.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/011-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/012-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/013-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-204.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/014-DELETE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/015-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-MOVE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/016-MOVE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/017-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/017-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/017-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/017-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/018-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/018-201.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/018-MOVE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/018-MOVE.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/019-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/019-207.response
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/019-PROPFIND.request
Executable file → Normal file
0
Tests/WebDAV-Cyberduck/019-PROPFIND.request
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user