diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index ece9c6171..59f1a9ada 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -86,7 +86,7 @@ RCT_EXPORT_MODULE() } RCTDownloadTask *task = [_bridge.networking downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { - if (response && !error) { + if (response && !error && [response.URL.scheme hasPrefix:@"http"]) { RCTImageDownloader *strongSelf = weakSelf; NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed]; [strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request]; @@ -106,9 +106,15 @@ RCT_EXPORT_MODULE() progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler { - if ([imageURL.scheme.lowercaseString hasPrefix:@"http"]) { + if ([imageURL.scheme.lowercaseString hasPrefix:@"http"] || + [imageURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame) { __block RCTImageLoaderCancellationBlock decodeCancel = nil; + // Add missing png extension + if (imageURL.fileURL && imageURL.pathExtension.length == 0) { + imageURL = [NSURL fileURLWithPath:[imageURL.path stringByAppendingPathExtension:@"png"]]; + } + __weak RCTImageDownloader *weakSelf = self; RCTImageLoaderCancellationBlock downloadCancel = [self downloadDataForURL:imageURL progressHandler:progressHandler completionHandler:^(NSError *error, NSData *imageData) { if (error) { @@ -153,31 +159,6 @@ RCT_EXPORT_MODULE() return ^{ cancelled = YES; }; - } else if ([imageURL.scheme isEqualToString:@"file"]) { - __block BOOL cancelled = NO; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (cancelled) { - return; - } - - UIImage *image = [UIImage imageWithContentsOfFile:imageURL.resourceSpecifier]; - if (image) { - if (progressHandler) { - progressHandler(1, 1); - } - if (completionHandler) { - completionHandler(nil, image); - } - } else { - if (completionHandler) { - NSString *message = [NSString stringWithFormat:@"Could not find image at path: %@", imageURL.absoluteString]; - completionHandler(RCTErrorWithMessage(message), nil); - } - } - }); - return ^{ - cancelled = YES; - }; } else { RCTLogError(@"Unexpected image schema %@", imageURL.scheme); return ^{}; diff --git a/Libraries/Network/RCTDownloadTask.m b/Libraries/Network/RCTDownloadTask.m index e523f1606..a70c64113 100644 --- a/Libraries/Network/RCTDownloadTask.m +++ b/Libraries/Network/RCTDownloadTask.m @@ -9,7 +9,7 @@ #import "RCTDownloadTask.h" -#import "RCTAssert.h" +#import "RCTLog.h" @implementation RCTDownloadTask { @@ -65,8 +65,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) { if (![requestToken isEqual:_requestToken]) { if (RCT_DEBUG) { - RCTAssert([requestToken isEqual:_requestToken], - @"Unrecognized request token: %@", requestToken); + RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken); } if (_completionBlock) { _completionBlock(_response, _data, [NSError errorWithDomain:RCTErrorDomain code:0 diff --git a/Libraries/Network/RCTFileRequestHandler.h b/Libraries/Network/RCTFileRequestHandler.h new file mode 100644 index 000000000..3fe5a1772 --- /dev/null +++ b/Libraries/Network/RCTFileRequestHandler.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTURLRequestHandler.h" +#import "RCTInvalidating.h" + +/** + * This is the default RCTURLRequestHandler implementation for file requests. + */ +@interface RCTFileRequestHandler : NSObject + +@end diff --git a/Libraries/Network/RCTFileRequestHandler.m b/Libraries/Network/RCTFileRequestHandler.m new file mode 100644 index 000000000..c4f3f045d --- /dev/null +++ b/Libraries/Network/RCTFileRequestHandler.m @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTFileRequestHandler.h" + +#import + +@implementation RCTFileRequestHandler +{ + NSOperationQueue *_fileQueue; +} + +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +- (void)invalidate +{ + [_fileQueue cancelAllOperations]; + _fileQueue = nil; +} + +- (BOOL)canHandleRequest:(NSURLRequest *)request +{ + return [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame; +} + +- (NSOperation *)sendRequest:(NSURLRequest *)request + withDelegate:(id)delegate +{ + // Lazy setup + if (!_fileQueue) { + _fileQueue = [NSOperationQueue new]; + _fileQueue.maxConcurrentOperationCount = 4; + } + + __block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ + + // Get content length + NSError *error = nil; + NSFileManager *fileManager = [NSFileManager new]; + NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error]; + if (error) { + [delegate URLRequest:op didCompleteWithError:error]; + return; + } + + // Get mime type + NSString *fileExtension = [request.URL pathExtension]; + NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag( + kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL); + NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass( + (__bridge CFStringRef)UTI, kUTTagClassMIMEType); + + // Send response + NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL + MIMEType:contentType + expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue] + textEncodingName:nil]; + + [delegate URLRequest:op didReceiveResponse:response]; + + // Load data + NSData *data = [NSData dataWithContentsOfURL:request.URL + options:NSDataReadingMappedIfSafe + error:&error]; + if (data) { + [delegate URLRequest:op didReceiveData:data]; + } + [delegate URLRequest:op didCompleteWithError:error]; + }]; + + [_fileQueue addOperation:op]; + return op; +} + +- (void)cancelRequest:(NSOperation *)op +{ + [op cancel]; +} + +@end diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m index 4b4c40344..961bedb22 100644 --- a/Libraries/Network/RCTHTTPRequestHandler.m +++ b/Libraries/Network/RCTHTTPRequestHandler.m @@ -50,7 +50,9 @@ RCT_EXPORT_MODULE() static NSSet *schemes = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - schemes = [[NSSet alloc] initWithObjects:@"http", @"https", @"file", nil]; + // technically, RCTHTTPRequestHandler can handle file:// as well, + // but it's less efficient than using RCTFileRequestHandler + schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil]; }); return [schemes containsObject:request.URL.scheme.lowercaseString]; } diff --git a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj index 546b301c2..37b87a261 100644 --- a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj +++ b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */; }; 13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; }; + 13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */; settings = {ASSET_TAGS = (); }; }; 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; }; 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; }; /* End PBXBuildFile section */ @@ -30,6 +31,8 @@ 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = ""; }; 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = ""; }; + 13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFileRequestHandler.h; sourceTree = ""; }; + 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileRequestHandler.m; sourceTree = ""; }; 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = ""; }; 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = ""; }; 58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,6 +58,8 @@ 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */, 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */, 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */, + 13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */, + 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */, 1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */, 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */, 58B512061A9E6CE300147676 /* RCTNetworking.h */, @@ -130,6 +135,7 @@ buildActionMask = 2147483647; files = ( 13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */, + 13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */, 1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */, 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */, 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */, diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 7621eada9..7c3a83bb1 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -314,16 +314,17 @@ RCT_EXPORT_MODULE() void (^responseBlock)(NSURLResponse *) = ^(NSURLResponse *response) { dispatch_async(_methodQueue, ^{ - NSHTTPURLResponse *httpResponse = nil; - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - // Might be a local file request - httpResponse = (NSHTTPURLResponse *)response; + NSDictionary *headers; + NSInteger status; + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + headers = httpResponse.allHeaderFields ?: @{}; + status = httpResponse.statusCode; + } else { + headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{}; + status = 200; } - NSArray *responseJSON = @[task.requestID, - @(httpResponse.statusCode ?: 200), - httpResponse.allHeaderFields ?: @{}, - ]; - + NSArray *responseJSON = @[task.requestID, @(status), headers]; [_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse" body:responseJSON]; });