Now set the correct base URL for each article's webview, and now load app JavaScripts as WebKit "user" scripts.

Setting the real base URL (rather than using a file URL pointing to the app's Resources folder) allows relative URLs to work correctly within the article, such as for images, and is compatible with Cross-Site-Origin policies that restrict use of resources outside of the origin domain.

It also implicitly eliminates access to the local file system from within the webview, as the use of a non-file base URL makes WebKit treats the webview's content as being from a remote server, and its default security policy is to then disallow local file access (except with explicit user action, such as drag-and-drop or via an `input` form element).

Note: the base URL is currently typically taken from the feed itself (specifically the "link" feed (channel) metadata).  That is controlled by the feed author (or a man-in-the-middle attacker).  It should perhaps be validated to ensure it's actually an HTTP/HTTPS URL, to prevent security problems.

The app-specific JavaScripts - used for fixing styling issues and the like - are now formally loaded as extensions to the web page, "user scripts" in WebKit parlance.  They're isolated to their own JavaScript world - meaning they can't be seen or manipulated by JavaScript from the feed article itself, and are more secure as a result.

Fixes #4156.

Co-Authored-By: Brent Simmons <1297121+brentsimmons@users.noreply.github.com>
This commit is contained in:
Wade Tregaskis
2023-11-22 13:41:58 -08:00
parent e9f26c9adc
commit bc15440ded
5 changed files with 34 additions and 20 deletions

View File

@@ -96,6 +96,18 @@ protocol DetailWebViewControllerDelegate: AnyObject {
userContentController.add(self, name: MessageName.windowDidScroll)
userContentController.add(self, name: MessageName.mouseDidEnter)
userContentController.add(self, name: MessageName.mouseDidExit)
let baseURL = ArticleRenderer.page.baseURL
let appScriptsWorld = WKContentWorld.world(name: "NetNewsWire")
for fileName in ["main.js", "main_mac.js", "newsfoot.js"] {
userContentController.addUserScript(
.init(source: try! String(contentsOf: baseURL.appending(path: fileName,
directoryHint: .notDirectory)),
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
in: appScriptsWorld))
}
configuration.userContentController = userContentController
webView = DetailWebView(frame: NSRect.zero, configuration: configuration)
@@ -326,7 +338,7 @@ private extension DetailWebViewController {
]
let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions)
webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL)
webView.loadHTMLString(html, baseURL: URL(string: rendering.baseURL))
}
func scrollInfo() async -> ScrollInfo? {

View File

@@ -4,14 +4,6 @@
<style>
[[style]]
</style>
<script src="main.js"></script>
<script src="main_mac.js"></script>
<script src="newsfoot.js" async="async"></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
processPage();
})
</script>
<base href="[[baseURL]]">
</head>
<body>

View File

@@ -168,3 +168,8 @@ function processPage() {
removeWpSmiley()
postRenderProcessing();
}
document.addEventListener("DOMContentLoaded", function(event) {
window.scrollTo(0, [[windowScrollY]]);
processPage();
})

View File

@@ -529,6 +529,20 @@ private extension WebViewController {
}
configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme)
let userContentController = WKUserContentController()
let baseURL = ArticleRenderer.page.baseURL
let appScriptsWorld = WKContentWorld.world(name: "NetNewsWire")
for fileName in ["main.js", "main_ios.js", "newsfoot.js"] {
userContentController.addUserScript(
.init(source: try! String(contentsOf: baseURL.appending(path: fileName,
directoryHint: .notDirectory)),
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
in: appScriptsWorld))
}
configuration.userContentController = userContentController
let webView = WKWebView(frame: self.view.bounds, configuration: configuration)
webView.isOpaque = false;
webView.backgroundColor = .clear;
@@ -591,8 +605,8 @@ private extension WebViewController {
]
let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions)
webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL)
webView.loadHTMLString(html, baseURL: URL(string: rendering.baseURL))
}
func finalScrollPosition(scrollingUp: Bool) -> CGFloat {

View File

@@ -5,15 +5,6 @@
<style>
[[style]]
</style>
<script src="main.js"></script>
<script src="main_ios.js"></script>
<script src="newsfoot.js" async="async"></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
window.scrollTo(0, [[windowScrollY]]);
processPage();
})
</script>
<base href="[[baseURL]]">
</head>
<body>