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 = /()/i const bodyRe = /()/i const htmlRe = /()/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 = await Promise.resolve(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, ` ${htmlToInject} $1`) case !htmlRe.test(html): return replace(htmlRe, `$1 ${htmlToInject} `) case !doctypeRe.test(html): // if only content, inject after doctype return `${html} ${htmlToInject} ` default: return ` ${htmlToInject} ${html}` } } export function security (opts: SecurityOpts) { return getRewriter(opts.useAstSourceRewriting).stripStream(opts) }