Use CocoaPods instead of including copy of GCDWebServer in Project. Reduces conflict chance with other plugins
This commit is contained in:
parent
5d08202131
commit
cfe5dab601
1
.gitignore
vendored
1
.gitignore
vendored
@ -60,3 +60,4 @@ typings/
|
|||||||
# dotenv environment variables file
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
webserver-test
|
||||||
|
2204
package-lock.json
generated
2204
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
49
plugin.xml
49
plugin.xml
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
|
<plugin xmlns="http://cordova.apache.org/ns/plugins/1.0"
|
||||||
id="cordova-plugin-webserver"
|
id="cordova-plugin-webserver"
|
||||||
version="1.0.1">
|
version="1.1.0">
|
||||||
<engines>
|
<engines>
|
||||||
<engine name="cordova" version=">=6.5.0" />
|
<engine name="cordova" version=">=6.5.0" />
|
||||||
</engines>
|
</engines>
|
||||||
@ -27,55 +27,22 @@
|
|||||||
</platform>
|
</platform>
|
||||||
|
|
||||||
<platform name="ios">
|
<platform name="ios">
|
||||||
|
<config-file target="config.xml" parent="/*">
|
||||||
|
<feature name="Webserver">
|
||||||
|
<param name="ios-package" value="Webserver" />
|
||||||
|
</feature>
|
||||||
|
</config-file>
|
||||||
|
|
||||||
<source-file src="src/ios/Webserver.swift" />
|
<source-file src="src/ios/Webserver.swift" />
|
||||||
<source-file src="src/ios/SynchronizedDictionary.swift" />
|
<source-file src="src/ios/SynchronizedDictionary.swift" />
|
||||||
<dependency id="cordova-plugin-add-swift-support" version="1.6.1"/>
|
<dependency id="cordova-plugin-add-swift-support" version="1.6.1"/>
|
||||||
|
|
||||||
<framework src="libz.tbd" />
|
<framework src="libz.tbd" />
|
||||||
<framework src="libxml2.tbd" />
|
<framework src="libxml2.tbd" />
|
||||||
|
<framework src="GCDWebServer" type="podspec" spec="~> 3.5.2" />
|
||||||
|
|
||||||
<!-- GCDWebserver -->
|
<!-- GCDWebserver -->
|
||||||
<header-file src="src/ios/GCDWebServer/Core/GCDWebServer.h" />
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Core/GCDWebServer.m" /> -->
|
|
||||||
<!-- -->
|
|
||||||
<header-file src="src/ios/GCDWebServer-Bridging-Header.h"/>
|
<header-file src="src/ios/GCDWebServer-Bridging-Header.h"/>
|
||||||
<!-- -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Core/GCDWebServerConnection.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Core/GCDWebServerConnection.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Core/GCDWebServerFunctions.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Core/GCDWebServerFunctions.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Core/GCDWebServerPrivate.h" /> -->
|
|
||||||
<header-file src="src/ios/GCDWebServer/Core/GCDWebServerRequest.h" />
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Core/GCDWebServerRequest.m" /> -->
|
|
||||||
<header-file src="src/ios/GCDWebServer/Core/GCDWebServerResponse.h" />
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Core/GCDWebServerResponse.m" /> -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- <!-- GCDWebserver Requests --> -->
|
|
||||||
<header-file src="src/ios/GCDWebServer/Requests/GCDWebServerDataRequest.h" />
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerDataRequest.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Requests/GCDWebServerFileRequest.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerFileRequest.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m" /> -->
|
|
||||||
<!-- -->
|
|
||||||
<!-- <!-- GCDWebserver Response --> -->
|
|
||||||
<header-file src="src/ios/GCDWebServer/Responses/GCDWebServerDataResponse.h" />
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerDataResponse.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Responses/GCDWebServerErrorResponse.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerErrorResponse.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Responses/GCDWebServerFileResponse.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerFileResponse.m" /> -->
|
|
||||||
<!-- <header-file src="src/ios/GCDWebServer/Responses/GCDWebServerStreamedResponse.h" /> -->
|
|
||||||
<!-- <source-file src="src/ios/GCDWebServer/Responses/GCDWebServerStreamedResponse.m" /> -->
|
|
||||||
|
|
||||||
|
|
||||||
<config-file target="config.xml" parent="/*">
|
|
||||||
<feature name="Webserver">
|
|
||||||
<param name="ios-package" value="Webserver" />
|
|
||||||
</feature>
|
|
||||||
</config-file>
|
|
||||||
</platform>
|
</platform>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
@ -1,623 +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 <TargetConditionals.h>
|
|
||||||
|
|
||||||
#import "GCDWebServerRequest.h"
|
|
||||||
#import "GCDWebServerResponse.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerMatchBlock is called for every handler added to the
|
|
||||||
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
|
|
||||||
* 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* _Nullable (^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* _Nullable (^GCDWebServerProcessBlock)(__kindof 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* _Nullable response);
|
|
||||||
typedef void (^GCDWebServerAsyncProcessBlock)(__kindof 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 nil.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 GCDWebServer.
|
|
||||||
*/
|
|
||||||
extern NSString* const GCDWebServerOption_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 GCDWebServerOption_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 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 GCDWebServerOption_DispatchQueuePriority;
|
|
||||||
|
|
||||||
#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.
|
|
||||||
*
|
|
||||||
* Use the "bonjourServerURL" property to retrieve the Bonjour address of the
|
|
||||||
* server.
|
|
||||||
*/
|
|
||||||
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)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:(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, weak, nullable) 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, nullable) 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, nullable) 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:(nullable NSDictionary*)options error:(NSError** _Nullable)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, nullable) 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, nullable) 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, nullable) 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:(nullable 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:(nullable 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:(nullable NSDictionary*)options error:(NSError** _Nullable)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:(nullable 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:(nullable 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(...)
|
|
||||||
*
|
|
||||||
* 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 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.
|
|
||||||
*
|
|
||||||
* 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 __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:(nullable NSDictionary*)options inDirectory:(NSString*)path;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
File diff suppressed because it is too large
Load Diff
@ -1,183 +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 "GCDWebServer.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@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.
|
|
||||||
*/
|
|
||||||
- (nullable 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:(nullable GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the connection is closed.
|
|
||||||
*/
|
|
||||||
- (void)close;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,868 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import <TargetConditionals.h>
|
|
||||||
#import <netdb.h>
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
|
||||||
#import <libkern/OSAtomic.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.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 __GCDWEBSERVER_ENABLE_TESTING__
|
|
||||||
static int32_t _connectionCounter = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface GCDWebServerConnection (Read)
|
|
||||||
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block;
|
|
||||||
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block;
|
|
||||||
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block;
|
|
||||||
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerConnection (Write)
|
|
||||||
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block;
|
|
||||||
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block;
|
|
||||||
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block;
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
|
|
||||||
@implementation GCDWebServerConnection {
|
|
||||||
CFSocketNativeHandle _socket;
|
|
||||||
BOOL _virtualHEAD;
|
|
||||||
|
|
||||||
CFHTTPMessageRef _requestMessage;
|
|
||||||
GCDWebServerRequest* _request;
|
|
||||||
GCDWebServerHandler* _handler;
|
|
||||||
CFHTTPMessageRef _responseMessage;
|
|
||||||
GCDWebServerResponse* _response;
|
|
||||||
NSInteger _statusCode;
|
|
||||||
|
|
||||||
BOOL _opened;
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
|
||||||
NSUInteger _connectionIndex;
|
|
||||||
NSString* _requestPath;
|
|
||||||
int _requestFD;
|
|
||||||
NSString* _responsePath;
|
|
||||||
int _responseFD;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (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 = GCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)));
|
|
||||||
CFRelease(uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)isUsingIPv6 {
|
|
||||||
const struct sockaddr* localSockAddr = _localAddressData.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)GCDWebServerFormatRFC822([NSDate date]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_startProcessingRequest {
|
|
||||||
GWS_DCHECK(_responseMessage == NULL);
|
|
||||||
|
|
||||||
GCDWebServerResponse* preflightResponse = [self preflightRequest:_request];
|
|
||||||
if (preflightResponse) {
|
|
||||||
[self _finishProcessingRequest:preflightResponse];
|
|
||||||
} else {
|
|
||||||
[self processRequest:_request
|
|
||||||
completion:^(GCDWebServerResponse* processResponse) {
|
|
||||||
[self _finishProcessingRequest:processResponse];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
|
||||||
- (void)_finishProcessingRequest:(GCDWebServerResponse*)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)GCDWebServerFormatRFC822((NSDate*)_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)GCDWebServerNormalizeHeaderValue(_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:kGCDWebServerHTTPStatusCode_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:kGCDWebServerHTTPStatusCode_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:kGCDWebServerHTTPStatusCode_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:kGCDWebServerHTTPStatusCode_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:kGCDWebServerHTTPStatusCode_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:kGCDWebServerHTTPStatusCode_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:kGCDWebServerHTTPStatusCode_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* urlPath = requestURL ? CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL)) : nil; // Don't use -[NSURL path] which strips the ending slash
|
|
||||||
NSString* requestPath = urlPath ? GCDWebServerUnescapeURLString(urlPath) : nil;
|
|
||||||
NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
|
|
||||||
NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{};
|
|
||||||
if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) {
|
|
||||||
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:kGCDWebServerHTTPStatusCode_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:kGCDWebServerHTTPStatusCode_BadRequest];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[self _startProcessingRequest];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
|
|
||||||
GWS_DCHECK(_request);
|
|
||||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_MethodNotAllowed];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket {
|
|
||||||
if ((self = [super init])) {
|
|
||||||
_server = server;
|
|
||||||
_localAddressData = localAddress;
|
|
||||||
_remoteAddressData = 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 GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)remoteAddressString {
|
|
||||||
return GCDWebServerStringFromSockAddr(_remoteAddressData.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 GCDWebServerConnection (Read)
|
|
||||||
|
|
||||||
- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block {
|
|
||||||
dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) {
|
|
||||||
|
|
||||||
@autoreleasepool {
|
|
||||||
if (error == 0) {
|
|
||||||
size_t size = dispatch_data_get_size(buffer);
|
|
||||||
if (size > 0) {
|
|
||||||
NSUInteger originalLength = data.length;
|
|
||||||
dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) {
|
|
||||||
[data appendBytes:chunkBytes length:chunkSize];
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
[self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)];
|
|
||||||
block(YES);
|
|
||||||
} else {
|
|
||||||
if (_totalBytesRead > 0) {
|
|
||||||
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
|
|
||||||
} else {
|
|
||||||
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
|
|
||||||
}
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
|
|
||||||
GWS_DCHECK(_requestMessage);
|
|
||||||
[self readData:headersData
|
|
||||||
withLength:NSUIntegerMax
|
|
||||||
completionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)];
|
|
||||||
if (range.location == NSNotFound) {
|
|
||||||
[self readHeaders:headersData withCompletionBlock:block];
|
|
||||||
} else {
|
|
||||||
NSUInteger length = range.location + range.length;
|
|
||||||
if (CFHTTPMessageAppendBytes(_requestMessage, headersData.bytes, length)) {
|
|
||||||
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
|
|
||||||
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
|
||||||
block(nil);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
|
||||||
block(nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
block(nil);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
|
|
||||||
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
|
|
||||||
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
|
|
||||||
[self readData:bodyData
|
|
||||||
withLength:length
|
|
||||||
completionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
if (bodyData.length <= length) {
|
|
||||||
NSError* error = nil;
|
|
||||||
if ([_request performWriteData:bodyData error:&error]) {
|
|
||||||
NSUInteger remainingLength = length - bodyData.length;
|
|
||||||
if (remainingLength) {
|
|
||||||
[self readBodyWithRemainingLength:remainingLength completionBlock:block];
|
|
||||||
} else {
|
|
||||||
block(YES);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
|
||||||
block(NO);
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
|
||||||
char buffer[size + 1];
|
|
||||||
bcopy(bytes, buffer, size);
|
|
||||||
buffer[size] = 0;
|
|
||||||
char* end = NULL;
|
|
||||||
long result = strtol(buffer, &end, 16);
|
|
||||||
return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
|
|
||||||
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
|
|
||||||
if (range.location == NSNotFound) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions
|
|
||||||
NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location);
|
|
||||||
if (length != NSNotFound) {
|
|
||||||
if (length) {
|
|
||||||
if (chunkData.length < range.location + range.length + length + 2) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const char* ptr = (char*)chunkData.bytes + range.location + range.length + length;
|
|
||||||
if ((*ptr == '\r') && (*(ptr + 1) == '\n')) {
|
|
||||||
NSError* error = nil;
|
|
||||||
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
|
|
||||||
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers
|
|
||||||
if (trailerRange.location != NSNotFound) {
|
|
||||||
block(YES);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[self readData:chunkData
|
|
||||||
withLength:NSUIntegerMax
|
|
||||||
completionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
[self readNextBodyChunk:chunkData completionBlock:block];
|
|
||||||
} else {
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerConnection (Write)
|
|
||||||
|
|
||||||
- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block {
|
|
||||||
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{
|
|
||||||
[data self]; // Keeps ARC from releasing data too early
|
|
||||||
});
|
|
||||||
dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) {
|
|
||||||
|
|
||||||
@autoreleasepool {
|
|
||||||
if (error == 0) {
|
|
||||||
GWS_DCHECK(remainingData == NULL);
|
|
||||||
[self didWriteBytes:data.bytes length:data.length];
|
|
||||||
block(YES);
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
|
||||||
dispatch_release(buffer);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
|
|
||||||
GWS_DCHECK(_responseMessage);
|
|
||||||
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
|
|
||||||
[self writeData:(__bridge NSData*)data withCompletionBlock:block];
|
|
||||||
CFRelease(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
|
||||||
GWS_DCHECK([_response hasBody]);
|
|
||||||
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
if (data.length) {
|
|
||||||
if (_response.usesChunkedTransferEncoding) {
|
|
||||||
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
|
||||||
size_t hexLength = strlen(hexString);
|
|
||||||
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
|
||||||
if (chunk == nil) {
|
|
||||||
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
|
|
||||||
bcopy(hexString, ptr, hexLength);
|
|
||||||
ptr += hexLength;
|
|
||||||
*ptr++ = '\r';
|
|
||||||
*ptr++ = '\n';
|
|
||||||
bcopy(data.bytes, ptr, data.length);
|
|
||||||
ptr += data.length;
|
|
||||||
*ptr++ = '\r';
|
|
||||||
*ptr = '\n';
|
|
||||||
data = chunk;
|
|
||||||
}
|
|
||||||
[self writeData:data
|
|
||||||
withCompletionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
[self writeBodyWithCompletionBlock:block];
|
|
||||||
} else {
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
if (_response.usesChunkedTransferEncoding) {
|
|
||||||
[self writeData:_lastChunkData
|
|
||||||
withCompletionBlock:^(BOOL success) {
|
|
||||||
|
|
||||||
block(success);
|
|
||||||
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
block(YES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
|
||||||
block(NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerConnection (Subclassing)
|
|
||||||
|
|
||||||
- (BOOL)open {
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
|
||||||
if (_server.recordingEnabled) {
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
||||||
_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);
|
|
||||||
_totalBytesRead += length;
|
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_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);
|
|
||||||
_totalBytesWritten += length;
|
|
||||||
|
|
||||||
#ifdef __GCDWEBSERVER_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
|
|
||||||
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
|
|
||||||
GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
|
|
||||||
GCDWebServerResponse* 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 = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_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 = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm");
|
|
||||||
if ([realm isEqualToString:_server.authenticationRealm]) {
|
|
||||||
NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce");
|
|
||||||
if ([nonce isEqualToString:_digestAuthenticationNonce]) {
|
|
||||||
NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username");
|
|
||||||
NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri");
|
|
||||||
NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response");
|
|
||||||
NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username];
|
|
||||||
NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required
|
|
||||||
NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2);
|
|
||||||
if ([actualResponse isEqualToString:expectedResponse]) {
|
|
||||||
authenticated = YES;
|
|
||||||
}
|
|
||||||
} else if (nonce.length) {
|
|
||||||
isStaled = YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!authenticated) {
|
|
||||||
response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_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:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
|
|
||||||
GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead);
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)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"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed;
|
|
||||||
GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code];
|
|
||||||
newResponse.cacheControlMaxAge = response.cacheControlMaxAge;
|
|
||||||
newResponse.lastModifiedDate = response.lastModifiedDate;
|
|
||||||
newResponse.eTag = response.eTag;
|
|
||||||
GWS_DCHECK(newResponse);
|
|
||||||
return newResponse;
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)abortRequest:(GCDWebServerRequest*)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 __GCDWEBSERVER_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)_totalBytesRead, (unsigned long)_totalBytesWritten);
|
|
||||||
} else {
|
|
||||||
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@ -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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a file extension to the corresponding MIME type.
|
|
||||||
* If there is no match, "application/octet-stream" is returned.
|
|
||||||
*
|
|
||||||
* Overrides allow to customize the built-in mapping from extensions to MIME
|
|
||||||
* types. Keys of the dictionary must be lowercased file extensions without
|
|
||||||
* the period, and the values must be the corresponding MIME types.
|
|
||||||
*/
|
|
||||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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* _Nullable GCDWebServerEscapeURLString(NSString* string);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unescapes a URL percent-encoded string.
|
|
||||||
*/
|
|
||||||
NSString* _Nullable GCDWebServerUnescapeURLString(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* GCDWebServerParseURLEncodedForm(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* _Nullable GCDWebServerGetPrimaryIPAddress(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* GCDWebServerFormatRFC822(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* _Nullable GCDWebServerParseRFC822(NSString* string);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a date into a string using IOS 8601 formatting.
|
|
||||||
* http://tools.ietf.org/html/rfc3339#section-5.6
|
|
||||||
*/
|
|
||||||
NSString* GCDWebServerFormatISO8601(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* _Nullable GCDWebServerParseISO8601(NSString* string);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,316 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import <TargetConditionals.h>
|
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
#import <MobileCoreServices/MobileCoreServices.h>
|
|
||||||
#else
|
|
||||||
#import <SystemConfiguration/SystemConfiguration.h>
|
|
||||||
#endif
|
|
||||||
#import <CommonCrypto/CommonDigest.h>
|
|
||||||
|
|
||||||
#import <ifaddrs.h>
|
|
||||||
#import <net/if.h>
|
|
||||||
#import <netdb.h>
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.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 GCDWebServerInitializeFunctions() {
|
|
||||||
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* GCDWebServerNormalizeHeaderValue(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* GCDWebServerTruncateHeaderValue(NSString* value) {
|
|
||||||
if (value) {
|
|
||||||
NSRange range = [value rangeOfString:@";"];
|
|
||||||
if (range.location != NSNotFound) {
|
|
||||||
return [value substringToIndex:range.location];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) {
|
|
||||||
NSString* parameter = nil;
|
|
||||||
if (value) {
|
|
||||||
NSScanner* scanner = [[NSScanner alloc] initWithString:value];
|
|
||||||
[scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive
|
|
||||||
NSString* string = [NSString stringWithFormat:@"%@=", name];
|
|
||||||
if ([scanner scanUpToString:string intoString:NULL]) {
|
|
||||||
[scanner scanString:string intoString:NULL];
|
|
||||||
if ([scanner scanString:@"\"" intoString:NULL]) {
|
|
||||||
[scanner scanUpToString:@"\"" intoString:¶meter];
|
|
||||||
} else {
|
|
||||||
[scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://www.w3schools.com/tags/ref_charactersets.asp
|
|
||||||
NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) {
|
|
||||||
NSStringEncoding encoding = kCFStringEncodingInvalidId;
|
|
||||||
if (charset) {
|
|
||||||
encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
|
|
||||||
}
|
|
||||||
return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerFormatRFC822(NSDate* date) {
|
|
||||||
__block NSString* string;
|
|
||||||
dispatch_sync(_dateFormatterQueue, ^{
|
|
||||||
string = [_dateFormatterRFC822 stringFromDate:date];
|
|
||||||
});
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSDate* GCDWebServerParseRFC822(NSString* string) {
|
|
||||||
__block NSDate* date;
|
|
||||||
dispatch_sync(_dateFormatterQueue, ^{
|
|
||||||
date = [_dateFormatterRFC822 dateFromString:string];
|
|
||||||
});
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerFormatISO8601(NSDate* date) {
|
|
||||||
__block NSString* string;
|
|
||||||
dispatch_sync(_dateFormatterQueue, ^{
|
|
||||||
string = [_dateFormatterISO8601 stringFromDate:date];
|
|
||||||
});
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSDate* GCDWebServerParseISO8601(NSString* string) {
|
|
||||||
__block NSDate* date;
|
|
||||||
dispatch_sync(_dateFormatterQueue, ^{
|
|
||||||
date = [_dateFormatterISO8601 dateFromString:string];
|
|
||||||
});
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL GCDWebServerIsTextContentType(NSString* type) {
|
|
||||||
return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerDescribeData(NSData* data, NSString* type) {
|
|
||||||
if (GCDWebServerIsTextContentType(type)) {
|
|
||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset");
|
|
||||||
NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
|
||||||
if (string) {
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* overrides) {
|
|
||||||
NSDictionary* builtInOverrides = @{@"css": @"text/css"};
|
|
||||||
NSString* mimeType = nil;
|
|
||||||
extension = [extension lowercaseString];
|
|
||||||
if (extension.length) {
|
|
||||||
mimeType = [overrides objectForKey:extension];
|
|
||||||
if (mimeType == nil) {
|
|
||||||
mimeType = [builtInOverrides 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 : kGCDWebServerDefaultMimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerEscapeURLString(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* GCDWebServerUnescapeURLString(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* GCDWebServerParseURLEncodedForm(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 ? GCDWebServerUnescapeURLString(key) : nil;
|
|
||||||
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
|
||||||
NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(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* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
|
|
||||||
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) {
|
|
||||||
#if DEBUG
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
#else
|
|
||||||
return @"";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerGetPrimaryIPAddress(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("GCDWebServer"), 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) {
|
|
||||||
NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"];
|
|
||||||
if (interface) {
|
|
||||||
primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool
|
|
||||||
}
|
|
||||||
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 = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
freeifaddrs(list);
|
|
||||||
}
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GCDWebServerComputeMD5Digest(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*)[NSString stringWithUTF8String:buffer];
|
|
||||||
}
|
|
@ -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 <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience constants for "informational" HTTP status codes.
|
|
||||||
*/
|
|
||||||
typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) {
|
|
||||||
kGCDWebServerHTTPStatusCode_Continue = 100,
|
|
||||||
kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101,
|
|
||||||
kGCDWebServerHTTPStatusCode_Processing = 102
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience constants for "successful" HTTP status codes.
|
|
||||||
*/
|
|
||||||
typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) {
|
|
||||||
kGCDWebServerHTTPStatusCode_OK = 200,
|
|
||||||
kGCDWebServerHTTPStatusCode_Created = 201,
|
|
||||||
kGCDWebServerHTTPStatusCode_Accepted = 202,
|
|
||||||
kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203,
|
|
||||||
kGCDWebServerHTTPStatusCode_NoContent = 204,
|
|
||||||
kGCDWebServerHTTPStatusCode_ResetContent = 205,
|
|
||||||
kGCDWebServerHTTPStatusCode_PartialContent = 206,
|
|
||||||
kGCDWebServerHTTPStatusCode_MultiStatus = 207,
|
|
||||||
kGCDWebServerHTTPStatusCode_AlreadyReported = 208
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience constants for "redirection" HTTP status codes.
|
|
||||||
*/
|
|
||||||
typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) {
|
|
||||||
kGCDWebServerHTTPStatusCode_MultipleChoices = 300,
|
|
||||||
kGCDWebServerHTTPStatusCode_MovedPermanently = 301,
|
|
||||||
kGCDWebServerHTTPStatusCode_Found = 302,
|
|
||||||
kGCDWebServerHTTPStatusCode_SeeOther = 303,
|
|
||||||
kGCDWebServerHTTPStatusCode_NotModified = 304,
|
|
||||||
kGCDWebServerHTTPStatusCode_UseProxy = 305,
|
|
||||||
kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307,
|
|
||||||
kGCDWebServerHTTPStatusCode_PermanentRedirect = 308
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience constants for "client error" HTTP status codes.
|
|
||||||
*/
|
|
||||||
typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) {
|
|
||||||
kGCDWebServerHTTPStatusCode_BadRequest = 400,
|
|
||||||
kGCDWebServerHTTPStatusCode_Unauthorized = 401,
|
|
||||||
kGCDWebServerHTTPStatusCode_PaymentRequired = 402,
|
|
||||||
kGCDWebServerHTTPStatusCode_Forbidden = 403,
|
|
||||||
kGCDWebServerHTTPStatusCode_NotFound = 404,
|
|
||||||
kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405,
|
|
||||||
kGCDWebServerHTTPStatusCode_NotAcceptable = 406,
|
|
||||||
kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407,
|
|
||||||
kGCDWebServerHTTPStatusCode_RequestTimeout = 408,
|
|
||||||
kGCDWebServerHTTPStatusCode_Conflict = 409,
|
|
||||||
kGCDWebServerHTTPStatusCode_Gone = 410,
|
|
||||||
kGCDWebServerHTTPStatusCode_LengthRequired = 411,
|
|
||||||
kGCDWebServerHTTPStatusCode_PreconditionFailed = 412,
|
|
||||||
kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413,
|
|
||||||
kGCDWebServerHTTPStatusCode_RequestURITooLong = 414,
|
|
||||||
kGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415,
|
|
||||||
kGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416,
|
|
||||||
kGCDWebServerHTTPStatusCode_ExpectationFailed = 417,
|
|
||||||
kGCDWebServerHTTPStatusCode_UnprocessableEntity = 422,
|
|
||||||
kGCDWebServerHTTPStatusCode_Locked = 423,
|
|
||||||
kGCDWebServerHTTPStatusCode_FailedDependency = 424,
|
|
||||||
kGCDWebServerHTTPStatusCode_UpgradeRequired = 426,
|
|
||||||
kGCDWebServerHTTPStatusCode_PreconditionRequired = 428,
|
|
||||||
kGCDWebServerHTTPStatusCode_TooManyRequests = 429,
|
|
||||||
kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience constants for "server error" HTTP status codes.
|
|
||||||
*/
|
|
||||||
typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) {
|
|
||||||
kGCDWebServerHTTPStatusCode_InternalServerError = 500,
|
|
||||||
kGCDWebServerHTTPStatusCode_NotImplemented = 501,
|
|
||||||
kGCDWebServerHTTPStatusCode_BadGateway = 502,
|
|
||||||
kGCDWebServerHTTPStatusCode_ServiceUnavailable = 503,
|
|
||||||
kGCDWebServerHTTPStatusCode_GatewayTimeout = 504,
|
|
||||||
kGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505,
|
|
||||||
kGCDWebServerHTTPStatusCode_InsufficientStorage = 507,
|
|
||||||
kGCDWebServerHTTPStatusCode_LoopDetected = 508,
|
|
||||||
kGCDWebServerHTTPStatusCode_NotExtended = 510,
|
|
||||||
kGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511
|
|
||||||
};
|
|
@ -1,245 +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 <os/object.h>
|
|
||||||
#import <sys/socket.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All GCDWebServer headers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "GCDWebServerHTTPStatusCodes.h"
|
|
||||||
#import "GCDWebServerFunctions.h"
|
|
||||||
|
|
||||||
#import "GCDWebServer.h"
|
|
||||||
#import "GCDWebServerConnection.h"
|
|
||||||
|
|
||||||
#import "GCDWebServerDataRequest.h"
|
|
||||||
#import "GCDWebServerFileRequest.h"
|
|
||||||
#import "GCDWebServerMultiPartFormRequest.h"
|
|
||||||
#import "GCDWebServerURLEncodedFormRequest.h"
|
|
||||||
|
|
||||||
#import "GCDWebServerDataResponse.h"
|
|
||||||
#import "GCDWebServerErrorResponse.h"
|
|
||||||
#import "GCDWebServerFileResponse.h"
|
|
||||||
#import "GCDWebServerStreamedResponse.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a custom logging facility should be used instead.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(__GCDWEBSERVER_LOGGING_HEADER__)
|
|
||||||
|
|
||||||
#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__
|
|
||||||
|
|
||||||
#import __GCDWEBSERVER_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 __GCDWEBSERVER_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 <CocoaLumberjack/CocoaLumberjack.h>
|
|
||||||
|
|
||||||
#define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__
|
|
||||||
|
|
||||||
#undef LOG_LEVEL_DEF
|
|
||||||
#define LOG_LEVEL_DEF GCDWebServerLogLevel
|
|
||||||
extern DDLogLevel GCDWebServerLogLevel;
|
|
||||||
|
|
||||||
#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 GCDWebServer built-in
|
|
||||||
* logging facility.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#define __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
|
|
||||||
|
|
||||||
typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
|
|
||||||
kGCDWebServerLoggingLevel_Debug = 0,
|
|
||||||
kGCDWebServerLoggingLevel_Verbose,
|
|
||||||
kGCDWebServerLoggingLevel_Info,
|
|
||||||
kGCDWebServerLoggingLevel_Warning,
|
|
||||||
kGCDWebServerLoggingLevel_Error
|
|
||||||
};
|
|
||||||
|
|
||||||
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
|
|
||||||
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
#define GWS_LOG_DEBUG(...) \
|
|
||||||
do { \
|
|
||||||
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); \
|
|
||||||
} while (0)
|
|
||||||
#else
|
|
||||||
#define GWS_LOG_DEBUG(...)
|
|
||||||
#endif
|
|
||||||
#define GWS_LOG_VERBOSE(...) \
|
|
||||||
do { \
|
|
||||||
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); \
|
|
||||||
} while (0)
|
|
||||||
#define GWS_LOG_INFO(...) \
|
|
||||||
do { \
|
|
||||||
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); \
|
|
||||||
} while (0)
|
|
||||||
#define GWS_LOG_WARNING(...) \
|
|
||||||
do { \
|
|
||||||
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); \
|
|
||||||
} while (0)
|
|
||||||
#define GWS_LOG_ERROR(...) \
|
|
||||||
do { \
|
|
||||||
if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GCDWebServer internal constants and APIs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
|
||||||
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
|
|
||||||
|
|
||||||
static inline BOOL GCDWebServerIsValidByteRange(NSRange range) {
|
|
||||||
return ((range.location != NSUIntegerMax) || (range.length > 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline NSError* GCDWebServerMakePosixError(int code) {
|
|
||||||
return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithUTF8String:strerror(code)]}];
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void GCDWebServerInitializeFunctions();
|
|
||||||
extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value);
|
|
||||||
extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value);
|
|
||||||
extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute);
|
|
||||||
extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
|
|
||||||
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
|
||||||
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
|
||||||
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2);
|
|
||||||
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
|
|
||||||
|
|
||||||
@interface GCDWebServerConnection ()
|
|
||||||
- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServer ()
|
|
||||||
@property(nonatomic, readonly) NSMutableArray* handlers;
|
|
||||||
@property(nonatomic, readonly) NSString* serverName;
|
|
||||||
@property(nonatomic, readonly) NSString* authenticationRealm;
|
|
||||||
@property(nonatomic, readonly) NSMutableDictionary* authenticationBasicAccounts;
|
|
||||||
@property(nonatomic, readonly) NSMutableDictionary* authenticationDigestAccounts;
|
|
||||||
@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET;
|
|
||||||
@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority;
|
|
||||||
- (void)willStartConnection:(GCDWebServerConnection*)connection;
|
|
||||||
- (void)didEndConnection:(GCDWebServerConnection*)connection;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerHandler : NSObject
|
|
||||||
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
|
|
||||||
@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerRequest ()
|
|
||||||
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
|
||||||
@property(nonatomic) NSData* localAddressData;
|
|
||||||
@property(nonatomic) NSData* remoteAddressData;
|
|
||||||
- (void)prepareForWriting;
|
|
||||||
- (BOOL)performOpen:(NSError**)error;
|
|
||||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
|
|
||||||
- (BOOL)performClose:(NSError**)error;
|
|
||||||
- (void)setAttribute:(nullable id)attribute forKey:(NSString*)key;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerResponse ()
|
|
||||||
@property(nonatomic, readonly) NSDictionary* additionalHeaders;
|
|
||||||
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
|
||||||
- (void)prepareForReading;
|
|
||||||
- (BOOL)performOpen:(NSError**)error;
|
|
||||||
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
|
|
||||||
- (void)performClose;
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,210 +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 <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
|
|
||||||
* with the contents of any regular expression captures done on the request path.
|
|
||||||
*
|
|
||||||
* @warning This attribute will only be set on the request if adding a handler using
|
|
||||||
* -addHandlerForMethod:pathRegex:requestClass:processBlock:.
|
|
||||||
*/
|
|
||||||
extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This protocol is used by the GCDWebServerConnection to communicate with
|
|
||||||
* the GCDWebServerRequest and write the received HTTP body data.
|
|
||||||
*
|
|
||||||
* Note that multiple GCDWebServerBodyWriter objects can be chained together
|
|
||||||
* internally e.g. to automatically decode gzip encoded content before
|
|
||||||
* passing it on to the GCDWebServerRequest.
|
|
||||||
*
|
|
||||||
* @warning These methods can be called on any GCD thread.
|
|
||||||
*/
|
|
||||||
@protocol GCDWebServerBodyWriter <NSObject>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 GCDWebServerRequest class is instantiated by the GCDWebServerConnection
|
|
||||||
* after the HTTP headers have been received. Each instance wraps a single HTTP
|
|
||||||
* request. If a body is present, the methods from the GCDWebServerBodyWriter
|
|
||||||
* protocol will be called by the GCDWebServerConnection to receive it.
|
|
||||||
*
|
|
||||||
* The default implementation of the GCDWebServerBodyWriter protocol on the class
|
|
||||||
* simply ignores the body data.
|
|
||||||
*
|
|
||||||
* @warning GCDWebServerRequest instances can be created and used on any GCD thread.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerRequest : NSObject <GCDWebServerBodyWriter>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, nullable) 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, nullable) 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, nullable) NSDate* ifModifiedSince;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the parsed "If-None-Match" header or nil if absent or malformed.
|
|
||||||
*/
|
|
||||||
@property(nonatomic, readonly, nullable) 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:(nullable 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.
|
|
||||||
*/
|
|
||||||
- (nullable id)attributeForKey:(NSString*)key;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,303 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import <zlib.h>
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerRequestAttribute_RegexCaptures";
|
|
||||||
|
|
||||||
#define kZlibErrorDomain @"ZlibErrorDomain"
|
|
||||||
#define kGZipInitialBufferSize (256 * 1024)
|
|
||||||
|
|
||||||
@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerBodyDecoder {
|
|
||||||
GCDWebServerRequest* __unsafe_unretained _request;
|
|
||||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id<GCDWebServerBodyWriter> _Nonnull)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
|
|
||||||
|
|
||||||
@implementation GCDWebServerGZipDecoder {
|
|
||||||
z_stream _stream;
|
|
||||||
BOOL _finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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
|
|
||||||
|
|
||||||
@implementation GCDWebServerRequest {
|
|
||||||
BOOL _opened;
|
|
||||||
NSMutableArray* _decoders;
|
|
||||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
|
||||||
NSMutableDictionary* _attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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;
|
|
||||||
|
|
||||||
_contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]);
|
|
||||||
_usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
|
|
||||||
NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
|
|
||||||
if (lengthHeader) {
|
|
||||||
NSInteger length = [lengthHeader integerValue];
|
|
||||||
if (_usesChunkedTransferEncoding || (length < 0)) {
|
|
||||||
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL);
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
_contentLength = length;
|
|
||||||
if (_contentType == nil) {
|
|
||||||
_contentType = kGCDWebServerDefaultMimeType;
|
|
||||||
}
|
|
||||||
} else if (_usesChunkedTransferEncoding) {
|
|
||||||
if (_contentType == nil) {
|
|
||||||
_contentType = kGCDWebServerDefaultMimeType;
|
|
||||||
}
|
|
||||||
_contentLength = NSUIntegerMax;
|
|
||||||
} else {
|
|
||||||
if (_contentType) {
|
|
||||||
GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL);
|
|
||||||
_contentType = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense
|
|
||||||
}
|
|
||||||
_contentLength = NSUIntegerMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
|
|
||||||
if (modifiedHeader) {
|
|
||||||
_ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
|
|
||||||
}
|
|
||||||
_ifNoneMatch = [_headers objectForKey:@"If-None-Match"];
|
|
||||||
|
|
||||||
_byteRange = NSMakeRange(NSUIntegerMax, 0);
|
|
||||||
NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_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"
|
|
||||||
_byteRange.location = startValue;
|
|
||||||
_byteRange.length = endValue - startValue + 1;
|
|
||||||
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
|
|
||||||
_byteRange.location = startValue;
|
|
||||||
_byteRange.length = NSUIntegerMax;
|
|
||||||
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
|
|
||||||
_byteRange.location = NSUIntegerMax;
|
|
||||||
_byteRange.length = endValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((_byteRange.location == NSUIntegerMax) && (_byteRange.length == 0)) { // Ignore "Range" header if syntactically invalid
|
|
||||||
GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
|
|
||||||
_acceptsGzipContentEncoding = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
_decoders = [[NSMutableArray alloc] init];
|
|
||||||
_attributes = [[NSMutableDictionary alloc] init];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)hasBody {
|
|
||||||
return _contentType ? YES : NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)hasByteRange {
|
|
||||||
return GCDWebServerIsValidByteRange(_byteRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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 ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) {
|
|
||||||
GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer];
|
|
||||||
[_decoders addObject:decoder];
|
|
||||||
_writer = decoder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)performOpen:(NSError**)error {
|
|
||||||
GWS_DCHECK(_contentType);
|
|
||||||
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 GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)remoteAddressString {
|
|
||||||
return GCDWebServerStringFromSockAddr(_remoteAddressData.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
|
|
@ -1,212 +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 <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
|
|
||||||
* GCDWebServerBodyReader object when reading data from it asynchronously.
|
|
||||||
*/
|
|
||||||
typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* _Nullable error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This protocol is used by the GCDWebServerConnection to communicate with
|
|
||||||
* the GCDWebServerResponse and read the HTTP body data to send.
|
|
||||||
*
|
|
||||||
* Note that multiple GCDWebServerBodyReader objects can be chained together
|
|
||||||
* internally e.g. to automatically apply gzip encoding to the content before
|
|
||||||
* passing it on to the GCDWebServerResponse.
|
|
||||||
*
|
|
||||||
* @warning These methods can be called on any GCD thread.
|
|
||||||
*/
|
|
||||||
@protocol GCDWebServerBodyReader <NSObject>
|
|
||||||
|
|
||||||
@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.
|
|
||||||
*/
|
|
||||||
- (nullable 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:(GCDWebServerBodyReaderCompletionBlock)block;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerResponse class is used to wrap a single HTTP response.
|
|
||||||
* It is instantiated by the handler of the GCDWebServer that handled the request.
|
|
||||||
* If a body is present, the methods from the GCDWebServerBodyReader protocol
|
|
||||||
* will be called by the GCDWebServerConnection to send it.
|
|
||||||
*
|
|
||||||
* The default implementation of the GCDWebServerBodyReader protocol
|
|
||||||
* on the class simply returns an empty body.
|
|
||||||
*
|
|
||||||
* @warning GCDWebServerResponse instances can be created and used on any GCD thread.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerResponse : NSObject <GCDWebServerBodyReader>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, nullable) 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 GCDWebServerConnection 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, nullable) NSDate* lastModifiedDate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the ETag for the response using the "ETag" header.
|
|
||||||
*
|
|
||||||
* The default value is nil.
|
|
||||||
*/
|
|
||||||
@property(nonatomic, copy, nullable) 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 GCDWebServerResponse like "Content-Type", "ETag", etc...
|
|
||||||
*/
|
|
||||||
- (void)setValue:(nullable NSString*)value forAdditionalHeader:(NSString*)header;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience method that checks if the contentType property is defined.
|
|
||||||
*/
|
|
||||||
- (BOOL)hasBody;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerResponse (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
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,284 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import <zlib.h>
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
#define kZlibErrorDomain @"ZlibErrorDomain"
|
|
||||||
#define kGZipInitialBufferSize (256 * 1024)
|
|
||||||
|
|
||||||
@interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerBodyEncoder {
|
|
||||||
GCDWebServerResponse* __unsafe_unretained _response;
|
|
||||||
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)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
|
|
||||||
|
|
||||||
@implementation GCDWebServerGZipEncoder {
|
|
||||||
z_stream _stream;
|
|
||||||
BOOL _finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id<GCDWebServerBodyReader> _Nonnull)reader {
|
|
||||||
if ((self = [super initWithResponse:response reader:reader])) {
|
|
||||||
response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it
|
|
||||||
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
|
|
||||||
}
|
|
||||||
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
|
|
||||||
|
|
||||||
@implementation GCDWebServerResponse {
|
|
||||||
BOOL _opened;
|
|
||||||
NSMutableArray* _encoders;
|
|
||||||
id<GCDWebServerBodyReader> __unsafe_unretained _reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (instancetype)response {
|
|
||||||
return [[[self class] alloc] init];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init {
|
|
||||||
if ((self = [super init])) {
|
|
||||||
_contentType = nil;
|
|
||||||
_contentLength = NSUIntegerMax;
|
|
||||||
_statusCode = kGCDWebServerHTTPStatusCode_OK;
|
|
||||||
_cacheControlMaxAge = 0;
|
|
||||||
_additionalHeaders = [[NSMutableDictionary alloc] init];
|
|
||||||
_encoders = [[NSMutableArray alloc] init];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header {
|
|
||||||
[_additionalHeaders setValue:value forKey:header];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)hasBody {
|
|
||||||
return _contentType ? YES : NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)usesChunkedTransferEncoding {
|
|
||||||
return (_contentType != nil) && (_contentLength == NSUIntegerMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)open:(NSError**)error {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSData*)readData:(NSError**)error {
|
|
||||||
return [NSData data];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)close {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)prepareForReading {
|
|
||||||
_reader = self;
|
|
||||||
if (_gzipContentEncodingEnabled) {
|
|
||||||
GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader];
|
|
||||||
[_encoders addObject:encoder];
|
|
||||||
_reader = encoder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)performOpen:(NSError**)error {
|
|
||||||
GWS_DCHECK(_contentType);
|
|
||||||
GWS_DCHECK(_reader);
|
|
||||||
if (_opened) {
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
_opened = YES;
|
|
||||||
return [_reader open:error];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)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)_statusCode];
|
|
||||||
if (_contentType) {
|
|
||||||
[description appendFormat:@"\nContent Type = %@", _contentType];
|
|
||||||
}
|
|
||||||
if (_contentLength != NSUIntegerMax) {
|
|
||||||
[description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength];
|
|
||||||
}
|
|
||||||
[description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge];
|
|
||||||
if (_lastModifiedDate) {
|
|
||||||
[description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate];
|
|
||||||
}
|
|
||||||
if (_eTag) {
|
|
||||||
[description appendFormat:@"\nETag = %@", _eTag];
|
|
||||||
}
|
|
||||||
if (_additionalHeaders.count) {
|
|
||||||
[description appendString:@"\n"];
|
|
||||||
for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
|
||||||
[description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerResponse (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 ? kGCDWebServerHTTPStatusCode_MovedPermanently : kGCDWebServerHTTPStatusCode_TemporaryRedirect;
|
|
||||||
[self setValue:[location absoluteString] forAdditionalHeader:@"Location"];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@ -1,64 +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 "GCDWebServerRequest.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
|
|
||||||
* of the HTTP request in memory.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerDataRequest : GCDWebServerRequest
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data for the request body.
|
|
||||||
*/
|
|
||||||
@property(nonatomic, readonly) NSData* data;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface GCDWebServerDataRequest (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, nullable) NSString* text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data for the request body interpreted as a JSON object. If the
|
|
||||||
* content type of the body is not JSON, or if an error occurs, nil is returned.
|
|
||||||
*/
|
|
||||||
@property(nonatomic, readonly, nullable) id jsonObject;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,104 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
@interface GCDWebServerDataRequest ()
|
|
||||||
@property(nonatomic) NSMutableData* data;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerDataRequest {
|
|
||||||
NSString* _text;
|
|
||||||
id _jsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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:kGCDWebServerErrorDomain 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:GCDWebServerDescribeData(_data, (NSString*)self.contentType)];
|
|
||||||
}
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerDataRequest (Extensions)
|
|
||||||
|
|
||||||
- (NSString*)text {
|
|
||||||
if (_text == nil) {
|
|
||||||
if ([self.contentType hasPrefix:@"text/"]) {
|
|
||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
|
||||||
_text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
|
||||||
} else {
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _text;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)jsonObject {
|
|
||||||
if (_jsonObject == nil) {
|
|
||||||
NSString* mimeType = GCDWebServerTruncateHeaderValue(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
|
|
@ -1,49 +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 "GCDWebServerRequest.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body
|
|
||||||
* of the HTTP request to a file on disk.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerFileRequest : GCDWebServerRequest
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the path to the temporary file containing the request body.
|
|
||||||
*
|
|
||||||
* @warning This temporary file will be automatically deleted when the
|
|
||||||
* GCDWebServerFileRequest is deallocated. If you want to preserve this file,
|
|
||||||
* you must move it to a different location beforehand.
|
|
||||||
*/
|
|
||||||
@property(nonatomic, readonly) NSString* temporaryPath;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,102 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
@implementation GCDWebServerFileRequest {
|
|
||||||
int _file;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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 = GCDWebServerMakePosixError(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 = GCDWebServerMakePosixError(errno);
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)close:(NSError**)error {
|
|
||||||
if (close(_file) < 0) {
|
|
||||||
if (error) {
|
|
||||||
*error = GCDWebServerMakePosixError(errno);
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
|
||||||
NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"];
|
|
||||||
if (creationDateHeader) {
|
|
||||||
NSDate* date = GCDWebServerParseISO8601(creationDateHeader);
|
|
||||||
if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"];
|
|
||||||
if (modifiedDateHeader) {
|
|
||||||
NSDate* date = GCDWebServerParseRFC822(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
|
|
@ -1,136 +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 "GCDWebServerRequest.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerMultiPart class is an abstract class that wraps the content
|
|
||||||
* of a part.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerMultiPart : 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 GCDWebServerMultiPartArgument subclass of GCDWebServerMultiPart wraps
|
|
||||||
* the content of a part as data in memory.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, nullable) NSString* string;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps
|
|
||||||
* the content of a part as a file on disk.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* GCDWebServerMultiPartFile 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 GCDWebServerMultiPartFormRequest subclass of GCDWebServerRequest
|
|
||||||
* parses the body of the HTTP request as a multipart encoded form.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the argument parts from the multipart encoded form as
|
|
||||||
* name / GCDWebServerMultiPartArgument pairs.
|
|
||||||
*/
|
|
||||||
@property(nonatomic, readonly) NSArray* arguments;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the files parts from the multipart encoded form as
|
|
||||||
* name / GCDWebServerMultiPartFile 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.
|
|
||||||
*/
|
|
||||||
- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first file for a given control name or nil if not found.
|
|
||||||
*/
|
|
||||||
- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,405 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
#define kMultiPartBufferSize (256 * 1024)
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
kParserState_Undefined = 0,
|
|
||||||
kParserState_Start,
|
|
||||||
kParserState_Headers,
|
|
||||||
kParserState_Content,
|
|
||||||
kParserState_End
|
|
||||||
} ParserState;
|
|
||||||
|
|
||||||
@interface GCDWebServerMIMEStreamParser : NSObject
|
|
||||||
@end
|
|
||||||
|
|
||||||
static NSData* _newlineData = nil;
|
|
||||||
static NSData* _newlinesData = nil;
|
|
||||||
static NSData* _dashNewlineData = nil;
|
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPart
|
|
||||||
|
|
||||||
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type {
|
|
||||||
if ((self = [super init])) {
|
|
||||||
_controlName = [name copy];
|
|
||||||
_contentType = [type copy];
|
|
||||||
_mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType);
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPartArgument
|
|
||||||
|
|
||||||
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data {
|
|
||||||
if ((self = [super initWithControlName:name contentType:type])) {
|
|
||||||
_data = data;
|
|
||||||
|
|
||||||
if ([self.contentType hasPrefix:@"text/"]) {
|
|
||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
|
||||||
_string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)description {
|
|
||||||
return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPartFile
|
|
||||||
|
|
||||||
- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath {
|
|
||||||
if ((self = [super initWithControlName:name contentType:type])) {
|
|
||||||
_fileName = [fileName copy];
|
|
||||||
_temporaryPath = [temporaryPath copy];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc {
|
|
||||||
unlink([_temporaryPath fileSystemRepresentation]);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)description {
|
|
||||||
return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMIMEStreamParser {
|
|
||||||
NSData* _boundary;
|
|
||||||
NSString* _defaultcontrolName;
|
|
||||||
ParserState _state;
|
|
||||||
NSMutableData* _data;
|
|
||||||
NSMutableArray* _arguments;
|
|
||||||
NSMutableArray* _files;
|
|
||||||
|
|
||||||
NSString* _controlName;
|
|
||||||
NSString* _fileName;
|
|
||||||
NSString* _contentType;
|
|
||||||
NSString* _tmpPath;
|
|
||||||
int _tmpFile;
|
|
||||||
GCDWebServerMIMEStreamParser* _subParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray* _Nonnull)arguments files:(NSMutableArray* _Nonnull)files {
|
|
||||||
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
|
|
||||||
if (data == nil) {
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
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 = GCDWebServerNormalizeHeaderValue(value);
|
|
||||||
} else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) {
|
|
||||||
NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value);
|
|
||||||
if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) {
|
|
||||||
_controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name");
|
|
||||||
_fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename");
|
|
||||||
} else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) {
|
|
||||||
_controlName = _defaultcontrolName;
|
|
||||||
_fileName = GCDWebServerExtractHeaderValueParameter(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 ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) {
|
|
||||||
NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary");
|
|
||||||
_subParser = [[GCDWebServerMIMEStreamParser 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;
|
|
||||||
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile 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];
|
|
||||||
GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument 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 GCDWebServerMultiPartFormRequest ()
|
|
||||||
@property(nonatomic) NSMutableArray* arguments;
|
|
||||||
@property(nonatomic) NSMutableArray* files;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerMultiPartFormRequest {
|
|
||||||
GCDWebServerMIMEStreamParser* _parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (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 = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
|
|
||||||
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
|
|
||||||
if (_parser == nil) {
|
|
||||||
if (error) {
|
|
||||||
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain 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:kGCDWebServerErrorDomain 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:kGCDWebServerErrorDomain code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Failed finishing to parse multipart form data" }];
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name {
|
|
||||||
for (GCDWebServerMultiPartArgument* argument in _arguments) {
|
|
||||||
if ([argument.controlName isEqualToString:name]) {
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name {
|
|
||||||
for (GCDWebServerMultiPartFile* 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 (GCDWebServerMultiPartArgument* argument in _arguments) {
|
|
||||||
[description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType];
|
|
||||||
[description appendString:GCDWebServerDescribeData(argument.data, argument.contentType)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_files.count) {
|
|
||||||
[description appendString:@"\n"];
|
|
||||||
for (GCDWebServerMultiPartFile* file in _files) {
|
|
||||||
[description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@ -1,55 +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 "GCDWebServerDataRequest.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest
|
|
||||||
* parses the body of the HTTP request as a URL encoded form using
|
|
||||||
* GCDWebServerParseURLEncodedForm().
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if !__has_feature(objc_arc)
|
|
||||||
#error GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
@implementation GCDWebServerURLEncodedFormRequest
|
|
||||||
|
|
||||||
+ (NSString*)mimeType {
|
|
||||||
return @"application/x-www-form-urlencoded";
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)close:(NSError**)error {
|
|
||||||
if (![super close:error]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
|
||||||
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
|
||||||
_arguments = GCDWebServerParseURLEncodedForm(string);
|
|
||||||
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
|
|
@ -1,113 +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 "GCDWebServerResponse.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body
|
|
||||||
* of the HTTP response from memory.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerDataResponse : GCDWebServerResponse
|
|
||||||
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a response with data in memory and a given content type.
|
|
||||||
*/
|
|
||||||
+ (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 GCDWebServerDataResponse (Extensions)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a data response from text encoded using UTF-8.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithText:(NSString*)text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a data response from HTML encoded using UTF-8.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithHTML:(NSString*)html;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a data response from an HTML template encoded using UTF-8.
|
|
||||||
* See -initWithHTMLTemplate:variables: for details.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a data response from a serialized JSON object and the default
|
|
||||||
* "application/json" content type.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithJSONObject:(id)object;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a data response from a serialized JSON object and a custom
|
|
||||||
* content type.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a data response from text encoded using UTF-8.
|
|
||||||
*/
|
|
||||||
- (nullable instancetype)initWithText:(NSString*)text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a data response from HTML encoded using UTF-8.
|
|
||||||
*/
|
|
||||||
- (nullable 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.
|
|
||||||
*/
|
|
||||||
- (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a data response from a serialized JSON object and the default
|
|
||||||
* "application/json" content type.
|
|
||||||
*/
|
|
||||||
- (nullable instancetype)initWithJSONObject:(id)object;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a data response from a serialized JSON object and a custom
|
|
||||||
* content type.
|
|
||||||
*/
|
|
||||||
- (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,136 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
@implementation GCDWebServerDataResponse {
|
|
||||||
NSData* _data;
|
|
||||||
BOOL _done;
|
|
||||||
}
|
|
||||||
|
|
||||||
@dynamic contentType;
|
|
||||||
|
|
||||||
+ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type {
|
|
||||||
return [[[self class] alloc] initWithData:data contentType:type];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
|
|
||||||
if ((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:GCDWebServerDescribeData(_data, self.contentType)];
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GCDWebServerDataResponse (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)];
|
|
||||||
}];
|
|
||||||
return [self initWithHTML:html];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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) {
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
return [self initWithData:data contentType:type];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
@ -1,85 +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 "GCDWebServerDataResponse.h"
|
|
||||||
#import "GCDWebServerHTTPStatusCodes.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates
|
|
||||||
* an HTML body from an HTTP status code and an error message.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerErrorResponse : GCDWebServerDataResponse
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a client error response with the corresponding HTTP status code.
|
|
||||||
*/
|
|
||||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a server error response with the corresponding HTTP status code.
|
|
||||||
*/
|
|
||||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)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:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a server error response with the corresponding HTTP status code
|
|
||||||
* and an underlying NSError.
|
|
||||||
*/
|
|
||||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a client error response with the corresponding HTTP status code.
|
|
||||||
*/
|
|
||||||
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a server error response with the corresponding HTTP status code.
|
|
||||||
*/
|
|
||||||
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)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:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a server error response with the corresponding HTTP status code
|
|
||||||
* and an underlying NSError.
|
|
||||||
*/
|
|
||||||
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4);
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,124 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
@implementation GCDWebServerErrorResponse
|
|
||||||
|
|
||||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
|
||||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
|
||||||
va_list arguments;
|
|
||||||
va_start(arguments, format);
|
|
||||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
|
||||||
va_end(arguments);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
|
||||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
|
||||||
va_list arguments;
|
|
||||||
va_start(arguments, format);
|
|
||||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
|
||||||
va_end(arguments);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
|
||||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
|
||||||
va_list arguments;
|
|
||||||
va_start(arguments, format);
|
|
||||||
GCDWebServerErrorResponse* response = [[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
|
||||||
va_end(arguments);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
|
||||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
|
||||||
va_list arguments;
|
|
||||||
va_start(arguments, format);
|
|
||||||
GCDWebServerErrorResponse* 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:@"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\"><title>%@</title></head><body><h1>%@: %@</h1><h3>%@</h3></body></html>",
|
|
||||||
title, title, _EscapeHTMLString(message), error];
|
|
||||||
if ((self = [self initWithHTML:html])) {
|
|
||||||
self.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)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:(GCDWebServerServerErrorHTTPStatusCode)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:(GCDWebServerClientErrorHTTPStatusCode)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:(GCDWebServerServerErrorHTTPStatusCode)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
|
|
@ -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 "GCDWebServerResponse.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body
|
|
||||||
* of the HTTP response from a file on disk.
|
|
||||||
*
|
|
||||||
* It will automatically set the contentType, lastModifiedDate and eTag
|
|
||||||
* properties of the GCDWebServerResponse according to the file extension and
|
|
||||||
* metadata.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerFileResponse : GCDWebServerResponse
|
|
||||||
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
|
|
||||||
@property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null
|
|
||||||
@property(nonatomic, copy) NSString* eTag; // Redeclare as non-null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a response with the contents of a file.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithFile:(NSString*)path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a response like +responseWithFile: and sets the "Content-Disposition"
|
|
||||||
* HTTP header for a download if the "attachment" argument is YES.
|
|
||||||
*/
|
|
||||||
+ (nullable 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.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a response like +responseWithFile:byteRange: and sets the
|
|
||||||
* "Content-Disposition" HTTP header for a download if the "attachment"
|
|
||||||
* argument is YES.
|
|
||||||
*/
|
|
||||||
+ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a response with the contents of a file.
|
|
||||||
*/
|
|
||||||
- (nullable instancetype)initWithFile:(NSString*)path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a response like +responseWithFile: and sets the
|
|
||||||
* "Content-Disposition" HTTP header for a download if the "attachment"
|
|
||||||
* argument is YES.
|
|
||||||
*/
|
|
||||||
- (nullable 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 GCDWebServerRequest.
|
|
||||||
*/
|
|
||||||
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is the designated initializer for the class.
|
|
||||||
*
|
|
||||||
* If MIME type overrides are specified, they allow to customize the built-in
|
|
||||||
* mapping from extensions to MIME types. Keys of the dictionary must be lowercased
|
|
||||||
* file extensions without the period, and the values must be the corresponding
|
|
||||||
* MIME types.
|
|
||||||
*/
|
|
||||||
- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,185 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import <sys/stat.h>
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
#define kFileReadBufferSize (32 * 1024)
|
|
||||||
|
|
||||||
@implementation GCDWebServerFileResponse {
|
|
||||||
NSString* _path;
|
|
||||||
NSUInteger _offset;
|
|
||||||
NSUInteger _size;
|
|
||||||
int _file;
|
|
||||||
}
|
|
||||||
|
|
||||||
@dynamic contentType, lastModifiedDate, eTag;
|
|
||||||
|
|
||||||
+ (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 mimeTypeOverrides:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithFile:(NSString*)path {
|
|
||||||
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO mimeTypeOverrides:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
|
||||||
return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment mimeTypeOverrides:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range {
|
|
||||||
return [self initWithFile:path byteRange:range isAttachment:NO mimeTypeOverrides:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
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 mimeTypeOverrides:(NSDictionary*)overrides {
|
|
||||||
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 = GCDWebServerIsValidByteRange(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:kGCDWebServerHTTPStatusCode_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, GCDWebServerEscapeURLString(fileName)];
|
|
||||||
[self setValue:value forAdditionalHeader:@"Content-Disposition"];
|
|
||||||
} else {
|
|
||||||
GWS_DNOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension], overrides);
|
|
||||||
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 = GCDWebServerMakePosixError(errno);
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
|
|
||||||
if (error) {
|
|
||||||
*error = GCDWebServerMakePosixError(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 = GCDWebServerMakePosixError(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
|
|
@ -1,80 +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 "GCDWebServerResponse.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerStreamBlock is called to stream the data for the HTTP body.
|
|
||||||
* The block must return either a chunk of data, an empty NSData when done, or
|
|
||||||
* nil on error and set the "error" argument which is guaranteed to be non-NULL.
|
|
||||||
*/
|
|
||||||
typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock
|
|
||||||
* 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 (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams
|
|
||||||
* the body of the HTTP response using a GCD block.
|
|
||||||
*/
|
|
||||||
@interface GCDWebServerStreamedResponse : GCDWebServerResponse
|
|
||||||
@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a response with streamed data and a given content type.
|
|
||||||
*/
|
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a response with async streamed data and a given content type.
|
|
||||||
*/
|
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a response with streamed data and a given content type.
|
|
||||||
*/
|
|
||||||
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is the designated initializer for the class.
|
|
||||||
*/
|
|
||||||
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,78 +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 GCDWebServer requires ARC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
|
||||||
|
|
||||||
@implementation GCDWebServerStreamedResponse {
|
|
||||||
GCDWebServerAsyncStreamBlock _block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@dynamic contentType;
|
|
||||||
|
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
|
||||||
return [[[self class] alloc] initWithContentType:type streamBlock:block];
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
|
||||||
return [[[self class] alloc] initWithContentType:type asyncStreamBlock:block];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
|
||||||
return [self initWithContentType:type
|
|
||||||
asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
|
||||||
|
|
||||||
NSError* error = nil;
|
|
||||||
NSData* data = block(&error);
|
|
||||||
completionBlock(data, error);
|
|
||||||
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
|
||||||
if ((self = [super init])) {
|
|
||||||
_block = [block copy];
|
|
||||||
|
|
||||||
self.contentType = type;
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
|
|
||||||
_block(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString*)description {
|
|
||||||
NSMutableString* description = [NSMutableString stringWithString:[super description]];
|
|
||||||
[description appendString:@"\n\n<STREAM>"];
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
Loading…
x
Reference in New Issue
Block a user