From d9602276a432419d65c6c4f4f26766e140180f39 Mon Sep 17 00:00:00 2001 From: Andrea Barletti Date: Wed, 8 Apr 2020 12:43:32 +0200 Subject: [PATCH 1/5] Create react-native-http-bridge --- react-native-http-bridge | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 react-native-http-bridge diff --git a/react-native-http-bridge b/react-native-http-bridge new file mode 100644 index 0000000..e032be4 --- /dev/null +++ b/react-native-http-bridge @@ -0,0 +1,19 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) + +Pod::Spec.new do |s| + s.name = package['name'] + s.version = package['version'] + s.summary = package['description'] + s.license = package['license'] + + s.authors = package['author'] + s.homepage = package['homepage'] + s.platform = :ios, "9.0" + + s.source = { :git => "https://github.com/babeone/react-native-http-bridge.git", :tag => "v#{s.version}" } + s.source_files = "ios/**/*.{h,m}" + + s.dependency 'React' +end From ad760b274be9ae7923d78320212725cb8415f3a3 Mon Sep 17 00:00:00 2001 From: Andrea Barletti Date: Wed, 8 Apr 2020 12:46:05 +0200 Subject: [PATCH 2/5] Rename react-native-http-bridge to react-native-http-bridge.podspec --- react-native-http-bridge => react-native-http-bridge.podspec | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename react-native-http-bridge => react-native-http-bridge.podspec (100%) diff --git a/react-native-http-bridge b/react-native-http-bridge.podspec similarity index 100% rename from react-native-http-bridge rename to react-native-http-bridge.podspec From da976bbf1c12d6d460141f7b2fad94ae12a8993c Mon Sep 17 00:00:00 2001 From: Andrea Barletti Date: Wed, 8 Apr 2020 13:28:13 +0200 Subject: [PATCH 3/5] Update RCTHttpServer.m --- ios/RCTHttpServer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RCTHttpServer.m b/ios/RCTHttpServer.m index 1d34ef6..7aa93af 100644 --- a/ios/RCTHttpServer.m +++ b/ios/RCTHttpServer.m @@ -64,7 +64,7 @@ - (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type serviceName:(NSString *) serviceName) { RCTLogInfo(@"Running HTTP bridge server: %ld", port); - _requestResponses = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *_requestResponses = [[NSMutableDictionary alloc] init]; dispatch_sync(dispatch_get_main_queue(), ^{ _webServer = [[WGCDWebServer alloc] init]; @@ -104,7 +104,7 @@ - (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type [_completionBlocks removeObjectForKey:requestId]; } - completionBlock(requestResponse); + if (completionBlock) completionBlock(requestResponse); } @end From 2bc5845a251d49901a3fcadce0224ac86f2b56d7 Mon Sep 17 00:00:00 2001 From: Andrea Barletti Date: Thu, 9 Apr 2020 09:53:39 +0200 Subject: [PATCH 4/5] test for fixing ios --- ios/RCTHttpServer.m | 26 +- ios/WGCDWebServer/Core/WGCDWebServer.h | 619 -------- ios/WGCDWebServer/Core/WGCDWebServer.m | 1324 ----------------- .../Core/WGCDWebServerConnection.h | 179 --- .../Core/WGCDWebServerConnection.m | 846 ----------- .../Core/WGCDWebServerFunctions.h | 101 -- .../Core/WGCDWebServerFunctions.m | 307 ---- .../Core/WGCDWebServerHTTPStatusCodes.h | 116 -- ios/WGCDWebServer/Core/WGCDWebServerPrivate.h | 226 --- ios/WGCDWebServer/Core/WGCDWebServerRequest.h | 206 --- ios/WGCDWebServer/Core/WGCDWebServerRequest.m | 334 ----- .../Core/WGCDWebServerResponse.h | 208 --- .../Core/WGCDWebServerResponse.m | 310 ---- .../Requests/WGCDWebServerDataRequest.h | 60 - .../Requests/WGCDWebServerDataRequest.m | 108 -- .../Requests/WGCDWebServerFileRequest.h | 45 - .../Requests/WGCDWebServerFileRequest.m | 109 -- .../WGCDWebServerMultiPartFormRequest.h | 132 -- .../WGCDWebServerMultiPartFormRequest.m | 445 ------ .../WGCDWebServerURLEncodedFormRequest.h | 51 - .../WGCDWebServerURLEncodedFormRequest.m | 70 - .../Responses/WGCDWebServerDataResponse.h | 108 -- .../Responses/WGCDWebServerDataResponse.m | 143 -- .../Responses/WGCDWebServerErrorResponse.h | 81 - .../Responses/WGCDWebServerErrorResponse.m | 128 -- .../Responses/WGCDWebServerFileResponse.h | 96 -- .../Responses/WGCDWebServerFileResponse.m | 187 --- .../Responses/WGCDWebServerStreamedResponse.h | 75 - .../Responses/WGCDWebServerStreamedResponse.m | 79 - react-native-http-bridge.podspec | 1 + 30 files changed, 14 insertions(+), 6706 deletions(-) delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServer.h delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServer.m delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerConnection.h delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerConnection.m delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerFunctions.h delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerFunctions.m delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerHTTPStatusCodes.h delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerPrivate.h delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerRequest.h delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerRequest.m delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerResponse.h delete mode 100755 ios/WGCDWebServer/Core/WGCDWebServerResponse.m delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.h delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.m delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.h delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.m delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.h delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.m delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.h delete mode 100755 ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.m delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.h delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.m delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.h delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.m delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.h delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.m delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.h delete mode 100755 ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.m diff --git a/ios/RCTHttpServer.m b/ios/RCTHttpServer.m index 7aa93af..46e2f8c 100644 --- a/ios/RCTHttpServer.m +++ b/ios/RCTHttpServer.m @@ -3,14 +3,14 @@ #import "React/RCTLog.h" #import "React/RCTEventDispatcher.h" -#import "WGCDWebServer.h" -#import "WGCDWebServerDataResponse.h" -#import "WGCDWebServerDataRequest.h" -#import "WGCDWebServerPrivate.h" +#import "GCDWebServer.h" +#import "GCDWebServerDataResponse.h" +#import "GCDWebServerDataRequest.h" +#import "GCDWebServerPrivate.h" #include @interface RCTHttpServer : NSObject { - WGCDWebServer* _webServer; + GCDWebServer* _webServer; NSMutableDictionary* _completionBlocks; } @end @@ -24,10 +24,10 @@ @implementation RCTHttpServer RCT_EXPORT_MODULE(); -- (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type { +- (void)initResponseReceivedFor:(GCDWebServer *)server forType:(NSString*)type { [server addDefaultHandlerForMethod:type - requestClass:[WGCDWebServerDataRequest class] - asyncProcessBlock:^(WGCDWebServerRequest* request, WGCDWebServerCompletionBlock completionBlock) { + requestClass:[GCDWebServerDataRequest class] + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { long long milliseconds = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0); int r = arc4random_uniform(1000000); @@ -38,8 +38,8 @@ - (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type } @try { - if ([WGCDWebServerTruncateHeaderValue(request.contentType) isEqualToString:@"application/json"]) { - WGCDWebServerDataRequest* dataRequest = (WGCDWebServerDataRequest*)request; + if ([GCDWebServerTruncateHeaderValue(request.contentType) isEqualToString:@"application/json"]) { + GCDWebServerDataRequest* dataRequest = (GCDWebServerDataRequest*)request; [self.bridge.eventDispatcher sendAppEventWithName:@"httpServerResponseReceived" body:@{@"requestId": requestId, @"postData": dataRequest.jsonObject, @@ -67,7 +67,7 @@ - (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type NSMutableDictionary *_requestResponses = [[NSMutableDictionary alloc] init]; dispatch_sync(dispatch_get_main_queue(), ^{ - _webServer = [[WGCDWebServer alloc] init]; + _webServer = [[GCDWebServer alloc] init]; [self initResponseReceivedFor:_webServer forType:@"POST"]; [self initResponseReceivedFor:_webServer forType:@"PUT"]; @@ -95,10 +95,10 @@ - (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type body: (NSString *) body) { NSData* data = [body dataUsingEncoding:NSUTF8StringEncoding]; - WGCDWebServerDataResponse* requestResponse = [[WGCDWebServerDataResponse alloc] initWithData:data contentType:type]; + GCDWebServerDataResponse* requestResponse = [[GCDWebServerDataResponse alloc] initWithData:data contentType:type]; requestResponse.statusCode = code; - WGCDWebServerCompletionBlock completionBlock = nil; + GCDWebServerCompletionBlock completionBlock = nil; @synchronized (self) { completionBlock = [_completionBlocks objectForKey:requestId]; [_completionBlocks removeObjectForKey:requestId]; diff --git a/ios/WGCDWebServer/Core/WGCDWebServer.h b/ios/WGCDWebServer/Core/WGCDWebServer.h deleted file mode 100755 index 34befdb..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServer.h +++ /dev/null @@ -1,619 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import - -#import "WGCDWebServerRequest.h" -#import "WGCDWebServerResponse.h" - -/** - * The WGCDWebServerMatchBlock is called for every handler added to the - * WGCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have - * been received). The block is passed the basic info for the request (HTTP method, - * URL, headers...) and must decide if it wants to handle it or not. - * - * If the handler can handle the request, the block must return a new - * WGCDWebServerRequest instance created with the same basic info. - * Otherwise, it simply returns nil. - */ -typedef WGCDWebServerRequest* (^WGCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); - -/** - * The WGCDWebServerProcessBlock is called after the HTTP request has been fully - * received (i.e. the entire HTTP body has been read). The block is passed the - * WGCDWebServerRequest created at the previous step by the WGCDWebServerMatchBlock. - * - * The block must return a WGCDWebServerResponse or nil on error, which will - * result in a 500 HTTP status code returned to the client. It's however - * recommended to return a WGCDWebServerErrorResponse on error so more useful - * information can be returned to the client. - */ -typedef WGCDWebServerResponse* (^WGCDWebServerProcessBlock)(__kindof WGCDWebServerRequest* request); - -/** - * The WGCDWebServerAsynchronousProcessBlock works like the WGCDWebServerProcessBlock - * except the WGCDWebServerResponse can be returned to the server at a later time - * allowing for asynchronous generation of the response. - * - * The block must eventually call "completionBlock" passing a WGCDWebServerResponse - * or nil on error, which will result in a 500 HTTP status code returned to the client. - * It's however recommended to return a WGCDWebServerErrorResponse on error so more - * useful information can be returned to the client. - */ -typedef void (^WGCDWebServerCompletionBlock)(WGCDWebServerResponse* response); -typedef void (^WGCDWebServerAsyncProcessBlock)(__kindof WGCDWebServerRequest* request, WGCDWebServerCompletionBlock completionBlock); - -/** - * The port used by the WGCDWebServer (NSNumber / NSUInteger). - * - * The default value is 0 i.e. let the OS pick a random port. - */ -extern NSString* const WGCDWebServerOption_Port; - -/** - * The Bonjour name used by the WGCDWebServer (NSString). If set to an empty string, - * the name will automatically take the value of the WGCDWebServerOption_ServerName - * option. If this option is set to nil, Bonjour will be disabled. - * - * The default value is nil. - */ -extern NSString* const WGCDWebServerOption_BonjourName; - -/** - * The Bonjour service type used by the WGCDWebServer (NSString). - * - * The default value is "_http._tcp", the service type for HTTP web servers. - */ -extern NSString* const WGCDWebServerOption_BonjourType; - -/** - * Request a port mapping in the NAT gateway (NSNumber / BOOL). - * - * This uses the DNSService API under the hood which supports IPv4 mappings only. - * - * The default value is NO. - * - * @warning The external port set up by the NAT gateway may be different than - * the one used by the WGCDWebServer. - */ -extern NSString* const WGCDWebServerOption_RequestNATPortMapping; - -/** - * Only accept HTTP requests coming from localhost i.e. not from the outside - * network (NSNumber / BOOL). - * - * The default value is NO. - * - * @warning Bonjour and NAT port mapping should be disabled if using this option - * since the server will not be reachable from the outside network anyway. - */ -extern NSString* const WGCDWebServerOption_BindToLocalhost; - -/** - * The maximum number of incoming HTTP requests that can be queued waiting to - * be handled before new ones are dropped (NSNumber / NSUInteger). - * - * The default value is 16. - */ -extern NSString* const WGCDWebServerOption_MaxPendingConnections; - -/** - * The value for "Server" HTTP header used by the WGCDWebServer (NSString). - * - * The default value is the WGCDWebServer class name. - */ -extern NSString* const WGCDWebServerOption_ServerName; - -/** - * The authentication method used by the WGCDWebServer - * (one of "WGCDWebServerAuthenticationMethod_..."). - * - * The default value is nil i.e. authentication is disabled. - */ -extern NSString* const WGCDWebServerOption_AuthenticationMethod; - -/** - * The authentication realm used by the WGCDWebServer (NSString). - * - * The default value is the same as the WGCDWebServerOption_ServerName option. - */ -extern NSString* const WGCDWebServerOption_AuthenticationRealm; - -/** - * The authentication accounts used by the WGCDWebServer - * (NSDictionary of username / password pairs). - * - * The default value is nil i.e. no accounts. - */ -extern NSString* const WGCDWebServerOption_AuthenticationAccounts; - -/** - * The class used by the WGCDWebServer when instantiating WGCDWebServerConnection - * (subclass of WGCDWebServerConnection). - * - * The default value is the WGCDWebServerConnection class. - */ -extern NSString* const WGCDWebServerOption_ConnectionClass; - -/** - * Allow the WGCDWebServer to pretend "HEAD" requests are actually "GET" ones - * and automatically discard the HTTP body of the response (NSNumber / BOOL). - * - * The default value is YES. - */ -extern NSString* const WGCDWebServerOption_AutomaticallyMapHEADToGET; - -/** - * The interval expressed in seconds used by the WGCDWebServer to decide how to - * coalesce calls to -webServerDidConnect: and -webServerDidDisconnect: - * (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0. - * - * The default value is 1.0 second. - */ -extern NSString* const WGCDWebServerOption_ConnectedStateCoalescingInterval; - -/** - * Set the dispatch queue priority on which server connection will be - * run (NSNumber / long). - * - * - * The default value is DISPATCH_QUEUE_PRIORITY_DEFAULT. - */ -extern NSString* const WGCDWebServerOption_DispatchQueuePriority; - -#if TARGET_OS_IPHONE - -/** - * Enables the WGCDWebServer to automatically suspend itself (as if -stop was - * called) when the iOS app goes into the background and the last - * WGCDWebServerConnection is closed, then resume itself (as if -start was called) - * when the iOS app comes back to the foreground (NSNumber / BOOL). - * - * See the README.md file for more information about this option. - * - * The default value is YES. - * - * @warning The running property will be NO while the WGCDWebServer is suspended. - */ -extern NSString* const WGCDWebServerOption_AutomaticallySuspendInBackground; - -#endif - -/** - * HTTP Basic Authentication scheme (see https://tools.ietf.org/html/rfc2617). - * - * @warning Use of this authentication scheme is not recommended as the - * passwords are sent in clear. - */ -extern NSString* const WGCDWebServerAuthenticationMethod_Basic; - -/** - * HTTP Digest Access Authentication scheme (see https://tools.ietf.org/html/rfc2617). - */ -extern NSString* const WGCDWebServerAuthenticationMethod_DigestAccess; - -@class WGCDWebServer; - -/** - * Delegate methods for WGCDWebServer. - * - * @warning These methods are always called on the main thread in a serialized way. - */ -@protocol WGCDWebServerDelegate -@optional - -/** - * This method is called after the server has successfully started. - */ -- (void)webServerDidStart:(WGCDWebServer*)server; - -/** - * This method is called after the Bonjour registration for the server has - * successfully completed. - * - * Use the "bonjourServerURL" property to retrieve the Bonjour address of the - * server. - */ -- (void)webServerDidCompleteBonjourRegistration:(WGCDWebServer*)server; - -/** - * This method is called after the NAT port mapping for the server has been - * updated. - * - * Use the "publicServerURL" property to retrieve the public address of the - * server. - */ -- (void)webServerDidUpdateNATPortMapping:(WGCDWebServer*)server; - -/** - * This method is called when the first WGCDWebServerConnection is opened by the - * server to serve a series of HTTP requests. - * - * A series of HTTP requests is considered ongoing as long as new HTTP requests - * keep coming (and new WGCDWebServerConnection instances keep being opened), - * until before the last HTTP request has been responded to (and the - * corresponding last WGCDWebServerConnection closed). - */ -- (void)webServerDidConnect:(WGCDWebServer*)server; - -/** - * This method is called when the last WGCDWebServerConnection is closed after - * the server has served a series of HTTP requests. - * - * The WGCDWebServerOption_ConnectedStateCoalescingInterval option can be used - * to have the server wait some extra delay before considering that the series - * of HTTP requests has ended (in case there some latency between consecutive - * requests). This effectively coalesces the calls to -webServerDidConnect: - * and -webServerDidDisconnect:. - */ -- (void)webServerDidDisconnect:(WGCDWebServer*)server; - -/** - * This method is called after the server has stopped. - */ -- (void)webServerDidStop:(WGCDWebServer*)server; - -@end - -/** - * The WGCDWebServer class listens for incoming HTTP requests on a given port, - * then passes each one to a "handler" capable of generating an HTTP response - * for it, which is then sent back to the client. - * - * WGCDWebServer instances can be created and used from any thread but it's - * recommended to have the main thread's runloop be running so internal callbacks - * can be handled e.g. for Bonjour registration. - * - * See the README.md file for more information about the architecture of WGCDWebServer. - */ -@interface WGCDWebServer : NSObject - -/** - * Sets the delegate for the server. - */ -@property(nonatomic, assign) id delegate; - -/** - * Returns YES if the server is currently running. - */ -@property(nonatomic, readonly, getter=isRunning) BOOL running; - -/** - * Returns the port used by the server. - * - * @warning This property is only valid if the server is running. - */ -@property(nonatomic, readonly) NSUInteger port; - -/** - * Returns the Bonjour name used by the server. - * - * @warning This property is only valid if the server is running and Bonjour - * registration has successfully completed, which can take up to a few seconds. - */ -@property(nonatomic, readonly) NSString* bonjourName; - -/** - * Returns the Bonjour service type used by the server. - * - * @warning This property is only valid if the server is running and Bonjour - * registration has successfully completed, which can take up to a few seconds. - */ -@property(nonatomic, readonly) NSString* bonjourType; - -/** - * This method is the designated initializer for the class. - */ -- (instancetype)init; - -/** - * Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests. - * - * Handlers are called in a LIFO queue, so if multiple handlers can potentially - * respond to a given request, the latest added one wins. - * - * @warning Addling handlers while the server is running is not allowed. - */ -- (void)addHandlerWithMatchBlock:(WGCDWebServerMatchBlock)matchBlock processBlock:(WGCDWebServerProcessBlock)processBlock; - -/** - * Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests. - * - * Handlers are called in a LIFO queue, so if multiple handlers can potentially - * respond to a given request, the latest added one wins. - * - * @warning Addling handlers while the server is running is not allowed. - */ -- (void)addHandlerWithMatchBlock:(WGCDWebServerMatchBlock)matchBlock asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)processBlock; - -/** - * Removes all handlers previously added to the server. - * - * @warning Removing handlers while the server is running is not allowed. - */ -- (void)removeAllHandlers; - -/** - * Starts the server with explicit options. This method is the designated way - * to start the server. - * - * Returns NO if the server failed to start and sets "error" argument if not NULL. - */ -- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error; - -/** - * Stops the server and prevents it to accepts new HTTP requests. - * - * @warning Stopping the server does not abort WGCDWebServerConnection instances - * currently handling already received HTTP requests. These connections will - * continue to execute normally until completion. - */ -- (void)stop; - -@end - -@interface WGCDWebServer (Extensions) - -/** - * Returns the server's URL. - * - * @warning This property is only valid if the server is running. - */ -@property(nonatomic, readonly) NSURL* serverURL; - -/** - * Returns the server's Bonjour URL. - * - * @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. - * Also be aware this property will not automatically update if the Bonjour hostname - * has been dynamically changed after the server started running (this should be rare). - */ -@property(nonatomic, readonly) NSURL* bonjourServerURL; - -/** - * Returns the server's public URL. - * - * @warning This property is only valid if the server is running and NAT port - * mapping is active. - */ -@property(nonatomic, readonly) NSURL* publicServerURL; - -/** - * Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS) - * using the default Bonjour name. - * - * Returns NO if the server failed to start. - */ -- (BOOL)start; - -/** - * Starts the server on a given port and with a specific Bonjour name. - * Pass a nil Bonjour name to disable Bonjour entirely or an empty string to - * use the default name. - * - * Returns NO if the server failed to start. - */ -- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; - -#if !TARGET_OS_IPHONE - -/** - * Runs the server synchronously using -startWithPort:bonjourName: until a - * SIGINT signal is received i.e. Ctrl-C. This method is intended to be used - * by command line tools. - * - * Returns NO if the server failed to start. - * - * @warning This method must be used from the main thread only. - */ -- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name; - -/** - * Runs the server synchronously using -startWithOptions: until a SIGTERM or - * SIGINT signal is received i.e. Ctrl-C in Terminal. This method is intended to - * be used by command line tools. - * - * Returns NO if the server failed to start and sets "error" argument if not NULL. - * - * @warning This method must be used from the main thread only. - */ -- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error; - -#endif - -@end - -@interface WGCDWebServer (Handlers) - -/** - * Adds a default handler to the server to handle all incoming HTTP requests - * with a given HTTP method and generate responses synchronously. - */ -- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(WGCDWebServerProcessBlock)block; - -/** - * Adds a default handler to the server to handle all incoming HTTP requests - * with a given HTTP method and generate responses asynchronously. - */ -- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)block; - -/** - * Adds a handler to the server to handle incoming HTTP requests with a given - * HTTP method and a specific case-insensitive path and generate responses - * synchronously. - */ -- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(WGCDWebServerProcessBlock)block; - -/** - * Adds a handler to the server to handle incoming HTTP requests with a given - * HTTP method and a specific case-insensitive path and generate responses - * asynchronously. - */ -- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)block; - -/** - * Adds a handler to the server to handle incoming HTTP requests with a given - * HTTP method and a path matching a case-insensitive regular expression and - * generate responses synchronously. - */ -- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(WGCDWebServerProcessBlock)block; - -/** - * Adds a handler to the server to handle incoming HTTP requests with a given - * HTTP method and a path matching a case-insensitive regular expression and - * generate responses asynchronously. - */ -- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)block; - -@end - -@interface WGCDWebServer (GETHandlers) - -/** - * Adds a handler to the server to respond to incoming "GET" HTTP requests - * with a specific case-insensitive path with in-memory data. - */ -- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge; - -/** - * Adds a handler to the server to respond to incoming "GET" HTTP requests - * with a specific case-insensitive path with a file. - */ -- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; - -/** - * Adds a handler to the server to respond to incoming "GET" HTTP requests - * with a case-insensitive path inside a base path with the corresponding file - * inside a local directory. If no local file matches the request path, a 401 - * HTTP status code is returned to the client. - * - * The "indexFilename" argument allows to specify an "index" file name to use - * when the request path corresponds to a directory. - */ -- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; - -@end - -/** - * WGCDWebServer provides its own built-in logging facility which is used by - * default. It simply sends log messages to stderr assuming it is connected - * to a terminal type device. - * - * WGCDWebServer is also compatible with a limited set of third-party logging - * facilities. If one of them is available at compile time, WGCDWebServer will - * automatically use it in place of the built-in one. - * - * Currently supported third-party logging facilities are: - * - XLFacility (by the same author as WGCDWebServer): https://github.com/swisspol/XLFacility - * - CocoaLumberjack: https://github.com/CocoaLumberjack/CocoaLumberjack - * - * For both the built-in logging facility and CocoaLumberjack, the default - * logging level is INFO (or DEBUG if the preprocessor constant "DEBUG" - * evaluates to non-zero at compile time). - * - * It's possible to have WGCDWebServer use a custom logging facility by defining - * the "__WGCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build - * settings to the name of a custom header file (escaped like \"MyLogging.h\"). - * This header file must define the following set of macros: - * - * GWS_LOG_DEBUG(...) - * GWS_LOG_VERBOSE(...) - * GWS_LOG_INFO(...) - * GWS_LOG_WARNING(...) - * GWS_LOG_ERROR(...) - * - * IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() - * macro should not do anything unless the preprocessor constant "DEBUG" evaluates - * to non-zero. - * - * The logging methods below send log messages to the same logging facility - * used by WGCDWebServer. They can be used for consistency wherever you interact - * with WGCDWebServer in your code (e.g. in the implementation of handlers). - */ -@interface WGCDWebServer (Logging) - -/** - * Sets the log level of the logging facility below which log messages are discarded. - * - * @warning The interpretation of the "level" argument depends on the logging - * facility used at compile time. - * - * If using the built-in logging facility, the log levels are as follow: - * DEBUG = 0 - * VERBOSE = 1 - * INFO = 2 - * WARNING = 3 - * ERROR = 4 - */ -+ (void)setLogLevel:(int)level; - -/** - * Logs a message to the logging facility at the VERBOSE level. - */ -- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - -/** - * Logs a message to the logging facility at the INFO level. - */ -- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - -/** - * Logs a message to the logging facility at the WARNING level. - */ -- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - -/** - * Logs a message to the logging facility at the ERROR level. - */ -- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); - -@end - -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - -@interface WGCDWebServer (Testing) - -/** - * Activates recording of HTTP requests and responses which create files in the - * current directory containing the raw data for all requests and responses. - * - * @warning The current directory must not contain any prior recording files. - */ -@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled; - -/** - * Runs tests by playing back pre-recorded HTTP requests in the given directory - * and comparing the generated responses with the pre-recorded ones. - * - * Returns the number of failed tests or -1 if server failed to start. - */ -- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path; - -@end - -#endif diff --git a/ios/WGCDWebServer/Core/WGCDWebServer.m b/ios/WGCDWebServer/Core/WGCDWebServer.m deleted file mode 100755 index 3bc0d76..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServer.m +++ /dev/null @@ -1,1324 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import -#if TARGET_OS_IPHONE -#import -#else -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ -#import -#endif -#endif -#import -#import - -#import "WGCDWebServerPrivate.h" - -#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR -#define kDefaultPort 80 -#else -#define kDefaultPort 8080 -#endif - -#define kBonjourResolutionTimeout 5.0 - -NSString* const WGCDWebServerOption_Port = @"Port"; -NSString* const WGCDWebServerOption_BonjourName = @"BonjourName"; -NSString* const WGCDWebServerOption_BonjourType = @"BonjourType"; -NSString* const WGCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping"; -NSString* const WGCDWebServerOption_BindToLocalhost = @"BindToLocalhost"; -NSString* const WGCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections"; -NSString* const WGCDWebServerOption_ServerName = @"ServerName"; -NSString* const WGCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod"; -NSString* const WGCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm"; -NSString* const WGCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts"; -NSString* const WGCDWebServerOption_ConnectionClass = @"ConnectionClass"; -NSString* const WGCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET"; -NSString* const WGCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval"; -NSString* const WGCDWebServerOption_DispatchQueuePriority = @"DispatchQueuePriority"; -#if TARGET_OS_IPHONE -NSString* const WGCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground"; -#endif - -NSString* const WGCDWebServerAuthenticationMethod_Basic = @"Basic"; -NSString* const WGCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess"; - -#if defined(__WGCDWEBSERVER_LOGGING_FACILITY_BUILTIN__) -#if DEBUG -WGCDWebServerLoggingLevel WGCDWebServerLogLevel = kWGCDWebServerLoggingLevel_Debug; -#else -WGCDWebServerLoggingLevel WGCDWebServerLogLevel = kWGCDWebServerLoggingLevel_Info; -#endif -#elif defined(__WGCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__) -#if DEBUG -DDLogLevel WGCDWebServerLogLevel = DDLogLevelDebug; -#else -DDLogLevel WGCDWebServerLogLevel = DDLogLevelInfo; -#endif -#endif - -#if !TARGET_OS_IPHONE -static BOOL _run; -#endif - -#ifdef __WGCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ - -void WGCDWebServerLogMessage(WGCDWebServerLoggingLevel level, NSString* format, ...) { - static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR"}; - static int enableLogging = -1; - if (enableLogging < 0) { - enableLogging = (isatty(STDERR_FILENO) ? 1 : 0); - } - if (enableLogging) { - va_list arguments; - va_start(arguments, format); - NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; - va_end(arguments); - fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]); - } -} - -#endif - -#if !TARGET_OS_IPHONE - -static void _SignalHandler(int signal) { - _run = NO; - printf("\n"); -} - -#endif - -#if !TARGET_OS_IPHONE || defined(__WGCDWEBSERVER_ENABLE_TESTING__) - -// This utility function is used to ensure scheduled callbacks on the main thread are called when running the server synchronously -// https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html -// The main queue works with the application’s run loop to interleave the execution of queued tasks with the execution of other event sources attached to the run loop -// TODO: Ensure all scheduled blocks on the main queue are also executed -static void _ExecuteMainThreadRunLoopSources() { - SInt32 result; - do { - result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); - } while (result == kCFRunLoopRunHandledSource); -} - -#endif - -@interface WGCDWebServerHandler () { -@private - WGCDWebServerMatchBlock _matchBlock; - WGCDWebServerAsyncProcessBlock _asyncProcessBlock; -} -@end - -@implementation WGCDWebServerHandler - -@synthesize matchBlock=_matchBlock, asyncProcessBlock=_asyncProcessBlock; - -- (id)initWithMatchBlock:(WGCDWebServerMatchBlock)matchBlock asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)processBlock { - if ((self = [super init])) { - _matchBlock = [matchBlock copy]; - _asyncProcessBlock = [processBlock copy]; - } - return self; -} - -@end - -@interface WGCDWebServer () { -@private - id __unsafe_unretained _delegate; - dispatch_queue_t _syncQueue; - dispatch_group_t _sourceGroup; - NSMutableArray* _handlers; - NSInteger _activeConnections; // Accessed through _syncQueue only - BOOL _connected; // Accessed on main thread only - CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only - - NSDictionary* _options; - NSString* _serverName; - NSString* _authenticationRealm; - NSMutableDictionary* _authenticationBasicAccounts; - NSMutableDictionary* _authenticationDigestAccounts; - Class _connectionClass; - BOOL _mapHEADToGET; - CFTimeInterval _disconnectDelay; - dispatch_queue_priority_t _dispatchQueuePriority; - NSUInteger _port; - dispatch_source_t _source4; - dispatch_source_t _source6; - CFNetServiceRef _registrationService; - CFNetServiceRef _resolutionService; - DNSServiceRef _dnsService; - CFSocketRef _dnsSocket; - CFRunLoopSourceRef _dnsSource; - NSString* _dnsAddress; - NSUInteger _dnsPort; - BOOL _bindToLocalhost; -#if TARGET_OS_IPHONE - BOOL _suspendInBackground; - UIBackgroundTaskIdentifier _backgroundTask; -#endif -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - BOOL _recording; -#endif -} -@end - -@implementation WGCDWebServer - -@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm, - authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts, - shouldAutomaticallyMapHEADToGET=_mapHEADToGET, dispatchQueuePriority=_dispatchQueuePriority; - -+ (void)initialize { - WGCDWebServerInitializeFunctions(); -} - -- (instancetype)init { - if ((self = [super init])) { - _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL); - _sourceGroup = dispatch_group_create(); - _handlers = [[NSMutableArray alloc] init]; -#if TARGET_OS_IPHONE - _backgroundTask = UIBackgroundTaskInvalid; -#endif - } - return self; -} - -- (void)dealloc { - GWS_DCHECK(_connected == NO); - GWS_DCHECK(_activeConnections == 0); - GWS_DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source - GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle - -#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE - dispatch_release(_sourceGroup); - dispatch_release(_syncQueue); -#endif -} - -#if TARGET_OS_IPHONE - -// Always called on main thread -- (void)_startBackgroundTask { - GWS_DCHECK([NSThread isMainThread]); - if (_backgroundTask == UIBackgroundTaskInvalid) { - GWS_LOG_DEBUG(@"Did start background task"); - _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ - - GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]); - [self _endBackgroundTask]; - - }]; - } else { - GWS_DNOT_REACHED(); - } -} - -#endif - -// Always called on main thread -- (void)_didConnect { - GWS_DCHECK([NSThread isMainThread]); - GWS_DCHECK(_connected == NO); - _connected = YES; - GWS_LOG_DEBUG(@"Did connect"); - -#if TARGET_OS_IPHONE - if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) { - [self _startBackgroundTask]; - } -#endif - - if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) { - [_delegate webServerDidConnect:self]; - } -} - -- (void)willStartConnection:(WGCDWebServerConnection*)connection { - dispatch_sync(_syncQueue, ^{ - - GWS_DCHECK(_activeConnections >= 0); - if (_activeConnections == 0) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (_disconnectTimer) { - CFRunLoopTimerInvalidate(_disconnectTimer); - CFRelease(_disconnectTimer); - _disconnectTimer = NULL; - } - if (_connected == NO) { - [self _didConnect]; - } - }); - } - _activeConnections += 1; - - }); -} - -#if TARGET_OS_IPHONE - -// Always called on main thread -- (void)_endBackgroundTask { - GWS_DCHECK([NSThread isMainThread]); - if (_backgroundTask != UIBackgroundTaskInvalid) { - if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) { - [self _stop]; - } - [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask]; - _backgroundTask = UIBackgroundTaskInvalid; - GWS_LOG_DEBUG(@"Did end background task"); - } -} - -#endif - -// Always called on main thread -- (void)_didDisconnect { - GWS_DCHECK([NSThread isMainThread]); - GWS_DCHECK(_connected == YES); - _connected = NO; - GWS_LOG_DEBUG(@"Did disconnect"); - -#if TARGET_OS_IPHONE - [self _endBackgroundTask]; -#endif - - if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) { - [_delegate webServerDidDisconnect:self]; - } -} - -- (void)didEndConnection:(WGCDWebServerConnection*)connection { - dispatch_sync(_syncQueue, ^{ - GWS_DCHECK(_activeConnections > 0); - _activeConnections -= 1; - if (_activeConnections == 0) { - dispatch_async(dispatch_get_main_queue(), ^{ - if ((_disconnectDelay > 0.0) && (_source4 != NULL)) { - if (_disconnectTimer) { - CFRunLoopTimerInvalidate(_disconnectTimer); - CFRelease(_disconnectTimer); - } - _disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + _disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) { - GWS_DCHECK([NSThread isMainThread]); - [self _didDisconnect]; - CFRelease(_disconnectTimer); - _disconnectTimer = NULL; - }); - CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes); - } else { - [self _didDisconnect]; - } - }); - } - }); -} - -- (NSString*)bonjourName { - CFStringRef name = _resolutionService ? CFNetServiceGetName(_resolutionService) : NULL; - return name && CFStringGetLength(name) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil; -} - -- (NSString*)bonjourType { - CFStringRef type = _resolutionService ? CFNetServiceGetType(_resolutionService) : NULL; - return type && CFStringGetLength(type) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil; -} - -- (void)addHandlerWithMatchBlock:(WGCDWebServerMatchBlock)matchBlock processBlock:(WGCDWebServerProcessBlock)processBlock { - [self addHandlerWithMatchBlock:matchBlock asyncProcessBlock:^(WGCDWebServerRequest* request, WGCDWebServerCompletionBlock completionBlock) { - completionBlock(processBlock(request)); - }]; -} - -- (void)addHandlerWithMatchBlock:(WGCDWebServerMatchBlock)matchBlock asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)processBlock { - GWS_DCHECK(_options == nil); - WGCDWebServerHandler* handler = [[WGCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock]; - [_handlers insertObject:handler atIndex:0]; -} - -- (void)removeAllHandlers { - GWS_DCHECK(_options == nil); - [_handlers removeAllObjects]; -} - -static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { - GWS_DCHECK([NSThread isMainThread]); - @autoreleasepool { - if (error->error) { - GWS_LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain); - } else { - WGCDWebServer* server = (__bridge WGCDWebServer*)info; - GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]); - if (!CFNetServiceResolveWithTimeout(server->_resolutionService, kBonjourResolutionTimeout, NULL)) { - GWS_LOG_ERROR(@"Failed starting Bonjour resolution"); - GWS_DNOT_REACHED(); - } - } - } -} - -static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { - GWS_DCHECK([NSThread isMainThread]); - @autoreleasepool { - if (error->error) { - if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) { - GWS_LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain); - } - } else { - WGCDWebServer* server = (__bridge WGCDWebServer*)info; - GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL); - if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) { - [server.delegate webServerDidCompleteBonjourRegistration:server]; - } - } - } -} - -static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) { - GWS_DCHECK([NSThread isMainThread]); - @autoreleasepool { - WGCDWebServer* server = (__bridge WGCDWebServer*)context; - if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) { - struct sockaddr_in addr4; - bzero(&addr4, sizeof(addr4)); - addr4.sin_len = sizeof(addr4); - addr4.sin_family = AF_INET; - addr4.sin_addr.s_addr = externalAddress; // Already in network byte order - server->_dnsAddress = WGCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO); - server->_dnsPort = ntohs(externalPort); - GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL); - } else { - GWS_LOG_ERROR(@"DNS service error %i", errorCode); - server->_dnsAddress = nil; - server->_dnsPort = 0; - } - if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) { - [server.delegate webServerDidUpdateNATPortMapping:server]; - } - } -} - -static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) { - GWS_DCHECK([NSThread isMainThread]); - @autoreleasepool { - WGCDWebServer* server = (__bridge WGCDWebServer*)info; - DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService); - if (status != kDNSServiceErr_NoError) { - GWS_LOG_ERROR(@"DNS service error %i", status); - } - } -} - -static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) { - id value = [options objectForKey:key]; - return value ? value : defaultValue; -} - -static inline NSString* _EncodeBase64(NSString* string) { - NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding]; -#if (TARGET_OS_IPHONE && !(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0)) || (!TARGET_OS_IPHONE && !(__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9)) - if (![data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) { - return [data base64Encoding]; - } -#endif - return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]; -} - -- (int)_createListeningSocket:(BOOL)useIPv6 - localAddress:(const void*)address - length:(socklen_t)length - maxPendingConnections:(NSUInteger)maxPendingConnections - error:(NSError**)error { - int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP); - if (listeningSocket > 0) { - int yes = 1; - setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - if (bind(listeningSocket, address, length) == 0) { - if (listen(listeningSocket, (int)maxPendingConnections) == 0) { - GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket); - return listeningSocket; - } else { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); - close(listeningSocket); - } - } else { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); - close(listeningSocket); - } - - } else { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); - } - return -1; -} - -- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 { - dispatch_group_enter(_sourceGroup); - dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0)); - dispatch_source_set_cancel_handler(source, ^{ - - @autoreleasepool { - int result = close(listeningSocket); - if (result != 0) { - GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); - } else { - GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket); - } - } - dispatch_group_leave(_sourceGroup); - - }); - dispatch_source_set_event_handler(source, ^{ - - @autoreleasepool { - struct sockaddr_storage remoteSockAddr; - socklen_t remoteAddrLen = sizeof(remoteSockAddr); - int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen); - if (socket > 0) { - NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen]; - - struct sockaddr_storage localSockAddr; - socklen_t localAddrLen = sizeof(localSockAddr); - NSData* localAddress = nil; - if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) { - localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen]; - GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6)); - } else { - GWS_DNOT_REACHED(); - } - - int noSigPipe = 1; - setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE - - WGCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened - [connection self]; // Prevent compiler from complaining about unused variable / useless statement - } else { - GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); - } - } - - }); - return source; -} - -- (BOOL)_start:(NSError**)error { - GWS_DCHECK(_source4 == NULL); - - NSUInteger port = [_GetOption(_options, WGCDWebServerOption_Port, @0) unsignedIntegerValue]; - BOOL bindToLocalhost = [_GetOption(_options, WGCDWebServerOption_BindToLocalhost, @NO) boolValue]; - NSUInteger maxPendingConnections = [_GetOption(_options, WGCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; - - struct sockaddr_in addr4; - bzero(&addr4, sizeof(addr4)); - addr4.sin_len = sizeof(addr4); - addr4.sin_family = AF_INET; - addr4.sin_port = htons(port); - addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY); - int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error]; - if (listeningSocket4 <= 0) { - return NO; - } - if (port == 0) { - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) { - port = ntohs(addr.sin_port); - } else { - GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno); - } - } - - struct sockaddr_in6 addr6; - bzero(&addr6, sizeof(addr6)); - addr6.sin6_len = sizeof(addr6); - addr6.sin6_family = AF_INET6; - addr6.sin6_port = htons(port); - addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any; - int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error]; - if (listeningSocket6 <= 0) { - close(listeningSocket4); - return NO; - } - - _serverName = [_GetOption(_options, WGCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; - NSString* authenticationMethod = _GetOption(_options, WGCDWebServerOption_AuthenticationMethod, nil); - if ([authenticationMethod isEqualToString:WGCDWebServerAuthenticationMethod_Basic]) { - _authenticationRealm = [_GetOption(_options, WGCDWebServerOption_AuthenticationRealm, _serverName) copy]; - _authenticationBasicAccounts = [[NSMutableDictionary alloc] init]; - NSDictionary* accounts = _GetOption(_options, WGCDWebServerOption_AuthenticationAccounts, @{}); - [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { - [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username]; - }]; - } else if ([authenticationMethod isEqualToString:WGCDWebServerAuthenticationMethod_DigestAccess]) { - _authenticationRealm = [_GetOption(_options, WGCDWebServerOption_AuthenticationRealm, _serverName) copy]; - _authenticationDigestAccounts = [[NSMutableDictionary alloc] init]; - NSDictionary* accounts = _GetOption(_options, WGCDWebServerOption_AuthenticationAccounts, @{}); - [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { - [_authenticationDigestAccounts setObject:WGCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username]; - }]; - } - _connectionClass = _GetOption(_options, WGCDWebServerOption_ConnectionClass, [WGCDWebServerConnection class]); - _mapHEADToGET = [_GetOption(_options, WGCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; - _disconnectDelay = [_GetOption(_options, WGCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue]; - _dispatchQueuePriority = [_GetOption(_options, WGCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue]; - - _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO]; - _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES]; - _port = port; - _bindToLocalhost = bindToLocalhost; - - NSString* bonjourName = _GetOption(_options, WGCDWebServerOption_BonjourName, nil); - NSString* bonjourType = _GetOption(_options, WGCDWebServerOption_BonjourType, @"_http._tcp"); - if (bonjourName) { - _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port); - if (_registrationService) { - CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; - - CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context); - CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); - CFStreamError streamError = {0}; - CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError); - - _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService); - if (_resolutionService) { - CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context); - CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); - } else { - GWS_LOG_ERROR(@"Failed creating CFNetService for resolution"); - } - } else { - GWS_LOG_ERROR(@"Failed creating CFNetService for registration"); - } - } - - if ([_GetOption(_options, WGCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) { - DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self); - if (status == kDNSServiceErr_NoError) { - CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; - _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context); - if (_dnsSocket) { - CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate); - _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0); - if (_dnsSource) { - CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes); - } else { - GWS_LOG_ERROR(@"Failed creating CFRunLoopSource"); - GWS_DNOT_REACHED(); - } - } else { - GWS_LOG_ERROR(@"Failed creating CFSocket"); - GWS_DNOT_REACHED(); - } - } else { - GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status); - } - } - - dispatch_resume(_source4); - dispatch_resume(_source6); - GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL); - if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [_delegate webServerDidStart:self]; - }); - } - - return YES; -} - -- (void)_stop { - GWS_DCHECK(_source4 != NULL); - - if (_dnsService) { - _dnsAddress = nil; - _dnsPort = 0; - if (_dnsSource) { - CFRunLoopSourceInvalidate(_dnsSource); - CFRelease(_dnsSource); - _dnsSource = NULL; - } - if (_dnsSocket) { - CFRelease(_dnsSocket); - _dnsSocket = NULL; - } - DNSServiceRefDeallocate(_dnsService); - _dnsService = NULL; - } - - if (_registrationService) { - if (_resolutionService) { - CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); - CFNetServiceSetClient(_resolutionService, NULL, NULL); - CFNetServiceCancel(_resolutionService); - CFRelease(_resolutionService); - _resolutionService = NULL; - } - CFNetServiceUnscheduleFromRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); - CFNetServiceSetClient(_registrationService, NULL, NULL); - CFNetServiceCancel(_registrationService); - CFRelease(_registrationService); - _registrationService = NULL; - } - - dispatch_source_cancel(_source6); - dispatch_source_cancel(_source4); - dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed -#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE - dispatch_release(_source6); -#endif - _source6 = NULL; -#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE - dispatch_release(_source4); -#endif - _source4 = NULL; - _port = 0; - _bindToLocalhost = NO; - - _serverName = nil; - _authenticationRealm = nil; - _authenticationBasicAccounts = nil; - _authenticationDigestAccounts = nil; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (_disconnectTimer) { - CFRunLoopTimerInvalidate(_disconnectTimer); - CFRelease(_disconnectTimer); - _disconnectTimer = NULL; - [self _didDisconnect]; - } - }); - - GWS_LOG_INFO(@"%@ stopped", [self class]); - if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [_delegate webServerDidStop:self]; - }); - } -} - -#if TARGET_OS_IPHONE - -- (void)_didEnterBackground:(NSNotification*)notification { - GWS_DCHECK([NSThread isMainThread]); - GWS_LOG_DEBUG(@"Did enter background"); - if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) { - [self _stop]; - } -} - -- (void)_willEnterForeground:(NSNotification*)notification { - GWS_DCHECK([NSThread isMainThread]); - GWS_LOG_DEBUG(@"Will enter foreground"); - if (!_source4) { - [self _start:NULL]; // TODO: There's probably nothing we can do on failure - } -} - -#endif - -- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error { - if (_options == nil) { - _options = options ? [options copy] : @{}; -#if TARGET_OS_IPHONE - _suspendInBackground = [_GetOption(_options, WGCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue]; - if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error]) -#else - if (![self _start:error]) -#endif - { - _options = nil; - return NO; - } -#if TARGET_OS_IPHONE - if (_suspendInBackground) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - } -#endif - return YES; - } else { - GWS_DNOT_REACHED(); - } - return NO; -} - -- (BOOL)isRunning { - return (_options ? YES : NO); -} - -- (void)stop { - if (_options) { -#if TARGET_OS_IPHONE - if (_suspendInBackground) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - } -#endif - if (_source4) { - [self _stop]; - } - _options = nil; - } else { - GWS_DNOT_REACHED(); - } -} - -@end - -@implementation WGCDWebServer (Extensions) - -- (NSURL*)serverURL { - if (_source4) { - NSString* ipAddress = _bindToLocalhost ? @"localhost" : WGCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice - if (ipAddress) { - if (_port != 80) { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]]; - } else { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", ipAddress]]; - } - } - } - return nil; -} - -- (NSURL*)bonjourServerURL { - if (_source4 && _resolutionService) { - NSString* name = (__bridge NSString*)CFNetServiceGetTargetHost(_resolutionService); - if (name.length) { - name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain - if (_port != 80) { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", name, (int)_port]]; - } else { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", name]]; - } - } - } - return nil; -} - -- (NSURL*)publicServerURL { - if (_source4 && _dnsService && _dnsAddress && _dnsPort) { - if (_dnsPort != 80) { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]]; - } else { - return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]]; - } - } - return nil; -} - -- (BOOL)start { - return [self startWithPort:kDefaultPort bonjourName:@""]; -} - -- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name { - NSMutableDictionary* options = [NSMutableDictionary dictionary]; - [options setObject:[NSNumber numberWithInteger:port] forKey:WGCDWebServerOption_Port]; - [options setValue:name forKey:WGCDWebServerOption_BonjourName]; - return [self startWithOptions:options error:NULL]; -} - -#if !TARGET_OS_IPHONE - -- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name { - NSMutableDictionary* options = [NSMutableDictionary dictionary]; - [options setObject:[NSNumber numberWithInteger:port] forKey:WGCDWebServerOption_Port]; - [options setValue:name forKey:WGCDWebServerOption_BonjourName]; - return [self runWithOptions:options error:NULL]; -} - -- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error { - GWS_DCHECK([NSThread isMainThread]); - BOOL success = NO; - _run = YES; - void (*termHandler)(int) = signal(SIGTERM, _SignalHandler); - void (*intHandler)(int) = signal(SIGINT, _SignalHandler); - if ((termHandler != SIG_ERR) && (intHandler != SIG_ERR)) { - if ([self startWithOptions:options error:error]) { - while (_run) { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true); - } - [self stop]; - success = YES; - } - _ExecuteMainThreadRunLoopSources(); - signal(SIGINT, intHandler); - signal(SIGTERM, termHandler); - } - return success; -} - -#endif - -@end - -@implementation WGCDWebServer (Handlers) - -- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(WGCDWebServerProcessBlock)block { - [self addDefaultHandlerForMethod:method requestClass:aClass asyncProcessBlock:^(WGCDWebServerRequest* request, WGCDWebServerCompletionBlock completionBlock) { - completionBlock(block(request)); - }]; -} - -- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)block { - [self addHandlerWithMatchBlock:^WGCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { - - if (![requestMethod isEqualToString:method]) { - return nil; - } - return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; - - } asyncProcessBlock:block]; -} - -- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(WGCDWebServerProcessBlock)block { - [self addHandlerForMethod:method path:path requestClass:aClass asyncProcessBlock:^(WGCDWebServerRequest* request, WGCDWebServerCompletionBlock completionBlock) { - completionBlock(block(request)); - }]; -} - -- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)block { - if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[WGCDWebServerRequest class]]) { - [self addHandlerWithMatchBlock:^WGCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { - - if (![requestMethod isEqualToString:method]) { - return nil; - } - if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) { - return nil; - } - return [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; - - } asyncProcessBlock:block]; - } else { - GWS_DNOT_REACHED(); - } -} - -- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(WGCDWebServerProcessBlock)block { - [self addHandlerForMethod:method pathRegex:regex requestClass:aClass asyncProcessBlock:^(WGCDWebServerRequest* request, WGCDWebServerCompletionBlock completionBlock) { - completionBlock(block(request)); - }]; -} - -- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(WGCDWebServerAsyncProcessBlock)block { - NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL]; - if (expression && [aClass isSubclassOfClass:[WGCDWebServerRequest class]]) { - [self addHandlerWithMatchBlock:^WGCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { - - if (![requestMethod isEqualToString:method]) { - return nil; - } - - NSArray* matches = [expression matchesInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)]; - if (matches.count == 0) { - return nil; - } - - NSMutableArray* captures = [NSMutableArray array]; - for (NSTextCheckingResult* result in matches) { - // Start at 1; index 0 is the whole string - for (NSUInteger i = 1; i < result.numberOfRanges; i++) { - NSRange range = [result rangeAtIndex:i]; - // range is {NSNotFound, 0} "if one of the capture groups did not participate in this particular match" - // see discussion in -[NSRegularExpression firstMatchInString:options:range:] - if (range.location != NSNotFound) { - [captures addObject:[urlPath substringWithRange:range]]; - } - } - } - - WGCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; - [request setAttribute:captures forKey:WGCDWebServerRequestAttribute_RegexCaptures]; - return request; - - } asyncProcessBlock:block]; - } else { - GWS_DNOT_REACHED(); - } -} - -@end - -@implementation WGCDWebServer (GETHandlers) - -- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge { - [self addHandlerForMethod:@"GET" path:path requestClass:[WGCDWebServerRequest class] processBlock:^WGCDWebServerResponse *(WGCDWebServerRequest* request) { - - WGCDWebServerResponse* response = [WGCDWebServerDataResponse responseWithData:staticData contentType:contentType]; - response.cacheControlMaxAge = cacheAge; - return response; - - }]; -} - -- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { - [self addHandlerForMethod:@"GET" path:path requestClass:[WGCDWebServerRequest class] processBlock:^WGCDWebServerResponse *(WGCDWebServerRequest* request) { - - WGCDWebServerResponse* response = nil; - if (allowRangeRequests) { - response = [WGCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment]; - [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; - } else { - response = [WGCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment]; - } - response.cacheControlMaxAge = cacheAge; - return response; - - }]; -} - -- (WGCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path { - NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path]; - if (enumerator == nil) { - return nil; - } - NSMutableString* html = [NSMutableString string]; - [html appendString:@"\n"]; - [html appendString:@"\n"]; - [html appendString:@"
    \n"]; - for (NSString* file in enumerator) { - if (![file hasPrefix:@"."]) { - NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; -#pragma clang diagnostic pop - GWS_DCHECK(escapedFile); - if ([type isEqualToString:NSFileTypeRegular]) { - [html appendFormat:@"
  • %@
  • \n", escapedFile, file]; - } else if ([type isEqualToString:NSFileTypeDirectory]) { - [html appendFormat:@"
  • %@/
  • \n", escapedFile, file]; - } - } - [enumerator skipDescendents]; - } - [html appendString:@"
\n"]; - [html appendString:@"\n"]; - return [WGCDWebServerDataResponse responseWithHTML:html]; -} - -- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { - if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) { - WGCDWebServer* __unsafe_unretained server = self; - [self addHandlerWithMatchBlock:^WGCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { - - if (![requestMethod isEqualToString:@"GET"]) { - return nil; - } - if (![urlPath hasPrefix:basePath]) { - return nil; - } - return [[WGCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; - - } processBlock:^WGCDWebServerResponse *(WGCDWebServerRequest* request) { - - WGCDWebServerResponse* response = nil; - NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]]; - NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType]; - if (fileType) { - if ([fileType isEqualToString:NSFileTypeDirectory]) { - if (indexFilename) { - NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename]; - NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType]; - if ([indexType isEqualToString:NSFileTypeRegular]) { - return [WGCDWebServerFileResponse responseWithFile:indexPath]; - } - } - response = [server _responseWithContentsOfDirectory:filePath]; - } else if ([fileType isEqualToString:NSFileTypeRegular]) { - if (allowRangeRequests) { - response = [WGCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange]; - [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; - } else { - response = [WGCDWebServerFileResponse responseWithFile:filePath]; - } - } - } - if (response) { - response.cacheControlMaxAge = cacheAge; - } else { - response = [WGCDWebServerResponse responseWithStatusCode:kWGCDWebServerHTTPStatusCode_NotFound]; - } - return response; - - }]; - } else { - GWS_DNOT_REACHED(); - } -} - -@end - -@implementation WGCDWebServer (Logging) - -+ (void)setLogLevel:(int)level { -#if defined(__WGCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__) - [XLSharedFacility setMinLogLevel:level]; -#elif defined(__WGCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__) - WGCDWebServerLogLevel = level; -#elif defined(__WGCDWEBSERVER_LOGGING_FACILITY_BUILTIN__) - WGCDWebServerLogLevel = level; -#endif -} - -- (void)logVerbose:(NSString*)format, ... { - va_list arguments; - va_start(arguments, format); - GWS_LOG_VERBOSE(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); - va_end(arguments); -} - -- (void)logInfo:(NSString*)format, ... { - va_list arguments; - va_start(arguments, format); - GWS_LOG_INFO(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); - va_end(arguments); -} - -- (void)logWarning:(NSString*)format, ... { - va_list arguments; - va_start(arguments, format); - GWS_LOG_WARNING(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); - va_end(arguments); -} - -- (void)logError:(NSString*)format, ... { - va_list arguments; - va_start(arguments, format); - GWS_LOG_ERROR(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); - va_end(arguments); -} - -@end - -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - -@implementation WGCDWebServer (Testing) - -- (void)setRecordingEnabled:(BOOL)flag { - _recording = flag; -} - -- (BOOL)isRecordingEnabled { - return _recording; -} - -static CFHTTPMessageRef _CreateHTTPMessageFromData(NSData* data, BOOL isRequest) { - CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, isRequest); - if (CFHTTPMessageAppendBytes(message, data.bytes, data.length)) { - return message; - } - CFRelease(message); - return NULL; -} - -static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData, NSUInteger port) { - CFHTTPMessageRef response = NULL; - int httpSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if (httpSocket > 0) { - struct sockaddr_in addr4; - bzero(&addr4, sizeof(addr4)); - addr4.sin_len = sizeof(port); - addr4.sin_family = AF_INET; - addr4.sin_port = htons(8080); - addr4.sin_addr.s_addr = htonl(INADDR_ANY); - if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) { - if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) { - NSMutableData* outData = [[NSMutableData alloc] initWithLength:(256 * 1024)]; - NSUInteger length = 0; - while (1) { - ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length); - if (result < 0) { - length = NSUIntegerMax; - break; - } else if (result == 0) { - break; - } - length += result; - if (length >= outData.length) { - outData.length = 2 * outData.length; - } - } - if (length != NSUIntegerMax) { - outData.length = length; - response = _CreateHTTPMessageFromData(outData, NO); - } else { - GWS_DNOT_REACHED(); - } - } - } - close(httpSocket); - } - return response; -} - -static void _LogResult(NSString* format, ...) { - va_list arguments; - va_start(arguments, format); - NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; - va_end(arguments); - fprintf(stdout, "%s\n", [message UTF8String]); -} - -- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path { - GWS_DCHECK([NSThread isMainThread]); - NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs - NSInteger result = -1; - if ([self startWithOptions:options error:NULL]) { - _ExecuteMainThreadRunLoopSources(); - - result = 0; - NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL]; - for (NSString* requestFile in files) { - if (![requestFile hasSuffix:@".request"]) { - continue; - } - @autoreleasepool { - NSString* index = [[requestFile componentsSeparatedByString:@"-"] firstObject]; - BOOL success = NO; - NSData* requestData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:requestFile]]; - if (requestData) { - CFHTTPMessageRef request = _CreateHTTPMessageFromData(requestData, YES); - if (request) { - NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(request)); - NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(request)); - _LogResult(@"[%i] %@ %@", (int)[index integerValue], requestMethod, requestURL.path); - NSString* prefix = [index stringByAppendingString:@"-"]; - for (NSString* responseFile in files) { - if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) { - NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]]; - if (responseData) { - CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO); - if (expectedResponse) { - CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port); - if (actualResponse) { - success = YES; - - CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse); - CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse); - if (actualStatusCode != expectedStatusCode) { - _LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode); - success = NO; - } - - NSDictionary* expectedHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(expectedResponse)); - NSDictionary* actualHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(actualResponse)); - for (NSString* expectedHeader in expectedHeaders) { - if ([ignoredHeaders containsObject:expectedHeader]) { - continue; - } - NSString* expectedValue = [expectedHeaders objectForKey:expectedHeader]; - NSString* actualValue = [actualHeaders objectForKey:expectedHeader]; - if (![actualValue isEqualToString:expectedValue]) { - _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", expectedHeader, expectedValue, actualValue); - success = NO; - } - } - for (NSString* actualHeader in actualHeaders) { - if (![expectedHeaders objectForKey:actualHeader]) { - _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", actualHeader, nil, [actualHeaders objectForKey:actualHeader]); - success = NO; - } - } - - NSString* expectedContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length"))); - NSData* expectedBody = CFBridgingRelease(CFHTTPMessageCopyBody(expectedResponse)); - NSString* actualContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length"))); - NSData* actualBody = CFBridgingRelease(CFHTTPMessageCopyBody(actualResponse)); - if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file) - actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)]; - } - if (![actualBody isEqualToData:expectedBody]) { - _LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length); - success = NO; -#if !TARGET_OS_IPHONE -#if DEBUG - if (WGCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) { - NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; - NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; - if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) { - NSTask* task = [[NSTask alloc] init]; - [task setLaunchPath:@"/usr/bin/opendiff"]; - [task setArguments:@[expectedPath, actualPath]]; - [task launch]; - } - } -#endif -#endif - } - - CFRelease(actualResponse); - } - CFRelease(expectedResponse); - } - } else { - GWS_DNOT_REACHED(); - } - break; - } - } - CFRelease(request); - } - } else { - GWS_DNOT_REACHED(); - } - _LogResult(@""); - if (!success) { - ++result; - } - } - _ExecuteMainThreadRunLoopSources(); - } - - [self stop]; - - _ExecuteMainThreadRunLoopSources(); - } - return result; -} - -@end - -#endif diff --git a/ios/WGCDWebServer/Core/WGCDWebServerConnection.h b/ios/WGCDWebServer/Core/WGCDWebServerConnection.h deleted file mode 100755 index cca95d6..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerConnection.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServer.h" - -@class WGCDWebServerHandler; - -/** - * The WGCDWebServerConnection class is instantiated by WGCDWebServer to handle - * each new HTTP connection. Each instance stays alive until the connection is - * closed. - * - * You cannot use this class directly, but it is made public so you can - * subclass it to override some hooks. Use the WGCDWebServerOption_ConnectionClass - * option for WGCDWebServer to install your custom subclass. - * - * @warning The WGCDWebServerConnection retains the WGCDWebServer until the - * connection is closed. - */ -@interface WGCDWebServerConnection : NSObject - -/** - * Returns the WGCDWebServer that owns the connection. - */ -@property(nonatomic, readonly) WGCDWebServer* server; - -/** - * Returns YES if the connection is using IPv6. - */ -@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6; - -/** - * Returns the address of the local peer (i.e. server) of the connection - * as a raw "struct sockaddr". - */ -@property(nonatomic, readonly) NSData* localAddressData; - -/** - * Returns the address of the local peer (i.e. server) of the connection - * as a string. - */ -@property(nonatomic, readonly) NSString* localAddressString; - -/** - * Returns the address of the remote peer (i.e. client) of the connection - * as a raw "struct sockaddr". - */ -@property(nonatomic, readonly) NSData* remoteAddressData; - -/** - * Returns the address of the remote peer (i.e. client) of the connection - * as a string. - */ -@property(nonatomic, readonly) NSString* remoteAddressString; - -/** - * Returns the total number of bytes received from the remote peer (i.e. client) - * so far. - */ -@property(nonatomic, readonly) NSUInteger totalBytesRead; - -/** - * Returns the total number of bytes sent to the remote peer (i.e. client) so far. - */ -@property(nonatomic, readonly) NSUInteger totalBytesWritten; - -@end - -/** - * Hooks to customize the behavior of WGCDWebServer HTTP connections. - * - * @warning These methods can be called on any WGCD thread. - * Be sure to also call "super" when overriding them. - */ -@interface WGCDWebServerConnection (Subclassing) - -/** - * This method is called when the connection is opened. - * - * Return NO to reject the connection e.g. after validating the local - * or remote address. - */ -- (BOOL)open; - -/** - * This method is called whenever data has been received - * from the remote peer (i.e. client). - * - * @warning Do not attempt to modify this data. - */ -- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; - -/** - * This method is called whenever data has been sent - * to the remote peer (i.e. client). - * - * @warning Do not attempt to modify this data. - */ -- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; - -/** - * This method is called after the HTTP headers have been received to - * allow replacing the request URL by another one. - * - * The default implementation returns the original URL. - */ -- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers; - -/** - * Assuming a valid HTTP request was received, this method is called before - * the request is processed. - * - * Return a non-nil WGCDWebServerResponse to bypass the request processing entirely. - * - * The default implementation checks for HTTP authentication if applicable - * and returns a barebone 401 status code response if authentication failed. - */ -- (WGCDWebServerResponse*)preflightRequest:(WGCDWebServerRequest*)request; - -/** - * Assuming a valid HTTP request was received and -preflightRequest: returned nil, - * this method is called to process the request by executing the handler's - * process block. - */ -- (void)processRequest:(WGCDWebServerRequest*)request completion:(WGCDWebServerCompletionBlock)completion; - -/** - * Assuming a valid HTTP request was received and either -preflightRequest: - * or -processRequest:completion: returned a non-nil WGCDWebServerResponse, - * this method is called to override the response. - * - * You can either modify the current response and return it, or return a - * completely new one. - * - * The default implementation replaces any response matching the "ETag" or - * "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) - * one. - */ -- (WGCDWebServerResponse*)overrideResponse:(WGCDWebServerResponse*)response forRequest:(WGCDWebServerRequest*)request; - -/** - * This method is called if any error happens while validing or processing - * the request or if no WGCDWebServerResponse was generated during processing. - * - * @warning If the request was invalid (e.g. the HTTP headers were malformed), - * the "request" argument will be nil. - */ -- (void)abortRequest:(WGCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; - -/** - * Called when the connection is closed. - */ -- (void)close; - -@end diff --git a/ios/WGCDWebServer/Core/WGCDWebServerConnection.m b/ios/WGCDWebServer/Core/WGCDWebServerConnection.m deleted file mode 100755 index 91a1292..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerConnection.m +++ /dev/null @@ -1,846 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import -#import -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ -#import -#endif - -#import "WGCDWebServerPrivate.h" - -#define kHeadersReadCapacity (1 * 1024) -#define kBodyReadCapacity (256 * 1024) - -typedef void (^ReadDataCompletionBlock)(BOOL success); -typedef void (^ReadHeadersCompletionBlock)(NSData* extraData); -typedef void (^ReadBodyCompletionBlock)(BOOL success); - -typedef void (^WriteDataCompletionBlock)(BOOL success); -typedef void (^WriteHeadersCompletionBlock)(BOOL success); -typedef void (^WriteBodyCompletionBlock)(BOOL success); - -static NSData* _CRLFData = nil; -static NSData* _CRLFCRLFData = nil; -static NSData* _continueData = nil; -static NSData* _lastChunkData = nil; -static NSString* _digestAuthenticationNonce = nil; -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ -static int32_t _connectionCounter = 0; -#endif - -@interface WGCDWebServerConnection () { -@private - WGCDWebServer* _server; - NSData* _localAddress; - NSData* _remoteAddress; - CFSocketNativeHandle _socket; - NSUInteger _bytesRead; - NSUInteger _bytesWritten; - BOOL _virtualHEAD; - - CFHTTPMessageRef _requestMessage; - WGCDWebServerRequest* _request; - WGCDWebServerHandler* _handler; - CFHTTPMessageRef _responseMessage; - WGCDWebServerResponse* _response; - NSInteger _statusCode; - - BOOL _opened; -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - NSUInteger _connectionIndex; - NSString* _requestPath; - int _requestFD; - NSString* _responsePath; - int _responseFD; -#endif -} -@end - -@implementation WGCDWebServerConnection (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 WGCDWebServerConnection (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 WGCDWebServerConnection - -@synthesize server=_server, localAddressData=_localAddress, remoteAddressData=_remoteAddress, totalBytesRead=_bytesRead, totalBytesWritten=_bytesWritten; - -+ (void)initialize { - if (_CRLFData == nil) { - _CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2]; - GWS_DCHECK(_CRLFData); - } - if (_CRLFCRLFData == nil) { - _CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; - GWS_DCHECK(_CRLFCRLFData); - } - if (_continueData == nil) { - CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1); - _continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); - CFRelease(message); - GWS_DCHECK(_continueData); - } - if (_lastChunkData == nil) { - _lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5]; - } - if (_digestAuthenticationNonce == nil) { - CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); - _digestAuthenticationNonce = WGCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid))); - CFRelease(uuid); - } -} - -- (BOOL)isUsingIPv6 { - const struct sockaddr* localSockAddr = _localAddress.bytes; - return (localSockAddr->sa_family == AF_INET6); -} - -- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode { - _statusCode = statusCode; - _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close")); - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (__bridge CFStringRef)_server.serverName); - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (__bridge CFStringRef)WGCDWebServerFormatRFC822([NSDate date])); -} - -- (void)_startProcessingRequest { - GWS_DCHECK(_responseMessage == NULL); - - WGCDWebServerResponse* preflightResponse = [self preflightRequest:_request]; - if (preflightResponse) { - [self _finishProcessingRequest:preflightResponse]; - } else { - [self processRequest:_request completion:^(WGCDWebServerResponse* processResponse) { - [self _finishProcessingRequest:processResponse]; - }]; - } -} - -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html -- (void)_finishProcessingRequest:(WGCDWebServerResponse*)response { - GWS_DCHECK(_responseMessage == NULL); - BOOL hasBody = NO; - - if (response) { - response = [self overrideResponse:response forRequest:_request]; - } - if (response) { - if ([response hasBody]) { - [response prepareForReading]; - hasBody = !_virtualHEAD; - } - NSError* error = nil; - if (hasBody && ![response performOpen:&error]) { - GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error); - } else { - _response = response; - } - } - - if (_response) { - [self _initializeResponseHeadersWithStatusCode:_response.statusCode]; - if (_response.lastModifiedDate) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)WGCDWebServerFormatRFC822(_response.lastModifiedDate)); - } - if (_response.eTag) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag); - } - if ((_response.statusCode >= 200) && (_response.statusCode < 300)) { - if (_response.cacheControlMaxAge > 0) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]); - } else { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache")); - } - } - if (_response.contentType != nil) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)WGCDWebServerNormalizeHeaderValue(_response.contentType)); - } - if (_response.contentLength != NSUIntegerMax) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]); - } - if (_response.usesChunkedTransferEncoding) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked")); - } - [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) { - CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); - }]; - [self _writeHeadersWithCompletionBlock:^(BOOL success) { - - if (success) { - if (hasBody) { - [self _writeBodyWithCompletionBlock:^(BOOL successInner) { - - [_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent - - }]; - } - } else if (hasBody) { - [_response performClose]; - } - - }]; - } else { - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - } - -} - -- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData { - NSError* error = nil; - if (![_request performOpen:&error]) { - GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - return; - } - - if (initialData.length) { - if (![_request performWriteData:initialData error:&error]) { - GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); - if (![_request performClose:&error]) { - GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); - } - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - return; - } - length -= initialData.length; - } - - if (length) { - [self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) { - - NSError* localError = nil; - if ([_request performClose:&localError]) { - [self _startProcessingRequest]; - } else { - GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - } - - }]; - } else { - if ([_request performClose:&error]) { - [self _startProcessingRequest]; - } else { - GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - } - } -} - -- (void)_readChunkedBodyWithInitialData:(NSData*)initialData { - NSError* error = nil; - if (![_request performOpen:&error]) { - GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - return; - } - - NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData]; - [self _readNextBodyChunk:chunkData completionBlock:^(BOOL success) { - - NSError* localError = nil; - if ([_request performClose:&localError]) { - [self _startProcessingRequest]; - } else { - GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - } - - }]; -} - -- (void)_readRequestHeaders { - _requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true); - NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity]; - [self _readHeaders:headersData withCompletionBlock:^(NSData* extraData) { - - if (extraData) { - NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase - if (_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) { - requestMethod = @"GET"; - _virtualHEAD = YES; - } - NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones - NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(_requestMessage)); - if (requestURL) { - requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders]; - GWS_DCHECK(requestURL); - } - NSString* requestPath = requestURL ? WGCDWebServerUnescapeURLString(CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash - NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped; - NSDictionary* requestQuery = queryString ? WGCDWebServerParseURLEncodedForm(queryString) : @{}; - if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) { - for (_handler in _server.handlers) { - _request = _handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery); - if (_request) { - break; - } - } - if (_request) { - _request.localAddressData = self.localAddressData; - _request.remoteAddressData = self.remoteAddressData; - if ([_request hasBody]) { - [_request prepareForWriting]; - if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) { - NSString* expectHeader = [requestHeaders objectForKey:@"Expect"]; - if (expectHeader) { - if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing - [self _writeData:_continueData withCompletionBlock:^(BOOL success) { - - if (success) { - if (_request.usesChunkedTransferEncoding) { - [self _readChunkedBodyWithInitialData:extraData]; - } else { - [self _readBodyWithLength:_request.contentLength initialData:extraData]; - } - } - - }]; - } else { - GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_ExpectationFailed]; - } - } else { - if (_request.usesChunkedTransferEncoding) { - [self _readChunkedBodyWithInitialData:extraData]; - } else { - [self _readBodyWithLength:_request.contentLength initialData:extraData]; - } - } - } else { - GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_BadRequest]; - } - } else { - [self _startProcessingRequest]; - } - } else { - _request = [[WGCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery]; - GWS_DCHECK(_request); - [self abortRequest:_request withStatusCode:kWGCDWebServerHTTPStatusCode_MethodNotAllowed]; - } - } else { - [self abortRequest:nil withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - GWS_DNOT_REACHED(); - } - } else { - [self abortRequest:nil withStatusCode:kWGCDWebServerHTTPStatusCode_InternalServerError]; - } - - }]; -} - -- (id)initWithServer:(WGCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket { - if ((self = [super init])) { - _server = server; - _localAddress = localAddress; - _remoteAddress = remoteAddress; - _socket = socket; - GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket); - - [_server willStartConnection:self]; - - if (![self open]) { - close(_socket); - return nil; - } - _opened = YES; - - [self _readRequestHeaders]; - } - return self; -} - -- (NSString*)localAddressString { - return WGCDWebServerStringFromSockAddr(_localAddress.bytes, YES); -} - -- (NSString*)remoteAddressString { - return WGCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES); -} - -- (void)dealloc { - int result = close(_socket); - if (result != 0) { - GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno); - } else { - GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket); - } - - if (_opened) { - [self close]; - } - - [_server didEndConnection:self]; - - if (_requestMessage) { - CFRelease(_requestMessage); - } - - if (_responseMessage) { - CFRelease(_responseMessage); - } -} - -@end - -@implementation WGCDWebServerConnection (Subclassing) - -- (BOOL)open { -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - if (_server.recordingEnabled) { - _connectionIndex = OSAtomicIncrement32(&_connectionCounter); - - _requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; - _requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - GWS_DCHECK(_requestFD > 0); - - _responsePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; - _responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - GWS_DCHECK(_responseFD > 0); - } -#endif - - return YES; -} - -- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length { - GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket); - _bytesRead += length; - -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) { - GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno); - close(_requestFD); - _requestFD = 0; - } -#endif -} - -- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length { - GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket); - _bytesWritten += length; - -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) { - GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno); - close(_responseFD); - _responseFD = 0; - } -#endif -} - -- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers { - return url; -} - -// https://tools.ietf.org/html/rfc2617 -- (WGCDWebServerResponse*)preflightRequest:(WGCDWebServerRequest*)request { - GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead); - WGCDWebServerResponse* response = nil; - if (_server.authenticationBasicAccounts) { - __block BOOL authenticated = NO; - NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; - if ([authorizationHeader hasPrefix:@"Basic "]) { - NSString* basicAccount = [authorizationHeader substringFromIndex:6]; - [_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) { - if ([basicAccount isEqualToString:digest]) { - authenticated = YES; - *stop = YES; - } - }]; - } - if (!authenticated) { - response = [WGCDWebServerResponse responseWithStatusCode:kWGCDWebServerHTTPStatusCode_Unauthorized]; - [response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"]; - } - } else if (_server.authenticationDigestAccounts) { - BOOL authenticated = NO; - BOOL isStaled = NO; - NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; - if ([authorizationHeader hasPrefix:@"Digest "]) { - NSString* realm = WGCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm"); - if ([realm isEqualToString:_server.authenticationRealm]) { - NSString* nonce = WGCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce"); - if ([nonce isEqualToString:_digestAuthenticationNonce]) { - NSString* username = WGCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username"); - NSString* uri = WGCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri"); - NSString* actualResponse = WGCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response"); - NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username]; - NSString* ha2 = WGCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required - NSString* expectedResponse = WGCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2); - if ([actualResponse isEqualToString:expectedResponse]) { - authenticated = YES; - } - } else if (nonce.length) { - isStaled = YES; - } - } - } - if (!authenticated) { - response = [WGCDWebServerResponse responseWithStatusCode:kWGCDWebServerHTTPStatusCode_Unauthorized]; - [response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop") - } - } - return response; -} - -- (void)processRequest:(WGCDWebServerRequest*)request completion:(WGCDWebServerCompletionBlock)completion { - GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead); - _handler.asyncProcessBlock(request, [completion copy]); -} - -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 -static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) { - if (requestLastModified && responseLastModified) { - if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) { - return YES; - } - } - if (requestETag && responseETag) { // Per the specs "If-None-Match" must be checked after "If-Modified-Since" - if ([requestETag isEqualToString:@"*"]) { - return YES; - } - if ([responseETag isEqualToString:requestETag]) { - return YES; - } - } - return NO; -} - -- (WGCDWebServerResponse*)overrideResponse:(WGCDWebServerResponse*)response forRequest:(WGCDWebServerRequest*)request { - if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) { - NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kWGCDWebServerHTTPStatusCode_NotModified : kWGCDWebServerHTTPStatusCode_PreconditionFailed; - WGCDWebServerResponse* newResponse = [WGCDWebServerResponse responseWithStatusCode:code]; - newResponse.cacheControlMaxAge = response.cacheControlMaxAge; - newResponse.lastModifiedDate = response.lastModifiedDate; - newResponse.eTag = response.eTag; - GWS_DCHECK(newResponse); - return newResponse; - } - return response; -} - -- (void)abortRequest:(WGCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode { - GWS_DCHECK(_responseMessage == NULL); - GWS_DCHECK((statusCode >= 400) && (statusCode < 600)); - [self _initializeResponseHeadersWithStatusCode:statusCode]; - [self _writeHeadersWithCompletionBlock:^(BOOL success) { - ; // Nothing more to do - }]; - GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket); -} - -- (void)close { -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - if (_requestPath) { - BOOL success = NO; - NSError* error = nil; - if (_requestFD > 0) { - close(_requestFD); - NSString* name = [NSString stringWithFormat:@"%03lu-%@.request", (unsigned long)_connectionIndex, _virtualHEAD ? @"HEAD" : _request.method]; - success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; - } - if (!success) { - GWS_LOG_ERROR(@"Failed saving recorded request: %@", error); - GWS_DNOT_REACHED(); - } - unlink([_requestPath fileSystemRepresentation]); - } - - if (_responsePath) { - BOOL success = NO; - NSError* error = nil; - if (_responseFD > 0) { - close(_responseFD); - NSString* name = [NSString stringWithFormat:@"%03lu-%i.response", (unsigned long)_connectionIndex, (int)_statusCode]; - success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; - } - if (!success) { - GWS_LOG_ERROR(@"Failed saving recorded response: %@", error); - GWS_DNOT_REACHED(); - } - unlink([_responsePath fileSystemRepresentation]); - } -#endif - - if (_request) { - GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten); - } else { - GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten); - } -} - -@end diff --git a/ios/WGCDWebServer/Core/WGCDWebServerFunctions.h b/ios/WGCDWebServer/Core/WGCDWebServerFunctions.h deleted file mode 100755 index 852355f..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerFunctions.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Converts a file extension to the corresponding MIME type. - * If there is no match, "application/octet-stream" is returned. - */ -NSString* WGCDWebServerGetMimeTypeForExtension(NSString* extension); - -/** - * Add percent-escapes to a string so it can be used in a URL. - * The legal characters ":@/?&=+" are also escaped to ensure compatibility - * with URL encoded forms and URL queries. - */ -NSString* WGCDWebServerEscapeURLString(NSString* string); - -/** - * Unescapes a URL percent-encoded string. - */ -NSString* WGCDWebServerUnescapeURLString(NSString* string); - -/** - * Extracts the unescaped names and values from an - * "application/x-www-form-urlencoded" form. - * http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 - */ -NSDictionary* WGCDWebServerParseURLEncodedForm(NSString* form); - -/** - * On OS X, returns the IPv4 or IPv6 address as a string of the primary - * connected service or nil if not available. - * - * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi - * interface if connected or nil otherwise. - */ -NSString* WGCDWebServerGetPrimaryIPAddress(BOOL useIPv6); - -/** - * Converts a date into a string using RFC822 formatting. - * https://tools.ietf.org/html/rfc822#section-5 - * https://tools.ietf.org/html/rfc1123#section-5.2.14 - */ -NSString* WGCDWebServerFormatRFC822(NSDate* date); - -/** - * Converts a RFC822 formatted string into a date. - * https://tools.ietf.org/html/rfc822#section-5 - * https://tools.ietf.org/html/rfc1123#section-5.2.14 - * - * @warning Timezones other than GMT are not supported by this function. - */ -NSDate* WGCDWebServerParseRFC822(NSString* string); - -/** - * Converts a date into a string using IOS 8601 formatting. - * http://tools.ietf.org/html/rfc3339#section-5.6 - */ -NSString* WGCDWebServerFormatISO8601(NSDate* date); - -/** - * Converts a ISO 8601 formatted string into a date. - * http://tools.ietf.org/html/rfc3339#section-5.6 - * - * @warning Only "calendar" variant is supported at this time and timezones - * other than GMT are not supported either. - */ -NSDate* WGCDWebServerParseISO8601(NSString* string); - -#ifdef __cplusplus -} -#endif diff --git a/ios/WGCDWebServer/Core/WGCDWebServerFunctions.m b/ios/WGCDWebServer/Core/WGCDWebServerFunctions.m deleted file mode 100755 index 3dcf046..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerFunctions.m +++ /dev/null @@ -1,307 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import -#if TARGET_OS_IPHONE -#import -#else -#import -#endif -#import - -#import -#import -#import - -#import "WGCDWebServerPrivate.h" - -static NSDateFormatter* _dateFormatterRFC822 = nil; -static NSDateFormatter* _dateFormatterISO8601 = nil; -static dispatch_queue_t _dateFormatterQueue = NULL; - -// TODO: Handle RFC 850 and ANSI C's asctime() format -void WGCDWebServerInitializeFunctions() { - GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread - if (_dateFormatterRFC822 == nil) { - _dateFormatterRFC822 = [[NSDateFormatter alloc] init]; - _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; - _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"; - _dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - GWS_DCHECK(_dateFormatterRFC822); - } - if (_dateFormatterISO8601 == nil) { - _dateFormatterISO8601 = [[NSDateFormatter alloc] init]; - _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; - _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'"; - _dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - GWS_DCHECK(_dateFormatterISO8601); - } - if (_dateFormatterQueue == NULL) { - _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); - GWS_DCHECK(_dateFormatterQueue); - } -} - -NSString* WGCDWebServerNormalizeHeaderValue(NSString* value) { - if (value) { - NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive - if (range.location != NSNotFound) { - value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]]; - } else { - value = [value lowercaseString]; - } - } - return value; -} - -NSString* WGCDWebServerTruncateHeaderValue(NSString* value) { - NSRange range = [value rangeOfString:@";"]; - return range.location != NSNotFound ? [value substringToIndex:range.location] : value; -} - -NSString* WGCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { - NSString* parameter = nil; - NSScanner* scanner = [[NSScanner alloc] initWithString:value]; - [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive - NSString* string = [NSString stringWithFormat:@"%@=", name]; - if ([scanner scanUpToString:string intoString:NULL]) { - [scanner scanString:string intoString:NULL]; - if ([scanner scanString:@"\"" intoString:NULL]) { - [scanner scanUpToString:@"\"" intoString:¶meter]; - } else { - [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter]; - } - } - return parameter; -} - -// http://www.w3schools.com/tags/ref_charactersets.asp -NSStringEncoding WGCDWebServerStringEncodingFromCharset(NSString* charset) { - NSStringEncoding encoding = kCFStringEncodingInvalidId; - if (charset) { - encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset)); - } - return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding); -} - -NSString* WGCDWebServerFormatRFC822(NSDate* date) { - __block NSString* string; - dispatch_sync(_dateFormatterQueue, ^{ - string = [_dateFormatterRFC822 stringFromDate:date]; - }); - return string; -} - -NSDate* WGCDWebServerParseRFC822(NSString* string) { - __block NSDate* date; - dispatch_sync(_dateFormatterQueue, ^{ - date = [_dateFormatterRFC822 dateFromString:string]; - }); - return date; -} - -NSString* WGCDWebServerFormatISO8601(NSDate* date) { - __block NSString* string; - dispatch_sync(_dateFormatterQueue, ^{ - string = [_dateFormatterISO8601 stringFromDate:date]; - }); - return string; -} - -NSDate* WGCDWebServerParseISO8601(NSString* string) { - __block NSDate* date; - dispatch_sync(_dateFormatterQueue, ^{ - date = [_dateFormatterISO8601 dateFromString:string]; - }); - return date; -} - -BOOL WGCDWebServerIsTextContentType(NSString* type) { - return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]); -} - -NSString* WGCDWebServerDescribeData(NSData* data, NSString* type) { - if (WGCDWebServerIsTextContentType(type)) { - NSString* charset = WGCDWebServerExtractHeaderValueParameter(type, @"charset"); - NSString* string = [[NSString alloc] initWithData:data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; - if (string) { - return string; - } - } - return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length]; -} - -NSString* WGCDWebServerGetMimeTypeForExtension(NSString* extension) { - static NSDictionary* _overrides = nil; - if (_overrides == nil) { - _overrides = [[NSDictionary alloc] initWithObjectsAndKeys: - @"text/css", @"css", - nil]; - } - NSString* mimeType = nil; - extension = [extension lowercaseString]; - if (extension.length) { - mimeType = [_overrides objectForKey:extension]; - if (mimeType == nil) { - CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); - if (uti) { - mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); - CFRelease(uti); - } - } - } - return mimeType ? mimeType : kWGCDWebServerDefaultMimeType; -} - -NSString* WGCDWebServerEscapeURLString(NSString* string) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8)); -#pragma clang diagnostic pop -} - -NSString* WGCDWebServerUnescapeURLString(NSString* string) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8)); -#pragma clang diagnostic pop -} - -NSDictionary* WGCDWebServerParseURLEncodedForm(NSString* form) { - NSMutableDictionary* parameters = [NSMutableDictionary dictionary]; - NSScanner* scanner = [[NSScanner alloc] initWithString:form]; - [scanner setCharactersToBeSkipped:nil]; - while (1) { - NSString* key = nil; - if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) { - break; - } - [scanner setScanLocation:([scanner scanLocation] + 1)]; - - NSString* value = nil; - [scanner scanUpToString:@"&" intoString:&value]; - if (value == nil) { - value = @""; - } - - key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; - NSString* unescapedKey = key ? WGCDWebServerUnescapeURLString(key) : nil; - value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; - NSString* unescapedValue = value ? WGCDWebServerUnescapeURLString(value) : nil; - if (unescapedKey && unescapedValue) { - [parameters setObject:unescapedValue forKey:unescapedKey]; - } else { - GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value); - GWS_DNOT_REACHED(); - } - - if ([scanner isAtEnd]) { - break; - } - [scanner setScanLocation:([scanner scanLocation] + 1)]; - } - return parameters; -} - -NSString* WGCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) { - NSString* string = nil; - char hostBuffer[NI_MAXHOST]; - char serviceBuffer[NI_MAXSERV]; - if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) { - string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer]; - } else { - GWS_DNOT_REACHED(); - } - return string; -} - -NSString* WGCDWebServerGetPrimaryIPAddress(BOOL useIPv6) { - NSString* address = nil; -#if TARGET_OS_IPHONE -#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV - const char* primaryInterface = "en0"; // WiFi interface on iOS -#endif -#else - const char* primaryInterface = NULL; - SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("WGCDWebServer"), NULL, NULL); - if (store) { - CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same - if (info) { - primaryInterface = [[NSString stringWithString:[(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String]; - CFRelease(info); - } - CFRelease(store); - } - if (primaryInterface == NULL) { - primaryInterface = "lo0"; - } -#endif - struct ifaddrs* list; - if (getifaddrs(&list) >= 0) { - for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) { -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV - // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator - // Assumption holds for Apple TV running tvOS - if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1")) -#else - if (strcmp(ifap->ifa_name, primaryInterface)) -#endif - { - continue; - } - if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) { - address = WGCDWebServerStringFromSockAddr(ifap->ifa_addr, NO); - break; - } - } - freeifaddrs(list); - } - return address; -} - -NSString* WGCDWebServerComputeMD5Digest(NSString* format, ...) { - va_list arguments; - va_start(arguments, format); - const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String]; - va_end(arguments); - unsigned char md5[CC_MD5_DIGEST_LENGTH]; - CC_MD5(string, (CC_LONG)strlen(string), md5); - char buffer[2 * CC_MD5_DIGEST_LENGTH + 1]; - for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) { - unsigned char byte = md5[i]; - unsigned char byteHi = (byte & 0xF0) >> 4; - buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi; - unsigned char byteLo = byte & 0x0F; - buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo; - } - buffer[2 * CC_MD5_DIGEST_LENGTH] = 0; - return [NSString stringWithUTF8String:buffer]; -} diff --git a/ios/WGCDWebServer/Core/WGCDWebServerHTTPStatusCodes.h b/ios/WGCDWebServer/Core/WGCDWebServerHTTPStatusCodes.h deleted file mode 100755 index 0bb07cc..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerHTTPStatusCodes.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html -// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - -#import - -/** - * Convenience constants for "informational" HTTP status codes. - */ -typedef NS_ENUM(NSInteger, WGCDWebServerInformationalHTTPStatusCode) { - kWGCDWebServerHTTPStatusCode_Continue = 100, - kWGCDWebServerHTTPStatusCode_SwitchingProtocols = 101, - kWGCDWebServerHTTPStatusCode_Processing = 102 -}; - -/** - * Convenience constants for "successful" HTTP status codes. - */ -typedef NS_ENUM(NSInteger, WGCDWebServerSuccessfulHTTPStatusCode) { - kWGCDWebServerHTTPStatusCode_OK = 200, - kWGCDWebServerHTTPStatusCode_Created = 201, - kWGCDWebServerHTTPStatusCode_Accepted = 202, - kWGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203, - kWGCDWebServerHTTPStatusCode_NoContent = 204, - kWGCDWebServerHTTPStatusCode_ResetContent = 205, - kWGCDWebServerHTTPStatusCode_PartialContent = 206, - kWGCDWebServerHTTPStatusCode_MultiStatus = 207, - kWGCDWebServerHTTPStatusCode_AlreadyReported = 208 -}; - -/** - * Convenience constants for "redirection" HTTP status codes. - */ -typedef NS_ENUM(NSInteger, WGCDWebServerRedirectionHTTPStatusCode) { - kWGCDWebServerHTTPStatusCode_MultipleChoices = 300, - kWGCDWebServerHTTPStatusCode_MovedPermanently = 301, - kWGCDWebServerHTTPStatusCode_Found = 302, - kWGCDWebServerHTTPStatusCode_SeeOther = 303, - kWGCDWebServerHTTPStatusCode_NotModified = 304, - kWGCDWebServerHTTPStatusCode_UseProxy = 305, - kWGCDWebServerHTTPStatusCode_TemporaryRedirect = 307, - kWGCDWebServerHTTPStatusCode_PermanentRedirect = 308 -}; - -/** - * Convenience constants for "client error" HTTP status codes. - */ -typedef NS_ENUM(NSInteger, WGCDWebServerClientErrorHTTPStatusCode) { - kWGCDWebServerHTTPStatusCode_BadRequest = 400, - kWGCDWebServerHTTPStatusCode_Unauthorized = 401, - kWGCDWebServerHTTPStatusCode_PaymentRequired = 402, - kWGCDWebServerHTTPStatusCode_Forbidden = 403, - kWGCDWebServerHTTPStatusCode_NotFound = 404, - kWGCDWebServerHTTPStatusCode_MethodNotAllowed = 405, - kWGCDWebServerHTTPStatusCode_NotAcceptable = 406, - kWGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407, - kWGCDWebServerHTTPStatusCode_RequestTimeout = 408, - kWGCDWebServerHTTPStatusCode_Conflict = 409, - kWGCDWebServerHTTPStatusCode_Gone = 410, - kWGCDWebServerHTTPStatusCode_LengthRequired = 411, - kWGCDWebServerHTTPStatusCode_PreconditionFailed = 412, - kWGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413, - kWGCDWebServerHTTPStatusCode_RequestURITooLong = 414, - kWGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415, - kWGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416, - kWGCDWebServerHTTPStatusCode_ExpectationFailed = 417, - kWGCDWebServerHTTPStatusCode_UnprocessableEntity = 422, - kWGCDWebServerHTTPStatusCode_Locked = 423, - kWGCDWebServerHTTPStatusCode_FailedDependency = 424, - kWGCDWebServerHTTPStatusCode_UpgradeRequired = 426, - kWGCDWebServerHTTPStatusCode_PreconditionRequired = 428, - kWGCDWebServerHTTPStatusCode_TooManyRequests = 429, - kWGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431 -}; - -/** - * Convenience constants for "server error" HTTP status codes. - */ -typedef NS_ENUM(NSInteger, WGCDWebServerServerErrorHTTPStatusCode) { - kWGCDWebServerHTTPStatusCode_InternalServerError = 500, - kWGCDWebServerHTTPStatusCode_NotImplemented = 501, - kWGCDWebServerHTTPStatusCode_BadGateway = 502, - kWGCDWebServerHTTPStatusCode_ServiceUnavailable = 503, - kWGCDWebServerHTTPStatusCode_GatewayTimeout = 504, - kWGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505, - kWGCDWebServerHTTPStatusCode_InsufficientStorage = 507, - kWGCDWebServerHTTPStatusCode_LoopDetected = 508, - kWGCDWebServerHTTPStatusCode_NotExtended = 510, - kWGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511 -}; diff --git a/ios/WGCDWebServer/Core/WGCDWebServerPrivate.h b/ios/WGCDWebServer/Core/WGCDWebServerPrivate.h deleted file mode 100755 index de40285..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerPrivate.h +++ /dev/null @@ -1,226 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import -#import - -/** - * All WGCDWebServer headers. - */ - -#import "WGCDWebServerHTTPStatusCodes.h" -#import "WGCDWebServerFunctions.h" - -#import "WGCDWebServer.h" -#import "WGCDWebServerConnection.h" - -#import "WGCDWebServerDataRequest.h" -#import "WGCDWebServerFileRequest.h" -#import "WGCDWebServerMultiPartFormRequest.h" -#import "WGCDWebServerURLEncodedFormRequest.h" - -#import "WGCDWebServerDataResponse.h" -#import "WGCDWebServerErrorResponse.h" -#import "WGCDWebServerFileResponse.h" -#import "WGCDWebServerStreamedResponse.h" - -/** - * Check if a custom logging facility should be used instead. - */ - -#if defined(__WGCDWEBSERVER_LOGGING_HEADER__) - -#define __WGCDWEBSERVER_LOGGING_FACILITY_CUSTOM__ - -#import __WGCDWEBSERVER_LOGGING_HEADER__ - -/** - * Automatically detect if XLFacility is available and if so use it as a - * logging facility. - */ - -#elif defined(__has_include) && __has_include("XLFacilityMacros.h") - -#define __WGCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__ - -#undef XLOG_TAG -#define XLOG_TAG @"gcdwebserver.internal" - -#import "XLFacilityMacros.h" - -#define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__) -#define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__) -#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__) -#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__) -#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__) - -#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__) -#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE() - -/** - * Automatically detect if CocoaLumberJack is available and if so use - * it as a logging facility. - */ - -#elif defined(__has_include) && __has_include("CocoaLumberjack/CocoaLumberjack.h") - -#import - -#define __WGCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__ - -#undef LOG_LEVEL_DEF -#define LOG_LEVEL_DEF WGCDWebServerLogLevel -extern DDLogLevel WGCDWebServerLogLevel; - -#define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__) -#define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__) -#define GWS_LOG_INFO(...) DDLogInfo(__VA_ARGS__) -#define GWS_LOG_WARNING(...) DDLogWarn(__VA_ARGS__) -#define GWS_LOG_ERROR(...) DDLogError(__VA_ARGS__) - -/** - * If all of the above fail, then use WGCDWebServer built-in - * logging facility. - */ - -#else - -#define __WGCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ - -typedef NS_ENUM(int, WGCDWebServerLoggingLevel) { - kWGCDWebServerLoggingLevel_Debug = 0, - kWGCDWebServerLoggingLevel_Verbose, - kWGCDWebServerLoggingLevel_Info, - kWGCDWebServerLoggingLevel_Warning, - kWGCDWebServerLoggingLevel_Error -}; - -extern WGCDWebServerLoggingLevel WGCDWebServerLogLevel; -extern void WGCDWebServerLogMessage(WGCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3); - -#if DEBUG -#define GWS_LOG_DEBUG(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Debug) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Debug, __VA_ARGS__); } while (0) -#else -#define GWS_LOG_DEBUG(...) -#endif -#define GWS_LOG_VERBOSE(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Verbose) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); } while (0) -#define GWS_LOG_INFO(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Info) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Info, __VA_ARGS__); } while (0) -#define GWS_LOG_WARNING(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Warning) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Warning, __VA_ARGS__); } while (0) -#define GWS_LOG_ERROR(...) do { if (WGCDWebServerLogLevel <= kWGCDWebServerLoggingLevel_Error) WGCDWebServerLogMessage(kWGCDWebServerLoggingLevel_Error, __VA_ARGS__); } while (0) - -#endif - -/** - * Consistency check macros used when building Debug only. - */ - -#if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED) - -#if DEBUG - -#define GWS_DCHECK(__CONDITION__) \ - do { \ - if (!(__CONDITION__)) { \ - abort(); \ - } \ - } while (0) -#define GWS_DNOT_REACHED() abort() - -#else - -#define GWS_DCHECK(__CONDITION__) -#define GWS_DNOT_REACHED() - -#endif - -#endif - -/** - * WGCDWebServer internal constants and APIs. - */ - -#define kWGCDWebServerDefaultMimeType @"application/octet-stream" -#define kWGCDWebServerErrorDomain @"WGCDWebServerErrorDomain" - -static inline BOOL WGCDWebServerIsValidByteRange(NSRange range) { - return ((range.location != NSUIntegerMax) || (range.length > 0)); -} - -static inline NSError* WGCDWebServerMakePosixError(int code) { - return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithUTF8String:strerror(code)]}]; -} - -extern void WGCDWebServerInitializeFunctions(); -extern NSString* WGCDWebServerNormalizeHeaderValue(NSString* value); -extern NSString* WGCDWebServerTruncateHeaderValue(NSString* value); -extern NSString* WGCDWebServerExtractHeaderValueParameter(NSString* header, NSString* attribute); -extern NSStringEncoding WGCDWebServerStringEncodingFromCharset(NSString* charset); -extern BOOL WGCDWebServerIsTextContentType(NSString* type); -extern NSString* WGCDWebServerDescribeData(NSData* data, NSString* contentType); -extern NSString* WGCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2); -extern NSString* WGCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService); - -@interface WGCDWebServerConnection () -- (id)initWithServer:(WGCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; -@end - -@interface WGCDWebServer () -@property(nonatomic, readonly) NSArray* handlers; -@property(nonatomic, readonly) NSString* serverName; -@property(nonatomic, readonly) NSString* authenticationRealm; -@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts; -@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts; -@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; -@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority; -- (void)willStartConnection:(WGCDWebServerConnection*)connection; -- (void)didEndConnection:(WGCDWebServerConnection*)connection; -@end - -@interface WGCDWebServerHandler : NSObject -@property(nonatomic, readonly) WGCDWebServerMatchBlock matchBlock; -@property(nonatomic, readonly) WGCDWebServerAsyncProcessBlock asyncProcessBlock; -@end - -@interface WGCDWebServerRequest () -@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; -@property(nonatomic, readwrite) NSData* localAddressData; -@property(nonatomic, readwrite) NSData* remoteAddressData; -- (void)prepareForWriting; -- (BOOL)performOpen:(NSError**)error; -- (BOOL)performWriteData:(NSData*)data error:(NSError**)error; -- (BOOL)performClose:(NSError**)error; -- (void)setAttribute:(id)attribute forKey:(NSString*)key; -@end - -@interface WGCDWebServerResponse () -@property(nonatomic, readonly) NSDictionary* additionalHeaders; -@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; -- (void)prepareForReading; -- (BOOL)performOpen:(NSError**)error; -- (void)performReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block; -- (void)performClose; -@end diff --git a/ios/WGCDWebServer/Core/WGCDWebServerRequest.h b/ios/WGCDWebServer/Core/WGCDWebServerRequest.h deleted file mode 100755 index 8d509ec..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerRequest.h +++ /dev/null @@ -1,206 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import - -/** - * Attribute key to retrieve an NSArray containing NSStrings from a WGCDWebServerRequest - * with the contents of any regular expression captures done on the request path. - * - * @warning This attribute will only be set on the request if adding a handler using - * -addHandlerForMethod:pathRegex:requestClass:processBlock:. - */ -extern NSString* const WGCDWebServerRequestAttribute_RegexCaptures; - -/** - * This protocol is used by the WGCDWebServerConnection to communicate with - * the WGCDWebServerRequest and write the received HTTP body data. - * - * Note that multiple WGCDWebServerBodyWriter objects can be chained together - * internally e.g. to automatically decode gzip encoded content before - * passing it on to the WGCDWebServerRequest. - * - * @warning These methods can be called on any WGCD thread. - */ -@protocol WGCDWebServerBodyWriter - -/** - * This method is called before any body data is received. - * - * It should return YES on success or NO on failure and set the "error" argument - * which is guaranteed to be non-NULL. - */ -- (BOOL)open:(NSError**)error; - -/** - * This method is called whenever body data has been received. - * - * It should return YES on success or NO on failure and set the "error" argument - * which is guaranteed to be non-NULL. - */ -- (BOOL)writeData:(NSData*)data error:(NSError**)error; - -/** - * This method is called after all body data has been received. - * - * It should return YES on success or NO on failure and set the "error" argument - * which is guaranteed to be non-NULL. - */ -- (BOOL)close:(NSError**)error; - -@end - -/** - * The WGCDWebServerRequest class is instantiated by the WGCDWebServerConnection - * after the HTTP headers have been received. Each instance wraps a single HTTP - * request. If a body is present, the methods from the WGCDWebServerBodyWriter - * protocol will be called by the WGCDWebServerConnection to receive it. - * - * The default implementation of the WGCDWebServerBodyWriter protocol on the class - * simply ignores the body data. - * - * @warning WGCDWebServerRequest instances can be created and used on any WGCD thread. - */ -@interface WGCDWebServerRequest : NSObject - -/** - * Returns the HTTP method for the request. - */ -@property(nonatomic, readonly) NSString* method; - -/** - * Returns the URL for the request. - */ -@property(nonatomic, readonly) NSURL* URL; - -/** - * Returns the HTTP headers for the request. - */ -@property(nonatomic, readonly) NSDictionary* headers; - -/** - * Returns the path component of the URL for the request. - */ -@property(nonatomic, readonly) NSString* path; - -/** - * Returns the parsed and unescaped query component of the URL for the request. - * - * @warning This property will be nil if there is no query in the URL. - */ -@property(nonatomic, readonly) NSDictionary* query; - -/** - * Returns the content type for the body of the request parsed from the - * "Content-Type" header. - * - * This property will be nil if the request has no body or set to - * "application/octet-stream" if a body is present but there was no - * "Content-Type" header. - */ -@property(nonatomic, readonly) NSString* contentType; - -/** - * Returns the content length for the body of the request parsed from the - * "Content-Length" header. - * - * This property will be set to "NSUIntegerMax" if the request has no body or - * if there is a body but no "Content-Length" header, typically because - * chunked transfer encoding is used. - */ -@property(nonatomic, readonly) NSUInteger contentLength; - -/** - * Returns the parsed "If-Modified-Since" header or nil if absent or malformed. - */ -@property(nonatomic, readonly) NSDate* ifModifiedSince; - -/** - * Returns the parsed "If-None-Match" header or nil if absent or malformed. - */ -@property(nonatomic, readonly) NSString* ifNoneMatch; - -/** - * Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed. - * The range will be set to (offset, length) if expressed from the beginning - * of the entity body, or (NSUIntegerMax, length) if expressed from its end. - */ -@property(nonatomic, readonly) NSRange byteRange; - -/** - * Returns YES if the client supports gzip content encoding according to the - * "Accept-Encoding" header. - */ -@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding; - -/** - * Returns the address of the local peer (i.e. server) for the request - * as a raw "struct sockaddr". - */ -@property(nonatomic, readonly) NSData* localAddressData; - -/** - * Returns the address of the local peer (i.e. server) for the request - * as a string. - */ -@property(nonatomic, readonly) NSString* localAddressString; - -/** - * Returns the address of the remote peer (i.e. client) for the request - * as a raw "struct sockaddr". - */ -@property(nonatomic, readonly) NSData* remoteAddressData; - -/** - * Returns the address of the remote peer (i.e. client) for the request - * as a string. - */ -@property(nonatomic, readonly) NSString* remoteAddressString; - -/** - * 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; - -/** - * Convenience method that checks if the contentType property is defined. - */ -- (BOOL)hasBody; - -/** - * Convenience method that checks if the byteRange property is defined. - */ -- (BOOL)hasByteRange; - -/** - * Retrieves an attribute associated with this request using the given key. - * - * @return The attribute value for the key. - */ -- (id)attributeForKey:(NSString*)key; - -@end diff --git a/ios/WGCDWebServer/Core/WGCDWebServerRequest.m b/ios/WGCDWebServer/Core/WGCDWebServerRequest.m deleted file mode 100755 index 3b26014..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerRequest.m +++ /dev/null @@ -1,334 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import - -#import "WGCDWebServerPrivate.h" - -NSString* const WGCDWebServerRequestAttribute_RegexCaptures = @"WGCDWebServerRequestAttribute_RegexCaptures"; - -#define kZlibErrorDomain @"ZlibErrorDomain" -#define kGZipInitialBufferSize (256 * 1024) - -@interface WGCDWebServerBodyDecoder : NSObject -- (id)initWithRequest:(WGCDWebServerRequest*)request writer:(id)writer; -@end - -@interface WGCDWebServerGZipDecoder : WGCDWebServerBodyDecoder -@end - -@interface WGCDWebServerBodyDecoder () { -@private - WGCDWebServerRequest* __unsafe_unretained _request; - id __unsafe_unretained _writer; -} -@end - -@implementation WGCDWebServerBodyDecoder - -- (id)initWithRequest:(WGCDWebServerRequest*)request writer:(id)writer { - if ((self = [super init])) { - _request = request; - _writer = writer; - } - return self; -} - -- (BOOL)open:(NSError**)error { - return [_writer open:error]; -} - -- (BOOL)writeData:(NSData*)data error:(NSError**)error { - return [_writer writeData:data error:error]; -} - -- (BOOL)close:(NSError**)error { - return [_writer close:error]; -} - -@end - -@interface WGCDWebServerGZipDecoder () { -@private - z_stream _stream; - BOOL _finished; -} -@end - -@implementation WGCDWebServerGZipDecoder - -- (BOOL)open:(NSError**)error { - int result = inflateInit2(&_stream, 15 + 16); - if (result != Z_OK) { - if (error) { - *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; - } - return NO; - } - if (![super open:error]) { - inflateEnd(&_stream); - return NO; - } - return YES; -} - -- (BOOL)writeData:(NSData*)data error:(NSError**)error { - GWS_DCHECK(!_finished); - _stream.next_in = (Bytef*)data.bytes; - _stream.avail_in = (uInt)data.length; - NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; - if (decodedData == nil) { - GWS_DNOT_REACHED(); - return NO; - } - NSUInteger length = 0; - while (1) { - NSUInteger maxLength = decodedData.length - length; - _stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length); - _stream.avail_out = (uInt)maxLength; - int result = inflate(&_stream, Z_NO_FLUSH); - if ((result != Z_OK) && (result != Z_STREAM_END)) { - if (error) { - *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; - } - return NO; - } - length += maxLength - _stream.avail_out; - if (_stream.avail_out > 0) { - if (result == Z_STREAM_END) { - _finished = YES; - } - break; - } - decodedData.length = 2 * decodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available - } - decodedData.length = length; - BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet - return success; -} - -- (BOOL)close:(NSError**)error { - GWS_DCHECK(_finished); - inflateEnd(&_stream); - return [super close:error]; -} - -@end - -@interface WGCDWebServerRequest () { -@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; - NSMutableArray* _decoders; - NSMutableDictionary* _attributes; - id __unsafe_unretained _writer; -} -@end - -@implementation WGCDWebServerRequest : NSObject - -@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch, - byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked, localAddressData=_localAddress, remoteAddressData=_remoteAddress; - -- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - if ((self = [super init])) { - _method = [method copy]; - _url = url; - _headers = headers; - _path = [path copy]; - _query = query; - - _type = WGCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]); - _chunked = [WGCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"]; - NSString* lengthHeader = [_headers objectForKey:@"Content-Length"]; - if (lengthHeader) { - NSInteger length = [lengthHeader integerValue]; - if (_chunked || (length < 0)) { - GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _url); - GWS_DNOT_REACHED(); - return nil; - } - _length = length; - if (_type == nil) { - _type = kWGCDWebServerDefaultMimeType; - } - } else if (_chunked) { - if (_type == nil) { - _type = kWGCDWebServerDefaultMimeType; - } - _length = NSUIntegerMax; - } else { - if (_type) { - GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _url); - _type = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense - } - _length = NSUIntegerMax; - } - - NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"]; - if (modifiedHeader) { - _modifiedSince = [WGCDWebServerParseRFC822(modifiedHeader) copy]; - } - _noneMatch = [_headers objectForKey:@"If-None-Match"]; - - _range = NSMakeRange(NSUIntegerMax, 0); - NSString* rangeHeader = WGCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]); - if (rangeHeader) { - if ([rangeHeader hasPrefix:@"bytes="]) { - NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","]; - if (components.count == 1) { - components = [[components firstObject] componentsSeparatedByString:@"-"]; - if (components.count == 2) { - NSString* startString = [components objectAtIndex:0]; - NSInteger startValue = [startString integerValue]; - NSString* endString = [components objectAtIndex:1]; - NSInteger endValue = [endString integerValue]; - if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999" - _range.location = startValue; - _range.length = endValue - startValue + 1; - } else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-" - _range.location = startValue; - _range.length = NSUIntegerMax; - } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500" - _range.location = NSUIntegerMax; - _range.length = endValue; - } - } - } - } - if ((_range.location == NSUIntegerMax) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid - GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url); - } - } - - if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) { - _gzipAccepted = YES; - } - - _decoders = [[NSMutableArray alloc] init]; - _attributes = [[NSMutableDictionary alloc] init]; - } - return self; -} - -- (BOOL)hasBody { - return _type ? YES : NO; -} - -- (BOOL)hasByteRange { - return WGCDWebServerIsValidByteRange(_range); -} - -- (id)attributeForKey:(NSString*)key { - return [_attributes objectForKey:key]; -} - -- (BOOL)open:(NSError**)error { - return YES; -} - -- (BOOL)writeData:(NSData*)data error:(NSError**)error { - return YES; -} - -- (BOOL)close:(NSError**)error { - return YES; -} - -- (void)prepareForWriting { - _writer = self; - if ([WGCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) { - WGCDWebServerGZipDecoder* decoder = [[WGCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer]; - [_decoders addObject:decoder]; - _writer = decoder; - } -} - -- (BOOL)performOpen:(NSError**)error { - GWS_DCHECK(_type); - GWS_DCHECK(_writer); - if (_opened) { - GWS_DNOT_REACHED(); - return NO; - } - _opened = YES; - return [_writer open:error]; -} - -- (BOOL)performWriteData:(NSData*)data error:(NSError**)error { - GWS_DCHECK(_opened); - return [_writer writeData:data error:error]; -} - -- (BOOL)performClose:(NSError**)error { - GWS_DCHECK(_opened); - return [_writer close:error]; -} - -- (void)setAttribute:(id)attribute forKey:(NSString*)key { - [_attributes setValue:attribute forKey:key]; -} - -- (NSString*)localAddressString { - return WGCDWebServerStringFromSockAddr(_localAddress.bytes, YES); -} - -- (NSString*)remoteAddressString { - return WGCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES); -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path]; - for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) { - [description appendFormat:@"\n %@ = %@", argument, [_query objectForKey:argument]]; - } - [description appendString:@"\n"]; - for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) { - [description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]]; - } - return description; -} - -@end diff --git a/ios/WGCDWebServer/Core/WGCDWebServerResponse.h b/ios/WGCDWebServer/Core/WGCDWebServerResponse.h deleted file mode 100755 index c5bf5b4..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerResponse.h +++ /dev/null @@ -1,208 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import - -/** - * The WGCDWebServerBodyReaderCompletionBlock is passed by WGCDWebServer to the - * WGCDWebServerBodyReader object when reading data from it asynchronously. - */ -typedef void (^WGCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* error); - -/** - * This protocol is used by the WGCDWebServerConnection to communicate with - * the WGCDWebServerResponse and read the HTTP body data to send. - * - * Note that multiple WGCDWebServerBodyReader objects can be chained together - * internally e.g. to automatically apply gzip encoding to the content before - * passing it on to the WGCDWebServerResponse. - * - * @warning These methods can be called on any WGCD thread. - */ -@protocol WGCDWebServerBodyReader - -@required - -/** - * This method is called before any body data is sent. - * - * It should return YES on success or NO on failure and set the "error" argument - * which is guaranteed to be non-NULL. - */ -- (BOOL)open:(NSError**)error; - -/** - * This method is called whenever body data is sent. - * - * It should return a non-empty NSData if there is body data available, - * or an empty NSData there is no more body data, or nil on error and set - * the "error" argument which is guaranteed to be non-NULL. - */ -- (NSData*)readData:(NSError**)error; - -/** - * This method is called after all body data has been sent. - */ -- (void)close; - -@optional - -/** - * If this method is implemented, it will be preferred over -readData:. - * - * It must call the passed block when data is available, passing a non-empty - * NSData if there is body data available, or an empty NSData there is no more - * body data, or nil on error and pass an NSError along. - */ -- (void)asyncReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block; - -@end - -/** - * The WGCDWebServerResponse class is used to wrap a single HTTP response. - * It is instantiated by the handler of the WGCDWebServer that handled the request. - * If a body is present, the methods from the WGCDWebServerBodyReader protocol - * will be called by the WGCDWebServerConnection to send it. - * - * The default implementation of the WGCDWebServerBodyReader protocol - * on the class simply returns an empty body. - * - * @warning WGCDWebServerResponse instances can be created and used on any WGCD thread. - */ -@interface WGCDWebServerResponse : NSObject - -/** - * Sets the content type for the body of the response. - * - * The default value is nil i.e. the response has no body. - * - * @warning This property must be set if a body is present. - */ -@property(nonatomic, copy) NSString* contentType; - -/** - * Sets the content length for the body of the response. If a body is present - * but this property is set to "NSUIntegerMax", this means the length of the body - * cannot be known ahead of time. Chunked transfer encoding will be - * automatically enabled by the WGCDWebServerConnection to comply with HTTP/1.1 - * specifications. - * - * The default value is "NSUIntegerMax" i.e. the response has no body or its length - * is undefined. - */ -@property(nonatomic) NSUInteger contentLength; - -/** - * Sets the HTTP status code for the response. - * - * The default value is 200 i.e. "OK". - */ -@property(nonatomic) NSInteger statusCode; - -/** - * Sets the caching hint for the response using the "Cache-Control" header. - * This value is expressed in seconds. - * - * The default value is 0 i.e. "no-cache". - */ -@property(nonatomic) NSUInteger cacheControlMaxAge; - -/** - * Sets the last modified date for the response using the "Last-Modified" header. - * - * The default value is nil. - */ -@property(nonatomic, retain) NSDate* lastModifiedDate; - -/** - * Sets the ETag for the response using the "ETag" header. - * - * The default value is nil. - */ -@property(nonatomic, copy) NSString* eTag; - -/** - * Enables gzip encoding for the response body. - * - * The default value is NO. - * - * @warning Enabling gzip encoding will remove any "Content-Length" header - * since the length of the body is not known anymore. The client will still - * be able to determine the body length when connection is closed per - * HTTP/1.1 specifications. - */ -@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled; - -/** - * Creates an empty response. - */ -+ (instancetype)response; - -/** - * This method is the designated initializer for the class. - */ -- (instancetype)init; - -/** - * Sets an additional HTTP header on the response. - * Pass a nil value to remove an additional header. - * - * @warning Do not attempt to override the primary headers used - * by WGCDWebServerResponse like "Content-Type", "ETag", etc... - */ -- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header; - -/** - * Convenience method that checks if the contentType property is defined. - */ -- (BOOL)hasBody; - -@end - -@interface WGCDWebServerResponse (Extensions) - -/** - * Creates a empty response with a specific HTTP status code. - */ -+ (instancetype)responseWithStatusCode:(NSInteger)statusCode; - -/** - * Creates an HTTP redirect response to a new URL. - */ -+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent; - -/** - * Initializes an empty response with a specific HTTP status code. - */ -- (instancetype)initWithStatusCode:(NSInteger)statusCode; - -/** - * Initializes an HTTP redirect response to a new URL. - */ -- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent; - -@end diff --git a/ios/WGCDWebServer/Core/WGCDWebServerResponse.m b/ios/WGCDWebServer/Core/WGCDWebServerResponse.m deleted file mode 100755 index 62c9e97..0000000 --- a/ios/WGCDWebServer/Core/WGCDWebServerResponse.m +++ /dev/null @@ -1,310 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import - -#import "WGCDWebServerPrivate.h" - -#define kZlibErrorDomain @"ZlibErrorDomain" -#define kGZipInitialBufferSize (256 * 1024) - -@interface WGCDWebServerBodyEncoder : NSObject -- (id)initWithResponse:(WGCDWebServerResponse*)response reader:(id)reader; -@end - -@interface WGCDWebServerGZipEncoder : WGCDWebServerBodyEncoder -@end - -@interface WGCDWebServerBodyEncoder () { -@private - WGCDWebServerResponse* __unsafe_unretained _response; - id __unsafe_unretained _reader; -} -@end - -@implementation WGCDWebServerBodyEncoder - -- (id)initWithResponse:(WGCDWebServerResponse*)response reader:(id)reader { - if ((self = [super init])) { - _response = response; - _reader = reader; - } - return self; -} - -- (BOOL)open:(NSError**)error { - return [_reader open:error]; -} - -- (NSData*)readData:(NSError**)error { - return [_reader readData:error]; -} - -- (void)close { - [_reader close]; -} - -@end - -@interface WGCDWebServerGZipEncoder () { -@private - z_stream _stream; - BOOL _finished; -} -@end - -@implementation WGCDWebServerGZipEncoder - -- (id)initWithResponse:(WGCDWebServerResponse*)response reader:(id)reader { - if ((self = [super initWithResponse:response reader:reader])) { - response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it - [response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"]; - } - return self; -} - -- (BOOL)open:(NSError**)error { - int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); - if (result != Z_OK) { - if (error) { - *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; - } - return NO; - } - if (![super open:error]) { - deflateEnd(&_stream); - return NO; - } - return YES; -} - -- (NSData*)readData:(NSError**)error { - NSMutableData* encodedData; - if (_finished) { - encodedData = [[NSMutableData alloc] init]; - } else { - encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; - if (encodedData == nil) { - GWS_DNOT_REACHED(); - return nil; - } - NSUInteger length = 0; - do { - NSData* data = [super readData:error]; - if (data == nil) { - return nil; - } - _stream.next_in = (Bytef*)data.bytes; - _stream.avail_in = (uInt)data.length; - while (1) { - NSUInteger maxLength = encodedData.length - length; - _stream.next_out = (Bytef*)((char*)encodedData.mutableBytes + length); - _stream.avail_out = (uInt)maxLength; - int result = deflate(&_stream, data.length ? Z_NO_FLUSH : Z_FINISH); - if (result == Z_STREAM_END) { - _finished = YES; - } else if (result != Z_OK) { - if (error) { - *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; - } - return nil; - } - length += maxLength - _stream.avail_out; - if (_stream.avail_out > 0) { - break; - } - encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available - } - GWS_DCHECK(_stream.avail_in == 0); - } while (length == 0); // Make sure we don't return an empty NSData if not in finished state - encodedData.length = length; - } - return encodedData; -} - -- (void)close { - deflateEnd(&_stream); - [super close]; -} - -@end - -@interface WGCDWebServerResponse () { -@private - NSString* _type; - NSUInteger _length; - NSInteger _status; - NSUInteger _maxAge; - NSDate* _lastModified; - NSString* _eTag; - NSMutableDictionary* _headers; - BOOL _chunked; - BOOL _gzipped; - - BOOL _opened; - NSMutableArray* _encoders; - id __unsafe_unretained _reader; -} -@end - -@implementation WGCDWebServerResponse - -@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, lastModifiedDate=_lastModified, eTag=_eTag, - gzipContentEncodingEnabled=_gzipped, additionalHeaders=_headers; - -+ (instancetype)response { - return [[[self class] alloc] init]; -} - -- (instancetype)init { - if ((self = [super init])) { - _type = nil; - _length = NSUIntegerMax; - _status = kWGCDWebServerHTTPStatusCode_OK; - _maxAge = 0; - _headers = [[NSMutableDictionary alloc] init]; - _encoders = [[NSMutableArray alloc] init]; - } - return self; -} - -- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header { - [_headers setValue:value forKey:header]; -} - -- (BOOL)hasBody { - return _type ? YES : NO; -} - -- (BOOL)usesChunkedTransferEncoding { - return (_type != nil) && (_length == NSUIntegerMax); -} - -- (BOOL)open:(NSError**)error { - return YES; -} - -- (NSData*)readData:(NSError**)error { - return [NSData data]; -} - -- (void)close { - ; -} - -- (void)prepareForReading { - _reader = self; - if (_gzipped) { - WGCDWebServerGZipEncoder* encoder = [[WGCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader]; - [_encoders addObject:encoder]; - _reader = encoder; - } -} - -- (BOOL)performOpen:(NSError**)error { - GWS_DCHECK(_type); - GWS_DCHECK(_reader); - if (_opened) { - GWS_DNOT_REACHED(); - return NO; - } - _opened = YES; - return [_reader open:error]; -} - -- (void)performReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block { - GWS_DCHECK(_opened); - if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) { - [_reader asyncReadDataWithCompletion:[block copy]]; - } else { - NSError* error = nil; - NSData* data = [_reader readData:&error]; - block(data, error); - } -} - -- (void)performClose { - GWS_DCHECK(_opened); - [_reader close]; -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_status]; - if (_type) { - [description appendFormat:@"\nContent Type = %@", _type]; - } - if (_length != NSUIntegerMax) { - [description appendFormat:@"\nContent Length = %lu", (unsigned long)_length]; - } - [description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_maxAge]; - if (_lastModified) { - [description appendFormat:@"\nLast Modified Date = %@", _lastModified]; - } - if (_eTag) { - [description appendFormat:@"\nETag = %@", _eTag]; - } - if (_headers.count) { - [description appendString:@"\n"]; - for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) { - [description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]]; - } - } - return description; -} - -@end - -@implementation WGCDWebServerResponse (Extensions) - -+ (instancetype)responseWithStatusCode:(NSInteger)statusCode { - return [[self alloc] initWithStatusCode:statusCode]; -} - -+ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent { - return [[self alloc] initWithRedirect:location permanent:permanent]; -} - -- (instancetype)initWithStatusCode:(NSInteger)statusCode { - if ((self = [self init])) { - self.statusCode = statusCode; - } - return self; -} - -- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent { - if ((self = [self init])) { - self.statusCode = permanent ? kWGCDWebServerHTTPStatusCode_MovedPermanently : kWGCDWebServerHTTPStatusCode_TemporaryRedirect; - [self setValue:[location absoluteString] forAdditionalHeader:@"Location"]; - } - return self; -} - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.h b/ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.h deleted file mode 100755 index 76dfb9b..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerRequest.h" - -/** - * The WGCDWebServerDataRequest subclass of WGCDWebServerRequest stores the body - * of the HTTP request in memory. - */ -@interface WGCDWebServerDataRequest : WGCDWebServerRequest - -/** - * Returns the data for the request body. - */ -@property(nonatomic, readonly) NSData* data; - -@end - -@interface WGCDWebServerDataRequest (Extensions) - -/** - * Returns the data for the request body interpreted as text. If the content - * type of the body is not a text one, or if an error occurs, nil is returned. - * - * The text encoding used to interpret the data is extracted from the - * "Content-Type" header or defaults to UTF-8. - */ -@property(nonatomic, readonly) NSString* text; - -/** - * Returns the data for the request body interpreted as a JSON object. If the - * content type of the body is not JSON, or if an error occurs, nil is returned. - */ -@property(nonatomic, readonly) id jsonObject; - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.m b/ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.m deleted file mode 100755 index 30983f3..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerDataRequest.m +++ /dev/null @@ -1,108 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import "WGCDWebServerPrivate.h" - -@interface WGCDWebServerDataRequest () { -@private - NSMutableData* _data; - - NSString* _text; - id _jsonObject; -} -@end - -@implementation WGCDWebServerDataRequest - -@synthesize data=_data; - -- (BOOL)open:(NSError**)error { - if (self.contentLength != NSUIntegerMax) { - _data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; - } else { - _data = [[NSMutableData alloc] init]; - } - if (_data == nil) { - if (error) { - *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}]; - } - return NO; - } - return YES; -} - -- (BOOL)writeData:(NSData*)data error:(NSError**)error { - [_data appendData:data]; - return YES; -} - -- (BOOL)close:(NSError**)error { - return YES; -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithString:[super description]]; - if (_data) { - [description appendString:@"\n\n"]; - [description appendString:WGCDWebServerDescribeData(_data, self.contentType)]; - } - return description; -} - -@end - -@implementation WGCDWebServerDataRequest (Extensions) - -- (NSString*)text { - if (_text == nil) { - if ([self.contentType hasPrefix:@"text/"]) { - NSString* charset = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); - _text = [[NSString alloc] initWithData:self.data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; - } else { - GWS_DNOT_REACHED(); - } - } - return _text; -} - -- (id)jsonObject { - if (_jsonObject == nil) { - NSString* mimeType = WGCDWebServerTruncateHeaderValue(self.contentType); - if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) { - _jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]; - } else { - GWS_DNOT_REACHED(); - } - } - return _jsonObject; -} - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.h b/ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.h deleted file mode 100755 index b1ab0dd..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerRequest.h" - -/** - * The WGCDWebServerFileRequest subclass of WGCDWebServerRequest stores the body - * of the HTTP request to a file on disk. - */ -@interface WGCDWebServerFileRequest : WGCDWebServerRequest - -/** - * Returns the path to the temporary file containing the request body. - * - * @warning This temporary file will be automatically deleted when the - * WGCDWebServerFileRequest is deallocated. If you want to preserve this file, - * you must move it to a different location beforehand. - */ -@property(nonatomic, readonly) NSString* temporaryPath; - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.m b/ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.m deleted file mode 100755 index b2eec0f..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerFileRequest.m +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import "WGCDWebServerPrivate.h" - -@interface WGCDWebServerFileRequest () { -@private - NSString* _temporaryPath; - int _file; -} -@end - -@implementation WGCDWebServerFileRequest - -@synthesize temporaryPath=_temporaryPath; - -- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { - _temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; - } - return self; -} - -- (void)dealloc { - unlink([_temporaryPath fileSystemRepresentation]); -} - -- (BOOL)open:(NSError**)error { - _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (_file <= 0) { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - return NO; - } - return YES; -} - -- (BOOL)writeData:(NSData*)data error:(NSError**)error { - if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - return NO; - } - return YES; -} - -- (BOOL)close:(NSError**)error { - if (close(_file) < 0) { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - return NO; - } -#ifdef __WGCDWEBSERVER_ENABLE_TESTING__ - NSString* creationDateHeader = [self.headers objectForKey:@"X-WGCDWebServer-CreationDate"]; - if (creationDateHeader) { - NSDate* date = WGCDWebServerParseISO8601(creationDateHeader); - if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate: date} ofItemAtPath:_temporaryPath error:error]) { - return NO; - } - } - NSString* modifiedDateHeader = [self.headers objectForKey:@"X-WGCDWebServer-ModifiedDate"]; - if (modifiedDateHeader) { - NSDate* date = WGCDWebServerParseRFC822(modifiedDateHeader); - if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate: date} ofItemAtPath:_temporaryPath error:error]) { - return NO; - } - } -#endif - return YES; -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithString:[super description]]; - [description appendFormat:@"\n\n{%@}", _temporaryPath]; - return description; -} - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.h b/ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.h deleted file mode 100755 index a84d1d2..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerRequest.h" - -/** - * The WGCDWebServerMultiPart class is an abstract class that wraps the content - * of a part. - */ -@interface WGCDWebServerMultiPart : NSObject - -/** - * Returns the control name retrieved from the part headers. - */ -@property(nonatomic, readonly) NSString* controlName; - -/** - * Returns the content type retrieved from the part headers or "text/plain" - * if not available (per HTTP specifications). - */ -@property(nonatomic, readonly) NSString* contentType; - -/** - * Returns the MIME type component of the content type for the part. - */ -@property(nonatomic, readonly) NSString* mimeType; - -@end - -/** - * The WGCDWebServerMultiPartArgument subclass of WGCDWebServerMultiPart wraps - * the content of a part as data in memory. - */ -@interface WGCDWebServerMultiPartArgument : WGCDWebServerMultiPart - -/** - * Returns the data for the part. - */ -@property(nonatomic, readonly) NSData* data; - -/** - * Returns the data for the part interpreted as text. If the content - * type of the part is not a text one, or if an error occurs, nil is returned. - * - * The text encoding used to interpret the data is extracted from the - * "Content-Type" header or defaults to UTF-8. - */ -@property(nonatomic, readonly) NSString* string; - -@end - -/** - * The WGCDWebServerMultiPartFile subclass of WGCDWebServerMultiPart wraps - * the content of a part as a file on disk. - */ -@interface WGCDWebServerMultiPartFile : WGCDWebServerMultiPart - -/** - * Returns the file name retrieved from the part headers. - */ -@property(nonatomic, readonly) NSString* fileName; - -/** - * Returns the path to the temporary file containing the part data. - * - * @warning This temporary file will be automatically deleted when the - * WGCDWebServerMultiPartFile is deallocated. If you want to preserve this file, - * you must move it to a different location beforehand. - */ -@property(nonatomic, readonly) NSString* temporaryPath; - -@end - -/** - * The WGCDWebServerMultiPartFormRequest subclass of WGCDWebServerRequest - * parses the body of the HTTP request as a multipart encoded form. - */ -@interface WGCDWebServerMultiPartFormRequest : WGCDWebServerRequest - -/** - * Returns the argument parts from the multipart encoded form as - * name / WGCDWebServerMultiPartArgument pairs. - */ -@property(nonatomic, readonly) NSArray* arguments; - -/** - * Returns the files parts from the multipart encoded form as - * name / WGCDWebServerMultiPartFile pairs. - */ -@property(nonatomic, readonly) NSArray* files; - -/** - * Returns the MIME type for multipart encoded forms - * i.e. "multipart/form-data". - */ -+ (NSString*)mimeType; - -/** - * Returns the first argument for a given control name or nil if not found. - */ -- (WGCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name; - -/** - * Returns the first file for a given control name or nil if not found. - */ -- (WGCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name; - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.m b/ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.m deleted file mode 100755 index b2dc2d5..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerMultiPartFormRequest.m +++ /dev/null @@ -1,445 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import "WGCDWebServerPrivate.h" - -#define kMultiPartBufferSize (256 * 1024) - -typedef enum { - kParserState_Undefined = 0, - kParserState_Start, - kParserState_Headers, - kParserState_Content, - kParserState_End -} ParserState; - -@interface WGCDWebServerMIMEStreamParser : NSObject -- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files; -- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length; -- (BOOL)isAtEnd; -@end - -static NSData* _newlineData = nil; -static NSData* _newlinesData = nil; -static NSData* _dashNewlineData = nil; - -@interface WGCDWebServerMultiPart () { -@private - NSString* _controlName; - NSString* _contentType; - NSString* _mimeType; -} -@end - -@implementation WGCDWebServerMultiPart - -@synthesize controlName=_controlName, contentType=_contentType, mimeType=_mimeType; - -- (id)initWithControlName:(NSString*)name contentType:(NSString*)type { - if ((self = [super init])) { - _controlName = [name copy]; - _contentType = [type copy]; - _mimeType = WGCDWebServerTruncateHeaderValue(_contentType); - } - return self; -} - -@end - -@interface WGCDWebServerMultiPartArgument () { -@private - NSData* _data; - NSString* _string; -} -@end - -@implementation WGCDWebServerMultiPartArgument - -@synthesize data=_data, string=_string; - -- (id)initWithControlName:(NSString*)name contentType:(NSString*)type data:(NSData*)data { - if ((self = [super initWithControlName:name contentType:type])) { - _data = data; - - if ([self.contentType hasPrefix:@"text/"]) { - NSString* charset = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); - _string = [[NSString alloc] initWithData:_data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; - } - } - return self; -} - -- (NSString*)description { - return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length]; -} - -@end - -@interface WGCDWebServerMultiPartFile () { -@private - NSString* _fileName; - NSString* _temporaryPath; -} -@end - -@implementation WGCDWebServerMultiPartFile - -@synthesize fileName=_fileName, temporaryPath=_temporaryPath; - -- (id)initWithControlName:(NSString*)name contentType:(NSString*)type fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath { - if ((self = [super initWithControlName:name contentType:type])) { - _fileName = [fileName copy]; - _temporaryPath = [temporaryPath copy]; - } - return self; -} - -- (void)dealloc { - unlink([_temporaryPath fileSystemRepresentation]); -} - -- (NSString*)description { - return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName]; -} - -@end - -@interface WGCDWebServerMIMEStreamParser () { -@private - NSData* _boundary; - NSString* _defaultcontrolName; - ParserState _state; - NSMutableData* _data; - NSMutableArray* _arguments; - NSMutableArray* _files; - - NSString* _controlName; - NSString* _fileName; - NSString* _contentType; - NSString* _tmpPath; - int _tmpFile; - WGCDWebServerMIMEStreamParser* _subParser; -} -@end - -@implementation WGCDWebServerMIMEStreamParser - -+ (void)initialize { - if (_newlineData == nil) { - _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2]; - GWS_DCHECK(_newlineData); - } - if (_newlinesData == nil) { - _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; - GWS_DCHECK(_newlinesData); - } - if (_dashNewlineData == nil) { - _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4]; - GWS_DCHECK(_dashNewlineData); - } -} - -- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files { - NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil; - if (data == nil) { - GWS_DNOT_REACHED(); - return nil; - } - if ((self = [super init])) { - _boundary = data; - _defaultcontrolName = name; - _arguments = arguments; - _files = files; - _data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize]; - _state = kParserState_Start; - } - return self; -} - -- (void)dealloc { - if (_tmpFile > 0) { - close(_tmpFile); - unlink([_tmpPath fileSystemRepresentation]); - } -} - -// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 -- (BOOL)_parseData { - BOOL success = YES; - - if (_state == kParserState_Headers) { - NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)]; - if (range.location != NSNotFound) { - - _controlName = nil; - _fileName = nil; - _contentType = nil; - _tmpPath = nil; - _subParser = nil; - NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding]; - if (headers) { - for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) { - NSRange subRange = [header rangeOfString:@":"]; - if (subRange.location != NSNotFound) { - NSString* name = [header substringToIndex:subRange.location]; - NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) { - _contentType = WGCDWebServerNormalizeHeaderValue(value); - } else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) { - NSString* contentDisposition = WGCDWebServerNormalizeHeaderValue(value); - if ([WGCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) { - _controlName = WGCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"); - _fileName = WGCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); - } else if ([WGCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) { - _controlName = _defaultcontrolName; - _fileName = WGCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); - } - } - } else { - GWS_DNOT_REACHED(); - } - } - if (_contentType == nil) { - _contentType = @"text/plain"; - } - } else { - GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'"); - GWS_DNOT_REACHED(); - } - if (_controlName) { - if ([WGCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) { - NSString* boundary = WGCDWebServerExtractHeaderValueParameter(_contentType, @"boundary"); - _subParser = [[WGCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files]; - if (_subParser == nil) { - GWS_DNOT_REACHED(); - success = NO; - } - } else if (_fileName) { - NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; - _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (_tmpFile > 0) { - _tmpPath = [path copy]; - } else { - GWS_DNOT_REACHED(); - success = NO; - } - } - } else { - GWS_DNOT_REACHED(); - success = NO; - } - - [_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; - _state = kParserState_Content; - } - } - - if ((_state == kParserState_Start) || (_state == kParserState_Content)) { - NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)]; - if (range.location != NSNotFound) { - NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length); - NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; - NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; - if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) { - - if (_state == kParserState_Content) { - const void* dataBytes = _data.bytes; - NSUInteger dataLength = range.location - 2; - if (_subParser) { - if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) { - GWS_DNOT_REACHED(); - success = NO; - } - _subParser = nil; - } else if (_tmpPath) { - ssize_t result = write(_tmpFile, dataBytes, dataLength); - if (result == (ssize_t)dataLength) { - if (close(_tmpFile) == 0) { - _tmpFile = 0; - WGCDWebServerMultiPartFile* file = [[WGCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath]; - [_files addObject:file]; - } else { - GWS_DNOT_REACHED(); - success = NO; - } - } else { - GWS_DNOT_REACHED(); - success = NO; - } - _tmpPath = nil; - } else { - NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength]; - WGCDWebServerMultiPartArgument* argument = [[WGCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data]; - [_arguments addObject:argument]; - } - } - - if (subRange1.location != NSNotFound) { - [_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; - _state = kParserState_Headers; - success = [self _parseData]; - } else { - _state = kParserState_End; - } - } - } else { - NSUInteger margin = 2 * _boundary.length; - if (_data.length > margin) { - NSUInteger length = _data.length - margin; - if (_subParser) { - if ([_subParser appendBytes:_data.bytes length:length]) { - [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; - } else { - GWS_DNOT_REACHED(); - success = NO; - } - } else if (_tmpPath) { - ssize_t result = write(_tmpFile, _data.bytes, length); - if (result == (ssize_t)length) { - [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; - } else { - GWS_DNOT_REACHED(); - success = NO; - } - } - } - } - } - - return success; -} - -- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length { - [_data appendBytes:bytes length:length]; - return [self _parseData]; -} - -- (BOOL)isAtEnd { - return (_state == kParserState_End); -} - -@end - -@interface WGCDWebServerMultiPartFormRequest () { -@private - WGCDWebServerMIMEStreamParser* _parser; - NSMutableArray* _arguments; - NSMutableArray* _files; -} -@end - -@implementation WGCDWebServerMultiPartFormRequest - -@synthesize arguments=_arguments, files=_files; - -+ (NSString*)mimeType { - return @"multipart/form-data"; -} - -- (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])) { - _arguments = [[NSMutableArray alloc] init]; - _files = [[NSMutableArray alloc] init]; - } - return self; -} - -- (BOOL)open:(NSError**)error { - NSString* boundary = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); - _parser = [[WGCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files]; - if (_parser == nil) { - if (error) { - *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}]; - } - return NO; - } - return YES; -} - -- (BOOL)writeData:(NSData*)data error:(NSError**)error { - if (![_parser appendBytes:data.bytes length:data.length]) { - if (error) { - *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}]; - } - return NO; - } - return YES; -} - -- (BOOL)close:(NSError**)error { - BOOL atEnd = [_parser isAtEnd]; - _parser = nil; - if (!atEnd) { - if (error) { - *error = [NSError errorWithDomain:kWGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}]; - } - return NO; - } - return YES; -} - -- (WGCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name { - for (WGCDWebServerMultiPartArgument* argument in _arguments) { - if ([argument.controlName isEqualToString:name]) { - return argument; - } - } - return nil; -} - -- (WGCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name { - for (WGCDWebServerMultiPartFile* file in _files) { - if ([file.controlName isEqualToString:name]) { - return file; - } - } - return nil; -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithString:[super description]]; - if (_arguments.count) { - [description appendString:@"\n"]; - for (WGCDWebServerMultiPartArgument* argument in _arguments) { - [description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType]; - [description appendString:WGCDWebServerDescribeData(argument.data, argument.contentType)]; - } - } - if (_files.count) { - [description appendString:@"\n"]; - for (WGCDWebServerMultiPartFile* file in _files) { - [description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath]; - } - } - return description; -} - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.h b/ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.h deleted file mode 100755 index b5860d0..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerDataRequest.h" - -/** - * The WGCDWebServerURLEncodedFormRequest subclass of WGCDWebServerRequest - * parses the body of the HTTP request as a URL encoded form using - * WGCDWebServerParseURLEncodedForm(). - */ -@interface WGCDWebServerURLEncodedFormRequest : WGCDWebServerDataRequest - -/** - * Returns the unescaped control names and values for the URL encoded form. - * - * The text encoding used to interpret the data is extracted from the - * "Content-Type" header or defaults to UTF-8. - */ -@property(nonatomic, readonly) NSDictionary* arguments; - -/** - * Returns the MIME type for URL encoded forms - * i.e. "application/x-www-form-urlencoded". - */ -+ (NSString*)mimeType; - -@end diff --git a/ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.m b/ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.m deleted file mode 100755 index b1e5015..0000000 --- a/ios/WGCDWebServer/Requests/WGCDWebServerURLEncodedFormRequest.m +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import "WGCDWebServerPrivate.h" - -@interface WGCDWebServerURLEncodedFormRequest () { -@private - NSDictionary* _arguments; -} -@end - -@implementation WGCDWebServerURLEncodedFormRequest - -@synthesize arguments=_arguments; - -+ (NSString*)mimeType { - return @"application/x-www-form-urlencoded"; -} - -- (BOOL)close:(NSError**)error { - if (![super close:error]) { - return NO; - } - - NSString* charset = WGCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); - NSString* string = [[NSString alloc] initWithData:self.data encoding:WGCDWebServerStringEncodingFromCharset(charset)]; - _arguments = WGCDWebServerParseURLEncodedForm(string); - GWS_DCHECK(_arguments); - - return YES; -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithString:[super description]]; - [description appendString:@"\n"]; - for (NSString* argument in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) { - [description appendFormat:@"\n%@ = %@", argument, [_arguments objectForKey:argument]]; - } - return description; -} - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.h b/ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.h deleted file mode 100755 index 20c75a5..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerResponse.h" - -/** - * The WGCDWebServerDataResponse subclass of WGCDWebServerResponse reads the body - * of the HTTP response from memory. - */ -@interface WGCDWebServerDataResponse : WGCDWebServerResponse - -/** - * Creates a response with data in memory and a given content type. - */ -+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; - -/** - * This method is the designated initializer for the class. - */ -- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; - -@end - -@interface WGCDWebServerDataResponse (Extensions) - -/** - * Creates a data response from text encoded using UTF-8. - */ -+ (instancetype)responseWithText:(NSString*)text; - -/** - * Creates a data response from HTML encoded using UTF-8. - */ -+ (instancetype)responseWithHTML:(NSString*)html; - -/** - * Creates a data response from an HTML template encoded using UTF-8. - * See -initWithHTMLTemplate:variables: for details. - */ -+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; - -/** - * Creates a data response from a serialized JSON object and the default - * "application/json" content type. - */ -+ (instancetype)responseWithJSONObject:(id)object; - -/** - * Creates a data response from a serialized JSON object and a custom - * content type. - */ -+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; - -/** - * Initializes a data response from text encoded using UTF-8. - */ -- (instancetype)initWithText:(NSString*)text; - -/** - * Initializes a data response from HTML encoded using UTF-8. - */ -- (instancetype)initWithHTML:(NSString*)html; - -/** - * Initializes a data response from an HTML template encoded using UTF-8. - * - * All occurences of "%variable%" within the HTML template are replaced with - * their corresponding values. - */ -- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; - -/** - * Initializes a data response from a serialized JSON object and the default - * "application/json" content type. - */ -- (instancetype)initWithJSONObject:(id)object; - -/** - * Initializes a data response from a serialized JSON object and a custom - * content type. - */ -- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.m b/ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.m deleted file mode 100755 index ca849e6..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerDataResponse.m +++ /dev/null @@ -1,143 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import "WGCDWebServerPrivate.h" - -@interface WGCDWebServerDataResponse () { -@private - NSData* _data; - BOOL _done; -} -@end - -@implementation WGCDWebServerDataResponse - -+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { - return [[[self class] alloc] initWithData:data contentType:type]; -} - -- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { - if (data == nil) { - GWS_DNOT_REACHED(); - return nil; - } - - if ((self = [super init])) { - _data = data; - - self.contentType = type; - self.contentLength = data.length; - } - return self; -} - -- (NSData*)readData:(NSError**)error { - NSData* data; - if (_done) { - data = [NSData data]; - } else { - data = _data; - _done = YES; - } - return data; -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithString:[super description]]; - [description appendString:@"\n\n"]; - [description appendString:WGCDWebServerDescribeData(_data, self.contentType)]; - return description; -} - -@end - -@implementation WGCDWebServerDataResponse (Extensions) - -+ (instancetype)responseWithText:(NSString*)text { - return [[self alloc] initWithText:text]; -} - -+ (instancetype)responseWithHTML:(NSString*)html { - return [[self alloc] initWithHTML:html]; -} - -+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { - return [[self alloc] initWithHTMLTemplate:path variables:variables]; -} - -+ (instancetype)responseWithJSONObject:(id)object { - return [[self alloc] initWithJSONObject:object]; -} - -+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type { - return [[self alloc] initWithJSONObject:object contentType:type]; -} - -- (instancetype)initWithText:(NSString*)text { - NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; - if (data == nil) { - GWS_DNOT_REACHED(); - return nil; - } - return [self initWithData:data contentType:@"text/plain; charset=utf-8"]; -} - -- (instancetype)initWithHTML:(NSString*)html { - NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding]; - if (data == nil) { - GWS_DNOT_REACHED(); - return nil; - } - return [self initWithData:data contentType:@"text/html; charset=utf-8"]; -} - -- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { - NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; - [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { - [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; - }]; - id response = [self initWithHTML:html]; - return response; -} - -- (instancetype)initWithJSONObject:(id)object { - return [self initWithJSONObject:object contentType:@"application/json"]; -} - -- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type { - NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL]; - if (data == nil) { - return nil; - } - return [self initWithData:data contentType:type]; -} - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.h b/ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.h deleted file mode 100755 index dacb6a6..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerDataResponse.h" -#import "WGCDWebServerHTTPStatusCodes.h" - -/** - * The WGCDWebServerDataResponse subclass of WGCDWebServerDataResponse generates - * an HTML body from an HTTP status code and an error message. - */ -@interface WGCDWebServerErrorResponse : WGCDWebServerDataResponse - -/** - * Creates a client error response with the corresponding HTTP status code. - */ -+ (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); - -/** - * Creates a server error response with the corresponding HTTP status code. - */ -+ (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); - -/** - * Creates a client error response with the corresponding HTTP status code - * and an underlying NSError. - */ -+ (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); - -/** - * Creates a server error response with the corresponding HTTP status code - * and an underlying NSError. - */ -+ (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); - -/** - * Initializes a client error response with the corresponding HTTP status code. - */ -- (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); - -/** - * Initializes a server error response with the corresponding HTTP status code. - */ -- (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); - -/** - * Initializes a client error response with the corresponding HTTP status code - * and an underlying NSError. - */ -- (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); - -/** - * Initializes a server error response with the corresponding HTTP status code - * and an underlying NSError. - */ -- (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4); - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.m b/ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.m deleted file mode 100755 index 575b408..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerErrorResponse.m +++ /dev/null @@ -1,128 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import "WGCDWebServerPrivate.h" - -@interface WGCDWebServerErrorResponse () -- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments; -@end - -@implementation WGCDWebServerErrorResponse - -+ (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); - va_list arguments; - va_start(arguments, format); - WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; - va_end(arguments); - return response; -} - -+ (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); - va_list arguments; - va_start(arguments, format); - WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; - va_end(arguments); - return response; -} - -+ (instancetype)responseWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); - va_list arguments; - va_start(arguments, format); - WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; - va_end(arguments); - return response; -} - -+ (instancetype)responseWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); - va_list arguments; - va_start(arguments, format); - WGCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; - va_end(arguments); - return response; -} - -static inline NSString* _EscapeHTMLString(NSString* string) { - return [string stringByReplacingOccurrencesOfString:@"\"" withString:@"""]; -} - -- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments { - NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; - NSString* title = [NSString stringWithFormat:@"HTTP Error %i", (int)statusCode]; - NSString* error = underlyingError ? [NSString stringWithFormat:@"[%@] %@ (%li)", underlyingError.domain, _EscapeHTMLString(underlyingError.localizedDescription), (long)underlyingError.code] : @""; - NSString* html = [NSString stringWithFormat:@"%@

%@: %@

%@

", - title, title, _EscapeHTMLString(message), error]; - if ((self = [self initWithHTML:html])) { - self.statusCode = statusCode; - } - return self; -} - -- (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); - va_list arguments; - va_start(arguments, format); - self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; - va_end(arguments); - return self; -} - -- (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); - va_list arguments; - va_start(arguments, format); - self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; - va_end(arguments); - return self; -} - -- (instancetype)initWithClientError:(WGCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); - va_list arguments; - va_start(arguments, format); - self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; - va_end(arguments); - return self; -} - -- (instancetype)initWithServerError:(WGCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { - GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); - va_list arguments; - va_start(arguments, format); - self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; - va_end(arguments); - return self; -} - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.h b/ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.h deleted file mode 100755 index 53f99d6..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerResponse.h" - -/** - * The WGCDWebServerFileResponse subclass of WGCDWebServerResponse reads the body - * of the HTTP response from a file on disk. - * - * It will automatically set the contentType, lastModifiedDate and eTag - * properties of the WGCDWebServerResponse according to the file extension and - * metadata. - */ -@interface WGCDWebServerFileResponse : WGCDWebServerResponse - -/** - * Creates a response with the contents of a file. - */ -+ (instancetype)responseWithFile:(NSString*)path; - -/** - * Creates a response like +responseWithFile: and sets the "Content-Disposition" - * HTTP header for a download if the "attachment" argument is YES. - */ -+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; - -/** - * Creates a response like +responseWithFile: but restricts the file contents - * to a specific byte range. - * - * See -initWithFile:byteRange: for details. - */ -+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; - -/** - * Creates a response like +responseWithFile:byteRange: and sets the - * "Content-Disposition" HTTP header for a download if the "attachment" - * argument is YES. - */ -+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; - -/** - * Initializes a response with the contents of a file. - */ -- (instancetype)initWithFile:(NSString*)path; - -/** - * Initializes a response like +responseWithFile: and sets the - * "Content-Disposition" HTTP header for a download if the "attachment" - * argument is YES. - */ -- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; - -/** - * Initializes a response like -initWithFile: but restricts the file contents - * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for - * the full file, (offset, length) if expressed from the beginning of the file, - * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset" - * and "length" values will be automatically adjusted to be compatible with the - * actual size of the file. - * - * This argument would typically be set to the value of the byteRange property - * of the current WGCDWebServerRequest. - */ -- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; - -/** - * This method is the designated initializer for the class. - */ -- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.m b/ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.m deleted file mode 100755 index 7661c23..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerFileResponse.m +++ /dev/null @@ -1,187 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import - -#import "WGCDWebServerPrivate.h" - -#define kFileReadBufferSize (32 * 1024) - -@interface WGCDWebServerFileResponse () { -@private - NSString* _path; - NSUInteger _offset; - NSUInteger _size; - int _file; -} -@end - -@implementation WGCDWebServerFileResponse - -+ (instancetype)responseWithFile:(NSString*)path { - return [[[self class] alloc] initWithFile:path]; -} - -+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment { - return [[[self class] alloc] initWithFile:path isAttachment:attachment]; -} - -+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range { - return [[[self class] alloc] initWithFile:path byteRange:range]; -} - -+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { - return [[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]; -} - -- (instancetype)initWithFile:(NSString*)path { - return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO]; -} - -- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { - return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment]; -} - -- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range { - return [self initWithFile:path byteRange:range isAttachment:NO]; -} - -static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) { - return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)]; -} - -- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { - struct stat info; - if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) { - GWS_DNOT_REACHED(); - return nil; - } -#ifndef __LP64__ - if (info.st_size >= (off_t)4294967295) { // In 32 bit mode, we can't handle files greater than 4 GiBs (don't use "NSUIntegerMax" here to avoid potential unsigned to signed conversion issues) - GWS_DNOT_REACHED(); - return nil; - } -#endif - NSUInteger fileSize = (NSUInteger)info.st_size; - - BOOL hasByteRange = WGCDWebServerIsValidByteRange(range); - if (hasByteRange) { - if (range.location != NSUIntegerMax) { - range.location = MIN(range.location, fileSize); - range.length = MIN(range.length, fileSize - range.location); - } else { - range.length = MIN(range.length, fileSize); - range.location = fileSize - range.length; - } - if (range.length == 0) { - return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header - } - } else { - range.location = 0; - range.length = fileSize; - } - - if ((self = [super init])) { - _path = [path copy]; - _offset = range.location; - _size = range.length; - if (hasByteRange) { - [self setStatusCode:kWGCDWebServerHTTPStatusCode_PartialContent]; - [self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"]; - GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path); - } - - if (attachment) { - NSString* fileName = [path lastPathComponent]; - NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]; - NSString* lossyFileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil; - if (lossyFileName) { - NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, WGCDWebServerEscapeURLString(fileName)]; - [self setValue:value forAdditionalHeader:@"Content-Disposition"]; - } else { - GWS_DNOT_REACHED(); - } - } - - self.contentType = WGCDWebServerGetMimeTypeForExtension([_path pathExtension]); - self.contentLength = _size; - self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec); - self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec]; - } - return self; -} - -- (BOOL)open:(NSError**)error { - _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY); - if (_file <= 0) { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - return NO; - } - if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - close(_file); - return NO; - } - return YES; -} - -- (NSData*)readData:(NSError**)error { - size_t length = MIN((NSUInteger)kFileReadBufferSize, _size); - NSMutableData* data = [[NSMutableData alloc] initWithLength:length]; - ssize_t result = read(_file, data.mutableBytes, length); - if (result < 0) { - if (error) { - *error = WGCDWebServerMakePosixError(errno); - } - return nil; - } - if (result > 0) { - [data setLength:result]; - _size -= result; - } - return data; -} - -- (void)close { - close(_file); -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithString:[super description]]; - [description appendFormat:@"\n\n{%@}", _path]; - return description; -} - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.h b/ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.h deleted file mode 100755 index 568b43f..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#import "WGCDWebServerResponse.h" - -/** - * The WGCDWebServerStreamBlock is called to stream the data for the HTTP body. - * The block must return either a chunk of data, an empty NSData when done, or - * nil on error and set the "error" argument which is guaranteed to be non-NULL. - */ -typedef NSData* (^WGCDWebServerStreamBlock)(NSError** error); - -/** - * The WGCDWebServerAsyncStreamBlock works like the WGCDWebServerStreamBlock - * except the streamed data can be returned at a later time allowing for - * truly asynchronous generation of the data. - * - * The block must call "completionBlock" passing the new chunk of data when ready, - * an empty NSData when done, or nil on error and pass a NSError. - * - * The block cannot call "completionBlock" more than once per invocation. - */ -typedef void (^WGCDWebServerAsyncStreamBlock)(WGCDWebServerBodyReaderCompletionBlock completionBlock); - -/** - * The WGCDWebServerStreamedResponse subclass of WGCDWebServerResponse streams - * the body of the HTTP response using a WGCD block. - */ -@interface WGCDWebServerStreamedResponse : WGCDWebServerResponse - -/** - * Creates a response with streamed data and a given content type. - */ -+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block; - -/** - * Creates a response with async streamed data and a given content type. - */ -+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block; - -/** - * Initializes a response with streamed data and a given content type. - */ -- (instancetype)initWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block; - -/** - * This method is the designated initializer for the class. - */ -- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block; - -@end diff --git a/ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.m b/ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.m deleted file mode 100755 index 1fff516..0000000 --- a/ios/WGCDWebServer/Responses/WGCDWebServerStreamedResponse.m +++ /dev/null @@ -1,79 +0,0 @@ -/* - Copyright (c) 2012-2015, Pierre-Olivier Latour - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The name of Pierre-Olivier Latour may not be used to endorse - or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#if !__has_feature(objc_arc) -#error WGCDWebServer requires ARC -#endif - -#import "WGCDWebServerPrivate.h" - -@interface WGCDWebServerStreamedResponse () { -@private - WGCDWebServerAsyncStreamBlock _block; -} -@end - -@implementation WGCDWebServerStreamedResponse - -+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block { - return [[[self class] alloc] initWithContentType:type streamBlock:block]; -} - -+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block { - return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block]; -} - -- (instancetype)initWithContentType:(NSString*)type streamBlock:(WGCDWebServerStreamBlock)block { - return [self initWithContentType:type asyncStreamBlock:^(WGCDWebServerBodyReaderCompletionBlock completionBlock) { - - NSError* error = nil; - NSData* data = block(&error); - completionBlock(data, error); - - }]; -} - -- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(WGCDWebServerAsyncStreamBlock)block { - if ((self = [super init])) { - _block = [block copy]; - - self.contentType = type; - } - return self; -} - -- (void)asyncReadDataWithCompletion:(WGCDWebServerBodyReaderCompletionBlock)block { - _block(block); -} - -- (NSString*)description { - NSMutableString* description = [NSMutableString stringWithString:[super description]]; - [description appendString:@"\n\n"]; - return description; -} - -@end diff --git a/react-native-http-bridge.podspec b/react-native-http-bridge.podspec index e032be4..1c7df19 100644 --- a/react-native-http-bridge.podspec +++ b/react-native-http-bridge.podspec @@ -16,4 +16,5 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m}" s.dependency 'React' + s.dependency 'GCDWebServer' end From 3e8bd0d001db403c8ff81cef05ab281ce0b17161 Mon Sep 17 00:00:00 2001 From: Andrea Barletti Date: Thu, 9 Apr 2020 13:02:27 +0200 Subject: [PATCH 5/5] Fixed IOS crash --- ios/RCTHttpServer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/RCTHttpServer.m b/ios/RCTHttpServer.m index 46e2f8c..0d9a5a3 100644 --- a/ios/RCTHttpServer.m +++ b/ios/RCTHttpServer.m @@ -33,6 +33,7 @@ - (void)initResponseReceivedFor:(GCDWebServer *)server forType:(NSString*)type { int r = arc4random_uniform(1000000); NSString *requestId = [NSString stringWithFormat:@"%lld:%d", milliseconds, r]; + _completionBlocks = [[NSMutableDictionary alloc] init]; @synchronized (self) { [_completionBlocks setObject:completionBlock forKey:requestId]; } @@ -104,7 +105,7 @@ - (void)initResponseReceivedFor:(GCDWebServer *)server forType:(NSString*)type { [_completionBlocks removeObjectForKey:requestId]; } - if (completionBlock) completionBlock(requestResponse); + completionBlock(requestResponse); } @end