-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(lib/vscode): patch authority in asWebviewUri #3895
Conversation
692d4e4
to
6c18207
Compare
Codecov Report
@@ Coverage Diff @@
## main #3895 +/- ##
=======================================
Coverage 63.51% 63.51%
=======================================
Files 36 36
Lines 1872 1872
Branches 379 379
=======================================
Hits 1189 1189
Misses 580 580
Partials 103 103 Continue to review full report at Codecov.
|
return URI.from({ | ||
scheme: Schemas.https, | ||
authority: `${resource.scheme}+${resource.authority}.${webviewRootResourceAuthority}`, | ||
authority: `${resource.scheme}+${resource.authority.split(':')[0]}.${webviewRootResourceAuthority}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Be careful with this - authority could be an IPv6 address, which will contain colons and possibly also a port number, for example: https://[2607:f8b0:4005:80f::200e]:443
(this is Google's IPv6 address)
It might be preferable to use something like new URL("https://localhost:8443/test/abc").hostname
to do this instead?
> new URL("https://localhost:8443/test/abc").hostname
"localhost"
> new URL("https://[2607:f8b0:4005:80f::200e]:443/").hostname
"[2607:f8b0:4005:80f::200e]"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, didn't know or consider that. Okay, maybe we should do something like this instead?
+${new URL(`http://${resource.authority}/test/abc``).hostname}
-${resource.authority.split(':')[0]}
Would that be okay?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note - the above doesn't account for any exceptions thrown from the URL constructor. I don't know if we want to try and handle that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call @vapurrmaid - let's have @code-asher chime in with his thoughts. I think he's in favor of the least intrusive patch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good, it's probably benign - just something to weigh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhhh IPv6 is an interesting edge case.
To think out loud and summarize a bit, what happens here is that VS Code has a service worker that intercepts all requests in the web view (the iframe). It then extracts the "real" URI from this fake one and sends it to the parent (via postMessage
) where the actual request is made after which the response is sent back (again through postMessage
) for the service worker to resolve with.
So it doesn't matter too much what this URL is as long as:
- The browser thinks it is valid and so actually makes the request inside the iframe for the service worker to then catch.
- The URL has the info in it required to reconstruct the real URL and complete the real request successfully.
On the first point, the browser does nothing at all if there is a colon anywhere in the hostname (encoding it doesn't work either). For example, these all silently do nothing (no error at all from the browser indicating these are invalid):
<script src="https://test:5000.com/test.js"></script>
<script src="https://test%3A5000.com/test.js"></script>
<script src="https://test:.com/test.js"></script>
<script src="https://test.com:d/test.js"></script>
Maybe there is an argument to be made here that VS Code's URI
class should have thrown an error rather than spit out an invalid URL but I'm not sure.
In any case it seems that the solution must involve removing the port from the domain. new URL(url).hostname
feels like an excellent solution to this. I think we will be safe here since we use the host header as the remote authority so it should be valid.
That leads to the second point. Is that port necessary for VS Code to reconstruct the real URL? If it is, we should add it back in the way @jawnsy is proposing. This may mean we also have to tweak the reconstruction side of things to correctly extract the port assuming VS Code hasn't accounted for this case.
But since it apparently works without the port (and without the authority entirely), do we even need to worry about it? Actually I'm confused why the authority would be required at all. The scheme and path should be all VS Code needs since it already knows where the backend is.
Anyway, the reconstructions seems to happen here:
So sure enough if we strip the port then this authority
here will be port-less but we could patch this to add it back in. This is probably the "correct" solution. I wonder why it works without the authority though? Note the may be empty
in the above code.
Well if we chase this down it eventually gets passed through to our file provider through the file provider channel. This channel (like all the channels) are set up during the initial connection and remain open so the authority has no use here. The file provider simply uses fsPath
off the URI and ignores all the other properties of the URI.
In fact if you log all the requests to the file provider you'll see that many of them don't have the authority at all. So maybe we could just remove it but...
That doesn't mean there is absolutely no case where the authority won't be used. For example if a plugin implements their own scheme handler and they try to read the authority for some reason, maybe that could be an issue.
So I think the most correct solution is to stick the port in the proper place then add it back to the authority during the reconstruction linked above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this maybe (and add the port to asWebviewUri
):
const authority = firstHostSegment.slice(scheme.length + 1) + requestUrl.port ? (":" + requestUrl.port) : ""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@code-asher all that makes sense to me. Should we still submit a patch upstream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we could get it fixed upstream. I suppose worst thing that happens is that it gets closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool cool! Let me fix this up here then re-request a review from everyone and then I'll look into following up with a PR upstream to fix there too
lib/vscode/src/vs/workbench/contrib/webview/browser/pre/service-worker.js
Outdated
Show resolved
Hide resolved
4e36e44
to
f5df6a7
Compare
Something strange is happening so converting back to a draft |
f5df6a7
to
ead6c5a
Compare
All fixed - ready for review! |
@@ -55,9 +55,12 @@ export function asWebviewUri( | |||
}); | |||
} | |||
|
|||
// NOTE@coder: Add the port separately because if the port is in the domain the | |||
// URL will be invalid and the browser will not request it. | |||
const authorityUrl = new URL(`http://${resource.authority}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we guaranteed that the incoming authority is a valid URL? otherwise as Grey mentioned before, we'd have to try/catch a possible error right?
Also it does't matter since we're not using the scheme part, but you might want to consider using https here just because it looks a little nicer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK authority
may represent any fully qualified host e.g. localhost:8080
, example.com/any/path
. The scheme may not necessarily be http
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call! I'll use the resource.scheme
here instead of http
or https
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We set the remote authority here:
So potentially it could be blank although I'm not sure how often the host header is missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I think letting the error throw is fine although maybe we should catch and re-throw it with a more helpful message ("host header may be invalid" or something like that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although VS Code does not make the authority mandatory so we should probably gracefully handle a blank authority.
LGTM. Are we still planning to offer this patch upstream? |
return URI.from({ | ||
scheme: Schemas.https, | ||
authority: `${resource.scheme}+${resource.authority}.${webviewRootResourceAuthority}`, | ||
authority: `${resource.scheme}+${authorityUrl.hostname}.${webviewRootResourceAuthority}${authorityUrl.port ? (':' + authorityUrl.port) : ''}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jsjoeio IMO the bug stems from modifying URLs as strings, rather than leaning on the browser’s innate URL constructor Would something like this help with URL?
// e.g. new URL(‘abc://localhost:3000/foo/bar?baz=qux')
// The scheme isn’t too big of a deal here but it helps to lean on what’s known from above.
const authorityUrl = new URL(`${resource.scheme}://${resource.authority}`);
authorityUrl.hostname = `${authorityUrl.hostname}+${resource.authority}.${webviewRootResourceAuthority}`;
// Remove the protocol
const authority = authorityUrl.toString().slice(`${authorityUrl.protocol}//`.length);
return URI.from({
scheme: Schemas.https,
authority,
path: resource.path,
fragment: resource.fragment,
query: resource.query,
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That might work! It sounds like a more complete fix. I'll give it a try and report back! (thanks for the through codeblock too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting to draft stage and then i'll re-request a review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we try/catch
the URL construction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, I think we'll leave as is and let it bubble up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay...looking at your solution, I don't think I fully understand how it helps (more on my lack of understanding, not critiquing your code 😅). I tried it, but I think either the hostname or the port gets mangled (though I might be doing something else wrong). I'm going to leave as is for now. And if you want to follow-up with more details or have me submit another PR to improve this, happy to do so!
@jsjoeio thankyou so much for this. |
Yessir! Added to the PR description just now: microsoft/vscode#130148 |
We do this because resources in webviews don't load properly when the port is used in the resource uri. We're not sure why this is happening but adding this fix to get webviews working like before.
ead6c5a
to
99503fb
Compare
I made Asher's requested changes but he is taking PTO so dismissing (Jonathan double-checked)
With #3895, we caused a regression where the Content-Security-Policy prevented images in the previewer to not work due to the ports in the resource URI. This modifies the CSP in the webview to make sure images are not blocked by CSP. I assume once we upgrade VS Code, we will revert this change.
This PR patches
asWebviewUri
inlib/vscode/src/vs/workbench/api/common/shared/webview.ts
to remove the port number from theauthority
field used to create the URI.For now, we found that removing the port fixes the issue (resources load as expected in webviews), but long-term, we want to understand how this is meant to be used (having the port in the URI authority field).
Related:
Video
Here is a video of an extension using a webview which loads both CSS and JS working.
Screen.Recording.2021-08-04.at.2.06.20.PM.mov
Fixes N/A
TODOS
asWebviewUri
fix: handle localhost and port in asWebviewUri microsoft/vscode#130148