@@ -24,6 +24,12 @@ Licensed to the Apache Software Foundation (ASF) under one
24
24
#import < Foundation/Foundation.h>
25
25
#import < MobileCoreServices/MobileCoreServices.h>
26
26
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
+
27
33
@interface CDVURLSchemeHandler ()
28
34
29
35
@property (nonatomic , weak ) CDVViewController *viewController;
@@ -57,86 +63,157 @@ - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)ur
57
63
}
58
64
}
59
65
66
+
67
+ NSURLRequest *req = urlSchemeTask.request ;
68
+ if (![req.URL.scheme isEqualToString: self .viewController.appScheme]) {
69
+ return ;
70
+ }
71
+
60
72
// Indicate that we are handling this task, by adding an entry with a null plugin
61
73
// We do this so that we can (in future) detect if the task is cancelled before we finished feeding it response data
62
74
[self .handlerMap setObject: (id )[NSNull null ] forKey: urlSchemeTask];
63
75
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;
68
79
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];
77
84
}
85
+
86
+ @synchronized (self.handlerMap ) {
87
+ [self .handlerMap removeObjectForKey: urlSchemeTask];
88
+ }
89
+ return ;
78
90
}
79
- }
80
91
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 ];
102
95
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
+ }
108
133
109
- [self .handlerMap removeObjectForKey: urlSchemeTask];
134
+ @synchronized (self.handlerMap ) {
135
+ [self .handlerMap removeObjectForKey: urlSchemeTask];
136
+ }
137
+ }];
110
138
}
111
139
112
140
- (void )webView : (WKWebView *)webView stopURLSchemeTask : (id <WKURLSchemeTask >)urlSchemeTask
113
141
{
114
- CDVPlugin <CDVPluginSchemeHandler> *plugin = [self .handlerMap objectForKey: urlSchemeTask];
142
+ CDVPlugin <CDVPluginSchemeHandler> *plugin;
143
+ @synchronized (self.handlerMap ) {
144
+ plugin = [self .handlerMap objectForKey: urlSchemeTask];
145
+ }
146
+
115
147
if (![plugin isEqual: [NSNull null ]] && [plugin respondsToSelector: @selector (stopSchemeTask: )]) {
116
148
[plugin stopSchemeTask: urlSchemeTask];
117
149
}
118
150
119
- [self .handlerMap removeObjectForKey: urlSchemeTask];
151
+ @synchronized (self.handlerMap ) {
152
+ [self .handlerMap removeObjectForKey: urlSchemeTask];
153
+ }
120
154
}
121
155
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];
127
166
} 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 ];
129
184
}
185
+ #endif
186
+
187
+ NSString *type;
188
+ [url getResourceValue: &type forKey: NSURLTypeIdentifierKey error: nil ];
189
+ return (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass ((__bridge CFStringRef)type, kUTTagClassMIMEType );
130
190
}
131
191
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 ;
137
208
}
138
- return NO ;
139
209
}
140
210
211
+ - (BOOL )taskActive : (id <WKURLSchemeTask >)task
212
+ {
213
+ @synchronized (self.handlerMap ) {
214
+ return [self .handlerMap objectForKey: task] != nil ;
215
+ }
216
+ }
141
217
142
218
@end
219
+
0 commit comments