blob: a34cb6a2ac411577259e7bd2de22a5a474b2f5ce [file] [log] [blame]
#import "HTTPFileResponse.h"
#import "HTTPConnection.h"
#import "HTTPLogging.h"
#import <unistd.h>
#import <fcntl.h>
#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 DDLogLevel httpLogLevel = DDLogLevelWarning; // | 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