From a43ae5fc8103826e93544d88bbe4e258b219cff3 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 10 Mar 2020 01:56:59 -0700 Subject: [PATCH] - Redo HTTP buffering mechanism - Handle HTTP errors better - Fix handling ShoutCAST servers responding ICY headers --- Plugins/HTTPSource/HTTPSource.h | 6 +- Plugins/HTTPSource/HTTPSource.m | 175 ++++++++++++++++++++++---------- 2 files changed, 126 insertions(+), 55 deletions(-) diff --git a/Plugins/HTTPSource/HTTPSource.h b/Plugins/HTTPSource/HTTPSource.h index 0a69ffc1f..95e77c9c1 100644 --- a/Plugins/HTTPSource/HTTPSource.h +++ b/Plugins/HTTPSource/HTTPSource.h @@ -20,10 +20,14 @@ NSURLSession * session; NSURLSessionDataTask * task; + Boolean didReceiveResponse; + Boolean didReceiveRandomData; + Boolean didComplete; + Boolean redirected; NSMutableArray * redirectURLs; - NSMutableArray * bufferedData; + NSMutableData * bufferedData; long _bytesBuffered; long _byteCount; diff --git a/Plugins/HTTPSource/HTTPSource.m b/Plugins/HTTPSource/HTTPSource.m index 1cae268c4..6e82ec1f1 100644 --- a/Plugins/HTTPSource/HTTPSource.m +++ b/Plugins/HTTPSource/HTTPSource.m @@ -11,6 +11,9 @@ #import "Logging.h" +#import +#import + #define BUFFER_SIZE 131072 @implementation HTTPSource @@ -18,7 +21,6 @@ - (NSURLSession *)createSession { queue = [[NSOperationQueue alloc] init]; - [queue setMaxConcurrentOperationCount:1]; NSURLSession *session = nil; session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] @@ -32,16 +34,102 @@ 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 addObject:data]; + [bufferedData appendData:data]; _bytesBuffered += [data length]; bytesBuffered = _bytesBuffered; } - if (bytesBuffered > BUFFER_SIZE) { + if (bytesBuffered >= BUFFER_SIZE) { [task suspend]; } } +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; + if (statusCode != 200) { + completionHandler(NSURLSessionResponseCancel); + @synchronized (task) { + task = nil; + } + return; + } + } + _mimeType = [response MIMEType]; + if ([_mimeType isEqualToString:@"application/octet-stream"]) + didReceiveRandomData = YES; + else + didReceiveResponse = YES; + + completionHandler(NSURLSessionResponseAllow); +} + - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response @@ -49,6 +137,7 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLRequest *))completionHandler { NSURL * url = [request URL]; if ([redirectURLs containsObject:url]) { + completionHandler(NULL); @synchronized(self->task) { self->task = nil; } @@ -56,14 +145,13 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response else { [redirectURLs addObject:url]; redirected = YES; + didReceiveResponse = NO; + didComplete = NO; @synchronized(bufferedData) { - [bufferedData removeAllObjects]; + [bufferedData setLength:0]; _bytesBuffered = 0; } - @synchronized(self->task) { - self->task = [session dataTaskWithRequest:request]; - [self->task resume]; - } + completionHandler(request); } } @@ -77,6 +165,7 @@ didBecomeInvalidWithError:(NSError *)error { - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{ + didComplete = YES; } - (void)URLSession:(NSURLSession *)session @@ -89,10 +178,12 @@ didCompleteWithError:(NSError *)error{ - (BOOL)open:(NSURL *)url { + didReceiveResponse = NO; + didReceiveRandomData = NO; redirected = NO; redirectURLs = [[NSMutableArray alloc] init]; - bufferedData = [[NSMutableArray alloc] init]; + bufferedData = [[NSMutableData alloc] init]; URL = url; [redirectURLs addObject:URL]; @@ -102,25 +193,10 @@ didCompleteWithError:(NSError *)error{ task = [session dataTaskWithRequest:request]; [task resume]; - NSURLResponse * response = nil; - while (!response) { - @synchronized(task) { - if (!task) return NO; - redirected = NO; - response = [task response]; - } - if (response && !redirected) break; - if (redirected) continue; - usleep(100); - } - if (response) { - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; - if (statusCode != 200) - return NO; - } - _mimeType = [response MIMEType]; - } + while (task && !didReceiveResponse) + usleep(1000); + + if (!task) return NO; return YES; } @@ -148,18 +224,25 @@ didCompleteWithError:(NSError *)error{ - (long)read:(void *)buffer amount:(long)amount { + if (didComplete) + return 0; + long totalRead = 0; long bytesBuffered = 0; while (totalRead < amount) { NSData * dataBlock = nil; + NSUInteger copySize = amount - totalRead; @synchronized(bufferedData) { - if ([bufferedData count]) - dataBlock = [bufferedData objectAtIndex:0]; + if ([bufferedData length]) { + if (copySize > [bufferedData length]) + copySize = [bufferedData length]; + dataBlock = [bufferedData subdataWithRange:NSMakeRange(0, copySize)]; + } } if (!dataBlock) { @synchronized(task) { - if (!task) return totalRead; + if (!task || didComplete) return totalRead; } usleep(1000); continue; @@ -169,36 +252,20 @@ didCompleteWithError:(NSError *)error{ break; } - NSInteger amountUsed = amountReceived; - - if (amountUsed > (amount - totalRead) ) - amountUsed = amount - totalRead; - const void * dataBytes = [dataBlock bytes]; - memcpy(((uint8_t *)buffer) + totalRead, dataBytes, amountUsed); + memcpy(((uint8_t *)buffer) + totalRead, dataBytes, amountReceived); - if (amountUsed < amountReceived) { - NSData * dataOut = [NSData dataWithBytes:(((uint8_t *)dataBytes) + amountUsed) length:(amountReceived - amountUsed)]; - @synchronized(bufferedData) { - [bufferedData removeObjectAtIndex:0]; - [bufferedData insertObject:dataOut atIndex:0]; - _bytesBuffered -= amountUsed; - bytesBuffered = _bytesBuffered; - } - } - else { - @synchronized(bufferedData) { - [bufferedData removeObjectAtIndex:0]; - _bytesBuffered -= amountUsed; - bytesBuffered = _bytesBuffered; - } + @synchronized(bufferedData) { + [bufferedData replaceBytesInRange:NSMakeRange(0, amountReceived) withBytes:NULL length:0]; + _bytesBuffered -= amountReceived; + bytesBuffered = _bytesBuffered; } - if (bytesBuffered <= (BUFFER_SIZE * 3 / 4)) { + if (!didComplete && bytesBuffered <= (BUFFER_SIZE * 3 / 4)) { [task resume]; } - totalRead += amountUsed; + totalRead += amountReceived; } _byteCount += totalRead;