| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Common from '../../core/common/common.js'; |
| import * as Host from '../../core/host/host.js'; |
| import * as Platform from '../../core/platform/platform.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| import * as Protocol from '../../generated/protocol.js'; |
| import { |
| createTarget, |
| deinitializeGlobalVars, |
| initializeGlobalVars, |
| } from '../../testing/EnvironmentHelpers.js'; |
| import {describeWithMockConnection} from '../../testing/MockConnection.js'; |
| import {createWorkspaceProject, setUpEnvironment} from '../../testing/OverridesHelpers.js'; |
| import {setMockResourceTree} from '../../testing/ResourceTreeHelpers.js'; |
| import {createFileSystemUISourceCode} from '../../testing/UISourceCodeHelpers.js'; |
| import * as Persistence from '../persistence/persistence.js'; |
| import * as Workspace from '../workspace/workspace.js'; |
| |
| const {urlString} = Platform.DevToolsPath; |
| const setUpEnvironmentWithUISourceCode = |
| (url: string, resourceType: Common.ResourceType.ResourceType, project?: Workspace.Workspace.Project) => { |
| const {workspace, networkPersistenceManager} = setUpEnvironment(); |
| |
| if (!project) { |
| project = {id: () => url, type: () => Workspace.Workspace.projectTypes.Network} as Workspace.Workspace.Project; |
| } |
| |
| const uiSourceCode = new Workspace.UISourceCode.UISourceCode(project, urlString`${url}`, resourceType); |
| |
| project.uiSourceCodes = () => [uiSourceCode]; |
| |
| workspace.addProject(project); |
| |
| return {workspace, project, uiSourceCode, networkPersistenceManager}; |
| }; |
| |
| describeWithMockConnection('NetworkPersistenceManager', () => { |
| beforeEach(async () => { |
| SDK.NetworkManager.MultitargetNetworkManager.dispose(); |
| const target = createTarget(); |
| sinon.stub(target.fetchAgent(), 'invoke_enable'); |
| }); |
| |
| it('can create an overridden file with Local Overrides enabled', async () => { |
| const url = 'http://www.example.com/list-fetch.json'; |
| const resourceType = Common.ResourceType.resourceTypes.Document; |
| |
| const {uiSourceCode} = setUpEnvironmentWithUISourceCode(url, resourceType); |
| const networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, []); |
| |
| const saveSpy = sinon.spy(networkPersistenceManager, 'saveUISourceCodeForOverrides'); |
| const actual = await networkPersistenceManager.setupAndStartLocalOverrides(uiSourceCode); |
| |
| saveSpy.restore(); |
| |
| assert.isTrue(saveSpy.calledOnce, 'should override content once'); |
| assert.isTrue(actual, 'should complete override successfully'); |
| }); |
| |
| it('can create an overridden file with Local Overrides folder set up but disabled', async () => { |
| Common.Settings.Settings.instance().moduleSetting('persistence-network-overrides-enabled').set(false); |
| |
| const url = 'http://www.example.com/list-xhr.json'; |
| const resourceType = Common.ResourceType.resourceTypes.Document; |
| |
| const {uiSourceCode} = setUpEnvironmentWithUISourceCode(url, resourceType); |
| const networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, []); |
| |
| const saveSpy = sinon.spy(networkPersistenceManager, 'saveUISourceCodeForOverrides'); |
| const actual = await networkPersistenceManager.setupAndStartLocalOverrides(uiSourceCode); |
| |
| saveSpy.restore(); |
| |
| assert.isTrue(saveSpy.calledOnce, 'should override content once'); |
| assert.isTrue(actual, 'should complete override successfully'); |
| }); |
| }); |
| |
| describeWithMockConnection('NetworkPersistenceManager', () => { |
| it('does not create interception patterns for forbidden URLs', async () => { |
| SDK.NetworkManager.MultitargetNetworkManager.dispose(); |
| const target = createTarget(); |
| |
| const networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, [ |
| {name: 'helloWorld.html', path: 'www.example.com/', content: 'Hello World!'}, |
| {name: 'forbidden.html', path: 'chromewebstore.google.com/', content: 'Chrome Web Store'}, |
| {name: 'flags', path: 'chrome:/', content: 'Chrome Flags'}, |
| {name: 'index.html', path: 'chrome.google.com/', content: 'Chrome'}, |
| {name: 'allowed.html', path: 'www.google.com/', content: 'Google Search'}, |
| ]); |
| |
| const stub = sinon.stub(target.fetchAgent(), 'invoke_enable'); |
| await networkPersistenceManager.updateInterceptionPatternsForTests(); |
| |
| const patterns = stub.lastCall.args[0].patterns; |
| const expected = [ |
| { |
| urlPattern: 'http?://www.example.com/helloWorld.html', |
| requestStage: Protocol.Fetch.RequestStage.Response, |
| }, |
| { |
| urlPattern: 'http?://www.google.com/allowed.html', |
| requestStage: Protocol.Fetch.RequestStage.Response, |
| }, |
| ]; |
| assert.deepEqual(patterns, expected); |
| }); |
| |
| it('recognizes forbidden network URLs', () => { |
| assert.isTrue(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl( |
| urlString`chrome://version`)); |
| assert.isTrue(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl( |
| urlString`https://chromewebstore.google.com/index.html`)); |
| assert.isTrue(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl( |
| urlString`https://chrome.google.com/script.js`)); |
| assert.isFalse(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl( |
| urlString`https://www.example.com/script.js`)); |
| }); |
| }); |
| |
| describeWithMockConnection('NetworkPersistenceManager', () => { |
| let networkPersistenceManager: Persistence.NetworkPersistenceManager.NetworkPersistenceManager; |
| |
| beforeEach(async () => { |
| SDK.NetworkManager.MultitargetNetworkManager.dispose(); |
| setMockResourceTree(false); |
| const target = createTarget(); |
| networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, [ |
| { |
| name: '.headers', |
| path: 'www.example.com/', |
| content: `[ |
| { |
| "applyTo": "index.html", |
| "headers": [{ |
| "name": "index-only", |
| "value": "only added to index.html" |
| }] |
| }, |
| { |
| "applyTo": "*.css", |
| "headers": [{ |
| "name": "css-only", |
| "value": "only added to css files" |
| }] |
| }, |
| { |
| "applyTo": "path/to/*.js", |
| "headers": [{ |
| "name": "another-header", |
| "value": "only added to specific path" |
| }] |
| }, |
| { |
| "applyTo": "repeated.html", |
| "headers": [ |
| { |
| "name": "repeated", |
| "value": "first override" |
| }, |
| { |
| "name": "repeated", |
| "value": "second override" |
| } |
| ] |
| } |
| ]`, |
| }, |
| { |
| name: '.headers', |
| path: '', |
| content: `[ |
| { |
| "applyTo": "*", |
| "headers": [{ |
| "name": "age", |
| "value": "overridden" |
| }] |
| } |
| ]`, |
| }, |
| {name: 'helloWorld.html', path: 'www.example.com/', content: 'Hello World!'}, |
| ]); |
| sinon.stub(target.fetchAgent(), 'invoke_enable'); |
| await networkPersistenceManager.updateInterceptionPatternsForTests(); |
| }); |
| |
| it('merges request headers with override without overlap', async () => { |
| const interceptedRequest = { |
| request: { |
| url: 'https://www.example.com/', |
| }, |
| responseHeaders: [ |
| {name: 'server', value: 'DevTools mock server'}, |
| ], |
| } as SDK.NetworkManager.InterceptedRequest; |
| |
| const expected = [ |
| {name: 'age', value: 'overridden'}, |
| {name: 'index-only', value: 'only added to index.html'}, |
| {name: 'server', value: 'DevTools mock server'}, |
| ]; |
| const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest); |
| assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected); |
| }); |
| |
| it('merges request headers with override with overlap', async () => { |
| const interceptedRequest = { |
| request: { |
| url: 'https://www.example.com/index.html', |
| }, |
| responseHeaders: [ |
| {name: 'server', value: 'DevTools mock server'}, |
| {name: 'age', value: '1'}, |
| ], |
| } as SDK.NetworkManager.InterceptedRequest; |
| |
| const expected = [ |
| {name: 'age', value: 'overridden'}, |
| {name: 'index-only', value: 'only added to index.html'}, |
| {name: 'server', value: 'DevTools mock server'}, |
| ]; |
| const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest); |
| assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected); |
| }); |
| |
| it('merges request headers with override with file type wildcard', async () => { |
| const interceptedRequest = { |
| request: { |
| url: 'https://www.example.com/styles.css', |
| }, |
| responseHeaders: [ |
| {name: 'server', value: 'DevTools mock server'}, |
| {name: 'age', value: '1'}, |
| ], |
| } as SDK.NetworkManager.InterceptedRequest; |
| |
| const expected = [ |
| {name: 'age', value: 'overridden'}, |
| {name: 'css-only', value: 'only added to css files'}, |
| {name: 'server', value: 'DevTools mock server'}, |
| ]; |
| const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest); |
| assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected); |
| }); |
| |
| it('merges request headers with override with specific path', async () => { |
| const interceptedRequest = { |
| request: { |
| url: 'https://www.example.com/path/to/script.js', |
| }, |
| responseHeaders: [ |
| {name: 'server', value: 'DevTools mock server'}, |
| {name: 'age', value: '1'}, |
| ], |
| } as SDK.NetworkManager.InterceptedRequest; |
| |
| const expected = [ |
| {name: 'age', value: 'overridden'}, |
| {name: 'another-header', value: 'only added to specific path'}, |
| {name: 'server', value: 'DevTools mock server'}, |
| ]; |
| const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest); |
| assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected); |
| }); |
| |
| it('merges request headers only when domain matches', async () => { |
| const interceptedRequest = { |
| request: { |
| url: 'https://www.web.dev/index.html', |
| }, |
| responseHeaders: [ |
| {name: 'server', value: 'DevTools mock server'}, |
| ], |
| } as SDK.NetworkManager.InterceptedRequest; |
| |
| const expected = [ |
| {name: 'age', value: 'overridden'}, |
| {name: 'server', value: 'DevTools mock server'}, |
| ]; |
| const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest); |
| assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected); |
| }); |
| |
| it('merges headers while leaving muliple headers with the same name unchanged', async () => { |
| const interceptedRequest = { |
| request: { |
| url: 'https://www.example.com/index.html', |
| }, |
| responseHeaders: [ |
| {name: 'repeated', value: 'first'}, |
| {name: 'repeated', value: 'second'}, |
| {name: 'repeated', value: 'third'}, |
| ], |
| } as SDK.NetworkManager.InterceptedRequest; |
| |
| const expected = [ |
| {name: 'age', value: 'overridden'}, |
| {name: 'index-only', value: 'only added to index.html'}, |
| {name: 'repeated', value: 'first'}, |
| {name: 'repeated', value: 'second'}, |
| {name: 'repeated', value: 'third'}, |
| ]; |
| const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest); |
| assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected); |
| }); |
| |
| it('merges headers and can override muliple headers with the same name', async () => { |
| const interceptedRequest = { |
| request: { |
| url: 'https://www.example.com/repeated.html', |
| }, |
| responseHeaders: [ |
| {name: 'repeated', value: 'first'}, |
| {name: 'repeated', value: 'second'}, |
| {name: 'repeated', value: 'third'}, |
| ], |
| } as SDK.NetworkManager.InterceptedRequest; |
| |
| const expected = [ |
| {name: 'age', value: 'overridden'}, |
| {name: 'repeated', value: 'first override'}, |
| {name: 'repeated', value: 'second override'}, |
| ]; |
| const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest); |
| assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected); |
| }); |
| |
| it('translates URLs into raw and encoded paths', async () => { |
| let toTest = [ |
| // Simple tests. |
| { |
| url: 'www.example.com/', |
| raw: 'www.example.com/index.html', |
| encoded: 'www.example.com/index.html', |
| }, |
| { |
| url: 'www.example.com/simple', |
| raw: 'www.example.com/simple', |
| encoded: 'www.example.com/simple', |
| }, |
| { |
| url: 'www.example.com/hello/foo/bar', |
| raw: 'www.example.com/hello/foo/bar', |
| encoded: 'www.example.com/hello/foo/bar', |
| }, |
| { |
| url: 'www.example.com/.', |
| raw: 'www.example.com/.', |
| encoded: 'www.example.com/', |
| }, |
| { |
| url: 'localhost:8090/endswith.', |
| raw: 'localhost:8090/endswith.', |
| encoded: 'localhost:8090/endswith.', |
| }, |
| // Query parameters. |
| { |
| url: 'example.com/fo?o/bar', |
| raw: 'example.com/fo?o%2Fbar', |
| encoded: 'example.com/fo%3Fo%252Fbar', |
| }, |
| { |
| url: 'example.com/foo?/bar', |
| raw: 'example.com/foo?%2Fbar', |
| encoded: 'example.com/foo%3F%252Fbar', |
| }, |
| { |
| url: 'example.com/foo/?bar', |
| raw: 'example.com/foo/?bar', |
| encoded: 'example.com/foo/%3Fbar', |
| }, |
| { |
| url: 'example.com/?foo/bar/3', |
| raw: 'example.com/?foo%2Fbar%2F3', |
| encoded: 'example.com/%3Ffoo%252Fbar%252F3', |
| }, |
| { |
| url: 'example.com/foo/bar/?3hello/bar', |
| raw: 'example.com/foo/bar/?3hello%2Fbar', |
| encoded: 'example.com/foo/bar/%3F3hello%252Fbar', |
| }, |
| {url: 'https://www.example.com/?foo=bar', raw: 'www.example.com/?foo=bar', encoded: 'www.example.com/%3Ffoo=bar'}, |
| { |
| url: 'http://www.example.com/?foo=bar/', |
| raw: 'www.example.com/?foo=bar%2F', |
| encoded: 'www.example.com/%3Ffoo=bar%252F', |
| }, |
| { |
| url: 'http://www.example.com/?foo=bar?', |
| raw: 'www.example.com/?foo=bar?', |
| encoded: 'www.example.com/%3Ffoo=bar%3F', |
| }, |
| // Hash parameters. |
| { |
| url: 'example.com/?foo/bar/3#hello/bar', |
| raw: 'example.com/?foo%2Fbar%2F3', |
| encoded: 'example.com/%3Ffoo%252Fbar%252F3', |
| }, |
| { |
| url: 'example.com/#foo/bar/3hello/bar', |
| raw: 'example.com/index.html', |
| encoded: 'example.com/index.html', |
| }, |
| { |
| url: 'example.com/foo/bar/#?3hello/bar', |
| raw: 'example.com/foo/bar/index.html', |
| encoded: 'example.com/foo/bar/index.html', |
| }, |
| { |
| url: 'example.com/foo.js#', |
| raw: 'example.com/foo.js', |
| encoded: 'example.com/foo.js', |
| }, |
| { |
| url: 'http://www.web.dev/path/page.html#anchor', |
| raw: 'www.web.dev/path/page.html', |
| encoded: 'www.web.dev/path/page.html', |
| }, |
| { |
| url: 'http://www.example.com/file&$*?.html', |
| raw: 'www.example.com/file&$%2A?.html', |
| encoded: 'www.example.com/file&$%252A%3F.html', |
| }, |
| { |
| url: 'localhost:8090/', |
| raw: 'localhost:8090/index.html', |
| encoded: 'localhost:8090/index.html', |
| }, |
| {url: 'localhost:8090/lpt1', raw: 'localhost:8090/lpt1', encoded: 'localhost:8090/lpt1'}, |
| { |
| url: 'example.com/foo .js', |
| raw: 'example.com/foo%20.js', |
| encoded: 'example.com/foo%2520.js', |
| }, |
| { |
| url: 'example.com///foo.js', |
| raw: 'example.com/foo.js', |
| encoded: 'example.com/foo.js', |
| }, |
| { |
| url: 'example.com///', |
| raw: 'example.com/index.html', |
| encoded: 'example.com/index.html', |
| }, |
| // Very long file names. |
| { |
| url: 'example.com' + |
| '/THIS/PATH/IS_MORE_THAN/200/Chars'.repeat(8), |
| raw: 'example.com/longurls/Chars-141a715a', |
| encoded: 'example.com/longurls/Chars-141a715a', |
| }, |
| { |
| url: ('example.com' + |
| '/THIS/PATH/IS_LESS_THAN/200/Chars'.repeat(5)) |
| .slice(0, -1), |
| raw: |
| 'example.com/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Char', |
| encoded: |
| 'example.com/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Char', |
| }, |
| ]; |
| if (Host.Platform.isWin()) { |
| toTest = [ |
| { |
| url: 'https://www.example.com/?foo=bar', |
| raw: 'www.example.com/%3Ffoo=bar', |
| encoded: 'www.example.com/%253Ffoo=bar', |
| }, |
| { |
| url: 'http://www.web.dev/path/page.html#anchor', |
| raw: 'www.web.dev/path/page.html', |
| encoded: 'www.web.dev/path/page.html', |
| }, |
| { |
| url: 'http://www.example.com/?foo=bar/', |
| raw: 'www.example.com/%3Ffoo=bar%2F', |
| encoded: 'www.example.com/%253Ffoo=bar%252F', |
| }, |
| { |
| url: 'http://www.example.com/?foo=bar?', |
| raw: 'www.example.com/%3Ffoo=bar%3F', |
| encoded: 'www.example.com/%253Ffoo=bar%253F', |
| }, |
| { |
| url: 'http://www.example.com/file&$*?.html', |
| raw: 'www.example.com/file&$%2A%3F.html', |
| encoded: 'www.example.com/file&$%252A%253F.html', |
| }, |
| { |
| url: 'localhost:8090/', |
| raw: 'localhost%3A8090/index.html', |
| encoded: 'localhost%253A8090/index.html', |
| }, |
| // Windows cannot end with . (period) and space. |
| { |
| url: 'example.com/foo.js.', |
| raw: 'example.com/foo.js%2E', |
| encoded: 'example.com/foo.js%252E', |
| }, |
| { |
| url: 'localhost:8090/endswith.', |
| raw: 'localhost%3A8090/endswith%2E', |
| encoded: 'localhost%253A8090/endswith%252E', |
| }, |
| { |
| url: 'example.com/foo.js ', |
| raw: 'example.com/foo.js%20', |
| encoded: 'example.com/foo.js%2520', |
| }, |
| // Reserved filenames on Windows. |
| { |
| url: 'example.com/CON', |
| raw: 'example.com/%43%4F%4E', |
| encoded: 'example.com/%2543%254F%254E', |
| }, |
| { |
| url: 'example.com/cOn', |
| raw: 'example.com/%63%4F%6E', |
| encoded: 'example.com/%2563%254F%256E', |
| }, |
| { |
| url: 'example.com/cOn/hello', |
| raw: 'example.com/%63%4F%6E/hello', |
| encoded: 'example.com/%2563%254F%256E/hello', |
| }, |
| { |
| url: 'example.com/PRN', |
| raw: 'example.com/%50%52%4E', |
| encoded: 'example.com/%2550%2552%254E', |
| }, |
| { |
| url: 'example.com/AUX', |
| raw: 'example.com/%41%55%58', |
| encoded: 'example.com/%2541%2555%2558', |
| }, |
| { |
| url: 'example.com/NUL', |
| raw: 'example.com/%4E%55%4C', |
| encoded: 'example.com/%254E%2555%254C', |
| }, |
| { |
| url: 'example.com/COM1', |
| raw: 'example.com/%43%4F%4D%31', |
| encoded: 'example.com/%2543%254F%254D%2531', |
| }, |
| { |
| url: 'example.com/COM2', |
| raw: 'example.com/%43%4F%4D%32', |
| encoded: 'example.com/%2543%254F%254D%2532', |
| }, |
| { |
| url: 'example.com/COM3', |
| raw: 'example.com/%43%4F%4D%33', |
| encoded: 'example.com/%2543%254F%254D%2533', |
| }, |
| { |
| url: 'example.com/COM4', |
| raw: 'example.com/%43%4F%4D%34', |
| encoded: 'example.com/%2543%254F%254D%2534', |
| }, |
| { |
| url: 'example.com/COM5', |
| raw: 'example.com/%43%4F%4D%35', |
| encoded: 'example.com/%2543%254F%254D%2535', |
| }, |
| { |
| url: 'example.com/COM6', |
| raw: 'example.com/%43%4F%4D%36', |
| encoded: 'example.com/%2543%254F%254D%2536', |
| }, |
| { |
| url: 'example.com/COM7', |
| raw: 'example.com/%43%4F%4D%37', |
| encoded: 'example.com/%2543%254F%254D%2537', |
| }, |
| { |
| url: 'example.com/COM8', |
| raw: 'example.com/%43%4F%4D%38', |
| encoded: 'example.com/%2543%254F%254D%2538', |
| }, |
| { |
| url: 'example.com/COM9', |
| raw: 'example.com/%43%4F%4D%39', |
| encoded: 'example.com/%2543%254F%254D%2539', |
| }, |
| { |
| url: 'localhost:8090/lpt1', |
| raw: 'localhost%3A8090/%6C%70%74%31', |
| encoded: 'localhost%253A8090/%256C%2570%2574%2531', |
| }, |
| { |
| url: 'example.com/LPT1', |
| raw: 'example.com/%4C%50%54%31', |
| encoded: 'example.com/%254C%2550%2554%2531', |
| }, |
| { |
| url: 'example.com/LPT2', |
| raw: 'example.com/%4C%50%54%32', |
| encoded: 'example.com/%254C%2550%2554%2532', |
| }, |
| { |
| url: 'example.com/LPT3', |
| raw: 'example.com/%4C%50%54%33', |
| encoded: 'example.com/%254C%2550%2554%2533', |
| }, |
| { |
| url: 'example.com/LPT4', |
| raw: 'example.com/%4C%50%54%34', |
| encoded: 'example.com/%254C%2550%2554%2534', |
| }, |
| { |
| url: 'example.com/LPT5', |
| raw: 'example.com/%4C%50%54%35', |
| encoded: 'example.com/%254C%2550%2554%2535', |
| }, |
| { |
| url: 'example.com/LPT6', |
| raw: 'example.com/%4C%50%54%36', |
| encoded: 'example.com/%254C%2550%2554%2536', |
| }, |
| { |
| url: 'example.com/LPT7', |
| raw: 'example.com/%4C%50%54%37', |
| encoded: 'example.com/%254C%2550%2554%2537', |
| }, |
| { |
| url: 'example.com/LPT8', |
| raw: 'example.com/%4C%50%54%38', |
| encoded: 'example.com/%254C%2550%2554%2538', |
| }, |
| { |
| url: 'example.com/LPT9', |
| raw: 'example.com/%4C%50%54%39', |
| encoded: 'example.com/%254C%2550%2554%2539', |
| }, |
| |
| ]; |
| } |
| toTest.forEach(testStrings => { |
| assert.deepEqual(networkPersistenceManager.rawPathFromUrl(urlString`${testStrings.url}`), testStrings.raw); |
| assert.deepEqual( |
| networkPersistenceManager.encodedPathFromUrl(urlString`${testStrings.url}`), testStrings.encoded); |
| }); |
| }); |
| |
| it('is aware of which \'.headers\' files are currently active', done => { |
| const workspace = Workspace.Workspace.WorkspaceImpl.instance(); |
| const project = { |
| type: () => Workspace.Workspace.projectTypes.Network, |
| } as Workspace.Workspace.Project; |
| const networkUISourceCode = { |
| url: () => 'https://www.example.com/hello/world/index.html', |
| project: () => project, |
| contentType: () => Common.ResourceType.resourceTypes.Document, |
| } as Workspace.UISourceCode.UISourceCode; |
| project.uiSourceCodes = () => [networkUISourceCode]; |
| |
| const eventURLs: string[] = []; |
| networkPersistenceManager.addEventListener( |
| Persistence.NetworkPersistenceManager.Events.REQUEST_FOR_HEADER_OVERRIDES_FILE_CHANGED, event => { |
| eventURLs.push(event.data.url()); |
| }); |
| |
| workspace.dispatchEventToListeners(Workspace.Workspace.Events.UISourceCodeAdded, networkUISourceCode); |
| |
| assert.isTrue(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({ |
| url: () => 'file:///path/to/overrides/www.example.com/.headers', |
| project: () => networkPersistenceManager.project(), |
| } as Workspace.UISourceCode.UISourceCode)); |
| assert.isTrue(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({ |
| url: () => 'file:///path/to/overrides/.headers', |
| project: () => networkPersistenceManager.project(), |
| } as Workspace.UISourceCode.UISourceCode)); |
| assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({ |
| url: () => 'file:///path/to/overrides/www.foo.com/.headers', |
| project: () => networkPersistenceManager.project(), |
| } as Workspace.UISourceCode.UISourceCode)); |
| |
| workspace.dispatchEventToListeners(Workspace.Workspace.Events.ProjectRemoved, project); |
| |
| setTimeout(() => { |
| assert.deepEqual( |
| eventURLs, ['file:///path/to/overrides/.headers', 'file:///path/to/overrides/www.example.com/.headers']); |
| assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({ |
| url: () => 'file:///path/to/overrides/www.example.com/.headers', |
| project: () => networkPersistenceManager.project(), |
| } as Workspace.UISourceCode.UISourceCode)); |
| assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({ |
| url: () => 'file:///path/to/overrides/.headers', |
| project: () => networkPersistenceManager.project(), |
| } as Workspace.UISourceCode.UISourceCode)); |
| assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({ |
| url: () => 'file:///path/to/overrides/www.foo.com/.headers', |
| project: () => networkPersistenceManager.project(), |
| } as Workspace.UISourceCode.UISourceCode)); |
| done(); |
| }, 0); |
| }); |
| }); |
| |
| describeWithMockConnection('NetworkPersistenceManager', () => { |
| beforeEach(() => { |
| SDK.NetworkManager.MultitargetNetworkManager.dispose(); |
| }); |
| |
| it('updates active state when target detach and attach', async () => { |
| const {networkPersistenceManager} = setUpEnvironment(); |
| const {project} = createFileSystemUISourceCode({url: urlString`file:///tmp`, mimeType: 'text/plain'}); |
| await networkPersistenceManager.setProject(project); |
| const targetManager = SDK.TargetManager.TargetManager.instance(); |
| assert.isNull(targetManager.rootTarget()); |
| assert.isFalse(networkPersistenceManager.active()); |
| |
| const target = await createTarget(); |
| assert.isTrue(networkPersistenceManager.active()); |
| |
| targetManager.removeTarget(target); |
| target.dispose('test'); |
| |
| assert.isFalse(networkPersistenceManager.active()); |
| }); |
| }); |
| |
| describe('NetworkPersistenceManager', () => { |
| before(async () => { |
| await initializeGlobalVars(); |
| }); |
| after(async () => { |
| await deinitializeGlobalVars(); |
| }); |
| |
| it('escapes patterns to be used in RegExes', () => { |
| assert.strictEqual(Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/'), 'www\\.example\\.com/'); |
| assert.strictEqual( |
| Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/index.html'), |
| 'www\\.example\\.com/index\\.html'); |
| assert.strictEqual( |
| Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/*'), 'www\\.example\\.com/.*'); |
| assert.strictEqual( |
| Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/*.js'), 'www\\.example\\.com/.*\\.js'); |
| assert.strictEqual( |
| Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/file([{with-special$_^chars}])'), |
| 'www\\.example\\.com/file\\(\\[\\{with\\-special\\$_\\^chars\\}\\]\\)'); |
| assert.strictEqual( |
| Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/page.html?foo=bar'), |
| 'www\\.example\\.com/page\\.html\\?foo=bar'); |
| assert.strictEqual( |
| Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/*?foo=bar'), |
| 'www\\.example\\.com/.*\\?foo=bar'); |
| }); |
| |
| it('detects when the tail of a path matches with a default index file', () => { |
| assert.deepEqual( |
| Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.html'), {head: '', tail: 'index.html'}); |
| assert.deepEqual( |
| Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.htm'), {head: '', tail: 'index.htm'}); |
| assert.deepEqual( |
| Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.php'), {head: '', tail: 'index.php'}); |
| assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.ht'), {head: 'index.ht'}); |
| assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('*.html'), {head: '', tail: '*.html'}); |
| assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('*.htm'), {head: '', tail: '*.htm'}); |
| assert.deepEqual( |
| Persistence.NetworkPersistenceManager.extractDirectoryIndex('path/*.html'), {head: 'path/', tail: '*.html'}); |
| assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('foo*.html'), {head: 'foo*.html'}); |
| assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('a*'), {head: 'a*'}); |
| assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('a/*'), {head: 'a/*'}); |
| }); |
| |
| it('merges headers which do not overlap', () => { |
| const {networkPersistenceManager} = setUpEnvironment(); |
| const baseHeaders = [{ |
| name: 'age', |
| value: '0', |
| }]; |
| const overrideHeaders = [{ |
| name: 'accept-ranges', |
| value: 'bytes', |
| }]; |
| const merged = [ |
| {name: 'accept-ranges', value: 'bytes'}, |
| {name: 'age', value: '0'}, |
| ]; |
| assert.deepEqual(networkPersistenceManager.mergeHeaders(baseHeaders, overrideHeaders), merged); |
| }); |
| |
| it('merges headers which overlap', () => { |
| const {networkPersistenceManager} = setUpEnvironment(); |
| const baseHeaders = [{ |
| name: 'age', |
| value: '0', |
| }]; |
| const overrideHeaders = [ |
| {name: 'accept-ranges', value: 'bytes'}, |
| {name: 'age', value: '1'}, |
| ]; |
| const merged = [ |
| {name: 'accept-ranges', value: 'bytes'}, |
| {name: 'age', value: '1'}, |
| ]; |
| assert.deepEqual(networkPersistenceManager.mergeHeaders(baseHeaders, overrideHeaders), merged); |
| }); |
| |
| it('generates header patterns', async () => { |
| const {networkPersistenceManager} = setUpEnvironment(); |
| const headers = `[ |
| { |
| "applyTo": "*", |
| "headers": [{ |
| "name": "age", |
| "value": "0" |
| }] |
| }, |
| { |
| "applyTo": "page.html", |
| "headers": [{ |
| "name": "age", |
| "value": "1" |
| }] |
| }, |
| { |
| "applyTo": "index.html", |
| "headers": [{ |
| "name": "age", |
| "value": "2" |
| }] |
| }, |
| { |
| "applyTo": "nested/path/*.js", |
| "headers": [{ |
| "name": "age", |
| "value": "3" |
| }] |
| }, |
| { |
| "applyTo": "*/path/*.js", |
| "headers": [{ |
| "name": "age", |
| "value": "4" |
| }] |
| } |
| ]`; |
| |
| const {uiSourceCode} = createFileSystemUISourceCode({ |
| url: urlString`file:///path/to/overrides/www.example.com/.headers`, |
| content: headers, |
| mimeType: 'text/plain', |
| fileSystemPath: 'file:///path/to/overrides', |
| }); |
| |
| const expectedPatterns = [ |
| 'http?://www.example.com/*', |
| 'http?://www.example.com/page.html', |
| 'http?://www.example.com/index.html', |
| 'http?://www.example.com/', |
| 'http?://www.example.com/nested/path/*.js', |
| 'http?://www.example.com/*/path/*.js', |
| ]; |
| |
| const {headerPatterns, path, overridesWithRegex} = |
| await networkPersistenceManager.generateHeaderPatterns(uiSourceCode); |
| assert.deepEqual(Array.from(headerPatterns).sort(), expectedPatterns.sort()); |
| |
| const expectedMapping = [ |
| { |
| applyTo: /^www\.example\.com\/.*$/.toString(), |
| headers: [{name: 'age', value: '0'}], |
| }, |
| { |
| applyTo: /^www\.example\.com\/page\.html$/.toString(), |
| headers: [{name: 'age', value: '1'}], |
| }, |
| { |
| applyTo: /^www\.example\.com\/(index\.html)?$/.toString(), |
| headers: [{name: 'age', value: '2'}], |
| }, |
| { |
| applyTo: /^www\.example\.com\/nested\/path\/.*\.js$/.toString(), |
| headers: [{name: 'age', value: '3'}], |
| }, |
| { |
| applyTo: /^www\.example\.com\/.*\/path\/.*\.js$/.toString(), |
| headers: [{name: 'age', value: '4'}], |
| }, |
| ]; |
| |
| assert.strictEqual(path, 'www.example.com/'); |
| const actualMapping = overridesWithRegex.map( |
| override => ({applyTo: override.applyToRegex.toString(), headers: override.headers}), |
| ); |
| assert.deepEqual(actualMapping, expectedMapping); |
| }); |
| |
| it('generates header patterns for global header overrides', async () => { |
| const {networkPersistenceManager} = setUpEnvironment(); |
| const headers = `[ |
| { |
| "applyTo": "*", |
| "headers": [{ |
| "name": "age", |
| "value": "0" |
| }] |
| } |
| ]`; |
| |
| const {uiSourceCode} = createFileSystemUISourceCode({ |
| url: urlString`file:///path/to/overrides/.headers`, |
| content: headers, |
| mimeType: 'text/plain', |
| fileSystemPath: 'file:///path/to/overrides', |
| }); |
| |
| const {headerPatterns} = await networkPersistenceManager.generateHeaderPatterns(uiSourceCode); |
| assert.deepEqual(Array.from(headerPatterns), ['http?://*', 'file:///*']); |
| }); |
| |
| it('generates header patterns for long URLs', async () => { |
| const {networkPersistenceManager} = setUpEnvironment(); |
| const headers = `[ |
| { |
| "applyTo": "index.html-5b9f4873.html", |
| "headers": [{ |
| "name": "foo", |
| "value": "bar" |
| }] |
| } |
| ]`; |
| |
| const {uiSourceCode} = createFileSystemUISourceCode({ |
| url: urlString`file:///path/to/overrides/www.longurls.com/longurls/.headers`, |
| content: headers, |
| mimeType: 'text/plain', |
| fileSystemPath: 'file:///path/to/overrides', |
| }); |
| |
| const {headerPatterns, path, overridesWithRegex} = |
| await networkPersistenceManager.generateHeaderPatterns(uiSourceCode); |
| assert.deepEqual(Array.from(headerPatterns), ['http?://www.longurls.com/*']); |
| assert.strictEqual(path, 'www.longurls.com/longurls/'); |
| |
| const expectedMapping = [ |
| { |
| applyTo: /^www\.longurls\.com\/longurls\/index\.html\-5b9f4873\.html$/.toString(), |
| headers: [{name: 'foo', value: 'bar'}], |
| }, |
| ]; |
| const actualMapping = overridesWithRegex.map( |
| override => ({applyTo: override.applyToRegex.toString(), headers: override.headers}), |
| ); |
| assert.deepEqual(actualMapping, expectedMapping); |
| }); |
| |
| it('updates interception patterns upon edit of .headers file', async () => { |
| const {networkPersistenceManager} = setUpEnvironment(); |
| const headers = `[ |
| { |
| "applyTo": "index.html", |
| "headers": [{ |
| "name": "foo", |
| "value": "bar" |
| }] |
| } |
| ]`; |
| |
| const {uiSourceCode} = createFileSystemUISourceCode({ |
| url: urlString`file:///path/to/overrides/www.example.com/.headers`, |
| content: headers, |
| mimeType: 'text/plain', |
| fileSystemPath: 'file:///path/to/overrides', |
| }); |
| const spy = sinon.spy(networkPersistenceManager, 'updateInterceptionPatterns'); |
| sinon.assert.notCalled(spy); |
| |
| uiSourceCode.setWorkingCopy(`[ |
| { |
| "applyTo": "index.html", |
| "headers": [{ |
| "name": "foo2", |
| "value": "bar2" |
| }] |
| } |
| ]`); |
| uiSourceCode.commitWorkingCopy(); |
| sinon.assert.calledOnce(spy); |
| }); |
| }); |