Try to encode authority for asWebviewUri
Fixes #123494 This change attempts to address issues where `asWebviewUri` could create an valid uri but an invalid http uri. This issue appears to be when the host contains a forbidden character (even if it is percent encoded): https://url.spec.whatwg.org/#forbidden-host-code-point Having one of these characters in the host causes the url to become invalid, resulting in network requests never being made To fix this, I've added a very simple (poor) encoding mechanism for the authority. I went with my own mechanism over something like base64 because base64 can output `/` and it's also not easy to use across both node and browsers. I also considered base62 and punycode, but both of these would be best to pull in libraries for
This commit is contained in:
parent
813c0b2178
commit
649dd18019
3 changed files with 49 additions and 5 deletions
|
@ -3,6 +3,7 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { CharCode } from 'vs/base/common/charCode';
|
||||||
import { Schemas } from 'vs/base/common/network';
|
import { Schemas } from 'vs/base/common/network';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import type * as vscode from 'vscode';
|
import type * as vscode from 'vscode';
|
||||||
|
@ -57,9 +58,27 @@ export function asWebviewUri(
|
||||||
|
|
||||||
return URI.from({
|
return URI.from({
|
||||||
scheme: Schemas.https,
|
scheme: Schemas.https,
|
||||||
authority: `${resource.scheme}+${resource.authority}.${webviewRootResourceAuthority}`,
|
authority: `${resource.scheme}+${encodeAuthority(resource.authority)}.${webviewRootResourceAuthority}`,
|
||||||
path: resource.path,
|
path: resource.path,
|
||||||
fragment: resource.fragment,
|
fragment: resource.fragment,
|
||||||
query: resource.query,
|
query: resource.query,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encodeAuthority(authority: string): string {
|
||||||
|
return authority.replace(/./g, char => {
|
||||||
|
const code = char.charCodeAt(0);
|
||||||
|
if (
|
||||||
|
(code >= CharCode.a && code <= CharCode.z)
|
||||||
|
|| (code >= CharCode.A && code <= CharCode.Z)
|
||||||
|
|| (code >= CharCode.Digit0 && code <= CharCode.Digit9)
|
||||||
|
) {
|
||||||
|
return char;
|
||||||
|
}
|
||||||
|
return '-' + code.toString(16).padStart(4, '0');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeAuthority(authority: string) {
|
||||||
|
return authority.replace(/-([0-9a-f]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16)));
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot
|
||||||
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
|
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping';
|
import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping';
|
||||||
import { asWebviewUri, webviewGenericCspSource, webviewRootResourceAuthority } from 'vs/workbench/api/common/shared/webview';
|
import { asWebviewUri, decodeAuthority, webviewGenericCspSource, webviewRootResourceAuthority } from 'vs/workbench/api/common/shared/webview';
|
||||||
import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading';
|
import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading';
|
||||||
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
|
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
|
||||||
import { areWebviewContentOptionsEqual, Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
|
import { areWebviewContentOptionsEqual, Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
|
||||||
|
@ -243,9 +243,11 @@ export class IFrameWebview extends Disposable implements Webview {
|
||||||
|
|
||||||
this._register(this.on(WebviewMessageChannels.loadResource, (entry: { id: number, path: string, query: string, scheme: string, authority: string, ifNoneMatch?: string }) => {
|
this._register(this.on(WebviewMessageChannels.loadResource, (entry: { id: number, path: string, query: string, scheme: string, authority: string, ifNoneMatch?: string }) => {
|
||||||
try {
|
try {
|
||||||
|
// Restore the authority we previously encoded
|
||||||
|
const authority = decodeAuthority(entry.authority);
|
||||||
const uri = URI.from({
|
const uri = URI.from({
|
||||||
scheme: entry.scheme,
|
scheme: entry.scheme,
|
||||||
authority: entry.authority,
|
authority: authority,
|
||||||
path: decodeURIComponent(entry.path), // This gets re-encoded
|
path: decodeURIComponent(entry.path), // This gets re-encoded
|
||||||
query: entry.query ? decodeURIComponent(entry.query) : entry.query,
|
query: entry.query ? decodeURIComponent(entry.query) : entry.query,
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,12 +15,12 @@ import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDep
|
||||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||||
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
|
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
|
||||||
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
|
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
|
||||||
import { webviewResourceBaseHost } from 'vs/workbench/api/common/shared/webview';
|
import { decodeAuthority, webviewResourceBaseHost } from 'vs/workbench/api/common/shared/webview';
|
||||||
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||||
import type * as vscode from 'vscode';
|
import type * as vscode from 'vscode';
|
||||||
import { SingleProxyRPCProtocol } from './testRPCProtocol';
|
import { SingleProxyRPCProtocol } from './testRPCProtocol';
|
||||||
|
|
||||||
suite('ExtHostWebview', () => {
|
suite.only('ExtHostWebview', () => {
|
||||||
|
|
||||||
let rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined;
|
let rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined;
|
||||||
|
|
||||||
|
@ -119,6 +119,29 @@ suite('ExtHostWebview', () => {
|
||||||
'Unix basic'
|
'Unix basic'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('asWebviewUri for remote with / and + in name', () => {
|
||||||
|
const webview = createWebview(rpcProtocol, /* remoteAuthority */ 'remote');
|
||||||
|
const authority = 'ssh-remote+localhost=foo/bar';
|
||||||
|
|
||||||
|
const sourceUri = URI.from({
|
||||||
|
scheme: 'vscode-remote',
|
||||||
|
authority: authority,
|
||||||
|
path: '/Users/cody/x.png'
|
||||||
|
});
|
||||||
|
|
||||||
|
const webviewUri = webview.webview.asWebviewUri(sourceUri);
|
||||||
|
assert.strictEqual(
|
||||||
|
webviewUri.toString(),
|
||||||
|
`https://vscode-remote%2Bssh-002dremote-002blocalhost-003dfoo-002fbar.vscode-resource.vscode-webview.net/Users/cody/x.png`,
|
||||||
|
'Check transform');
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
decodeAuthority(webviewUri.authority),
|
||||||
|
`vscode-remote+${authority}.vscode-resource.vscode-webview.net`,
|
||||||
|
'Check decoded authority'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createWebview(rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined, remoteAuthority: string | undefined) {
|
function createWebview(rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined, remoteAuthority: string | undefined) {
|
||||||
|
|
Loading…
Reference in a new issue