diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 9359b05f9b..3c20cd7478 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -30,7 +30,7 @@ mainBuildFilters: &mainBuildFilters - /^release\/\d+\.\d+\.\d+$/ # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - - 'revert-runner' + - 'reduce_requests_paused' # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -41,7 +41,7 @@ macWorkflowFilters: &darwin-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'revert-runner', << pipeline.git.branch >> ] + - equal: [ 'reduce_requests_paused', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -52,7 +52,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'revert-runner', << pipeline.git.branch >> ] + - equal: [ 'reduce_requests_paused', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -72,7 +72,7 @@ windowsWorkflowFilters: &windows-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'revert-runner', << pipeline.git.branch >> ] + - equal: [ 'reduce_requests_paused', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -139,7 +139,7 @@ commands: - run: name: Check current branch to persist artifacts command: | - if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "revert-runner" ]]; then + if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "reduce_requests_paused" ]]; then echo "Not uploading artifacts or posting install comment for this branch." circleci-agent step halt fi diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 4f463afc5e..d7f1e30b95 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,6 +1,13 @@ ## 12.17.2 +_Released 08/01/2023 (PENDING)_ + +**Performance:** + +- Fixed an issue where unnecessary requests were being paused. No longer sends `X-Cypress-Is-XHR-Or-Fetch` header and infers resource type off of the server pre-request object. Fixes [#26620](https://github.com/cypress-io/cypress/issues/26620) and [#26622](https://github.com/cypress-io/cypress/issues/26622). +## 12.17.2 + _Released 07/20/2023_ **Bugfixes:** diff --git a/packages/driver/cypress/e2e/e2e/origin/patches.cy.ts b/packages/driver/cypress/e2e/e2e/origin/patches.cy.ts index 27f38fb577..a1d82d7d3e 100644 --- a/packages/driver/cypress/e2e/e2e/origin/patches.cy.ts +++ b/packages/driver/cypress/e2e/e2e/origin/patches.cy.ts @@ -89,7 +89,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: assertCredentialStatus, }) }) @@ -109,7 +109,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: assertCredentialStatus, }) }) @@ -129,7 +129,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: assertCredentialStatus, }) }) @@ -173,7 +173,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://app.foobar.com:3500/test-request', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: 'include', }) }) @@ -218,7 +218,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request-credentials', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: assertCredentialStatus, }) }) @@ -248,7 +248,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request-credentials', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: assertCredentialStatus, }) }) @@ -276,7 +276,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request-credentials', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: assertCredentialStatus, }) }) @@ -327,7 +327,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://app.foobar.com:3500/test-request', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: 'include', }) }) @@ -346,7 +346,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://localhost:3500/foo.bar.baz.json', - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: 'same-origin', }) }) @@ -410,7 +410,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request', - requestedWith: 'xhr', + resourceType: 'xhr', credentialStatus: withCredentials, }) }) @@ -450,7 +450,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://app.foobar.com:3500/test-request', - requestedWith: 'xhr', + resourceType: 'xhr', credentialStatus: true, }) }) @@ -502,7 +502,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://www.foobar.com:3500/test-request-credentials', - requestedWith: 'xhr', + resourceType: 'xhr', credentialStatus: withCredentials, }) }) @@ -554,7 +554,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://app.foobar.com:3500/test-request', - requestedWith: 'xhr', + resourceType: 'xhr', credentialStatus: true, }) }) @@ -574,7 +574,7 @@ describe('src/cross-origin/patches', { browser: '!webkit', defaultCommandTimeout cy.then(() => { expect(Cypress.backend).to.have.been.calledWith('request:sent:with:credentials', { url: 'http://localhost:3500/foo.bar.baz.json', - requestedWith: 'xhr', + resourceType: 'xhr', credentialStatus: false, }) }) diff --git a/packages/extension/app/background.js b/packages/extension/app/background.js index 7dd81726f3..b7ac721497 100644 --- a/packages/extension/app/background.js +++ b/packages/extension/app/background.js @@ -63,43 +63,24 @@ const connect = function (host, path, extraOpts) { // adds a header to the request to mark it as a request for the AUT frame // itself, so the proxy can utilize that for injection purposes browser.webRequest.onBeforeSendHeaders.addListener((details) => { - const requestModifications = { - requestHeaders: [ - ...(details.requestHeaders || []), - /** - * Unlike CDP, the web extensions onBeforeSendHeaders resourceType cannot discern the difference - * between fetch or xhr resource types, but classifies both as 'xmlhttprequest'. Because of this, - * we set X-Cypress-Is-XHR-Or-Fetch to true if the request is made with 'xhr' or 'fetch' so the - * middleware doesn't incorrectly assume which request type is being sent - * @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType - */ - ...(details.type === 'xmlhttprequest' ? [{ - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: 'true', - }] : []), - ], - } - if ( // parentFrameId: 0 means the parent is the top-level, so if it isn't // 0, it's nested inside the AUT and can't be the AUT itself details.parentFrameId !== 0 - // isn't an iframe - || details.type !== 'sub_frame' // is the spec frame, not the AUT || details.url.includes('__cypress') - ) return requestModifications + ) return return { requestHeaders: [ - ...requestModifications.requestHeaders, + ...details.requestHeaders, { name: 'X-Cypress-Is-AUT-Frame', value: 'true', }, ], } - }, { urls: [''] }, ['blocking', 'requestHeaders']) + }, { urls: [''], types: ['sub_frame'] }, ['blocking', 'requestHeaders']) }) const fail = (id, err) => { diff --git a/packages/extension/test/integration/background_spec.js b/packages/extension/test/integration/background_spec.js index 0c11943abb..0c7ca3fe3f 100644 --- a/packages/extension/test/integration/background_spec.js +++ b/packages/extension/test/integration/background_spec.js @@ -285,7 +285,7 @@ describe('app/background', () => { const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details) - expect(result).to.deep.equal({ requestHeaders: [] }) + expect(result).to.be.undefined }) it('does not add header if it is a nested frame', async function () { @@ -299,22 +299,7 @@ describe('app/background', () => { const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details) - expect(result).to.deep.equal({ requestHeaders: [] }) - }) - - it('does not add header if it is not a sub frame request', async function () { - const details = { - parentFrameId: 0, - type: 'stylesheet', - } - - sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener') - - await this.connect() - - const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details) - - expect(result).to.deep.equal({ requestHeaders: [] }) + expect(result).to.be.undefined }) it('does not add header if it is a spec frame request', async function () { @@ -329,7 +314,7 @@ describe('app/background', () => { await this.connect() const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details) - expect(result).to.deep.equal({ requestHeaders: [] }) + expect(result).to.be.undefined }) it('appends X-Cypress-Is-AUT-Frame header to AUT iframe request', async function () { @@ -361,60 +346,6 @@ describe('app/background', () => { }) }) - it('appends X-Cypress-Is-XHR-Or-Fetch header to request if the resourceType is "xmlhttprequest"', async function () { - const details = { - parentFrameId: 0, - type: 'xmlhttprequest', - url: 'http://localhost:3000/index.html', - requestHeaders: [ - { name: 'X-Foo', value: 'Bar' }, - ], - } - - sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener') - - await this.connect() - const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details) - - expect(result).to.deep.equal({ - requestHeaders: [ - { - name: 'X-Foo', - value: 'Bar', - }, - { - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: 'true', - }, - ], - }) - }) - - it('does not append X-Cypress-Is-XHR-Or-Fetch header to request if the resourceType is not an "xmlhttprequest"', async function () { - const details = { - parentFrameId: 0, - type: 'sub_frame', - url: 'http://localhost:3000/index.html', - requestHeaders: [ - { name: 'X-Foo', value: 'Bar' }, - ], - } - - sinon.stub(browser.webRequest.onBeforeSendHeaders, 'addListener') - - await this.connect() - const result = browser.webRequest.onBeforeSendHeaders.addListener.lastCall.args[0](details) - - expect(result).to.not.deep.equal({ - requestHeaders: [ - { - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: 'true', - }, - ], - }) - }) - it('does not add before-headers listener if in non-Firefox browser', async function () { browser.runtime.getBrowserInfo = undefined diff --git a/packages/proxy/lib/http/index.ts b/packages/proxy/lib/http/index.ts index 7d3884f539..7168893d3b 100644 --- a/packages/proxy/lib/http/index.ts +++ b/packages/proxy/lib/http/index.ts @@ -24,7 +24,7 @@ import type { Readable } from 'stream' import type { Request, Response } from 'express' import type { RemoteStates } from '@packages/server/lib/remote_states' import type { CookieJar, SerializableAutomationCookie } from '@packages/server/lib/util/cookies' -import type { RequestedWithAndCredentialManager } from '@packages/server/lib/util/requestedWithAndCredentialManager' +import type { ResourceTypeAndCredentialManager } from '@packages/server/lib/util/resourceTypeAndCredentialManager' function getRandomColorFn () { return chalk.hex(`#${Number( @@ -82,7 +82,7 @@ export type ServerCtx = Readonly<{ getFileServerToken: () => string | undefined getCookieJar: () => CookieJar remoteStates: RemoteStates - requestedWithAndCredentialManager: RequestedWithAndCredentialManager + resourceTypeAndCredentialManager: ResourceTypeAndCredentialManager getRenderedHTMLOrigins: Http['getRenderedHTMLOrigins'] netStubbingState: NetStubbingState middleware: HttpMiddlewareStacks @@ -258,7 +258,7 @@ export class Http { request: any socket: CyServer.Socket serverBus: EventEmitter - requestedWithAndCredentialManager: RequestedWithAndCredentialManager + resourceTypeAndCredentialManager: ResourceTypeAndCredentialManager renderedHTMLOrigins: {[key: string]: boolean} = {} autUrl?: string getCookieJar: () => CookieJar @@ -275,7 +275,7 @@ export class Http { this.socket = opts.socket this.request = opts.request this.serverBus = opts.serverBus - this.requestedWithAndCredentialManager = opts.requestedWithAndCredentialManager + this.resourceTypeAndCredentialManager = opts.resourceTypeAndCredentialManager this.getCookieJar = opts.getCookieJar if (typeof opts.middleware === 'undefined') { @@ -303,7 +303,7 @@ export class Http { netStubbingState: this.netStubbingState, socket: this.socket, serverBus: this.serverBus, - requestedWithAndCredentialManager: this.requestedWithAndCredentialManager, + resourceTypeAndCredentialManager: this.resourceTypeAndCredentialManager, getCookieJar: this.getCookieJar, simulatedCookies: [], debug: (formatter, ...args) => { diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index dbdb797b23..23b9798639 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -31,7 +31,6 @@ const ExtractCypressMetadataHeaders: RequestMiddleware = function () { const span = telemetry.startSpan({ name: 'extract:cypress:metadata:headers', parentSpan: this.reqMiddlewareSpan, isVerbose }) this.req.isAUTFrame = !!this.req.headers['x-cypress-is-aut-frame'] - const requestIsXhrOrFetch = this.req.headers['x-cypress-is-xhr-or-fetch'] span?.setAttributes({ isAUTFrame: this.req.isAUTFrame, @@ -41,34 +40,6 @@ const ExtractCypressMetadataHeaders: RequestMiddleware = function () { delete this.req.headers['x-cypress-is-aut-frame'] } - if (this.req.headers['x-cypress-is-xhr-or-fetch']) { - this.debug(`found x-cypress-is-xhr-or-fetch header. Deleting x-cypress-is-xhr-or-fetch header.`) - delete this.req.headers['x-cypress-is-xhr-or-fetch'] - } - - if (!doesTopNeedToBeSimulated(this) || - // this should be unreachable, as the x-cypress-is-xhr-or-fetch header is only attached if - // the resource type is 'xhr' or 'fetch or 'true' (in the case of electron|extension). - // This is only needed for defensive purposes. - (requestIsXhrOrFetch !== 'true' && requestIsXhrOrFetch !== 'xhr' && requestIsXhrOrFetch !== 'fetch')) { - this.next() - - return - } - - this.debug(`looking up credentials for ${this.req.proxiedUrl}`) - const { requestedWith, credentialStatus } = this.requestedWithAndCredentialManager.get(this.req.proxiedUrl, requestIsXhrOrFetch !== 'true' ? requestIsXhrOrFetch : undefined) - - this.debug(`credentials calculated for ${requestedWith}:${credentialStatus}`) - - this.req.requestedWith = requestedWith - this.req.credentialsLevel = credentialStatus - - span?.setAttributes({ - calculatedResourceType: this.req.resourceType, - credentialsLevel: credentialStatus, - }) - span?.end() this.next() } @@ -103,74 +74,6 @@ const MaybeSimulateSecHeaders: RequestMiddleware = function () { this.next() } -const MaybeAttachCrossOriginCookies: RequestMiddleware = function () { - const span = telemetry.startSpan({ name: 'maybe:attach:cross:origin:cookies', parentSpan: this.reqMiddlewareSpan, isVerbose }) - - const doesTopNeedSimulation = doesTopNeedToBeSimulated(this) - - span?.setAttributes({ - doesTopNeedToBeSimulated: doesTopNeedSimulation, - resourceType: this.req.resourceType, - }) - - if (!doesTopNeedSimulation) { - span?.end() - - return this.next() - } - - // Top needs to be simulated since the AUT is in a cross origin state. Get the "requested with" and credentials and see what cookies need to be attached - const currentAUTUrl = this.getAUTUrl() - const shouldCookiesBeAttachedToRequest = shouldAttachAndSetCookies(this.req.proxiedUrl, currentAUTUrl, this.req.requestedWith, this.req.credentialsLevel, this.req.isAUTFrame) - - span?.setAttributes({ - currentAUTUrl, - shouldCookiesBeAttachedToRequest, - }) - - this.debug(`should cookies be attached to request?: ${shouldCookiesBeAttachedToRequest}`) - if (!shouldCookiesBeAttachedToRequest) { - span?.end() - - return this.next() - } - - const sameSiteContext = getSameSiteContext( - currentAUTUrl, - this.req.proxiedUrl, - this.req.isAUTFrame, - ) - - span?.setAttributes({ - sameSiteContext, - currentAUTUrl, - isAUTFrame: this.req.isAUTFrame, - }) - - const applicableCookiesInCookieJar = this.getCookieJar().getCookies(this.req.proxiedUrl, sameSiteContext) - const cookiesOnRequest = (this.req.headers['cookie'] || '').split('; ') - - const existingCookiesInJar = applicableCookiesInCookieJar.join('; ') - const addedCookiesFromHeader = cookiesOnRequest.join('; ') - - this.debug('existing cookies on request from cookie jar: %s', existingCookiesInJar) - this.debug('add cookies to request from header: %s', addedCookiesFromHeader) - - // if the cookie header is empty (i.e. ''), set it to undefined for expected behavior - this.req.headers['cookie'] = addCookieJarCookiesToRequest(applicableCookiesInCookieJar, cookiesOnRequest) || undefined - - span?.setAttributes({ - existingCookiesInJar, - addedCookiesFromHeader, - cookieHeader: this.req.headers['cookie'], - }) - - this.debug('cookies being sent with request: %s', this.req.headers['cookie']) - - span?.end() - this.next() -} - const CorrelateBrowserPreRequest: RequestMiddleware = async function () { const span = telemetry.startSpan({ name: 'correlate:prerequest', parentSpan: this.reqMiddlewareSpan, isVerbose }) @@ -231,6 +134,93 @@ const CorrelateBrowserPreRequest: RequestMiddleware = async function () { })) } +const CalculateCredentialLevelIfApplicable: RequestMiddleware = function () { + if (!doesTopNeedToBeSimulated(this) || + (this.req.resourceType !== undefined && this.req.resourceType !== 'xhr' && this.req.resourceType !== 'fetch')) { + this.next() + + return + } + + this.debug(`looking up credentials for ${this.req.proxiedUrl}`) + const { credentialStatus, resourceType } = this.resourceTypeAndCredentialManager.get(this.req.proxiedUrl, this.req.resourceType) + + this.debug(`credentials calculated for ${resourceType}:${credentialStatus}`) + + // if for some reason the resourceType is not set by the prerequest, have a fallback in place + this.req.resourceType = !this.req.resourceType ? resourceType : this.req.resourceType + this.req.credentialsLevel = credentialStatus + this.next() +} + +const MaybeAttachCrossOriginCookies: RequestMiddleware = function () { + const span = telemetry.startSpan({ name: 'maybe:attach:cross:origin:cookies', parentSpan: this.reqMiddlewareSpan, isVerbose }) + + const doesTopNeedSimulation = doesTopNeedToBeSimulated(this) + + span?.setAttributes({ + doesTopNeedToBeSimulated: doesTopNeedSimulation, + resourceType: this.req.resourceType, + }) + + if (!doesTopNeedSimulation) { + span?.end() + + return this.next() + } + + // Top needs to be simulated since the AUT is in a cross origin state. Get the "requested with" and credentials and see what cookies need to be attached + const currentAUTUrl = this.getAUTUrl() + const shouldCookiesBeAttachedToRequest = shouldAttachAndSetCookies(this.req.proxiedUrl, currentAUTUrl, this.req.resourceType, this.req.credentialsLevel, this.req.isAUTFrame) + + span?.setAttributes({ + currentAUTUrl, + shouldCookiesBeAttachedToRequest, + }) + + this.debug(`should cookies be attached to request?: ${shouldCookiesBeAttachedToRequest}`) + if (!shouldCookiesBeAttachedToRequest) { + span?.end() + + return this.next() + } + + const sameSiteContext = getSameSiteContext( + currentAUTUrl, + this.req.proxiedUrl, + this.req.isAUTFrame, + ) + + span?.setAttributes({ + sameSiteContext, + currentAUTUrl, + isAUTFrame: this.req.isAUTFrame, + }) + + const applicableCookiesInCookieJar = this.getCookieJar().getCookies(this.req.proxiedUrl, sameSiteContext) + const cookiesOnRequest = (this.req.headers['cookie'] || '').split('; ') + + const existingCookiesInJar = applicableCookiesInCookieJar.join('; ') + const addedCookiesFromHeader = cookiesOnRequest.join('; ') + + this.debug('existing cookies on request from cookie jar: %s', existingCookiesInJar) + this.debug('add cookies to request from header: %s', addedCookiesFromHeader) + + // if the cookie header is empty (i.e. ''), set it to undefined for expected behavior + this.req.headers['cookie'] = addCookieJarCookiesToRequest(applicableCookiesInCookieJar, cookiesOnRequest) || undefined + + span?.setAttributes({ + existingCookiesInJar, + addedCookiesFromHeader, + cookieHeader: this.req.headers['cookie'], + }) + + this.debug('cookies being sent with request: %s', this.req.headers['cookie']) + + span?.end() + this.next() +} + function shouldLog (req: CypressIncomingRequest) { // 1. Any matching `cy.intercept()` should cause `req` to be logged by default, unless `log: false` is passed explicitly. if (req.matchingRoutes?.length) { @@ -525,9 +515,10 @@ export default { LogRequest, ExtractCypressMetadataHeaders, MaybeSimulateSecHeaders, + CorrelateBrowserPreRequest, + CalculateCredentialLevelIfApplicable, MaybeAttachCrossOriginCookies, MaybeEndRequestWithBufferedResponse, - CorrelateBrowserPreRequest, SetMatchingRoutes, SendToDriver, InterceptRequest, diff --git a/packages/proxy/lib/http/response-middleware.ts b/packages/proxy/lib/http/response-middleware.ts index 1165fb7907..04cd8692e6 100644 --- a/packages/proxy/lib/http/response-middleware.ts +++ b/packages/proxy/lib/http/response-middleware.ts @@ -580,7 +580,7 @@ const MaybeCopyCookiesFromIncomingRes: ResponseMiddleware = async function () { url: this.req.proxiedUrl, isAUTFrame: this.req.isAUTFrame, doesTopNeedSimulating, - requestedWith: this.req.requestedWith, + resourceType: this.req.resourceType, credentialLevel: this.req.credentialsLevel, }, }) diff --git a/packages/proxy/lib/http/util/cookies.ts b/packages/proxy/lib/http/util/cookies.ts index c51bb5ce41..391a0b20db 100644 --- a/packages/proxy/lib/http/util/cookies.ts +++ b/packages/proxy/lib/http/util/cookies.ts @@ -4,7 +4,8 @@ import { URL } from 'url' import { cors } from '@packages/network' import { urlOriginsMatch, urlSameSiteMatch } from '@packages/network/lib/cors' import { SerializableAutomationCookie, Cookie, CookieJar, toughCookieToAutomationCookie } from '@packages/server/lib/util/cookies' -import type { RequestCredentialLevel, RequestedWithHeader } from '../../types' +import type { RequestCredentialLevel } from '../../types' +import type { ResourceType } from 'cypress/types/net-stubbing' type SiteContext = 'same-origin' | 'same-site' | 'cross-site' @@ -12,7 +13,7 @@ interface RequestDetails { url: string isAUTFrame: boolean doesTopNeedSimulating: boolean - requestedWith?: RequestedWithHeader + resourceType?: ResourceType credentialLevel?: RequestCredentialLevel } @@ -23,18 +24,18 @@ interface RequestDetails { * which is critical for lax cookies * @param {string} requestUrl - the url of the request * @param {string} AUTUrl - The current url of the app under test - * @param {requestedWith} [requestedWith] - + * @param {resourceType} [resourceType] - the request resourceType * @param {RequestCredentialLevel} [credentialLevel] - The credentialLevel of the request. For `fetch` this is `omit|same-origin|include` (defaults to same-origin) * and for `XmlHttpRequest` it is `true|false` (defaults to false) * @param {isAutFrame} [boolean] - whether or not the request is from the AUT Iframe or not * @returns {boolean} */ -export const shouldAttachAndSetCookies = (requestUrl: string, AUTUrl: string | undefined, requestedWith?: RequestedWithHeader, credentialLevel?: RequestCredentialLevel, isAutFrame?: boolean): boolean => { +export const shouldAttachAndSetCookies = (requestUrl: string, AUTUrl: string | undefined, resourceType?: ResourceType, credentialLevel?: RequestCredentialLevel, isAutFrame?: boolean): boolean => { if (!AUTUrl) return false const siteContext = calculateSiteContext(requestUrl, AUTUrl) - switch (requestedWith) { + switch (resourceType) { case 'fetch': // never attach cookies regardless of siteContext if omit is optioned if (credentialLevel === 'omit') { @@ -59,7 +60,7 @@ export const shouldAttachAndSetCookies = (requestUrl: string, AUTUrl: string | u return false default: - // if we cannot determine a resource level, we likely should store the cookie as it is a navigation or another event as long as the context is same-origin + // if we cannot determine a resource level or it isn't applicable,, we likely should store the cookie as it is a navigation or another event as long as the context is same-origin if (siteContext === 'same-origin' || isAutFrame) { return true } @@ -220,7 +221,9 @@ export class CookiesHelper { // cross site cookies cannot set lax/strict cookies in the browser for xhr/fetch requests (but ok with navigation/document requests) // NOTE: This is allowable in firefox as the default cookie behavior is no_restriction (none). However, this shouldn't // impact what is happening in the server-side cookie jar as Set-Cookie is still called and firefox will allow it to be set in the browser - if (this.request.requestedWith && this.siteContext === 'cross-site' && toughCookie.sameSite !== 'none') { + const isXhrOrFetchRequest = this.request.resourceType === 'fetch' || this.request.resourceType === 'xhr' + + if (isXhrOrFetchRequest && this.siteContext === 'cross-site' && toughCookie.sameSite !== 'none') { this.debug(`cannot set cookie with SameSite=${toughCookie.sameSite} when site context is ${this.siteContext}`) return @@ -228,10 +231,10 @@ export class CookiesHelper { // don't set the cookie in our own cookie jar if the cookie would otherwise fail being set in the browser if the AUT Url // was actually top. This prevents cookies from being applied to our cookie jar when they shouldn't, preventing possible security implications. - const shouldSetCookieGivenSiteContext = shouldAttachAndSetCookies(this.request.url, this.currentAUTUrl, this.request.requestedWith, this.request.credentialLevel, this.request.isAUTFrame) + const shouldSetCookieGivenSiteContext = shouldAttachAndSetCookies(this.request.url, this.currentAUTUrl, this.request.resourceType, this.request.credentialLevel, this.request.isAUTFrame) if (!shouldSetCookieGivenSiteContext) { - this.debug(`not setting cookie for ${this.request.url} with simulated top ${ this.currentAUTUrl} for ${ this.request.requestedWith}:${this.request.credentialLevel}, cookie: ${toughCookie}`) + this.debug(`not setting cookie for ${this.request.url} with simulated top ${ this.currentAUTUrl} for ${ this.request.resourceType}:${this.request.credentialLevel}, cookie: ${toughCookie}`) return } diff --git a/packages/proxy/lib/types.ts b/packages/proxy/lib/types.ts index 8343b6640d..a6879a56a8 100644 --- a/packages/proxy/lib/types.ts +++ b/packages/proxy/lib/types.ts @@ -15,7 +15,6 @@ export type CypressIncomingRequest = Request & { responseTimeout?: number followRedirect?: boolean isAUTFrame: boolean - requestedWith?: RequestedWithHeader credentialsLevel?: RequestCredentialLevel /** * Resource type from browserPreRequest. Copied to req so intercept matching can work. @@ -27,8 +26,6 @@ export type CypressIncomingRequest = Request & { matchingRoutes?: BackendRoute[] } -export type RequestedWithHeader = 'fetch' | 'xhr' | 'true' - export type RequestCredentialLevel = 'same-origin' | 'include' | 'omit' | boolean export type CypressWantsInjection = 'full' | 'fullCrossOrigin' | 'partial' | false diff --git a/packages/proxy/test/integration/net-stubbing.spec.ts b/packages/proxy/test/integration/net-stubbing.spec.ts index 6437cdb429..525c9739f4 100644 --- a/packages/proxy/test/integration/net-stubbing.spec.ts +++ b/packages/proxy/test/integration/net-stubbing.spec.ts @@ -49,10 +49,10 @@ context('network stubbing', () => { request: new Request(), getRenderedHTMLOrigins: () => ({}), serverBus: new EventEmitter(), - requestedWithAndCredentialManager: { + resourceTypeAndCredentialManager: { get () { return { - requestedWith: 'xhr', + resourceType: 'xhr', credentialStatus: 'same-origin', } }, diff --git a/packages/proxy/test/unit/http/request-middleware.spec.ts b/packages/proxy/test/unit/http/request-middleware.spec.ts index f63ef09295..9025432306 100644 --- a/packages/proxy/test/unit/http/request-middleware.spec.ts +++ b/packages/proxy/test/unit/http/request-middleware.spec.ts @@ -15,9 +15,10 @@ describe('http/request-middleware', () => { 'LogRequest', 'ExtractCypressMetadataHeaders', 'MaybeSimulateSecHeaders', + 'CorrelateBrowserPreRequest', + 'CalculateCredentialLevelIfApplicable', 'MaybeAttachCrossOriginCookies', 'MaybeEndRequestWithBufferedResponse', - 'CorrelateBrowserPreRequest', 'SetMatchingRoutes', 'SendToDriver', 'InterceptRequest', @@ -77,58 +78,16 @@ describe('http/request-middleware', () => { expect(ctx.req.isAUTFrame).to.be.false }) }) + }) - it('removes x-cypress-is-xhr-or-fetch header when it exists', async () => { - const ctx = { - getAUTUrl: sinon.stub().returns('http://localhost:8080'), - remoteStates: { - isPrimarySuperDomainOrigin: sinon.stub().returns(true), - }, - req: { - headers: { - 'x-cypress-is-xhr-or-fetch': 'true', - }, - } as Partial, - res: { - on: (event, listener) => {}, - off: (event, listener) => {}, - }, - } + describe('CalculateCredentialLevelIfApplicable', () => { + const { CalculateCredentialLevelIfApplicable } = RequestMiddleware - await testMiddleware([ExtractCypressMetadataHeaders], ctx) - .then(() => { - expect(ctx.req.headers['x-cypress-is-xhr-or-fetch']).not.to.exist - }) - }) - - it('removes x-cypress-is-xhr-or-fetch header when it does not exist', async () => { - const ctx = { - getAUTUrl: sinon.stub().returns('http://localhost:8080'), - remoteStates: { - isPrimarySuperDomainOrigin: sinon.stub().returns(false), - }, - req: { - headers: {}, - } as Partial, - res: { - on: (event, listener) => {}, - off: (event, listener) => {}, - }, - } - - await testMiddleware([ExtractCypressMetadataHeaders], ctx) - .then(() => { - expect(ctx.req.headers['x-cypress-is-xhr-or-fetch']).not.to.exist - }) - }) - - it('does not set requestedWith or credentialLevel on the request if top does NOT need to be simulated', async () => { + it('does not set credentialLevel on the request if top does NOT need to be simulated', async () => { const ctx = { getAUTUrl: sinon.stub().returns(undefined), req: { - headers: { - 'x-cypress-is-xhr-or-fetch': 'true', - }, + resourceType: 'xhr', } as Partial, res: { on: (event, listener) => {}, @@ -136,23 +95,20 @@ describe('http/request-middleware', () => { }, } - await testMiddleware([ExtractCypressMetadataHeaders], ctx) + await testMiddleware([CalculateCredentialLevelIfApplicable], ctx) .then(() => { - expect(ctx.req.requestedWith).not.to.exist expect(ctx.req.credentialsLevel).not.to.exist }) }) - it('does not set requestedWith or credentialLevel on the request if x-cypress-is-xhr-or-fetch has invalid values', async () => { + it('does not set credentialLevel on the request if resourceType has invalid value', async () => { const ctx = { getAUTUrl: sinon.stub().returns('http://localhost:8080'), remoteStates: { isPrimarySuperDomainOrigin: sinon.stub().returns(false), }, req: { - headers: { - 'x-cypress-is-xhr-or-fetch': 'sub_frame', - }, + resourceType: 'document', } as Partial, res: { on: (event, listener) => {}, @@ -160,28 +116,25 @@ describe('http/request-middleware', () => { }, } - await testMiddleware([ExtractCypressMetadataHeaders], ctx) + await testMiddleware([CalculateCredentialLevelIfApplicable], ctx) .then(() => { - expect(ctx.req.requestedWith).not.to.exist expect(ctx.req.credentialsLevel).not.to.exist }) }) // CDP can determine whether or not the request is xhr | fetch, but the extension or electron cannot - it('provides requestedWithAndCredentialManager with requestedWith if able to determine from header (xhr)', async () => { + it('provides resourceTypeAndCredentialManager with resourceType if able to determine from prerequest (xhr)', async () => { const ctx = { getAUTUrl: sinon.stub().returns('http://localhost:8080'), remoteStates: { isPrimarySuperDomainOrigin: sinon.stub().returns(false), }, - requestedWithAndCredentialManager: { + resourceTypeAndCredentialManager: { get: sinon.stub().returns({}), }, req: { + resourceType: 'xhr', proxiedUrl: 'http://localhost:8080', - headers: { - 'x-cypress-is-xhr-or-fetch': 'xhr', - }, } as Partial, res: { on: (event, listener) => {}, @@ -189,27 +142,25 @@ describe('http/request-middleware', () => { }, } - await testMiddleware([ExtractCypressMetadataHeaders], ctx) + await testMiddleware([CalculateCredentialLevelIfApplicable], ctx) .then(() => { - expect(ctx.requestedWithAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `xhr`) + expect(ctx.resourceTypeAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `xhr`) }) }) // CDP can determine whether or not the request is xhr | fetch, but the extension or electron cannot - it('provides requestedWithAndCredentialManager with requestedWith if able to determine from header (fetch)', async () => { + it('provides resourceTypeAndCredentialManager with resourceType if able to determine from prerequest (fetch)', async () => { const ctx = { getAUTUrl: sinon.stub().returns('http://localhost:8080'), remoteStates: { isPrimarySuperDomainOrigin: sinon.stub().returns(false), }, - requestedWithAndCredentialManager: { + resourceTypeAndCredentialManager: { get: sinon.stub().returns({}), }, req: { + resourceType: 'fetch', proxiedUrl: 'http://localhost:8080', - headers: { - 'x-cypress-is-xhr-or-fetch': 'fetch', - }, } as Partial, res: { on: (event, listener) => {}, @@ -217,29 +168,27 @@ describe('http/request-middleware', () => { }, } - await testMiddleware([ExtractCypressMetadataHeaders], ctx) + await testMiddleware([CalculateCredentialLevelIfApplicable], ctx) .then(() => { - expect(ctx.requestedWithAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `fetch`) + expect(ctx.resourceTypeAndCredentialManager.get).to.have.been.calledWith('http://localhost:8080', `fetch`) }) }) - it('sets the requestedWith and credentialsLevel on the request from whatever is returned by requestedWithAndCredentialManager if conditions apply', async () => { + it('sets the resourceType and credentialsLevel on the request from whatever is returned by resourceTypeAndCredentialManager if conditions apply, assuming resourceType does NOT exist on the request', async () => { const ctx = { getAUTUrl: sinon.stub().returns('http://localhost:8080'), remoteStates: { isPrimarySuperDomainOrigin: sinon.stub().returns(false), }, - requestedWithAndCredentialManager: { + resourceTypeAndCredentialManager: { get: sinon.stub().returns({ - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: 'same-origin', }), }, req: { + resourceType: undefined, proxiedUrl: 'http://localhost:8080', - headers: { - 'x-cypress-is-xhr-or-fetch': 'true', - }, } as Partial, res: { on: (event, listener) => {}, @@ -247,9 +196,9 @@ describe('http/request-middleware', () => { }, } - await testMiddleware([ExtractCypressMetadataHeaders], ctx) + await testMiddleware([CalculateCredentialLevelIfApplicable], ctx) .then(() => { - expect(ctx.req.requestedWith).to.equal('fetch') + expect(ctx.req.resourceType).to.equal('fetch') expect(ctx.req.credentialsLevel).to.equal('same-origin') }) }) @@ -373,7 +322,7 @@ describe('http/request-middleware', () => { it('is a noop if cookies do NOT need to be attached to request', async () => { const ctx = await getContext(['request=cookie'], ['jar=cookie'], 'http://foobar.com', 'http://app.foobar.com') - ctx.req.requestedWith = 'fetch' + ctx.req.resourceType = 'fetch' ctx.req.credentialsLevel = 'omit' await testMiddleware([MaybeAttachCrossOriginCookies], ctx) @@ -384,7 +333,7 @@ describe('http/request-middleware', () => { it(`allows setting cookies on request if resource type cannot be determined, but comes from the AUT frame (likely in the case of documents or redirects)`, async function () { const ctx = await getContext([], ['jar=cookie'], 'http://foobar.com/index.html', 'http://app.foobar.com/index.html') - ctx.req.requestedWith = undefined + ctx.req.resourceType = undefined ctx.req.credentialsLevel = undefined ctx.req.isAUTFrame = true await testMiddleware([MaybeAttachCrossOriginCookies], ctx) @@ -395,7 +344,7 @@ describe('http/request-middleware', () => { it(`otherwise, does not allow setting cookies if request type cannot be determined and is not from the AUT and is cross-origin`, async function () { const ctx = await getContext([], ['jar=cookie'], 'http://foobar.com/index.html', 'http://app.foobar.com/index.html') - ctx.req.requestedWith = undefined + ctx.req.resourceType = undefined ctx.req.credentialsLevel = undefined ctx.req.isAUTFrame = false await testMiddleware([MaybeAttachCrossOriginCookies], ctx) @@ -406,7 +355,7 @@ describe('http/request-middleware', () => { it('sets the cookie header to undefined if no cookies exist on the request, none in the jar, but cookies should be attached', async () => { const ctx = await getContext([], [], 'http://foobar.com', 'http://app.foobar.com') - ctx.req.requestedWith = 'xhr' + ctx.req.resourceType = 'xhr' ctx.req.credentialsLevel = true await testMiddleware([MaybeAttachCrossOriginCookies], ctx) @@ -417,7 +366,7 @@ describe('http/request-middleware', () => { it('prepends cookie jar cookies to request', async () => { const ctx = await getContext(['request=cookie'], ['jar=cookie'], 'http://foobar.com', 'http://app.foobar.com') - ctx.req.requestedWith = 'fetch' + ctx.req.resourceType = 'fetch' ctx.req.credentialsLevel = 'include' await testMiddleware([MaybeAttachCrossOriginCookies], ctx) diff --git a/packages/proxy/test/unit/http/response-middleware.spec.ts b/packages/proxy/test/unit/http/response-middleware.spec.ts index 52829fad34..770700ff3f 100644 --- a/packages/proxy/test/unit/http/response-middleware.spec.ts +++ b/packages/proxy/test/unit/http/response-middleware.spec.ts @@ -1104,7 +1104,7 @@ describe('http/response-middleware', function () { }, req: { // a same-site request that has the ability to set first-party cookies in the browser - requestedWith: 'fetch', + resourceType: 'fetch', credentialsLevel: credentialLevel, proxiedUrl: 'https://www.foobar.com/test-request', }, @@ -1159,7 +1159,7 @@ describe('http/response-middleware', function () { }, req: { // a same-site request that has the ability to set first-party cookies in the browser - requestedWith: 'xhr', + resourceType: 'xhr', credentialsLevel: credentialLevel, proxiedUrl: 'https://www.foobar.com/test-request', }, @@ -1213,7 +1213,7 @@ describe('http/response-middleware', function () { }, req: { // a same-site request that has the ability to set first-party cookies in the browser - requestedWith: 'fetch', + resourceType: 'fetch', credentialsLevel: 'omit', proxiedUrl: 'https://www.foobar.com/test-request', }, @@ -1269,7 +1269,7 @@ describe('http/response-middleware', function () { }, req: { // a same-site request that has the ability to set first-party cookies in the browser - requestedWith: 'fetch', + resourceType: 'fetch', credentialsLevel: 'include', proxiedUrl: 'https://app.foobar.com/test-request', }, @@ -1322,7 +1322,7 @@ describe('http/response-middleware', function () { }, req: { // a same-site request that has the ability to set first-party cookies in the browser - requestedWith: 'xhr', + resourceType: 'xhr', credentialsLevel: true, proxiedUrl: 'https://app.foobar.com/test-request', }, @@ -1376,7 +1376,7 @@ describe('http/response-middleware', function () { }, req: { // a same-site request that has the ability to set first-party cookies in the browser - requestedWith: 'fetch', + resourceType: 'fetch', credentialsLevel: credentialLevel, proxiedUrl: 'https://app.foobar.com/test-request', }, @@ -1415,7 +1415,7 @@ describe('http/response-middleware', function () { }, req: { // a cross-site request that has the ability to set cookies in the browser - requestedWith: 'fetch', + resourceType: 'fetch', credentialsLevel: 'include', proxiedUrl: 'https://www.barbaz.com/test-request', }, @@ -1466,7 +1466,7 @@ describe('http/response-middleware', function () { }, req: { // a cross-site request that has the ability to set cookies in the browser - requestedWith: 'fetch', + resourceType: 'fetch', credentialsLevel: credentialLevel, proxiedUrl: 'https://www.barbaz.com/test-request', }, @@ -1500,7 +1500,7 @@ describe('http/response-middleware', function () { }, req: { // a cross-site request that has the ability to set cookies in the browser - requestedWith: 'xhr', + resourceType: 'xhr', credentialsLevel: true, proxiedUrl: 'https://www.barbaz.com/test-request', }, @@ -1550,7 +1550,7 @@ describe('http/response-middleware', function () { }, req: { // a cross-site request that has the ability to set cookies in the browser - requestedWith: 'xhr', + resourceType: 'xhr', credentialsLevel: false, proxiedUrl: 'https://www.barbaz.com/test-request', }, @@ -1584,7 +1584,7 @@ describe('http/response-middleware', function () { }, req: { // a cross-site request that has the ability to set cookies in the browser - requestedWith: 'xhr', + resourceType: 'xhr', credentialsLevel: true, proxiedUrl: 'https://www.barbaz.com/test-request', }, diff --git a/packages/runner/injection/patches/fetch.ts b/packages/runner/injection/patches/fetch.ts index e7dcaa4717..7db4e90493 100644 --- a/packages/runner/injection/patches/fetch.ts +++ b/packages/runner/injection/patches/fetch.ts @@ -33,10 +33,10 @@ export const patchFetch = (window) => { credentials = credentials || 'same-origin' // if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies - // if the option isn't set, we can imply the default as we know the requestedWith in the proxy + // if the option isn't set, we can imply the default as we know the resourceType in the proxy await requestSentWithCredentials({ url, - requestedWith: 'fetch', + resourceType: 'fetch', credentialStatus: credentials, }) } finally { diff --git a/packages/runner/injection/patches/utils/index.ts b/packages/runner/injection/patches/utils/index.ts index 1f622dae2f..c9cc50942a 100644 --- a/packages/runner/injection/patches/utils/index.ts +++ b/packages/runner/injection/patches/utils/index.ts @@ -57,10 +57,10 @@ export const postMessagePromise = ({ event, data = {}, timeout }: {event: str /** * Returns a promise from the backend request for the 'request:sent:with:credentials' event. - * @param args - an object containing a url, requestedWith and Credential status. + * @param args - an object containing a url, resourceType and Credential status. * @returns A Promise or null depending on the url parameter. */ -export const requestSentWithCredentials = (args: {url?: string, requestedWith: 'xhr' | 'fetch', credentialStatus: string | boolean}): Promise | undefined => { +export const requestSentWithCredentials = (args: {url?: string, resourceType: 'xhr' | 'fetch', credentialStatus: string | boolean}): Promise | undefined => { if (args.url) { // If cypress is enabled on the window use that, otherwise use post message to call out to the primary cypress instance. // cypress may be found on the window if this is either the primary cypress instance or if a spec bridge has already been created for this spec bridge. diff --git a/packages/runner/injection/patches/xmlHttpRequest.ts b/packages/runner/injection/patches/xmlHttpRequest.ts index 90351f01ef..f1dfdb69b8 100644 --- a/packages/runner/injection/patches/xmlHttpRequest.ts +++ b/packages/runner/injection/patches/xmlHttpRequest.ts @@ -20,10 +20,10 @@ export const patchXmlHttpRequest = (window: Window) => { window.XMLHttpRequest.prototype.send = async function (...args) { try { // if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies - // if the option isn't set, we can imply the default as we know the "requestedWith" in the proxy + // if the option isn't set, we can imply the default as we know the "resourceType" in the proxy await requestSentWithCredentials({ url: this._url, - requestedWith: 'xhr', + resourceType: 'xhr', credentialStatus: this.withCredentials, }) } finally { diff --git a/packages/server/lib/browsers/cdp_automation.ts b/packages/server/lib/browsers/cdp_automation.ts index c71e5f2f29..77558279d5 100644 --- a/packages/server/lib/browsers/cdp_automation.ts +++ b/packages/server/lib/browsers/cdp_automation.ts @@ -293,19 +293,19 @@ export class CdpAutomation { }) } - private _continueRequest = (client, params, headers?) => { + private _continueRequest = (client, params, header?) => { const details: Protocol.Fetch.ContinueRequestRequest = { requestId: params.requestId, } - if (headers && headers.length) { + if (header) { // headers are received as an object but need to be an array // to modify them const currentHeaders = _.map(params.request.headers, (value, name) => ({ name, value })) details.headers = [ ...currentHeaders, - ...headers, + header, ] } @@ -346,46 +346,26 @@ export class CdpAutomation { _handlePausedRequests = async (client) => { // NOTE: only supported in chromium based browsers - await client.send('Fetch.enable') + await client.send('Fetch.enable', { + // only enable request pausing for documents to determine the AUT iframe + patterns: [{ + resourceType: 'Document', + }], + }) // adds a header to the request to mark it as a request for the AUT frame // itself, so the proxy can utilize that for injection purposes client.on('Fetch.requestPaused', async (params: Protocol.Fetch.RequestPausedEvent) => { - const addedHeaders: { - name: string - value: string - }[] = [] + if (await this._isAUTFrame(params.frameId)) { + debugVerbose('add X-Cypress-Is-AUT-Frame header to: %s', params.request.url) - /** - * Unlike the the web extension or Electrons's onBeforeSendHeaders, CDP can discern the difference - * between fetch or xhr resource types. Because of this, we set X-Cypress-Is-XHR-Or-Fetch to either - * 'xhr' or 'fetch' with CDP so the middleware can assume correct defaults in case credential/resourceTypes - * are not sent to the server. - * @see https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType - */ - if (params.resourceType === 'XHR' || params.resourceType === 'Fetch') { - debugVerbose('add X-Cypress-Is-XHR-Or-Fetch header to: %s', params.request.url) - addedHeaders.push({ - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: params.resourceType.toLowerCase(), + return this._continueRequest(client, params, { + name: 'X-Cypress-Is-AUT-Frame', + value: 'true', }) } - if ( - // is a script, stylesheet, image, etc - params.resourceType !== 'Document' - || !(await this._isAUTFrame(params.frameId)) - ) { - return this._continueRequest(client, params, addedHeaders) - } - - debugVerbose('add X-Cypress-Is-AUT-Frame header to: %s', params.request.url) - addedHeaders.push({ - name: 'X-Cypress-Is-AUT-Frame', - value: 'true', - }) - - return this._continueRequest(client, params, addedHeaders) + return this._continueRequest(client, params) }) } diff --git a/packages/server/lib/server-base.ts b/packages/server/lib/server-base.ts index 8676881d95..c6532fe367 100644 --- a/packages/server/lib/server-base.ts +++ b/packages/server/lib/server-base.ts @@ -33,7 +33,7 @@ import type { FoundSpec } from '@packages/types' import type { Server as WebSocketServer } from 'ws' import { RemoteStates } from './remote_states' import { cookieJar, SerializableAutomationCookie } from './util/cookies' -import { requestedWithAndCredentialManager, RequestedWithAndCredentialManager } from './util/requestedWithAndCredentialManager' +import { resourceTypeAndCredentialManager, ResourceTypeAndCredentialManager } from './util/resourceTypeAndCredentialManager' const debug = Debug('cypress:server:server-base') @@ -116,7 +116,7 @@ export abstract class ServerBase { protected request: Request protected isListening: boolean protected socketAllowed: SocketAllowed - protected requestedWithAndCredentialManager: RequestedWithAndCredentialManager + protected resourceTypeAndCredentialManager: ResourceTypeAndCredentialManager protected _fileServer: FileServer | null protected _baseUrl: string | null protected _server?: DestroyableHttpServer @@ -147,7 +147,7 @@ export abstract class ServerBase { } }) - this.requestedWithAndCredentialManager = requestedWithAndCredentialManager + this.resourceTypeAndCredentialManager = resourceTypeAndCredentialManager } ensureProp = ensureProp @@ -189,7 +189,7 @@ export abstract class ServerBase { this.socket.toDriver('cross:origin:cookies', cookies) }) - this.socket.localBus.on('request:sent:with:credentials', this.requestedWithAndCredentialManager.set) + this.socket.localBus.on('request:sent:with:credentials', this.resourceTypeAndCredentialManager.set) } abstract createServer ( @@ -229,7 +229,7 @@ export abstract class ServerBase { this.createNetworkProxy({ config, remoteStates: this._remoteStates, - requestedWithAndCredentialManager: this.requestedWithAndCredentialManager, + resourceTypeAndCredentialManager: this.resourceTypeAndCredentialManager, shouldCorrelatePreRequests, }) @@ -323,7 +323,7 @@ export abstract class ServerBase { return e } - createNetworkProxy ({ config, remoteStates, requestedWithAndCredentialManager, shouldCorrelatePreRequests }) { + createNetworkProxy ({ config, remoteStates, resourceTypeAndCredentialManager, shouldCorrelatePreRequests }) { const getFileServerToken = () => { return this._fileServer?.token } @@ -340,7 +340,7 @@ export abstract class ServerBase { netStubbingState: this.netStubbingState, request: this.request, serverBus: this._eventBus, - requestedWithAndCredentialManager, + resourceTypeAndCredentialManager, }) } @@ -354,7 +354,7 @@ export abstract class ServerBase { this.networkProxy.reset() this.netStubbingState.reset() this._remoteStates.reset() - this.requestedWithAndCredentialManager.clear() + this.resourceTypeAndCredentialManager.clear() } const io = this.socket.startListening(this.server, automation, config, options) @@ -489,7 +489,7 @@ export abstract class ServerBase { reset () { this._networkProxy?.reset() - this.requestedWithAndCredentialManager.clear() + this.resourceTypeAndCredentialManager.clear() const baseUrl = this._baseUrl ?? '' return this._remoteStates.set(baseUrl) diff --git a/packages/server/lib/util/requestedWithAndCredentialManager.ts b/packages/server/lib/util/resourceTypeAndCredentialManager.ts similarity index 65% rename from packages/server/lib/util/requestedWithAndCredentialManager.ts rename to packages/server/lib/util/resourceTypeAndCredentialManager.ts index ff0a85bf1d..f108a562cd 100644 --- a/packages/server/lib/util/requestedWithAndCredentialManager.ts +++ b/packages/server/lib/util/resourceTypeAndCredentialManager.ts @@ -1,9 +1,10 @@ import md5 from 'md5' import Debug from 'debug' -import type { RequestCredentialLevel, RequestedWithHeader } from '@packages/proxy' +import type { RequestCredentialLevel } from '@packages/proxy' +import type { ResourceType } from '@packages/net-stubbing' type AppliedCredentialByUrlAndResourceMap = Map> @@ -16,16 +17,16 @@ const hashUrl = (url: string): string => { // leverage a singleton Map throughout the server to prevent clashes with this context bindings const _appliedCredentialByUrlAndResourceMap: AppliedCredentialByUrlAndResourceMap = new Map() -class RequestedWithAndCredentialManagerClass { - get (url: string, optionalRequestedWith?: RequestedWithHeader): { - requestedWith: RequestedWithHeader +class ResourceTypeAndCredentialManagerClass { + get (url: string, optionalResourceType?: ResourceType): { + resourceType: ResourceType credentialStatus: RequestCredentialLevel } { const hashKey = hashUrl(url) debug(`credentials request received for request url ${url}, hashKey ${hashKey}`) let value: { - requestedWith: RequestedWithHeader + resourceType: ResourceType credentialStatus: RequestCredentialLevel } | undefined @@ -37,12 +38,12 @@ class RequestedWithAndCredentialManagerClass { debug(`credential value found ${value}`) } - // if value is undefined for any reason, apply defaults and assume xhr if no optionalRequestedWith - // optionalRequestedWith should be provided with CDP based browsers, so at least we have a fallback that is more accurate + // if value is undefined for any reason, apply defaults and assume xhr if no optionalResourceType + // optionalResourceType should be provided by the prerequest resourceType, so at least we have a fallback that is more accurate if (value === undefined) { value = { - requestedWith: optionalRequestedWith || 'xhr', - credentialStatus: optionalRequestedWith === 'fetch' ? 'same-origin' : false, + resourceType: optionalResourceType || 'xhr', + credentialStatus: optionalResourceType === 'fetch' ? 'same-origin' : false, } } @@ -50,27 +51,27 @@ class RequestedWithAndCredentialManagerClass { } set ({ url, - requestedWith, + resourceType, credentialStatus, }: { url: string - requestedWith: RequestedWithHeader + resourceType: ResourceType credentialStatus: RequestCredentialLevel }) { const hashKey = hashUrl(url) - debug(`credentials request stored for request url ${url}, requestedWith ${requestedWith}, hashKey ${hashKey}`) + debug(`credentials request stored for request url ${url}, resourceType ${resourceType}, hashKey ${hashKey}`) let urlHashValue = _appliedCredentialByUrlAndResourceMap.get(hashKey) if (!urlHashValue) { _appliedCredentialByUrlAndResourceMap.set(hashKey, [{ - requestedWith, + resourceType, credentialStatus, }]) } else { urlHashValue.push({ - requestedWith, + resourceType, credentialStatus, }) } @@ -82,7 +83,7 @@ class RequestedWithAndCredentialManagerClass { } // export as a singleton -export const requestedWithAndCredentialManager = new RequestedWithAndCredentialManagerClass() +export const resourceTypeAndCredentialManager = new ResourceTypeAndCredentialManagerClass() // export but only as a type. We do NOT want others to create instances of the class as it is intended to be a singleton -export type RequestedWithAndCredentialManager = RequestedWithAndCredentialManagerClass +export type ResourceTypeAndCredentialManager = ResourceTypeAndCredentialManagerClass diff --git a/packages/server/test/unit/browsers/chrome_spec.js b/packages/server/test/unit/browsers/chrome_spec.js index d368c7fcf3..4f02a94523 100644 --- a/packages/server/test/unit/browsers/chrome_spec.js +++ b/packages/server/test/unit/browsers/chrome_spec.js @@ -399,10 +399,14 @@ describe('lib/browsers/chrome', () => { this.pageCriClient.send.withArgs('Page.getFrameTree').resolves(frameTree) }) - it('sends Fetch.enable', async function () { + it('sends Fetch.enable only for Document ResourceType', async function () { await chrome.open('chrome', 'http://', openOpts, this.automation) - expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable') + expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable', { + patterns: [{ + resourceType: 'Document', + }], + }) }) it('does not add header when not a document', async function () { @@ -413,9 +417,7 @@ describe('lib/browsers/chrome', () => { resourceType: 'Script', }) - expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', { - requestId: '1234', - }) + expect(this.pageCriClient.send).not.to.be.calledWith('Fetch.continueRequest') }) it('does not add header when it is a spec frame request', async function () { @@ -469,70 +471,6 @@ describe('lib/browsers/chrome', () => { }) }) - it('appends X-Cypress-Is-XHR-Or-Fetch header to fetch request', async function () { - await chrome.open('chrome', 'http://', openOpts, this.automation) - - this.pageCriClient.on.withArgs('Page.frameAttached').yield() - - await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({ - frameId: 'aut-frame-id', - requestId: '1234', - resourceType: 'Fetch', - request: { - url: 'http://localhost:3000/test-request', - headers: { - 'X-Foo': 'Bar', - }, - }, - }) - - expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', { - requestId: '1234', - headers: [ - { - name: 'X-Foo', - value: 'Bar', - }, - { - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: 'fetch', - }, - ], - }) - }) - - it('appends X-Cypress-Is-XHR-Or-Fetch header to xhr request', async function () { - await chrome.open('chrome', 'http://', openOpts, this.automation) - - this.pageCriClient.on.withArgs('Page.frameAttached').yield() - - await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({ - frameId: 'aut-frame-id', - requestId: '1234', - resourceType: 'XHR', - request: { - url: 'http://localhost:3000/test-request', - headers: { - 'X-Foo': 'Bar', - }, - }, - }) - - expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', { - requestId: '1234', - headers: [ - { - name: 'X-Foo', - value: 'Bar', - }, - { - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: 'xhr', - }, - ], - }) - }) - it('gets frame tree on Page.frameAttached', async function () { await chrome.open('chrome', 'http://', openOpts, this.automation) diff --git a/packages/server/test/unit/browsers/electron_spec.js b/packages/server/test/unit/browsers/electron_spec.js index 33d7d09664..462412dc4d 100644 --- a/packages/server/test/unit/browsers/electron_spec.js +++ b/packages/server/test/unit/browsers/electron_spec.js @@ -370,10 +370,14 @@ describe('lib/browsers/electron', () => { this.pageCriClient.send.withArgs('Page.getFrameTree').resolves(frameTree) }) - it('sends Fetch.enable', async function () { + it('sends Fetch.enable only for Document ResourceType', async function () { await electron._launch(this.win, this.url, this.automation, this.options) - expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable') + expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable', { + patterns: [{ + resourceType: 'Document', + }], + }) }) it('does not add header when not a document', async function () { @@ -384,9 +388,7 @@ describe('lib/browsers/electron', () => { resourceType: 'Script', }) - expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', { - requestId: '1234', - }) + expect(this.pageCriClient.send).not.to.be.calledWith('Fetch.continueRequest') }) it('does not add header when it is a spec frame request', async function () { @@ -440,70 +442,6 @@ describe('lib/browsers/electron', () => { }) }) - it('appends X-Cypress-Is-XHR-Or-Fetch header to fetch request', async function () { - await electron._launch(this.win, this.url, this.automation, this.options) - - this.pageCriClient.on.withArgs('Page.frameAttached').yield() - - await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({ - frameId: 'aut-frame-id', - requestId: '1234', - resourceType: 'Fetch', - request: { - url: 'http://localhost:3000/test-request', - headers: { - 'X-Foo': 'Bar', - }, - }, - }) - - expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', { - requestId: '1234', - headers: [ - { - name: 'X-Foo', - value: 'Bar', - }, - { - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: 'fetch', - }, - ], - }) - }) - - it('appends X-Cypress-Is-XHR-Or-Fetch header to xhr request', async function () { - await electron._launch(this.win, this.url, this.automation, this.options) - - this.pageCriClient.on.withArgs('Page.frameAttached').yield() - - await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({ - frameId: 'aut-frame-id', - requestId: '1234', - resourceType: 'XHR', - request: { - url: 'http://localhost:3000/test-request', - headers: { - 'X-Foo': 'Bar', - }, - }, - }) - - expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', { - requestId: '1234', - headers: [ - { - name: 'X-Foo', - value: 'Bar', - }, - { - name: 'X-Cypress-Is-XHR-Or-Fetch', - value: 'xhr', - }, - ], - }) - }) - it('gets frame tree on Page.frameAttached', async function () { await electron._launch(this.win, this.url, this.automation, this.options) diff --git a/packages/server/test/unit/util/requestedWithAndCredential_spec.ts b/packages/server/test/unit/util/requestedWithAndCredential_spec.ts deleted file mode 100644 index 03b822d5fe..0000000000 --- a/packages/server/test/unit/util/requestedWithAndCredential_spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { expect } from 'chai' -import { requestedWithAndCredentialManager } from '../../../lib/util/requestedWithAndCredentialManager' - -context('requestedWithAndCredentialManager Singleton', () => { - beforeEach(() => { - requestedWithAndCredentialManager.clear() - requestedWithAndCredentialManager.set({ - url: 'www.foobar.com/test-request', - requestedWith: 'xhr', - credentialStatus: true, - }) - - requestedWithAndCredentialManager.set({ - url: 'www.foobar.com%2Ftest-request-2', - requestedWith: 'fetch', - credentialStatus: 'same-origin', - }) - - requestedWithAndCredentialManager.set({ - url: 'www.foobar.com/test-request-2', - requestedWith: 'fetch', - credentialStatus: 'include', - }) - - requestedWithAndCredentialManager.set({ - url: 'www.foobar.com/test-request', - requestedWith: 'fetch', - credentialStatus: 'omit', - }) - - requestedWithAndCredentialManager.set({ - url: 'www.foobar.com/test-request', - requestedWith: 'fetch', - credentialStatus: 'include', - }) - }) - - it('gets the first record out of the queue matching the absolute url and removes it', () => { - expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ - requestedWith: 'xhr', - credentialStatus: true, - }) - - expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ - requestedWith: 'fetch', - credentialStatus: 'omit', - }) - - expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ - requestedWith: 'fetch', - credentialStatus: 'include', - }) - - // the default as no other records should exist in the map for this URL - expect(requestedWithAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ - requestedWith: 'xhr', - credentialStatus: false, - }) - }) - - it('can locate a record hash even when the URL is encoded', () => { - expect(requestedWithAndCredentialManager.get('www.foobar.com%2Ftest-request')).to.deep.equal({ - requestedWith: 'xhr', - credentialStatus: true, - }) - }) - - it('applies defaults if a record cannot be found without a requestedWith', () => { - expect(requestedWithAndCredentialManager.get('www.barbaz.com/test-request')).to.deep.equal({ - requestedWith: 'xhr', - credentialStatus: false, - }) - }) - - it('applies defaults if a record cannot be found with a requestedWith', () => { - expect(requestedWithAndCredentialManager.get('www.barbaz.com/test-request', 'xhr')).to.deep.equal({ - requestedWith: 'xhr', - credentialStatus: false, - }) - - expect(requestedWithAndCredentialManager.get('www.barbaz.com/test-request', 'fetch')).to.deep.equal({ - requestedWith: 'fetch', - credentialStatus: 'same-origin', - }) - }) -}) diff --git a/packages/server/test/unit/util/resourceTypeAndCredential_spec.ts b/packages/server/test/unit/util/resourceTypeAndCredential_spec.ts new file mode 100644 index 0000000000..a0422885fd --- /dev/null +++ b/packages/server/test/unit/util/resourceTypeAndCredential_spec.ts @@ -0,0 +1,86 @@ +import { expect } from 'chai' +import { resourceTypeAndCredentialManager } from '../../../lib/util/resourceTypeAndCredentialManager' + +context('resourceTypeAndCredentialManager Singleton', () => { + beforeEach(() => { + resourceTypeAndCredentialManager.clear() + resourceTypeAndCredentialManager.set({ + url: 'www.foobar.com/test-request', + resourceType: 'xhr', + credentialStatus: true, + }) + + resourceTypeAndCredentialManager.set({ + url: 'www.foobar.com%2Ftest-request-2', + resourceType: 'fetch', + credentialStatus: 'same-origin', + }) + + resourceTypeAndCredentialManager.set({ + url: 'www.foobar.com/test-request-2', + resourceType: 'fetch', + credentialStatus: 'include', + }) + + resourceTypeAndCredentialManager.set({ + url: 'www.foobar.com/test-request', + resourceType: 'fetch', + credentialStatus: 'omit', + }) + + resourceTypeAndCredentialManager.set({ + url: 'www.foobar.com/test-request', + resourceType: 'fetch', + credentialStatus: 'include', + }) + }) + + it('gets the first record out of the queue matching the absolute url and removes it', () => { + expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ + resourceType: 'xhr', + credentialStatus: true, + }) + + expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ + resourceType: 'fetch', + credentialStatus: 'omit', + }) + + expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ + resourceType: 'fetch', + credentialStatus: 'include', + }) + + // the default as no other records should exist in the map for this URL + expect(resourceTypeAndCredentialManager.get('www.foobar.com/test-request')).to.deep.equal({ + resourceType: 'xhr', + credentialStatus: false, + }) + }) + + it('can locate a record hash even when the URL is encoded', () => { + expect(resourceTypeAndCredentialManager.get('www.foobar.com%2Ftest-request')).to.deep.equal({ + resourceType: 'xhr', + credentialStatus: true, + }) + }) + + it('applies defaults if a record cannot be found without a resourceType', () => { + expect(resourceTypeAndCredentialManager.get('www.barbaz.com/test-request')).to.deep.equal({ + resourceType: 'xhr', + credentialStatus: false, + }) + }) + + it('applies defaults if a record cannot be found with a resourceType', () => { + expect(resourceTypeAndCredentialManager.get('www.barbaz.com/test-request', 'xhr')).to.deep.equal({ + resourceType: 'xhr', + credentialStatus: false, + }) + + expect(resourceTypeAndCredentialManager.get('www.barbaz.com/test-request', 'fetch')).to.deep.equal({ + resourceType: 'fetch', + credentialStatus: 'same-origin', + }) + }) +}) diff --git a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json index 22593fda0b..ebbc188893 100644 --- a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json @@ -4435,7 +4435,7 @@ "./packages/server/lib/util/print-run.ts", "./packages/server/lib/util/proxy.ts", "./packages/server/lib/util/random.js", - "./packages/server/lib/util/requestedWithAndCredentialManager.ts", + "./packages/server/lib/util/resourceTypeAndCredentialManager.ts", "./packages/server/lib/util/server_destroy.ts", "./packages/server/lib/util/shell.js", "./packages/server/lib/util/socket_allowed.ts", diff --git a/tooling/v8-snapshot/cache/linux/snapshot-meta.json b/tooling/v8-snapshot/cache/linux/snapshot-meta.json index a8b8392b1d..4bc0a95b56 100644 --- a/tooling/v8-snapshot/cache/linux/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/linux/snapshot-meta.json @@ -4434,7 +4434,7 @@ "./packages/server/lib/util/print-run.ts", "./packages/server/lib/util/proxy.ts", "./packages/server/lib/util/random.js", - "./packages/server/lib/util/requestedWithAndCredentialManager.ts", + "./packages/server/lib/util/resourceTypeAndCredentialManager.ts", "./packages/server/lib/util/server_destroy.ts", "./packages/server/lib/util/shell.js", "./packages/server/lib/util/socket_allowed.ts", diff --git a/tooling/v8-snapshot/cache/win32/snapshot-meta.json b/tooling/v8-snapshot/cache/win32/snapshot-meta.json index 40964be09e..d4c5693399 100644 --- a/tooling/v8-snapshot/cache/win32/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/win32/snapshot-meta.json @@ -4434,7 +4434,7 @@ "./packages/server/lib/util/print-run.ts", "./packages/server/lib/util/proxy.ts", "./packages/server/lib/util/random.js", - "./packages/server/lib/util/requestedWithAndCredentialManager.ts", + "./packages/server/lib/util/resourceTypeAndCredentialManager.ts", "./packages/server/lib/util/server_destroy.ts", "./packages/server/lib/util/shell.js", "./packages/server/lib/util/socket_allowed.ts",