From de4dd9ce9fc50cf1428fc0a2edee70fbb42fda15 Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Tue, 27 Aug 2024 21:53:02 -0700 Subject: [PATCH] feat(schemes): Support range requests Based on code from https://github.com/ionic-team/cordova-plugin-ionic-webview/pull/692 Closes GH-1033. Co-Authored-By: David Holmgren --- .../CDVWebViewEngine/CDVURLSchemeHandler.m | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVURLSchemeHandler.m b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVURLSchemeHandler.m index 980785bc6..fbb0cf774 100644 --- a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVURLSchemeHandler.m +++ b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVURLSchemeHandler.m @@ -89,24 +89,59 @@ - (void)webView:(WKWebView *)webView startURLSchemeTask:(id )ur return; } + NSInteger statusCode = 200; // Default to 200 OK status NSString *mimeType = [self getMimeType:fileURL] ?: @"application/octet-stream"; NSNumber *fileLength; [fileURL getResourceValue:&fileLength forKey:NSURLFileSizeKey error:nil]; NSNumber *responseSize = fileLength; + NSUInteger responseSent = 0; + + NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithCapacity:5]; + headers[@"Content-Type"] = mimeType; + headers[@"Cache-Control"] = @"no-cache"; + headers[@"Content-Length"] = [responseSize stringValue]; + + // Check for Range header + NSString *rangeHeader = [urlSchemeTask.request valueForHTTPHeaderField:@"Range"]; + if (rangeHeader) { + NSRange range = NSMakeRange(NSNotFound, 0); + + if ([rangeHeader hasPrefix:@"bytes="]) { + NSString *byteRange = [rangeHeader substringFromIndex:6]; + NSArray *rangeParts = [byteRange componentsSeparatedByString:@"-"]; + NSUInteger start = (NSUInteger)[rangeParts[0] integerValue]; + NSUInteger end = rangeParts.count > 1 && ![rangeParts[1] isEqualToString:@""] ? (NSUInteger)[rangeParts[1] integerValue] : [fileLength unsignedIntegerValue] - 1; + range = NSMakeRange(start, end - start + 1); + } + + if (range.location != NSNotFound) { + // Ensure range is valid + if (range.location >= [fileLength unsignedIntegerValue] && [self taskActive:urlSchemeTask]) { + headers[@"Content-Range"] = [NSString stringWithFormat:@"bytes */%@", fileLength]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:req.URL statusCode:416 HTTPVersion:@"HTTP/1.1" headerFields:headers]; + [urlSchemeTask didReceiveResponse:response]; + [urlSchemeTask didFinish]; + + @synchronized(self.handlerMap) { + [self.handlerMap removeObjectForKey:urlSchemeTask]; + } + return; + } - NSDictionary *headers = @{ - @"Content-Length" : [responseSize stringValue], - @"Content-Type" : mimeType, - @"Cache-Control": @"no-cache" - }; + [fileHandle seekToFileOffset:range.location]; + responseSize = [NSNumber numberWithUnsignedInteger:range.length]; + statusCode = 206; // Partial Content + headers[@"Content-Range"] = [NSString stringWithFormat:@"bytes %lu-%lu/%@", (unsigned long)range.location, (unsigned long)(range.location + range.length - 1), fileLength]; + headers[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)range.length]; + } + } - NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:req.URL statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:headers]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:req.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headers]; if ([self taskActive:urlSchemeTask]) { [urlSchemeTask didReceiveResponse:response]; } - NSUInteger responseSent = 0; while ([self taskActive:urlSchemeTask] && responseSent < [responseSize unsignedIntegerValue]) { @autoreleasepool { NSData *data = [self readFromFileHandle:fileHandle upTo:FILE_BUFFER_SIZE error:&error];