mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-09-29 16:57:00 +08:00
Check file access on the host side of the extension api
Fixed: 406034851 Change-Id: I125bfa572ba9e987569e8524da99de84db83d389 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6401296 Reviewed-by: Danil Somsikov <dsv@chromium.org> Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
This commit is contained in:
committed by
Devtools-frontend LUCI CQ
parent
4091180134
commit
cdcca1ad4e
@@ -699,9 +699,7 @@ self.injectedExtensionAPI = function(
|
||||
userAction = true;
|
||||
try {
|
||||
const {resource, lineNumber} = message as {resource: APIImpl.ResourceData, lineNumber: number};
|
||||
if (canAccessResource(resource)) {
|
||||
callback.call(null, new (Constructor(Resource))(resource), lineNumber);
|
||||
}
|
||||
callback.call(null, new (Constructor(Resource))(resource), lineNumber);
|
||||
} finally {
|
||||
userAction = false;
|
||||
}
|
||||
@@ -1228,29 +1226,10 @@ self.injectedExtensionAPI = function(
|
||||
},
|
||||
};
|
||||
|
||||
const protocolGet = Object.getOwnPropertyDescriptor(URL.prototype, 'protocol')?.get;
|
||||
function getProtocol(url: string): string {
|
||||
if (!protocolGet) {
|
||||
throw new Error('URL.protocol is not available');
|
||||
}
|
||||
return protocolGet.call(new URL(url));
|
||||
}
|
||||
|
||||
function canAccessResource(resource: APIImpl.ResourceData): boolean {
|
||||
try {
|
||||
return extensionInfo.allowFileAccess || getProtocol(resource.url) !== 'file:';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function InspectedWindow(this: PublicAPI.Chrome.DevTools.InspectedWindow): void {
|
||||
function dispatchResourceEvent(
|
||||
this: APIImpl.EventSink<(resource: APIImpl.Resource) => unknown>, message: {arguments: unknown[]}): void {
|
||||
const resourceData = message.arguments[0] as APIImpl.ResourceData;
|
||||
if (!canAccessResource(resourceData)) {
|
||||
return;
|
||||
}
|
||||
this._fire(new (Constructor(Resource))(resourceData));
|
||||
}
|
||||
|
||||
@@ -1258,9 +1237,6 @@ self.injectedExtensionAPI = function(
|
||||
this: APIImpl.EventSink<(resource: APIImpl.Resource, content: string) => unknown>,
|
||||
message: {arguments: unknown[]}): void {
|
||||
const resourceData = message.arguments[0] as APIImpl.ResourceData;
|
||||
if (!canAccessResource(resourceData)) {
|
||||
return;
|
||||
}
|
||||
this._fire(new (Constructor(Resource))(resourceData), message.arguments[1] as string);
|
||||
}
|
||||
|
||||
@@ -1323,16 +1299,13 @@ self.injectedExtensionAPI = function(
|
||||
return new (Constructor(Resource))(resourceData);
|
||||
}
|
||||
function callbackWrapper(resources: unknown): void {
|
||||
callback?.((resources as APIImpl.ResourceData[]).filter(canAccessResource).map(wrapResource));
|
||||
callback?.((resources as APIImpl.ResourceData[]).map(wrapResource));
|
||||
}
|
||||
extensionServer.sendRequest({command: PrivateAPI.Commands.GetPageResources}, callback && callbackWrapper);
|
||||
},
|
||||
};
|
||||
|
||||
function ResourceImpl(this: APIImpl.Resource, resourceData: APIImpl.ResourceData): void {
|
||||
if (!canAccessResource(resourceData)) {
|
||||
throw new Error('Resource access not allowed');
|
||||
}
|
||||
this._url = resourceData.url;
|
||||
this._type = resourceData.type;
|
||||
}
|
||||
|
||||
@@ -618,14 +618,13 @@ describeWithDevtoolsExtension('Runtime hosts policy', {hostsPolicy}, context =>
|
||||
assert.exists(context.chrome.devtools);
|
||||
const resources =
|
||||
await new Promise<Chrome.DevTools.Resource[]>(r => context.chrome.devtools?.inspectedWindow.getResources(r));
|
||||
assert.deepEqual(resources.map(r => r.url), [blockedUrl, allowedUrl]);
|
||||
assert.deepEqual(resources.map(r => r.url), [allowedUrl]);
|
||||
|
||||
const resourceContents = await Promise.all(resources.map(
|
||||
resource => new Promise<{url: string, content?: string, encoding?: string}>(
|
||||
r => resource.getContent((content, encoding) => r({url: resource.url, content, encoding})))));
|
||||
|
||||
assert.deepEqual(resourceContents, [
|
||||
{url: blockedUrl, content: undefined, encoding: undefined},
|
||||
{url: allowedUrl, content: 'content', encoding: ''},
|
||||
]);
|
||||
});
|
||||
@@ -683,7 +682,7 @@ describeWithDevtoolsExtension('Runtime hosts policy', {hostsPolicy}, context =>
|
||||
assert.exists(context.chrome.devtools);
|
||||
const resources =
|
||||
await new Promise<Chrome.DevTools.Resource[]>(r => context.chrome.devtools?.inspectedWindow.getResources(r));
|
||||
assert.deepEqual(resources.map(r => r.url), [blockedUrl, allowedUrl]);
|
||||
assert.deepEqual(resources.map(r => r.url), [allowedUrl]);
|
||||
|
||||
assert.deepEqual(project.uiSourceCodeForURL(allowedUrl)?.content(), 'content');
|
||||
assert.deepEqual(project.uiSourceCodeForURL(blockedUrl)?.content(), 'content');
|
||||
@@ -691,8 +690,8 @@ describeWithDevtoolsExtension('Runtime hosts policy', {hostsPolicy}, context =>
|
||||
resource => new Promise<Object|undefined>(r => resource.setContent('modified', true, r)))) as
|
||||
Array<undefined|{code: string, details: string[]}>;
|
||||
|
||||
assert.deepEqual(responses.map(response => response?.code), ['E_FAILED', 'OK']);
|
||||
assert.deepEqual(responses.map(response => response?.details), [['Permission denied'], []]);
|
||||
assert.deepEqual(responses.map(response => response?.code), ['OK']);
|
||||
assert.deepEqual(responses.map(response => response?.details), [[]]);
|
||||
|
||||
assert.deepEqual(project.uiSourceCodeForURL(allowedUrl)?.content(), 'modified');
|
||||
assert.deepEqual(project.uiSourceCodeForURL(blockedUrl)?.content(), 'content');
|
||||
|
||||
@@ -281,27 +281,27 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
}
|
||||
|
||||
notifySearchAction(panelId: string, action: string, searchString?: string): void {
|
||||
this.postNotification(PrivateAPI.Events.PanelSearch + panelId, action, searchString);
|
||||
this.postNotification(PrivateAPI.Events.PanelSearch + panelId, [action, searchString]);
|
||||
}
|
||||
|
||||
notifyViewShown(identifier: string, frameIndex?: number): void {
|
||||
this.postNotification(PrivateAPI.Events.ViewShown + identifier, frameIndex);
|
||||
this.postNotification(PrivateAPI.Events.ViewShown + identifier, [frameIndex]);
|
||||
}
|
||||
|
||||
notifyViewHidden(identifier: string): void {
|
||||
this.postNotification(PrivateAPI.Events.ViewHidden + identifier);
|
||||
this.postNotification(PrivateAPI.Events.ViewHidden + identifier, []);
|
||||
}
|
||||
|
||||
notifyButtonClicked(identifier: string): void {
|
||||
this.postNotification(PrivateAPI.Events.ButtonClicked + identifier);
|
||||
this.postNotification(PrivateAPI.Events.ButtonClicked + identifier, []);
|
||||
}
|
||||
|
||||
profilingStarted(): void {
|
||||
this.postNotification(PrivateAPI.Events.ProfilingStarted);
|
||||
this.postNotification(PrivateAPI.Events.ProfilingStarted, []);
|
||||
}
|
||||
|
||||
profilingStopped(): void {
|
||||
this.postNotification(PrivateAPI.Events.ProfilingStopped);
|
||||
this.postNotification(PrivateAPI.Events.ProfilingStopped, []);
|
||||
}
|
||||
|
||||
private registerLanguageExtensionEndpoint(
|
||||
@@ -449,7 +449,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
return this.status.OK();
|
||||
}
|
||||
|
||||
private onSetFunctionRangesForScript(message: PrivateAPI.ExtensionServerRequestMessage): Record {
|
||||
private onSetFunctionRangesForScript(message: PrivateAPI.ExtensionServerRequestMessage, port: MessagePort): Record {
|
||||
if (message.command !== PrivateAPI.Commands.SetFunctionRangesForScript) {
|
||||
return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.SetFunctionRangesForScript}`);
|
||||
}
|
||||
@@ -457,6 +457,9 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
if (!scriptUrl || !ranges?.length) {
|
||||
return this.status.E_BADARG('command', 'expected valid scriptUrl and non-empty NamedFunctionRanges');
|
||||
}
|
||||
if (!this.extensionAllowedOnURL(scriptUrl as Platform.DevToolsPath.UrlString, port)) {
|
||||
return this.status.E_FAILED('Permission denied');
|
||||
}
|
||||
const uiSourceCode =
|
||||
Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(scriptUrl as Platform.DevToolsPath.UrlString);
|
||||
if (!uiSourceCode) {
|
||||
@@ -527,7 +530,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
this.requests = new Map();
|
||||
this.enableExtensions();
|
||||
const url = event.data.inspectedURL();
|
||||
this.postNotification(PrivateAPI.Events.InspectedURLChanged, url);
|
||||
this.postNotification(PrivateAPI.Events.InspectedURLChanged, [url]);
|
||||
const extensions = this.#pendingExtensions.splice(0);
|
||||
extensions.forEach(e => this.addExtension(e));
|
||||
}
|
||||
@@ -536,20 +539,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
return this.subscribers.has(type);
|
||||
}
|
||||
|
||||
private isNotificationAllowedForExtension(port: MessagePort, type: string, ..._args: unknown[]): boolean {
|
||||
if (type === PrivateAPI.Events.NetworkRequestFinished) {
|
||||
const entry = _args[1] as HAR.Log.EntryDTO;
|
||||
const origin = extensionOrigins.get(port);
|
||||
const extension = origin && this.registeredExtensions.get(origin);
|
||||
if (extension?.isAllowedOnTarget(entry.request.url)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private postNotification(type: string, ..._vararg: unknown[]): void {
|
||||
private postNotification(type: string, args: unknown[], filter?: (extension: RegisteredExtension) => boolean): void {
|
||||
if (!this.extensionsEnabled) {
|
||||
return;
|
||||
}
|
||||
@@ -557,11 +547,19 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
if (!subscribers) {
|
||||
return;
|
||||
}
|
||||
const message = {command: 'notify-' + type, arguments: Array.prototype.slice.call(arguments, 1)};
|
||||
const message = {command: 'notify-' + type, arguments: args};
|
||||
for (const subscriber of subscribers) {
|
||||
if (this.extensionEnabled(subscriber) && this.isNotificationAllowedForExtension(subscriber, type, ..._vararg)) {
|
||||
subscriber.postMessage(message);
|
||||
if (!this.extensionEnabled(subscriber)) {
|
||||
continue;
|
||||
}
|
||||
if (filter) {
|
||||
const origin = extensionOrigins.get(subscriber);
|
||||
const extension = origin && this.registeredExtensions.get(origin);
|
||||
if (!extension || !filter(extension)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
subscriber.postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,8 +846,10 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
|
||||
private handleOpenURL(
|
||||
port: MessagePort, contentProvider: TextUtils.ContentProvider.ContentProvider, lineNumber: number): void {
|
||||
port.postMessage(
|
||||
{command: 'open-resource', resource: this.makeResource(contentProvider), lineNumber: lineNumber + 1});
|
||||
if (this.extensionAllowedOnURL(contentProvider.contentURL(), port)) {
|
||||
port.postMessage(
|
||||
{command: 'open-resource', resource: this.makeResource(contentProvider), lineNumber: lineNumber + 1});
|
||||
}
|
||||
}
|
||||
|
||||
private extensionAllowedOnURL(url: Platform.DevToolsPath.UrlString, port: MessagePort): boolean {
|
||||
@@ -936,7 +936,8 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
}>();
|
||||
function pushResourceData(
|
||||
this: ExtensionServer, contentProvider: TextUtils.ContentProvider.ContentProvider): boolean {
|
||||
if (!resources.has(contentProvider.contentURL())) {
|
||||
if (!resources.has(contentProvider.contentURL()) &&
|
||||
this.extensionAllowedOnURL(contentProvider.contentURL(), port)) {
|
||||
resources.set(contentProvider.contentURL(), this.makeResource(contentProvider));
|
||||
}
|
||||
return false;
|
||||
@@ -999,7 +1000,8 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private onAttachSourceMapToResource(message: PrivateAPI.ExtensionServerRequestMessage): Record|undefined {
|
||||
private onAttachSourceMapToResource(message: PrivateAPI.ExtensionServerRequestMessage, port: MessagePort): Record
|
||||
|undefined {
|
||||
if (message.command !== PrivateAPI.Commands.AttachSourceMapToResource) {
|
||||
return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.GetResourceContent}`);
|
||||
}
|
||||
@@ -1009,6 +1011,9 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
}
|
||||
|
||||
const url = message.contentUrl as Platform.DevToolsPath.UrlString;
|
||||
if (!this.extensionAllowedOnURL(url, port)) {
|
||||
return this.status.E_FAILED('Permission denied');
|
||||
}
|
||||
const contentProvider = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(url);
|
||||
if (!contentProvider) {
|
||||
return this.status.E_NOTFOUND(url);
|
||||
@@ -1145,34 +1150,42 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
|
||||
private notifyResourceAdded(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
|
||||
const uiSourceCode = event.data;
|
||||
this.postNotification(PrivateAPI.Events.ResourceAdded, this.makeResource(uiSourceCode));
|
||||
this.postNotification(
|
||||
PrivateAPI.Events.ResourceAdded, [this.makeResource(uiSourceCode)],
|
||||
extension => extension.isAllowedOnTarget(uiSourceCode.url()));
|
||||
}
|
||||
|
||||
private notifyUISourceCodeContentCommitted(
|
||||
event: Common.EventTarget.EventTargetEvent<Workspace.Workspace.WorkingCopyCommitedEvent>): void {
|
||||
const {uiSourceCode, content} = event.data;
|
||||
this.postNotification(PrivateAPI.Events.ResourceContentCommitted, this.makeResource(uiSourceCode), content);
|
||||
this.postNotification(
|
||||
PrivateAPI.Events.ResourceContentCommitted, [this.makeResource(uiSourceCode), content],
|
||||
extension => extension.isAllowedOnTarget(uiSourceCode.url()));
|
||||
}
|
||||
|
||||
private async notifyRequestFinished(event: Common.EventTarget.EventTargetEvent<SDK.NetworkRequest.NetworkRequest>):
|
||||
Promise<void> {
|
||||
const request = event.data;
|
||||
const entry = await HAR.Log.Entry.build(request, {sanitize: false});
|
||||
this.postNotification(PrivateAPI.Events.NetworkRequestFinished, this.requestId(request), entry);
|
||||
this.postNotification(
|
||||
PrivateAPI.Events.NetworkRequestFinished, [this.requestId(request), entry],
|
||||
extension => extension.isAllowedOnTarget(entry.request.url));
|
||||
}
|
||||
|
||||
private notifyElementsSelectionChanged(): void {
|
||||
this.postNotification(PrivateAPI.Events.PanelObjectSelected + 'elements');
|
||||
this.postNotification(PrivateAPI.Events.PanelObjectSelected + 'elements', []);
|
||||
}
|
||||
|
||||
sourceSelectionChanged(url: Platform.DevToolsPath.UrlString, range: TextUtils.TextRange.TextRange): void {
|
||||
this.postNotification(PrivateAPI.Events.PanelObjectSelected + 'sources', {
|
||||
startLine: range.startLine,
|
||||
startColumn: range.startColumn,
|
||||
endLine: range.endLine,
|
||||
endColumn: range.endColumn,
|
||||
url,
|
||||
});
|
||||
this.postNotification(
|
||||
PrivateAPI.Events.PanelObjectSelected + 'sources', [{
|
||||
startLine: range.startLine,
|
||||
startColumn: range.startColumn,
|
||||
endLine: range.endLine,
|
||||
endColumn: range.endColumn,
|
||||
url,
|
||||
}],
|
||||
extension => extension.isAllowedOnTarget(url));
|
||||
}
|
||||
|
||||
private setInspectedTabId(event: Common.EventTarget.EventTargetEvent<string>): void {
|
||||
|
||||
Reference in New Issue
Block a user