Cog/Plugins/HTTPSource/HTTPSource.m
Christopher Snowhill e0e7274339 HTTP Reader: Fix opening really tiny files
Tiny files complete fetching within the scope of the open function, so
the URL session task would have completed already. Now the function will
accept the data, and allow reading it.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-02-04 22:03:22 -08:00

307 lines
8.3 KiB
Objective-C

//
// HTTPSource.m
// HTTPSource
//
// Created by Vincent Spader on 3/1/07.
// Replaced by Christopher Snowhill on 3/7/20.
// Copyright 2020-2022 __LoSnoCo__. All rights reserved.
//
#import "HTTPSource.h"
#import "Logging.h"
#import <stdlib.h>
#import <string.h>
#define BUFFER_SIZE 262144
@implementation HTTPSource
- (NSURLSession *)createSession
{
queue = [[NSOperationQueue alloc] init];
NSURLSession *session = nil;
session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:queue];
return session;
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data{
long bytesBuffered = 0;
if (!task) return;
if (didReceiveRandomData) {
// Parse ICY header here?
// XXX
didReceiveRandomData = NO;
const char * header = "ICY 200 OK\r\n";
size_t length = [data length];
if (length >= strlen(header)) {
const char * dataBytes = (const char *) [data bytes];
const char * dataStart = dataBytes;
if (memcmp(dataBytes, header, strlen(header)) == 0) {
const char * dataEnd = dataBytes + length;
Boolean endFound = NO;
while (dataBytes + 4 <= dataEnd) {
if (memcmp(dataBytes, "\r\n\r\n", 4) == 0) {
endFound = YES;
break;
}
dataBytes++;
}
if (!endFound) {
@synchronized(task) {
didComplete = YES;
[task cancel];
task = nil;
return;
}
}
dataEnd = dataBytes + 4;
NSUInteger dataLeft = length - (dataEnd - dataStart);
dataBytes = dataStart;
dataBytes += strlen("ICY 200 OK\r\n");
char headerBuffer[80 * 1024 + 1];
while (dataBytes < dataEnd - 2) {
const char * string = dataBytes;
while (dataBytes < dataEnd - 2) {
if (memcmp(dataBytes, "\r\n", 2) == 0) break;
dataBytes++;
}
if (dataBytes - string > 80 * 1024)
dataBytes = string + 80 * 1024;
strncpy(headerBuffer, string, dataBytes - string);
headerBuffer[dataBytes - string] = '\0';
char * colon = strchr(headerBuffer, ':');
if (colon) {
*colon = '\0';
colon++;
}
if (strcasecmp(headerBuffer, "content-type") == 0) {
_mimeType = [NSString stringWithUTF8String:colon];
}
dataBytes += 2;
}
data = [NSData dataWithBytes:dataEnd length:dataLeft];
didReceiveResponse = YES;
}
}
}
@synchronized(bufferedData) {
[bufferedData appendData:data];
_bytesBuffered += [data length];
bytesBuffered = _bytesBuffered;
}
if (bytesBuffered >= BUFFER_SIZE) {
[task suspend];
taskSuspended = YES;
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) { if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
completionHandler(NSURLSessionResponseCancel);
@synchronized (task) {
task = nil;
}
return;
}
}
_mimeType = [response MIMEType];
if ([_mimeType isEqualToString:@"application/octet-stream"] ||
[_mimeType isEqualToString:@"text/plain"])
didReceiveRandomData = YES;
else
didReceiveResponse = YES;
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler {
NSURL * url = [request URL];
if ([redirectURLs containsObject:url]) {
completionHandler(nil);
@synchronized(self->task) {
self->task = nil;
}
}
else {
[redirectURLs addObject:url];
redirected = YES;
didReceiveResponse = NO;
didComplete = NO;
@synchronized(bufferedData) {
[bufferedData setLength:0];
_bytesBuffered = 0;
}
completionHandler(request);
}
}
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error {
@synchronized(task) {
task = nil;
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{
didComplete = YES;
completionHandler(nil);
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error{
@synchronized(self->task) {
self->task = nil;
}
}
- (BOOL)open:(NSURL *)url
{
didReceiveResponse = NO;
didReceiveRandomData = NO;
redirected = NO;
taskSuspended = NO;
redirectURLs = [[NSMutableArray alloc] init];
bufferedData = [[NSMutableData alloc] init];
URL = url;
[redirectURLs addObject:URL];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
session = [self createSession];
task = [session dataTaskWithRequest:request];
[task resume];
while (task && !didReceiveResponse)
usleep(1000);
if (!task && !didReceiveResponse) return NO;
return YES;
}
- (NSString *)mimeType
{
DLog(@"Returning mimetype! %@", _mimeType);
return _mimeType;
}
- (BOOL)seekable
{
return NO;
}
- (BOOL)seek:(long)position whence:(int)whence
{
return NO;
}
- (long)tell
{
return _byteCount;
}
- (long)read:(void *)buffer amount:(long)amount
{
@synchronized (bufferedData) {
if (didComplete && ![bufferedData length])
return 0;
}
long totalRead = 0;
long bytesBuffered = 0;
while (totalRead < amount) {
NSData * dataBlock = nil;
NSUInteger copySize = amount - totalRead;
@synchronized(bufferedData) {
if ([bufferedData length]) {
if (copySize > [bufferedData length])
copySize = [bufferedData length];
dataBlock = [bufferedData subdataWithRange:NSMakeRange(0, copySize)];
}
}
if (!dataBlock) {
@synchronized(task) {
if (!task || didComplete) return totalRead;
}
usleep(1000);
continue;
}
NSInteger amountReceived = [dataBlock length];
if (amountReceived <= 0) {
break;
}
const void * dataBytes = [dataBlock bytes];
memcpy(((uint8_t *)buffer) + totalRead, dataBytes, amountReceived);
@synchronized(bufferedData) {
[bufferedData replaceBytesInRange:NSMakeRange(0, amountReceived) withBytes:NULL length:0];
_bytesBuffered -= amountReceived;
bytesBuffered = _bytesBuffered;
}
if (!didComplete && taskSuspended && bytesBuffered <= (BUFFER_SIZE * 3 / 4)) {
[task resume];
taskSuspended = NO;
}
totalRead += amountReceived;
}
_byteCount += totalRead;
return totalRead;
}
- (void)close
{
if (task) [task cancel];
task = nil;
_mimeType = nil;
}
- (void)dealloc
{
[self close];
}
- (NSURL *)url
{
return URL;
}
+ (NSArray *)schemes
{
return @[@"http", @"https"];
}
@end