@@ -24,6 +24,12 @@ Licensed to the Apache Software Foundation (ASF) under one
2424#import < Foundation/Foundation.h>
2525#import < MobileCoreServices/MobileCoreServices.h>
2626
27+ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
28+ #import < UniformTypeIdentifiers/UniformTypeIdentifiers.h>
29+ #endif
30+
31+ static const NSUInteger FILE_BUFFER_SIZE = 1024 * 1024 * 4 ; // 4 MiB
32+
2733@interface CDVURLSchemeHandler ()
2834
2935@property (nonatomic , weak ) CDVViewController *viewController;
@@ -57,86 +63,157 @@ - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)ur
5763 }
5864 }
5965
66+
67+ NSURLRequest *req = urlSchemeTask.request ;
68+ if (![req.URL.scheme isEqualToString: self .viewController.appScheme]) {
69+ return ;
70+ }
71+
6072 // Indicate that we are handling this task, by adding an entry with a null plugin
6173 // We do this so that we can (in future) detect if the task is cancelled before we finished feeding it response data
6274 [self .handlerMap setObject: (id )[NSNull null ] forKey: urlSchemeTask];
6375
64- NSString * startPath = [[NSBundle mainBundle ] pathForResource: self .viewController.webContentFolderName ofType: nil ];
65- NSURL * url = urlSchemeTask.request .URL ;
66- NSString * stringToLoad = url.path ;
67- NSString * scheme = url.scheme ;
76+ [self .viewController.commandDelegate runInBackground: ^{
77+ NSURL *fileURL = [self fileURLForRequestURL: req.URL];
78+ NSError *error;
6879
69- if ([scheme isEqualToString: self .viewController.appScheme]) {
70- if ([stringToLoad hasPrefix: @" /_app_file_" ]) {
71- startPath = [stringToLoad stringByReplacingOccurrencesOfString: @" /_app_file_" withString: @" " ];
72- } else {
73- if ([stringToLoad isEqualToString: @" " ] || [url.pathExtension isEqualToString: @" " ]) {
74- startPath = [startPath stringByAppendingPathComponent: self .viewController.startPage];
75- } else {
76- startPath = [startPath stringByAppendingPathComponent: stringToLoad];
80+ NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL: fileURL error: &error];
81+ if (!fileHandle || error) {
82+ if ([self taskActive: urlSchemeTask]) {
83+ [urlSchemeTask didFailWithError: error];
7784 }
85+
86+ @synchronized (self.handlerMap ) {
87+ [self .handlerMap removeObjectForKey: urlSchemeTask];
88+ }
89+ return ;
7890 }
79- }
8091
81- NSError * fileError = nil ;
82- NSData * data = nil ;
83- if ([self isMediaExtension: url.pathExtension]) {
84- data = [NSData dataWithContentsOfFile: startPath options: NSDataReadingMappedIfSafe error: &fileError];
85- }
86- if (!data || fileError) {
87- data = [[NSData alloc ] initWithContentsOfFile: startPath];
88- }
89- NSInteger statusCode = 200 ;
90- if (!data) {
91- statusCode = 404 ;
92- }
93- NSURL * localUrl = [NSURL URLWithString: url.absoluteString];
94- NSString * mimeType = [self getMimeType: url.pathExtension];
95- id response = nil ;
96- if (data && [self isMediaExtension: url.pathExtension]) {
97- response = [[NSURLResponse alloc ] initWithURL: localUrl MIMEType: mimeType expectedContentLength: data.length textEncodingName: nil ];
98- } else {
99- NSDictionary * headers = @{ @" Content-Type" : mimeType, @" Cache-Control" : @" no-cache" };
100- response = [[NSHTTPURLResponse alloc ] initWithURL: localUrl statusCode: statusCode HTTPVersion: nil headerFields: headers];
101- }
92+ NSString *mimeType = [self getMimeType: fileURL] ?: @" application/octet-stream" ;
93+ NSNumber *fileLength;
94+ [fileURL getResourceValue: &fileLength forKey: NSURLFileSizeKey error: nil ];
10295
103- [urlSchemeTask didReceiveResponse: response];
104- if (data) {
105- [urlSchemeTask didReceiveData: data];
106- }
107- [urlSchemeTask didFinish ];
96+ NSNumber *responseSize = fileLength;
97+
98+ NSDictionary *headers = @{
99+ @" Content-Length" : [responseSize stringValue ],
100+ @" Content-Type" : mimeType,
101+ @" Cache-Control" : @" no-cache"
102+ };
103+
104+ NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc ] initWithURL: req.URL statusCode: 200 HTTPVersion: @" HTTP/1.1" headerFields: headers];
105+ if ([self taskActive: urlSchemeTask]) {
106+ [urlSchemeTask didReceiveResponse: response];
107+ }
108+
109+ NSUInteger responseSent = 0 ;
110+ while ([self taskActive: urlSchemeTask] && responseSent < [responseSize unsignedIntegerValue ]) {
111+ @autoreleasepool {
112+ NSData *data = [self readFromFileHandle: fileHandle upTo: FILE_BUFFER_SIZE error: &error];
113+ if (!data || error) {
114+ if ([self taskActive: urlSchemeTask]) {
115+ [urlSchemeTask didFailWithError: error];
116+ }
117+ break ;
118+ }
119+
120+ if ([self taskActive: urlSchemeTask]) {
121+ [urlSchemeTask didReceiveData: data];
122+ }
123+
124+ responseSent += data.length ;
125+ }
126+ }
127+
128+ [fileHandle closeFile ];
129+
130+ if ([self taskActive: urlSchemeTask]) {
131+ [urlSchemeTask didFinish ];
132+ }
108133
109- [self .handlerMap removeObjectForKey: urlSchemeTask];
134+ @synchronized (self.handlerMap ) {
135+ [self .handlerMap removeObjectForKey: urlSchemeTask];
136+ }
137+ }];
110138}
111139
112140- (void )webView : (WKWebView *)webView stopURLSchemeTask : (id <WKURLSchemeTask >)urlSchemeTask
113141{
114- CDVPlugin <CDVPluginSchemeHandler> *plugin = [self .handlerMap objectForKey: urlSchemeTask];
142+ CDVPlugin <CDVPluginSchemeHandler> *plugin;
143+ @synchronized (self.handlerMap ) {
144+ plugin = [self .handlerMap objectForKey: urlSchemeTask];
145+ }
146+
115147 if (![plugin isEqual: [NSNull null ]] && [plugin respondsToSelector: @selector (stopSchemeTask: )]) {
116148 [plugin stopSchemeTask: urlSchemeTask];
117149 }
118150
119- [self .handlerMap removeObjectForKey: urlSchemeTask];
151+ @synchronized (self.handlerMap ) {
152+ [self .handlerMap removeObjectForKey: urlSchemeTask];
153+ }
120154}
121155
122- -(NSString *) getMimeType : (NSString *)fileExtension {
123- if (fileExtension && ![fileExtension isEqualToString: @" " ]) {
124- NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension , (__bridge CFStringRef)fileExtension, NULL );
125- NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass ((__bridge CFStringRef)UTI, kUTTagClassMIMEType );
126- return contentType ? contentType : @" application/octet-stream" ;
156+ #pragma mark - Utility methods
157+
158+ - (NSURL *)fileURLForRequestURL : (NSURL *)url
159+ {
160+ NSURL *resDir = [[NSBundle mainBundle ] URLForResource: self .viewController.webContentFolderName withExtension: nil ];
161+ NSURL *filePath;
162+
163+ if ([url.path hasPrefix: @" /_app_file_" ]) {
164+ NSString *path = [url.path stringByReplacingOccurrencesOfString: @" /_app_file_" withString: @" " ];
165+ filePath = [resDir URLByAppendingPathComponent: path];
127166 } else {
128- return @" text/html" ;
167+ if ([url.path isEqualToString: @" " ] || [url.pathExtension isEqualToString: @" " ]) {
168+ filePath = [resDir URLByAppendingPathComponent: self .viewController.startPage];
169+ } else {
170+ filePath = [resDir URLByAppendingPathComponent: url.path];
171+ }
172+ }
173+
174+ return filePath.URLByStandardizingPath ;
175+ }
176+
177+ -(NSString *)getMimeType : (NSURL *)url
178+ {
179+ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
180+ if (@available (iOS 14.0 , *)) {
181+ UTType *uti;
182+ [url getResourceValue: &uti forKey: NSURLContentTypeKey error: nil ];
183+ return [uti preferredMIMEType ];
129184 }
185+ #endif
186+
187+ NSString *type;
188+ [url getResourceValue: &type forKey: NSURLTypeIdentifierKey error: nil ];
189+ return (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass ((__bridge CFStringRef)type, kUTTagClassMIMEType );
130190}
131191
132- -(BOOL ) isMediaExtension : (NSString *) pathExtension {
133- NSArray * mediaExtensions = @[@" m4v" , @" mov" , @" mp4" ,
134- @" aac" , @" ac3" , @" aiff" , @" au" , @" flac" , @" m4a" , @" mp3" , @" wav" ];
135- if ([mediaExtensions containsObject: pathExtension.lowercaseString]) {
136- return YES ;
192+ - (nullable NSData *)readFromFileHandle : (NSFileHandle *)handle upTo : (NSUInteger )length error : (NSError **)err
193+ {
194+ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
195+ if (@available (iOS 14.0 , *)) {
196+ return [handle readDataUpToLength: length error: err];
197+ }
198+ #endif
199+
200+ @try {
201+ return [handle readDataOfLength: length];
202+ }
203+ @catch (NSError *error) {
204+ if (err != nil ) {
205+ *err = error;
206+ }
207+ return nil ;
137208 }
138- return NO ;
139209}
140210
211+ - (BOOL )taskActive : (id <WKURLSchemeTask >)task
212+ {
213+ @synchronized (self.handlerMap ) {
214+ return [self .handlerMap objectForKey: task] != nil ;
215+ }
216+ }
141217
142218@end
219+
0 commit comments