diff --git a/mac/Config/Config/PackageContentWebView.swift b/mac/Config/Config/PackageContentWebView.swift index c63180ced6..305d2d736f 100644 --- a/mac/Config/Config/PackageContentWebView.swift +++ b/mac/Config/Config/PackageContentWebView.swift @@ -13,9 +13,7 @@ import WebKit import KeymanSettings public struct PackageContentWebView: NSViewRepresentable { - private static let allowedLocalExtensions = ["pdf", "txt", "png", "jpg", "jpeg"] - private static let allowedMimeTypes = ["application/pdf", "text/plain", "image/jpeg", "image/png"] - + let packageFileUrl: URL // create the AppKit view instance @@ -24,12 +22,18 @@ public struct PackageContentWebView: NSViewRepresentable { // Connect the delegate to catch link clicks webView.navigationDelegate = context.coordinator - + return webView } - // update the view when SwiftUI state changes + /** + * update the view when SwiftUI state changes + */ public func updateNSView(_ nsView: WKWebView, context: Context) { + // first update the parent in the coordinator so we know which + // package we are displaying content for + context.coordinator.parent = self + let request = URLRequest(url: packageFileUrl) // only load the request if it's not already loading/loaded to prevent infinite loops @@ -44,19 +48,27 @@ public struct PackageContentWebView: NSViewRepresentable { * Coordinator acts as the WKNavigationDelegate */ public func makeCoordinator() -> Coordinator { - Coordinator() + Coordinator(self) } - /** - * If a url links to the web rather than locally, open it in the default browser - */ @MainActor public class Coordinator: NSObject, WKNavigationDelegate { + var parent: PackageContentWebView + + init(_ parent: PackageContentWebView) { + self.parent = parent + } + + /** + * If a url links to the web rather than locally, open it in the default browser + * Must force load links in the package content that change the main frame + * because WKWebView's security sandbox will block loading of new pages + */ public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor @Sendable (WKNavigationActionPolicy) -> Void) { - - // if not url, cancel + + // if no url, cancel guard let url = navigationAction.request.url else { decisionHandler(.cancel) return @@ -64,59 +76,47 @@ public struct PackageContentWebView: NSViewRepresentable { // if not user-activated, pass through, e.g. for redirects guard navigationAction.navigationType == .linkActivated else { - decisionHandler(.allow) - return - } - - // load local files in webview, force-loading some common file types - if url.isFileURL { - let fileExtension = url.pathExtension.lowercased() - - if PackageContentWebView.allowedLocalExtensions.contains(fileExtension) { - // cancel the automatic navigation (which fails silently) - decisionHandler(.cancel) - - // force-load the file into the web view frame - webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent()) - return - } - + print("url not user activated \(url.path())") decisionHandler(.allow) return } - - // handle external links by opening in web browser + + // load local files in webview, force-loading files that are in the same directory + if url.isFileURL { + let standardizedIncomingUrl = url.standardizedFileURL + let packageDirectoryUrl = parent.packageFileUrl.deletingLastPathComponent() + let standardizedPackageDirectoryUrl = packageDirectoryUrl.standardizedFileURL + + // check whether the file is from the current package + if standardizedIncomingUrl.path.hasPrefix(standardizedPackageDirectoryUrl.path) { + decisionHandler(.cancel) // Cancel regular navigation + + // Force load with read permissions of package directory + webView.loadFileURL(standardizedIncomingUrl, allowingReadAccessTo: standardizedPackageDirectoryUrl) + return + } else { + // block local files outside of your target directory, not sure how we would receive one + decisionHandler(.cancel) + return + } + } + + // not a file URL: handle external links by opening in web browser decisionHandler(.cancel) - + var externalUrl = url - + // strip "link:" prefix if present let urlString = url.absoluteString if urlString.hasPrefix("link:https://") || urlString.hasPrefix("link:http://") { - let cleanString = urlString.replacingOccurrences(of: "link:", with: "") - if let cleanUrl = URL(string: cleanString) { - externalUrl = cleanUrl - } + let cleanString = urlString.replacingOccurrences(of: "link:", with: "") + if let cleanUrl = URL(string: cleanString) { + externalUrl = cleanUrl + } } - + // Open the external link in the default browser NSWorkspace.shared.open(externalUrl) } } - - /** - * Allow display of some common file types that may be linked in the package help - */ - public func webView(_ webView: WKWebView, - decidePolicyFor navigationResponse: WKNavigationResponse, - decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { - - if let mimeType = navigationResponse.response.mimeType { - if PackageContentWebView.allowedMimeTypes.contains(mimeType) { - decisionHandler(.allow) - return - } - } - decisionHandler(.allow) - } }