#import "HTTPFileResponse.h" #import "HTTPConnection.h" #import "HTTPLogging.h" #import #import #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Log levels : off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; #define NULL_FD -1 @implementation HTTPFileResponse - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent { if((self = [super init])) { HTTPLogTrace(); connection = parent; // Parents retain children, children do NOT retain parents fileFD = NULL_FD; filePath = [[fpath copy] stringByResolvingSymlinksInPath]; if (filePath == nil) { HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); return nil; } NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; if (fileAttributes == nil) { HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); return nil; } fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; fileOffset = 0; aborted = NO; // We don't bother opening the file here. // If this is a HEAD request we only need to know the fileLength. } return self; } - (void)abort { HTTPLogTrace(); [connection responseDidAbort:self]; aborted = YES; } - (BOOL)openFile { HTTPLogTrace(); fileFD = open([filePath UTF8String], O_RDONLY); if (fileFD == NULL_FD) { HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath); [self abort]; return NO; } HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); return YES; } - (BOOL)openFileIfNeeded { if (aborted) { // The file operation has been aborted. // This could be because we failed to open the file, // or the reading process failed. return NO; } if (fileFD != NULL_FD) { // File has already been opened. return YES; } return [self openFile]; } - (UInt64)contentLength { HTTPLogTrace(); return fileLength; } - (UInt64)offset { HTTPLogTrace(); return fileOffset; } - (void)setOffset:(UInt64)offset { HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); if (![self openFileIfNeeded]) { // File opening failed, // or response has been aborted due to another error. return; } fileOffset = offset; off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); if (result == -1) { HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); [self abort]; } } - (NSData *)readDataOfLength:(NSUInteger)length { HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); if (![self openFileIfNeeded]) { // File opening failed, // or response has been aborted due to another error. return nil; } // Determine how much data we should read. // // It is OK if we ask to read more bytes than exist in the file. // It is NOT OK to over-allocate the buffer. UInt64 bytesLeftInFile = fileLength - fileOffset; NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile); // Make sure buffer is big enough for read request. // Do not over-allocate. if (buffer == NULL || bufferSize < bytesToRead) { bufferSize = bytesToRead; buffer = reallocf(buffer, (size_t)bufferSize); if (buffer == NULL) { HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); [self abort]; return nil; } } // Perform the read HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); ssize_t result = read(fileFD, buffer, bytesToRead); // Check the results if (result < 0) { HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); [self abort]; return nil; } else if (result == 0) { HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); [self abort]; return nil; } else // (result > 0) { HTTPLogVerbose(@"%@[%p]: Read %ld bytes from file", THIS_FILE, self, (long)result); fileOffset += result; return [NSData dataWithBytes:buffer length:result]; } } - (BOOL)isDone { BOOL result = (fileOffset == fileLength); HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); return result; } - (NSString *)filePath { return filePath; } - (void)dealloc { HTTPLogTrace(); if (fileFD != NULL_FD) { HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD); close(fileFD); } if (buffer) free(buffer); } @end