diff --git a/CGDWebServer/GCDWebServer.m b/CGDWebServer/GCDWebServer.m index 53819fb..a8cab82 100644 --- a/CGDWebServer/GCDWebServer.m +++ b/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) { diff --git a/CGDWebServer/GCDWebServerDataRequest.h b/CGDWebServer/GCDWebServerDataRequest.h new file mode 100644 index 0000000..2cb923f --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerDataRequest.m b/CGDWebServer/GCDWebServerDataRequest.m new file mode 100644 index 0000000..1bfab58 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerDataResponse.h b/CGDWebServer/GCDWebServerDataResponse.h new file mode 100644 index 0000000..05ecdb5 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerDataResponse.m b/CGDWebServer/GCDWebServerDataResponse.m new file mode 100644 index 0000000..b36d2df --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerFileRequest.h b/CGDWebServer/GCDWebServerFileRequest.h new file mode 100644 index 0000000..7ef3ac2 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerFileRequest.m b/CGDWebServer/GCDWebServerFileRequest.m new file mode 100644 index 0000000..2a5c1c7 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerFileResponse.h b/CGDWebServer/GCDWebServerFileResponse.h new file mode 100644 index 0000000..ec296d7 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerFileResponse.m b/CGDWebServer/GCDWebServerFileResponse.m new file mode 100644 index 0000000..2631c51 --- /dev/null +++ b/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 + +#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 diff --git a/CGDWebServer/GCDWebServerMultiPartFormRequest.h b/CGDWebServer/GCDWebServerMultiPartFormRequest.h new file mode 100644 index 0000000..a3ea6b7 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerMultiPartFormRequest.m b/CGDWebServer/GCDWebServerMultiPartFormRequest.m new file mode 100644 index 0000000..52f5933 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerPrivate.h b/CGDWebServer/GCDWebServerPrivate.h index ec8d823..4fe60ae 100644 --- a/CGDWebServer/GCDWebServerPrivate.h +++ b/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 diff --git a/CGDWebServer/GCDWebServerRequest.h b/CGDWebServer/GCDWebServerRequest.h index fce7752..5dcc645 100644 --- a/CGDWebServer/GCDWebServerRequest.h +++ b/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 diff --git a/CGDWebServer/GCDWebServerRequest.m b/CGDWebServer/GCDWebServerRequest.m index df77c55..9a03b5b 100644 --- a/CGDWebServer/GCDWebServerRequest.m +++ b/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 diff --git a/CGDWebServer/GCDWebServerResponse.h b/CGDWebServer/GCDWebServerResponse.h index 0d89e43..e8469bc 100644 --- a/CGDWebServer/GCDWebServerResponse.h +++ b/CGDWebServer/GCDWebServerResponse.h @@ -27,8 +27,6 @@ #import -typedef NSData* (^GCDWebServerStreamBlock)(NSError** error); - @protocol GCDWebServerBodyReader - (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 diff --git a/CGDWebServer/GCDWebServerResponse.m b/CGDWebServer/GCDWebServerResponse.m index a78e17a..770d839 100644 --- a/CGDWebServer/GCDWebServerResponse.m +++ b/CGDWebServer/GCDWebServerResponse.m @@ -25,14 +25,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import #import #import "GCDWebServerPrivate.h" #define kZlibErrorDomain @"ZlibErrorDomain" #define kGZipInitialBufferSize (256 * 1024) -#define kFileReadBufferSize (32 * 1024) @interface GCDWebServerBodyEncoder : NSObject - (id)initWithResponse:(GCDWebServerResponse*)response reader:(id)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 diff --git a/CGDWebServer/GCDWebServerStreamResponse.h b/CGDWebServer/GCDWebServerStreamResponse.h new file mode 100644 index 0000000..62ea9b0 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerStreamResponse.m b/CGDWebServer/GCDWebServerStreamResponse.m new file mode 100644 index 0000000..9ab33e0 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerURLEncodedFormRequest.h b/CGDWebServer/GCDWebServerURLEncodedFormRequest.h new file mode 100644 index 0000000..c369da3 --- /dev/null +++ b/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 diff --git a/CGDWebServer/GCDWebServerURLEncodedFormRequest.m b/CGDWebServer/GCDWebServerURLEncodedFormRequest.m new file mode 100644 index 0000000..e2fe12a --- /dev/null +++ b/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 diff --git a/GCDWebServer.xcodeproj/project.pbxproj b/GCDWebServer.xcodeproj/project.pbxproj index b076fcc..92090a0 100644 --- a/GCDWebServer.xcodeproj/project.pbxproj +++ b/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 = ""; }; + E2A0E7EC18F1D03700C580B1 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = ""; }; + E2A0E7EF18F1D12E00C580B1 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = ""; }; + E2A0E7F018F1D12E00C580B1 /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileResponse.m; sourceTree = ""; }; + E2A0E7F318F1D1E500C580B1 /* GCDWebServerStreamResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamResponse.h; sourceTree = ""; }; + E2A0E7F418F1D1E500C580B1 /* GCDWebServerStreamResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamResponse.m; sourceTree = ""; }; + E2A0E7F718F1D24700C580B1 /* GCDWebServerDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataRequest.h; sourceTree = ""; }; + E2A0E7F818F1D24700C580B1 /* GCDWebServerDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataRequest.m; sourceTree = ""; }; + E2A0E7FB18F1D36C00C580B1 /* GCDWebServerFileRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileRequest.h; sourceTree = ""; }; + E2A0E7FC18F1D36C00C580B1 /* GCDWebServerFileRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileRequest.m; sourceTree = ""; }; + E2A0E7FF18F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerMultiPartFormRequest.h; sourceTree = ""; }; + E2A0E80018F1D3DE00C580B1 /* GCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerMultiPartFormRequest.m; sourceTree = ""; }; + E2A0E80318F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerURLEncodedFormRequest.h; sourceTree = ""; }; + E2A0E80418F1D4A700C580B1 /* GCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerURLEncodedFormRequest.m; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; @@ -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; diff --git a/GCDWebUploader/GCDWebUploader.m b/GCDWebUploader/GCDWebUploader.m index 0445d72..ee7f377 100644 --- a/GCDWebUploader/GCDWebUploader.m +++ b/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 diff --git a/Mac/main.m b/Mac/main.m index 983ac9c..b3862c5 100644 --- a/Mac/main.m +++ b/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; diff --git a/README.md b/README.md index 5fd3839..e29bd28 100644 --- a/README.md +++ b/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