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>
307 lines
8.3 KiB
Objective-C
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
|