You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by sh...@apache.org on 2016/07/25 07:57:27 UTC

[41/57] [abbrv] cordova-plugins git commit: Squashed 'local-webserver/src/ios/GCDWebServer/' content from commit 15caa9c

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/1547b6bc/GCDWebServer/Core/GCDWebServer.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServer.h b/GCDWebServer/Core/GCDWebServer.h
new file mode 100644
index 0000000..ff414c9
--- /dev/null
+++ b/GCDWebServer/Core/GCDWebServer.h
@@ -0,0 +1,567 @@
+/*
+ Copyright (c) 2012-2014, 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 <TargetConditionals.h>
+
+#import "GCDWebServerRequest.h"
+#import "GCDWebServerResponse.h"
+
+/**
+ *  The GCDWebServerMatchBlock is called for every handler added to the
+ *  GCDWebServer 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
+ *  GCDWebServerRequest instance created with the same basic info.
+ *  Otherwise, it simply returns nil.
+ */
+typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
+
+/**
+ *  The GCDWebServerProcessBlock is called after the HTTP request has been fully
+ *  received (i.e. the entire HTTP body has been read). The block is passed the
+ *  GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock.
+ *
+ *  The block must return a GCDWebServerResponse or nil on error, which will
+ *  result in a 500 HTTP status code returned to the client. It's however
+ *  recommended to return a GCDWebServerErrorResponse on error so more useful
+ *  information can be returned to the client.
+ */
+typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
+
+/**
+ *  The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
+ *  except the GCDWebServerResponse 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 GCDWebServerResponse
+ *  or nil on error, which will result in a 500 HTTP status code returned to the client.
+ *  It's however recommended to return a GCDWebServerErrorResponse on error so more
+ *  useful information can be returned to the client.
+ */
+typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
+typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
+
+/**
+ *  The port used by the GCDWebServer (NSNumber / NSUInteger).
+ *
+ *  The default value is 0 i.e. let the OS pick a random port.
+ */
+extern NSString* const GCDWebServerOption_Port;
+
+/**
+ *  The Bonjour name used by the GCDWebServer (NSString). If set to an empty string,
+ *  the name will automatically take the value of the GCDWebServerOption_ServerName
+ *  option. If this option is set to nil, Bonjour will be disabled.
+ *
+ *  The default value is an empty string.
+ */
+extern NSString* const GCDWebServerOption_BonjourName;
+
+/**
+ *  The Bonjour service type used by the GCDWebServer (NSString).
+ *
+ *  The default value is "_http._tcp", the service type for HTTP web servers.
+ */
+extern NSString* const GCDWebServerOption_BonjourType;
+
+/**
+ *  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 GCDWebServerOption_MaxPendingConnections;
+
+/**
+ *  The value for "Server" HTTP header used by the GCDWebServer (NSString).
+ *
+ *  The default value is the GCDWebServer class name.
+ */
+extern NSString* const GCDWebServerOption_ServerName;
+
+/**
+ *  The authentication method used by the GCDWebServer
+ *  (one of "GCDWebServerAuthenticationMethod_...").
+ *
+ *  The default value is nil i.e. authentication is disabled.
+ */
+extern NSString* const GCDWebServerOption_AuthenticationMethod;
+
+/**
+ *  The authentication realm used by the GCDWebServer (NSString).
+ *
+ *  The default value is the same as the GCDWebServerOption_ServerName option.
+ */
+extern NSString* const GCDWebServerOption_AuthenticationRealm;
+
+/**
+ *  The authentication accounts used by the GCDWebServer
+ *  (NSDictionary of username / password pairs).
+ *
+ *  The default value is nil i.e. no accounts.
+ */
+extern NSString* const GCDWebServerOption_AuthenticationAccounts;
+
+/**
+ *  The class used by the GCDWebServer when instantiating GCDWebServerConnection
+ *  (subclass of GCDWebServerConnection).
+ *
+ *  The default value is the GCDWebServerConnection class.
+ */
+extern NSString* const GCDWebServerOption_ConnectionClass;
+
+/**
+ *  Allow the GCDWebServer 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 GCDWebServerOption_AutomaticallyMapHEADToGET;
+
+/**
+ *  The interval expressed in seconds used by the GCDWebServer 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 GCDWebServerOption_ConnectedStateCoalescingInterval;
+
+#if TARGET_OS_IPHONE
+
+/**
+ *  Enables the GCDWebServer to automatically suspend itself (as if -stop was
+ *  called) when the iOS app goes into the background and the last
+ *  GCDWebServerConnection 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 GCDWebServer is suspended.
+ */
+extern NSString* const GCDWebServerOption_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 GCDWebServerAuthenticationMethod_Basic;
+
+/**
+ *  HTTP Digest Access Authentication scheme (see https://tools.ietf.org/html/rfc2617).
+ */
+extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
+
+@class GCDWebServer;
+
+/**
+ *  Delegate methods for GCDWebServer.
+ *
+ *  @warning These methods are always called on the main thread in a serialized way.
+ */
+@protocol GCDWebServerDelegate <NSObject>
+@optional
+
+/**
+ *  This method is called after the server has successfully started.
+ */
+- (void)webServerDidStart:(GCDWebServer*)server;
+
+/**
+ *  This method is called after the Bonjour registration for the server has
+ *  successfully completed.
+ */
+- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;
+
+/**
+ *  This method is called when the first GCDWebServerConnection 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 GCDWebServerConnection instances keep being opened),
+ *  until before the last HTTP request has been responded to (and the
+ *  corresponding last GCDWebServerConnection closed).
+ */
+- (void)webServerDidConnect:(GCDWebServer*)server;
+
+/**
+ *  This method is called when the last GCDWebServerConnection is closed after
+ *  the server has served a series of HTTP requests.
+ *
+ *  The GCDWebServerOption_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:(GCDWebServer*)server;
+
+/**
+ *  This method is called after the server has stopped.
+ */
+- (void)webServerDidStop:(GCDWebServer*)server;
+
+@end
+
+/**
+ *  The GCDWebServer 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.
+ *
+ *  GCDWebServer 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 GCDWebServer.
+ */
+@interface GCDWebServer : NSObject
+
+/**
+ *  Sets the delegate for the server.
+ */
+@property(nonatomic, assign) id<GCDWebServerDelegate> 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:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)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:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)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 GCDWebServerConnection instances
+ *  currently handling already received HTTP requests. These connections will
+ *  continue to execute normally until completion.
+ */
+- (void)stop;
+
+@end
+
+@interface GCDWebServer (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;
+
+/**
+ *  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 GCDWebServer (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:(GCDWebServerProcessBlock)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:(GCDWebServerAsyncProcessBlock)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:(GCDWebServerProcessBlock)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:(GCDWebServerAsyncProcessBlock)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:(GCDWebServerProcessBlock)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:(GCDWebServerAsyncProcessBlock)block;
+
+@end
+
+@interface GCDWebServer (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
+
+/**
+ *  GCDWebServer 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.
+ *
+ *  GCDWebServer is also compatible with a limited set of third-party logging
+ *  facilities. If one of them is available at compile time, GCDWebServer will
+ *  automatically use it in place of the built-in one.
+ *
+ *  Currently supported third-party logging facilities are:
+ *  - XLFacility (by the same author as GCDWebServer): 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 GCDWebServer use a custom logging facility by defining
+ *  the "__GCDWEBSERVER_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(...)
+ *    GWS_LOG_EXCEPTION(__EXCEPTION__)
+ *
+ *  IMPORTANT: Except for GWS_LOG_EXCEPTION() which gets passed an NSException,
+ *  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 GCDWebServer. They can be used for consistency wherever you interact
+ *  with GCDWebServer in your code (e.g. in the implementation of handlers).
+ */
+@interface GCDWebServer (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.
+ */
++ (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);
+
+/**
+ *  Logs an exception to the logging facility at the EXCEPTION level.
+ */
+- (void)logException:(NSException*)exception;
+
+@end
+
+#ifdef __GCDWEBSERVER_ENABLE_TESTING__
+
+@interface GCDWebServer (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

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/1547b6bc/GCDWebServer/Core/GCDWebServer.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m
new file mode 100644
index 0000000..852a9db
--- /dev/null
+++ b/GCDWebServer/Core/GCDWebServer.m
@@ -0,0 +1,1214 @@
+/*
+ Copyright (c) 2012-2014, 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 GCDWebServer requires ARC
+#endif
+
+#import <TargetConditionals.h>
+#if TARGET_OS_IPHONE
+#import <UIKit/UIKit.h>
+#else
+#ifdef __GCDWEBSERVER_ENABLE_TESTING__
+#import <AppKit/AppKit.h>
+#endif
+#endif
+#import <netinet/in.h>
+
+#import "GCDWebServerPrivate.h"
+
+#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
+#define kDefaultPort 80
+#else
+#define kDefaultPort 8080
+#endif
+
+NSString* const GCDWebServerOption_Port = @"Port";
+NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
+NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
+NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
+NSString* const GCDWebServerOption_ServerName = @"ServerName";
+NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod";
+NSString* const GCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm";
+NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts";
+NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass";
+NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET";
+NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval";
+#if TARGET_OS_IPHONE
+NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground";
+#endif
+
+NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
+NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";
+
+#if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
+#if DEBUG
+GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Debug;
+#else
+GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
+#endif
+#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
+#if DEBUG
+int GCDWebServerLogLevel = LOG_LEVEL_DEBUG;
+#else
+int GCDWebServerLogLevel = LOG_LEVEL_INFO;
+#endif
+#endif
+
+#if !TARGET_OS_IPHONE
+static BOOL _run;
+#endif
+
+#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
+
+void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) {
+  static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR", "EXCEPTION"};
+  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(__GCDWEBSERVER_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\u2019s 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 GCDWebServerHandler () {
+@private
+  GCDWebServerMatchBlock _matchBlock;
+  GCDWebServerAsyncProcessBlock _asyncProcessBlock;
+}
+@end
+
+@implementation GCDWebServerHandler
+
+@synthesize matchBlock=_matchBlock, asyncProcessBlock=_asyncProcessBlock;
+
+- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
+  if ((self = [super init])) {
+    _matchBlock = [matchBlock copy];
+    _asyncProcessBlock = [processBlock copy];
+  }
+  return self;
+}
+
+@end
+
+@interface GCDWebServer () {
+@private
+  id<GCDWebServerDelegate> __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;
+  NSUInteger _port;
+  dispatch_source_t _source4;
+  dispatch_source_t _source6;
+  CFNetServiceRef _registrationService;
+  CFNetServiceRef _resolutionService;
+#if TARGET_OS_IPHONE
+  BOOL _suspendInBackground;
+  UIBackgroundTaskIdentifier _backgroundTask;
+#endif
+#ifdef __GCDWEBSERVER_ENABLE_TESTING__
+  BOOL _recording;
+#endif
+}
+@end
+
+@implementation GCDWebServer
+
+@synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm,
+            authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts,
+            shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
+
++ (void)initialize {
+  GCDWebServerInitializeFunctions();
+}
+
+- (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
+  [self _startBackgroundTask];
+#endif
+  
+  if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
+    [_delegate webServerDidConnect:self];
+  }
+}
+
+- (void)willStartConnection:(GCDWebServerConnection*)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");
+  } else {
+    GWS_DNOT_REACHED();
+  }
+}
+
+#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:(GCDWebServerConnection*)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:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
+  [self addHandlerWithMatchBlock:matchBlock asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
+    completionBlock(processBlock(request));
+  }];
+}
+
+- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
+  GWS_DCHECK(_options == nil);
+  GCDWebServerHandler* handler = [[GCDWebServerHandler 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 {
+      GCDWebServer* server = (__bridge GCDWebServer*)info;
+      GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
+      CFNetServiceResolveWithTimeout(server->_resolutionService, 1.0, NULL);
+    }
+  }
+}
+
+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 {
+      GCDWebServer* server = (__bridge GCDWebServer*)info;
+      GWS_LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
+      if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
+        [server.delegate webServerDidCompleteBonjourRegistration:server];
+      }
+    }
+  }
+}
+
+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 = GCDWebServerMakePosixError(errno);
+        }
+        GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
+        close(listeningSocket);
+      }
+    } else {
+      if (error) {
+        *error = GCDWebServerMakePosixError(errno);
+      }
+      GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
+      close(listeningSocket);
+    }
+    
+  } else {
+    if (error) {
+      *error = GCDWebServerMakePosixError(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, kGCDWebServerGCDQueue);
+  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 remoteSockAddr;
+      socklen_t remoteAddrLen = sizeof(remoteSockAddr);
+      int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen);
+      if (socket > 0) {
+        NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
+        
+        struct sockaddr localSockAddr;
+        socklen_t localAddrLen = sizeof(localSockAddr);
+        NSData* localAddress = nil;
+        if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) {
+          localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
+          GWS_DCHECK((!isIPv6 && localSockAddr.sa_family == AF_INET) || (isIPv6 && localSockAddr.sa_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
+        
+        GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket];  // Connection will automatically retain itself while opened
+        [connection self];  // Prevent compiler from complaining about unused variable / useless statement
+      } 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, GCDWebServerOption_Port, @0) unsignedIntegerValue];
+  NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_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 = 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 addr;
+    socklen_t addrlen = sizeof(addr);
+    if (getsockname(listeningSocket4, &addr, &addrlen) == 0) {
+      struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
+      port = ntohs(sockaddr->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 = 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, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
+  NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
+  if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
+    _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
+    _authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
+    NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
+    [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
+      [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
+    }];
+  } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
+    _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
+    _authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
+    NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
+    [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
+      [_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
+    }];
+  }
+  _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
+  _mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
+  _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
+  
+  _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
+  _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
+  _port = port;
+  
+  NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
+  NSString* bonjourType = _GetOption(_options, GCDWebServerOption_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");
+    }
+  }
+  
+  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 (_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;
+  
+  _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 copy];
+#if TARGET_OS_IPHONE
+    _suspendInBackground = [_GetOption(_options, GCDWebServerOption_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 GCDWebServer (Extensions)
+
+- (NSURL*)serverURL {
+  if (_source4) {
+    NSString* ipAddress = GCDWebServerGetPrimaryIPAddress(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;
+}
+
+- (BOOL)start {
+  return [self startWithPort:kDefaultPort bonjourName:@""];
+}
+
+- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
+  NSMutableDictionary* options = [NSMutableDictionary dictionary];
+  [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
+  [options setValue:name forKey:GCDWebServerOption_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:GCDWebServerOption_Port];
+  [options setValue:name forKey:GCDWebServerOption_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 GCDWebServer (Handlers)
+
+- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
+  [self addDefaultHandlerForMethod:method requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
+    completionBlock(block(request));
+  }];
+}
+
+- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
+  [self addHandlerWithMatchBlock:^GCDWebServerRequest *(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:(GCDWebServerProcessBlock)block {
+  [self addHandlerForMethod:method path:path requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
+    completionBlock(block(request));
+  }];
+}
+
+- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
+  if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
+    [self addHandlerWithMatchBlock:^GCDWebServerRequest *(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:(GCDWebServerProcessBlock)block {
+  [self addHandlerForMethod:method pathRegex:regex requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
+    completionBlock(block(request));
+  }];
+}
+
+- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
+  NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
+  if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
+    [self addHandlerWithMatchBlock:^GCDWebServerRequest *(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++) {
+          [captures addObject:[urlPath substringWithRange:[result rangeAtIndex:i]]];
+        }
+      }
+
+      GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
+      [request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
+      return request;
+      
+    } asyncProcessBlock:block];
+  } else {
+    GWS_DNOT_REACHED();
+  }
+}
+
+@end
+
+@implementation GCDWebServer (GETHandlers)
+
+- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
+  [self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+    
+    GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
+    response.cacheControlMaxAge = cacheAge;
+    return response;
+    
+  }];
+}
+
+- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
+  [self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+    
+    GCDWebServerResponse* response = nil;
+    if (allowRangeRequests) {
+      response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
+      [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
+    } else {
+      response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment];
+    }
+    response.cacheControlMaxAge = cacheAge;
+    return response;
+    
+  }];
+}
+
+- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
+  NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
+  if (enumerator == nil) {
+    return nil;
+  }
+  NSMutableString* html = [NSMutableString string];
+  [html appendString:@"<!DOCTYPE html>\n"];
+  [html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
+  [html appendString:@"<ul>\n"];
+  for (NSString* file in enumerator) {
+    if (![file hasPrefix:@"."]) {
+      NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
+      NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+      GWS_DCHECK(escapedFile);
+      if ([type isEqualToString:NSFileTypeRegular]) {
+        [html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
+      } else if ([type isEqualToString:NSFileTypeDirectory]) {
+        [html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, file];
+      }
+    }
+    [enumerator skipDescendents];
+  }
+  [html appendString:@"</ul>\n"];
+  [html appendString:@"</body></html>\n"];
+  return [GCDWebServerDataResponse responseWithHTML:html];
+}
+
+- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
+  if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
+    GCDWebServer* __unsafe_unretained server = self;
+    [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
+      
+      if (![requestMethod isEqualToString:@"GET"]) {
+        return nil;
+      }
+      if (![urlPath hasPrefix:basePath]) {
+        return nil;
+      }
+      return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
+      
+    } processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+      
+      GCDWebServerResponse* 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 [GCDWebServerFileResponse responseWithFile:indexPath];
+            }
+          }
+          response = [server _responseWithContentsOfDirectory:filePath];
+        } else if ([fileType isEqualToString:NSFileTypeRegular]) {
+          if (allowRangeRequests) {
+            response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
+            [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
+          } else {
+            response = [GCDWebServerFileResponse responseWithFile:filePath];
+          }
+        }
+      }
+      if (response) {
+        response.cacheControlMaxAge = cacheAge;
+      } else {
+        response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound];
+      }
+      return response;
+      
+    }];
+  } else {
+    GWS_DNOT_REACHED();
+  }
+}
+
+@end
+
+@implementation GCDWebServer (Logging)
+
++ (void)setLogLevel:(int)level {
+#if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__)
+  [XLSharedFacility setMinLogLevel:level];
+#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
+  GCDWebServerLogLevel = level;
+#else
+  GCDWebServerLogLevel = 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);
+}
+
+- (void)logException:(NSException*)exception {
+  GWS_LOG_EXCEPTION(exception);
+}
+
+@end
+
+#ifdef __GCDWEBSERVER_ENABLE_TESTING__
+
+@implementation GCDWebServer (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 (GCDWebServerIsTextContentType([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

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/1547b6bc/GCDWebServer/Core/GCDWebServerConnection.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerConnection.h b/GCDWebServer/Core/GCDWebServerConnection.h
new file mode 100644
index 0000000..8e4aaf5
--- /dev/null
+++ b/GCDWebServer/Core/GCDWebServerConnection.h
@@ -0,0 +1,179 @@
+/*
+ Copyright (c) 2012-2014, 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 "GCDWebServer.h"
+
+@class GCDWebServerHandler;
+
+/**
+ *  The GCDWebServerConnection class is instantiated by GCDWebServer 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 GCDWebServerOption_ConnectionClass
+ *  option for GCDWebServer to install your custom subclass.
+ *
+ *  @warning The GCDWebServerConnection retains the GCDWebServer until the
+ *  connection is closed.
+ */
+@interface GCDWebServerConnection : NSObject
+
+/**
+ *  Returns the GCDWebServer that owns the connection.
+ */
+@property(nonatomic, readonly) GCDWebServer* 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 GCDWebServer HTTP connections.
+ *
+ *  @warning These methods can be called on any GCD thread.
+ *  Be sure to also call "super" when overriding them.
+ */
+@interface GCDWebServerConnection (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 GCDWebServerResponse 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.
+ */
+- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)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:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion;
+
+/**
+ *  Assuming a valid HTTP request was received and either -preflightRequest:
+ *  or -processRequest:completion: returned a non-nil GCDWebServerResponse,
+ *  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.
+ */
+- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request;
+
+/**
+ *  This method is called if any error happens while validing or processing
+ *  the request or if no GCDWebServerResponse 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:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
+
+/**
+ *  Called when the connection is closed.
+ */
+- (void)close;
+
+@end


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org