Pierre-Olivier Latour 11 rokov pred
rodič
commit
1f9a0d38d0

+ 27 - 0
CGDWebServer/GCDWebServer.m

@@ -88,6 +88,33 @@ void GCDLogMessage(long level, NSString* format, ...) {
 
 #endif
 
+NSString* GCDWebServerExtractHeaderParameter(NSString* header, NSString* attribute) {
+  NSString* value = nil;
+  if (header) {
+    NSScanner* scanner = [[NSScanner alloc] initWithString:header];
+    NSString* string = [NSString stringWithFormat:@"%@=", attribute];
+    if ([scanner scanUpToString:string intoString:NULL]) {
+      [scanner scanString:string intoString:NULL];
+      if ([scanner scanString:@"\"" intoString:NULL]) {
+        [scanner scanUpToString:@"\"" intoString:&value];
+      } else {
+        [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&value];
+      }
+    }
+    ARC_RELEASE(scanner);
+  }
+  return value;
+}
+
+// http://www.w3schools.com/tags/ref_charactersets.asp
+NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) {
+  NSStringEncoding encoding = kCFStringEncodingInvalidId;
+  if (charset) {
+    encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
+  }
+  return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
+}
+
 NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
   static NSDictionary* _overrides = nil;
   if (_overrides == nil) {

+ 32 - 0
CGDWebServer/GCDWebServerDataRequest.h

@@ -0,0 +1,32 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerRequest.h"
+
+@interface GCDWebServerDataRequest : GCDWebServerRequest
+@property(nonatomic, readonly) NSData* data;  // Only valid after open / write / close sequence
+@end

+ 64 - 0
CGDWebServer/GCDWebServerDataRequest.m

@@ -0,0 +1,64 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerPrivate.h"
+
+@interface GCDWebServerDataRequest () {
+@private
+  NSMutableData* _data;
+}
+@end
+
+@implementation GCDWebServerDataRequest
+
+@synthesize data=_data;
+
+- (void)dealloc {
+  DCHECK(_data != nil);
+  ARC_RELEASE(_data);
+  
+  ARC_DEALLOC(super);
+}
+
+- (BOOL)open {
+  DCHECK(_data == nil);
+  _data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
+  return _data ? YES : NO;
+}
+
+- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
+  DCHECK(_data != nil);
+  [_data appendBytes:buffer length:length];
+  return length;
+}
+
+- (BOOL)close {
+  DCHECK(_data != nil);
+  return YES;
+}
+
+@end

+ 46 - 0
CGDWebServer/GCDWebServerDataResponse.h

@@ -0,0 +1,46 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerResponse.h"
+
+@interface GCDWebServerDataResponse : GCDWebServerResponse
++ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type;
+- (id)initWithData:(NSData*)data contentType:(NSString*)type;
+@end
+
+@interface GCDWebServerDataResponse (Extensions)
++ (GCDWebServerDataResponse*)responseWithText:(NSString*)text;
++ (GCDWebServerDataResponse*)responseWithHTML:(NSString*)html;
++ (GCDWebServerDataResponse*)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
++ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object;
++ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object contentType:(NSString*)type;
+- (id)initWithText:(NSString*)text;  // Encodes using UTF-8
+- (id)initWithHTML:(NSString*)html;  // Encodes using UTF-8
+- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;  // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8)
+- (id)initWithJSONObject:(id)object;
+- (id)initWithJSONObject:(id)object contentType:(NSString*)type;
+@end

+ 143 - 0
CGDWebServer/GCDWebServerDataResponse.m

@@ -0,0 +1,143 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerPrivate.h"
+
+@interface GCDWebServerDataResponse () {
+@private
+  NSData* _data;
+  BOOL _done;
+}
+@end
+
+@implementation GCDWebServerDataResponse
+
++ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type {
+  return ARC_AUTORELEASE([[[self class] alloc] initWithData:data contentType:type]);
+}
+
+- (id)initWithData:(NSData*)data contentType:(NSString*)type {
+  if (data == nil) {
+    DNOT_REACHED();
+    ARC_RELEASE(self);
+    return nil;
+  }
+  
+  if ((self = [super init])) {
+    _data = ARC_RETAIN(data);
+    
+    self.contentType = type;
+    self.contentLength = data.length;
+  }
+  return self;
+}
+
+- (void)dealloc {
+  ARC_RELEASE(_data);
+  
+  ARC_DEALLOC(super);
+}
+
+- (NSData*)readData:(NSError**)error {
+  NSData* data;
+  if (_done) {
+    data = [NSData data];
+  } else {
+    data = _data;
+    _done = YES;
+  }
+  return data;
+}
+
+@end
+
+@implementation GCDWebServerDataResponse (Extensions)
+
++ (GCDWebServerDataResponse*)responseWithText:(NSString*)text {
+  return ARC_AUTORELEASE([[self alloc] initWithText:text]);
+}
+
++ (GCDWebServerDataResponse*)responseWithHTML:(NSString*)html {
+  return ARC_AUTORELEASE([[self alloc] initWithHTML:html]);
+}
+
++ (GCDWebServerDataResponse*)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
+  return ARC_AUTORELEASE([[self alloc] initWithHTMLTemplate:path variables:variables]);
+}
+
++ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object {
+  return ARC_AUTORELEASE([[self alloc] initWithJSONObject:object]);
+}
+
++ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object contentType:(NSString*)type {
+  return ARC_AUTORELEASE([[self alloc] initWithJSONObject:object contentType:type]);
+}
+
+- (id)initWithText:(NSString*)text {
+  NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding];
+  if (data == nil) {
+    DNOT_REACHED();
+    ARC_RELEASE(self);
+    return nil;
+  }
+  return [self initWithData:data contentType:@"text/plain; charset=utf-8"];
+}
+
+- (id)initWithHTML:(NSString*)html {
+  NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding];
+  if (data == nil) {
+    DNOT_REACHED();
+    ARC_RELEASE(self);
+    return nil;
+  }
+  return [self initWithData:data contentType:@"text/html; charset=utf-8"];
+}
+
+- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
+  NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
+  [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
+    [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
+  }];
+  id response = [self initWithHTML:html];
+  ARC_RELEASE(html);
+  return response;
+}
+
+- (id)initWithJSONObject:(id)object {
+  return [self initWithJSONObject:object contentType:@"application/json"];
+}
+
+- (id)initWithJSONObject:(id)object contentType:(NSString*)type {
+  NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
+  if (data == nil) {
+    ARC_RELEASE(self);
+    return nil;
+  }
+  return [self initWithData:data contentType:type];
+}
+
+@end

+ 32 - 0
CGDWebServer/GCDWebServerFileRequest.h

@@ -0,0 +1,32 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerRequest.h"
+
+@interface GCDWebServerFileRequest : GCDWebServerRequest
+@property(nonatomic, readonly) NSString* filePath;  // Only valid after open / write / close sequence
+@end

+ 74 - 0
CGDWebServer/GCDWebServerFileRequest.m

@@ -0,0 +1,74 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerPrivate.h"
+
+@interface GCDWebServerFileRequest () {
+@private
+  NSString* _filePath;
+  int _file;
+}
+@end
+
+@implementation GCDWebServerFileRequest
+
+@synthesize filePath=_filePath;
+
+- (id)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])) {
+    _filePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
+  }
+  return self;
+}
+
+- (void)dealloc {
+  DCHECK(_file < 0);
+  unlink([_filePath fileSystemRepresentation]);
+  ARC_RELEASE(_filePath);
+  
+  ARC_DEALLOC(super);
+}
+
+- (BOOL)open {
+  DCHECK(_file == 0);
+  _file = open([_filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+  return (_file > 0 ? YES : NO);
+}
+
+- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
+  DCHECK(_file > 0);
+  return write(_file, buffer, length);
+}
+
+- (BOOL)close {
+  DCHECK(_file > 0);
+  int result = close(_file);
+  _file = -1;
+  return (result == 0 ? YES : NO);
+}
+
+@end

+ 39 - 0
CGDWebServer/GCDWebServerFileResponse.h

@@ -0,0 +1,39 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerResponse.h"
+
+@interface GCDWebServerFileResponse : GCDWebServerResponse
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path;
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range;
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
+- (id)initWithFile:(NSString*)path;
+- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
+- (id)initWithFile:(NSString*)path byteRange:(NSRange)range;  // Pass [NSNotFound, 0] to disable byte range entirely, [offset, length] to enable byte range from beginning of file or [NSNotFound, -bytes] from end of file
+- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
+@end

+ 173 - 0
CGDWebServer/GCDWebServerFileResponse.m

@@ -0,0 +1,173 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <sys/stat.h>
+
+#import "GCDWebServerPrivate.h"
+
+#define kFileReadBufferSize (32 * 1024)
+
+@interface GCDWebServerFileResponse () {
+@private
+  NSString* _path;
+  NSUInteger _offset;
+  NSUInteger _size;
+  int _file;
+}
+@end
+
+static inline NSError* _MakePosixError(int code) {
+  return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%s", strerror(code)]}];
+}
+
+@implementation GCDWebServerFileResponse
+
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path {
+  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path]);
+}
+
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
+  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path isAttachment:attachment]);
+}
+
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range {
+  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range]);
+}
+
++ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
+  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]);
+}
+
+- (id)initWithFile:(NSString*)path {
+  return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:NO];
+}
+
+- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
+  return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:attachment];
+}
+
+- (id)initWithFile:(NSString*)path byteRange:(NSRange)range {
+  return [self initWithFile:path byteRange:range isAttachment:NO];
+}
+
+- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
+  struct stat info;
+  if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
+    DNOT_REACHED();
+    ARC_RELEASE(self);
+    return nil;
+  }
+  if ((range.location != NSNotFound) || (range.length > 0)) {
+    if (range.location != NSNotFound) {
+      range.location = MIN(range.location, (NSUInteger)info.st_size);
+      range.length = MIN(range.length, (NSUInteger)info.st_size - range.location);
+    } else {
+      range.length = MIN(range.length, (NSUInteger)info.st_size);
+      range.location = (NSUInteger)info.st_size - range.length;
+    }
+    if (range.length == 0) {
+      ARC_RELEASE(self);
+      return nil;  // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header
+    }
+  }
+  
+  if ((self = [super init])) {
+    _path = [path copy];
+    if (range.location != NSNotFound) {
+      _offset = range.location;
+      _size = range.length;
+      [self setStatusCode:206];
+      [self setValue:[NSString stringWithFormat:@"bytes %i-%i/%i", (int)range.location, (int)(range.location + range.length - 1), (int)info.st_size] forAdditionalHeader:@"Content-Range"];
+      LOG_DEBUG(@"Using content bytes range [%i-%i] for file \"%@\"", (int)range.location, (int)(range.location + range.length - 1), path);
+    } else {
+      _offset = 0;
+      _size = (NSUInteger)info.st_size;
+    }
+    
+    if (attachment) {  // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1
+      NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
+      NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
+      if (fileName) {
+        [self setValue:[NSString stringWithFormat:@"attachment; filename=\"%@\"", fileName] forAdditionalHeader:@"Content-Disposition"];
+        ARC_RELEASE(fileName);
+      } else {
+        DNOT_REACHED();
+      }
+    }
+    
+    self.contentType = GCDWebServerGetMimeTypeForExtension([path pathExtension]);
+    self.contentLength = (range.location != NSNotFound ? range.length : (NSUInteger)info.st_size);
+  }
+  return self;
+}
+
+- (void)dealloc {
+  DCHECK(_file <= 0);
+  ARC_RELEASE(_path);
+  
+  ARC_DEALLOC(super);
+}
+
+- (BOOL)open:(NSError**)error {
+  DCHECK(_file <= 0);
+  _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
+  if (_file <= 0) {
+    *error = _MakePosixError(errno);
+    return NO;
+  }
+  if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
+    *error = _MakePosixError(errno);
+    close(_file);
+    _file = 0;
+    return NO;
+  }
+  return YES;
+}
+
+- (NSData*)readData:(NSError**)error {
+  DCHECK(_file > 0);
+  size_t length = MIN((NSUInteger)kFileReadBufferSize, _size);
+  NSMutableData* data = [[NSMutableData alloc] initWithLength:length];
+  ssize_t result = read(_file, data.mutableBytes, length);
+  if (result < 0) {
+    *error = _MakePosixError(errno);
+    return nil;
+  }
+  if (result > 0) {
+    [data setLength:result];
+    _size -= result;
+  }
+  return ARC_AUTORELEASE(data);
+}
+
+- (void)close {
+  DCHECK(_file > 0);
+  close(_file);
+  _file = 0;
+}
+
+@end

+ 49 - 0
CGDWebServer/GCDWebServerMultiPartFormRequest.h

@@ -0,0 +1,49 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerRequest.h"
+
+@interface GCDWebServerMultiPart : NSObject
+@property(nonatomic, readonly) NSString* contentType;  // May be nil
+@property(nonatomic, readonly) NSString* mimeType;  // Defaults to "text/plain" per specifications if undefined
+@end
+
+@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart
+@property(nonatomic, readonly) NSData* data;
+@property(nonatomic, readonly) NSString* string;  // May be nil (only valid for text mime types)
+@end
+
+@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
+@property(nonatomic, readonly) NSString* fileName;  // May be nil
+@property(nonatomic, readonly) NSString* temporaryPath;
+@end
+
+@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest
+@property(nonatomic, readonly) NSDictionary* arguments;  // Only valid after open / write / close sequence
+@property(nonatomic, readonly) NSDictionary* files;  // Only valid after open / write / close sequence
++ (NSString*)mimeType;
+@end

+ 367 - 0
CGDWebServer/GCDWebServerMultiPartFormRequest.m

@@ -0,0 +1,367 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerPrivate.h"
+
+#define kMultiPartBufferSize (256 * 1024)
+
+enum {
+  kParserState_Undefined = 0,
+  kParserState_Start,
+  kParserState_Headers,
+  kParserState_Content,
+  kParserState_End
+};
+
+static NSData* _newlineData = nil;
+static NSData* _newlinesData = nil;
+static NSData* _dashNewlineData = nil;
+
+@interface GCDWebServerMultiPart () {
+@private
+  NSString* _contentType;
+  NSString* _mimeType;
+}
+@end
+
+@implementation GCDWebServerMultiPart
+
+@synthesize contentType=_contentType, mimeType=_mimeType;
+
+- (id)initWithContentType:(NSString*)contentType {
+  if ((self = [super init])) {
+    _contentType = [contentType copy];
+    NSArray* components = [_contentType componentsSeparatedByString:@";"];
+    if (components.count) {
+      _mimeType = ARC_RETAIN([[components objectAtIndex:0] lowercaseString]);
+    }
+    if (_mimeType == nil) {
+      _mimeType = @"text/plain";
+    }
+  }
+  return self;
+}
+
+- (void)dealloc {
+  ARC_RELEASE(_contentType);
+  ARC_RELEASE(_mimeType);
+  
+  ARC_DEALLOC(super);
+}
+
+@end
+
+@interface GCDWebServerMultiPartArgument () {
+@private
+  NSData* _data;
+  NSString* _string;
+}
+@end
+
+@implementation GCDWebServerMultiPartArgument
+
+@synthesize data=_data, string=_string;
+
+- (id)initWithContentType:(NSString*)contentType data:(NSData*)data {
+  if ((self = [super initWithContentType:contentType])) {
+    _data = ARC_RETAIN(data);
+    
+    if ([self.mimeType hasPrefix:@"text/"]) {
+      NSString* charset = GCDWebServerExtractHeaderParameter(self.contentType, @"charset");
+      _string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)];
+    }
+  }
+  return self;
+}
+
+- (void)dealloc {
+  ARC_RELEASE(_data);
+  ARC_RELEASE(_string);
+  
+  ARC_DEALLOC(super);
+}
+
+- (NSString*)description {
+  return [NSString stringWithFormat:@"<%@ | '%@' | %i bytes>", [self class], self.mimeType, (int)_data.length];
+}
+
+@end
+
+@interface GCDWebServerMultiPartFile () {
+@private
+  NSString* _fileName;
+  NSString* _temporaryPath;
+}
+@end
+
+@implementation GCDWebServerMultiPartFile
+
+@synthesize fileName=_fileName, temporaryPath=_temporaryPath;
+
+- (id)initWithContentType:(NSString*)contentType fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
+  if ((self = [super initWithContentType:contentType])) {
+    _fileName = [fileName copy];
+    _temporaryPath = [temporaryPath copy];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  unlink([_temporaryPath fileSystemRepresentation]);
+  
+  ARC_RELEASE(_fileName);
+  ARC_RELEASE(_temporaryPath);
+  
+  ARC_DEALLOC(super);
+}
+
+- (NSString*)description {
+  return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
+}
+
+@end
+
+@interface GCDWebServerMultiPartFormRequest () {
+@private
+  NSData* _boundary;
+  
+  NSUInteger _parserState;
+  NSMutableData* _parserData;
+  NSString* _controlName;
+  NSString* _fileName;
+  NSString* _contentType;
+  NSString* _tmpPath;
+  int _tmpFile;
+  
+  NSMutableDictionary* _arguments;
+  NSMutableDictionary* _files;
+}
+@end
+
+@implementation GCDWebServerMultiPartFormRequest
+
+@synthesize arguments=_arguments, files=_files;
+
++ (void)initialize {
+  if (_newlineData == nil) {
+    _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
+    DCHECK(_newlineData);
+  }
+  if (_newlinesData == nil) {
+    _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
+    DCHECK(_newlinesData);
+  }
+  if (_dashNewlineData == nil) {
+    _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
+    DCHECK(_dashNewlineData);
+  }
+}
+
++ (NSString*)mimeType {
+  return @"multipart/form-data";
+}
+
+- (id)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])) {
+    NSString* boundary = GCDWebServerExtractHeaderParameter(self.contentType, @"boundary");
+    if (boundary) {
+      NSData* data = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding];
+      _boundary = ARC_RETAIN(data);
+    }
+    if (_boundary == nil) {
+      DNOT_REACHED();
+      ARC_RELEASE(self);
+      return nil;
+    }
+    
+    _arguments = [[NSMutableDictionary alloc] init];
+    _files = [[NSMutableDictionary alloc] init];
+  }
+  return self;
+}
+
+- (BOOL)open {
+  DCHECK(_parserData == nil);
+  _parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
+  _parserState = kParserState_Start;
+  return YES;
+}
+
+// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
+- (BOOL)_parseData {
+  BOOL success = YES;
+  
+  if (_parserState == kParserState_Headers) {
+    NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)];
+    if (range.location != NSNotFound) {
+      
+      ARC_RELEASE(_controlName);
+      _controlName = nil;
+      ARC_RELEASE(_fileName);
+      _fileName = nil;
+      ARC_RELEASE(_contentType);
+      _contentType = nil;
+      ARC_RELEASE(_tmpPath);
+      _tmpPath = nil;
+      CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
+      const char* temp = "GET / HTTP/1.0\r\n";
+      CFHTTPMessageAppendBytes(message, (const UInt8*)temp, strlen(temp));
+      CFHTTPMessageAppendBytes(message, _parserData.bytes, range.location + range.length);
+      if (CFHTTPMessageIsHeaderComplete(message)) {
+        NSString* controlName = nil;
+        NSString* fileName = nil;
+        NSDictionary* headers = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(message));
+        NSString* contentDisposition = [headers objectForKey:@"Content-Disposition"];
+        if ([[contentDisposition lowercaseString] hasPrefix:@"form-data;"]) {
+          controlName = GCDWebServerExtractHeaderParameter(contentDisposition, @"name");
+          fileName = GCDWebServerExtractHeaderParameter(contentDisposition, @"filename");
+        }
+        _controlName = [controlName copy];
+        _fileName = [fileName copy];
+        _contentType = ARC_RETAIN([headers objectForKey:@"Content-Type"]);
+      }
+      CFRelease(message);
+      if (_controlName) {
+        if (_fileName) {
+          NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
+          _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+          if (_tmpFile > 0) {
+            _tmpPath = [path copy];
+          } else {
+            DNOT_REACHED();
+            success = NO;
+          }
+        }
+      } else {
+        DNOT_REACHED();
+        success = NO;
+      }
+      
+      [_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
+      _parserState = kParserState_Content;
+    }
+  }
+  
+  if ((_parserState == kParserState_Start) || (_parserState == kParserState_Content)) {
+    NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)];
+    if (range.location != NSNotFound) {
+      NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length);
+      NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
+      NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
+      if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
+        
+        if (_parserState == kParserState_Content) {
+          const void* dataBytes = _parserData.bytes;
+          NSUInteger dataLength = range.location - 2;
+          if (_tmpPath) {
+            ssize_t result = write(_tmpFile, dataBytes, dataLength);
+            if (result == (ssize_t)dataLength) {
+              if (close(_tmpFile) == 0) {
+                _tmpFile = 0;
+                GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
+                [_files setObject:file forKey:_controlName];
+                ARC_RELEASE(file);
+              } else {
+                DNOT_REACHED();
+                success = NO;
+              }
+            } else {
+              DNOT_REACHED();
+              success = NO;
+            }
+            ARC_RELEASE(_tmpPath);
+            _tmpPath = nil;
+          } else {
+            NSData* data = [[NSData alloc] initWithBytesNoCopy:(void*)dataBytes length:dataLength freeWhenDone:NO];
+            GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithContentType:_contentType data:data];
+            [_arguments setObject:argument forKey:_controlName];
+            ARC_RELEASE(argument);
+            ARC_RELEASE(data);
+          }
+        }
+        
+        if (subRange1.location != NSNotFound) {
+          [_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
+          _parserState = kParserState_Headers;
+          success = [self _parseData];
+        } else {
+          _parserState = kParserState_End;
+        }
+      }
+    } else {
+      NSUInteger margin = 2 * _boundary.length;
+      if (_tmpPath && (_parserData.length > margin)) {
+        NSUInteger length = _parserData.length - margin;
+        ssize_t result = write(_tmpFile, _parserData.bytes, length);
+        if (result == (ssize_t)length) {
+          [_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
+        } else {
+          DNOT_REACHED();
+          success = NO;
+        }
+      }
+    }
+  }
+  return success;
+}
+
+- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
+  DCHECK(_parserData != nil);
+  [_parserData appendBytes:buffer length:length];
+  return ([self _parseData] ? length : -1);
+}
+
+- (BOOL)close {
+  DCHECK(_parserData != nil);
+  ARC_RELEASE(_parserData);
+  _parserData = nil;
+  ARC_RELEASE(_controlName);
+  _controlName = nil;
+  ARC_RELEASE(_fileName);
+  _fileName = nil;
+  ARC_RELEASE(_contentType);
+  _contentType = nil;
+  if (_tmpFile > 0) {
+    close(_tmpFile);
+    unlink([_tmpPath fileSystemRepresentation]);
+    _tmpFile = 0;
+  }
+  ARC_RELEASE(_tmpPath);
+  _tmpPath = nil;
+  return (_parserState == kParserState_End ? YES : NO);
+}
+
+- (void)dealloc {
+  DCHECK(_parserData == nil);
+  ARC_RELEASE(_arguments);
+  ARC_RELEASE(_files);
+  ARC_RELEASE(_boundary);
+  
+  ARC_DEALLOC(super);
+}
+
+@end

+ 12 - 0
CGDWebServer/GCDWebServerPrivate.h

@@ -52,6 +52,15 @@
 
 #import "GCDWebServerConnection.h"
 
+#import "GCDWebServerDataRequest.h"
+#import "GCDWebServerFileRequest.h"
+#import "GCDWebServerMultiPartFormRequest.h"
+#import "GCDWebServerURLEncodedFormRequest.h"
+
+#import "GCDWebServerDataResponse.h"
+#import "GCDWebServerFileResponse.h"
+#import "GCDWebServerStreamResponse.h"
+
 #ifdef __GCDWEBSERVER_LOGGING_HEADER__
 
 // Define __GCDWEBSERVER_LOGGING_HEADER__ as a preprocessor constant to redirect GCDWebServer logging to your own system
@@ -91,6 +100,9 @@ extern void GCDLogMessage(long level, NSString* format, ...) NS_FORMAT_FUNCTION(
 #define kGCDWebServerDefaultMimeType @"application/octet-stream"
 #define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
 
+extern NSString* GCDWebServerExtractHeaderParameter(NSString* header, NSString* attribute);
+extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset);
+
 @interface GCDWebServerConnection ()
 - (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
 @end

+ 0 - 34
CGDWebServer/GCDWebServerRequest.h

@@ -45,37 +45,3 @@
 - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length;  // Implementation required
 - (BOOL)close;  // Implementation required
 @end
-
-@interface GCDWebServerDataRequest : GCDWebServerRequest
-@property(nonatomic, readonly) NSData* data;  // Only valid after open / write / close sequence
-@end
-
-@interface GCDWebServerFileRequest : GCDWebServerRequest
-@property(nonatomic, readonly) NSString* filePath;  // Only valid after open / write / close sequence
-@end
-
-@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
-@property(nonatomic, readonly) NSDictionary* arguments;  // Only valid after open / write / close sequence
-+ (NSString*)mimeType;
-@end
-
-@interface GCDWebServerMultiPart : NSObject
-@property(nonatomic, readonly) NSString* contentType;  // May be nil
-@property(nonatomic, readonly) NSString* mimeType;  // Defaults to "text/plain" per specifications if undefined
-@end
-
-@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart
-@property(nonatomic, readonly) NSData* data;
-@property(nonatomic, readonly) NSString* string;  // May be nil (only valid for text mime types)
-@end
-
-@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
-@property(nonatomic, readonly) NSString* fileName;  // May be nil
-@property(nonatomic, readonly) NSString* temporaryPath;
-@end
-
-@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest
-@property(nonatomic, readonly) NSDictionary* arguments;  // Only valid after open / write / close sequence
-@property(nonatomic, readonly) NSDictionary* files;  // Only valid after open / write / close sequence
-+ (NSString*)mimeType;
-@end

+ 0 - 483
CGDWebServer/GCDWebServerRequest.m

@@ -27,16 +27,6 @@
 
 #import "GCDWebServerPrivate.h"
 
-#define kMultiPartBufferSize (256 * 1024)
-
-enum {
-  kParserState_Undefined = 0,
-  kParserState_Start,
-  kParserState_Headers,
-  kParserState_Content,
-  kParserState_End
-};
-
 @interface GCDWebServerRequest () {
 @private
   NSString* _method;
@@ -50,94 +40,6 @@ enum {
 }
 @end
 
-@interface GCDWebServerDataRequest () {
-@private
-  NSMutableData* _data;
-}
-@end
-
-@interface GCDWebServerFileRequest () {
-@private
-  NSString* _filePath;
-  int _file;
-}
-@end
-
-@interface GCDWebServerURLEncodedFormRequest () {
-@private
-  NSDictionary* _arguments;
-}
-@end
-
-@interface GCDWebServerMultiPart () {
-@private
-  NSString* _contentType;
-  NSString* _mimeType;
-}
-@end
-
-@interface GCDWebServerMultiPartArgument () {
-@private
-  NSData* _data;
-  NSString* _string;
-}
-@end
-
-@interface GCDWebServerMultiPartFile () {
-@private
-  NSString* _fileName;
-  NSString* _temporaryPath;
-}
-@end
-
-@interface GCDWebServerMultiPartFormRequest () {
-@private
-  NSData* _boundary;
-  
-  NSUInteger _parserState;
-  NSMutableData* _parserData;
-  NSString* _controlName;
-  NSString* _fileName;
-  NSString* _contentType;
-  NSString* _tmpPath;
-  int _tmpFile;
-  
-  NSMutableDictionary* _arguments;
-  NSMutableDictionary* _files;
-}
-@end
-
-static NSData* _newlineData = nil;
-static NSData* _newlinesData = nil;
-static NSData* _dashNewlineData = nil;
-
-static NSString* _ExtractHeaderParameter(NSString* header, NSString* attribute) {
-  NSString* value = nil;
-  if (header) {
-    NSScanner* scanner = [[NSScanner alloc] initWithString:header];
-    NSString* string = [NSString stringWithFormat:@"%@=", attribute];
-    if ([scanner scanUpToString:string intoString:NULL]) {
-      [scanner scanString:string intoString:NULL];
-      if ([scanner scanString:@"\"" intoString:NULL]) {
-        [scanner scanUpToString:@"\"" intoString:&value];
-      } else {
-        [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&value];
-      }
-    }
-    ARC_RELEASE(scanner);
-  }
-  return value;
-}
-
-// http://www.w3schools.com/tags/ref_charactersets.asp
-static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
-  NSStringEncoding encoding = kCFStringEncodingInvalidId;
-  if (charset) {
-    encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset));
-  }
-  return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding);
-}
-
 @implementation GCDWebServerRequest : NSObject
 
 @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, byteRange=_range;
@@ -234,388 +136,3 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
 }
 
 @end
-
-@implementation GCDWebServerDataRequest
-
-@synthesize data=_data;
-
-- (void)dealloc {
-  DCHECK(_data != nil);
-  ARC_RELEASE(_data);
-  
-  ARC_DEALLOC(super);
-}
-
-- (BOOL)open {
-  DCHECK(_data == nil);
-  _data = [[NSMutableData alloc] initWithCapacity:self.contentLength];
-  return _data ? YES : NO;
-}
-
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
-  DCHECK(_data != nil);
-  [_data appendBytes:buffer length:length];
-  return length;
-}
-
-- (BOOL)close {
-  DCHECK(_data != nil);
-  return YES;
-}
-
-@end
-
-@implementation GCDWebServerFileRequest
-
-@synthesize filePath=_filePath;
-
-- (id)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])) {
-    _filePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
-  }
-  return self;
-}
-
-- (void)dealloc {
-  DCHECK(_file < 0);
-  unlink([_filePath fileSystemRepresentation]);
-  ARC_RELEASE(_filePath);
-  
-  ARC_DEALLOC(super);
-}
-
-- (BOOL)open {
-  DCHECK(_file == 0);
-  _file = open([_filePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
-  return (_file > 0 ? YES : NO);
-}
-
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
-  DCHECK(_file > 0);
-  return write(_file, buffer, length);
-}
-
-- (BOOL)close {
-  DCHECK(_file > 0);
-  int result = close(_file);
-  _file = -1;
-  return (result == 0 ? YES : NO);
-}
-
-@end
-
-@implementation GCDWebServerURLEncodedFormRequest
-
-@synthesize arguments=_arguments;
-
-+ (NSString*)mimeType {
-  return @"application/x-www-form-urlencoded";
-}
-
-- (void)dealloc {
-  ARC_RELEASE(_arguments);
-  
-  ARC_DEALLOC(super);
-}
-
-- (BOOL)close {
-  if (![super close]) {
-    return NO;
-  }
-  
-  NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
-  NSString* string = [[NSString alloc] initWithData:self.data encoding:_StringEncodingFromCharset(charset)];
-  _arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
-  ARC_RELEASE(string);
-  
-  return (_arguments ? YES : NO);
-}
-
-@end
-
-@implementation GCDWebServerMultiPart
-
-@synthesize contentType=_contentType, mimeType=_mimeType;
-
-- (id)initWithContentType:(NSString*)contentType {
-  if ((self = [super init])) {
-    _contentType = [contentType copy];
-    NSArray* components = [_contentType componentsSeparatedByString:@";"];
-    if (components.count) {
-      _mimeType = ARC_RETAIN([[components objectAtIndex:0] lowercaseString]);
-    }
-    if (_mimeType == nil) {
-      _mimeType = @"text/plain";
-    }
-  }
-  return self;
-}
-
-- (void)dealloc {
-  ARC_RELEASE(_contentType);
-  ARC_RELEASE(_mimeType);
-  
-  ARC_DEALLOC(super);
-}
-
-@end
-
-@implementation GCDWebServerMultiPartArgument
-
-@synthesize data=_data, string=_string;
-
-- (id)initWithContentType:(NSString*)contentType data:(NSData*)data {
-  if ((self = [super initWithContentType:contentType])) {
-    _data = ARC_RETAIN(data);
-    
-    if ([self.mimeType hasPrefix:@"text/"]) {
-      NSString* charset = _ExtractHeaderParameter(self.contentType, @"charset");
-      _string = [[NSString alloc] initWithData:_data encoding:_StringEncodingFromCharset(charset)];
-    }
-  }
-  return self;
-}
-
-- (void)dealloc {
-  ARC_RELEASE(_data);
-  ARC_RELEASE(_string);
-  
-  ARC_DEALLOC(super);
-}
-
-- (NSString*)description {
-  return [NSString stringWithFormat:@"<%@ | '%@' | %i bytes>", [self class], self.mimeType, (int)_data.length];
-}
-
-@end
-
-@implementation GCDWebServerMultiPartFile
-
-@synthesize fileName=_fileName, temporaryPath=_temporaryPath;
-
-- (id)initWithContentType:(NSString*)contentType fileName:(NSString*)fileName temporaryPath:(NSString*)temporaryPath {
-  if ((self = [super initWithContentType:contentType])) {
-    _fileName = [fileName copy];
-    _temporaryPath = [temporaryPath copy];
-  }
-  return self;
-}
-
-- (void)dealloc {
-  unlink([_temporaryPath fileSystemRepresentation]);
-  
-  ARC_RELEASE(_fileName);
-  ARC_RELEASE(_temporaryPath);
-  
-  ARC_DEALLOC(super);
-}
-
-- (NSString*)description {
-  return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName];
-}
-
-@end
-
-@implementation GCDWebServerMultiPartFormRequest
-
-@synthesize arguments=_arguments, files=_files;
-
-+ (void)initialize {
-  if (_newlineData == nil) {
-    _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
-    DCHECK(_newlineData);
-  }
-  if (_newlinesData == nil) {
-    _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
-    DCHECK(_newlinesData);
-  }
-  if (_dashNewlineData == nil) {
-    _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
-    DCHECK(_dashNewlineData);
-  }
-}
-
-+ (NSString*)mimeType {
-  return @"multipart/form-data";
-}
-
-- (id)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])) {
-    NSString* boundary = _ExtractHeaderParameter(self.contentType, @"boundary");
-    if (boundary) {
-      NSData* data = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding];
-      _boundary = ARC_RETAIN(data);
-    }
-    if (_boundary == nil) {
-      DNOT_REACHED();
-      ARC_RELEASE(self);
-      return nil;
-    }
-    
-    _arguments = [[NSMutableDictionary alloc] init];
-    _files = [[NSMutableDictionary alloc] init];
-  }
-  return self;
-}
-
-- (BOOL)open {
-  DCHECK(_parserData == nil);
-  _parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize];
-  _parserState = kParserState_Start;
-  return YES;
-}
-
-// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
-- (BOOL)_parseData {
-  BOOL success = YES;
-  
-  if (_parserState == kParserState_Headers) {
-    NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)];
-    if (range.location != NSNotFound) {
-      
-      ARC_RELEASE(_controlName);
-      _controlName = nil;
-      ARC_RELEASE(_fileName);
-      _fileName = nil;
-      ARC_RELEASE(_contentType);
-      _contentType = nil;
-      ARC_RELEASE(_tmpPath);
-      _tmpPath = nil;
-      CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true);
-      const char* temp = "GET / HTTP/1.0\r\n";
-      CFHTTPMessageAppendBytes(message, (const UInt8*)temp, strlen(temp));
-      CFHTTPMessageAppendBytes(message, _parserData.bytes, range.location + range.length);
-      if (CFHTTPMessageIsHeaderComplete(message)) {
-        NSString* controlName = nil;
-        NSString* fileName = nil;
-        NSDictionary* headers = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(message));
-        NSString* contentDisposition = [headers objectForKey:@"Content-Disposition"];
-        if ([[contentDisposition lowercaseString] hasPrefix:@"form-data;"]) {
-          controlName = _ExtractHeaderParameter(contentDisposition, @"name");
-          fileName = _ExtractHeaderParameter(contentDisposition, @"filename");
-        }
-        _controlName = [controlName copy];
-        _fileName = [fileName copy];
-        _contentType = ARC_RETAIN([headers objectForKey:@"Content-Type"]);
-      }
-      CFRelease(message);
-      if (_controlName) {
-        if (_fileName) {
-          NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
-          _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
-          if (_tmpFile > 0) {
-            _tmpPath = [path copy];
-          } else {
-            DNOT_REACHED();
-            success = NO;
-          }
-        }
-      } else {
-        DNOT_REACHED();
-        success = NO;
-      }
-      
-      [_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0];
-      _parserState = kParserState_Content;
-    }
-  }
-  
-  if ((_parserState == kParserState_Start) || (_parserState == kParserState_Content)) {
-    NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)];
-    if (range.location != NSNotFound) {
-      NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length);
-      NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange];
-      NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange];
-      if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) {
-        
-        if (_parserState == kParserState_Content) {
-          const void* dataBytes = _parserData.bytes;
-          NSUInteger dataLength = range.location - 2;
-          if (_tmpPath) {
-            ssize_t result = write(_tmpFile, dataBytes, dataLength);
-            if (result == (ssize_t)dataLength) {
-              if (close(_tmpFile) == 0) {
-                _tmpFile = 0;
-                GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
-                [_files setObject:file forKey:_controlName];
-                ARC_RELEASE(file);
-              } else {
-                DNOT_REACHED();
-                success = NO;
-              }
-            } else {
-              DNOT_REACHED();
-              success = NO;
-            }
-            ARC_RELEASE(_tmpPath);
-            _tmpPath = nil;
-          } else {
-            NSData* data = [[NSData alloc] initWithBytesNoCopy:(void*)dataBytes length:dataLength freeWhenDone:NO];
-            GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithContentType:_contentType data:data];
-            [_arguments setObject:argument forKey:_controlName];
-            ARC_RELEASE(argument);
-            ARC_RELEASE(data);
-          }
-        }
-        
-        if (subRange1.location != NSNotFound) {
-          [_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0];
-          _parserState = kParserState_Headers;
-          success = [self _parseData];
-        } else {
-          _parserState = kParserState_End;
-        }
-      }
-    } else {
-      NSUInteger margin = 2 * _boundary.length;
-      if (_tmpPath && (_parserData.length > margin)) {
-        NSUInteger length = _parserData.length - margin;
-        ssize_t result = write(_tmpFile, _parserData.bytes, length);
-        if (result == (ssize_t)length) {
-          [_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
-        } else {
-          DNOT_REACHED();
-          success = NO;
-        }
-      }
-    }
-  }
-  return success;
-}
-
-- (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
-  DCHECK(_parserData != nil);
-  [_parserData appendBytes:buffer length:length];
-  return ([self _parseData] ? length : -1);
-}
-
-- (BOOL)close {
-  DCHECK(_parserData != nil);
-  ARC_RELEASE(_parserData);
-  _parserData = nil;
-  ARC_RELEASE(_controlName);
-  _controlName = nil;
-  ARC_RELEASE(_fileName);
-  _fileName = nil;
-  ARC_RELEASE(_contentType);
-  _contentType = nil;
-  if (_tmpFile > 0) {
-    close(_tmpFile);
-    unlink([_tmpPath fileSystemRepresentation]);
-    _tmpFile = 0;
-  }
-  ARC_RELEASE(_tmpPath);
-  _tmpPath = nil;
-  return (_parserState == kParserState_End ? YES : NO);
-}
-
-- (void)dealloc {
-  DCHECK(_parserData == nil);
-  ARC_RELEASE(_arguments);
-  ARC_RELEASE(_files);
-  ARC_RELEASE(_boundary);
-  
-  ARC_DEALLOC(super);
-}
-
-@end

+ 0 - 36
CGDWebServer/GCDWebServerResponse.h

@@ -27,8 +27,6 @@
 
 #import <Foundation/Foundation.h>
 
-typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
-
 @protocol GCDWebServerBodyReader <NSObject>
 - (BOOL)open:(NSError**)error;
 - (NSData*)readData:(NSError**)error;  // Return nil on error or empty NSData if at end
@@ -54,37 +52,3 @@ typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
 - (id)initWithStatusCode:(NSInteger)statusCode;
 - (id)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
 @end
-
-@interface GCDWebServerDataResponse : GCDWebServerResponse
-+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type;
-- (id)initWithData:(NSData*)data contentType:(NSString*)type;
-@end
-
-@interface GCDWebServerDataResponse (Extensions)
-+ (GCDWebServerDataResponse*)responseWithText:(NSString*)text;
-+ (GCDWebServerDataResponse*)responseWithHTML:(NSString*)html;
-+ (GCDWebServerDataResponse*)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;
-+ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object;
-+ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object contentType:(NSString*)type;
-- (id)initWithText:(NSString*)text;  // Encodes using UTF-8
-- (id)initWithHTML:(NSString*)html;  // Encodes using UTF-8
-- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables;  // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8)
-- (id)initWithJSONObject:(id)object;
-- (id)initWithJSONObject:(id)object contentType:(NSString*)type;
-@end
-
-@interface GCDWebServerFileResponse : GCDWebServerResponse
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path;
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range;
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
-- (id)initWithFile:(NSString*)path;
-- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
-- (id)initWithFile:(NSString*)path byteRange:(NSRange)range;  // Pass [NSNotFound, 0] to disable byte range entirely, [offset, length] to enable byte range from beginning of file or [NSNotFound, -bytes] from end of file
-- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
-@end
-
-@interface GCDWebServerStreamResponse : GCDWebServerResponse  // Forces chunked transfer encoding
-+ (GCDWebServerStreamResponse*)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
-- (id)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;  // Block must return empty NSData when done or nil on error
-@end

+ 0 - 292
CGDWebServer/GCDWebServerResponse.m

@@ -25,14 +25,12 @@
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#import <sys/stat.h>
 #import <zlib.h>
 
 #import "GCDWebServerPrivate.h"
 
 #define kZlibErrorDomain @"ZlibErrorDomain"
 #define kGZipInitialBufferSize (256 * 1024)
-#define kFileReadBufferSize (32 * 1024)
 
 @interface GCDWebServerBodyEncoder : NSObject <GCDWebServerBodyReader>
 - (id)initWithResponse:(GCDWebServerResponse*)response reader:(id<GCDWebServerBodyReader>)reader;
@@ -332,293 +330,3 @@
 }
 
 @end
-
-@interface GCDWebServerDataResponse () {
-@private
-  NSData* _data;
-  BOOL _done;
-}
-@end
-
-@implementation GCDWebServerDataResponse
-
-+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type {
-  return ARC_AUTORELEASE([[[self class] alloc] initWithData:data contentType:type]);
-}
-
-- (id)initWithData:(NSData*)data contentType:(NSString*)type {
-  if (data == nil) {
-    DNOT_REACHED();
-    ARC_RELEASE(self);
-    return nil;
-  }
-  
-  if ((self = [super init])) {
-    _data = ARC_RETAIN(data);
-    
-    self.contentType = type;
-    self.contentLength = data.length;
-  }
-  return self;
-}
-
-- (void)dealloc {
-  ARC_RELEASE(_data);
-  
-  ARC_DEALLOC(super);
-}
-
-- (NSData*)readData:(NSError**)error {
-  NSData* data;
-  if (_done) {
-    data = [NSData data];
-  } else {
-    data = _data;
-    _done = YES;
-  }
-  return data;
-}
-
-@end
-
-@implementation GCDWebServerDataResponse (Extensions)
-
-+ (GCDWebServerDataResponse*)responseWithText:(NSString*)text {
-  return ARC_AUTORELEASE([[self alloc] initWithText:text]);
-}
-
-+ (GCDWebServerDataResponse*)responseWithHTML:(NSString*)html {
-  return ARC_AUTORELEASE([[self alloc] initWithHTML:html]);
-}
-
-+ (GCDWebServerDataResponse*)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
-  return ARC_AUTORELEASE([[self alloc] initWithHTMLTemplate:path variables:variables]);
-}
-
-+ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object {
-  return ARC_AUTORELEASE([[self alloc] initWithJSONObject:object]);
-}
-
-+ (GCDWebServerDataResponse*)responseWithJSONObject:(id)object contentType:(NSString*)type {
-  return ARC_AUTORELEASE([[self alloc] initWithJSONObject:object contentType:type]);
-}
-
-- (id)initWithText:(NSString*)text {
-  NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding];
-  if (data == nil) {
-    DNOT_REACHED();
-    ARC_RELEASE(self);
-    return nil;
-  }
-  return [self initWithData:data contentType:@"text/plain; charset=utf-8"];
-}
-
-- (id)initWithHTML:(NSString*)html {
-  NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding];
-  if (data == nil) {
-    DNOT_REACHED();
-    ARC_RELEASE(self);
-    return nil;
-  }
-  return [self initWithData:data contentType:@"text/html; charset=utf-8"];
-}
-
-- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables {
-  NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
-  [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) {
-    [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)];
-  }];
-  id response = [self initWithHTML:html];
-  ARC_RELEASE(html);
-  return response;
-}
-
-- (id)initWithJSONObject:(id)object {
-  return [self initWithJSONObject:object contentType:@"application/json"];
-}
-
-- (id)initWithJSONObject:(id)object contentType:(NSString*)type {
-  NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
-  if (data == nil) {
-    ARC_RELEASE(self);
-    return nil;
-  }
-  return [self initWithData:data contentType:type];
-}
-
-@end
-
-@interface GCDWebServerFileResponse () {
-@private
-  NSString* _path;
-  NSUInteger _offset;
-  NSUInteger _size;
-  int _file;
-}
-@end
-
-@implementation GCDWebServerFileResponse
-
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path {
-  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path]);
-}
-
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment {
-  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path isAttachment:attachment]);
-}
-
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range {
-  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range]);
-}
-
-+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
-  return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]);
-}
-
-- (id)initWithFile:(NSString*)path {
-  return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:NO];
-}
-
-- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
-  return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:attachment];
-}
-
-- (id)initWithFile:(NSString*)path byteRange:(NSRange)range {
-  return [self initWithFile:path byteRange:range isAttachment:NO];
-}
-
-- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
-  struct stat info;
-  if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
-    DNOT_REACHED();
-    ARC_RELEASE(self);
-    return nil;
-  }
-  if ((range.location != NSNotFound) || (range.length > 0)) {
-    if (range.location != NSNotFound) {
-      range.location = MIN(range.location, (NSUInteger)info.st_size);
-      range.length = MIN(range.length, (NSUInteger)info.st_size - range.location);
-    } else {
-      range.length = MIN(range.length, (NSUInteger)info.st_size);
-      range.location = (NSUInteger)info.st_size - range.length;
-    }
-    if (range.length == 0) {
-      ARC_RELEASE(self);
-      return nil;  // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header
-    }
-  }
-  
-  if ((self = [super init])) {
-    _path = [path copy];
-    if (range.location != NSNotFound) {
-      _offset = range.location;
-      _size = range.length;
-      [self setStatusCode:206];
-      [self setValue:[NSString stringWithFormat:@"bytes %i-%i/%i", (int)range.location, (int)(range.location + range.length - 1), (int)info.st_size] forAdditionalHeader:@"Content-Range"];
-      LOG_DEBUG(@"Using content bytes range [%i-%i] for file \"%@\"", (int)range.location, (int)(range.location + range.length - 1), path);
-    } else {
-      _offset = 0;
-      _size = (NSUInteger)info.st_size;
-    }
-    
-    if (attachment) {  // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1
-      NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
-      NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
-      if (fileName) {
-        [self setValue:[NSString stringWithFormat:@"attachment; filename=\"%@\"", fileName] forAdditionalHeader:@"Content-Disposition"];
-        ARC_RELEASE(fileName);
-      } else {
-        DNOT_REACHED();
-      }
-    }
-    
-    self.contentType = GCDWebServerGetMimeTypeForExtension([path pathExtension]);
-    self.contentLength = (range.location != NSNotFound ? range.length : (NSUInteger)info.st_size);
-  }
-  return self;
-}
-
-- (void)dealloc {
-  DCHECK(_file <= 0);
-  ARC_RELEASE(_path);
-  
-  ARC_DEALLOC(super);
-}
-
-static inline NSError* _MakePosixError(int code) {
-  return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%s", strerror(code)]}];
-}
-
-- (BOOL)open:(NSError**)error {
-  DCHECK(_file <= 0);
-  _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
-  if (_file <= 0) {
-    *error = _MakePosixError(errno);
-    return NO;
-  }
-  if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
-    *error = _MakePosixError(errno);
-    close(_file);
-    _file = 0;
-    return NO;
-  }
-  return YES;
-}
-
-- (NSData*)readData:(NSError**)error {
-  DCHECK(_file > 0);
-  size_t length = MIN((NSUInteger)kFileReadBufferSize, _size);
-  NSMutableData* data = [[NSMutableData alloc] initWithLength:length];
-  ssize_t result = read(_file, data.mutableBytes, length);
-  if (result < 0) {
-    *error = _MakePosixError(errno);
-    return nil;
-  }
-  if (result > 0) {
-    [data setLength:result];
-    _size -= result;
-  }
-  return ARC_AUTORELEASE(data);
-}
-
-- (void)close {
-  DCHECK(_file > 0);
-  close(_file);
-  _file = 0;
-}
-
-@end
-
-@interface GCDWebServerStreamResponse () {
-@private
-  GCDWebServerStreamBlock _block;
-}
-@end
-
-@implementation GCDWebServerStreamResponse
-
-+ (GCDWebServerStreamResponse*)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
-  return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type streamBlock:block]);
-}
-
-- (id)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
-  if ((self = [super init])) {
-    _block = [block copy];
-    
-    self.contentType = type;
-    self.chunkedTransferEncoding = YES;
-  }
-  return self;
-}
-
-- (void)dealloc {
-  ARC_RELEASE(_block);
-  
-  ARC_DEALLOC(super);
-}
-
-- (NSData*)readData:(NSError**)error {
-  return _block(error);
-}
-
-@end

+ 35 - 0
CGDWebServer/GCDWebServerStreamResponse.h

@@ -0,0 +1,35 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerStreamResponse.h"
+
+typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
+
+@interface GCDWebServerStreamResponse : GCDWebServerResponse  // Forces chunked transfer encoding
++ (GCDWebServerStreamResponse*)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
+- (id)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;  // Block must return empty NSData when done or nil on error
+@end

+ 62 - 0
CGDWebServer/GCDWebServerStreamResponse.m

@@ -0,0 +1,62 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerPrivate.h"
+
+@interface GCDWebServerStreamResponse () {
+@private
+  GCDWebServerStreamBlock _block;
+}
+@end
+
+@implementation GCDWebServerStreamResponse
+
++ (GCDWebServerStreamResponse*)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
+  return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type streamBlock:block]);
+}
+
+- (id)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
+  if ((self = [super init])) {
+    _block = [block copy];
+    
+    self.contentType = type;
+    self.chunkedTransferEncoding = YES;
+  }
+  return self;
+}
+
+- (void)dealloc {
+  ARC_RELEASE(_block);
+  
+  ARC_DEALLOC(super);
+}
+
+- (NSData*)readData:(NSError**)error {
+  return _block(error);
+}
+
+@end

+ 33 - 0
CGDWebServer/GCDWebServerURLEncodedFormRequest.h

@@ -0,0 +1,33 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerDataRequest.h"
+
+@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
+@property(nonatomic, readonly) NSDictionary* arguments;  // Only valid after open / write / close sequence
++ (NSString*)mimeType;
+@end

+ 63 - 0
CGDWebServer/GCDWebServerURLEncodedFormRequest.m

@@ -0,0 +1,63 @@
+/*
+ Copyright (c) 2012-2014, Pierre-Olivier Latour
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The name of Pierre-Olivier Latour may not be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "GCDWebServerPrivate.h"
+
+@interface GCDWebServerURLEncodedFormRequest () {
+@private
+  NSDictionary* _arguments;
+}
+@end
+
+@implementation GCDWebServerURLEncodedFormRequest
+
+@synthesize arguments=_arguments;
+
++ (NSString*)mimeType {
+  return @"application/x-www-form-urlencoded";
+}
+
+- (void)dealloc {
+  ARC_RELEASE(_arguments);
+  
+  ARC_DEALLOC(super);
+}
+
+- (BOOL)close {
+  if (![super close]) {
+    return NO;
+  }
+  
+  NSString* charset = GCDWebServerExtractHeaderParameter(self.contentType, @"charset");
+  NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
+  _arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
+  ARC_RELEASE(string);
+  
+  return (_arguments ? YES : NO);
+}
+
+@end

+ 56 - 0
GCDWebServer.xcodeproj/project.pbxproj

@@ -38,6 +38,20 @@
 		E22112991690B7AA0048D2B2 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E22112981690B7AA0048D2B2 /* CFNetwork.framework */; };
 		E221129B1690B7B10048D2B2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129A1690B7B10048D2B2 /* UIKit.framework */; };
 		E221129D1690B7BA0048D2B2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */; };
+		E2A0E7ED18F1D03700C580B1 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */; };
+		E2A0E7EE18F1D03700C580B1 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */; };
+		E2A0E7F118F1D12E00C580B1 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7F018F1D12E00C580B1 /* GCDWebServerFileResponse.m */; };
+		E2A0E7F218F1D12E00C580B1 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7F018F1D12E00C580B1 /* GCDWebServerFileResponse.m */; };
+		E2A0E7F518F1D1E500C580B1 /* GCDWebServerStreamResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7F418F1D1E500C580B1 /* GCDWebServerStreamResponse.m */; };
+		E2A0E7F618F1D1E500C580B1 /* GCDWebServerStreamResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7F418F1D1E500C580B1 /* GCDWebServerStreamResponse.m */; };
+		E2A0E7F918F1D24700C580B1 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7F818F1D24700C580B1 /* GCDWebServerDataRequest.m */; };
+		E2A0E7FA18F1D24700C580B1 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7F818F1D24700C580B1 /* GCDWebServerDataRequest.m */; };
+		E2A0E7FD18F1D36C00C580B1 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7FC18F1D36C00C580B1 /* GCDWebServerFileRequest.m */; };
+		E2A0E7FE18F1D36C00C580B1 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E7FC18F1D36C00C580B1 /* GCDWebServerFileRequest.m */; };
+		E2A0E80118F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E80018F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m */; };
+		E2A0E80218F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E80018F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m */; };
+		E2A0E80518F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E80418F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m */; };
+		E2A0E80618F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A0E80418F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m */; };
 		E2B0D4A718F13495009A7927 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E2B0D4A618F13495009A7927 /* libz.dylib */; };
 		E2B0D4A918F134A8009A7927 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E2B0D4A818F134A8009A7927 /* libz.dylib */; };
 		E2BE850A18E77ECA0061360B /* GCDWebUploader.bundle in Resources */ = {isa = PBXBuildFile; fileRef = E2BE850718E77ECA0061360B /* GCDWebUploader.bundle */; };
@@ -99,6 +113,20 @@
 		E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
 		E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
 		E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
+		E2A0E7EB18F1D03700C580B1 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataResponse.h; sourceTree = "<group>"; };
+		E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = "<group>"; };
+		E2A0E7EF18F1D12E00C580B1 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = "<group>"; };
+		E2A0E7F018F1D12E00C580B1 /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileResponse.m; sourceTree = "<group>"; };
+		E2A0E7F318F1D1E500C580B1 /* GCDWebServerStreamResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamResponse.h; sourceTree = "<group>"; };
+		E2A0E7F418F1D1E500C580B1 /* GCDWebServerStreamResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamResponse.m; sourceTree = "<group>"; };
+		E2A0E7F718F1D24700C580B1 /* GCDWebServerDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataRequest.h; sourceTree = "<group>"; };
+		E2A0E7F818F1D24700C580B1 /* GCDWebServerDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataRequest.m; sourceTree = "<group>"; };
+		E2A0E7FB18F1D36C00C580B1 /* GCDWebServerFileRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileRequest.h; sourceTree = "<group>"; };
+		E2A0E7FC18F1D36C00C580B1 /* GCDWebServerFileRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileRequest.m; sourceTree = "<group>"; };
+		E2A0E7FF18F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerMultiPartFormRequest.h; sourceTree = "<group>"; };
+		E2A0E80018F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerMultiPartFormRequest.m; sourceTree = "<group>"; };
+		E2A0E80318F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerURLEncodedFormRequest.h; sourceTree = "<group>"; };
+		E2A0E80418F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerURLEncodedFormRequest.m; sourceTree = "<group>"; };
 		E2B0D4A618F13495009A7927 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
 		E2B0D4A818F134A8009A7927 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk/usr/lib/libz.dylib; sourceTree = DEVELOPER_DIR; };
 		E2BE850718E77ECA0061360B /* GCDWebUploader.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = GCDWebUploader.bundle; sourceTree = "<group>"; };
@@ -163,11 +191,25 @@
 				E221127D1690B63A0048D2B2 /* GCDWebServer.m */,
 				E221127E1690B63A0048D2B2 /* GCDWebServerConnection.h */,
 				E221127F1690B63A0048D2B2 /* GCDWebServerConnection.m */,
+				E2A0E7F718F1D24700C580B1 /* GCDWebServerDataRequest.h */,
+				E2A0E7F818F1D24700C580B1 /* GCDWebServerDataRequest.m */,
+				E2A0E7EB18F1D03700C580B1 /* GCDWebServerDataResponse.h */,
+				E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */,
+				E2A0E7FB18F1D36C00C580B1 /* GCDWebServerFileRequest.h */,
+				E2A0E7FC18F1D36C00C580B1 /* GCDWebServerFileRequest.m */,
+				E2A0E7EF18F1D12E00C580B1 /* GCDWebServerFileResponse.h */,
+				E2A0E7F018F1D12E00C580B1 /* GCDWebServerFileResponse.m */,
+				E2A0E7FF18F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.h */,
+				E2A0E80018F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m */,
 				E22112801690B63A0048D2B2 /* GCDWebServerPrivate.h */,
 				E22112811690B63A0048D2B2 /* GCDWebServerRequest.h */,
 				E22112821690B63A0048D2B2 /* GCDWebServerRequest.m */,
 				E22112831690B63A0048D2B2 /* GCDWebServerResponse.h */,
 				E22112841690B63A0048D2B2 /* GCDWebServerResponse.m */,
+				E2A0E7F318F1D1E500C580B1 /* GCDWebServerStreamResponse.h */,
+				E2A0E7F418F1D1E500C580B1 /* GCDWebServerStreamResponse.m */,
+				E2A0E80318F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.h */,
+				E2A0E80418F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m */,
 			);
 			path = CGDWebServer;
 			sourceTree = "<group>";
@@ -308,11 +350,18 @@
 			buildActionMask = 2147483647;
 			files = (
 				E22112851690B63A0048D2B2 /* GCDWebServer.m in Sources */,
+				E2A0E7FD18F1D36C00C580B1 /* GCDWebServerFileRequest.m in Sources */,
 				E22112871690B63A0048D2B2 /* GCDWebServerConnection.m in Sources */,
+				E2A0E80518F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m in Sources */,
+				E2A0E7F918F1D24700C580B1 /* GCDWebServerDataRequest.m in Sources */,
 				E22112891690B63A0048D2B2 /* GCDWebServerRequest.m in Sources */,
+				E2A0E7ED18F1D03700C580B1 /* GCDWebServerDataResponse.m in Sources */,
+				E2A0E7F518F1D1E500C580B1 /* GCDWebServerStreamResponse.m in Sources */,
+				E2A0E80118F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m in Sources */,
 				E221128B1690B63A0048D2B2 /* GCDWebServerResponse.m in Sources */,
 				E2BE850C18E785940061360B /* GCDWebUploader.m in Sources */,
 				E221128F1690B6470048D2B2 /* main.m in Sources */,
+				E2A0E7F118F1D12E00C580B1 /* GCDWebServerFileResponse.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -321,11 +370,18 @@
 			buildActionMask = 2147483647;
 			files = (
 				E22112861690B63A0048D2B2 /* GCDWebServer.m in Sources */,
+				E2A0E7EE18F1D03700C580B1 /* GCDWebServerDataResponse.m in Sources */,
+				E2A0E80218F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m in Sources */,
 				E22112881690B63A0048D2B2 /* GCDWebServerConnection.m in Sources */,
 				E221128A1690B63A0048D2B2 /* GCDWebServerRequest.m in Sources */,
 				E221128C1690B63A0048D2B2 /* GCDWebServerResponse.m in Sources */,
+				E2A0E7FA18F1D24700C580B1 /* GCDWebServerDataRequest.m in Sources */,
+				E2A0E80618F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m in Sources */,
 				E2BE850B18E77ECA0061360B /* GCDWebUploader.m in Sources */,
 				E22112951690B64F0048D2B2 /* AppDelegate.m in Sources */,
+				E2A0E7FE18F1D36C00C580B1 /* GCDWebServerFileRequest.m in Sources */,
+				E2A0E7F618F1D1E500C580B1 /* GCDWebServerStreamResponse.m in Sources */,
+				E2A0E7F218F1D12E00C580B1 /* GCDWebServerFileResponse.m in Sources */,
 				E22112971690B64F0048D2B2 /* main.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 5 - 0
GCDWebUploader/GCDWebUploader.m

@@ -33,6 +33,11 @@
 #endif
 
 #import "GCDWebUploader.h"
+#import "GCDWebServerDataRequest.h"
+#import "GCDWebServerMultiPartFormRequest.h"
+#import "GCDWebServerURLEncodedFormRequest.h"
+#import "GCDWebServerDataResponse.h"
+#import "GCDWebServerFileResponse.h"
 
 @interface GCDWebUploader () {
 @private

+ 3 - 0
Mac/main.m

@@ -26,6 +26,9 @@
  */
 
 #import "GCDWebUploader.h"
+#import "GCDWebServerDataRequest.h"
+#import "GCDWebServerURLEncodedFormRequest.h"
+#import "GCDWebServerDataResponse.h"
 
 int main(int argc, const char* argv[]) {
   BOOL success = NO;

+ 1 - 1
README.md

@@ -2,7 +2,7 @@ Overview
 ========
 
 GCDWebServer is a lightweight GCD based HTTP 1.1 server designed to be embedded in Mac & iOS apps. It was written from scratch with the following goals in mind:
-* Easy to use and understand: only 4 main classes and less than 10 source code files
+* Easy to use and understand: only 4 core classes to deal with
 * Well designed API for easy integration and customization
 * Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for maximum performance and concurrency
 * Support for streaming large HTTP bodies for requests and responses to minimize memory usage