瀏覽代碼

Modernize ObjC

Pierre-Olivier Latour 8 年之前
父節點
當前提交
09851dd71b
共有 33 個文件被更改,包括 850 次插入911 次删除
  1. 5 1
      GCDWebDAVServer/GCDWebDAVServer.h
  2. 113 107
      GCDWebDAVServer/GCDWebDAVServer.m
  3. 0 1
      GCDWebServer.xcodeproj/project.pbxproj
  4. 20 16
      GCDWebServer/Core/GCDWebServer.h
  5. 6 29
      GCDWebServer/Core/GCDWebServer.m
  6. 6 2
      GCDWebServer/Core/GCDWebServerConnection.h
  7. 317 310
      GCDWebServer/Core/GCDWebServerConnection.m
  8. 9 5
      GCDWebServer/Core/GCDWebServerFunctions.h
  9. 29 18
      GCDWebServer/Core/GCDWebServerFunctions.m
  10. 14 10
      GCDWebServer/Core/GCDWebServerPrivate.h
  11. 10 6
      GCDWebServer/Core/GCDWebServerRequest.h
  12. 37 68
      GCDWebServer/Core/GCDWebServerRequest.m
  13. 10 6
      GCDWebServer/Core/GCDWebServerResponse.h
  14. 26 52
      GCDWebServer/Core/GCDWebServerResponse.m
  15. 6 2
      GCDWebServer/Requests/GCDWebServerDataRequest.h
  16. 5 9
      GCDWebServer/Requests/GCDWebServerDataRequest.m
  17. 4 0
      GCDWebServer/Requests/GCDWebServerFileRequest.h
  18. 1 8
      GCDWebServer/Requests/GCDWebServerFileRequest.m
  19. 7 3
      GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h
  20. 12 50
      GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m
  21. 4 0
      GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h
  22. 0 10
      GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m
  23. 15 10
      GCDWebServer/Responses/GCDWebServerDataResponse.h
  24. 4 11
      GCDWebServer/Responses/GCDWebServerDataResponse.m
  25. 8 4
      GCDWebServer/Responses/GCDWebServerErrorResponse.h
  26. 0 4
      GCDWebServer/Responses/GCDWebServerErrorResponse.m
  27. 15 8
      GCDWebServer/Responses/GCDWebServerFileResponse.h
  28. 2 4
      GCDWebServer/Responses/GCDWebServerFileResponse.m
  29. 6 1
      GCDWebServer/Responses/GCDWebServerStreamedResponse.h
  30. 2 4
      GCDWebServer/Responses/GCDWebServerStreamedResponse.m
  31. 5 1
      GCDWebUploader/GCDWebUploader.h
  32. 151 150
      GCDWebUploader/GCDWebUploader.m
  33. 1 1
      Mac/main.m

+ 5 - 1
GCDWebDAVServer/GCDWebDAVServer.h

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

+ 113 - 107
GCDWebDAVServer/GCDWebDAVServer.m

@@ -55,12 +55,104 @@ typedef NS_ENUM(NSInteger, DAVProperties) {
   kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength
 };
 
-@interface GCDWebDAVServer () {
-@private
-  NSString* _uploadDirectory;
-  NSArray* _allowedExtensions;
-  BOOL _allowHidden;
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GCDWebDAVServer (Methods)
+- (nullable GCDWebServerResponse*)performOPTIONS:(GCDWebServerRequest*)request;
+- (nullable GCDWebServerResponse*)performGET:(GCDWebServerRequest*)request;
+- (nullable GCDWebServerResponse*)performPUT:(GCDWebServerFileRequest*)request;
+- (nullable GCDWebServerResponse*)performDELETE:(GCDWebServerRequest*)request;
+- (nullable GCDWebServerResponse*)performMKCOL:(GCDWebServerDataRequest*)request;
+- (nullable GCDWebServerResponse*)performCOPY:(GCDWebServerRequest*)request isMove:(BOOL)isMove;
+- (nullable GCDWebServerResponse*)performPROPFIND:(GCDWebServerDataRequest*)request;
+- (nullable GCDWebServerResponse*)performLOCK:(GCDWebServerDataRequest*)request;
+- (nullable GCDWebServerResponse*)performUNLOCK:(GCDWebServerRequest*)request;
+@end
+
+NS_ASSUME_NONNULL_END
+
+@implementation GCDWebDAVServer
+
+@dynamic delegate;
+
+- (instancetype)initWithUploadDirectory:(NSString*)path {
+  if ((self = [super init])) {
+    _uploadDirectory = [[path stringByStandardizingPath] copy];
+    GCDWebDAVServer* __unsafe_unretained server = self;
+
+    // 9.1 PROPFIND method
+    [self addDefaultHandlerForMethod:@"PROPFIND"
+                        requestClass:[GCDWebServerDataRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performPROPFIND:(GCDWebServerDataRequest*)request];
+                        }];
+
+    // 9.3 MKCOL Method
+    [self addDefaultHandlerForMethod:@"MKCOL"
+                        requestClass:[GCDWebServerDataRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performMKCOL:(GCDWebServerDataRequest*)request];
+                        }];
+
+    // 9.4 GET & HEAD methods
+    [self addDefaultHandlerForMethod:@"GET"
+                        requestClass:[GCDWebServerRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performGET:request];
+                        }];
+
+    // 9.6 DELETE method
+    [self addDefaultHandlerForMethod:@"DELETE"
+                        requestClass:[GCDWebServerRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performDELETE:request];
+                        }];
+
+    // 9.7 PUT method
+    [self addDefaultHandlerForMethod:@"PUT"
+                        requestClass:[GCDWebServerFileRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performPUT:(GCDWebServerFileRequest*)request];
+                        }];
+
+    // 9.8 COPY method
+    [self addDefaultHandlerForMethod:@"COPY"
+                        requestClass:[GCDWebServerRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performCOPY:request isMove:NO];
+                        }];
+
+    // 9.9 MOVE method
+    [self addDefaultHandlerForMethod:@"MOVE"
+                        requestClass:[GCDWebServerRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performCOPY:request isMove:YES];
+                        }];
+
+    // 9.10 LOCK method
+    [self addDefaultHandlerForMethod:@"LOCK"
+                        requestClass:[GCDWebServerDataRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performLOCK:(GCDWebServerDataRequest*)request];
+                        }];
+
+    // 9.11 UNLOCK method
+    [self addDefaultHandlerForMethod:@"UNLOCK"
+                        requestClass:[GCDWebServerRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performUNLOCK:request];
+                        }];
+
+    // 10.1 OPTIONS method / DAV Header
+    [self addDefaultHandlerForMethod:@"OPTIONS"
+                        requestClass:[GCDWebServerRequest class]
+                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                          return [server performOPTIONS:request];
+                        }];
+  }
+  return self;
 }
+
 @end
 
 @implementation GCDWebDAVServer (Methods)
@@ -71,7 +163,7 @@ typedef NS_ENUM(NSInteger, DAVProperties) {
 }
 
 - (BOOL)_checkFileExtension:(NSString*)fileName {
-  if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
+  if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
     return NO;
   }
   return YES;
@@ -101,7 +193,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
   }
 
   NSString* itemName = [absolutePath lastPathComponent];
-  if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading item name \"%@\" is not allowed", itemName];
   }
 
@@ -144,7 +236,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
   }
 
   NSString* fileName = [absolutePath lastPathComponent];
-  if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
+  if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploading file name \"%@\" is not allowed", fileName];
   }
 
@@ -180,7 +272,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
   }
 
   NSString* itemName = [absolutePath lastPathComponent];
-  if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
   }
 
@@ -217,7 +309,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
   }
 
   NSString* directoryName = [absolutePath lastPathComponent];
-  if (!_allowHidden && [directoryName hasPrefix:@"."]) {
+  if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
   }
 
@@ -262,7 +354,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
   }
 
   NSString* dstRelativePath = [request.headers objectForKey:@"Destination"];
-  NSRange range = [dstRelativePath rangeOfString:[request.headers objectForKey:@"Host"]];
+  NSRange range = [dstRelativePath rangeOfString:(NSString*)[request.headers objectForKey:@"Host"]];
   if ((dstRelativePath == nil) || (range.location == NSNotFound)) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_BadRequest message:@"Malformed 'Destination' header: %@", dstRelativePath];
   }
@@ -281,7 +373,7 @@ static inline BOOL _IsMacFinder(GCDWebServerRequest* request) {
   }
 
   NSString* itemName = [dstAbsolutePath lastPathComponent];
-  if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"%@ to item name \"%@\" is not allowed", isMove ? @"Moving" : @"Copying", itemName];
   }
 
@@ -365,11 +457,11 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
       }
 
       if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) {
-        [xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", GCDWebServerFormatISO8601([attributes fileCreationDate])];
+        [xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", GCDWebServerFormatISO8601((NSDate*)[attributes fileCreationDate])];
       }
 
       if ((properties & kDAVProperty_LastModified) && isFile && [attributes objectForKey:NSFileModificationDate]) {  // Last modification date is not useful for directories as it changes implicitely and 'Last-Modified' header is not provided for directories anyway
-        [xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", GCDWebServerFormatRFC822([attributes fileModificationDate])];
+        [xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", GCDWebServerFormatRFC822((NSDate*)[attributes fileModificationDate])];
       }
 
       if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) {
@@ -447,7 +539,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
   }
 
   NSString* itemName = [absolutePath lastPathComponent];
-  if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Retrieving properties for item name \"%@\" is not allowed", itemName];
   }
 
@@ -471,14 +563,14 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
       relativePath = [relativePath stringByAppendingString:@"/"];
     }
     for (NSString* item in items) {
-      if (_allowHidden || ![item hasPrefix:@"."]) {
+      if (_allowHiddenItems || ![item hasPrefix:@"."]) {
         [self _addPropertyResponseForItem:[absolutePath stringByAppendingPathComponent:item] resource:[relativePath stringByAppendingString:item] properties:properties xmlString:xmlString];
       }
     }
   }
   [xmlString appendString:@"</D:multistatus>"];
 
-  GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[xmlString dataUsingEncoding:NSUTF8StringEncoding]
+  GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
                                                                       contentType:@"application/xml; charset=\"utf-8\""];
   response.statusCode = kGCDWebServerHTTPStatusCode_MultiStatus;
   return response;
@@ -539,7 +631,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
   }
 
   NSString* itemName = [absolutePath lastPathComponent];
-  if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Locking item name \"%@\" is not allowed", itemName];
   }
 
@@ -570,13 +662,13 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
     [xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeoutHeader];
   }
   [xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token];
-  NSString* lockroot = [@"http://" stringByAppendingString:[[request.headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:relativePath]]];
+  NSString* lockroot = [@"http://" stringByAppendingString:[(NSString*)[request.headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:relativePath]]];
   [xmlString appendFormat:@"<D:lockroot><D:href>%@</D:href></D:lockroot>\n", lockroot];
   [xmlString appendString:@"</D:activelock>\n</D:lockdiscovery>\n"];
   [xmlString appendString:@"</D:prop>"];
 
   [self logVerbose:@"WebDAV pretending to lock \"%@\"", relativePath];
-  GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[xmlString dataUsingEncoding:NSUTF8StringEncoding]
+  GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:(NSData*)[xmlString dataUsingEncoding:NSUTF8StringEncoding]
                                                                       contentType:@"application/xml; charset=\"utf-8\""];
   return response;
 }
@@ -599,7 +691,7 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
   }
 
   NSString* itemName = [absolutePath lastPathComponent];
-  if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Unlocking item name \"%@\" is not allowed", itemName];
   }
 
@@ -609,92 +701,6 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
 
 @end
 
-@implementation GCDWebDAVServer
-
-@synthesize uploadDirectory = _uploadDirectory, allowedFileExtensions = _allowedExtensions, allowHiddenItems = _allowHidden;
-
-@dynamic delegate;
-
-- (instancetype)initWithUploadDirectory:(NSString*)path {
-  if ((self = [super init])) {
-    _uploadDirectory = [[path stringByStandardizingPath] copy];
-    GCDWebDAVServer* __unsafe_unretained server = self;
-
-    // 9.1 PROPFIND method
-    [self addDefaultHandlerForMethod:@"PROPFIND"
-                        requestClass:[GCDWebServerDataRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performPROPFIND:(GCDWebServerDataRequest*)request];
-                        }];
-
-    // 9.3 MKCOL Method
-    [self addDefaultHandlerForMethod:@"MKCOL"
-                        requestClass:[GCDWebServerDataRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performMKCOL:(GCDWebServerDataRequest*)request];
-                        }];
-
-    // 9.4 GET & HEAD methods
-    [self addDefaultHandlerForMethod:@"GET"
-                        requestClass:[GCDWebServerRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performGET:request];
-                        }];
-
-    // 9.6 DELETE method
-    [self addDefaultHandlerForMethod:@"DELETE"
-                        requestClass:[GCDWebServerRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performDELETE:request];
-                        }];
-
-    // 9.7 PUT method
-    [self addDefaultHandlerForMethod:@"PUT"
-                        requestClass:[GCDWebServerFileRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performPUT:(GCDWebServerFileRequest*)request];
-                        }];
-
-    // 9.8 COPY method
-    [self addDefaultHandlerForMethod:@"COPY"
-                        requestClass:[GCDWebServerRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performCOPY:request isMove:NO];
-                        }];
-
-    // 9.9 MOVE method
-    [self addDefaultHandlerForMethod:@"MOVE"
-                        requestClass:[GCDWebServerRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performCOPY:request isMove:YES];
-                        }];
-
-    // 9.10 LOCK method
-    [self addDefaultHandlerForMethod:@"LOCK"
-                        requestClass:[GCDWebServerDataRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performLOCK:(GCDWebServerDataRequest*)request];
-                        }];
-
-    // 9.11 UNLOCK method
-    [self addDefaultHandlerForMethod:@"UNLOCK"
-                        requestClass:[GCDWebServerRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performUNLOCK:request];
-                        }];
-
-    // 10.1 OPTIONS method / DAV Header
-    [self addDefaultHandlerForMethod:@"OPTIONS"
-                        requestClass:[GCDWebServerRequest class]
-                        processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                          return [server performOPTIONS:request];
-                        }];
-  }
-  return self;
-}
-
-@end
-
 @implementation GCDWebDAVServer (Subclassing)
 
 - (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {

+ 0 - 1
GCDWebServer.xcodeproj/project.pbxproj

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

+ 20 - 16
GCDWebServer/Core/GCDWebServer.h

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

+ 6 - 29
GCDWebServer/Core/GCDWebServer.m

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

+ 6 - 2
GCDWebServer/Core/GCDWebServerConnection.h

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

+ 317 - 310
GCDWebServer/Core/GCDWebServerConnection.m

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

+ 9 - 5
GCDWebServer/Core/GCDWebServerFunctions.h

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

+ 29 - 18
GCDWebServer/Core/GCDWebServerFunctions.m

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

+ 14 - 10
GCDWebServer/Core/GCDWebServerPrivate.h

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

+ 10 - 6
GCDWebServer/Core/GCDWebServerRequest.h

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

+ 37 - 68
GCDWebServer/Core/GCDWebServerRequest.m

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

+ 10 - 6
GCDWebServer/Core/GCDWebServerResponse.h

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

+ 26 - 52
GCDWebServer/Core/GCDWebServerResponse.m

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

+ 6 - 2
GCDWebServer/Requests/GCDWebServerDataRequest.h

@@ -27,6 +27,8 @@
 
 #import "GCDWebServerRequest.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 /**
  *  The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body
  *  of the HTTP request in memory.
@@ -49,12 +51,14 @@
  *  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;
+@property(nonatomic, readonly, nullable) NSString* text;
 
 /**
  *  Returns the data for the request body interpreted as a JSON object. If the
  *  content type of the body is not JSON, or if an error occurs, nil is returned.
  */
-@property(nonatomic, readonly) id jsonObject;
+@property(nonatomic, readonly, nullable) id jsonObject;
 
 @end
+
+NS_ASSUME_NONNULL_END

+ 5 - 9
GCDWebServer/Requests/GCDWebServerDataRequest.m

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

+ 4 - 0
GCDWebServer/Requests/GCDWebServerFileRequest.h

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

+ 1 - 8
GCDWebServer/Requests/GCDWebServerFileRequest.m

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

+ 7 - 3
GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h

@@ -27,6 +27,8 @@
 
 #import "GCDWebServerRequest.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 /**
  *  The GCDWebServerMultiPart class is an abstract class that wraps the content
  *  of a part.
@@ -69,7 +71,7 @@
  *  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;
+@property(nonatomic, readonly, nullable) NSString* string;
 
 @end
 
@@ -122,11 +124,13 @@
 /**
  *  Returns the first argument for a given control name or nil if not found.
  */
-- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
+- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name;
 
 /**
  *  Returns the first file for a given control name or nil if not found.
  */
-- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
+- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name;
 
 @end
+
+NS_ASSUME_NONNULL_END

+ 12 - 50
GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m

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

+ 4 - 0
GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h

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

+ 0 - 10
GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m

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

+ 15 - 10
GCDWebServer/Responses/GCDWebServerDataResponse.h

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

+ 4 - 11
GCDWebServer/Responses/GCDWebServerDataResponse.m

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

+ 8 - 4
GCDWebServer/Responses/GCDWebServerErrorResponse.h

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

+ 0 - 4
GCDWebServer/Responses/GCDWebServerErrorResponse.m

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

+ 15 - 8
GCDWebServer/Responses/GCDWebServerFileResponse.h

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

+ 2 - 4
GCDWebServer/Responses/GCDWebServerFileResponse.m

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

+ 6 - 1
GCDWebServer/Responses/GCDWebServerStreamedResponse.h

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

+ 2 - 4
GCDWebServer/Responses/GCDWebServerStreamedResponse.m

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

+ 5 - 1
GCDWebUploader/GCDWebUploader.h

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

+ 151 - 150
GCDWebUploader/GCDWebUploader.m

@@ -46,17 +46,149 @@
 #import "GCDWebServerErrorResponse.h"
 #import "GCDWebServerFileResponse.h"
 
-@interface GCDWebUploader () {
-@private
-  NSString* _uploadDirectory;
-  NSArray* _allowedExtensions;
-  BOOL _allowHidden;
-  NSString* _title;
-  NSString* _header;
-  NSString* _prologue;
-  NSString* _epilogue;
-  NSString* _footer;
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GCDWebUploader (Methods)
+- (nullable GCDWebServerResponse*)listDirectory:(GCDWebServerRequest*)request;
+- (nullable GCDWebServerResponse*)downloadFile:(GCDWebServerRequest*)request;
+- (nullable GCDWebServerResponse*)uploadFile:(GCDWebServerMultiPartFormRequest*)request;
+- (nullable GCDWebServerResponse*)moveItem:(GCDWebServerURLEncodedFormRequest*)request;
+- (nullable GCDWebServerResponse*)deleteItem:(GCDWebServerURLEncodedFormRequest*)request;
+- (nullable GCDWebServerResponse*)createDirectory:(GCDWebServerURLEncodedFormRequest*)request;
+@end
+
+NS_ASSUME_NONNULL_END
+
+@implementation GCDWebUploader
+
+@dynamic delegate;
+
+- (instancetype)initWithUploadDirectory:(NSString*)path {
+  if ((self = [super init])) {
+    NSString* bundlePath = [[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"];
+    if (bundlePath == nil) {
+      return nil;
+    }
+    NSBundle* siteBundle = [NSBundle bundleWithPath:bundlePath];
+    if (siteBundle == nil) {
+      return nil;
+    }
+    _uploadDirectory = [[path stringByStandardizingPath] copy];
+    GCDWebUploader* __unsafe_unretained server = self;
+
+    // Resource files
+    [self addGETHandlerForBasePath:@"/" directoryPath:(NSString*)[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
+
+    // Web page
+    [self addHandlerForMethod:@"GET"
+                         path:@"/"
+                 requestClass:[GCDWebServerRequest class]
+                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+
+#if TARGET_OS_IPHONE
+                   NSString* device = [[UIDevice currentDevice] name];
+#else
+          NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
+#endif
+                   NSString* title = server.title;
+                   if (title == nil) {
+                     title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+                     if (title == nil) {
+                       title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+                     }
+#if !TARGET_OS_IPHONE
+                     if (title == nil) {
+                       title = [[NSProcessInfo processInfo] processName];
+                     }
+#endif
+                   }
+                   NSString* header = server.header;
+                   if (header == nil) {
+                     header = title;
+                   }
+                   NSString* prologue = server.prologue;
+                   if (prologue == nil) {
+                     prologue = [siteBundle localizedStringForKey:@"PROLOGUE" value:@"" table:nil];
+                   }
+                   NSString* epilogue = server.epilogue;
+                   if (epilogue == nil) {
+                     epilogue = [siteBundle localizedStringForKey:@"EPILOGUE" value:@"" table:nil];
+                   }
+                   NSString* footer = server.footer;
+                   if (footer == nil) {
+                     NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+                     NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+#if !TARGET_OS_IPHONE
+                     if (!name && !version) {
+                       name = @"OS X";
+                       version = [[NSProcessInfo processInfo] operatingSystemVersionString];
+                     }
+#endif
+                     footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
+                   }
+                   return [GCDWebServerDataResponse responseWithHTMLTemplate:(NSString*)[siteBundle pathForResource:@"index" ofType:@"html"]
+                                                                   variables:@{
+                                                                     @"device" : device,
+                                                                     @"title" : title,
+                                                                     @"header" : header,
+                                                                     @"prologue" : prologue,
+                                                                     @"epilogue" : epilogue,
+                                                                     @"footer" : footer
+                                                                   }];
+
+                 }];
+
+    // File listing
+    [self addHandlerForMethod:@"GET"
+                         path:@"/list"
+                 requestClass:[GCDWebServerRequest class]
+                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                   return [server listDirectory:request];
+                 }];
+
+    // File download
+    [self addHandlerForMethod:@"GET"
+                         path:@"/download"
+                 requestClass:[GCDWebServerRequest class]
+                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                   return [server downloadFile:request];
+                 }];
+
+    // File upload
+    [self addHandlerForMethod:@"POST"
+                         path:@"/upload"
+                 requestClass:[GCDWebServerMultiPartFormRequest class]
+                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                   return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
+                 }];
+
+    // File and folder moving
+    [self addHandlerForMethod:@"POST"
+                         path:@"/move"
+                 requestClass:[GCDWebServerURLEncodedFormRequest class]
+                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                   return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
+                 }];
+
+    // File and folder deletion
+    [self addHandlerForMethod:@"POST"
+                         path:@"/delete"
+                 requestClass:[GCDWebServerURLEncodedFormRequest class]
+                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                   return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
+                 }];
+
+    // Directory creation
+    [self addHandlerForMethod:@"POST"
+                         path:@"/create"
+                 requestClass:[GCDWebServerURLEncodedFormRequest class]
+                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
+                   return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
+                 }];
+  }
+  return self;
 }
+
 @end
 
 @implementation GCDWebUploader (Methods)
@@ -67,7 +199,7 @@
 }
 
 - (BOOL)_checkFileExtension:(NSString*)fileName {
-  if (_allowedExtensions && ![_allowedExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
+  if (_allowedFileExtensions && ![_allowedFileExtensions containsObject:[[fileName pathExtension] lowercaseString]]) {
     return NO;
   }
   return YES;
@@ -82,7 +214,7 @@
     int retries = 0;
     do {
       if (extension.length) {
-        path = [directory stringByAppendingPathComponent:[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
+        path = [directory stringByAppendingPathComponent:(NSString*)[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
       } else {
         path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
       }
@@ -103,7 +235,7 @@
   }
 
   NSString* directoryName = [absolutePath lastPathComponent];
-  if (!_allowHidden && [directoryName hasPrefix:@"."]) {
+  if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Listing directory name \"%@\" is not allowed", directoryName];
   }
 
@@ -115,7 +247,7 @@
 
   NSMutableArray* array = [NSMutableArray array];
   for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
-    if (_allowHidden || ![item hasPrefix:@"."]) {
+    if (_allowHiddenItems || ![item hasPrefix:@"."]) {
       NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
       NSString* type = [attributes objectForKey:NSFileType];
       if ([type isEqualToString:NSFileTypeRegular] && [self _checkFileExtension:item]) {
@@ -147,7 +279,7 @@
   }
 
   NSString* fileName = [absolutePath lastPathComponent];
-  if (([fileName hasPrefix:@"."] && !_allowHidden) || ![self _checkFileExtension:fileName]) {
+  if (([fileName hasPrefix:@"."] && !_allowHiddenItems) || ![self _checkFileExtension:fileName]) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Downlading file name \"%@\" is not allowed", fileName];
   }
 
@@ -164,7 +296,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 firstFileForControlName:@"files[]"];
-  if ((!_allowHidden && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
+  if ((!_allowHiddenItems && [file.fileName hasPrefix:@"."]) || ![self _checkFileExtension:file.fileName]) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Uploaded file name \"%@\" is not allowed", file.fileName];
   }
   NSString* relativePath = [[request firstArgumentForControlName:@"path"] string];
@@ -205,7 +337,7 @@
   }
 
   NSString* itemName = [newAbsolutePath lastPathComponent];
-  if ((!_allowHidden && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if ((!_allowHiddenItems && [itemName hasPrefix:@"."]) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Moving to item name \"%@\" is not allowed", itemName];
   }
 
@@ -235,7 +367,7 @@
   }
 
   NSString* itemName = [absolutePath lastPathComponent];
-  if (([itemName hasPrefix:@"."] && !_allowHidden) || (!isDirectory && ![self _checkFileExtension:itemName])) {
+  if (([itemName hasPrefix:@"."] && !_allowHiddenItems) || (!isDirectory && ![self _checkFileExtension:itemName])) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Deleting item name \"%@\" is not allowed", itemName];
   }
 
@@ -264,7 +396,7 @@
   }
 
   NSString* directoryName = [absolutePath lastPathComponent];
-  if (!_allowHidden && [directoryName hasPrefix:@"."]) {
+  if (!_allowHiddenItems && [directoryName hasPrefix:@"."]) {
     return [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_Forbidden message:@"Creating directory name \"%@\" is not allowed", directoryName];
   }
 
@@ -287,137 +419,6 @@
 
 @end
 
-@implementation GCDWebUploader
-
-@synthesize uploadDirectory = _uploadDirectory, allowedFileExtensions = _allowedExtensions, allowHiddenItems = _allowHidden,
-            title = _title, header = _header, prologue = _prologue, epilogue = _epilogue, footer = _footer;
-
-@dynamic delegate;
-
-- (instancetype)initWithUploadDirectory:(NSString*)path {
-  if ((self = [super init])) {
-    NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
-    if (siteBundle == nil) {
-      return nil;
-    }
-    _uploadDirectory = [[path stringByStandardizingPath] copy];
-    GCDWebUploader* __unsafe_unretained server = self;
-
-    // Resource files
-    [self addGETHandlerForBasePath:@"/" directoryPath:[siteBundle resourcePath] indexFilename:nil cacheAge:3600 allowRangeRequests:NO];
-
-    // Web page
-    [self addHandlerForMethod:@"GET"
-                         path:@"/"
-                 requestClass:[GCDWebServerRequest class]
-                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-
-#if TARGET_OS_IPHONE
-                   NSString* device = [[UIDevice currentDevice] name];
-#else
-          NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
-#endif
-                   NSString* title = server.title;
-                   if (title == nil) {
-                     title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
-                     if (title == nil) {
-                       title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
-                     }
-#if !TARGET_OS_IPHONE
-                     if (title == nil) {
-                       title = [[NSProcessInfo processInfo] processName];
-                     }
-#endif
-                   }
-                   NSString* header = server.header;
-                   if (header == nil) {
-                     header = title;
-                   }
-                   NSString* prologue = server.prologue;
-                   if (prologue == nil) {
-                     prologue = [siteBundle localizedStringForKey:@"PROLOGUE" value:@"" table:nil];
-                   }
-                   NSString* epilogue = server.epilogue;
-                   if (epilogue == nil) {
-                     epilogue = [siteBundle localizedStringForKey:@"EPILOGUE" value:@"" table:nil];
-                   }
-                   NSString* footer = server.footer;
-                   if (footer == nil) {
-                     NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
-                     NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
-#if !TARGET_OS_IPHONE
-                     if (!name && !version) {
-                       name = @"OS X";
-                       version = [[NSProcessInfo processInfo] operatingSystemVersionString];
-                     }
-#endif
-                     footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER_FORMAT" value:@"" table:nil], name, version];
-                   }
-                   return [GCDWebServerDataResponse responseWithHTMLTemplate:[siteBundle pathForResource:@"index" ofType:@"html"]
-                                                                   variables:@{
-                                                                     @"device" : device,
-                                                                     @"title" : title,
-                                                                     @"header" : header,
-                                                                     @"prologue" : prologue,
-                                                                     @"epilogue" : epilogue,
-                                                                     @"footer" : footer
-                                                                   }];
-
-                 }];
-
-    // File listing
-    [self addHandlerForMethod:@"GET"
-                         path:@"/list"
-                 requestClass:[GCDWebServerRequest class]
-                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                   return [server listDirectory:request];
-                 }];
-
-    // File download
-    [self addHandlerForMethod:@"GET"
-                         path:@"/download"
-                 requestClass:[GCDWebServerRequest class]
-                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                   return [server downloadFile:request];
-                 }];
-
-    // File upload
-    [self addHandlerForMethod:@"POST"
-                         path:@"/upload"
-                 requestClass:[GCDWebServerMultiPartFormRequest class]
-                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                   return [server uploadFile:(GCDWebServerMultiPartFormRequest*)request];
-                 }];
-
-    // File and folder moving
-    [self addHandlerForMethod:@"POST"
-                         path:@"/move"
-                 requestClass:[GCDWebServerURLEncodedFormRequest class]
-                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                   return [server moveItem:(GCDWebServerURLEncodedFormRequest*)request];
-                 }];
-
-    // File and folder deletion
-    [self addHandlerForMethod:@"POST"
-                         path:@"/delete"
-                 requestClass:[GCDWebServerURLEncodedFormRequest class]
-                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                   return [server deleteItem:(GCDWebServerURLEncodedFormRequest*)request];
-                 }];
-
-    // Directory creation
-    [self addHandlerForMethod:@"POST"
-                         path:@"/create"
-                 requestClass:[GCDWebServerURLEncodedFormRequest class]
-                 processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) {
-                   return [server createDirectory:(GCDWebServerURLEncodedFormRequest*)request];
-                 }];
-  }
-  return self;
-}
-
-@end
-
 @implementation GCDWebUploader (Subclassing)
 
 - (BOOL)shouldUploadFileAtPath:(NSString*)path withTemporaryFile:(NSString*)tempPath {

+ 1 - 1
Mac/main.m

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