mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-06 23:29:51 -06:00
* Add winPropAccessor to security.js, remove other replacers
* Add start of Cypress.resolveWindowReference
* Add regexes for dot and bracket access
* Some security_spec tests pass with new injection
* Add resolveWindowReference unit tests
* Old security_spec now passes with resolveWindowReference
* Inject stub resolveWindowReference so proxy still works outside of Cypress
* wip: rewrite HTML + JS with tokenizer
* Move to using esprima + hyntax to rewrite JS + HTML
* remove comment; oneLine makes the whole thing commented
* Fix tests, apple.com edge case
* wip: add getOrSet
* Revert "wip: add getOrSet"
This reverts commit a5c647c00f.
* release 3.5.0 [skip ci]
* use recast to replace window property accesses
* replace assignments to top properly
* fix yarn.lock
* bump deps
* update integration tests
* remove old security ts?
* fix integration spec
* always ignore js interception failure
* use globalThis instead of window
* add experimentalSourceRewriting flag
* restore regex-writer spec
* fix types
* update config_spec
* add source rewriting spec
* cleanup
* simplify rewriting logic, move rules into rewriter package
* create threaded rewriting tool for non-streaming use
* update @packages/rewriter to use threads for async
* use async rewriting where convenient
* add worker-shim.js
* add performance info to debug logs
* properly handle +=, -=, ...
* add proxy, rewriter to unit-tests stage
* cleanup
* use parse5 to rewrite HTML, strip SRI
* update tests
* reorganization, cleanup
* rewrite ALL parent, top identifiers except in a few cases
* handle many JS edge cases
* ensure parse5@5.1.1 is installed
* update yarn.lock
* update tests
* add debugging, add tests
* add attempted repro for .href issue
* implement source maps + extending inline source maps
* update opts passing in proxy layer
* fix sourcemap naming structure
* update tests to account for sourcemaps
* sourcemap tests
* remote source maps work
* comment
* update rewriter tests
* clean up TODOs in resolveWindowReference
* remove @types/nock
* clean up todos in deferred-source-map-cache
* fix rewriter build script
* fix concatStream import
* bump expectedresultcount
* clean up js-rules
* threading improvements, workaround for Electron segfault
* no visit_spec for now
* fix 6_visit_spec
* update MAX_WORKER_THREADS
* add repro for #3975
* cleanup
* cleanup
* make better use of namedTypes and builders
* get rid of the horrific closureDetectionTernary
ast-types keeps track of scope, so it is unneeded
* fix #3975, #3994
* add x-sourcemap, sourcemap header support
* snap-shot-it 7.9.3
* add deferred-source-map-cache-spec
* add tests
* Throw error in driver if AST rewriting fails
* Fix "location = 'relative-url'"
* fix max recursion depth
* slim down some fixtures
* fix window.location usage
* don't mess with `frames` at all
* no integration tests
* skip testing apple.com for now
* update wording: regex-based vs. ast-based
* skip real-world tests for now
* add some padding to process.exit workaround
* fix resolvers_spec
* fix html-spec
* cleanup
* Update packages/rewriter/lib/js-rules.ts
* Update packages/driver/src/cypress/resolvers.ts
* just import find by itself
* privatize typedefs for Cypress.state, remove .gitignore, remove dead code
Co-authored-by: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
262 lines
6.0 KiB
TypeScript
262 lines
6.0 KiB
TypeScript
import _ from 'lodash'
|
|
import debugModule from 'debug'
|
|
import ErrorMiddleware from './error-middleware'
|
|
import { HttpBuffers } from './util/buffers'
|
|
import { IncomingMessage } from 'http'
|
|
import Bluebird from 'bluebird'
|
|
import { Readable } from 'stream'
|
|
import { Request, Response } from 'express'
|
|
import RequestMiddleware from './request-middleware'
|
|
import ResponseMiddleware from './response-middleware'
|
|
import { DeferredSourceMapCache } from '@packages/rewriter'
|
|
|
|
const debug = debugModule('cypress:proxy:http')
|
|
|
|
export enum HttpStages {
|
|
IncomingRequest,
|
|
IncomingResponse,
|
|
Error
|
|
}
|
|
|
|
export type HttpMiddleware<T> = (this: HttpMiddlewareThis<T>) => void
|
|
|
|
export type CypressRequest = Request & {
|
|
// TODO: what's this difference from req.url? is it only for non-proxied requests?
|
|
proxiedUrl: string
|
|
abort: () => void
|
|
}
|
|
|
|
type MiddlewareStacks = {
|
|
[stage in HttpStages]: {
|
|
[name: string]: HttpMiddleware<any>
|
|
}
|
|
}
|
|
|
|
export type CypressResponse = Response & {
|
|
isInitial: null | boolean
|
|
wantsInjection: 'full' | 'partial' | false
|
|
wantsSecurityRemoved: null | boolean
|
|
}
|
|
|
|
type HttpMiddlewareCtx<T> = {
|
|
req: CypressRequest
|
|
res: CypressResponse
|
|
|
|
middleware: MiddlewareStacks
|
|
deferSourceMapRewrite: (opts: { js: string, url: string }) => string
|
|
} & T
|
|
|
|
const READONLY_MIDDLEWARE_KEYS: (keyof HttpMiddlewareThis<{}>)[] = [
|
|
'buffers',
|
|
'config',
|
|
'getFileServerToken',
|
|
'getRemoteState',
|
|
'request',
|
|
'next',
|
|
'end',
|
|
'onResponse',
|
|
'onError',
|
|
'skipMiddleware',
|
|
]
|
|
|
|
type HttpMiddlewareThis<T> = HttpMiddlewareCtx<T> & Readonly<{
|
|
buffers: HttpBuffers
|
|
config: any
|
|
getFileServerToken: () => string
|
|
getRemoteState: () => any
|
|
request: any
|
|
|
|
next: () => void
|
|
/**
|
|
* Call to completely end the stage, bypassing any remaining middleware.
|
|
*/
|
|
end: () => void
|
|
onResponse: (incomingRes: Response, resStream: Readable) => void
|
|
onError: (error: Error) => void
|
|
skipMiddleware: (name: string) => void
|
|
}>
|
|
|
|
export function _runStage (type: HttpStages, ctx: any) {
|
|
const stage = HttpStages[type]
|
|
|
|
debug('Entering stage %o', { stage })
|
|
|
|
const runMiddlewareStack = () => {
|
|
const middlewares = ctx.middleware[type]
|
|
|
|
// pop the first pair off the middleware
|
|
const middlewareName = _.keys(middlewares)[0]
|
|
|
|
if (!middlewareName) {
|
|
return Bluebird.resolve()
|
|
}
|
|
|
|
const middleware = middlewares[middlewareName]
|
|
|
|
ctx.middleware[type] = _.omit(middlewares, middlewareName)
|
|
|
|
return new Bluebird((resolve) => {
|
|
let ended = false
|
|
|
|
function copyChangedCtx () {
|
|
_.chain(fullCtx)
|
|
.omit(READONLY_MIDDLEWARE_KEYS)
|
|
.forEach((value, key) => {
|
|
if (ctx[key] !== value) {
|
|
ctx[key] = value
|
|
}
|
|
})
|
|
.value()
|
|
}
|
|
|
|
function _end (retval?) {
|
|
if (ended) {
|
|
return
|
|
}
|
|
|
|
ended = true
|
|
|
|
copyChangedCtx()
|
|
|
|
resolve(retval)
|
|
}
|
|
|
|
if (!middleware) {
|
|
return resolve()
|
|
}
|
|
|
|
debug('Running middleware %o', { stage, middlewareName })
|
|
|
|
const fullCtx = {
|
|
next: () => {
|
|
copyChangedCtx()
|
|
|
|
_end(runMiddlewareStack())
|
|
},
|
|
end: () => _end(),
|
|
onResponse: (incomingRes: IncomingMessage, resStream: Readable) => {
|
|
ctx.incomingRes = incomingRes
|
|
ctx.incomingResStream = resStream
|
|
|
|
_end()
|
|
},
|
|
onError: (error: Error) => {
|
|
debug('Error in middleware %o', { stage, middlewareName, error })
|
|
|
|
if (type === HttpStages.Error) {
|
|
return
|
|
}
|
|
|
|
ctx.error = error
|
|
|
|
_end(_runStage(HttpStages.Error, ctx))
|
|
},
|
|
|
|
skipMiddleware: (name) => {
|
|
ctx.middleware[type] = _.omit(ctx.middleware[type], name)
|
|
},
|
|
|
|
...ctx,
|
|
}
|
|
|
|
try {
|
|
middleware.call(fullCtx)
|
|
} catch (err) {
|
|
fullCtx.onError(err)
|
|
}
|
|
})
|
|
}
|
|
|
|
return runMiddlewareStack()
|
|
.then(() => {
|
|
debug('Leaving stage %o', { stage })
|
|
})
|
|
}
|
|
|
|
export class Http {
|
|
buffers: HttpBuffers
|
|
deferredSourceMapCache: DeferredSourceMapCache
|
|
config: any
|
|
getFileServerToken: () => string
|
|
getRemoteState: () => any
|
|
middleware: MiddlewareStacks
|
|
request: any
|
|
|
|
constructor (opts: {
|
|
config: any
|
|
getFileServerToken: () => string
|
|
getRemoteState: () => any
|
|
middleware?: MiddlewareStacks
|
|
request: any
|
|
}) {
|
|
this.buffers = new HttpBuffers()
|
|
this.deferredSourceMapCache = new DeferredSourceMapCache(opts.request)
|
|
|
|
this.config = opts.config
|
|
this.getFileServerToken = opts.getFileServerToken
|
|
this.getRemoteState = opts.getRemoteState
|
|
this.request = opts.request
|
|
|
|
if (typeof opts.middleware === 'undefined') {
|
|
this.middleware = {
|
|
[HttpStages.IncomingRequest]: RequestMiddleware,
|
|
[HttpStages.IncomingResponse]: ResponseMiddleware,
|
|
[HttpStages.Error]: ErrorMiddleware,
|
|
}
|
|
} else {
|
|
this.middleware = opts.middleware
|
|
}
|
|
}
|
|
|
|
handle (req: Request, res: Response) {
|
|
const ctx: HttpMiddlewareCtx<any> = {
|
|
req,
|
|
res,
|
|
|
|
buffers: this.buffers,
|
|
config: this.config,
|
|
getFileServerToken: this.getFileServerToken,
|
|
getRemoteState: this.getRemoteState,
|
|
request: this.request,
|
|
middleware: _.cloneDeep(this.middleware),
|
|
deferSourceMapRewrite: (opts) => {
|
|
this.deferredSourceMapCache.defer({
|
|
resHeaders: ctx.incomingRes.headers,
|
|
...opts,
|
|
})
|
|
},
|
|
}
|
|
|
|
return _runStage(HttpStages.IncomingRequest, ctx)
|
|
.then(() => {
|
|
if (ctx.incomingRes) {
|
|
return _runStage(HttpStages.IncomingResponse, ctx)
|
|
}
|
|
|
|
return debug('warning: Request was not fulfilled with a response.')
|
|
})
|
|
}
|
|
|
|
async handleSourceMapRequest (req: Request, res: Response) {
|
|
try {
|
|
const sm = await this.deferredSourceMapCache.resolve(req.params.id, req.headers)
|
|
|
|
if (!sm) {
|
|
throw new Error('no sourcemap found')
|
|
}
|
|
|
|
res.json(sm)
|
|
} catch (err) {
|
|
res.status(500).json({ err })
|
|
}
|
|
}
|
|
|
|
reset () {
|
|
this.buffers.reset()
|
|
}
|
|
|
|
setBuffer (buffer) {
|
|
return this.buffers.set(buffer)
|
|
}
|
|
}
|