Files
cypress/packages/proxy/lib/http/util/rewriter.ts
T
Zach Bloomquist 6960f7cd78 Rewrite JS/HTML using AST-based approach (#5273)
* 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>
2020-05-11 12:54:14 -04:00

80 lines
2.1 KiB
TypeScript

import * as inject from './inject'
import * as astRewriter from './ast-rewriter'
import * as regexRewriter from './regex-rewriter'
export type SecurityOpts = {
isHtml?: boolean
url: string
useAstSourceRewriting: boolean
deferSourceMapRewrite: (opts: any) => string
}
export type InjectionOpts = {
domainName: string
wantsInjection: WantsInjection
wantsSecurityRemoved: any
}
const doctypeRe = /(<\!doctype.*?>)/i
const headRe = /(<head(?!er).*?>)/i
const bodyRe = /(<body.*?>)/i
const htmlRe = /(<html.*?>)/i
type WantsInjection = 'full' | 'partial' | false
function getRewriter (useAstSourceRewriting: boolean) {
return useAstSourceRewriting ? astRewriter : regexRewriter
}
function getHtmlToInject ({ domainName, wantsInjection }: InjectionOpts) {
switch (wantsInjection) {
case 'full':
return inject.full(domainName)
case 'partial':
return inject.partial(domainName)
default:
return
}
}
export async function html (html: string, opts: SecurityOpts & InjectionOpts) {
const replace = (re, str) => {
return html.replace(re, str)
}
const htmlToInject = getHtmlToInject(opts)
// strip clickjacking and framebusting
// from the HTML if we've been told to
if (opts.wantsSecurityRemoved) {
html = await Promise.resolve(getRewriter(opts.useAstSourceRewriting).strip(html, opts))
}
if (!htmlToInject) {
return html
}
// TODO: move this into regex-rewriting and have ast-rewriting handle this in its own way
switch (false) {
case !headRe.test(html):
return replace(headRe, `$1 ${htmlToInject}`)
case !bodyRe.test(html):
return replace(bodyRe, `<head> ${htmlToInject} </head> $1`)
case !htmlRe.test(html):
return replace(htmlRe, `$1 <head> ${htmlToInject} </head>`)
case !doctypeRe.test(html):
// if only <!DOCTYPE> content, inject <head> after doctype
return `${html}<head> ${htmlToInject} </head>`
default:
return `<head> ${htmlToInject} </head>${html}`
}
}
export function security (opts: SecurityOpts) {
return getRewriter(opts.useAstSourceRewriting).stripStream(opts)
}