Skip to content

Commit

Permalink
fix(template): Use proxy instead of deep clone (#17075)
Browse files Browse the repository at this point in the history
  • Loading branch information
zharinov committed Aug 9, 2022
1 parent 1c429f6 commit 35df2bd
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 21 deletions.
43 changes: 43 additions & 0 deletions lib/util/template/index.spec.ts
Expand Up @@ -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();
});
});
});
48 changes: 27 additions & 21 deletions lib/util/template/index.ts
Expand Up @@ -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);

Expand Down Expand Up @@ -162,28 +161,35 @@ const allowedFieldsList = Object.keys(allowedFields)

type CompileInput = Record<string, unknown>;

type FilteredObject = Record<string, CompileInput | CompileInput[] | unknown>;

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<CompileInput> = {
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<CompileInput>(input, compileInputProxyHandler);
}

const templateRegex = /{{(#(if|unless) )?([a-zA-Z]+)}}/g; // TODO #12873
Expand All @@ -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);
Expand Down

0 comments on commit 35df2bd

Please sign in to comment.