| /* |
| 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 <TargetConditionals.h> |
| #import <netdb.h> |
| #if !TARGET_OS_IPHONE |
| #import <libkern/OSAtomic.h> |
| #endif |
| |
| #import "GCDWebServerPrivate.h" |
| |
| #define kHeadersReadBuffer 1024 |
| |
| typedef void (^ReadBufferCompletionBlock)(dispatch_data_t buffer); |
| typedef void (^ReadDataCompletionBlock)(NSData* data); |
| typedef void (^ReadHeadersCompletionBlock)(NSData* extraData); |
| typedef void (^ReadBodyCompletionBlock)(BOOL success); |
| |
| typedef void (^WriteBufferCompletionBlock)(BOOL success); |
| typedef void (^WriteDataCompletionBlock)(BOOL success); |
| typedef void (^WriteHeadersCompletionBlock)(BOOL success); |
| typedef void (^WriteBodyCompletionBlock)(BOOL success); |
| |
| static NSData* _CRLFData = nil; |
| static NSData* _CRLFCRLFData = nil; |
| static NSData* _continueData = nil; |
| static NSData* _lastChunkData = nil; |
| #if !TARGET_OS_IPHONE |
| static int32_t _connectionCounter = 0; |
| #endif |
| |
| @interface GCDWebServerConnection () { |
| @private |
| GCDWebServer* _server; |
| NSData* _localAddress; |
| NSData* _remoteAddress; |
| CFSocketNativeHandle _socket; |
| NSUInteger _bytesRead; |
| NSUInteger _bytesWritten; |
| BOOL _virtualHEAD; |
| |
| CFHTTPMessageRef _requestMessage; |
| GCDWebServerRequest* _request; |
| GCDWebServerHandler* _handler; |
| CFHTTPMessageRef _responseMessage; |
| GCDWebServerResponse* _response; |
| NSInteger _statusCode; |
| |
| #if !TARGET_OS_IPHONE |
| NSUInteger _connectionIndex; |
| NSString* _requestPath; |
| int _requestFD; |
| NSString* _responsePath; |
| int _responseFD; |
| #endif |
| } |
| @end |
| |
| @implementation GCDWebServerConnection (Read) |
| |
| - (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block { |
| dispatch_read(_socket, length, kGCDWebServerGCDQueue, ^(dispatch_data_t buffer, int error) { |
| |
| @autoreleasepool { |
| if (error == 0) { |
| size_t size = dispatch_data_get_size(buffer); |
| if (size > 0) { |
| LOG_DEBUG(@"Connection received %zu bytes on socket %i", size, _socket); |
| _bytesRead += size; |
| [self didUpdateBytesRead]; |
| #if !TARGET_OS_IPHONE |
| if (_requestFD > 0) { |
| bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { |
| return (write(_requestFD, chunkBytes, chunkSize) == (ssize_t)chunkSize); |
| }); |
| if (!success) { |
| LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno); |
| close(_requestFD); |
| _requestFD = 0; |
| } |
| } |
| #endif |
| block(buffer); |
| } else { |
| if (_bytesRead > 0) { |
| LOG_ERROR(@"No more data available on socket %i", _socket); |
| } else { |
| LOG_WARNING(@"No data received from socket %i", _socket); |
| } |
| block(NULL); |
| } |
| } else { |
| LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error); |
| block(NULL); |
| } |
| } |
| |
| }); |
| } |
| |
| - (void)_readDataWithCompletionBlock:(ReadDataCompletionBlock)block { |
| [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { |
| |
| if (buffer) { |
| NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dispatch_data_get_size(buffer)]; |
| dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { |
| [data appendBytes:chunkBytes length:chunkSize]; |
| return true; |
| }); |
| block(data); |
| ARC_RELEASE(data); |
| } else { |
| block(nil); |
| } |
| |
| }]; |
| } |
| |
| - (void)_readHeadersWithCompletionBlock:(ReadHeadersCompletionBlock)block { |
| DCHECK(_requestMessage); |
| [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { |
| |
| if (buffer) { |
| NSMutableData* data = [NSMutableData dataWithCapacity:kHeadersReadBuffer]; |
| dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { |
| [data appendBytes:chunkBytes length:chunkSize]; |
| return true; |
| }); |
| NSRange range = [data rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, data.length)]; |
| if (range.location == NSNotFound) { |
| if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, data.length)) { |
| [self _readHeadersWithCompletionBlock:block]; |
| } else { |
| LOG_ERROR(@"Failed appending request headers data from socket %i", _socket); |
| block(nil); |
| } |
| } else { |
| NSUInteger length = range.location + range.length; |
| if (CFHTTPMessageAppendBytes(_requestMessage, data.bytes, length)) { |
| if (CFHTTPMessageIsHeaderComplete(_requestMessage)) { |
| block([data subdataWithRange:NSMakeRange(length, data.length - length)]); |
| } else { |
| LOG_ERROR(@"Failed parsing request headers from socket %i", _socket); |
| block(nil); |
| } |
| } else { |
| LOG_ERROR(@"Failed appending request headers data from socket %i", _socket); |
| block(nil); |
| } |
| } |
| } else { |
| block(nil); |
| } |
| |
| }]; |
| } |
| |
| - (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block { |
| DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]); |
| [self _readBufferWithLength:length completionBlock:^(dispatch_data_t buffer) { |
| |
| if (buffer) { |
| if (dispatch_data_get_size(buffer) <= length) { |
| bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { |
| NSData* data = [NSData dataWithBytesNoCopy:(void*)chunkBytes length:chunkSize freeWhenDone:NO]; |
| NSError* error = nil; |
| if (![_request performWriteData:data error:&error]) { |
| LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); |
| return false; |
| } |
| return true; |
| }); |
| if (success) { |
| NSUInteger remainingLength = length - dispatch_data_get_size(buffer); |
| if (remainingLength) { |
| [self _readBodyWithRemainingLength:remainingLength completionBlock:block]; |
| } else { |
| block(YES); |
| } |
| } else { |
| block(NO); |
| } |
| } else { |
| LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket); |
| block(NO); |
| DNOT_REACHED(); |
| } |
| } else { |
| block(NO); |
| } |
| |
| }]; |
| } |
| |
| static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { |
| char buffer[size + 1]; |
| bcopy(bytes, buffer, size); |
| buffer[size] = 0; |
| char* end = NULL; |
| long result = strtol(buffer, &end, 16); |
| return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound); |
| } |
| |
| - (void)_readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block { |
| DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]); |
| |
| while (1) { |
| NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)]; |
| if (range.location == NSNotFound) { |
| break; |
| } |
| NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions |
| NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location); |
| if (length != NSNotFound) { |
| if (length) { |
| if (chunkData.length < range.location + range.length + length + 2) { |
| break; |
| } |
| const char* ptr = (char*)chunkData.bytes + range.location + range.length + length; |
| if ((*ptr == '\r') && (*(ptr + 1) == '\n')) { |
| NSError* error = nil; |
| if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) { |
| [chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0]; |
| } else { |
| LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); |
| block(NO); |
| return; |
| } |
| } else { |
| LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket); |
| block(NO); |
| return; |
| } |
| } else { |
| NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers |
| if (trailerRange.location != NSNotFound) { |
| block(YES); |
| return; |
| } |
| } |
| } else { |
| LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket); |
| block(NO); |
| return; |
| } |
| } |
| |
| [self _readBufferWithLength:SIZE_T_MAX completionBlock:^(dispatch_data_t buffer) { |
| |
| if (buffer) { |
| dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { |
| [chunkData appendBytes:chunkBytes length:chunkSize]; |
| return true; |
| }); |
| [self _readNextBodyChunk:chunkData completionBlock:block]; |
| } else { |
| block(NO); |
| } |
| |
| }]; |
| } |
| |
| @end |
| |
| @implementation GCDWebServerConnection (Write) |
| |
| - (void)_writeBuffer:(dispatch_data_t)buffer withCompletionBlock:(WriteBufferCompletionBlock)block { |
| size_t size = dispatch_data_get_size(buffer); |
| #if !TARGET_OS_IPHONE |
| ARC_DISPATCH_RETAIN(buffer); |
| #endif |
| dispatch_write(_socket, buffer, kGCDWebServerGCDQueue, ^(dispatch_data_t data, int error) { |
| |
| @autoreleasepool { |
| if (error == 0) { |
| DCHECK(data == NULL); |
| LOG_DEBUG(@"Connection sent %zu bytes on socket %i", size, _socket); |
| _bytesWritten += size; |
| [self didUpdateBytesWritten]; |
| #if !TARGET_OS_IPHONE |
| if (_responseFD > 0) { |
| bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { |
| return (write(_responseFD, chunkBytes, chunkSize) == (ssize_t)chunkSize); |
| }); |
| if (!success) { |
| LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno); |
| close(_responseFD); |
| _responseFD = 0; |
| } |
| } |
| #endif |
| block(YES); |
| } else { |
| LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error); |
| block(NO); |
| } |
| } |
| #if !TARGET_OS_IPHONE |
| ARC_DISPATCH_RELEASE(buffer); |
| #endif |
| |
| }); |
| } |
| |
| - (void)_writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block { |
| #if !__has_feature(objc_arc) |
| [data retain]; |
| #endif |
| dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, kGCDWebServerGCDQueue, ^{ |
| #if __has_feature(objc_arc) |
| [data self]; // Keeps ARC from releasing data too early |
| #else |
| [data release]; |
| #endif |
| }); |
| [self _writeBuffer:buffer withCompletionBlock:block]; |
| ARC_DISPATCH_RELEASE(buffer); |
| } |
| |
| - (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block { |
| DCHECK(_responseMessage); |
| CFDataRef message = CFHTTPMessageCopySerializedMessage(_responseMessage); |
| [self _writeData:(ARC_BRIDGE NSData*)message withCompletionBlock:block]; |
| CFRelease(message); |
| } |
| |
| - (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block { |
| DCHECK([_response hasBody]); |
| NSError* error = nil; |
| NSData* data = [_response performReadData:&error]; |
| if (data) { |
| if (data.length) { |
| if (_response.usesChunkedTransferEncoding) { |
| const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String]; |
| size_t hexLength = strlen(hexString); |
| NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)]; |
| if (chunk == nil) { |
| LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error); |
| block(NO); |
| return; |
| } |
| char* ptr = (char*)[(NSMutableData*)chunk mutableBytes]; |
| bcopy(hexString, ptr, hexLength); |
| ptr += hexLength; |
| *ptr++ = '\r'; |
| *ptr++ = '\n'; |
| bcopy(data.bytes, ptr, data.length); |
| ptr += data.length; |
| *ptr++ = '\r'; |
| *ptr = '\n'; |
| data = chunk; |
| } |
| [self _writeData:data withCompletionBlock:^(BOOL success) { |
| |
| if (success) { |
| [self _writeBodyWithCompletionBlock:block]; |
| } else { |
| block(NO); |
| } |
| |
| }]; |
| } else { |
| if (_response.usesChunkedTransferEncoding) { |
| [self _writeData:_lastChunkData withCompletionBlock:^(BOOL success) { |
| |
| block(success); |
| |
| }]; |
| } else { |
| block(YES); |
| } |
| } |
| } else { |
| LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error); |
| block(NO); |
| } |
| } |
| |
| @end |
| |
| @implementation GCDWebServerConnection |
| |
| @synthesize server=_server, localAddressData=_localAddress, remoteAddressData=_remoteAddress, totalBytesRead=_bytesRead, totalBytesWritten=_bytesWritten; |
| |
| + (void)initialize { |
| if (_CRLFData == nil) { |
| _CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2]; |
| DCHECK(_CRLFData); |
| } |
| if (_CRLFCRLFData == nil) { |
| _CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; |
| DCHECK(_CRLFCRLFData); |
| } |
| if (_continueData == nil) { |
| CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1); |
| #if __has_feature(objc_arc) |
| _continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); |
| #else |
| _continueData = (NSData*)CFHTTPMessageCopySerializedMessage(message); |
| #endif |
| CFRelease(message); |
| DCHECK(_continueData); |
| } |
| if (_lastChunkData == nil) { |
| _lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5]; |
| } |
| } |
| |
| - (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode { |
| _statusCode = statusCode; |
| _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close")); |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (ARC_BRIDGE CFStringRef)[[_server class] serverName]); |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822([NSDate date])); |
| } |
| |
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html |
| - (void)_processRequest { |
| DCHECK(_responseMessage == NULL); |
| BOOL hasBody = NO; |
| |
| GCDWebServerResponse* response = [self processRequest:_request withBlock:_handler.processBlock]; |
| if (response) { |
| response = [self replaceResponse:response forRequest:_request]; |
| if (response) { |
| if ([response hasBody]) { |
| [response prepareForReading]; |
| hasBody = !_virtualHEAD; |
| } |
| NSError* error = nil; |
| if (hasBody && ![response performOpen:&error]) { |
| LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error); |
| } else { |
| _response = ARC_RETAIN(response); |
| } |
| } |
| } |
| |
| if (_response) { |
| [self _initializeResponseHeadersWithStatusCode:_response.statusCode]; |
| if (_response.lastModifiedDate) { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822(_response.lastModifiedDate)); |
| } |
| if (_response.eTag) { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (ARC_BRIDGE CFStringRef)_response.eTag); |
| } |
| if ((_response.statusCode >= 200) && (_response.statusCode < 300)) { |
| if (_response.cacheControlMaxAge > 0) { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]); |
| } else { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache")); |
| } |
| } |
| if (_response.contentType != nil) { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (ARC_BRIDGE CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType)); |
| } |
| if (_response.contentLength != NSNotFound) { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (ARC_BRIDGE CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]); |
| } |
| if (_response.usesChunkedTransferEncoding) { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked")); |
| } |
| [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) { |
| CFHTTPMessageSetHeaderFieldValue(_responseMessage, (ARC_BRIDGE CFStringRef)key, (ARC_BRIDGE CFStringRef)obj); |
| }]; |
| [self _writeHeadersWithCompletionBlock:^(BOOL success) { |
| |
| if (success) { |
| if (hasBody) { |
| [self _writeBodyWithCompletionBlock:^(BOOL successInner) { |
| |
| [_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent |
| |
| }]; |
| } |
| } else if (hasBody) { |
| [_response performClose]; |
| } |
| |
| }]; |
| } else { |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| } |
| |
| } |
| |
| - (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData { |
| NSError* error = nil; |
| if (![_request performOpen:&error]) { |
| LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| return; |
| } |
| |
| if (initialData.length) { |
| if (![_request performWriteData:initialData error:&error]) { |
| LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); |
| if (![_request performClose:&error]) { |
| LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); |
| } |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| return; |
| } |
| length -= initialData.length; |
| } |
| |
| if (length) { |
| [self _readBodyWithRemainingLength:length completionBlock:^(BOOL success) { |
| |
| NSError* localError = nil; |
| if ([_request performClose:&localError]) { |
| [self _processRequest]; |
| } else { |
| LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| } |
| |
| }]; |
| } else { |
| if ([_request performClose:&error]) { |
| [self _processRequest]; |
| } else { |
| LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| } |
| } |
| } |
| |
| - (void)_readChunkedBodyWithInitialData:(NSData*)initialData { |
| NSError* error = nil; |
| if (![_request performOpen:&error]) { |
| LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| return; |
| } |
| |
| NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData]; |
| [self _readNextBodyChunk:chunkData completionBlock:^(BOOL success) { |
| |
| NSError* localError = nil; |
| if ([_request performClose:&localError]) { |
| [self _processRequest]; |
| } else { |
| LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| } |
| |
| }]; |
| ARC_RELEASE(chunkData); |
| } |
| |
| - (void)_readRequestHeaders { |
| _requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true); |
| [self _readHeadersWithCompletionBlock:^(NSData* extraData) { |
| |
| if (extraData) { |
| NSString* requestMethod = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestMethod(_requestMessage)); // Method verbs are case-sensitive and uppercase |
| if ([[_server class] shouldAutomaticallyMapHEADToGET] && [requestMethod isEqualToString:@"HEAD"]) { |
| requestMethod = @"GET"; |
| _virtualHEAD = YES; |
| } |
| NSURL* requestURL = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(_requestMessage)); |
| NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(ARC_BRIDGE_RELEASE(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash |
| NSString* queryString = requestURL ? ARC_BRIDGE_RELEASE(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped; |
| NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{}; |
| NSDictionary* requestHeaders = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyAllHeaderFields(_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones |
| if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) { |
| for (_handler in _server.handlers) { |
| _request = ARC_RETAIN(_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery)); |
| if (_request) { |
| break; |
| } |
| } |
| if (_request) { |
| if ([_request hasBody]) { |
| [_request prepareForWriting]; |
| if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) { |
| NSString* expectHeader = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyHeaderFieldValue(_requestMessage, CFSTR("Expect"))); |
| if (expectHeader) { |
| if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { |
| [self _writeData:_continueData withCompletionBlock:^(BOOL success) { |
| |
| if (success) { |
| if (_request.usesChunkedTransferEncoding) { |
| [self _readChunkedBodyWithInitialData:extraData]; |
| } else { |
| [self _readBodyWithLength:_request.contentLength initialData:extraData]; |
| } |
| } |
| |
| }]; |
| } else { |
| LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed]; |
| } |
| } else { |
| if (_request.usesChunkedTransferEncoding) { |
| [self _readChunkedBodyWithInitialData:extraData]; |
| } else { |
| [self _readBodyWithLength:_request.contentLength initialData:extraData]; |
| } |
| } |
| } else { |
| LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest]; |
| } |
| } else { |
| [self _processRequest]; |
| } |
| } else { |
| _request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery]; |
| DCHECK(_request); |
| [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_MethodNotAllowed]; |
| } |
| } else { |
| [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| DNOT_REACHED(); |
| } |
| } else { |
| [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; |
| } |
| |
| }]; |
| } |
| |
| - (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket { |
| if ((self = [super init])) { |
| _server = ARC_RETAIN(server); |
| _localAddress = ARC_RETAIN(localAddress); |
| _remoteAddress = ARC_RETAIN(remoteAddress); |
| _socket = socket; |
| |
| [self open]; |
| } |
| return self; |
| } |
| |
| static NSString* _StringFromAddressData(NSData* data) { |
| NSString* string = nil; |
| const struct sockaddr* addr = data.bytes; |
| char hostBuffer[NI_MAXHOST]; |
| char serviceBuffer[NI_MAXSERV]; |
| if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) { |
| string = [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer]; |
| } else { |
| DNOT_REACHED(); |
| } |
| return string; |
| } |
| |
| - (NSString*)localAddressString { |
| return _StringFromAddressData(_localAddress); |
| } |
| |
| - (NSString*)remoteAddressString { |
| return _StringFromAddressData(_remoteAddress); |
| } |
| |
| - (void)dealloc { |
| [self close]; |
| |
| ARC_RELEASE(_server); |
| ARC_RELEASE(_localAddress); |
| ARC_RELEASE(_remoteAddress); |
| |
| if (_requestMessage) { |
| CFRelease(_requestMessage); |
| } |
| ARC_RELEASE(_request); |
| |
| if (_responseMessage) { |
| CFRelease(_responseMessage); |
| } |
| ARC_RELEASE(_response); |
| |
| #if !TARGET_OS_IPHONE |
| ARC_RELEASE(_requestPath); |
| ARC_RELEASE(_responsePath); |
| #endif |
| |
| ARC_DEALLOC(super); |
| } |
| |
| @end |
| |
| @implementation GCDWebServerConnection (Subclassing) |
| |
| - (void)open { |
| LOG_DEBUG(@"Did open connection on socket %i", _socket); |
| |
| #if !TARGET_OS_IPHONE |
| if (_server.recordingEnabled) { |
| _connectionIndex = OSAtomicIncrement32(&_connectionCounter); |
| |
| _requestPath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]); |
| _requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY); |
| DCHECK(_requestFD > 0); |
| |
| _responsePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]); |
| _responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY); |
| DCHECK(_responseFD > 0); |
| } |
| #endif |
| |
| [self _readRequestHeaders]; |
| } |
| |
| - (void)didUpdateBytesRead { |
| ; |
| } |
| |
| - (void)didUpdateBytesWritten { |
| ; |
| } |
| |
| - (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block { |
| LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead); |
| GCDWebServerResponse* response = nil; |
| @try { |
| response = block(request); |
| } |
| @catch (NSException* exception) { |
| LOG_EXCEPTION(exception); |
| } |
| return response; |
| } |
| |
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 |
| static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) { |
| if ([requestETag isEqualToString:@"*"] && (!responseLastModified || !requestLastModified || ([responseLastModified compare:requestLastModified] != NSOrderedDescending))) { |
| return YES; |
| } else { |
| if ([responseETag isEqualToString:requestETag]) { |
| return YES; |
| } |
| if (responseLastModified && requestLastModified && ([responseLastModified compare:requestLastModified] != NSOrderedDescending)) { |
| return YES; |
| } |
| } |
| return NO; |
| } |
| |
| - (GCDWebServerResponse*)replaceResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request { |
| if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) { |
| NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed; |
| GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code]; |
| newResponse.cacheControlMaxAge = response.cacheControlMaxAge; |
| newResponse.lastModifiedDate = response.lastModifiedDate; |
| newResponse.eTag = response.eTag; |
| DCHECK(newResponse); |
| return newResponse; |
| } |
| return response; |
| } |
| |
| - (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode { |
| DCHECK(_responseMessage == NULL); |
| DCHECK((statusCode >= 400) && (statusCode < 600)); |
| [self _initializeResponseHeadersWithStatusCode:statusCode]; |
| [self _writeHeadersWithCompletionBlock:^(BOOL success) { |
| ; // Nothing more to do |
| }]; |
| LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket); |
| } |
| |
| - (void)close { |
| int result = close(_socket); |
| if (result != 0) { |
| LOG_ERROR(@"Failed closing socket %i for connection (%i): %s", _socket, errno, strerror(errno)); |
| } else { |
| LOG_DEBUG(@"Did close connection on socket %i", _socket); |
| } |
| |
| #if !TARGET_OS_IPHONE |
| if (_requestPath) { |
| BOOL success = NO; |
| NSError* error = nil; |
| if (_requestFD > 0) { |
| close(_requestFD); |
| NSString* name = [NSString stringWithFormat:@"%03lu-%@.request", (unsigned long)_connectionIndex, _virtualHEAD ? @"HEAD" : _request.method]; |
| success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; |
| } |
| if (!success) { |
| LOG_ERROR(@"Failed saving recorded request: %@", error); |
| DNOT_REACHED(); |
| } |
| unlink([_requestPath fileSystemRepresentation]); |
| } |
| |
| if (_responsePath) { |
| BOOL success = NO; |
| NSError* error = nil; |
| if (_responseFD > 0) { |
| close(_responseFD); |
| NSString* name = [NSString stringWithFormat:@"%03lu-%i.response", (unsigned long)_connectionIndex, (int)_statusCode]; |
| success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; |
| } |
| if (!success) { |
| LOG_ERROR(@"Failed saving recorded response: %@", error); |
| DNOT_REACHED(); |
| } |
| unlink([_responsePath fileSystemRepresentation]); |
| } |
| #endif |
| if (_request) { |
| LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten); |
| } else { |
| LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten); |
| } |
| } |
| |
| @end |