0

Updated question

I am loading a remote page that I control into a WKWebview in my app and customizing it by loading local CSS and font files. At page load, in webView:didFinishNavigation:, I am running some JS to inject some style tags and a link element whose src points to a local file via a custom URL scheme myfile implemented via WKURLSchemeHandler. The local file is being blocked from loading by what appears to be Content Security Policy.

I am not able to add headers because the page I'm loading is in an S3 bucket, but I have added a meta tag to the head on the server (not injected with JS):

<meta http-equiv="Content-Security-Policy" content="default-src 'self' otfile:; style-src 'self' 'unsafe-inline' 'unsafe-eval' http: https: data: otfile:"/>

Even with this addition, the web view does not attempt to load the local CSS - looking at the Safari web inspector, the link element is in the head but the CSS file doesn't show up on the network tab. Evaluating await fetch('otfile://myfile.css') in the console produces a warning like this:

[blocked] The page at https://my-bucket.s3.amazonaws.com/file.xhtml was not allowed to display insecure content from otfile://file.css.

An example project demonstrating the issue can be found here.

My question has now become: How do I specify to WKWebView / WebKit that it's permitted to load arbitrary content via my custom URL scheme here?


See below for original

I'm loading a remote page in a WKWebView, and I need to customize the styling of it by injecting a reference to a local CSS file at page load using Javascript. I'm using an implementation of WKURLSchemeHandler to do this. When I set up my web view:

WKFileLoadHandler *fileHandler = [[MyFileLoadHandler alloc]init];
[config setURLSchemeHandler:fileHandler forURLScheme:@"myfile"];

self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
self.webView.navigationDelegate = self;
self.webView.allowsLinkPreview = NO;
self.webView.opaque = NO;
[self.view addSubview:self.webView];
[self.webView autoPinEdgesToSuperviewEdges];

//self.url is a remote url
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url];
request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
[self.webView loadRequest:request];

Then, when the page has loaded, in webView:didFinishNavigation:

NSMutableString *strJs = [NSMutableString string];
[strJs appendString:@"var cssTag = document.createElement('link');\n"];
[strJs appendString:@"cssTag.setAttribute('type', 'text/css');\n"];
[strJs appendString:@"cssTag.setAttribute('href', 'myfile://bundle/css/engine.css');\n"];
[strJs appendString:@"cssTag.setAttribute('rel', 'stylesheet');\n"];
[strJs appendString:@"document.head.appendChild(cssTag);\n"];
[self.webView evaluateJavascript:strJs completionHandler:nil];

In my code I have a completion handler that I've omitted here for brevity, and there is no error reported when running the JS.

What I'm seeing is that the web view does not attempt to load the CSS file. It never even makes a call to the scheme handler, and it doesn't show up in the Web Inspector as a request that was attempted.

I have tried setting the href of the link element to a remote URL instead, and the web view attempts to load that. I also tried creating an img element with its src set to a myfile:// URL and appending it to the document body, and the web view attempts to load that.

Is there a property or security policy or something that prevents WKWebView from loading CSS documents using custom scheme handlers when the document it has open is via https?

Update: this appears to be the same issue as this question.

Tom Hamming
  • 9,484
  • 9
  • 66
  • 131
  • You may want to try inspecting the WKWebView using Safari, then checking the Network tab. See if your request is showing up there, and what its status is. – mattsven Mar 18 '20 at 17:39
  • @mattsven I did. It doesn't show up. And if I make the `href` of the `link` element `https://example.com/foobar.css` it tries to make the request. – Tom Hamming Mar 18 '20 at 18:58
  • The next thing to try is to fire off `await fetch("myfile://bundle/css/engine.css")` from the console, and see if that succeeds or fails. – mattsven Mar 18 '20 at 19:09
  • @mattsven "Fetch API cannot load due to access control checks." That makes sense. Some kind of cross-origin restriction. – Tom Hamming Mar 18 '20 at 20:15
  • Unless you control the HTTPs page and can do what's suggested in the other question you linked to (modifying the CSP policy) the next thing to try is injecting the raw CSS in a ` – mattsven Mar 18 '20 at 20:46
  • @mattsven I can inject the style in a style tag, but it'll load some other stuff like font files from the local device. I do control the remote page, so I'm going to add some headers to allow this. Now that I understand the issue, it totally makes sense from a security perspective. I shouldn't be able to load an arbitrary web page and change its behavior however I want. – Tom Hamming Mar 18 '20 at 20:47
  • I mean, technically you're doing just that by injecting anything at all! :) IIRC cross-origin restrictions are more about making sure that third-parties on a network (like Wifi) cannot modify the page you're on, so by default they force all requests on HTTPs pages to be to HTTPs ("trusted") resources. Regardless, I hope adding the CORS header works for you. – mattsven Mar 18 '20 at 21:04
  • @mattsven I think what will actually work is a `Content-Security-Policy` response header. I will add an answer here if I get it to work. – Tom Hamming Mar 18 '20 at 21:06
  • @mattsven updated with more details in case you know more here? – Tom Hamming Mar 19 '20 at 20:03
  • My thinking here is that you're not going to be able to load content from non-HTTPs sources if your page is HTTPs. That's the general policy of most browsers these days, and WebKit doesn't seem to make an exception for custom schemes. At this point your options are a.) turning all your resources into [blobs](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [base64 data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) b.) serving your resources using an HTTPs local server. c.) setting up some sort of HTTPs redirect but that would be messy/overkill – mattsven Mar 19 '20 at 20:13
  • @mattsven if I add an `img` element to the document with `src='otfile://test.jpg'`, that attempts to load. Why is CSS via a `link` element different? – Tom Hamming Mar 19 '20 at 20:18
  • When you say "attempts to load" — does it actually load? – mattsven Mar 19 '20 at 20:20
  • @mattsven no, because it's not a valid path, so my `WKURLSchemeHandler` returns a 404. But the web view makes the request, whereas it completely ignores the `link` element. – Tom Hamming Mar 19 '20 at 20:21
  • Hmm. I'd still check to be sure. Can you stick a valid image file in to find out? – mattsven Mar 19 '20 at 20:23
  • @mattsven yeah, I changed it to return a valid image and it loads just fine. – Tom Hamming Mar 19 '20 at 20:26
  • And — just to be clear — a `fetch()` for the image works, but a `fetch()` for the stylesheet does not? – mattsven Mar 19 '20 at 20:31
  • @mattsven nope, `fetch(myfile://testImg.png')` produces the same [blocked] message as above. – Tom Hamming Mar 19 '20 at 20:32
  • Are you injecting this image into the page via JS, or is it a natural part of the page? Also, here's the thing: if your CSP was working as you want it to, then both `fetch` requests would go through. It's very odd. I'd also check and see what happens when you place `otfile:` in your `default-src` only, leaving out `style-src` entirely. – mattsven Mar 19 '20 at 20:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/209953/discussion-between-mattsven-and-tom-hamming). – mattsven Mar 19 '20 at 20:59

0 Answers0