@@ -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