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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1237,7 +1237,6 @@
"-Wno-cstring-format-directive", "-Wno-cstring-format-directive",
"-Wno-reserved-id-macro", "-Wno-reserved-id-macro",
"-Wno-cast-qual", "-Wno-cast-qual",
"-Wno-nullable-to-nonnull-conversion",
"-Wno-partial-availability", "-Wno-partial-availability",
); );
}; };

View File

@ -30,6 +30,8 @@
#import "GCDWebServerRequest.h" #import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h" #import "GCDWebServerResponse.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* The GCDWebServerMatchBlock is called for every handler added to the * The GCDWebServerMatchBlock is called for every handler added to the
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have * GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
@ -40,7 +42,7 @@
* GCDWebServerRequest instance created with the same basic info. * GCDWebServerRequest instance created with the same basic info.
* Otherwise, it simply returns nil. * Otherwise, it simply returns nil.
*/ */
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
/** /**
* The GCDWebServerProcessBlock is called after the HTTP request has been fully * The GCDWebServerProcessBlock is called after the HTTP request has been fully
@ -52,7 +54,7 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
* recommended to return a GCDWebServerErrorResponse on error so more useful * recommended to return a GCDWebServerErrorResponse on error so more useful
* information can be returned to the client. * information can be returned to the client.
*/ */
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request); typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);
/** /**
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock * The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
@ -64,7 +66,7 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(__kindof GCDWebServerR
* It's however recommended to return a GCDWebServerErrorResponse on error so more * It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client. * useful information can be returned to the client.
*/ */
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response); typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response);
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock); typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
/** /**
@ -295,7 +297,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/** /**
* Sets the delegate for the server. * Sets the delegate for the server.
*/ */
@property(nonatomic, assign) id<GCDWebServerDelegate> delegate; @property(nonatomic, weak, nullable) id<GCDWebServerDelegate> delegate;
/** /**
* Returns YES if the server is currently running. * Returns YES if the server is currently running.
@ -315,7 +317,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @warning This property is only valid if the server is running and Bonjour * @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds. * registration has successfully completed, which can take up to a few seconds.
*/ */
@property(nonatomic, readonly) NSString* bonjourName; @property(nonatomic, readonly, nullable) NSString* bonjourName;
/** /**
* Returns the Bonjour service type used by the server. * Returns the Bonjour service type used by the server.
@ -323,7 +325,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @warning This property is only valid if the server is running and Bonjour * @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds. * registration has successfully completed, which can take up to a few seconds.
*/ */
@property(nonatomic, readonly) NSString* bonjourType; @property(nonatomic, readonly, nullable) NSString* bonjourType;
/** /**
* This method is the designated initializer for the class. * This method is the designated initializer for the class.
@ -363,7 +365,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* Returns NO if the server failed to start and sets "error" argument if not NULL. * Returns NO if the server failed to start and sets "error" argument if not NULL.
*/ */
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error; - (BOOL)startWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error;
/** /**
* Stops the server and prevents it to accepts new HTTP requests. * Stops the server and prevents it to accepts new HTTP requests.
@ -383,7 +385,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* @warning This property is only valid if the server is running. * @warning This property is only valid if the server is running.
*/ */
@property(nonatomic, readonly) NSURL* serverURL; @property(nonatomic, readonly, nullable) NSURL* serverURL;
/** /**
* Returns the server's Bonjour URL. * Returns the server's Bonjour URL.
@ -393,7 +395,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* Also be aware this property will not automatically update if the Bonjour hostname * Also be aware this property will not automatically update if the Bonjour hostname
* has been dynamically changed after the server started running (this should be rare). * has been dynamically changed after the server started running (this should be rare).
*/ */
@property(nonatomic, readonly) NSURL* bonjourServerURL; @property(nonatomic, readonly, nullable) NSURL* bonjourServerURL;
/** /**
* Returns the server's public URL. * Returns the server's public URL.
@ -401,7 +403,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* @warning This property is only valid if the server is running and NAT port * @warning This property is only valid if the server is running and NAT port
* mapping is active. * mapping is active.
*/ */
@property(nonatomic, readonly) NSURL* publicServerURL; @property(nonatomic, readonly, nullable) NSURL* publicServerURL;
/** /**
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS) * Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
@ -418,7 +420,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* Returns NO if the server failed to start. * Returns NO if the server failed to start.
*/ */
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; - (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
@ -431,7 +433,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* @warning This method must be used from the main thread only. * @warning This method must be used from the main thread only.
*/ */
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name; - (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
/** /**
* Runs the server synchronously using -startWithOptions: until a SIGTERM or * Runs the server synchronously using -startWithOptions: until a SIGTERM or
@ -442,7 +444,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* @warning This method must be used from the main thread only. * @warning This method must be used from the main thread only.
*/ */
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error; - (BOOL)runWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error;
#endif #endif
@ -498,7 +500,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* Adds a handler to the server to respond to incoming "GET" HTTP requests * Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a specific case-insensitive path with in-memory data. * with a specific case-insensitive path with in-memory data.
*/ */
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge; - (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(nullable NSString*)contentType cacheAge:(NSUInteger)cacheAge;
/** /**
* Adds a handler to the server to respond to incoming "GET" HTTP requests * Adds a handler to the server to respond to incoming "GET" HTTP requests
@ -515,7 +517,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* The "indexFilename" argument allows to specify an "index" file name to use * The "indexFilename" argument allows to specify an "index" file name to use
* when the request path corresponds to a directory. * when the request path corresponds to a directory.
*/ */
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; - (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(nullable NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
@end @end
@ -612,8 +614,10 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
* *
* Returns the number of failed tests or -1 if server failed to start. * Returns the number of failed tests or -1 if server failed to start.
*/ */
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path; - (NSInteger)runTestsWithOptions:(nullable NSDictionary*)options inDirectory:(NSString*)path;
@end @end
#endif #endif
NS_ASSUME_NONNULL_END

View File

@ -132,18 +132,9 @@ static void _ExecuteMainThreadRunLoopSources() {
#endif #endif
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerAsyncProcessBlock _asyncProcessBlock;
}
@end
@implementation GCDWebServerHandler @implementation GCDWebServerHandler
@synthesize matchBlock = _matchBlock, asyncProcessBlock = _asyncProcessBlock; - (instancetype)initWithMatchBlock:(GCDWebServerMatchBlock _Nonnull)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock _Nonnull)processBlock {
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
if ((self = [super init])) { if ((self = [super init])) {
_matchBlock = [matchBlock copy]; _matchBlock = [matchBlock copy];
_asyncProcessBlock = [processBlock copy]; _asyncProcessBlock = [processBlock copy];
@ -153,9 +144,7 @@ static void _ExecuteMainThreadRunLoopSources() {
@end @end
@interface GCDWebServer () { @implementation GCDWebServer {
@private
id<GCDWebServerDelegate> __unsafe_unretained _delegate;
dispatch_queue_t _syncQueue; dispatch_queue_t _syncQueue;
dispatch_group_t _sourceGroup; dispatch_group_t _sourceGroup;
NSMutableArray* _handlers; NSMutableArray* _handlers;
@ -164,15 +153,10 @@ static void _ExecuteMainThreadRunLoopSources() {
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
NSDictionary* _options; NSDictionary* _options;
NSString* _serverName;
NSString* _authenticationRealm;
NSMutableDictionary* _authenticationBasicAccounts; NSMutableDictionary* _authenticationBasicAccounts;
NSMutableDictionary* _authenticationDigestAccounts; NSMutableDictionary* _authenticationDigestAccounts;
Class _connectionClass; Class _connectionClass;
BOOL _mapHEADToGET;
CFTimeInterval _disconnectDelay; CFTimeInterval _disconnectDelay;
dispatch_queue_priority_t _dispatchQueuePriority;
NSUInteger _port;
dispatch_source_t _source4; dispatch_source_t _source4;
dispatch_source_t _source6; dispatch_source_t _source6;
CFNetServiceRef _registrationService; CFNetServiceRef _registrationService;
@ -191,13 +175,6 @@ static void _ExecuteMainThreadRunLoopSources() {
BOOL _recording; BOOL _recording;
#endif #endif
} }
@end
@implementation GCDWebServer
@synthesize delegate = _delegate, handlers = _handlers, port = _port, serverName = _serverName, authenticationRealm = _authenticationRealm,
authenticationBasicAccounts = _authenticationBasicAccounts, authenticationDigestAccounts = _authenticationDigestAccounts,
shouldAutomaticallyMapHEADToGET = _mapHEADToGET, dispatchQueuePriority = _dispatchQueuePriority;
+ (void)initialize { + (void)initialize {
GCDWebServerInitializeFunctions(); GCDWebServerInitializeFunctions();
@ -600,7 +577,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
}]; }];
} }
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; _shouldAutomaticallyMapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue]; _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
_dispatchQueuePriority = [_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue]; _dispatchQueuePriority = [_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];
@ -1294,9 +1271,9 @@ static void _LogResult(NSString* format, ...) {
success = NO; success = NO;
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
#if DEBUG #if DEBUG
if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) { if (GCDWebServerIsTextContentType((NSString*)[expectedHeaders objectForKey:@"Content-Type"])) {
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) { if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) {
NSTask* task = [[NSTask alloc] init]; NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/opendiff"]; [task setLaunchPath:@"/usr/bin/opendiff"];

View File

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

View File

@ -57,14 +57,25 @@ static NSString* _digestAuthenticationNonce = nil;
static int32_t _connectionCounter = 0; static int32_t _connectionCounter = 0;
#endif #endif
@interface GCDWebServerConnection () { NS_ASSUME_NONNULL_BEGIN
@private
GCDWebServer* _server; @interface GCDWebServerConnection (Read)
NSData* _localAddress; - (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block;
NSData* _remoteAddress; - (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block;
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block;
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block;
@end
@interface GCDWebServerConnection (Write)
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block;
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block;
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block;
@end
NS_ASSUME_NONNULL_END
@implementation GCDWebServerConnection {
CFSocketNativeHandle _socket; CFSocketNativeHandle _socket;
NSUInteger _bytesRead;
NSUInteger _bytesWritten;
BOOL _virtualHEAD; BOOL _virtualHEAD;
CFHTTPMessageRef _requestMessage; CFHTTPMessageRef _requestMessage;
@ -83,266 +94,6 @@ static int32_t _connectionCounter = 0;
int _responseFD; int _responseFD;
#endif #endif
} }
@end
@implementation GCDWebServerConnection (Read)
- (void)_readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
@autoreleasepool {
if (error == 0) {
size_t size = dispatch_data_get_size(buffer);
if (size > 0) {
NSUInteger originalLength = data.length;
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
[data appendBytes:chunkBytes length:chunkSize];
return true;
});
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
block(YES);
} else {
if (_bytesRead > 0) {
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
} else {
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
}
block(NO);
}
} else {
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
block(NO);
}
}
});
}
- (void)_readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
GWS_DCHECK(_requestMessage);
[self _readData:headersData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
if (range.location == NSNotFound) {
[self _readHeaders:headersData withCompletionBlock:block];
} else {
NSUInteger length = range.location + range.length;
if (CFHTTPMessageAppendBytes(_requestMessage, headersData.bytes, length)) {
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
} else {
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
block(nil);
}
} else {
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
block(nil);
}
}
} else {
block(nil);
}
}];
}
- (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
[self _readData:bodyData
withLength:length
completionBlock:^(BOOL success) {
if (success) {
if (bodyData.length <= length) {
NSError* error = nil;
if ([_request performWriteData:bodyData error:&error]) {
NSUInteger remainingLength = length - bodyData.length;
if (remainingLength) {
[self _readBodyWithRemainingLength:remainingLength completionBlock:block];
} else {
block(YES);
}
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
block(NO);
}
} else {
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
block(NO);
GWS_DNOT_REACHED();
}
} else {
block(NO);
}
}];
}
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
char buffer[size + 1];
bcopy(bytes, buffer, size);
buffer[size] = 0;
char* end = NULL;
long result = strtol(buffer, &end, 16);
return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
}
- (void)_readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
while (1) {
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
if (range.location == NSNotFound) {
break;
}
NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions
NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
if (length != NSNotFound) {
if (length) {
if (chunkData.length < range.location + range.length + length + 2) {
break;
}
const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
NSError* error = nil;
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
block(NO);
return;
}
} else {
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
block(NO);
return;
}
} else {
NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers
if (trailerRange.location != NSNotFound) {
block(YES);
return;
}
}
} else {
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
block(NO);
return;
}
}
[self _readData:chunkData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
[self _readNextBodyChunk:chunkData completionBlock:block];
} else {
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Write)
- (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
[data self]; // Keeps ARC from releasing data too early
});
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
@autoreleasepool {
if (error == 0) {
GWS_DCHECK(remainingData == NULL);
[self didWriteBytes:data.bytes length:data.length];
block(YES);
} else {
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
block(NO);
}
}
});
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
dispatch_release(buffer);
#endif
}
- (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
GWS_DCHECK(_responseMessage);
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
[self _writeData:(__bridge NSData*)data withCompletionBlock:block];
CFRelease(data);
}
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
GWS_DCHECK([_response hasBody]);
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
if (data) {
if (data.length) {
if (_response.usesChunkedTransferEncoding) {
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
size_t hexLength = strlen(hexString);
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
if (chunk == nil) {
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
block(NO);
return;
}
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
bcopy(hexString, ptr, hexLength);
ptr += hexLength;
*ptr++ = '\r';
*ptr++ = '\n';
bcopy(data.bytes, ptr, data.length);
ptr += data.length;
*ptr++ = '\r';
*ptr = '\n';
data = chunk;
}
[self _writeData:data
withCompletionBlock:^(BOOL success) {
if (success) {
[self _writeBodyWithCompletionBlock:block];
} else {
block(NO);
}
}];
} else {
if (_response.usesChunkedTransferEncoding) {
[self _writeData:_lastChunkData
withCompletionBlock:^(BOOL success) {
block(success);
}];
} else {
block(YES);
}
}
} else {
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection
@synthesize server = _server, localAddressData = _localAddress, remoteAddressData = _remoteAddress, totalBytesRead = _bytesRead, totalBytesWritten = _bytesWritten;
+ (void)initialize { + (void)initialize {
if (_CRLFData == nil) { if (_CRLFData == nil) {
@ -370,7 +121,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
- (BOOL)isUsingIPv6 { - (BOOL)isUsingIPv6 {
const struct sockaddr* localSockAddr = _localAddress.bytes; const struct sockaddr* localSockAddr = _localAddressData.bytes;
return (localSockAddr->sa_family == AF_INET6); return (localSockAddr->sa_family == AF_INET6);
} }
@ -420,7 +171,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
if (_response) { if (_response) {
[self _initializeResponseHeadersWithStatusCode:_response.statusCode]; [self _initializeResponseHeadersWithStatusCode:_response.statusCode];
if (_response.lastModifiedDate) { if (_response.lastModifiedDate) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822(_response.lastModifiedDate)); CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822((NSDate*)_response.lastModifiedDate));
} }
if (_response.eTag) { if (_response.eTag) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag); CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
@ -444,11 +195,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
[_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) { [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
}]; }];
[self _writeHeadersWithCompletionBlock:^(BOOL success) { [self writeHeadersWithCompletionBlock:^(BOOL success) {
if (success) { if (success) {
if (hasBody) { if (hasBody) {
[self _writeBodyWithCompletionBlock:^(BOOL successInner) { [self writeBodyWithCompletionBlock:^(BOOL successInner) {
[_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent [_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent
@ -485,18 +236,18 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
if (length) { if (length) {
[self _readBodyWithRemainingLength:length [self readBodyWithRemainingLength:length
completionBlock:^(BOOL success) { completionBlock:^(BOOL success) {
NSError* localError = nil; NSError* localError = nil;
if ([_request performClose:&localError]) { if ([_request performClose:&localError]) {
[self _startProcessingRequest]; [self _startProcessingRequest];
} else { } else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
} }
}]; }];
} else { } else {
if ([_request performClose:&error]) { if ([_request performClose:&error]) {
[self _startProcessingRequest]; [self _startProcessingRequest];
@ -516,24 +267,24 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData]; NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
[self _readNextBodyChunk:chunkData [self readNextBodyChunk:chunkData
completionBlock:^(BOOL success) { completionBlock:^(BOOL success) {
NSError* localError = nil; NSError* localError = nil;
if ([_request performClose:&localError]) { if ([_request performClose:&localError]) {
[self _startProcessingRequest]; [self _startProcessingRequest];
} else { } else {
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
} }
}]; }];
} }
- (void)_readRequestHeaders { - (void)_readRequestHeaders {
_requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true); _requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity]; NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity];
[self _readHeaders:headersData [self readHeaders:headersData
withCompletionBlock:^(NSData* extraData) { withCompletionBlock:^(NSData* extraData) {
if (extraData) { if (extraData) {
@ -548,7 +299,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders]; requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
GWS_DCHECK(requestURL); GWS_DCHECK(requestURL);
} }
NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash NSString* urlPath = requestURL ? CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL)) : nil; // Don't use -[NSURL path] which strips the ending slash
NSString* requestPath = urlPath ? GCDWebServerUnescapeURLString(urlPath) : nil;
NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped; NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{}; NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) { if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
@ -567,7 +319,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
NSString* expectHeader = [requestHeaders objectForKey:@"Expect"]; NSString* expectHeader = [requestHeaders objectForKey:@"Expect"];
if (expectHeader) { if (expectHeader) {
if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing
[self _writeData:_continueData [self writeData:_continueData
withCompletionBlock:^(BOOL success) { withCompletionBlock:^(BOOL success) {
if (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])) { if ((self = [super init])) {
_server = server; _server = server;
_localAddress = localAddress; _localAddressData = localAddress;
_remoteAddress = remoteAddress; _remoteAddressData = remoteAddress;
_socket = socket; _socket = socket;
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket); GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
@ -635,11 +387,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
- (NSString*)localAddressString { - (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES); return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
} }
- (NSString*)remoteAddressString { - (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES); return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES);
} }
- (void)dealloc { - (void)dealloc {
@ -667,6 +419,261 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
@end @end
@implementation GCDWebServerConnection (Read)
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
@autoreleasepool {
if (error == 0) {
size_t size = dispatch_data_get_size(buffer);
if (size > 0) {
NSUInteger originalLength = data.length;
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
[data appendBytes:chunkBytes length:chunkSize];
return true;
});
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
block(YES);
} else {
if (_totalBytesRead > 0) {
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
} else {
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
}
block(NO);
}
} else {
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
block(NO);
}
}
});
}
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
GWS_DCHECK(_requestMessage);
[self readData:headersData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
if (range.location == NSNotFound) {
[self readHeaders:headersData withCompletionBlock:block];
} else {
NSUInteger length = range.location + range.length;
if (CFHTTPMessageAppendBytes(_requestMessage, headersData.bytes, length)) {
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
} else {
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
block(nil);
}
} else {
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
block(nil);
}
}
} else {
block(nil);
}
}];
}
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
[self readData:bodyData
withLength:length
completionBlock:^(BOOL success) {
if (success) {
if (bodyData.length <= length) {
NSError* error = nil;
if ([_request performWriteData:bodyData error:&error]) {
NSUInteger remainingLength = length - bodyData.length;
if (remainingLength) {
[self readBodyWithRemainingLength:remainingLength completionBlock:block];
} else {
block(YES);
}
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
block(NO);
}
} else {
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
block(NO);
GWS_DNOT_REACHED();
}
} else {
block(NO);
}
}];
}
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
char buffer[size + 1];
bcopy(bytes, buffer, size);
buffer[size] = 0;
char* end = NULL;
long result = strtol(buffer, &end, 16);
return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
}
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
while (1) {
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
if (range.location == NSNotFound) {
break;
}
NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions
NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
if (length != NSNotFound) {
if (length) {
if (chunkData.length < range.location + range.length + length + 2) {
break;
}
const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
NSError* error = nil;
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
} else {
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
block(NO);
return;
}
} else {
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
block(NO);
return;
}
} else {
NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers
if (trailerRange.location != NSNotFound) {
block(YES);
return;
}
}
} else {
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
block(NO);
return;
}
}
[self readData:chunkData
withLength:NSUIntegerMax
completionBlock:^(BOOL success) {
if (success) {
[self readNextBodyChunk:chunkData completionBlock:block];
} else {
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Write)
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
[data self]; // Keeps ARC from releasing data too early
});
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
@autoreleasepool {
if (error == 0) {
GWS_DCHECK(remainingData == NULL);
[self didWriteBytes:data.bytes length:data.length];
block(YES);
} else {
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
block(NO);
}
}
});
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
dispatch_release(buffer);
#endif
}
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
GWS_DCHECK(_responseMessage);
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
[self writeData:(__bridge NSData*)data withCompletionBlock:block];
CFRelease(data);
}
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
GWS_DCHECK([_response hasBody]);
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
if (data) {
if (data.length) {
if (_response.usesChunkedTransferEncoding) {
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
size_t hexLength = strlen(hexString);
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
if (chunk == nil) {
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
block(NO);
return;
}
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
bcopy(hexString, ptr, hexLength);
ptr += hexLength;
*ptr++ = '\r';
*ptr++ = '\n';
bcopy(data.bytes, ptr, data.length);
ptr += data.length;
*ptr++ = '\r';
*ptr = '\n';
data = chunk;
}
[self writeData:data
withCompletionBlock:^(BOOL success) {
if (success) {
[self writeBodyWithCompletionBlock:block];
} else {
block(NO);
}
}];
} else {
if (_response.usesChunkedTransferEncoding) {
[self writeData:_lastChunkData
withCompletionBlock:^(BOOL success) {
block(success);
}];
} else {
block(YES);
}
}
} else {
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
block(NO);
}
}];
}
@end
@implementation GCDWebServerConnection (Subclassing) @implementation GCDWebServerConnection (Subclassing)
- (BOOL)open { - (BOOL)open {
@ -692,7 +699,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length { - (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket); GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
_bytesRead += length; _totalBytesRead += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__ #ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) { if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
@ -705,7 +712,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length { - (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket); GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
_bytesWritten += length; _totalBytesWritten += length;
#ifdef __GCDWEBSERVER_ENABLE_TESTING__ #ifdef __GCDWEBSERVER_ENABLE_TESTING__
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) { if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
@ -722,7 +729,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
// https://tools.ietf.org/html/rfc2617 // https://tools.ietf.org/html/rfc2617
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request { - (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead); GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
GCDWebServerResponse* response = nil; GCDWebServerResponse* response = nil;
if (_server.authenticationBasicAccounts) { if (_server.authenticationBasicAccounts) {
__block BOOL authenticated = NO; __block BOOL authenticated = NO;
@ -772,7 +779,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion { - (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead); GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
_handler.asyncProcessBlock(request, [completion copy]); _handler.asyncProcessBlock(request, [completion copy]);
} }
@ -812,7 +819,7 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
GWS_DCHECK(_responseMessage == NULL); GWS_DCHECK(_responseMessage == NULL);
GWS_DCHECK((statusCode >= 400) && (statusCode < 600)); GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
[self _initializeResponseHeadersWithStatusCode:statusCode]; [self _initializeResponseHeadersWithStatusCode:statusCode];
[self _writeHeadersWithCompletionBlock:^(BOOL success) { [self writeHeadersWithCompletionBlock:^(BOOL success) {
; // Nothing more to do ; // Nothing more to do
}]; }];
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket); GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
@ -852,9 +859,9 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
#endif #endif
if (_request) { if (_request) {
GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten); GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
} else { } else {
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten); GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
} }
} }

View File

@ -27,6 +27,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -42,12 +44,12 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
* The legal characters ":@/?&=+" are also escaped to ensure compatibility * The legal characters ":@/?&=+" are also escaped to ensure compatibility
* with URL encoded forms and URL queries. * with URL encoded forms and URL queries.
*/ */
NSString* GCDWebServerEscapeURLString(NSString* string); NSString* _Nullable GCDWebServerEscapeURLString(NSString* string);
/** /**
* Unescapes a URL percent-encoded string. * Unescapes a URL percent-encoded string.
*/ */
NSString* GCDWebServerUnescapeURLString(NSString* string); NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string);
/** /**
* Extracts the unescaped names and values from an * Extracts the unescaped names and values from an
@ -63,7 +65,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
* interface if connected or nil otherwise. * interface if connected or nil otherwise.
*/ */
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6); NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
/** /**
* Converts a date into a string using RFC822 formatting. * Converts a date into a string using RFC822 formatting.
@ -79,7 +81,7 @@ NSString* GCDWebServerFormatRFC822(NSDate* date);
* *
* @warning Timezones other than GMT are not supported by this function. * @warning Timezones other than GMT are not supported by this function.
*/ */
NSDate* GCDWebServerParseRFC822(NSString* string); NSDate* _Nullable GCDWebServerParseRFC822(NSString* string);
/** /**
* Converts a date into a string using IOS 8601 formatting. * Converts a date into a string using IOS 8601 formatting.
@ -94,8 +96,10 @@ NSString* GCDWebServerFormatISO8601(NSDate* date);
* @warning Only "calendar" variant is supported at this time and timezones * @warning Only "calendar" variant is supported at this time and timezones
* other than GMT are not supported either. * other than GMT are not supported either.
*/ */
NSDate* GCDWebServerParseISO8601(NSString* string); NSDate* _Nullable GCDWebServerParseISO8601(NSString* string);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
NS_ASSUME_NONNULL_END

View File

@ -83,21 +83,28 @@ NSString* GCDWebServerNormalizeHeaderValue(NSString* value) {
} }
NSString* GCDWebServerTruncateHeaderValue(NSString* value) { NSString* GCDWebServerTruncateHeaderValue(NSString* value) {
NSRange range = [value rangeOfString:@";"]; if (value) {
return range.location != NSNotFound ? [value substringToIndex:range.location] : value; NSRange range = [value rangeOfString:@";"];
if (range.location != NSNotFound) {
return [value substringToIndex:range.location];
}
}
return value;
} }
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
NSString* parameter = nil; NSString* parameter = nil;
NSScanner* scanner = [[NSScanner alloc] initWithString:value]; if (value) {
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive NSScanner* scanner = [[NSScanner alloc] initWithString:value];
NSString* string = [NSString stringWithFormat:@"%@=", name]; [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
if ([scanner scanUpToString:string intoString:NULL]) { NSString* string = [NSString stringWithFormat:@"%@=", name];
[scanner scanString:string intoString:NULL]; if ([scanner scanUpToString:string intoString:NULL]) {
if ([scanner scanString:@"\"" intoString:NULL]) { [scanner scanString:string intoString:NULL];
[scanner scanUpToString:@"\"" intoString:&parameter]; if ([scanner scanString:@"\"" intoString:NULL]) {
} else { [scanner scanUpToString:@"\"" intoString:&parameter];
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter]; } else {
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&parameter];
}
} }
} }
return parameter; return parameter;
@ -232,15 +239,16 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
} }
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) { NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
NSString* string = nil;
char hostBuffer[NI_MAXHOST]; char hostBuffer[NI_MAXHOST];
char serviceBuffer[NI_MAXSERV]; char serviceBuffer[NI_MAXSERV];
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) { if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) {
string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer]; #if DEBUG
} else {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
#else
return @"";
#endif
} }
return string; return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer];
} }
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) { NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
@ -255,7 +263,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
if (store) { if (store) {
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
if (info) { if (info) {
primaryInterface = [[NSString stringWithString:[(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String]; NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"];
if (interface) {
primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool
}
CFRelease(info); CFRelease(info);
} }
CFRelease(store); CFRelease(store);
@ -303,5 +314,5 @@ NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) {
buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo; buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo;
} }
buffer[2 * CC_MD5_DIGEST_LENGTH] = 0; buffer[2 * CC_MD5_DIGEST_LENGTH] = 0;
return [NSString stringWithUTF8String:buffer]; return (NSString*)[NSString stringWithUTF8String:buffer];
} }

View File

@ -48,6 +48,8 @@
#import "GCDWebServerFileResponse.h" #import "GCDWebServerFileResponse.h"
#import "GCDWebServerStreamedResponse.h" #import "GCDWebServerStreamedResponse.h"
NS_ASSUME_NONNULL_BEGIN
/** /**
* Check if a custom logging facility should be used instead. * Check if a custom logging facility should be used instead.
*/ */
@ -190,9 +192,9 @@ static inline NSError* GCDWebServerMakePosixError(int code) {
} }
extern void GCDWebServerInitializeFunctions(); extern void GCDWebServerInitializeFunctions();
extern NSString* GCDWebServerNormalizeHeaderValue(NSString* value); extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
extern NSString* GCDWebServerTruncateHeaderValue(NSString* value); extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
extern NSString* GCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute); extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset); extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
extern BOOL GCDWebServerIsTextContentType(NSString* type); extern BOOL GCDWebServerIsTextContentType(NSString* type);
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType); extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
@ -200,15 +202,15 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService); extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
@interface GCDWebServerConnection () @interface GCDWebServerConnection ()
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; - (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
@end @end
@interface GCDWebServer () @interface GCDWebServer ()
@property(nonatomic, readonly) NSArray* handlers; @property(nonatomic, readonly) NSMutableArray* handlers;
@property(nonatomic, readonly) NSString* serverName; @property(nonatomic, readonly) NSString* serverName;
@property(nonatomic, readonly) NSString* authenticationRealm; @property(nonatomic, readonly) NSString* authenticationRealm;
@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts; @property(nonatomic, readonly) NSMutableDictionary* authenticationBasicAccounts;
@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts; @property(nonatomic, readonly) NSMutableDictionary* authenticationDigestAccounts;
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; @property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority; @property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
- (void)willStartConnection:(GCDWebServerConnection*)connection; - (void)willStartConnection:(GCDWebServerConnection*)connection;
@ -222,13 +224,13 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
@interface GCDWebServerRequest () @interface GCDWebServerRequest ()
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
@property(nonatomic, readwrite) NSData* localAddressData; @property(nonatomic) NSData* localAddressData;
@property(nonatomic, readwrite) NSData* remoteAddressData; @property(nonatomic) NSData* remoteAddressData;
- (void)prepareForWriting; - (void)prepareForWriting;
- (BOOL)performOpen:(NSError**)error; - (BOOL)performOpen:(NSError**)error;
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error; - (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
- (BOOL)performClose:(NSError**)error; - (BOOL)performClose:(NSError**)error;
- (void)setAttribute:(id)attribute forKey:(NSString*)key; - (void)setAttribute:(nullable id)attribute forKey:(NSString*)key;
@end @end
@interface GCDWebServerResponse () @interface GCDWebServerResponse ()
@ -239,3 +241,5 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block; - (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
- (void)performClose; - (void)performClose;
@end @end
NS_ASSUME_NONNULL_END

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,16 +31,9 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerFileRequest () { @implementation GCDWebServerFileRequest {
@private
NSString* _temporaryPath;
int _file; int _file;
} }
@end
@implementation GCDWebServerFileRequest
@synthesize temporaryPath = _temporaryPath;
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) {

View File

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

View File

@ -42,50 +42,28 @@ typedef enum {
} ParserState; } ParserState;
@interface GCDWebServerMIMEStreamParser : NSObject @interface GCDWebServerMIMEStreamParser : NSObject
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files;
- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length;
- (BOOL)isAtEnd;
@end @end
static NSData* _newlineData = nil; static NSData* _newlineData = nil;
static NSData* _newlinesData = nil; static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil; static NSData* _dashNewlineData = nil;
@interface GCDWebServerMultiPart () {
@private
NSString* _controlName;
NSString* _contentType;
NSString* _mimeType;
}
@end
@implementation GCDWebServerMultiPart @implementation GCDWebServerMultiPart
@synthesize controlName = _controlName, contentType = _contentType, mimeType = _mimeType; - (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type {
if ((self = [super init])) { if ((self = [super init])) {
_controlName = [name copy]; _controlName = [name copy];
_contentType = [type copy]; _contentType = [type copy];
_mimeType = GCDWebServerTruncateHeaderValue(_contentType); _mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
} }
return self; return self;
} }
@end @end
@interface GCDWebServerMultiPartArgument () {
@private
NSData* _data;
NSString* _string;
}
@end
@implementation GCDWebServerMultiPartArgument @implementation GCDWebServerMultiPartArgument
@synthesize data = _data, string = _string; - (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data {
if ((self = [super initWithControlName:name contentType:type])) { if ((self = [super initWithControlName:name contentType:type])) {
_data = data; _data = data;
@ -103,18 +81,9 @@ static NSData* _dashNewlineData = nil;
@end @end
@interface GCDWebServerMultiPartFile () {
@private
NSString* _fileName;
NSString* _temporaryPath;
}
@end
@implementation GCDWebServerMultiPartFile @implementation GCDWebServerMultiPartFile
@synthesize fileName = _fileName, temporaryPath = _temporaryPath; - (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath {
- (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
if ((self = [super initWithControlName:name contentType:type])) { if ((self = [super initWithControlName:name contentType:type])) {
_fileName = [fileName copy]; _fileName = [fileName copy];
_temporaryPath = [temporaryPath copy]; _temporaryPath = [temporaryPath copy];
@ -132,8 +101,7 @@ static NSData* _dashNewlineData = nil;
@end @end
@interface GCDWebServerMIMEStreamParser () { @implementation GCDWebServerMIMEStreamParser {
@private
NSData* _boundary; NSData* _boundary;
NSString* _defaultcontrolName; NSString* _defaultcontrolName;
ParserState _state; ParserState _state;
@ -148,9 +116,6 @@ static NSData* _dashNewlineData = nil;
int _tmpFile; int _tmpFile;
GCDWebServerMIMEStreamParser* _subParser; GCDWebServerMIMEStreamParser* _subParser;
} }
@end
@implementation GCDWebServerMIMEStreamParser
+ (void)initialize { + (void)initialize {
if (_newlineData == nil) { if (_newlineData == nil) {
@ -167,7 +132,7 @@ static NSData* _dashNewlineData = nil;
} }
} }
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files { - (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray* _Nonnull)arguments files:(NSMutableArray* _Nonnull)files {
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil; NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
if (data == nil) { if (data == nil) {
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
@ -346,17 +311,14 @@ static NSData* _dashNewlineData = nil;
@end @end
@interface GCDWebServerMultiPartFormRequest () { @interface GCDWebServerMultiPartFormRequest ()
@private @property(nonatomic) NSMutableArray* arguments;
GCDWebServerMIMEStreamParser* _parser; @property(nonatomic) NSMutableArray* files;
NSMutableArray* _arguments;
NSMutableArray* _files;
}
@end @end
@implementation GCDWebServerMultiPartFormRequest @implementation GCDWebServerMultiPartFormRequest {
GCDWebServerMIMEStreamParser* _parser;
@synthesize arguments = _arguments, files = _files; }
+ (NSString*)mimeType { + (NSString*)mimeType {
return @"multipart/form-data"; return @"multipart/form-data";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,16 +35,14 @@
#define kFileReadBufferSize (32 * 1024) #define kFileReadBufferSize (32 * 1024)
@interface GCDWebServerFileResponse () { @implementation GCDWebServerFileResponse {
@private
NSString* _path; NSString* _path;
NSUInteger _offset; NSUInteger _offset;
NSUInteger _size; NSUInteger _size;
int _file; int _file;
} }
@end
@implementation GCDWebServerFileResponse @dynamic contentType, lastModifiedDate, eTag;
+ (instancetype)responseWithFile:(NSString*)path { + (instancetype)responseWithFile:(NSString*)path {
return [[[self class] alloc] initWithFile:path]; return [[[self class] alloc] initWithFile:path];

View File

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

View File

@ -31,13 +31,11 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerStreamedResponse () { @implementation GCDWebServerStreamedResponse {
@private
GCDWebServerAsyncStreamBlock _block; GCDWebServerAsyncStreamBlock _block;
} }
@end
@implementation GCDWebServerStreamedResponse @dynamic contentType;
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { + (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return [[[self class] alloc] initWithContentType:type streamBlock:block]; return [[[self class] alloc] initWithContentType:type streamBlock:block];

View File

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

View File

@ -46,257 +46,30 @@
#import "GCDWebServerErrorResponse.h" #import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h" #import "GCDWebServerFileResponse.h"
@interface GCDWebUploader () { NS_ASSUME_NONNULL_BEGIN
@private
NSString* _uploadDirectory; @interface GCDWebUploader (Methods)
NSArray* _allowedExtensions; - (nullable GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request;
BOOL _allowHidden; - (nullable GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request;
NSString* _title; - (nullable GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request;
NSString* _header; - (nullable GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request;
NSString* _prologue; - (nullable GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request;
NSString* _epilogue; - (nullable GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request;
NSString* _footer;
}
@end @end
@implementation GCDWebUploader (Methods) NS_ASSUME_NONNULL_END
// Must match implementation in GCDWebDAVServer
- (BOOL)_checkSandboxedPath:(NSString*)path {
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
}
- (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO;
}
return YES;
}
- (NSString*)_uniquePathForPath:(NSString*)path {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSString* directory = [path stringByDeletingLastPathComponent];
NSString* file = [path lastPathComponent];
NSString* base = [file stringByDeletingPathExtension];
NSString* extension = [file pathExtension];
int retries = 0;
do {
if (extension.length) {
path = [directory stringByAppendingPathComponent:[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
} else {
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
}
} while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
}
return path;
}
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (!isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
}
NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
}
NSError* error = nil;
NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
if (contents == nil) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
}
NSMutableArray* array = [NSMutableArray array];
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
if (_allowHidden || ![item hasPrefix:@"."]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
NSString* type = [attributes objectForKey:NSFileType];
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
[array addObject:@{
@"path" : [relativePath stringByAppendingPathComponent:item],
@"name" : item,
@"size" : [attributes objectForKey:NSFileSize]
}];
} else if ([type isEqualToString:NSFileTypeDirectory]) {
[array addObject:@{
@"path" : [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
@"name" : item
}];
}
}
}
return [GCDWebServerDataResponse responseWithJSONObject:array];
}
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
}
NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
});
}
return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
}
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch];
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
if ((!_allowHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
}
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didUploadFileAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
}
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
}
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
if (![self _checkSandboxedPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
}
NSString* itemName = [newAbsolutePath lastPathComponent];
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
}
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
}
if (![self shouldDeleteItemAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
}
if (![self shouldCreateDirectoryAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
@end
@implementation GCDWebUploader @implementation GCDWebUploader
@synthesize uploadDirectory = _uploadDirectory, allowedFileExtensions = _allowedExtensions, allowHiddenItems = _allowHidden,
title = _title, header = _header, prologue = _prologue, epilogue = _epilogue, footer = _footer;
@dynamic delegate; @dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path { - (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) { if ((self = [super init])) {
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"]]; NSString* bundlePath = [[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"];
if (bundlePath == nil) {
return nil;
}
NSBundle* siteBundle = [NSBundle bundleWithPath:bundlePath];
if (siteBundle == nil) { if (siteBundle == nil) {
return nil; return nil;
} }
@ -304,7 +77,7 @@
GCDWebUploader* __unsafe_unretained server = self; GCDWebUploader* __unsafe_unretained server = self;
// Resource files // Resource files
[self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO]; [self addGETHandlerForBasePath:@"/" directoryPath:(NSString*)[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
// Web page // Web page
[self addHandlerForMethod:@"GET" [self addHandlerForMethod:@"GET"
@ -353,7 +126,7 @@
#endif #endif
footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version]; footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
} }
return [GCDWebServerDataResponse responseWithHTMLTemplate:[siteBundle pathForResource:@"index" ofType:@"html"] return [GCDWebServerDataResponse responseWithHTMLTemplate:(NSString*)[siteBundle pathForResource:@"index" ofType:@"html"]
variables:@{ variables:@{
@"device" : device, @"device" : device,
@"title" : title, @"title" : title,
@ -418,6 +191,234 @@
@end @end
@implementation GCDWebUploader (Methods)
// Must match implementation in GCDWebDAVServer
- (BOOL)_checkSandboxedPath:(NSString*)path {
return [[path stringByStandardizingPath] hasPrefix:_uploadDirectory];
}
- (BOOL)_checkFileExtension:(NSString*)fileName {
if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
return NO;
}
return YES;
}
- (NSString*)_uniquePathForPath:(NSString*)path {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSString* directory = [path stringByDeletingLastPathComponent];
NSString* file = [path lastPathComponent];
NSString* base = [file stringByDeletingPathExtension];
NSString* extension = [file pathExtension];
int retries = 0;
do {
if (extension.length) {
path = [directory stringByAppendingPathComponent:(NSString*)[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
} else {
path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
}
} while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
}
return path;
}
- (GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (!isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is not a directory", relativePath];
}
NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
}
NSError* error = nil;
NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:&error];
if (contents == nil) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed listing directory \"%@\"", relativePath];
}
NSMutableArray* array = [NSMutableArray array];
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
if (_allowHiddenItems || ![item hasPrefix:@"."]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
NSString* type = [attributes objectForKey:NSFileType];
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
[array addObject:@{
@"path" : [relativePath stringByAppendingPathComponent:item],
@"name" : item,
@"size" : [attributes objectForKey:NSFileSize]
}];
} else if ([type isEqualToString:NSFileTypeDirectory]) {
[array addObject:@{
@"path" : [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
@"name" : item
}];
}
}
}
return [GCDWebServerDataResponse responseWithJSONObject:array];
}
- (GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request {
NSString* relativePath = [[request query] objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (isDirectory) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"\"%@\" is a directory", relativePath];
}
NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didDownloadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDownloadFileAtPath:absolutePath];
});
}
return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
}
- (GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request {
NSRange range = [[request.headers objectForKey:@"Accept"] rangeOfString:@"application/json" options:NSCaseInsensitiveSearch];
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
GCDWebServerMultiPartFile* file = [request firstFileForControlName:@"files[]"];
if ((!_allowHiddenItems && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
}
NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
NSString* absolutePath = [self _uniquePathForPath:[[_uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:file.fileName]];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
if (![self shouldUploadFileAtPath:absolutePath withTemporaryFile:file.temporaryPath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file \"%@\" to \"%@\" is not permitted", file.fileName, relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving uploaded file to \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didUploadFileAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didUploadFileAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
}
- (GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* oldRelativePath = [request.arguments objectForKey:@"oldPath"];
NSString* oldAbsolutePath = [_uploadDirectory stringByAppendingPathComponent:oldRelativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:oldAbsolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", oldRelativePath];
}
NSString* newRelativePath = [request.arguments objectForKey:@"newPath"];
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
if (![self _checkSandboxedPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", newRelativePath];
}
NSString* itemName = [newAbsolutePath lastPathComponent];
if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
}
if (![self shouldMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving \"%@\" to \"%@\" is not permitted", oldRelativePath, newRelativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed moving \"%@\" to \"%@\"", oldRelativePath, newRelativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didMoveItemFromPath:toPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
- (GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [_uploadDirectory stringByAppendingPathComponent:relativePath];
BOOL isDirectory = NO;
if (![self _checkSandboxedPath:absolutePath] || ![[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
}
if (![self shouldDeleteItemAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting \"%@\" is not permitted", relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:absolutePath error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed deleting \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didDeleteItemAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didDeleteItemAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
- (GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request {
NSString* relativePath = [request.arguments objectForKey:@"path"];
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
if (![self _checkSandboxedPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_NotFound message:@"\"%@\" does not exist", relativePath];
}
NSString* directoryName = [absolutePath lastPathComponent];
if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
}
if (![self shouldCreateDirectoryAtPath:absolutePath]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory \"%@\" is not permitted", relativePath];
}
NSError* error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:NO attributes:nil error:&error]) {
return [GCDWebServerErrorResponse responseWithServerError:kGCDWebServerHTTPStatusCode_InternalServerError underlyingError:error message:@"Failed creating directory \"%@\"", relativePath];
}
if ([self.delegate respondsToSelector:@selector(webUploader:didCreateDirectoryAtPath:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate webUploader:self didCreateDirectoryAtPath:absolutePath];
});
}
return [GCDWebServerDataResponse responseWithJSONObject:@{}];
}
@end
@implementation GCDWebUploader (Subclassing) @implementation GCDWebUploader (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath { - (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {

View File

@ -370,7 +370,7 @@ int main(int argc, const char* argv[]) {
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"]; GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
completionBlock(response); completionBlock(response);
}); });