From 35df2bdb7cc99883aeb7d2662994892720e2ed36 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Tue, 9 Aug 2022 20:19:03 +0300 Subject: [PATCH] fix(template): Use proxy instead of deep clone (#17075) --- lib/util/template/index.spec.ts | 43 +++++++++++++++++++++++++++++ lib/util/template/index.ts | 48 ++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/lib/util/template/index.spec.ts b/lib/util/template/index.spec.ts index cac4629ba0c8fe..3e5ae2d73efe4b 100644 --- a/lib/util/template/index.spec.ts +++ b/lib/util/template/index.spec.ts @@ -80,4 +80,47 @@ describe('util/template/index', () => { const output = template.compile(userTemplate, undefined as never); expect(output).toBe('foo'); }); + + describe('proxyCompileInput', () => { + const allowedField = 'body'; + const forbiddenField = 'foobar'; + + type TestCompileInput = Record< + typeof allowedField | typeof forbiddenField, + unknown + >; + + const compileInput: TestCompileInput = { + [allowedField]: 'allowed', + [forbiddenField]: 'forbidden', + }; + + it('accessing allowed files', () => { + const p = template.proxyCompileInput(compileInput); + + expect(p[allowedField]).toBe('allowed'); + expect(p[forbiddenField]).toBeUndefined(); + }); + + it('supports object nesting', () => { + const proxy = template.proxyCompileInput({ + [allowedField]: compileInput, + }); + + const obj = proxy[allowedField] as TestCompileInput; + expect(obj[allowedField]).toBe('allowed'); + expect(obj[forbiddenField]).toBeUndefined(); + }); + + it('supports array nesting', () => { + const proxy = template.proxyCompileInput({ + [allowedField]: [compileInput], + }); + + const arr = proxy[allowedField] as TestCompileInput[]; + const obj = arr[0]; + expect(obj[allowedField]).toBe('allowed'); + expect(obj[forbiddenField]).toBeUndefined(); + }); + }); }); diff --git a/lib/util/template/index.ts b/lib/util/template/index.ts index f6dfd30ae6b7fd..8a3b778beae93c 100644 --- a/lib/util/template/index.ts +++ b/lib/util/template/index.ts @@ -2,7 +2,6 @@ import is from '@sindresorhus/is'; import handlebars from 'handlebars'; import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; -import { clone } from '../clone'; handlebars.registerHelper('encodeURIComponent', encodeURIComponent); @@ -162,28 +161,35 @@ const allowedFieldsList = Object.keys(allowedFields) type CompileInput = Record; -type FilteredObject = Record; - -function getFilteredObject(input: CompileInput): FilteredObject { - const obj = clone(input); - const res: FilteredObject = {}; - const allAllowed = [ - ...Object.keys(allowedFields), - ...exposedConfigOptions, - ].sort(); - for (const field of allAllowed) { - const value = obj[field]; +const allowedTemplateFields = new Set([ + ...Object.keys(allowedFields), + ...exposedConfigOptions, +]); + +const compileInputProxyHandler: ProxyHandler = { + get(target: CompileInput, prop: keyof CompileInput): unknown { + if (!allowedTemplateFields.has(prop)) { + return undefined; + } + + const value = target[prop]; + if (is.array(value)) { - res[field] = value + return value .filter(is.plainObject) - .map((element) => getFilteredObject(element as CompileInput)); - } else if (is.plainObject(value)) { - res[field] = getFilteredObject(value); - } else if (!is.undefined(value)) { - res[field] = value; + .map((element) => proxyCompileInput(element as CompileInput)); } - } - return res; + + if (is.plainObject(value)) { + return proxyCompileInput(value); + } + + return value; + }, +}; + +export function proxyCompileInput(input: CompileInput): CompileInput { + return new Proxy(input, compileInputProxyHandler); } const templateRegex = /{{(#(if|unless) )?([a-zA-Z]+)}}/g; // TODO #12873 @@ -194,7 +200,7 @@ export function compile( filterFields = true ): string { const data = { ...GlobalConfig.get(), ...input }; - const filteredInput = filterFields ? getFilteredObject(data) : data; + const filteredInput = filterFields ? proxyCompileInput(data) : data; logger.trace({ template, filteredInput }, 'Compiling template'); if (filterFields) { const matches = template.matchAll(templateRegex);