mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-04 01:44:44 -06:00
parent
424ef28d9b
commit
8e2c1ac873
@ -5,12 +5,11 @@
|
||||
|
||||
import { VSBufferReadableStream } from '../../../../base/common/buffer.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { isUNC } from '../../../../base/common/extpath.js';
|
||||
import { Schemas } from '../../../../base/common/network.js';
|
||||
import { normalize, sep } from '../../../../base/common/path.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { FileOperationError, FileOperationResult, IFileService, IWriteFileOptions } from '../../../../platform/files/common/files.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
|
||||
import { getWebviewContentMimeType } from '../../../../platform/webview/common/mimeTypes.js';
|
||||
|
||||
export namespace WebviewResourceResponse {
|
||||
@ -48,11 +47,12 @@ export async function loadLocalResource(
|
||||
ifNoneMatch: string | undefined;
|
||||
roots: ReadonlyArray<URI>;
|
||||
},
|
||||
uriIdentityService: IUriIdentityService,
|
||||
fileService: IFileService,
|
||||
logService: ILogService,
|
||||
token: CancellationToken,
|
||||
): Promise<WebviewResourceResponse.StreamResponse> {
|
||||
const resourceToLoad = getResourceToLoad(requestUri, options.roots);
|
||||
const resourceToLoad = getResourceToLoad(requestUri, options.roots, uriIdentityService);
|
||||
|
||||
logService.trace(`Webview.loadLocalResource - trying to load resource. requestUri=${requestUri}, resourceToLoad=${resourceToLoad}`);
|
||||
|
||||
@ -84,12 +84,13 @@ export async function loadLocalResource(
|
||||
}
|
||||
}
|
||||
|
||||
function getResourceToLoad(
|
||||
export function getResourceToLoad(
|
||||
requestUri: URI,
|
||||
roots: ReadonlyArray<URI>,
|
||||
uriIdentityService: IUriIdentityService,
|
||||
): URI | undefined {
|
||||
for (const root of roots) {
|
||||
if (containsResource(root, requestUri)) {
|
||||
if (containsResource(root, requestUri, uriIdentityService)) {
|
||||
return normalizeResourcePath(requestUri);
|
||||
}
|
||||
}
|
||||
@ -97,20 +98,15 @@ function getResourceToLoad(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function containsResource(root: URI, resource: URI): boolean {
|
||||
if (root.scheme !== resource.scheme) {
|
||||
function containsResource(root: URI, resource: URI, uriIdentityService: IUriIdentityService): boolean {
|
||||
if (uriIdentityService.extUri.isEqual(root, resource, /* ignoreFragment */ true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let resourceFsPath = normalize(resource.fsPath);
|
||||
let rootPath = normalize(root.fsPath + (root.fsPath.endsWith(sep) ? '' : sep));
|
||||
|
||||
if (isUNC(root.fsPath) && isUNC(resource.fsPath)) {
|
||||
rootPath = rootPath.toLowerCase();
|
||||
resourceFsPath = resourceFsPath.toLowerCase();
|
||||
}
|
||||
|
||||
return resourceFsPath.startsWith(rootPath);
|
||||
return uriIdentityService.extUri.isEqualOrParent(
|
||||
resource.with({ query: '' }),
|
||||
root,
|
||||
/* ignoreFragment */ true);
|
||||
}
|
||||
|
||||
function normalizeResourcePath(resource: URI): URI {
|
||||
|
||||
@ -30,6 +30,7 @@ import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { INotificationService } from '../../../../platform/notification/common/notification.js';
|
||||
import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
|
||||
import { ITunnelService } from '../../../../platform/tunnel/common/tunnel.js';
|
||||
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
|
||||
import { WebviewPortMappingManager } from '../../../../platform/webview/common/webviewPortMapping.js';
|
||||
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
|
||||
import { decodeAuthority, webviewGenericCspSource, webviewRootResourceAuthority } from '../common/webview.js';
|
||||
@ -163,6 +164,7 @@ export class WebviewElement extends Disposable implements IWebviewElement, Webvi
|
||||
@ITunnelService private readonly _tunnelService: ITunnelService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
|
||||
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -763,7 +765,7 @@ export class WebviewElement extends Disposable implements IWebviewElement, Webvi
|
||||
const result = await loadLocalResource(uri, {
|
||||
ifNoneMatch,
|
||||
roots: this._content.options.localResourceRoots || [],
|
||||
}, this._fileService, this._logService, this._resourceLoadingCts.token);
|
||||
}, this._uriIdentityService, this._fileService, this._logService, this._resourceLoadingCts.token);
|
||||
|
||||
switch (result.type) {
|
||||
case WebviewResourceResponse.Type.Success: {
|
||||
|
||||
@ -19,6 +19,7 @@ import { INativeHostService } from '../../../../platform/native/common/native.js
|
||||
import { INotificationService } from '../../../../platform/notification/common/notification.js';
|
||||
import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js';
|
||||
import { ITunnelService } from '../../../../platform/tunnel/common/tunnel.js';
|
||||
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
|
||||
import { FindInFrameOptions, IWebviewManagerService } from '../../../../platform/webview/common/webviewManagerService.js';
|
||||
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
|
||||
import { WebviewThemeDataProvider } from '../browser/themeing.js';
|
||||
@ -56,10 +57,11 @@ export class ElectronWebviewElement extends WebviewElement {
|
||||
@INativeHostService private readonly _nativeHostService: INativeHostService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
@IUriIdentityService uriIdentityService: IUriIdentityService,
|
||||
) {
|
||||
super(initInfo, webviewThemeDataProvider,
|
||||
configurationService, contextMenuService, notificationService, environmentService,
|
||||
fileService, logService, remoteAuthorityResolverService, tunnelService, instantiationService, accessibilityService);
|
||||
fileService, logService, remoteAuthorityResolverService, tunnelService, instantiationService, accessibilityService, uriIdentityService);
|
||||
|
||||
this._webviewKeyboardHandler = new WindowIgnoreMenuShortcutsManager(configurationService, mainProcessService, _nativeHostService);
|
||||
|
||||
|
||||
@ -0,0 +1,243 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert from 'assert';
|
||||
import { isWindows } from '../../../../../base/common/platform.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
|
||||
import { FileService } from '../../../../../platform/files/common/fileService.js';
|
||||
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';
|
||||
import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js';
|
||||
import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js';
|
||||
import { getResourceToLoad } from '../../browser/resourceLoading.js';
|
||||
|
||||
suite('Webview Resource Loading - getResourceToLoad', () => {
|
||||
const disposableStore = ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
let uriIdentityService: IUriIdentityService;
|
||||
|
||||
setup(() => {
|
||||
const instantiationService = disposableStore.add(new TestInstantiationService());
|
||||
instantiationService.stub(ILogService, NullLogService);
|
||||
const fileService = disposableStore.add(new FileService(instantiationService.get(ILogService)));
|
||||
uriIdentityService = instantiationService.stub(IUriIdentityService, disposableStore.add(new UriIdentityService(fileService)));
|
||||
});
|
||||
|
||||
test('Returns resource when file is under root', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/project/file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('Returns resource when file is in nested directory', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/project/subdir/nested/file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('Fails when file is outside root', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/other/file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('Fails when file is root', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const result = getResourceToLoad(root, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('Fails when file is sibling of root directory', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
{
|
||||
const resource = URI.file('/home/user/projectOther/file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
}
|
||||
{
|
||||
const resource = URI.file('/home/user/project.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
test('Returns resource when root ends with /', () => {
|
||||
const root = URI.file('/home/user/project/');
|
||||
const resource = URI.file('/home/user/project/file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('Fails for sibling when root ends with / ', () => {
|
||||
const root = URI.file('/home/user/project/');
|
||||
const resource = URI.file('/home/user/projectOther/file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
(!isWindows /* UNC is windows only */ ? suite.skip : suite)('UNC paths', () => {
|
||||
test('Returns resource when file is under UNC root', () => {
|
||||
const root = URI.file('\\\\server\\share\\folder');
|
||||
const resource = URI.file('\\\\server\\share\\folder\\file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('Returns resource with case-insensitive comparison for UNC paths', () => {
|
||||
const root = URI.file('\\\\SERVER\\SHARE\\folder');
|
||||
const resource = URI.file('\\\\server\\share\\folder\\file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('Fails when file is outside UNC root', () => {
|
||||
const root = URI.file('\\\\server\\share\\folder');
|
||||
const resource = URI.file('\\\\server\\share\\other\\file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('Fails when UNC server differs', () => {
|
||||
const root = URI.file('\\\\server1\\share\\folder');
|
||||
const resource = URI.file('\\\\server2\\share\\folder\\file.txt');
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Different authorities', () => {
|
||||
test('Returns resource when authorities match', () => {
|
||||
const root = URI.from({ scheme: 'test-scheme', authority: 'ssh-remote+myserver', path: '/home/user/project' });
|
||||
const resource = URI.from({ scheme: 'test-scheme', authority: 'ssh-remote+myserver', path: '/home/user/project/file.txt' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.ok(result);
|
||||
});
|
||||
|
||||
test('Fails when authorities differ', () => {
|
||||
const root = URI.from({ scheme: 'test-scheme', authority: 'ssh-remote+server1', path: '/home/user/project' });
|
||||
const resource = URI.from({ scheme: 'test-scheme', authority: 'ssh-remote+server2', path: '/home/user/project/file.txt' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('handles empty authority', () => {
|
||||
const root = URI.from({ scheme: 'test-scheme', authority: '', path: '/home/user/project' });
|
||||
const resource = URI.from({ scheme: 'test-scheme', authority: '', path: '/home/user/project/file.txt' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
});
|
||||
|
||||
suite('Different schemes', () => {
|
||||
test('Fails when schemes differ', () => {
|
||||
const root = URI.from({ scheme: 'file', path: '/home/user/project' });
|
||||
const resource = URI.from({ scheme: 'http', path: '/home/user/project/file.txt' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('Returns resource when schemes match', () => {
|
||||
const root = URI.from({ scheme: 'custom-scheme', path: '/home/user/project' });
|
||||
const resource = URI.from({ scheme: 'custom-scheme', path: '/home/user/project/file.txt' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('normalizes vscode-remote scheme', () => {
|
||||
const root = URI.from({ scheme: 'vscode-remote', authority: 'test', path: '/home/user/project' });
|
||||
const resource = URI.from({ scheme: 'vscode-remote', authority: 'test', path: '/home/user/project/file.txt' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
|
||||
assert.ok(result);
|
||||
assert.strictEqual(result.scheme, 'vscode-remote');
|
||||
assert.strictEqual(result.authority, 'test');
|
||||
assert.strictEqual(result.path, '/vscode-resource');
|
||||
const query = JSON.parse(result.query);
|
||||
assert.strictEqual(query.requestResourcePath, '/home/user/project/file.txt');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Fragment and query strings', () => {
|
||||
test('preserves fragment in returned URI', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/project/file.txt').with({ fragment: 'section1' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.fragment, 'section1');
|
||||
});
|
||||
|
||||
test('preserves query in returned URI', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/project/file.txt').with({ query: 'version=2' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.query, 'version=2');
|
||||
});
|
||||
|
||||
test('preserves both fragment and query', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/project/file.txt').with({ fragment: 'section1', query: 'version=2' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result?.fragment, 'section1');
|
||||
assert.strictEqual(result?.query, 'version=2');
|
||||
});
|
||||
|
||||
test('still validates path containment with query params', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/other/file.txt').with({ query: 'version=2' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('still validates path containment with fragment', () => {
|
||||
const root = URI.file('/home/user/project');
|
||||
const resource = URI.file('/home/user/other/file.txt').with({ fragment: 'section1' });
|
||||
const result = getResourceToLoad(resource, [root], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Multiple roots', () => {
|
||||
test('Returns resource when file is under one of multiple roots', () => {
|
||||
const roots = [
|
||||
URI.file('/home/user/project1'),
|
||||
URI.file('/home/user/project2'),
|
||||
URI.file('/home/user/project3')
|
||||
];
|
||||
const resource = URI.file('/home/user/project2/file.txt');
|
||||
const result = getResourceToLoad(resource, roots, uriIdentityService);
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('Fails when file is not under any root', () => {
|
||||
const roots = [
|
||||
URI.file('/home/user/project1'),
|
||||
URI.file('/home/user/project2')
|
||||
];
|
||||
const resource = URI.file('/home/user/other/file.txt');
|
||||
const result = getResourceToLoad(resource, roots, uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
|
||||
test('Returns resource matching first valid root', () => {
|
||||
const roots = [
|
||||
URI.file('/home/user/project'),
|
||||
URI.file('/home/user/project/subdir')
|
||||
];
|
||||
const resource = URI.file('/home/user/project/subdir/file.txt');
|
||||
const result = getResourceToLoad(resource, roots, uriIdentityService);
|
||||
// Should match first root in the list
|
||||
assert.strictEqual(result?.toString(), resource.toString());
|
||||
});
|
||||
|
||||
test('handles empty roots array', () => {
|
||||
const resource = URI.file('/home/user/project/file.txt');
|
||||
const result = getResourceToLoad(resource, [], uriIdentityService);
|
||||
assert.strictEqual(result, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user