123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- /*
- 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 <zlib.h>
- #import "GCDWebServerPrivate.h"
- #define kZlibErrorDomain @"ZlibErrorDomain"
- #define kGZipInitialBufferSize (256 * 1024)
- @interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
- - (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer;
- @end
- @interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
- @end
- @interface GCDWebServerBodyDecoder () {
- @private
- GCDWebServerRequest* __unsafe_unretained _request;
- id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
- }
- @end
- @implementation GCDWebServerBodyDecoder
- - (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer {
- if ((self = [super init])) {
- _request = request;
- _writer = writer;
- }
- return self;
- }
- - (BOOL)open:(NSError**)error {
- return [_writer open:error];
- }
- - (BOOL)writeData:(NSData*)data error:(NSError**)error {
- return [_writer writeData:data error:error];
- }
- - (BOOL)close:(NSError**)error {
- return [_writer close:error];
- }
- @end
- @interface GCDWebServerGZipDecoder () {
- @private
- z_stream _stream;
- BOOL _finished;
- }
- @end
- @implementation GCDWebServerGZipDecoder
- - (BOOL)open:(NSError**)error {
- int result = inflateInit2(&_stream, 15 + 16);
- if (result != Z_OK) {
- *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
- return NO;
- }
- if (![super open:error]) {
- deflateEnd(&_stream);
- return NO;
- }
- return YES;
- }
- - (BOOL)writeData:(NSData*)data error:(NSError**)error {
- DCHECK(!_finished);
- _stream.next_in = (Bytef*)data.bytes;
- _stream.avail_in = (uInt)data.length;
- NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
- if (decodedData == nil) {
- DNOT_REACHED();
- return NO;
- }
- NSUInteger length = 0;
- while (1) {
- NSUInteger maxLength = decodedData.length - length;
- _stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length);
- _stream.avail_out = (uInt)maxLength;
- int result = inflate(&_stream, Z_NO_FLUSH);
- if ((result != Z_OK) && (result != Z_STREAM_END)) {
- ARC_RELEASE(decodedData);
- *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
- return NO;
- }
- length += maxLength - _stream.avail_out;
- if (_stream.avail_out > 0) {
- if (result == Z_STREAM_END) {
- _finished = YES;
- }
- break;
- }
- decodedData.length = 2 * decodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
- }
- decodedData.length = length;
- BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet
- ARC_RELEASE(decodedData);
- return success;
- }
- - (BOOL)close:(NSError**)error {
- DCHECK(_finished);
- inflateEnd(&_stream);
- return [super close:error];
- }
- @end
- @interface GCDWebServerRequest () {
- @private
- NSString* _method;
- NSURL* _url;
- NSDictionary* _headers;
- NSString* _path;
- NSDictionary* _query;
- NSString* _type;
- BOOL _chunked;
- NSUInteger _length;
- NSDate* _modifiedSince;
- NSString* _noneMatch;
- NSRange _range;
- BOOL _gzipAccepted;
-
- BOOL _opened;
- NSMutableArray* _decoders;
- id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
- }
- @end
- @implementation GCDWebServerRequest : NSObject
- @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch,
- byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked;
- - (instancetype)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(GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]));
- _chunked = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"];
- NSString* lengthHeader = [_headers objectForKey:@"Content-Length"];
- if (lengthHeader) {
- NSInteger length = [lengthHeader integerValue];
- if (_chunked || (length < 0)) {
- DNOT_REACHED();
- ARC_RELEASE(self);
- return nil;
- }
- _length = length;
- if (_type == nil) {
- _type = kGCDWebServerDefaultMimeType;
- }
- } else if (_chunked) {
- if (_type == nil) {
- _type = kGCDWebServerDefaultMimeType;
- }
- _length = NSNotFound;
- } else {
- if (_type) {
- DNOT_REACHED();
- ARC_RELEASE(self);
- return nil;
- }
- _length = NSNotFound;
- }
-
- NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"];
- if (modifiedHeader) {
- _modifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy];
- }
- _noneMatch = ARC_RETAIN([_headers objectForKey:@"If-None-Match"]);
-
- _range = NSMakeRange(NSNotFound, 0);
- NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]);
- 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);
- }
- }
-
- if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) {
- _gzipAccepted = YES;
- }
-
- _decoders = [[NSMutableArray alloc] init];
- }
- return self;
- }
- - (void)dealloc {
- ARC_RELEASE(_method);
- ARC_RELEASE(_url);
- ARC_RELEASE(_headers);
- ARC_RELEASE(_path);
- ARC_RELEASE(_query);
- ARC_RELEASE(_type);
- ARC_RELEASE(_modifiedSince);
- ARC_RELEASE(_noneMatch);
- ARC_RELEASE(_decoders);
-
- ARC_DEALLOC(super);
- }
- - (BOOL)hasBody {
- return _type ? YES : NO;
- }
- - (BOOL)hasByteRange {
- return GCDWebServerIsValidByteRange(_range);
- }
- - (BOOL)open:(NSError**)error {
- return YES;
- }
- - (BOOL)writeData:(NSData*)data error:(NSError**)error {
- return YES;
- }
- - (BOOL)close:(NSError**)error {
- return YES;
- }
- - (void)prepareForWriting {
- _writer = self;
- if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) {
- GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer];
- [_decoders addObject:decoder];
- ARC_RELEASE(decoder);
- _writer = decoder;
- }
- }
- - (BOOL)performOpen:(NSError**)error {
- DCHECK(_type);
- DCHECK(_writer);
- if (_opened) {
- DNOT_REACHED();
- return NO;
- }
- _opened = YES;
- return [_writer open:error];
- }
- - (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
- DCHECK(_opened);
- return [_writer writeData:data error:error];
- }
- - (BOOL)performClose:(NSError**)error {
- DCHECK(_opened);
- return [_writer close:error];
- }
- - (NSString*)description {
- NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
- for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
- [description appendFormat:@"\n %@ = %@", argument, [_query objectForKey:argument]];
- }
- [description appendString:@"\n"];
- for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
- [description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]];
- }
- return description;
- }
- @end
|