Skip to content

Commit 730be36

Browse files
authored
fix: Use a single scheme for all files (#278)
1 parent e0dd41e commit 730be36

File tree

6 files changed

+95
-138
lines changed

6 files changed

+95
-138
lines changed

src/android/com/ionicframework/cordova/webview/AndroidProtocolHandler.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,8 @@ public AndroidProtocolHandler(Context context) {
2626
this.context = context;
2727
}
2828

29-
public InputStream openAsset(String path, String assetPath) throws IOException {
30-
if (path.startsWith(assetPath + "/_file_")) {
31-
if (path.contains("content://")) {
32-
String contentPath = path.replace(assetPath + "/_file_/", "content://");
33-
return context.getContentResolver().openInputStream(Uri.parse(contentPath));
34-
} else {
35-
String filePath = path.replace(assetPath + "/_file_/", "");
36-
return new FileInputStream(new File(filePath));
37-
}
38-
} else {
39-
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
40-
}
29+
public InputStream openAsset(String path) throws IOException {
30+
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
4131
}
4232

4333
public InputStream openResource(Uri uri) {
@@ -78,10 +68,22 @@ public InputStream openResource(Uri uri) {
7868
}
7969

8070
public InputStream openFile(String filePath) throws IOException {
81-
File localFile = new File(filePath);
71+
String realPath = filePath.replace(WebViewLocalServer.fileStart, "");
72+
File localFile = new File(realPath);
8273
return new FileInputStream(localFile);
8374
}
8475

76+
public InputStream openContentUrl(Uri uri) throws IOException {
77+
String realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort() + WebViewLocalServer.contentStart, "content:/");
78+
InputStream stream = null;
79+
try {
80+
stream = context.getContentResolver().openInputStream(Uri.parse(realPath));
81+
} catch (SecurityException e) {
82+
Log.e(TAG, "Unable to open content URL: " + uri, e);
83+
}
84+
return stream;
85+
}
86+
8587
private static int getFieldId(Context context, String assetType, String assetName)
8688
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
8789
Class<?> d = context.getClassLoader()

src/android/com/ionicframework/cordova/webview/IonicWebViewEngine.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void init(CordovaWebView parentWebView, CordovaInterface cordova, final C
6262
CDV_LOCAL_SERVER = "http://localhost:" + port;
6363

6464
localServer = new WebViewLocalServer(cordova.getActivity(), "localhost:" + port, true, parser);
65-
WebViewLocalServer.AssetHostingDetails ahd = localServer.hostAssets("www");
65+
localServer.hostAssets("www");
6666

6767
webView.setWebViewClient(new ServerClient(this, parser));
6868

src/android/com/ionicframework/cordova/webview/WebViewLocalServer.java

Lines changed: 62 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,21 @@
4848
public class WebViewLocalServer {
4949
private static String TAG = "WebViewAssetServer";
5050
private String basePath;
51-
/**
52-
* capacitorapp.net is reserved by the Ionic team for use in local capacitor apps.
53-
*/
54-
public final static String knownUnusedAuthority = "capacitorapp.net";
5551
private final static String httpScheme = "http";
5652
private final static String httpsScheme = "https";
53+
public final static String fileStart = "/_app_file_";
54+
public final static String contentStart = "/_app_content_";
5755

5856
private final UriMatcher uriMatcher;
5957
private final AndroidProtocolHandler protocolHandler;
6058
private final String authority;
6159
// Whether we're serving local files or proxying (for example, when doing livereload on a
6260
// non-local endpoint (will be false in that case)
63-
private final boolean isLocal;
6461
private boolean isAsset;
6562
// Whether to route all requests to paths without extensions back to `index.html`
6663
private final boolean html5mode;
6764
private ConfigXmlParser parser;
6865

69-
public String getAuthority() { return authority; }
70-
7166
/**
7267
* A handler that produces responses for paths on the virtual asset server.
7368
* <p>
@@ -169,18 +164,7 @@ public Uri getHttpsPrefix() {
169164
this.html5mode = html5mode;
170165
this.parser = parser;
171166
this.protocolHandler = new AndroidProtocolHandler(context.getApplicationContext());
172-
if (authority != null) {
173-
this.authority = authority;
174-
if (authority.startsWith("localhost")) {
175-
this.isLocal = true;
176-
} else {
177-
this.isLocal = false;
178-
}
179-
180-
} else {
181-
this.isLocal = true;
182-
this.authority = UUID.randomUUID().toString() + "" + knownUnusedAuthority;
183-
}
167+
this.authority = authority;
184168
}
185169

186170
private static Uri parseAndVerifyUrl(String url) {
@@ -226,24 +210,40 @@ public WebResourceResponse shouldInterceptRequest(Uri uri) {
226210
return null;
227211
}
228212

229-
if (this.isLocal) {
213+
if (isLocalFile(uri) || uri.getAuthority().equals(this.authority)) {
230214
Log.d("SERVER", "Handling local request: " + uri.toString());
231215
return handleLocalRequest(uri, handler);
232216
} else {
233217
return handleProxyRequest(uri, handler);
234218
}
235219
}
236220

221+
private boolean isLocalFile(Uri uri) {
222+
String path = uri.getPath();
223+
if (path.startsWith(contentStart) || path.startsWith(fileStart)) {
224+
return true;
225+
}
226+
return false;
227+
}
228+
237229
private WebResourceResponse handleLocalRequest(Uri uri, PathHandler handler) {
238230
String path = uri.getPath();
231+
232+
if (isLocalFile(uri)) {
233+
InputStream responseStream = new LollipopLazyInputStream(handler, uri);
234+
String mimeType = getMimeType(path, responseStream);
235+
return createWebResourceResponse(mimeType, handler.getEncoding(),
236+
handler.getStatusCode(), handler.getReasonPhrase(), handler.getResponseHeaders(), responseStream);
237+
}
238+
239239
if (path.equals("/") || (!uri.getLastPathSegment().contains(".") && html5mode)) {
240240
InputStream stream;
241241
String launchURL = parser.getLaunchUrl();
242242
String launchFile = launchURL.substring(launchURL.lastIndexOf("/") + 1, launchURL.length());
243243
try {
244244
String startPath = this.basePath + "/" + launchFile;
245245
if (isAsset) {
246-
stream = protocolHandler.openAsset(startPath, "");
246+
stream = protocolHandler.openAsset(startPath);
247247
} else {
248248
stream = protocolHandler.openFile(startPath);
249249
}
@@ -367,10 +367,9 @@ void register(Uri uri, PathHandler handler) {
367367
*
368368
* @param assetPath the local path in the application's asset folder which will be made
369369
* available by the server (for example "/www").
370-
* @return prefixes under which the assets are hosted.
371370
*/
372-
public AssetHostingDetails hostAssets(String assetPath) {
373-
return hostAssets(authority, assetPath, "", true, true);
371+
public void hostAssets(String assetPath) {
372+
hostAssets(authority, assetPath, "", true, true);
374373
}
375374

376375

@@ -384,11 +383,10 @@ public AssetHostingDetails hostAssets(String assetPath) {
384383
* @param virtualAssetPath the path on the local server under which the assets should be hosted.
385384
* @param enableHttp whether to enable hosting using the http scheme.
386385
* @param enableHttps whether to enable hosting using the https scheme.
387-
* @return prefixes under which the assets are hosted.
388386
*/
389-
public AssetHostingDetails hostAssets(final String assetPath, final String virtualAssetPath,
387+
public void hostAssets(final String assetPath, final String virtualAssetPath,
390388
boolean enableHttp, boolean enableHttps) {
391-
return hostAssets(authority, assetPath, virtualAssetPath, enableHttp,
389+
hostAssets(authority, assetPath, virtualAssetPath, enableHttp,
392390
enableHttps);
393391
}
394392

@@ -403,36 +401,39 @@ public AssetHostingDetails hostAssets(final String assetPath, final String virtu
403401
* @param virtualAssetPath the path on the local server under which the assets should be hosted.
404402
* @param enableHttp whether to enable hosting using the http scheme.
405403
* @param enableHttps whether to enable hosting using the https scheme.
406-
* @return prefixes under which the assets are hosted.
407404
*/
408-
public AssetHostingDetails hostAssets(final String domain,
405+
public void hostAssets(final String domain,
409406
final String assetPath, final String virtualAssetPath,
410407
boolean enableHttp, boolean enableHttps) {
411408
this.isAsset = true;
412409
this.basePath = assetPath;
413-
Uri.Builder uriBuilder = new Uri.Builder();
414-
uriBuilder.scheme(httpScheme);
415-
uriBuilder.authority(domain);
416-
uriBuilder.path(virtualAssetPath);
410+
411+
createHostingDetails();
412+
}
413+
414+
private void createHostingDetails() {
415+
final String assetPath = this.basePath;
417416

418417
if (assetPath.indexOf('*') != -1) {
419418
throw new IllegalArgumentException("assetPath cannot contain the '*' character.");
420419
}
421-
if (virtualAssetPath.indexOf('*') != -1) {
422-
throw new IllegalArgumentException(
423-
"virtualAssetPath cannot contain the '*' character.");
424-
}
425-
426-
Uri httpPrefix = null;
427-
Uri httpsPrefix = null;
428420

429421
PathHandler handler = new PathHandler() {
430422
@Override
431423
public InputStream handle(Uri url) {
432-
InputStream stream;
433-
String path = url.getPath().replaceFirst(virtualAssetPath, assetPath);
424+
InputStream stream = null;
425+
String path = url.getPath();
434426
try {
435-
stream = protocolHandler.openAsset(path, assetPath);
427+
if (path.startsWith(contentStart)) {
428+
stream = protocolHandler.openContentUrl(url);
429+
} else if (path.startsWith(fileStart) || !isAsset) {
430+
if (!path.startsWith(fileStart)) {
431+
path = basePath + url.getPath();
432+
}
433+
stream = protocolHandler.openFile(path);
434+
} else {
435+
stream = protocolHandler.openAsset(assetPath + path);
436+
}
436437
} catch (IOException e) {
437438
Log.e(TAG, "Unable to open asset URL: " + url);
438439
return null;
@@ -442,18 +443,20 @@ public InputStream handle(Uri url) {
442443
}
443444
};
444445

445-
if (enableHttp) {
446-
httpPrefix = uriBuilder.build();
447-
register(Uri.withAppendedPath(httpPrefix, "/"), handler);
448-
register(Uri.withAppendedPath(httpPrefix, "**"), handler);
449-
}
450-
if (enableHttps) {
451-
uriBuilder.scheme(httpsScheme);
452-
httpsPrefix = uriBuilder.build();
453-
register(Uri.withAppendedPath(httpsPrefix, "/"), handler);
454-
register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
455-
}
456-
return new AssetHostingDetails(httpPrefix, httpsPrefix);
446+
registerUriForScheme(httpScheme, handler, authority);
447+
registerUriForScheme(httpsScheme, handler, authority);
448+
449+
}
450+
451+
private void registerUriForScheme(String scheme, PathHandler handler, String authority) {
452+
Uri.Builder uriBuilder = new Uri.Builder();
453+
uriBuilder.scheme(scheme);
454+
uriBuilder.authority(authority);
455+
uriBuilder.path("");
456+
Uri uriPrefix = uriBuilder.build();
457+
458+
register(Uri.withAppendedPath(uriPrefix, "/"), handler);
459+
register(Uri.withAppendedPath(uriPrefix, "**"), handler);
457460
}
458461

459462
/**
@@ -545,62 +548,16 @@ public InputStream handle(Uri url) {
545548
*
546549
* @param basePath the local path in the application's data folder which will be made
547550
* available by the server (for example "/www").
548-
* @return prefixes under which the assets are hosted.
549551
*/
550-
public AssetHostingDetails hostFiles(String basePath) {
551-
return hostFiles(basePath, true, true);
552+
public void hostFiles(String basePath) {
553+
hostFiles(basePath, true, true);
552554
}
553555

554-
public AssetHostingDetails hostFiles(final String basePath, boolean enableHttp,
556+
public void hostFiles(final String basePath, boolean enableHttp,
555557
boolean enableHttps) {
556558
this.isAsset = false;
557559
this.basePath = basePath;
558-
Uri.Builder uriBuilder = new Uri.Builder();
559-
uriBuilder.scheme(httpScheme);
560-
uriBuilder.authority(authority);
561-
uriBuilder.path("");
562-
563-
Uri httpPrefix = null;
564-
Uri httpsPrefix = null;
565-
566-
PathHandler handler = new PathHandler() {
567-
@Override
568-
public InputStream handle(Uri url) {
569-
InputStream stream;
570-
try {
571-
if (url.getPath().startsWith("/_file_/")) {
572-
stream = protocolHandler.openFile( url.getPath().replace("/_file_/", ""));
573-
} else {
574-
stream = protocolHandler.openFile(basePath + url.getPath());
575-
}
576-
} catch (IOException e) {
577-
Log.e(TAG, "Unable to open asset URL: " + url);
578-
return null;
579-
}
580-
581-
String mimeType = null;
582-
try {
583-
mimeType = URLConnection.guessContentTypeFromStream(stream);
584-
} catch (Exception ex) {
585-
Log.e(TAG, "Unable to get mime type" + url);
586-
}
587-
588-
return stream;
589-
}
590-
};
591-
592-
if (enableHttp) {
593-
httpPrefix = uriBuilder.build();
594-
register(Uri.withAppendedPath(httpPrefix, "/"), handler);
595-
register(Uri.withAppendedPath(httpPrefix, "**"), handler);
596-
}
597-
if (enableHttps) {
598-
uriBuilder.scheme(httpsScheme);
599-
httpsPrefix = uriBuilder.build();
600-
register(Uri.withAppendedPath(httpsPrefix, "/"), handler);
601-
register(Uri.withAppendedPath(httpsPrefix, "**"), handler);
602-
}
603-
return new AssetHostingDetails(httpPrefix, httpsPrefix);
560+
createHostingDetails();
604561
}
605562

606563
/**

src/ios/CDVWKWebViewEngine.m

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ - (void)pluginInitialize
332332
self.handler = [[IONAssetHandler alloc] init];
333333
[self.handler setAssetPath:[self getStartPath]];
334334
[configuration setURLSchemeHandler:self.handler forURLScheme:@"ionic"];
335-
[configuration setURLSchemeHandler:self.handler forURLScheme:@"ionic-asset"];
336335
}
337336
}
338337

@@ -975,8 +974,8 @@ -(void)setServerPath:(NSString *) path
975974
} else {
976975
[self.webServer addGETHandlerForBasePath:@"/" directoryPath:path indexFilename:((CDVViewController *)self.viewController).startPage cacheAge:0 allowRangeRequests:YES];
977976
}
978-
[self.webServer addHandlerForMethod:@"GET" pathRegex:@"_file_/" requestClass:GCDWebServerFileRequest.class asyncProcessBlock:^(__kindof GCDWebServerRequest * _Nonnull request, GCDWebServerCompletionBlock _Nonnull completionBlock) {
979-
NSString *urlToRemove = [serverUrl stringByAppendingString:@"/_file_"];
977+
[self.webServer addHandlerForMethod:@"GET" pathRegex:@"_app_file_/" requestClass:GCDWebServerFileRequest.class asyncProcessBlock:^(__kindof GCDWebServerRequest * _Nonnull request, GCDWebServerCompletionBlock _Nonnull completionBlock) {
978+
NSString *urlToRemove = [serverUrl stringByAppendingString:@"/_app_file_"];
980979
NSString *absUrl = [[[request URL] absoluteString] stringByReplacingOccurrencesOfString:urlToRemove withString:@""];
981980

982981
NSRange range = [absUrl rangeOfString:@"?"];

src/ios/IONAssetHandler.m

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)ur
1414
NSString * stringToLoad = url.path;
1515
NSString * scheme = url.scheme;
1616
if ([scheme isEqualToString:@"ionic"]) {
17-
startPath = self.basePath;
18-
if ([stringToLoad isEqualToString:@""] || !url.pathExtension) {
19-
startPath = [startPath stringByAppendingString:@"/index.html"];
17+
if ([stringToLoad hasPrefix:@"/_app_file_"]) {
18+
startPath = [stringToLoad stringByReplacingOccurrencesOfString:@"/_app_file_" withString:@""];
2019
} else {
21-
startPath = [startPath stringByAppendingString:stringToLoad];
22-
}
23-
} else {
24-
if (![stringToLoad isEqualToString:@""]) {
25-
startPath = stringToLoad;
20+
startPath = self.basePath;
21+
if ([stringToLoad isEqualToString:@""] || [url.pathExtension isEqualToString:@""]) {
22+
startPath = [startPath stringByAppendingString:@"/index.html"];
23+
} else {
24+
startPath = [startPath stringByAppendingString:stringToLoad];
25+
}
2626
}
2727
}
2828

src/www/util.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@ var WebView = {
55
if (!url) {
66
return url;
77
}
8-
if (!url.startsWith('file://')) {
9-
return url;
8+
if (url.startsWith('/')) {
9+
return window.WEBVIEW_SERVER_URL + '/_app_file_' + url;
1010
}
11-
if (window.WEBVIEW_SERVER_URL.startsWith('ionic://')) {
12-
return url.replace('file', 'ionic-asset');
11+
if (url.startsWith('file://')) {
12+
return window.WEBVIEW_SERVER_URL + url.replace('file://', '/_app_file_');
1313
}
14-
url = url.substr(7); // len("file://") == 7
15-
if (url.length === 0 || url[0] !== '/') { // ensure the new URL starts with /
16-
url = '/' + url;
14+
if (url.startsWith('content://')) {
15+
return window.WEBVIEW_SERVER_URL + url.replace('content:/', '/_app_content_');
1716
}
18-
return window.WEBVIEW_SERVER_URL + '/_file_' + url;
17+
return url;
1918
},
2019
setServerBasePath: function(path) {
2120
exec(null, null, 'IonicWebView', 'setServerBasePath', [path]);

0 commit comments

Comments
 (0)