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;
|
||||||
@@ -101,7 +193,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +236,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +272,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +309,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +354,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
@@ -281,7 +373,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,11 +457,11 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
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]) {
|
||||||
@@ -447,7 +539,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -539,7 +631,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -599,7 +691,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,73 +701,6 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
|
|||||||
|
|
||||||
@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
|
||||||
|
|
||||||
@@ -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,9 +144,7 @@ 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;
|
||||||
@@ -164,15 +153,10 @@ static void _ExecuteMainThreadRunLoopSources() {
|
|||||||
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();
|
||||||
@@ -356,7 +333,8 @@ 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
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(processBlock(request));
|
completionBlock(processBlock(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -599,7 +577,7 @@ 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];
|
||||||
|
|
||||||
@@ -896,7 +874,9 @@ 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
|
||||||
|
requestClass:aClass
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(block(request));
|
completionBlock(block(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -909,11 +889,15 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
}
|
}
|
||||||
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
|
||||||
|
path:path
|
||||||
|
requestClass:aClass
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(block(request));
|
completionBlock(block(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -930,14 +914,18 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
}
|
}
|
||||||
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
|
||||||
|
pathRegex:regex
|
||||||
|
requestClass:aClass
|
||||||
|
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||||
completionBlock(block(request));
|
completionBlock(block(request));
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -973,7 +961,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
[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,7 +973,10 @@ 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
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
|
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
|
||||||
response.cacheControlMaxAge = cacheAge;
|
response.cacheControlMaxAge = cacheAge;
|
||||||
@@ -994,7 +986,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (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
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
if (allowRangeRequests) {
|
if (allowRangeRequests) {
|
||||||
@@ -1052,7 +1047,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
|||||||
}
|
}
|
||||||
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;
|
GCDWebServerResponse* response = nil;
|
||||||
NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
|
NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
|
||||||
@@ -1275,9 +1271,9 @@ 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"];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -57,14 +57,25 @@ static NSString* _digestAuthenticationNonce = nil;
|
|||||||
static int32_t _connectionCounter = 0;
|
static int32_t _connectionCounter = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@interface GCDWebServerConnection () {
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
@private
|
|
||||||
GCDWebServer* _server;
|
@interface GCDWebServerConnection (Read)
|
||||||
NSData* _localAddress;
|
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block;
|
||||||
NSData* _remoteAddress;
|
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block;
|
||||||
|
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block;
|
||||||
|
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerConnection (Write)
|
||||||
|
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block;
|
||||||
|
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block;
|
||||||
|
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block;
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
@implementation GCDWebServerConnection {
|
||||||
CFSocketNativeHandle _socket;
|
CFSocketNativeHandle _socket;
|
||||||
NSUInteger _bytesRead;
|
|
||||||
NSUInteger _bytesWritten;
|
|
||||||
BOOL _virtualHEAD;
|
BOOL _virtualHEAD;
|
||||||
|
|
||||||
CFHTTPMessageRef _requestMessage;
|
CFHTTPMessageRef _requestMessage;
|
||||||
@@ -83,258 +94,6 @@ static int32_t _connectionCounter = 0;
|
|||||||
int _responseFD;
|
int _responseFD;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerConnection (Read)
|
|
||||||
|
|
||||||
- (void)_readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
|
|
||||||
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
|
|
||||||
|
|
||||||
@autoreleasepool {
|
|
||||||
if (error == 0) {
|
|
||||||
size_t size = dispatch_data_get_size(buffer);
|
|
||||||
if (size > 0) {
|
|
||||||
NSUInteger originalLength = data.length;
|
|
||||||
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
|
|
||||||
[data appendBytes:chunkBytes length:chunkSize];
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
|
|
||||||
block(YES);
|
|
||||||
} else {
|
|
||||||
if (_bytesRead > 0) {
|
|
||||||
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
|
|
||||||
} else {
|
|
||||||
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
|
|
||||||
}
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
|
|
||||||
GWS_DCHECK(_requestMessage);
|
|
||||||
[self _readData:headersData withLength:NSUIntegerMax completionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
|
|
||||||
if (range.location == NSNotFound) {
|
|
||||||
[self _readHeaders:headersData withCompletionBlock:block];
|
|
||||||
} else {
|
|
||||||
NSUInteger length = range.location + range.length;
|
|
||||||
if (CFHTTPMessageAppendBytes(_requestMessage, headersData.bytes, length)) {
|
|
||||||
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
|
|
||||||
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
|
||||||
block(nil);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
|
||||||
block(nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
block(nil);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
|
|
||||||
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
|
|
||||||
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
|
|
||||||
[self _readData:bodyData withLength:length completionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
if (bodyData.length <= length) {
|
|
||||||
NSError* error = nil;
|
|
||||||
if ([_request performWriteData:bodyData error:&error]) {
|
|
||||||
NSUInteger remainingLength = length - bodyData.length;
|
|
||||||
if (remainingLength) {
|
|
||||||
[self _readBodyWithRemainingLength:remainingLength completionBlock:block];
|
|
||||||
} else {
|
|
||||||
block(YES);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
|
||||||
block(NO);
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|
||||||
char buffer[size + 1];
|
|
||||||
bcopy(bytes, buffer, size);
|
|
||||||
buffer[size] = 0;
|
|
||||||
char* end = NULL;
|
|
||||||
long result = strtol(buffer, &end, 16);
|
|
||||||
return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
|
|
||||||
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
|
|
||||||
if (range.location == NSNotFound) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions
|
|
||||||
NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
|
|
||||||
if (length != NSNotFound) {
|
|
||||||
if (length) {
|
|
||||||
if (chunkData.length < range.location + range.length + length + 2) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
|
|
||||||
if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
|
|
||||||
NSError* error = nil;
|
|
||||||
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
|
|
||||||
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers
|
|
||||||
if (trailerRange.location != NSNotFound) {
|
|
||||||
block(YES);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[self _readData:chunkData withLength:NSUIntegerMax completionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
[self _readNextBodyChunk:chunkData completionBlock:block];
|
|
||||||
} else {
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerConnection (Write)
|
|
||||||
|
|
||||||
- (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
|
|
||||||
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
|
|
||||||
[data self]; // Keeps ARC from releasing data too early
|
|
||||||
});
|
|
||||||
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
|
|
||||||
|
|
||||||
@autoreleasepool {
|
|
||||||
if (error == 0) {
|
|
||||||
GWS_DCHECK(remainingData == NULL);
|
|
||||||
[self didWriteBytes:data.bytes length:data.length];
|
|
||||||
block(YES);
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
|
||||||
dispatch_release(buffer);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
|
|
||||||
GWS_DCHECK(_responseMessage);
|
|
||||||
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
|
|
||||||
[self _writeData:(__bridge NSData*)data withCompletionBlock:block];
|
|
||||||
CFRelease(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
|
||||||
GWS_DCHECK([_response hasBody]);
|
|
||||||
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
if (data.length) {
|
|
||||||
if (_response.usesChunkedTransferEncoding) {
|
|
||||||
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
|
||||||
size_t hexLength = strlen(hexString);
|
|
||||||
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
|
||||||
if (chunk == nil) {
|
|
||||||
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
|
|
||||||
bcopy(hexString, ptr, hexLength);
|
|
||||||
ptr += hexLength;
|
|
||||||
*ptr++ = '\r';
|
|
||||||
*ptr++ = '\n';
|
|
||||||
bcopy(data.bytes, ptr, data.length);
|
|
||||||
ptr += data.length;
|
|
||||||
*ptr++ = '\r';
|
|
||||||
*ptr = '\n';
|
|
||||||
data = chunk;
|
|
||||||
}
|
|
||||||
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
[self _writeBodyWithCompletionBlock:block];
|
|
||||||
} else {
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
if (_response.usesChunkedTransferEncoding) {
|
|
||||||
[self _writeData:_lastChunkData withCompletionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
block(success);
|
|
||||||
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
block(YES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerConnection
|
|
||||||
|
|
||||||
@synthesize server=_server, localAddressData=_localAddress, remoteAddressData=_remoteAddress, totalBytesRead=_bytesRead, totalBytesWritten=_bytesWritten;
|
|
||||||
|
|
||||||
+ (void)initialize {
|
+ (void)initialize {
|
||||||
if (_CRLFData == nil) {
|
if (_CRLFData == nil) {
|
||||||
@@ -362,7 +121,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isUsingIPv6 {
|
- (BOOL)isUsingIPv6 {
|
||||||
const struct sockaddr* localSockAddr = _localAddress.bytes;
|
const struct sockaddr* localSockAddr = _localAddressData.bytes;
|
||||||
return (localSockAddr->sa_family == AF_INET6);
|
return (localSockAddr->sa_family == AF_INET6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,7 +140,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
if (preflightResponse) {
|
if (preflightResponse) {
|
||||||
[self _finishProcessingRequest:preflightResponse];
|
[self _finishProcessingRequest:preflightResponse];
|
||||||
} else {
|
} else {
|
||||||
[self processRequest:_request completion:^(GCDWebServerResponse* processResponse) {
|
[self processRequest:_request
|
||||||
|
completion:^(GCDWebServerResponse* processResponse) {
|
||||||
[self _finishProcessingRequest:processResponse];
|
[self _finishProcessingRequest:processResponse];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -411,7 +171,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
if (_response) {
|
if (_response) {
|
||||||
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
|
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
|
||||||
if (_response.lastModifiedDate) {
|
if (_response.lastModifiedDate) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822(_response.lastModifiedDate));
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822((NSDate*)_response.lastModifiedDate));
|
||||||
}
|
}
|
||||||
if (_response.eTag) {
|
if (_response.eTag) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
|
||||||
@@ -435,11 +195,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
|
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
|
||||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
|
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
|
||||||
}];
|
}];
|
||||||
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
|
[self writeHeadersWithCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
if (hasBody) {
|
if (hasBody) {
|
||||||
[self _writeBodyWithCompletionBlock:^(BOOL successInner) {
|
[self writeBodyWithCompletionBlock:^(BOOL successInner) {
|
||||||
|
|
||||||
[_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent
|
[_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent
|
||||||
|
|
||||||
@@ -453,7 +213,6 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
} else {
|
} else {
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
|
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
|
||||||
@@ -477,7 +236,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (length) {
|
if (length) {
|
||||||
[self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) {
|
[self readBodyWithRemainingLength:length
|
||||||
|
completionBlock:^(BOOL success) {
|
||||||
|
|
||||||
NSError* localError = nil;
|
NSError* localError = nil;
|
||||||
if ([_request performClose:&localError]) {
|
if ([_request performClose:&localError]) {
|
||||||
@@ -507,7 +267,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
|
NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
|
||||||
[self _readNextBodyChunk:chunkData completionBlock:^(BOOL success) {
|
[self readNextBodyChunk:chunkData
|
||||||
|
completionBlock:^(BOOL success) {
|
||||||
|
|
||||||
NSError* localError = nil;
|
NSError* localError = nil;
|
||||||
if ([_request performClose:&localError]) {
|
if ([_request performClose:&localError]) {
|
||||||
@@ -523,7 +284,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
- (void)_readRequestHeaders {
|
- (void)_readRequestHeaders {
|
||||||
_requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
|
_requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
|
||||||
NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
|
NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
|
||||||
[self _readHeaders:headersData withCompletionBlock:^(NSData* extraData) {
|
[self readHeaders:headersData
|
||||||
|
withCompletionBlock:^(NSData* extraData) {
|
||||||
|
|
||||||
if (extraData) {
|
if (extraData) {
|
||||||
NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase
|
NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase
|
||||||
@@ -537,7 +299,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
|
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
|
||||||
GWS_DCHECK(requestURL);
|
GWS_DCHECK(requestURL);
|
||||||
}
|
}
|
||||||
NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash
|
NSString* urlPath = requestURL ? CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL)) : nil; // Don't use -[NSURL path] which strips the ending slash
|
||||||
|
NSString* requestPath = urlPath ? GCDWebServerUnescapeURLString(urlPath) : nil;
|
||||||
NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
|
NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
|
||||||
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
|
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
|
||||||
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
|
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
|
||||||
@@ -556,7 +319,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
|
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
|
||||||
if (expectHeader) {
|
if (expectHeader) {
|
||||||
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
|
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
|
||||||
[self _writeData:_continueData withCompletionBlock:^(BOOL success) {
|
[self writeData:_continueData
|
||||||
|
withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
if (_request.usesChunkedTransferEncoding) {
|
if (_request.usesChunkedTransferEncoding) {
|
||||||
@@ -601,11 +365,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (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 {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_server = server;
|
_server = server;
|
||||||
_localAddress = localAddress;
|
_localAddressData = localAddress;
|
||||||
_remoteAddress = remoteAddress;
|
_remoteAddressData = remoteAddress;
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
|
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
|
||||||
|
|
||||||
@@ -623,11 +387,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (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);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
@@ -655,12 +419,270 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@implementation GCDWebServerConnection (Read)
|
||||||
|
|
||||||
|
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
|
||||||
|
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
if (error == 0) {
|
||||||
|
size_t size = dispatch_data_get_size(buffer);
|
||||||
|
if (size > 0) {
|
||||||
|
NSUInteger originalLength = data.length;
|
||||||
|
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
|
||||||
|
[data appendBytes:chunkBytes length:chunkSize];
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
|
||||||
|
block(YES);
|
||||||
|
} else {
|
||||||
|
if (_totalBytesRead > 0) {
|
||||||
|
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
|
||||||
|
} else {
|
||||||
|
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
|
||||||
|
}
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
|
||||||
|
GWS_DCHECK(_requestMessage);
|
||||||
|
[self readData:headersData
|
||||||
|
withLength:NSUIntegerMax
|
||||||
|
completionBlock:^(BOOL success) {
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
|
||||||
|
if (range.location == NSNotFound) {
|
||||||
|
[self readHeaders:headersData withCompletionBlock:block];
|
||||||
|
} else {
|
||||||
|
NSUInteger length = range.location + range.length;
|
||||||
|
if (CFHTTPMessageAppendBytes(_requestMessage, headersData.bytes, length)) {
|
||||||
|
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
|
||||||
|
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
||||||
|
block(nil);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
||||||
|
block(nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
block(nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
|
||||||
|
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
|
||||||
|
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
|
||||||
|
[self readData:bodyData
|
||||||
|
withLength:length
|
||||||
|
completionBlock:^(BOOL success) {
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
if (bodyData.length <= length) {
|
||||||
|
NSError* error = nil;
|
||||||
|
if ([_request performWriteData:bodyData error:&error]) {
|
||||||
|
NSUInteger remainingLength = length - bodyData.length;
|
||||||
|
if (remainingLength) {
|
||||||
|
[self readBodyWithRemainingLength:remainingLength completionBlock:block];
|
||||||
|
} else {
|
||||||
|
block(YES);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
||||||
|
block(NO);
|
||||||
|
GWS_DNOT_REACHED();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||||
|
char buffer[size + 1];
|
||||||
|
bcopy(bytes, buffer, size);
|
||||||
|
buffer[size] = 0;
|
||||||
|
char* end = NULL;
|
||||||
|
long result = strtol(buffer, &end, 16);
|
||||||
|
return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
|
||||||
|
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
|
||||||
|
if (range.location == NSNotFound) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions
|
||||||
|
NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
|
||||||
|
if (length != NSNotFound) {
|
||||||
|
if (length) {
|
||||||
|
if (chunkData.length < range.location + range.length + length + 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
|
||||||
|
if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
|
||||||
|
NSError* error = nil;
|
||||||
|
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
|
||||||
|
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||||
|
block(NO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
|
||||||
|
block(NO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers
|
||||||
|
if (trailerRange.location != NSNotFound) {
|
||||||
|
block(YES);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
|
||||||
|
block(NO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[self readData:chunkData
|
||||||
|
withLength:NSUIntegerMax
|
||||||
|
completionBlock:^(BOOL success) {
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
[self readNextBodyChunk:chunkData completionBlock:block];
|
||||||
|
} else {
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation GCDWebServerConnection (Write)
|
||||||
|
|
||||||
|
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
|
||||||
|
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
|
||||||
|
[data self]; // Keeps ARC from releasing data too early
|
||||||
|
});
|
||||||
|
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
if (error == 0) {
|
||||||
|
GWS_DCHECK(remainingData == NULL);
|
||||||
|
[self didWriteBytes:data.bytes length:data.length];
|
||||||
|
block(YES);
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
||||||
|
dispatch_release(buffer);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
|
||||||
|
GWS_DCHECK(_responseMessage);
|
||||||
|
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
|
||||||
|
[self writeData:(__bridge NSData*)data withCompletionBlock:block];
|
||||||
|
CFRelease(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
||||||
|
GWS_DCHECK([_response hasBody]);
|
||||||
|
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
if (data.length) {
|
||||||
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
|
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
||||||
|
size_t hexLength = strlen(hexString);
|
||||||
|
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
||||||
|
if (chunk == nil) {
|
||||||
|
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
||||||
|
block(NO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
|
||||||
|
bcopy(hexString, ptr, hexLength);
|
||||||
|
ptr += hexLength;
|
||||||
|
*ptr++ = '\r';
|
||||||
|
*ptr++ = '\n';
|
||||||
|
bcopy(data.bytes, ptr, data.length);
|
||||||
|
ptr += data.length;
|
||||||
|
*ptr++ = '\r';
|
||||||
|
*ptr = '\n';
|
||||||
|
data = chunk;
|
||||||
|
}
|
||||||
|
[self writeData:data
|
||||||
|
withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
[self writeBodyWithCompletionBlock:block];
|
||||||
|
} else {
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
if (_response.usesChunkedTransferEncoding) {
|
||||||
|
[self writeData:_lastChunkData
|
||||||
|
withCompletionBlock:^(BOOL success) {
|
||||||
|
|
||||||
|
block(success);
|
||||||
|
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
block(YES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
||||||
|
block(NO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerConnection (Subclassing)
|
@implementation GCDWebServerConnection (Subclassing)
|
||||||
|
|
||||||
- (BOOL)open {
|
- (BOOL)open {
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
if (_server.recordingEnabled) {
|
if (_server.recordingEnabled) {
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
|
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
_requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
_requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
|
||||||
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||||
@@ -677,7 +699,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
|
|
||||||
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
|
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
|
||||||
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
|
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||||
_bytesRead += length;
|
_totalBytesRead += length;
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
|
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
|
||||||
@@ -690,7 +712,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
|
|
||||||
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
|
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
|
||||||
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
|
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||||
_bytesWritten += length;
|
_totalBytesWritten += length;
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||||
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
|
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
|
||||||
@@ -707,7 +729,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
|
|
||||||
// https://tools.ietf.org/html/rfc2617
|
// https://tools.ietf.org/html/rfc2617
|
||||||
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
|
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
|
||||||
GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
|
||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
if (_server.authenticationBasicAccounts) {
|
if (_server.authenticationBasicAccounts) {
|
||||||
__block BOOL authenticated = NO;
|
__block BOOL authenticated = NO;
|
||||||
@@ -757,7 +779,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
|
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
|
||||||
GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
|
||||||
_handler.asyncProcessBlock(request, [completion copy]);
|
_handler.asyncProcessBlock(request, [completion copy]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,7 +819,7 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
|||||||
GWS_DCHECK(_responseMessage == NULL);
|
GWS_DCHECK(_responseMessage == NULL);
|
||||||
GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
|
GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
|
||||||
[self _initializeResponseHeadersWithStatusCode:statusCode];
|
[self _initializeResponseHeadersWithStatusCode:statusCode];
|
||||||
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
|
[self writeHeadersWithCompletionBlock:^(BOOL success) {
|
||||||
; // Nothing more to do
|
; // Nothing more to do
|
||||||
}];
|
}];
|
||||||
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
|
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
|
||||||
@@ -837,9 +859,9 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (_request) {
|
if (_request) {
|
||||||
GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
|
||||||
} else {
|
} else {
|
||||||
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,12 +83,18 @@ NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
|
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
|
||||||
|
if (value) {
|
||||||
NSRange range = [value rangeOfString:@";"];
|
NSRange range = [value rangeOfString:@";"];
|
||||||
return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
|
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;
|
||||||
|
if (value) {
|
||||||
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
|
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
|
||||||
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
|
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
|
||||||
NSString* string = [NSString stringWithFormat:@"%@=", name];
|
NSString* string = [NSString stringWithFormat:@"%@=", name];
|
||||||
@@ -100,6 +106,7 @@ NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* nam
|
|||||||
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
[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) {
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -175,9 +192,9 @@ static inline NSError* GCDWebServerMakePosixError(int 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);
|
||||||
@@ -185,15 +202,15 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
|
|||||||
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,25 +195,25 @@ 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];
|
||||||
@@ -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) {
|
||||||
@@ -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])) {
|
||||||
|
|||||||
@@ -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,50 +42,28 @@ 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;
|
||||||
|
|
||||||
@@ -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,8 +101,7 @@ static NSData* _dashNewlineData = nil;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMIMEStreamParser () {
|
@implementation GCDWebServerMIMEStreamParser {
|
||||||
@private
|
|
||||||
NSData* _boundary;
|
NSData* _boundary;
|
||||||
NSString* _defaultcontrolName;
|
NSString* _defaultcontrolName;
|
||||||
ParserState _state;
|
ParserState _state;
|
||||||
@@ -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();
|
||||||
@@ -198,7 +163,6 @@ static NSData* _dashNewlineData = nil;
|
|||||||
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;
|
||||||
@@ -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;
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
@@ -53,8 +45,6 @@
|
|||||||
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,25 +31,18 @@
|
|||||||
|
|
||||||
#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;
|
||||||
|
|
||||||
@@ -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.
|
||||||
@@ -48,13 +50,13 @@
|
|||||||
* 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.
|
||||||
@@ -70,12 +72,14 @@
|
|||||||
* 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();
|
||||||
@@ -131,7 +129,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,7 +46,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (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;
|
NSError* error = nil;
|
||||||
NSData* data = block(&error);
|
NSData* data = block(&error);
|
||||||
|
|||||||
|
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,257 +46,30 @@
|
|||||||
#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
|
@end
|
||||||
|
|
||||||
@implementation GCDWebUploader (Methods)
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
// Must match implementation in GCDWebDAVServer
|
|
||||||
- (BOOL)_checkSandboxedPath:(NSString*)path {
|
|
||||||
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
|
||||||
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*) _uniquePathForPath:(NSString*)path {
|
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
||||||
NSString* directory = [path stringByDeletingLastPathComponent];
|
|
||||||
NSString* file = [path lastPathComponent];
|
|
||||||
NSString* base = [file stringByDeletingPathExtension];
|
|
||||||
NSString* extension = [file pathExtension];
|
|
||||||
int retries = 0;
|
|
||||||
do {
|
|
||||||
if (extension.length) {
|
|
||||||
path = [directory stringByAppendingPathComponent:[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
|
|
||||||
} else {
|
|
||||||
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
|
|
||||||
}
|
|
||||||
} while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
|
|
||||||
NSString* relativePath = [[request query] objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
if (!isDirectory) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* directoryName = [absolutePath lastPathComponent];
|
|
||||||
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
|
|
||||||
if (contents == nil) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSMutableArray* array = [NSMutableArray array];
|
|
||||||
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
|
|
||||||
if (_allowHidden || ![item hasPrefix:@"."]) {
|
|
||||||
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
|
|
||||||
NSString* type = [attributes objectForKey:NSFileType];
|
|
||||||
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
|
|
||||||
[array addObject:@{
|
|
||||||
@"path": [relativePath stringByAppendingPathComponent:item],
|
|
||||||
@"name": item,
|
|
||||||
@"size": [attributes objectForKey:NSFileSize]
|
|
||||||
}];
|
|
||||||
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
|
||||||
[array addObject:@{
|
|
||||||
@"path": [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
|
|
||||||
@"name": item
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:array];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
|
|
||||||
NSString* relativePath = [[request query] objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
if (isDirectory) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* fileName = [absolutePath lastPathComponent];
|
|
||||||
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath: )]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
|
|
||||||
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)
|
|
||||||
|
|
||||||
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
|
|
||||||
if ((!_allowHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
|
|
||||||
}
|
|
||||||
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
|
|
||||||
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
|
|
||||||
if (![self _checkSandboxedPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didUploadFileAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
|
|
||||||
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
|
|
||||||
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
|
|
||||||
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
|
|
||||||
if (![self _checkSandboxedPath:newAbsolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* itemName = [newAbsolutePath lastPathComponent];
|
|
||||||
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
|
|
||||||
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
|
||||||
BOOL isDirectory = NO;
|
|
||||||
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* itemName = [absolutePath lastPathComponent];
|
|
||||||
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldDeleteItemAtPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
|
|
||||||
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
|
||||||
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
|
|
||||||
if (![self _checkSandboxedPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* directoryName = [absolutePath lastPathComponent];
|
|
||||||
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![self shouldCreateDirectoryAtPath:absolutePath]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
|
|
||||||
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebUploader
|
@implementation GCDWebUploader
|
||||||
|
|
||||||
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden,
|
|
||||||
title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer;
|
|
||||||
|
|
||||||
@dynamic delegate;
|
@dynamic delegate;
|
||||||
|
|
||||||
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
- (instancetype)initWithUploadDirectory:(NSString*)path {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
|
NSString* bundlePath = [[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"];
|
||||||
|
if (bundlePath == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSBundle* siteBundle = [NSBundle bundleWithPath:bundlePath];
|
||||||
if (siteBundle == nil) {
|
if (siteBundle == nil) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@@ -304,10 +77,13 @@
|
|||||||
GCDWebUploader* __unsafe_unretained server = self;
|
GCDWebUploader* __unsafe_unretained server = self;
|
||||||
|
|
||||||
// Resource files
|
// Resource files
|
||||||
[self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
|
[self addGETHandlerForBasePath:@"/" directoryPath:(NSString*)[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
|
||||||
|
|
||||||
// Web page
|
// Web page
|
||||||
[self addHandlerForMethod:@"GET" path:@"/" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:@"/"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
NSString* device = [[UIDevice currentDevice] name];
|
NSString* device = [[UIDevice currentDevice] name];
|
||||||
@@ -350,7 +126,7 @@
|
|||||||
#endif
|
#endif
|
||||||
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
|
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
|
||||||
}
|
}
|
||||||
return [GCDWebServerDataResponse responseWithHTMLTemplate:[siteBundle pathForResource:@"index" ofType:@"html"]
|
return [GCDWebServerDataResponse responseWithHTMLTemplate:(NSString*)[siteBundle pathForResource:@"index" ofType:@"html"]
|
||||||
variables:@{
|
variables:@{
|
||||||
@"device" : device,
|
@"device" : device,
|
||||||
@"title" : title,
|
@"title" : title,
|
||||||
@@ -363,41 +139,286 @@
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
// File listing
|
// File listing
|
||||||
[self addHandlerForMethod:@"GET" path:@"/list" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:@"/list"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server listDirectory:request];
|
return [server listDirectory:request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File download
|
// File download
|
||||||
[self addHandlerForMethod:@"GET" path:@"/download" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"GET"
|
||||||
|
path:@"/download"
|
||||||
|
requestClass:[GCDWebServerRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server downloadFile:request];
|
return [server downloadFile:request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File upload
|
// File upload
|
||||||
[self addHandlerForMethod:@"POST" path:@"/upload" requestClass:[GCDWebServerMultiPartFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/upload"
|
||||||
|
requestClass:[GCDWebServerMultiPartFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
|
return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File and folder moving
|
// File and folder moving
|
||||||
[self addHandlerForMethod:@"POST" path:@"/move" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/move"
|
||||||
|
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
|
return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// File and folder deletion
|
// File and folder deletion
|
||||||
[self addHandlerForMethod:@"POST" path:@"/delete" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/delete"
|
||||||
|
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
|
return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// Directory creation
|
// Directory creation
|
||||||
[self addHandlerForMethod:@"POST" path:@"/create" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
[self addHandlerForMethod:@"POST"
|
||||||
|
path:@"/create"
|
||||||
|
requestClass:[GCDWebServerURLEncodedFormRequest class]
|
||||||
|
processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
|
||||||
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
|
return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@implementation GCDWebUploader (Methods)
|
||||||
|
|
||||||
|
// Must match implementation in GCDWebDAVServer
|
||||||
|
- (BOOL)_checkSandboxedPath:(NSString*)path {
|
||||||
|
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)_checkFileExtension:(NSString*)fileName {
|
||||||
|
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString*)_uniquePathForPath:(NSString*)path {
|
||||||
|
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||||
|
NSString* directory = [path stringByDeletingLastPathComponent];
|
||||||
|
NSString* file = [path lastPathComponent];
|
||||||
|
NSString* base = [file stringByDeletingPathExtension];
|
||||||
|
NSString* extension = [file pathExtension];
|
||||||
|
int retries = 0;
|
||||||
|
do {
|
||||||
|
if (extension.length) {
|
||||||
|
path = [directory stringByAppendingPathComponent:(NSString*)[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
|
||||||
|
} else {
|
||||||
|
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
|
||||||
|
}
|
||||||
|
} while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
|
||||||
|
NSString* relativePath = [[request query] objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
if (!isDirectory) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* directoryName = [absolutePath lastPathComponent];
|
||||||
|
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
|
||||||
|
if (contents == nil) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableArray* array = [NSMutableArray array];
|
||||||
|
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
|
||||||
|
if (_allowHiddenItems || ![item hasPrefix:@"."]) {
|
||||||
|
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
|
||||||
|
NSString* type = [attributes objectForKey:NSFileType];
|
||||||
|
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
|
||||||
|
[array addObject:@{
|
||||||
|
@"path" : [relativePath stringByAppendingPathComponent:item],
|
||||||
|
@"name" : item,
|
||||||
|
@"size" : [attributes objectForKey:NSFileSize]
|
||||||
|
}];
|
||||||
|
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
||||||
|
[array addObject:@{
|
||||||
|
@"path" : [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
|
||||||
|
@"name" : item
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:array];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
|
||||||
|
NSString* relativePath = [[request query] objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
if (isDirectory) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* fileName = [absolutePath lastPathComponent];
|
||||||
|
if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
|
||||||
|
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)
|
||||||
|
|
||||||
|
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
|
||||||
|
if ((!_allowHiddenItems && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
|
||||||
|
}
|
||||||
|
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
|
||||||
|
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
|
||||||
|
if (![self _checkSandboxedPath:absolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didUploadFileAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
|
||||||
|
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
|
||||||
|
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
|
||||||
|
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
|
||||||
|
if (![self _checkSandboxedPath:newAbsolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* itemName = [newAbsolutePath lastPathComponent];
|
||||||
|
if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
|
||||||
|
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
|
||||||
|
BOOL isDirectory = NO;
|
||||||
|
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* itemName = [absolutePath lastPathComponent];
|
||||||
|
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self shouldDeleteItemAtPath:absolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
|
||||||
|
NSString* relativePath = [request.arguments objectForKey:@"path"];
|
||||||
|
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
|
||||||
|
if (![self _checkSandboxedPath:absolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* directoryName = [absolutePath lastPathComponent];
|
||||||
|
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self shouldCreateDirectoryAtPath:absolutePath]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
|
||||||
|
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GCDWebUploader (Subclassing)
|
@implementation GCDWebUploader (Subclassing)
|
||||||
|
|
||||||
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
|
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
|
||||||
|
|||||||
13
Mac/main.m
13
Mac/main.m
@@ -204,7 +204,6 @@ 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]);
|
||||||
@@ -326,7 +325,8 @@ int main(int argc, const char* argv[]) {
|
|||||||
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);
|
usleep(100 * 1000);
|
||||||
if (countDown) {
|
if (countDown) {
|
||||||
@@ -344,7 +344,8 @@ int main(int argc, const char* argv[]) {
|
|||||||
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(), ^{
|
||||||
|
|
||||||
@@ -369,7 +370,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -382,7 +383,8 @@ int main(int argc, const char* argv[]) {
|
|||||||
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(), ^{
|
||||||
|
|
||||||
@@ -399,7 +401,6 @@ int main(int argc, const char* argv[]) {
|
|||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webServer) {
|
if (webServer) {
|
||||||
|
|||||||
@@ -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