From dd7b319207277494fb87731a0d0b0232845d7312 Mon Sep 17 00:00:00 2001 From: Vijay Vikram Singh Date: Thu, 23 Jan 2020 07:07:07 -0800 Subject: [PATCH] fix(ios): allow changing WebView read access when loading local file (#11431) Co-authored-by: Lokesh Choudhary Fixes TIMOB-27159 --- apidoc/Titanium/UI/WebView.yml | 15 ++++ iphone/Classes/TiUIWebView.h | 1 + iphone/Classes/TiUIWebView.m | 51 +++---------- iphone/Classes/TiUIWebViewProxy.m | 12 +++ tests/Resources/html/example.html | 17 +++++ tests/Resources/ti.ui.webview.addontest.js | 85 ++++++++++++++++++++++ 6 files changed, 142 insertions(+), 39 deletions(-) create mode 100644 tests/Resources/html/example.html create mode 100644 tests/Resources/ti.ui.webview.addontest.js diff --git a/apidoc/Titanium/UI/WebView.yml b/apidoc/Titanium/UI/WebView.yml index fd04468bf93..9ba13078fcf 100644 --- a/apidoc/Titanium/UI/WebView.yml +++ b/apidoc/Titanium/UI/WebView.yml @@ -731,6 +731,21 @@ properties: since: "3.0.0" default: false + - name: assetsDirectory + summary: Path of file or directory to allow read access by the WebView. + description: | + Use this property to change the resources the web view has access to when loading the content of a local file. + By default the web view only has access to files inside the same directory as the loaded file. To reference + resources from other directories (e.g. a parent directory) change this property accordingly. + + If assetsDirectory references a single file, only that file may be loaded. If assetsDirectory references a + directory, files inside that directory may be loaded. + + This property needs to be set before [url](Titanium.UI.WebView.url) is assigned to a local file. + type: String + platforms: [iphone, ipad] + since: "9.0.0" + - name: html summary: HTML content of this web view. description: | diff --git a/iphone/Classes/TiUIWebView.h b/iphone/Classes/TiUIWebView.h index df422a69d65..e99ed8cabfe 100644 --- a/iphone/Classes/TiUIWebView.h +++ b/iphone/Classes/TiUIWebView.h @@ -29,6 +29,7 @@ BOOL _tiCookieHandlerAdded; BOOL ignoreNextRequest; SEL reloadMethod; + NSString *assetsDirectory; } @property (nonatomic, retain) id reloadData; diff --git a/iphone/Classes/TiUIWebView.m b/iphone/Classes/TiUIWebView.m index 1dc8d0c4d1d..f9418bc22a8 100644 --- a/iphone/Classes/TiUIWebView.m +++ b/iphone/Classes/TiUIWebView.m @@ -204,6 +204,12 @@ - (void)setBackgroundColor_:(id)value [[self webView] setBackgroundColor:[[TiUtils colorValue:value] color]]; } +- (void)setAssetsDirectory_:(id)value +{ + ENSURE_TYPE_OR_NIL(value, NSString); + assetsDirectory = value; +} + - (void)setData_:(id)value { ignoreNextRequest = YES; @@ -697,51 +703,18 @@ - (void)addCookieHeaderForRequest:(NSMutableURLRequest *)request - (void)loadLocalURL:(NSURL *)url { - NSStringEncoding encoding = NSUTF8StringEncoding; NSString *path = [url path]; - NSString *mimeType = [Mimetypes mimeTypeForExtension:path]; - NSError *error = nil; NSURL *baseURL = [[url copy] autorelease]; // first check to see if we're attempting to load a file from the // filesystem and if so, and it exists, use that if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { - // per the Apple docs on what to do when you don't know the encoding ahead of a - // file read: - // step 1: read and attempt to have system determine - NSString *html = [NSString stringWithContentsOfFile:path usedEncoding:&encoding error:&error]; - if (html == nil && error != nil) { - //step 2: if unknown encoding, try UTF-8 - error = nil; - html = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; - if (html == nil && error != nil) { - //step 3: try an appropriate legacy encoding (if one) -- what's that? Latin-1? - //at this point we're just going to fail - //This is assuming, of course, that this just isn't a pdf or some other non-HTML file. - if ([[path pathExtension] hasPrefix:@"htm"]) { - DebugLog(@"[ERROR] Couldn't determine the proper encoding. Make sure this file: %@ is UTF-8 encoded.", [path lastPathComponent]); - } - } else { - // if we get here, it succeeded using UTF8 - encoding = NSUTF8StringEncoding; - } - } else { - error = nil; - } - if ((error != nil && [error code] == 261) || [mimeType isEqualToString:(NSString *)svgMimeType]) { - //TODO: Shouldn't we be checking for an HTML mime type before trying to read? This is right now rather inefficient, but it - //Gets the job done, with minimal reliance on extensions. - // this is a different encoding than specified, just send it to the webview to load - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - [self loadRequestWithURL:url]; - return; - } else if (error != nil) { - DebugLog(@"[DEBUG] Cannot load file: %@. Error message was: %@", path, error); - return; - } NSURL *requestURL = [NSURL fileURLWithPath:path]; - [self loadRequestWithURL:requestURL]; + NSString *readAccessDirectory = assetsDirectory ?: [[url URLByDeletingLastPathComponent] absoluteString]; + + [[self webView] loadFileURL:requestURL + allowingReadAccessToURL:[NSURL URLWithString:readAccessDirectory]]; + } else { // convert it into a app:// relative path to load the resource // from our application @@ -749,7 +722,7 @@ - (void)loadLocalURL:(NSURL *)url NSData *data = [TiUtils loadAppResource:url]; NSString *html = nil; if (data != nil) { - html = [[[NSString alloc] initWithData:data encoding:encoding] autorelease]; + html = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; } if (html != nil) { //Because local HTML may rely on JS that's stored in the app: schema, we must kee the url in the app: format. diff --git a/iphone/Classes/TiUIWebViewProxy.m b/iphone/Classes/TiUIWebViewProxy.m index 86109a6b467..7b225f2668a 100644 --- a/iphone/Classes/TiUIWebViewProxy.m +++ b/iphone/Classes/TiUIWebViewProxy.m @@ -16,6 +16,18 @@ @implementation TiUIWebViewProxy +static NSArray *webViewKeySequence; + +#pragma mark Internal + +- (NSArray *)keySequence +{ + if (webViewKeySequence == nil) { + webViewKeySequence = [[NSArray arrayWithObjects:@"assetsDirectory", @"url", nil] retain]; + } + return webViewKeySequence; +} + - (id)_initWithPageContext:(id)context { if (self = [super _initWithPageContext:context]) { diff --git a/tests/Resources/html/example.html b/tests/Resources/html/example.html new file mode 100644 index 00000000000..98294fb4f93 --- /dev/null +++ b/tests/Resources/html/example.html @@ -0,0 +1,17 @@ + + + + + + Title + + + + +

Test HTML

+ + diff --git a/tests/Resources/ti.ui.webview.addontest.js b/tests/Resources/ti.ui.webview.addontest.js new file mode 100644 index 00000000000..73526b3a04b --- /dev/null +++ b/tests/Resources/ti.ui.webview.addontest.js @@ -0,0 +1,85 @@ +/* + * Appcelerator Titanium Mobile + * Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ +/* eslint-env mocha */ +/* eslint no-unused-expressions: "off" */ +'use strict'; +var should = require('./utilities/assertions'), + utilities = require('./utilities/utilities'); + +describe('Titanium.UI.WebView', function () { + var win; + this.slow(3000); + this.timeout(30000); + + afterEach(function (done) { + if (win) { + // If `win` is already closed, we're done. + let t = setTimeout(function () { + if (win) { + win = null; + done(); + } + }, 3000); + + win.addEventListener('close', function listener () { + clearTimeout(t); + + if (win) { + win.removeEventListener('close', listener); + } + win = null; + done(); + }); + win.close(); + } else { + win = null; + done(); + } + }); + + it.ios('#assetsDirectory', function (finish) { + win = Ti.UI.createWindow(); + var loadCount = 0; + function createDirectory(f) { + if (f && !f.exists()) { + f.createDirectory(); + } + return f; + } + + // Copy from Resources to cache folder + var cacheDir = createDirectory(Ti.Filesystem.getFile(Ti.Filesystem.applicationCacheDirectory)); + createDirectory(Ti.Filesystem.getFile(cacheDir.nativePath, 'html')); + createDirectory(Ti.Filesystem.getFile(cacheDir.nativePath, 'folder with spaces')); + + var htmlFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'html', 'example.html'); + var nextHtmlFile = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'folder with spaces', 'comingSoon.html'); + + var htmlInCache = Ti.Filesystem.getFile(cacheDir.nativePath, 'html', 'example.html'); + var nextHtmlInCache = Ti.Filesystem.getFile(cacheDir.nativePath, 'folder with spaces', 'comingSoon.html'); + + htmlFile.copy(htmlInCache.nativePath); + nextHtmlFile.copy(nextHtmlInCache.nativePath); + + var webView = Ti.UI.createWebView({ + width: Ti.UI.FILL, + height: Ti.UI.FILL, + url: htmlInCache.nativePath, + assetsDirectory: cacheDir.nativePath + }); + + webView.addEventListener('load', function () { + loadCount++; + if (loadCount > 1) { + finish(); + } + }); + win.add(webView); + win.open(); + }); + +});