Modernize ObjC

This commit is contained in:
Pierre-Olivier Latour
2017-06-22 10:59:52 -07:00
parent 23f77b5765
commit 09851dd71b
33 changed files with 1386 additions and 1447 deletions
+5 -1
View File
@@ -27,6 +27,8 @@
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebDAVServer;
/**
@@ -86,7 +88,7 @@
/**
* 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.
@@ -154,3 +156,5 @@
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end
NS_ASSUME_NONNULL_END
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1237,7 +1237,6 @@
"-Wno-cstring-format-directive",
"-Wno-reserved-id-macro",
"-Wno-cast-qual",
"-Wno-nullable-to-nonnull-conversion",
"-Wno-partial-availability",
);
};
+20 -16
View File
@@ -30,6 +30,8 @@
#import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerMatchBlock is called for every handler added to the
* 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.
* 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
@@ -52,7 +54,7 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
* recommended to return a GCDWebServerErrorResponse on error so more useful
* 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
@@ -64,7 +66,7 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(__kindof GCDWebServerR
* It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client.
*/
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response);
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
/**
@@ -295,7 +297,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* 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.
@@ -315,7 +317,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds.
*/
@property(nonatomic, readonly) NSString* bonjourName;
@property(nonatomic, readonly, nullable) NSString* bonjourName;
/**
* 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
* 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.
@@ -363,7 +365,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* Returns NO if the server failed to start and sets "error" argument if not NULL.
*/
- (BOOL)startWithOptions:(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.
@@ -383,7 +385,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* @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.
@@ -393,7 +395,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* Also be aware this property will not automatically update if the Bonjour hostname
* has been dynamically changed after the server started running (this should be rare).
*/
@property(nonatomic, readonly) NSURL* bonjourServerURL;
@property(nonatomic, readonly, nullable) NSURL* bonjourServerURL;
/**
* 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
* 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)
@@ -418,7 +420,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* 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
@@ -431,7 +433,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* @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
@@ -442,7 +444,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* @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
@@ -498,7 +500,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* 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
@@ -515,7 +517,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* The "indexFilename" argument allows to specify an "index" file name to use
* 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
@@ -612,8 +614,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*
* 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
#endif
NS_ASSUME_NONNULL_END
+6 -29
View File
@@ -132,18 +132,9 @@ static void _ExecuteMainThreadRunLoopSources() {
#endif
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerAsyncProcessBlock _asyncProcessBlock;
}
@end
@implementation GCDWebServerHandler
@synthesize matchBlock = _matchBlock, asyncProcessBlock = _asyncProcessBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
- (instancetype)initWithMatchBlock:(GCDWebServerMatchBlock _Nonnull)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock _Nonnull)processBlock {
if ((self = [super init])) {
_matchBlock = [matchBlock copy];
_asyncProcessBlock = [processBlock copy];
@@ -153,9 +144,7 @@ static void _ExecuteMainThreadRunLoopSources() {
@end
@interface GCDWebServer () {
@private
id<GCDWebServerDelegate> __unsafe_unretained _delegate;
@implementation GCDWebServer {
dispatch_queue_t _syncQueue;
dispatch_group_t _sourceGroup;
NSMutableArray* _handlers;
@@ -164,15 +153,10 @@ static void _ExecuteMainThreadRunLoopSources() {
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
NSDictionary* _options;
NSString* _serverName;
NSString* _authenticationRealm;
NSMutableDictionary* _authenticationBasicAccounts;
NSMutableDictionary* _authenticationDigestAccounts;
Class _connectionClass;
BOOL _mapHEADToGET;
CFTimeInterval _disconnectDelay;
dispatch_queue_priority_t _dispatchQueuePriority;
NSUInteger _port;
dispatch_source_t _source4;
dispatch_source_t _source6;
CFNetServiceRef _registrationService;
@@ -191,13 +175,6 @@ static void _ExecuteMainThreadRunLoopSources() {
BOOL _recording;
#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 {
GCDWebServerInitializeFunctions();
@@ -600,7 +577,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
}];
}
_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];
_dispatchQueuePriority = [_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];
@@ -1294,9 +1271,9 @@ static void _LogResult(NSString* format, ...) {
success = NO;
#if !TARGET_OS_IPHONE
#if DEBUG
if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
if (GCDWebServerIsTextContentType((NSString*)[expectedHeaders objectForKey:@"Content-Type"])) {
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[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]) {
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/opendiff"];
+6 -2
View File
@@ -27,6 +27,8 @@
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebServerHandler;
/**
@@ -139,7 +141,7 @@
* The default implementation checks for HTTP authentication if applicable
* 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,
@@ -169,7 +171,7 @@
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
* 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.
@@ -177,3 +179,5 @@
- (void)close;
@end
NS_ASSUME_NONNULL_END
+313 -306
View File
@@ -57,14 +57,25 @@ static NSString* _digestAuthenticationNonce = nil;
static int32_t _connectionCounter = 0;
#endif
@interface GCDWebServerConnection () {
@private
GCDWebServer* _server;
NSData* _localAddress;
NSData* _remoteAddress;
NS_ASSUME_NONNULL_BEGIN
@interface GCDWebServerConnection (Read)
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block;
- (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;
NSUInteger _bytesRead;
NSUInteger _bytesWritten;
BOOL _virtualHEAD;
CFHTTPMessageRef _requestMessage;
@@ -83,266 +94,6 @@ static int32_t _connectionCounter = 0;
int _responseFD;
#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 {
if (_CRLFData == nil) {
@@ -370,7 +121,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
}
- (BOOL)isUsingIPv6 {
const struct sockaddr* localSockAddr = _localAddress.bytes;
const struct sockaddr* localSockAddr = _localAddressData.bytes;
return (localSockAddr->sa_family == AF_INET6);
}
@@ -420,7 +171,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
if (_response) {
[self _initializeResponseHeadersWithStatusCode:_response.statusCode];
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) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
@@ -444,11 +195,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
}];
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
[self writeHeadersWithCompletionBlock:^(BOOL success) {
if (success) {
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
@@ -485,18 +236,18 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
}
if (length) {
[self _readBodyWithRemainingLength:length
completionBlock:^(BOOL success) {
[self readBodyWithRemainingLength:length
completionBlock:^(BOOL success) {
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
}];
} else {
if ([_request performClose:&error]) {
[self _startProcessingRequest];
@@ -516,24 +267,24 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
}
NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
[self _readNextBodyChunk:chunkData
completionBlock:^(BOOL success) {
[self readNextBodyChunk:chunkData
completionBlock:^(BOOL success) {
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _startProcessingRequest];
} else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
}
}];
}];
}
- (void)_readRequestHeaders {
_requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
[self _readHeaders:headersData
[self readHeaders:headersData
withCompletionBlock:^(NSData* extraData) {
if (extraData) {
@@ -548,7 +299,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
GWS_DCHECK(requestURL);
}
NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash
NSString* 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;
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
@@ -567,7 +319,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
if (expectHeader) {
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
[self _writeData:_continueData
[self writeData:_continueData
withCompletionBlock:^(BOOL success) {
if (success) {
@@ -613,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])) {
_server = server;
_localAddress = localAddress;
_remoteAddress = remoteAddress;
_localAddressData = localAddress;
_remoteAddressData = remoteAddress;
_socket = socket;
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
@@ -635,11 +387,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
}
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
}
- (void)dealloc {
@@ -667,6 +419,261 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
@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)
- (BOOL)open {
@@ -692,7 +699,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
_bytesRead += length;
_totalBytesRead += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
@@ -705,7 +712,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
_bytesWritten += length;
_totalBytesWritten += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
@@ -722,7 +729,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
// https://tools.ietf.org/html/rfc2617
- (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;
if (_server.authenticationBasicAccounts) {
__block BOOL authenticated = NO;
@@ -772,7 +779,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
}
- (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]);
}
@@ -812,7 +819,7 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
GWS_DCHECK(_responseMessage == NULL);
GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
[self _initializeResponseHeadersWithStatusCode:statusCode];
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
[self writeHeadersWithCompletionBlock:^(BOOL success) {
; // Nothing more to do
}];
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
@@ -852,9 +859,9 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
#endif
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 {
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);
}
}
+9 -5
View File
@@ -27,6 +27,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifdef __cplusplus
extern "C" {
#endif
@@ -42,12 +44,12 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
* The legal characters ":@/?&=+" are also escaped to ensure compatibility
* with URL encoded forms and URL queries.
*/
NSString* GCDWebServerEscapeURLString(NSString* string);
NSString* _Nullable GCDWebServerEscapeURLString(NSString* string);
/**
* Unescapes a URL percent-encoded string.
*/
NSString* GCDWebServerUnescapeURLString(NSString* string);
NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string);
/**
* Extracts the unescaped names and values from an
@@ -63,7 +65,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
* interface if connected or nil otherwise.
*/
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
/**
* Converts a date into a string using RFC822 formatting.
@@ -79,7 +81,7 @@ NSString* GCDWebServerFormatRFC822(NSDate* date);
*
* @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.
@@ -94,8 +96,10 @@ NSString* GCDWebServerFormatISO8601(NSDate* date);
* @warning Only "calendar" variant is supported at this time and timezones
* other than GMT are not supported either.
*/
NSDate* GCDWebServerParseISO8601(NSString* string);
NSDate* _Nullable GCDWebServerParseISO8601(NSString* string);
#ifdef __cplusplus
}
#endif
NS_ASSUME_NONNULL_END
+29 -18
View File
@@ -83,21 +83,28 @@ NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
}
NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
NSRange range = [value rangeOfString:@";"];
return range.location != NSNotFound ? [value substringToIndex:range.location] : value;
if (value) {
NSRange range = [value rangeOfString:@";"];
if (range.location != NSNotFound) {
return [value substringToIndex:range.location];
}
}
return value;
}
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
NSString* parameter = nil;
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
NSString* string = [NSString stringWithFormat:@"%@=", name];
if ([scanner scanUpToString:string intoString:NULL]) {
[scanner scanString:string intoString:NULL];
if ([scanner scanString:@"\"" intoString:NULL]) {
[scanner scanUpToString:@"\"" intoString:&parameter];
} else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
if (value) {
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
NSString* string = [NSString stringWithFormat:@"%@=", name];
if ([scanner scanUpToString:string intoString:NULL]) {
[scanner scanString:string intoString:NULL];
if ([scanner scanString:@"\"" intoString:NULL]) {
[scanner scanUpToString:@"\"" intoString:&parameter];
} else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
}
}
}
return parameter;
@@ -232,15 +239,16 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
}
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
NSString* string = nil;
char hostBuffer[NI_MAXHOST];
char serviceBuffer[NI_MAXSERV];
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer];
} else {
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) {
#if DEBUG
GWS_DNOT_REACHED();
#else
return @"";
#endif
}
return string;
return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer];
}
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
@@ -255,7 +263,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
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
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(store);
@@ -303,5 +314,5 @@ NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
}
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
return [NSString stringWithUTF8String:buffer];
return (NSString*)[NSString stringWithUTF8String:buffer];
}
+14 -10
View File
@@ -48,6 +48,8 @@
#import "GCDWebServerFileResponse.h"
#import "GCDWebServerStreamedResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Check if a custom logging facility should be used instead.
*/
@@ -190,9 +192,9 @@ static inline NSError* GCDWebServerMakePosixError(int code) {
}
extern void GCDWebServerInitializeFunctions();
extern NSString* GCDWebServerNormalizeHeaderValue(NSString* value);
extern NSString* GCDWebServerTruncateHeaderValue(NSString* value);
extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute);
extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
extern BOOL GCDWebServerIsTextContentType(NSString* type);
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
@@ -200,15 +202,15 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
@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
@interface GCDWebServer ()
@property(nonatomic, readonly) NSArray* handlers;
@property(nonatomic, readonly) NSMutableArray* handlers;
@property(nonatomic, readonly) NSString* serverName;
@property(nonatomic, readonly) NSString* authenticationRealm;
@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts;
@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts;
@property(nonatomic, readonly) NSMutableDictionary* authenticationBasicAccounts;
@property(nonatomic, readonly) NSMutableDictionary* authenticationDigestAccounts;
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
- (void)willStartConnection:(GCDWebServerConnection*)connection;
@@ -222,13 +224,13 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
@interface GCDWebServerRequest ()
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
@property(nonatomic, readwrite) NSData* localAddressData;
@property(nonatomic, readwrite) NSData* remoteAddressData;
@property(nonatomic) NSData* localAddressData;
@property(nonatomic) NSData* remoteAddressData;
- (void)prepareForWriting;
- (BOOL)performOpen:(NSError**)error;
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
- (BOOL)performClose:(NSError**)error;
- (void)setAttribute:(id)attribute forKey:(NSString*)key;
- (void)setAttribute:(nullable id)attribute forKey:(NSString*)key;
@end
@interface GCDWebServerResponse ()
@@ -239,3 +241,5 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
- (void)performClose;
@end
NS_ASSUME_NONNULL_END
+10 -6
View File
@@ -27,6 +27,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
* 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.
*/
@property(nonatomic, readonly) NSDictionary* query;
@property(nonatomic, readonly, nullable) NSDictionary* query;
/**
* 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
* "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
@@ -137,12 +139,12 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
/**
* 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.
*/
@property(nonatomic, readonly) NSString* ifNoneMatch;
@property(nonatomic, readonly, nullable) NSString* ifNoneMatch;
/**
* 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.
*/
- (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.
@@ -201,6 +203,8 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
*
* @return The attribute value for the key.
*/
- (id)attributeForKey:(NSString*)key;
- (nullable id)attributeForKey:(NSString*)key;
@end
NS_ASSUME_NONNULL_END
+37 -68
View File
@@ -39,22 +39,17 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer;
@end
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
@end
@interface GCDWebServerBodyDecoder () {
@private
@implementation GCDWebServerBodyDecoder {
GCDWebServerRequest* __unsafe_unretained _request;
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
}
@end
@implementation GCDWebServerBodyDecoder
- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer {
- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id<GCDWebServerBodyWriter> _Nonnull)writer {
if ((self = [super init])) {
_request = request;
_writer = writer;
@@ -76,14 +71,10 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
@end
@interface GCDWebServerGZipDecoder () {
@private
@implementation GCDWebServerGZipDecoder {
z_stream _stream;
BOOL _finished;
}
@end
@implementation GCDWebServerGZipDecoder
- (BOOL)open:(NSError**)error {
int result = inflateInit2(&_stream, 15 + 16);
@@ -143,77 +134,55 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
@end
@interface 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;
@implementation GCDWebServerRequest {
BOOL _opened;
NSMutableArray* _decoders;
NSMutableDictionary* _attributes;
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 {
if ((self = [super init])) {
_method = [method copy];
_url = url;
_URL = url;
_headers = headers;
_path = [path copy];
_query = query;
_type = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
_chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
_contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
_usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
if (lengthHeader) {
NSInteger length = [lengthHeader integerValue];
if (_chunked || (length < 0)) {
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _url);
if (_usesChunkedTransferEncoding || (length < 0)) {
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL);
GWS_DNOT_REACHED();
return nil;
}
_length = length;
if (_type == nil) {
_type = kGCDWebServerDefaultMimeType;
_contentLength = length;
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
} else if (_chunked) {
if (_type == nil) {
_type = kGCDWebServerDefaultMimeType;
} else if (_usesChunkedTransferEncoding) {
if (_contentType == nil) {
_contentType = kGCDWebServerDefaultMimeType;
}
_length = NSUIntegerMax;
_contentLength = NSUIntegerMax;
} else {
if (_type) {
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
if (_contentType) {
GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL);
_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"];
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"]);
if (rangeHeader) {
if ([rangeHeader hasPrefix:@"bytes="]) {
@@ -226,25 +195,25 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
NSString* endString = [components objectAtIndex:1];
NSInteger endValue = [endString integerValue];
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
_range.location = startValue;
_range.length = endValue - startValue + 1;
_byteRange.location = startValue;
_byteRange.length = endValue - startValue + 1;
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
_range.location = startValue;
_range.length = NSUIntegerMax;
_byteRange.location = startValue;
_byteRange.length = NSUIntegerMax;
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
_range.location = NSUIntegerMax;
_range.length = endValue;
_byteRange.location = NSUIntegerMax;
_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);
}
}
if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
_gzipAccepted = YES;
_acceptsGzipContentEncoding = YES;
}
_decoders = [[NSMutableArray alloc] init];
@@ -254,11 +223,11 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
}
- (BOOL)hasBody {
return _type ? YES : NO;
return _contentType ? YES : NO;
}
- (BOOL)hasByteRange {
return GCDWebServerIsValidByteRange(_range);
return GCDWebServerIsValidByteRange(_byteRange);
}
- (id)attributeForKey:(NSString*)key {
@@ -287,7 +256,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_type);
GWS_DCHECK(_contentType);
GWS_DCHECK(_writer);
if (_opened) {
GWS_DNOT_REACHED();
@@ -312,11 +281,11 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
}
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
}
- (NSString*)description {
+10 -6
View File
@@ -27,11 +27,13 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
* 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
@@ -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
* 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.
@@ -102,7 +104,7 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
*
* @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
@@ -136,14 +138,14 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
*
* 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.
*
* The default value is nil.
*/
@property(nonatomic, copy) NSString* eTag;
@property(nonatomic, copy, nullable) NSString* eTag;
/**
* 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
* 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.
@@ -206,3 +208,5 @@ typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* err
- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
@end
NS_ASSUME_NONNULL_END
+26 -52
View File
@@ -37,22 +37,17 @@
#define kGZipInitialBufferSize (256 * 1024)
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
@end
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
@end
@interface GCDWebServerBodyEncoder () {
@private
@implementation GCDWebServerBodyEncoder {
GCDWebServerResponse* __unsafe_unretained _response;
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
}
@end
@implementation GCDWebServerBodyEncoder
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
if ((self = [super init])) {
_response = response;
_reader = reader;
@@ -74,16 +69,12 @@
@end
@interface GCDWebServerGZipEncoder () {
@private
@implementation GCDWebServerGZipEncoder {
z_stream _stream;
BOOL _finished;
}
@end
@implementation GCDWebServerGZipEncoder
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)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 setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
@@ -157,28 +148,11 @@
@end
@interface GCDWebServerResponse () {
@private
NSString* _type;
NSUInteger _length;
NSInteger _status;
NSUInteger _maxAge;
NSDate* _lastModified;
NSString* _eTag;
NSMutableDictionary* _headers;
BOOL _chunked;
BOOL _gzipped;
@implementation GCDWebServerResponse {
BOOL _opened;
NSMutableArray* _encoders;
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 {
return [[[self class] alloc] init];
@@ -186,26 +160,26 @@
- (instancetype)init {
if ((self = [super init])) {
_type = nil;
_length = NSUIntegerMax;
_status = kGCDWebServerHTTPStatusCode_OK;
_maxAge = 0;
_headers = [[NSMutableDictionary alloc] init];
_contentType = nil;
_contentLength = NSUIntegerMax;
_statusCode = kGCDWebServerHTTPStatusCode_OK;
_cacheControlMaxAge = 0;
_additionalHeaders = [[NSMutableDictionary alloc] init];
_encoders = [[NSMutableArray alloc] init];
}
return self;
}
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
[_headers setValue:value forKey:header];
[_additionalHeaders setValue:value forKey:header];
}
- (BOOL)hasBody {
return _type ? YES : NO;
return _contentType ? YES : NO;
}
- (BOOL)usesChunkedTransferEncoding {
return (_type != nil) && (_length == NSUIntegerMax);
return (_contentType != nil) && (_contentLength == NSUIntegerMax);
}
- (BOOL)open:(NSError**)error {
@@ -222,7 +196,7 @@
- (void)prepareForReading {
_reader = self;
if (_gzipped) {
if (_gzipContentEncodingEnabled) {
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
[_encoders addObject:encoder];
_reader = encoder;
@@ -230,7 +204,7 @@
}
- (BOOL)performOpen:(NSError**)error {
GWS_DCHECK(_type);
GWS_DCHECK(_contentType);
GWS_DCHECK(_reader);
if (_opened) {
GWS_DNOT_REACHED();
@@ -257,24 +231,24 @@
}
- (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_status];
if (_type) {
[description appendFormat:@"\nContent Type = %@", _type];
NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode];
if (_contentType) {
[description appendFormat:@"\nContent Type = %@", _contentType];
}
if (_length != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_length];
if (_contentLength != NSUIntegerMax) {
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength];
}
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge];
if (_lastModified) {
[description appendFormat:@"\nLast Modified Date = %@", _lastModified];
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge];
if (_lastModifiedDate) {
[description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate];
}
if (_eTag) {
[description appendFormat:@"\nETag = %@", _eTag];
}
if (_headers.count) {
if (_additionalHeaders.count) {
[description appendString:@"\n"];
for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]];
for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
[description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]];
}
}
return description;
@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request in memory.
@@ -49,12 +51,14 @@
* The text encoding used to interpret the data is extracted from the
* "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
* 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
NS_ASSUME_NONNULL_END
@@ -31,18 +31,14 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerDataRequest () {
@private
NSMutableData* _data;
@interface GCDWebServerDataRequest ()
@property(nonatomic) NSMutableData* data;
@end
@implementation GCDWebServerDataRequest {
NSString* _text;
id _jsonObject;
}
@end
@implementation GCDWebServerDataRequest
@synthesize data = _data;
- (BOOL)open:(NSError**)error {
if (self.contentLength != NSUIntegerMax) {
@@ -72,7 +68,7 @@
NSMutableString* description = [NSMutableString stringWithString:[super description]];
if (_data) {
[description appendString:@"\n\n"];
[description appendString:GCDWebServerDescribeData(_data, self.contentType)];
[description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)];
}
return description;
}
@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
* of the HTTP request to a file on disk.
@@ -43,3 +45,5 @@
@property(nonatomic, readonly) NSString* temporaryPath;
@end
NS_ASSUME_NONNULL_END
@@ -31,16 +31,9 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerFileRequest () {
@private
NSString* _temporaryPath;
@implementation GCDWebServerFileRequest {
int _file;
}
@end
@implementation GCDWebServerFileRequest
@synthesize temporaryPath = _temporaryPath;
- (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])) {
@@ -27,6 +27,8 @@
#import "GCDWebServerRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerMultiPart class is an abstract class that wraps the content
* of a part.
@@ -69,7 +71,7 @@
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly) NSString* string;
@property(nonatomic, readonly, nullable) NSString* string;
@end
@@ -122,11 +124,13 @@
/**
* 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.
*/
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
@end
NS_ASSUME_NONNULL_END
@@ -42,50 +42,28 @@ typedef enum {
} ParserState;
@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
static NSData* _newlineData = nil;
static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil;
@interface GCDWebServerMultiPart () {
@private
NSString* _controlName;
NSString* _contentType;
NSString* _mimeType;
}
@end
@implementation GCDWebServerMultiPart
@synthesize controlName = _controlName, contentType = _contentType, mimeType = _mimeType;
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type {
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
if ((self = [super init])) {
_controlName = [name copy];
_contentType = [type copy];
_mimeType = GCDWebServerTruncateHeaderValue(_contentType);
_mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
}
return self;
}
@end
@interface GCDWebServerMultiPartArgument () {
@private
NSData* _data;
NSString* _string;
}
@end
@implementation GCDWebServerMultiPartArgument
@synthesize data = _data, string = _string;
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data {
if ((self = [super initWithControlName:name contentType:type])) {
_data = data;
@@ -103,18 +81,9 @@ static NSData* _dashNewlineData = nil;
@end
@interface GCDWebServerMultiPartFile () {
@private
NSString* _fileName;
NSString* _temporaryPath;
}
@end
@implementation GCDWebServerMultiPartFile
@synthesize fileName = _fileName, temporaryPath = _temporaryPath;
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath {
if ((self = [super initWithControlName:name contentType:type])) {
_fileName = [fileName copy];
_temporaryPath = [temporaryPath copy];
@@ -132,8 +101,7 @@ static NSData* _dashNewlineData = nil;
@end
@interface GCDWebServerMIMEStreamParser () {
@private
@implementation GCDWebServerMIMEStreamParser {
NSData* _boundary;
NSString* _defaultcontrolName;
ParserState _state;
@@ -148,9 +116,6 @@ static NSData* _dashNewlineData = nil;
int _tmpFile;
GCDWebServerMIMEStreamParser* _subParser;
}
@end
@implementation GCDWebServerMIMEStreamParser
+ (void)initialize {
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;
if (data == nil) {
GWS_DNOT_REACHED();
@@ -346,17 +311,14 @@ static NSData* _dashNewlineData = nil;
@end
@interface GCDWebServerMultiPartFormRequest () {
@private
GCDWebServerMIMEStreamParser* _parser;
NSMutableArray* _arguments;
NSMutableArray* _files;
}
@interface GCDWebServerMultiPartFormRequest ()
@property(nonatomic) NSMutableArray* arguments;
@property(nonatomic) NSMutableArray* files;
@end
@implementation GCDWebServerMultiPartFormRequest
@synthesize arguments = _arguments, files = _files;
@implementation GCDWebServerMultiPartFormRequest {
GCDWebServerMIMEStreamParser* _parser;
}
+ (NSString*)mimeType {
return @"multipart/form-data";
@@ -27,6 +27,8 @@
#import "GCDWebServerDataRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest
* parses the body of the HTTP request as a URL encoded form using
@@ -49,3 +51,5 @@
+ (NSString*)mimeType;
@end
NS_ASSUME_NONNULL_END
@@ -31,16 +31,8 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerURLEncodedFormRequest () {
@private
NSDictionary* _arguments;
}
@end
@implementation GCDWebServerURLEncodedFormRequest
@synthesize arguments = _arguments;
+ (NSString*)mimeType {
return @"application/x-www-form-urlencoded";
}
@@ -53,8 +45,6 @@
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
_arguments = GCDWebServerParseURLEncodedForm(string);
GWS_DCHECK(_arguments);
return YES;
}
@@ -27,11 +27,14 @@
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from memory.
*/
@interface GCDWebServerDataResponse : GCDWebServerResponse
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
/**
* 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.
*/
+ (instancetype)responseWithText:(NSString*)text;
+ (nullable instancetype)responseWithText:(NSString*)text;
/**
* 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.
* 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
* "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
* 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.
*/
- (instancetype)initWithText:(NSString*)text;
- (nullable instancetype)initWithText:(NSString*)text;
/**
* 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.
@@ -91,18 +94,20 @@
* All occurences of "%variable%" within the HTML template are replaced with
* 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
* "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
* content type.
*/
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
- (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
@end
NS_ASSUME_NONNULL_END
@@ -31,25 +31,18 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerDataResponse () {
@private
@implementation GCDWebServerDataResponse {
NSData* _data;
BOOL _done;
}
@end
@implementation GCDWebServerDataResponse
@dynamic contentType;
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
return [[[self class] alloc] initWithData:data contentType:type];
}
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
if ((self = [super init])) {
_data = data;
@@ -124,8 +117,7 @@
[variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
[html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
}];
id response = [self initWithHTML:html];
return response;
return [self initWithHTML:html];
}
- (instancetype)initWithJSONObject:(id)object {
@@ -135,6 +127,7 @@
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type {
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
if (data == nil) {
GWS_DNOT_REACHED();
return nil;
}
return [self initWithData:data contentType:type];
@@ -28,6 +28,8 @@
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerHTTPStatusCodes.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates
* 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
* 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
* 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.
@@ -70,12 +72,14 @@
* Initializes a client error response with the corresponding HTTP status code
* 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
* 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
NS_ASSUME_NONNULL_END
@@ -31,10 +31,6 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerErrorResponse ()
- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments;
@end
@implementation GCDWebServerErrorResponse
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
@@ -27,6 +27,8 @@
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from a file on disk.
@@ -36,17 +38,20 @@
* metadata.
*/
@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.
*/
+ (instancetype)responseWithFile:(NSString*)path;
+ (nullable instancetype)responseWithFile:(NSString*)path;
/**
* Creates a response like +responseWithFile: and sets the "Content-Disposition"
* 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
@@ -54,26 +59,26 @@
*
* 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
* "Content-Disposition" HTTP header for a download if the "attachment"
* 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.
*/
- (instancetype)initWithFile:(NSString*)path;
- (nullable instancetype)initWithFile:(NSString*)path;
/**
* Initializes a response like +responseWithFile: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment"
* 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
@@ -86,11 +91,13 @@
* This argument would typically be set to the value of the byteRange property
* 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.
*/
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
@end
NS_ASSUME_NONNULL_END
@@ -35,16 +35,14 @@
#define kFileReadBufferSize (32 * 1024)
@interface GCDWebServerFileResponse () {
@private
@implementation GCDWebServerFileResponse {
NSString* _path;
NSUInteger _offset;
NSUInteger _size;
int _file;
}
@end
@implementation GCDWebServerFileResponse
@dynamic contentType, lastModifiedDate, eTag;
+ (instancetype)responseWithFile:(NSString*)path {
return [[[self class] alloc] initWithFile:path];
@@ -27,12 +27,14 @@
#import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/**
* 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
* 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
@@ -51,6 +53,7 @@ typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlo
* the body of the HTTP response using a GCD block.
*/
@interface GCDWebServerStreamedResponse : GCDWebServerResponse
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
/**
* 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;
@end
NS_ASSUME_NONNULL_END
@@ -31,13 +31,11 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerStreamedResponse () {
@private
@implementation GCDWebServerStreamedResponse {
GCDWebServerAsyncStreamBlock _block;
}
@end
@implementation GCDWebServerStreamedResponse
@dynamic contentType;
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return [[[self class] alloc] initWithContentType:type streamBlock:block];
+5 -1
View File
@@ -27,6 +27,8 @@
#import "GCDWebServer.h"
NS_ASSUME_NONNULL_BEGIN
@class GCDWebUploader;
/**
@@ -84,7 +86,7 @@
/**
* 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.
@@ -195,3 +197,5 @@
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end
NS_ASSUME_NONNULL_END
+245 -244
View File
@@ -46,257 +46,30 @@
#import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h"
@interface GCDWebUploader () {
@private
NSString* _uploadDirectory;
NSArray* _allowedExtensions;
BOOL _allowHidden;
NSString* _title;
NSString* _header;
NSString* _prologue;
NSString* _epilogue;
NSString* _footer;
}
NS_ASSUME_NONNULL_BEGIN
@interface GCDWebUploader (Methods)
- (nullable GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request;
- (nullable GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request;
- (nullable GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request;
- (nullable GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request;
- (nullable GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request;
- (nullable GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request;
@end
@implementation GCDWebUploader (Methods)
// 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
NS_ASSUME_NONNULL_END
@implementation GCDWebUploader
@synthesize uploadDirectory = _uploadDirectory, allowedFileExtensions = _allowedExtensions, allowHiddenItems = _allowHidden,
title = _title, header = _header, prologue = _prologue, epilogue = _epilogue, footer = _footer;
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
NSString* bundlePath = [[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"];
if (bundlePath == nil) {
return nil;
}
NSBundle* siteBundle = [NSBundle bundleWithPath:bundlePath];
if (siteBundle == nil) {
return nil;
}
@@ -304,7 +77,7 @@
GCDWebUploader* __unsafe_unretained server = self;
// 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
[self addHandlerForMethod:@"GET"
@@ -353,7 +126,7 @@
#endif
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:@{
@"device" : device,
@"title" : title,
@@ -418,6 +191,234 @@
@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)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {
+1 -1
View File
@@ -370,7 +370,7 @@ int main(int argc, const char* argv[]) {
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
completionBlock(response);
});