123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- /*
- 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
- };
- @interface GCDWebServerRequest () {
- @private
- NSString* _method;
- NSURL* _url;
- NSDictionary* _headers;
- NSString* _path;
- NSDictionary* _query;
- NSString* _type;
- NSUInteger _length;
- NSRange _range;
- }
- @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;
- - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
- if ((self = [super init])) {
- _method = [method copy];
- _url = ARC_RETAIN(url);
- _headers = ARC_RETAIN(headers);
- _path = [path copy];
- _query = ARC_RETAIN(query);
-
- _type = ARC_RETAIN([_headers objectForKey:@"Content-Type"]);
- NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
- if (_type) {
- NSInteger length = [lengthHeader integerValue];
- if ((lengthHeader == nil) || (length < 0)) {
- DNOT_REACHED();
- ARC_RELEASE(self);
- return nil;
- }
- _length = length;
- } else if (lengthHeader) {
- DNOT_REACHED();
- ARC_RELEASE(self);
- return nil;
- }
-
- _range = NSMakeRange(NSNotFound, 0);
- NSString* rangeHeader = [[_headers objectForKey:@"Range"] lowercaseString];
- if (rangeHeader) {
- if ([rangeHeader hasPrefix:@"bytes="]) {
- NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
- if (components.count == 1) {
- components = [[components firstObject] componentsSeparatedByString:@"-"];
- if (components.count == 2) {
- NSString* startString = [components objectAtIndex:0];
- NSInteger startValue = [startString integerValue];
- NSString* endString = [components objectAtIndex:1];
- NSInteger endValue = [endString integerValue];
- if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
- _range.location = startValue;
- _range.length = endValue - startValue + 1;
- } else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
- _range.location = startValue;
- _range.length = NSUIntegerMax;
- } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
- _range.location = NSNotFound;
- _range.length = endValue;
- }
- }
- }
- }
- if ((_range.location == NSNotFound) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid
- LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
- }
- }
- }
- return self;
- }
- - (void)dealloc {
- ARC_RELEASE(_method);
- ARC_RELEASE(_url);
- ARC_RELEASE(_headers);
- ARC_RELEASE(_path);
- ARC_RELEASE(_query);
- ARC_RELEASE(_type);
-
- ARC_DEALLOC(super);
- }
- - (BOOL)hasBody {
- return _type ? YES : NO;
- }
- @end
- @implementation GCDWebServerRequest (Subclassing)
- - (BOOL)open {
- [self doesNotRecognizeSelector:_cmd];
- return NO;
- }
- - (NSInteger)write:(const void*)buffer maxLength:(NSUInteger)length {
- [self doesNotRecognizeSelector:_cmd];
- return -1;
- }
- - (BOOL)close {
- [self doesNotRecognizeSelector:_cmd];
- return NO;
- }
- @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
|