22 Commits
2.3 ... 2.3.2

Author SHA1 Message Date
Pierre-Olivier Latour
5a0c274807 Fix 2014-04-23 10:40:43 -07:00
Pierre-Olivier Latour
591da12aa3 Exclude GCDWebServerPrivate.h from Podspec 2014-04-23 10:39:42 -07:00
Pierre-Olivier Latour
72475429e4 Bumped version 2014-04-23 10:33:50 -07:00
Pierre-Olivier Latour
01da5969e4 Update README.md 2014-04-22 22:47:07 -07:00
Pierre-Olivier Latour
46890a0642 Merge pull request #43 from iosphere/pullrequest/unusedVar
Fixes a static analyzer warning about unused variables for disabled logging
2014-04-21 21:15:35 -03:00
Pierre-Olivier Latour
143ca5b99f Fix 2014-04-20 10:34:45 -03:00
Pierre-Olivier Latour
519866bc03 Fix 2014-04-19 23:05:13 -03:00
Pierre-Olivier Latour
a93cac5ea6 Fix 2014-04-19 22:56:45 -03:00
Pierre-Olivier Latour
43b578677f Fix 2014-04-19 22:19:47 -03:00
Pierre-Olivier Latour
10cbe27e50 Fixed open() calls 2014-04-19 22:17:17 -03:00
Pierre-Olivier Latour
b1169ce7d1 Bumped version 2014-04-19 17:48:53 -03:00
Pierre-Olivier Latour
766072eb89 Renamed GCDWebServerStreamingResponse to GCDWebServerStreamedResponse.h 2014-04-19 17:43:36 -03:00
Pierre-Olivier Latour
0807cf5fe6 #33 Documented Requests/ and Responses/ 2014-04-19 17:39:09 -03:00
Pierre-Olivier Latour
c6701cd474 Renamed "showHiddenFiles" property to "allowHiddenItems" 2014-04-19 16:31:00 -03:00
Pierre-Olivier Latour
b6866bee8e #33 Documented GCDWebDAVServer/ and GCDWebUploader/ 2014-04-19 16:27:49 -03:00
Pierre-Olivier Latour
075774b6c0 Added -webServerDidCompleteBonjourRegistration: 2014-04-19 12:57:01 -03:00
Pierre-Olivier Latour
7743d18006 Added -logException: 2014-04-19 12:52:34 -03:00
Pierre-Olivier Latour
633d40f155 Don't cache GCDWebServerDataResponse 2014-04-19 12:51:36 -03:00
Pierre-Olivier Latour
5d82a80a34 #33 Documented Core/ 2014-04-19 12:51:00 -03:00
Felix Lamouroux
3e5fe3f956 Fixes logging for non-arc builds 2014-04-19 14:09:55 +02:00
Pierre-Olivier Latour
5a26a09d8e Update README.md 2014-04-19 00:17:45 -03:00
Felix Lamouroux
a5208bd60f Fixes a static analyzer warning about unused variables when logging is disabled 2014-04-18 21:26:18 +02:00
29 changed files with 1500 additions and 182 deletions

View File

@@ -29,30 +29,128 @@
@class GCDWebDAVServer;
// These methods are always called on main thread
/**
* Delegate methods for GCDWebDAVServer.
*
* @warning These methods are always called on the main thread in a serialized way.
*/
@protocol GCDWebDAVServerDelegate <GCDWebServerDelegate>
@optional
/**
* This method is called whenever a file has been downloaded.
*/
- (void)davServer:(GCDWebDAVServer*)server didDownloadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file has been uploaded.
*/
- (void)davServer:(GCDWebDAVServer*)server didUploadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file or directory has been moved.
*/
- (void)davServer:(GCDWebDAVServer*)server didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called whenever a file or directory has been copied.
*/
- (void)davServer:(GCDWebDAVServer*)server didCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called whenever a file or directory has been deleted.
*/
- (void)davServer:(GCDWebDAVServer*)server didDeleteItemAtPath:(NSString*)path;
/**
* This method is called whenever a directory has been created.
*/
- (void)davServer:(GCDWebDAVServer*)server didCreateDirectoryAtPath:(NSString*)path;
@end
/**
* The GCDWebDAVServer subclass of GCDWebServer implements a class 1 compliant
* WebDAV server. It is also partially class 2 compliant but only when the
* client is the OS X WebDAV implementation (so it can work with the OS X Finder).
*
* See the README.md file for more information about the features of GCDWebDAVServer.
*/
@interface GCDWebDAVServer : GCDWebServer
/**
* Returns the upload directory as specified when the server was initialized.
*/
@property(nonatomic, readonly) NSString* uploadDirectory;
/**
* Sets the delegate for the server.
*/
@property(nonatomic, assign) id<GCDWebDAVServerDelegate> delegate;
@property(nonatomic, copy) NSArray* allowedFileExtensions; // Default is nil i.e. all file extensions are allowed
@property(nonatomic) BOOL showHiddenFiles; // Default is NO
/**
* Sets which files are allowed to be operated on depending on their extension.
*
* The default value is nil i.e. all file extensions are allowed.
*/
@property(nonatomic, copy) NSArray* allowedFileExtensions;
/**
* Sets if files and directories whose name start with a period are allowed to
* be operated on.
*
* The default value is NO.
*/
@property(nonatomic) BOOL allowHiddenItems;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithUploadDirectory:(NSString*)path;
@end
// These methods can be called from any thread
/**
* Hooks to customize the behavior of GCDWebDAVServer.
*
* @warning These methods can be called on any GCD thread.
*/
@interface GCDWebDAVServer (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath; // Default implementation returns YES
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath; // Default implementation returns YES
- (BOOL)shouldCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath; // Default implementation returns YES
- (BOOL)shouldDeleteItemAtPath:(NSString*)path; // Default implementation returns YES
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path; // Default implementation returns YES
/**
* This method is called to check if a file upload is allowed to complete.
* The uploaded file is available for inspection at "tempPath".
*
* The default implementation returns YES.
*/
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath;
/**
* This method is called to check if a file or directory is allowed to be moved.
*
* The default implementation returns YES.
*/
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called to check if a file or directory is allowed to be copied.
*
* The default implementation returns YES.
*/
- (BOOL)shouldCopyItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called to check if a file or directory is allowed to be deleted.
*
* The default implementation returns YES.
*/
- (BOOL)shouldDeleteItemAtPath:(NSString*)path;
/**
* This method is called to check if a directory is allowed to be created.
*
* The default implementation returns YES.
*/
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end

View File

@@ -55,7 +55,7 @@ typedef NS_ENUM(NSInteger, DAVProperties) {
@private
NSString* _uploadDirectory;
NSArray* _allowedExtensions;
BOOL _showHidden;
BOOL _allowHidden;
}
@end
@@ -92,7 +92,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_showHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading item name \"%@\" is not allowed", itemName];
}
@@ -130,7 +130,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
}
NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_showHidden) || ![self _checkFileExtension:fileName]) {
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file name \"%@\" is not allowed", fileName];
}
@@ -166,7 +166,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_showHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
}
@@ -203,7 +203,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
}
NSString* directoryName = [absolutePath lastPathComponent];
if (!_showHidden && [directoryName hasPrefix:@"."]) {
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
}
@@ -264,7 +264,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
}
NSString* itemName = [dstAbsolutePath lastPathComponent];
if ((!_showHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", itemName];
}
@@ -430,7 +430,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_showHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Retrieving properties for item name \"%@\" is not allowed", itemName];
}
@@ -454,7 +454,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
relativePath = [relativePath stringByAppendingString:@"/"];
}
for (NSString* item in items) {
if (_showHidden || ![item hasPrefix:@"."]) {
if (_allowHidden || ![item hasPrefix:@"."]) {
[self _addPropertyResponseForItem:[absolutePath stringByAppendingPathComponent:item] resource:[relativePath stringByAppendingString:item] properties:properties xmlString:xmlString];
}
}
@@ -525,7 +525,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
}
NSString* itemName = [absolutePath lastPathComponent];
if ((!_showHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking item name \"%@\" is not allowed", itemName];
}
@@ -585,7 +585,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
}
NSString* itemName = [absolutePath lastPathComponent];
if ((!_showHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Unlocking item name \"%@\" is not allowed", itemName];
}
@@ -597,7 +597,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
@implementation GCDWebDAVServer
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, showHiddenFiles=_showHidden;
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden;
- (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) {

View File

@@ -7,7 +7,7 @@
Pod::Spec.new do |s|
s.name = 'GCDWebServer'
s.version = '2.3'
s.version = '2.3.2'
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.homepage = 'https://github.com/swisspol/GCDWebServer'
@@ -22,6 +22,7 @@ Pod::Spec.new do |s|
s.subspec 'Core' do |cs|
cs.source_files = 'GCDWebServer/**/*.{h,m}'
cs.private_header_files = "GCDWebServer/Core/GCDWebServerPrivate.h"
cs.requires_arc = true
cs.ios.library = 'z'
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'

View File

@@ -54,8 +54,8 @@
E28BAE4918F99C810095C089 /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE2F18F99C810095C089 /* GCDWebServerErrorResponse.m */; };
E28BAE4A18F99C810095C089 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE3118F99C810095C089 /* GCDWebServerFileResponse.m */; };
E28BAE4B18F99C810095C089 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE3118F99C810095C089 /* GCDWebServerFileResponse.m */; };
E28BAE4C18F99C810095C089 /* GCDWebServerStreamingResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE3318F99C810095C089 /* GCDWebServerStreamingResponse.m */; };
E28BAE4D18F99C810095C089 /* GCDWebServerStreamingResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE3318F99C810095C089 /* GCDWebServerStreamingResponse.m */; };
E28BAE4C18F99C810095C089 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE3318F99C810095C089 /* GCDWebServerStreamedResponse.m */; };
E28BAE4D18F99C810095C089 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E28BAE3318F99C810095C089 /* GCDWebServerStreamedResponse.m */; };
E2A0E80A18F3432600C580B1 /* GCDWebDAVServer.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E80918F3432600C580B1 /* GCDWebDAVServer.m */; };
E2A0E80B18F3432600C580B1 /* GCDWebDAVServer.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E80918F3432600C580B1 /* GCDWebDAVServer.m */; };
E2A0E80D18F35C9A00C580B1 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E2A0E80C18F35C9A00C580B1 /* libxml2.dylib */; };
@@ -138,8 +138,8 @@
E28BAE2F18F99C810095C089 /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerErrorResponse.m; sourceTree = "<group>"; };
E28BAE3018F99C810095C089 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = "<group>"; };
E28BAE3118F99C810095C089 /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileResponse.m; sourceTree = "<group>"; };
E28BAE3218F99C810095C089 /* GCDWebServerStreamingResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamingResponse.h; sourceTree = "<group>"; };
E28BAE3318F99C810095C089 /* GCDWebServerStreamingResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamingResponse.m; sourceTree = "<group>"; };
E28BAE3218F99C810095C089 /* GCDWebServerStreamedResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamedResponse.h; sourceTree = "<group>"; };
E28BAE3318F99C810095C089 /* GCDWebServerStreamedResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamedResponse.m; sourceTree = "<group>"; };
E2A0E80818F3432600C580B1 /* GCDWebDAVServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebDAVServer.h; sourceTree = "<group>"; };
E2A0E80918F3432600C580B1 /* GCDWebDAVServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebDAVServer.m; sourceTree = "<group>"; };
E2A0E80C18F35C9A00C580B1 /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk/usr/lib/libxml2.dylib; sourceTree = DEVELOPER_DIR; };
@@ -300,8 +300,8 @@
E28BAE2F18F99C810095C089 /* GCDWebServerErrorResponse.m */,
E28BAE3018F99C810095C089 /* GCDWebServerFileResponse.h */,
E28BAE3118F99C810095C089 /* GCDWebServerFileResponse.m */,
E28BAE3218F99C810095C089 /* GCDWebServerStreamingResponse.h */,
E28BAE3318F99C810095C089 /* GCDWebServerStreamingResponse.m */,
E28BAE3218F99C810095C089 /* GCDWebServerStreamedResponse.h */,
E28BAE3318F99C810095C089 /* GCDWebServerStreamedResponse.m */,
);
path = Responses;
sourceTree = "<group>";
@@ -437,7 +437,7 @@
E28BAE3618F99C810095C089 /* GCDWebServerConnection.m in Sources */,
E28BAE3C18F99C810095C089 /* GCDWebServerResponse.m in Sources */,
E28BAE4018F99C810095C089 /* GCDWebServerFileRequest.m in Sources */,
E28BAE4C18F99C810095C089 /* GCDWebServerStreamingResponse.m in Sources */,
E28BAE4C18F99C810095C089 /* GCDWebServerStreamedResponse.m in Sources */,
E28BAE3E18F99C810095C089 /* GCDWebServerDataRequest.m in Sources */,
E2A0E80A18F3432600C580B1 /* GCDWebDAVServer.m in Sources */,
E28BAE4218F99C810095C089 /* GCDWebServerMultiPartFormRequest.m in Sources */,
@@ -457,7 +457,7 @@
E28BAE4B18F99C810095C089 /* GCDWebServerFileResponse.m in Sources */,
E28BAE3918F99C810095C089 /* GCDWebServerFunctions.m in Sources */,
E28BAE4118F99C810095C089 /* GCDWebServerFileRequest.m in Sources */,
E28BAE4D18F99C810095C089 /* GCDWebServerStreamingResponse.m in Sources */,
E28BAE4D18F99C810095C089 /* GCDWebServerStreamedResponse.m in Sources */,
E28BAE3F18F99C810095C089 /* GCDWebServerDataRequest.m in Sources */,
E2BE850B18E77ECA0061360B /* GCDWebUploader.m in Sources */,
E22112951690B64F0048D2B2 /* AppDelegate.m in Sources */,

View File

@@ -30,8 +30,14 @@
#import "GCDWebServerRequest.h"
#import "GCDWebServerResponse.h"
/**
* Log levels used by GCDWebServer.
*
* @warning kGCDWebServerLogLevel_Debug is only available if "NDEBUG" is not
* defined when building.
*/
typedef NS_ENUM(int, GCDWebServerLogLevel) {
kGCDWebServerLogLevel_Debug = 0, // Only available if "NDEBUG" is not defined when building
kGCDWebServerLogLevel_Debug = 0,
kGCDWebServerLogLevel_Verbose,
kGCDWebServerLogLevel_Info,
kGCDWebServerLogLevel_Warning,
@@ -39,88 +45,436 @@ typedef NS_ENUM(int, GCDWebServerLogLevel) {
kGCDWebServerLogLevel_Exception,
};
/**
* The GCDWebServerMatchBlock is called for every handler added to the
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
* been received). The block is passed the basic info for the request (HTTP method,
* URL, headers...) and must decide if it wants to handle it or not.
*
* If the handler can handle the request, the block must return a new
* GCDWebServerRequest instance created with the same basic info.
* Otherwise, it simply returns nil.
*/
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
/**
* The GCDWebServerProcessBlock is called after the HTTP request has been fully
* received (i.e. the entire HTTP body has been read). The block is passed the
* GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock.
*
* The block must return a GCDWebServerResponse or nil on error, which will
* result in a 500 HTTP status code returned to the client. It's however
* recommended to return a GCDWebServerErrorResponse on error so more useful
* information can be returned to the client.
*/
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
extern NSString* const GCDWebServerOption_Port; // NSNumber / NSUInteger (default is 0 i.e. use a random port)
extern NSString* const GCDWebServerOption_BonjourName; // NSString (default is empty string i.e. use computer name)
extern NSString* const GCDWebServerOption_MaxPendingConnections; // NSNumber / NSUInteger (default is 16)
extern NSString* const GCDWebServerOption_ServerName; // NSString (default is server class name)
extern NSString* const GCDWebServerOption_AuthenticationMethod; // One of "GCDWebServerAuthenticationMethod_..." (default is nil i.e. no authentication)
extern NSString* const GCDWebServerOption_AuthenticationRealm; // NSString (default is server name)
extern NSString* const GCDWebServerOption_AuthenticationAccounts; // NSDictionary of username / password (default is nil i.e. no accounts)
extern NSString* const GCDWebServerOption_ConnectionClass; // Subclass of GCDWebServerConnection (default is GCDWebServerConnection class)
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; // NSNumber / BOOL (default is YES)
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; // NSNumber / double (default is 1.0 seconds - set to <=0.0 to disable coaslescing of -webServerDidConnect: / -webServerDidDisconnect:)
/**
* 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).
*
* The default value is an empty string i.e. use the computer / device name.
*/
extern NSString* const GCDWebServerOption_BonjourName;
/**
* The maximum number of incoming HTTP requests that can be queued waiting to
* be handled before new ones are dropped (NSNumber / NSUInteger).
*
* The default value is 16.
*/
extern NSString* const GCDWebServerOption_MaxPendingConnections;
/**
* The value for "Server" HTTP header used by the GCDWebServer (NSString).
*
* The default value is the GCDWebServer class name.
*/
extern NSString* const GCDWebServerOption_ServerName;
/**
* The authentication method used by the GCDWebServer
* (one of "GCDWebServerAuthenticationMethod_...").
*
* The default value is nil i.e. authentication is disabled.
*/
extern NSString* const GCDWebServerOption_AuthenticationMethod;
/**
* The authentication realm used by the GCDWebServer (NSString).
*
* The default value is the same as the GCDWebServerOption_ServerName option.
*/
extern NSString* const GCDWebServerOption_AuthenticationRealm;
/**
* The authentication accounts used by the GCDWebServer
* (NSDictionary of username / password pairs).
*
* The default value is nil i.e. no accounts.
*/
extern NSString* const GCDWebServerOption_AuthenticationAccounts;
/**
* The class used by the GCDWebServer when instantiating GCDWebServerConnection
* (subclass of GCDWebServerConnection).
*
* The default value is the GCDWebServerConnection class.
*/
extern NSString* const GCDWebServerOption_ConnectionClass;
/**
* Allow the GCDWebServer to pretend "HEAD" requests are actually "GET" ones
* and automatically discard the HTTP body of the response (NSNumber / BOOL).
*
* The default value is YES.
*/
extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET;
/**
* The interval expressed in seconds used by the GCDWebServer to decide how to
* coalesce calls to -webServerDidConnect: and -webServerDidDisconnect:
* (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0.
*
* The default value is 1.0 second.
*/
extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval;
#if TARGET_OS_IPHONE
extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; // NSNumber / BOOL (default is YES)
/**
* 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
extern NSString* const GCDWebServerAuthenticationMethod_Basic; // Not recommended as password is sent in clear
/**
* 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;
// These methods are always called on main thread
/**
* 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;
- (void)webServerDidConnect:(GCDWebServer*)server; // Called when first connection is opened
- (void)webServerDidDisconnect:(GCDWebServer*)server; // Called when last connection is closed
/**
* This method is called after the Bonjour registration for the server has
* successfully completed.
*/
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;
/**
* This method is called when the first GCDWebServerConnection is opened by the
* server to serve a series of HTTP requests.
*
* A series of HTTP requests is considered ongoing as long as new HTTP requests
* keep coming (and new GCDWebServerConnection instances keep being opened),
* until before the last HTTP request has been responded to (and the
* corresponding last GCDWebServerConnection closed).
*/
- (void)webServerDidConnect:(GCDWebServer*)server;
/**
* This method is called when the last GCDWebServerConnection is closed after
* the server has served a series of HTTP requests.
*
* The GCDWebServerOption_ConnectedStateCoalescingInterval option can be used
* to have the server wait some extra delay before considering that the series
* of HTTP requests has ended (in case there some latency between consecutive
* requests). This effectively coalesces the calls to -webServerDidConnect:
* and -webServerDidDisconnect:.
*/
- (void)webServerDidDisconnect:(GCDWebServer*)server;
/**
* This method is called after the server has stopped.
*/
- (void)webServerDidStop:(GCDWebServer*)server;
@end
/**
* The GCDWebServer class listens for incoming HTTP requests on a given port,
* then passes each one to a "handler" capable of generating an HTTP response
* for it, which is then sent back to the client.
*
* See the README.md file for more information about the architecture of GCDWebServer.
*/
@interface GCDWebServer : NSObject
/**
* Sets the delegate for the server.
*/
@property(nonatomic, assign) id<GCDWebServerDelegate> delegate;
/**
* Returns YES if the server is currently running.
*/
@property(nonatomic, readonly, getter=isRunning) BOOL running;
@property(nonatomic, readonly) NSUInteger port; // Only non-zero if running
@property(nonatomic, readonly) NSString* bonjourName; // Only non-nil if Bonjour registration is active
/**
* Returns the port used by the server.
*
* @warning This property is only valid if the server is running.
*/
@property(nonatomic, readonly) NSUInteger port;
/**
* Returns the Bonjour name used by the server.
*
* @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds.
*/
@property(nonatomic, readonly) NSString* bonjourName;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)init;
/**
* Adds a handler to the server to handle 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;
/**
* Removes all handlers previously added to the server.
*
* @warning Removing handlers while the server is running is not allowed.
*/
- (void)removeAllHandlers;
- (BOOL)start; // Default is port 8080 (OS X & iOS Simulator) or 80 (iOS) and computer / device name for Bonjour
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name; // Pass nil name to disable Bonjour or empty string to use computer name
/**
* Starts the server with explicit options. This method is the designated way
* to start the server.
*
* Returns NO if the server failed to start.
*/
- (BOOL)startWithOptions:(NSDictionary*)options;
- (void)stop; // Does not abort any currently opened connections
/**
* 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)
@property(nonatomic, readonly) NSURL* serverURL; // Only non-nil if server is running
@property(nonatomic, readonly) NSURL* bonjourServerURL; // Only non-nil if server is running and Bonjour registration is active
/**
* Returns the server's URL.
*
* @warning This property is only valid if the server is running.
*/
@property(nonatomic, readonly) NSURL* serverURL;
/**
* Returns the server's Bonjour URL.
*
* @warning This property is only valid if the server is running and Bonjour
* registration has successfully completed, which can take up to a few seconds.
*/
@property(nonatomic, readonly) NSURL* bonjourServerURL;
/**
* Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
* using the computer / device name for as the 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 computer / device name.
*
* Returns NO if the server failed to start.
*/
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name;
#if !TARGET_OS_IPHONE
/**
* Runs the server synchronously using -startWithPort:bonjourName: until a
* SIGINT signal is received i.e. Ctrl-C. This method is intended to be used
* by command line tools.
*
* Returns NO if the server failed to start.
*
* @warning This method must be used from the main thread only.
*/
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name;
- (BOOL)runWithOptions:(NSDictionary*)options; // Starts then automatically stops on SIGINT i.e. Ctrl-C (use on main thread only)
/**
* Runs the server synchronously using -startWithOptions: 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)runWithOptions:(NSDictionary*)options;
#endif
@end
@interface GCDWebServer (Handlers)
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; // Path is case-insensitive
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; // Regular expression is case-insensitive
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a specific case-insensitive path.
*/
- (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 path matching a case-insensitive regular expression.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
@end
@interface GCDWebServer (GETHandlers)
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge; // Path is case-insensitive
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; // Path is case-insensitive
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; // Base path is recursive and case-sensitive
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a specific case-insensitive path with in-memory data.
*/
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge;
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a specific case-insensitive path with a file.
*/
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
/**
* Adds a handler to the server to respond to incoming "GET" HTTP requests
* with a case-insensitive path inside a base path with the corresponding file
* inside a local directory. If no local file matches the request path, a 401
* HTTP status code is returned to the client.
*
* The "indexFilename" argument allows to specify an "index" file name to use
* when the request path corresponds to a directory.
*/
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests;
@end
@interface GCDWebServer (Logging)
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
+ (void)setLogLevel:(GCDWebServerLogLevel)level; // Default level is DEBUG or INFO if "NDEBUG" is defined when building (it can also be set at runtime with the "logLevel" environment variable)
/**
* Sets the current log level below which logged messages are discarded.
*
* The default level is either DEBUG or INFO if "NDEBUG" is defined at build-time.
* It can also be set at runtime with the "logLevel" environment variable.
*/
+ (void)setLogLevel:(GCDWebServerLogLevel)level;
#endif
/**
* Logs a message with the kGCDWebServerLogLevel_Verbose level.
*/
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
/**
* Logs a message with the kGCDWebServerLogLevel_Info level.
*/
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
/**
* Logs a message with the kGCDWebServerLogLevel_Warning level.
*/
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
/**
* Logs a message with the kGCDWebServerLogLevel_Error level.
*/
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
/**
* Logs an exception with the kGCDWebServerLogLevel_Exception level.
*/
- (void)logException:(NSException*)exception;
@end
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
@interface GCDWebServer (Testing)
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled; // Creates files in the current directory containing the raw data for all requests and responses (directory most NOT contain prior recordings)
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path; // Returns number of failed tests or -1 if server failed to start
/**
* Activates recording of HTTP requests and responses which create files in the
* current directory containing the raw data for all requests and responses.
*
* @warning The current directory must not contain any prior recording files.
*/
@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled;
/**
* Runs tests by playing back pre-recorded HTTP requests in the given directory
* and comparing the generated responses with the pre-recorded ones.
*
* Returns the number of failed tests or -1 if server failed to start.
*/
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path;
@end
#endif

View File

@@ -335,12 +335,16 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
}
static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
DCHECK([NSThread isMainThread]);
@autoreleasepool {
if (error->error) {
LOG_ERROR(@"Bonjour error %i (domain %i)", (int)error->error, (int)error->domain);
} else {
GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info;
LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
[server.delegate webServerDidCompleteBonjourRegistration:server];
}
}
}
}
@@ -524,17 +528,6 @@ static inline NSString* _EncodeBase64(NSString* string) {
}
}
- (BOOL)start {
return [self startWithPort:kDefaultPort bonjourName:@""];
}
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
[options setValue:name forKey:GCDWebServerOption_BonjourName];
return [self startWithOptions:options];
}
#if TARGET_OS_IPHONE
- (void)_didEnterBackground:(NSNotification*)notification {
@@ -636,6 +629,17 @@ static inline NSString* _EncodeBase64(NSString* string) {
return nil;
}
- (BOOL)start {
return [self startWithPort:kDefaultPort bonjourName:@""];
}
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
NSMutableDictionary* options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port];
[options setValue:name forKey:GCDWebServerOption_BonjourName];
return [self startWithOptions:options];
}
#if !TARGET_OS_IPHONE
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name {
@@ -722,10 +726,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
@implementation GCDWebServer (GETHandlers)
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
response.cacheControlMaxAge = cacheAge;
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
response.cacheControlMaxAge = cacheAge;
return response;
}];
@@ -842,37 +846,33 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (void)logVerbose:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_VERBOSE(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_VERBOSE(@"%@", message);
ARC_RELEASE(message);
}
- (void)logInfo:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_INFO(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_INFO(@"%@", message);
ARC_RELEASE(message);
}
- (void)logWarning:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_WARNING(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_WARNING(@"%@", message);
ARC_RELEASE(message);
}
- (void)logError:(NSString*)format, ... {
va_list arguments;
va_start(arguments, format);
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
LOG_ERROR(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
va_end(arguments);
LOG_ERROR(@"%@", message);
ARC_RELEASE(message);
}
- (void)logException:(NSException*)exception {
LOG_EXCEPTION(exception);
}
@end

View File

@@ -29,24 +29,137 @@
@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;
@property(nonatomic, readonly) NSData* localAddressData; // struct sockaddr
/**
* 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 dotted string.
*/
@property(nonatomic, readonly) NSString* localAddressString;
@property(nonatomic, readonly) NSData* remoteAddressData; // struct sockaddr
/**
* 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 dotted 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
// These methods can be called from any thread
/**
* 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)
- (BOOL)open; // Return NO to reject connection e.g. after validating local or remote addresses
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; // Called after data has been read from the connection
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; // Called after data has been written to the connection
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request; // Called before request is processed to return an override response bypassing processing or nil to continue - Default implementation checks authentication if applicable
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block; // Only called if the request can be processed
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request; // Default implementation replaces any response matching the "ETag" or "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) one
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; // If request headers were malformed, "request" will be nil
/**
* 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;
/**
* Assuming a valid HTTP request was received, this method is called before
* the request is processed.
*
* Return a non-nil GCDWebServerResponse to bypass the request processing entirely.
*
* The default implementation checks for HTTP authentication if applicable
* and returns a barebone 401 status code response if authentication failed.
*/
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request;
/**
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
* this method is called to process the request.
*/
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
/**
* Assuming a valid HTTP request was received and either -preflightRequest:
* or -processRequest:withBlock: returned a non-nil GCDWebServerResponse,
* this method is called to override the response.
*
* You can either modify the current response and return it, or return a
* completely new one.
*
* The default implementation replaces any response matching the "ETag" or
* "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304)
* one.
*/
- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request;
/**
* This method is called if any error happens while validing or processing
* the request or if no GCDWebServerResponse was generated during processing.
*
* @warning If the request was invalid (e.g. the HTTP headers were malformed),
* the "request" argument will be nil.
*/
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode;
/**
* Called when the connection is closed.
*/
- (void)close;
@end

View File

@@ -675,11 +675,11 @@ static NSString* _StringFromAddressData(NSData* data) {
_connectionIndex = OSAtomicIncrement32(&_connectionCounter);
_requestPath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY);
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
DCHECK(_requestFD > 0);
_responsePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY);
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
DCHECK(_responseFD > 0);
}
#endif

View File

@@ -31,14 +31,69 @@
extern "C" {
#endif
/**
* Converts a file extension to the corresponding MIME type.
* If there is no match, "application/octet-stream" is returned.
*/
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
/**
* Add percent-escapes to a string so it can be used in a URL.
* The legal characters ":@/?&=+" are also escaped to ensure compatibility
* with URL encoded forms and URL queries.
*/
NSString* GCDWebServerEscapeURLString(NSString* string);
/**
* Unescapes a URL percent-encoded string.
*/
NSString* 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);
NSString* GCDWebServerGetPrimaryIPv4Address(); // Returns IPv4 address of primary connected service on OS X or of WiFi interface on iOS if connected
/**
* On OS X, returns the IPv4 address as a dotted string of the primary connected
* service or nil if not available.
*
* On iOS, returns the IPv4 address as a dotted string of the WiFi interface
* if connected or nil otherwise.
*/
NSString* GCDWebServerGetPrimaryIPv4Address();
/**
* 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* 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* GCDWebServerParseISO8601(NSString* string);
#ifdef __cplusplus

View File

@@ -43,8 +43,7 @@ static NSDateFormatter* _dateFormatterRFC822 = nil;
static NSDateFormatter* _dateFormatterISO8601 = nil;
static dispatch_queue_t _dateFormatterQueue = NULL;
// HTTP/1.1 server must use RFC822
// TODO: Handle RFC 850 and ANSI C's asctime() format (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3)
// TODO: Handle RFC 850 and ANSI C's asctime() format
void GCDWebServerInitializeFunctions() {
DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
if (_dateFormatterRFC822 == nil) {
@@ -187,7 +186,6 @@ NSString* GCDWebServerUnescapeURLString(NSString* string) {
return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
}
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
NSScanner* scanner = [[NSScanner alloc] initWithString:form];

View File

@@ -30,12 +30,18 @@
#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,
@@ -48,6 +54,9 @@ typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_AlreadyReported = 208
};
/**
* Convenience constants for "redirection" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_MultipleChoices = 300,
kGCDWebServerHTTPStatusCode_MovedPermanently = 301,
@@ -59,6 +68,9 @@ typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_PermanentRedirect = 308
};
/**
* Convenience constants for "client error" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_BadRequest = 400,
kGCDWebServerHTTPStatusCode_Unauthorized = 401,
@@ -87,6 +99,9 @@ typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431
};
/**
* Convenience constants for "server error" HTTP status codes.
*/
typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) {
kGCDWebServerHTTPStatusCode_InternalServerError = 500,
kGCDWebServerHTTPStatusCode_NotImplemented = 501,

View File

@@ -67,7 +67,7 @@
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerErrorResponse.h"
#import "GCDWebServerFileResponse.h"
#import "GCDWebServerStreamingResponse.h"
#import "GCDWebServerStreamedResponse.h"
#ifdef __GCDWEBSERVER_LOGGING_HEADER__

View File

@@ -27,25 +27,140 @@
#import <Foundation/Foundation.h>
/**
* 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>
- (BOOL)open:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
- (BOOL)writeData:(NSData*)data error:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
- (BOOL)close:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
/**
* 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;
@property(nonatomic, readonly) NSDictionary* query; // May be nil
@property(nonatomic, readonly) NSString* contentType; // Automatically parsed from headers (nil if request has no body or set to "application/octet-stream" if a body is present without a "Content-Type" header)
@property(nonatomic, readonly) NSUInteger contentLength; // Automatically parsed from headers (NSNotFound if request has no "Content-Length" header)
@property(nonatomic, readonly) NSDate* ifModifiedSince; // Automatically parsed from headers (nil if request has no "If-Modified-Since" header or it is malformatted)
@property(nonatomic, readonly) NSString* ifNoneMatch; // Automatically parsed from headers (nil if request has no "If-None-Match" header)
@property(nonatomic, readonly) NSRange byteRange; // Automatically parsed from headers ([NSNotFound, 0] if request has no "Range" header, [offset, length] for byte range from beginning or [NSNotFound, -length] from end)
@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding; // Automatically parsed from headers
/**
* Returns the parsed and unescaped query component of the URL for the request.
*
* @warning This property will be nil if there is no query in the URL.
*/
@property(nonatomic, readonly) NSDictionary* query;
/**
* Returns the content type for the body of the request parsed from the
* "Content-Type" header.
*
* This property will be nil if the request has no body or set to
* "application/octet-stream" if a body is present but there was no
* "Content-Type" header.
*/
@property(nonatomic, readonly) NSString* contentType;
/**
* Returns the content length for the body of the request parsed from the
* "Content-Length" header.
*
* This property will be set to "NSNotFound" if the request has no body or
* if there is a body but no "Content-Length" header, typically because
* chunked transfer encoding is used.
*/
@property(nonatomic, readonly) NSUInteger contentLength;
/**
* Returns the parsed "If-Modified-Since" header or nil if absent or malformed.
*/
@property(nonatomic, readonly) NSDate* ifModifiedSince;
/**
* Returns the parsed "If-None-Match" header or nil if absent or malformed.
*/
@property(nonatomic, readonly) NSString* ifNoneMatch;
/**
* Returns the parsed "Range" header or (NSNotFound, 0) if absent or malformed.
* The range will be set to (offset, length) if expressed from the beginning
* of the entity body, or (NSNotFound, -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;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
- (BOOL)hasBody; // Convenience method that checks if "contentType" is not nil
- (BOOL)hasByteRange; // Convenience method that checks "byteRange"
/**
* Convenience method that checks if the contentType property is defined.
*/
- (BOOL)hasBody;
/**
* Convenience method that checks if the byteRange property is defined.
*/
- (BOOL)hasByteRange;
@end

View File

@@ -27,29 +27,163 @@
#import <Foundation/Foundation.h>
/**
* 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>
- (BOOL)open:(NSError**)error; // Return NO on error ("error" is guaranteed to be non-NULL)
- (NSData*)readData:(NSError**)error; // Must return nil on error or empty NSData if at end ("error" is guaranteed to be non-NULL)
/**
* This method is called before any body data is sent.
*
* It should return YES on success or NO on failure and set the "error" argument
* which is guaranteed to be non-NULL.
*/
- (BOOL)open:(NSError**)error;
/**
* This method is called whenever body data is sent.
*
* It should return a non-empty NSData if there is body data available,
* or an empty NSData there is no more body data, or nil on error and set
* the "error" argument which is guaranteed to be non-NULL.
*/
- (NSData*)readData:(NSError**)error;
/**
* This method is called after all body data has been sent.
*/
- (void)close;
@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>
@property(nonatomic, copy) NSString* contentType; // Default is nil i.e. no body (must be set if a body is present)
@property(nonatomic) NSUInteger contentLength; // Default is NSNotFound i.e. undefined (if a body is present but length is undefined, chunked transfer encoding will be enabled)
@property(nonatomic) NSInteger statusCode; // Default is 200
@property(nonatomic) NSUInteger cacheControlMaxAge; // Default is 0 seconds i.e. "Cache-Control: no-cache"
@property(nonatomic, retain) NSDate* lastModifiedDate; // Default is nil i.e. no "Last-Modified" header
@property(nonatomic, copy) NSString* eTag; // Default is nil i.e. no "ETag" header
@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled; // Default is disabled
/**
* Sets the content type for the body of the response.
*
* The default value is nil i.e. the response has no body.
*
* @warning This property must be set if a body is present.
*/
@property(nonatomic, copy) NSString* contentType;
/**
* Sets the content length for the body of the response. If a body is present
* but this property is set to "NSNotFound", 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 "NSNotFound" i.e. the response has no body or its length
* is undefined.
*/
@property(nonatomic) NSUInteger contentLength;
/**
* Sets the HTTP status code for the response.
*
* The default value is 200 i.e. "OK".
*/
@property(nonatomic) NSInteger statusCode;
/**
* Sets the caching hint for the response using the "Cache-Control" header.
* This value is expressed in seconds.
*
* The default value is 0 i.e. "no-cache".
*/
@property(nonatomic) NSUInteger cacheControlMaxAge;
/**
* Sets the last modified date for the response using the "Last-Modified" header.
*
* The default value is nil.
*/
@property(nonatomic, retain) NSDate* lastModifiedDate;
/**
* Sets the ETag for the response using the "ETag" header.
*
* The default value is nil.
*/
@property(nonatomic, copy) NSString* eTag;
/**
* Enables gzip encoding for the response body.
*
* The default value is NO.
*
* @warning Enabling gzip encoding will remove any "Content-Length" header
* since the length of the body is not known anymore. The client will still
* be able to determine the body length when connection is closed per
* HTTP/1.1 specifications.
*/
@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled;
/**
* Creates an empty response.
*/
+ (instancetype)response;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)init;
- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header; // Pass nil value to remove header
- (BOOL)hasBody; // Convenience method that checks if "contentType" is not nil
/**
* 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:(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

View File

@@ -81,7 +81,7 @@
- (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader {
if ((self = [super initWithResponse:response reader:reader])) {
response.contentLength = NSNotFound; // Make sure "Content-Length" header is not set since we don't know it (client will determine body length when connection is closed)
response.contentLength = NSNotFound; // Make sure "Content-Length" header is not set since we don't know it
[response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"];
}
return self;

View File

@@ -27,11 +27,34 @@
#import "GCDWebServerRequest.h"
/**
* 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)
@property(nonatomic, readonly) NSString* text; // Text encoding is extracted from Content-Type or defaults to UTF-8 - Returns nil on error
@property(nonatomic, readonly) id jsonObject; // Returns nil on error
/**
* Returns the data for the request body interpreted as text. If the content
* type of the body is not a text one, or if an error occurs, nil is returned.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly) NSString* text;
/**
* Returns the data for the request body interpreted as a JSON object. If the
* content type of the body is not JSON, or if an error occurs, nil is returned.
*/
@property(nonatomic, readonly) id jsonObject;
@end

View File

@@ -27,6 +27,19 @@
#import "GCDWebServerRequest.h"
/**
* 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

View File

@@ -27,23 +27,91 @@
#import "GCDWebServerRequest.h"
/**
* The GCDWebServerMultiPart class is an abstract class that wraps the content
* of a part.
*/
@interface GCDWebServerMultiPart : NSObject
@property(nonatomic, readonly) NSString* contentType; // Defaults to "text/plain" per specification if undefined
/**
* 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;
@property(nonatomic, readonly) NSString* string; // May be nil (only valid for text mime types)
/**
* Returns the data for the part interpreted as text. If the content
* type of the part is not a text one, or if an error occurs, nil is returned.
*
* The text encoding used to interpret the data is extracted from the
* "Content-Type" header or defaults to UTF-8.
*/
@property(nonatomic, readonly) NSString* string;
@end
/**
* The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps
* the content of a part as a file on disk.
*/
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
@property(nonatomic, readonly) NSString* fileName; // May be nil
/**
* 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) NSDictionary* arguments;
/**
* Returns the files parts from the multipart encoded form as
* name / GCDWebServerMultiPartFile pairs.
*/
@property(nonatomic, readonly) NSDictionary* files;
/**
* Returns the MIME type for multipart encoded forms
* i.e. "multipart/form-data".
*/
+ (NSString*)mimeType;
@end

View File

@@ -27,7 +27,25 @@
#import "GCDWebServerDataRequest.h"
/**
* The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest
* parses the body of the HTTP request as a URL encoded form using
* GCDWebServerParseURLEncodedForm().
*/
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
@property(nonatomic, readonly) NSDictionary* arguments; // Text encoding is extracted from Content-Type or defaults to UTF-8
/**
* Returns the unescaped 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

View File

@@ -27,20 +27,82 @@
#import "GCDWebServerResponse.h"
/**
* The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body
* of the HTTP response from memory.
*/
@interface GCDWebServerDataResponse : GCDWebServerResponse
/**
* 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.
*/
+ (instancetype)responseWithText:(NSString*)text;
/**
* Creates a data response from HTML encoded using UTF-8.
*/
+ (instancetype)responseWithHTML:(NSString*)html;
/**
* Creates a data response from an HTML template encoded using UTF-8.
* See -initWithHTMLTemplate:variables: for details.
*/
+ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
/**
* Creates a data response from a serialized JSON object and the default
* "application/json" content type.
*/
+ (instancetype)responseWithJSONObject:(id)object;
/**
* Creates a data response from a serialized JSON object and a custom
* content type.
*/
+ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type;
- (instancetype)initWithText:(NSString*)text; // Encodes using UTF-8
- (instancetype)initWithHTML:(NSString*)html; // Encodes using UTF-8
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8)
/**
* Initializes a data response from text encoded using UTF-8.
*/
- (instancetype)initWithText:(NSString*)text;
/**
* Initializes a data response from HTML encoded using UTF-8.
*/
- (instancetype)initWithHTML:(NSString*)html;
/**
* Initializes a data response from an HTML template encoded using UTF-8.
*
* All occurences of "%variable%" within the HTML template are replaced with
* their corresponding values.
*/
- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
/**
* Initializes a data response from a serialized JSON object and the default
* "application/json" content type.
*/
- (instancetype)initWithJSONObject:(id)object;
/**
* Initializes a data response from a serialized JSON object and a custom
* content type.
*/
- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type;
@end

View File

@@ -28,14 +28,54 @@
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerHTTPStatusCodes.h"
// Returns responses with an HTML body containing the error message
/**
* 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:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
/**
* Creates a server error response with the corresponding HTTP status code
* and an underlying NSError.
*/
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
/**
* 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:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
/**
* Initializes a server error response with the corresponding HTTP status code
* and an underlying NSError.
*/
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3,4);
@end

View File

@@ -27,13 +27,70 @@
#import "GCDWebServerResponse.h"
/**
* 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
/**
* Creates a response with the contents of a file.
*/
+ (instancetype)responseWithFile:(NSString*)path;
/**
* Creates a response like +responseWithFile: and sets the "Content-Disposition"
* HTTP header for a download if the "attachment" argument is YES.
*/
+ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
/**
* Creates a response like +responseWithFile: but restricts the file contents
* to a specific byte range.
*
* See -initWithFile:byteRange: for details.
*/
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range;
/**
* Creates a response like +responseWithFile:byteRange: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment"
* argument is YES.
*/
+ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
/**
* Initializes a response with the contents of a file.
*/
- (instancetype)initWithFile:(NSString*)path;
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; // If in attachment mode, "Content-Disposition" header will be set accordingly
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; // Pass [NSNotFound, 0] to disable byte range entirely, [offset, length] to enable byte range from beginning of file or [NSNotFound, -length] from end of file
/**
* Initializes a response like +responseWithFile: and sets the
* "Content-Disposition" HTTP header for a download if the "attachment"
* argument is YES.
*/
- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
/**
* Initializes a response like -initWithFile: but restricts the file contents
* to a specific byte range. This range should be set to (NSNotFound, 0) for
* the full file, (offset, length) if expressed from the beginning of the file,
* or (NSNotFound, -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.
*/
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
@end

View File

@@ -25,11 +25,29 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "GCDWebServerStreamingResponse.h"
#import "GCDWebServerStreamedResponse.h"
typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
/**
* The GCDWebServerStreamingBlock is called to stream the data for the HTTP body.
* The block must return empty NSData when done or nil on error and set the
* "error" argument which is guaranteed to be non-NULL.
*/
typedef NSData* (^GCDWebServerStreamingBlock)(NSError** error);
/**
* The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams
* the body of the HTTP response using a GCD block.
*/
@interface GCDWebServerStreamedResponse : GCDWebServerResponse
/**
* Creates a response with streamed data and a given content type.
*/
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block;
@interface GCDWebServerStreamingResponse : GCDWebServerResponse
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; // Block must return empty NSData when done or nil on error and set the "error" argument accordingly
@end

View File

@@ -27,19 +27,19 @@
#import "GCDWebServerPrivate.h"
@interface GCDWebServerStreamingResponse () {
@interface GCDWebServerStreamedResponse () {
@private
GCDWebServerStreamBlock _block;
GCDWebServerStreamingBlock _block;
}
@end
@implementation GCDWebServerStreamingResponse
@implementation GCDWebServerStreamedResponse
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block {
return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type streamBlock:block]);
}
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block {
if ((self = [super init])) {
_block = [block copy];

View File

@@ -29,33 +29,169 @@
@class GCDWebUploader;
// These methods are always called on main thread
/**
* Delegate methods for GCDWebUploader.
*
* @warning These methods are always called on the main thread in a serialized way.
*/
@protocol GCDWebUploaderDelegate <GCDWebServerDelegate>
@optional
/**
* This method is called whenever a file has been downloaded.
*/
- (void)webUploader:(GCDWebUploader*)uploader didDownloadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file has been uploaded.
*/
- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path;
/**
* This method is called whenever a file or directory has been moved.
*/
- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called whenever a file or directory has been deleted.
*/
- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path;
/**
* This method is called whenever a directory has been created.
*/
- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path;
@end
/**
* The GCDWebUploader subclass of GCDWebServer implements an HTML 5 web browser
* interface for uploading or downloading files, and moving or deleting files
* or directories.
*
* See the README.md file for more information about the features of GCDWebUploader.
*
* @warning For GCDWebUploader to work, "GCDWebUploader.bundle" must be added
* to the resources of the Xcode target.
*/
@interface GCDWebUploader : GCDWebServer
/**
* Returns the upload directory as specified when the uploader was initialized.
*/
@property(nonatomic, readonly) NSString* uploadDirectory;
/**
* Sets the delegate for the uploader.
*/
@property(nonatomic, assign) id<GCDWebUploaderDelegate> delegate;
@property(nonatomic, copy) NSArray* allowedFileExtensions; // Default is nil i.e. all file extensions are allowed
@property(nonatomic) BOOL showHiddenFiles; // Default is NO
@property(nonatomic, copy) NSString* title; // Default is application name (must be HTML escaped)
@property(nonatomic, copy) NSString* header; // Default is same as title (must be HTML escaped)
@property(nonatomic, copy) NSString* prologue; // Default is mini help (must be raw HTML)
@property(nonatomic, copy) NSString* epilogue; // Default is nothing (must be raw HTML)
@property(nonatomic, copy) NSString* footer; // Default is application name and version (must be HTML escaped)
/**
* Sets which files are allowed to be operated on depending on their extension.
*
* The default value is nil i.e. all file extensions are allowed.
*/
@property(nonatomic, copy) NSArray* allowedFileExtensions;
/**
* Sets if files and directories whose name start with a period are allowed to
* be operated on.
*
* The default value is NO.
*/
@property(nonatomic) BOOL allowHiddenItems;
/**
* Sets the title for the uploader web interface.
*
* The default value is the application name.
*
* @warning Any reserved HTML characters in the string value for this property
* must have been replaced by character entities e.g. "&" becomes "&amp;".
*/
@property(nonatomic, copy) NSString* title;
/**
* Sets the header for the uploader web interface.
*
* The default value is the same as the title property.
*
* @warning Any reserved HTML characters in the string value for this property
* must have been replaced by character entities e.g. "&" becomes "&amp;".
*/
@property(nonatomic, copy) NSString* header;
/**
* Sets the prologue for the uploader web interface.
*
* The default value is a short help text.
*
* @warning The string value for this property must be raw HTML
* e.g. "<p>Some text</p>"
*/
@property(nonatomic, copy) NSString* prologue;
/**
* Sets the epilogue for the uploader web interface.
*
* The default value is nil i.e. no epilogue.
*
* @warning The string value for this property must be raw HTML
* e.g. "<p>Some text</p>"
*/
@property(nonatomic, copy) NSString* epilogue;
/**
* Sets the footer for the uploader web interface.
*
* The default value is the application name and version.
*
* @warning Any reserved HTML characters in the string value for this property
* must have been replaced by character entities e.g. "&" becomes "&amp;".
*/
@property(nonatomic, copy) NSString* footer;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithUploadDirectory:(NSString*)path;
@end
// These methods can be called from any thread
/**
* Hooks to customize the behavior of GCDWebUploader.
*
* @warning These methods can be called on any GCD thread.
*/
@interface GCDWebUploader (Subclassing)
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath; // Default implementation returns YES
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath; // Default implementation returns YES
- (BOOL)shouldDeleteItemAtPath:(NSString*)path; // Default implementation returns YES
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path; // Default implementation returns YES
/**
* This method is called to check if a file upload is allowed to complete.
* The uploaded file is available for inspection at "tempPath".
*
* The default implementation returns YES.
*/
- (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath;
/**
* This method is called to check if a file or directory is allowed to be moved.
*
* The default implementation returns YES.
*/
- (BOOL)shouldMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
/**
* This method is called to check if a file or directory is allowed to be deleted.
*
* The default implementation returns YES.
*/
- (BOOL)shouldDeleteItemAtPath:(NSString*)path;
/**
* This method is called to check if a directory is allowed to be created.
*
* The default implementation returns YES.
*/
- (BOOL)shouldCreateDirectoryAtPath:(NSString*)path;
@end

View File

@@ -46,7 +46,7 @@
@private
NSString* _uploadDirectory;
NSArray* _allowedExtensions;
BOOL _showHidden;
BOOL _allowHidden;
NSString* _title;
NSString* _header;
NSString* _prologue;
@@ -94,7 +94,7 @@
}
NSString* directoryName = [absolutePath lastPathComponent];
if (!_showHidden && [directoryName hasPrefix:@"."]) {
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
}
@@ -106,7 +106,7 @@
NSMutableArray* array = [NSMutableArray array];
for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
if (_showHidden || ![item hasPrefix:@"."]) {
if (_allowHidden || ![item hasPrefix:@"."]) {
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
NSString* type = [attributes objectForKey:NSFileType];
if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
@@ -138,7 +138,7 @@
}
NSString* fileName = [absolutePath lastPathComponent];
if (([fileName hasPrefix:@"."] && !_showHidden) || ![self _checkFileExtension:fileName]) {
if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
}
@@ -155,7 +155,7 @@
NSString* contentType = (range.location != NSNotFound ? @"application/json" : @"text/plain; charset=utf-8"); // Required when using iFrame transport (see https://github.com/blueimp/jQuery-File-Upload/wiki/Setup)
GCDWebServerMultiPartFile* file = [request.files objectForKey:@"files[]"];
if ((!_showHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
if ((!_allowHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
}
NSString* relativePath = [(GCDWebServerMultiPartArgument*)[request.arguments objectForKey:@"path"] string];
@@ -190,7 +190,7 @@
NSString* newAbsolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:newRelativePath]];
NSString* itemName = [newAbsolutePath lastPathComponent];
if ((!_showHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
}
@@ -220,7 +220,7 @@
}
NSString* itemName = [absolutePath lastPathComponent];
if (([itemName hasPrefix:@"."] && !_showHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
}
@@ -246,7 +246,7 @@
NSString* absolutePath = [self _uniquePathForPath:[_uploadDirectory stringByAppendingPathComponent:relativePath]];
NSString* directoryName = [absolutePath lastPathComponent];
if (!_showHidden && [directoryName hasPrefix:@"."]) {
if (!_allowHidden && [directoryName hasPrefix:@"."]) {
return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
}
@@ -271,7 +271,7 @@
@implementation GCDWebUploader
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, showHiddenFiles=_showHidden,
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden,
title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer;
- (instancetype)initWithUploadDirectory:(NSString*)path {

View File

@@ -33,7 +33,7 @@
#import "GCDWebServerURLEncodedFormRequest.h"
#import "GCDWebServerDataResponse.h"
#import "GCDWebServerStreamingResponse.h"
#import "GCDWebServerStreamedResponse.h"
#import "GCDWebDAVServer.h"
@@ -65,6 +65,10 @@ typedef enum {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
- (void)webServerDidConnect:(GCDWebServer*)server {
[self _logDelegateCall:_cmd];
}
@@ -263,7 +267,7 @@ int main(int argc, const char* argv[]) {
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
__block int countDown = 10;
return [GCDWebServerStreamingResponse responseWithContentType:@"text/plain" streamBlock:^NSData *(NSError** error) {
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" streamBlock:^NSData *(NSError** error) {
usleep(100 * 1000);
if (countDown) {

View File

@@ -4,9 +4,9 @@ Overview
[![Build Status](https://travis-ci.org/swisspol/GCDWebServer.svg?branch=master)](https://travis-ci.org/swisspol/GCDWebServer)
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in OS X & iOS apps. It was written from scratch with the following goals in mind:
* Easy to use and understand architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
* Well designed API for easy integration and customization
* Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for maximal performance and concurrency
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
* Well designed API with fully documented headers for easy integration and customization
* Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for best performance and concurrency
* No dependencies on third-party source code
* Available under a friendly [New BSD License](LICENSE)
@@ -76,10 +76,7 @@ int main(int argc, const char* argv[]) {
}];
// Use convenience method that runs server on port 8080 until SIGINT received (i.e. Ctrl-C in Terminal)
[webServer runWithPort:8080];
// Destroy server (unnecessary if using ARC)
[webServer release];
[webServer runWithPort:8080 bonjourName:nil];
}
return 0;
@@ -172,7 +169,6 @@ int main(int argc, const char* argv[]) {
GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[webServer runWithPort:8080];
[webServer release]; // Remove if using ARC
}
return 0;

View File

@@ -57,7 +57,7 @@
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
_webServer = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath];
_webServer.delegate = self;
_webServer.showHiddenFiles = YES;
_webServer.allowHiddenItems = YES;
[_webServer start];
return YES;