From 20a9a1875e6716bd626d8e22320768a90b1d108f Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Tue, 29 Apr 2025 08:08:28 -0400 Subject: [PATCH] change reporting usage of `handlebars` to `@kbn/handlebars` (#217778) Change reporting's usage of `handlebars` to `@kbn/handlebars`. Also added a test to ensure user input is HTML escaped (it always has been, this just tests it). There should be no change to the final rendered output, at all. These changes only affect PDF and PNG reports, not CSV reports. (cherry picked from commit 3b5e96a4b8dc3d2741de658ea9ad7981617fe3db) --- .../browsers/chromium/templates/index.test.ts | 124 ++++++++++++++++++ .../browsers/chromium/templates/index.ts | 23 +++- .../shared/screenshotting/tsconfig.json | 1 + 3 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.test.ts diff --git a/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.test.ts b/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.test.ts new file mode 100644 index 0000000000000..90fdf4576c59e --- /dev/null +++ b/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.test.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getHeaderTemplate, getFooterTemplate, getDefaultFooterLogo } from '.'; + +let defaultFooterLogo: string; + +describe('templates/index', () => { + beforeAll(async () => { + defaultFooterLogo = (await getDefaultFooterLogo()).trim(); + }); + + describe('getHeaderTemplate()', () => { + it('works with a plain logo', async () => { + const title = 'plain'; + const result = await getHeaderTemplate({ title }); + expect(result).toBe(getHeader(title)); + }); + it('works with an html title', async () => { + const title = 'html'; + const result = await getHeaderTemplate({ title }); + expect(result).toBe(getHeader('<b>html</b>')); + }); + }); + + describe('getFooterTemplate()', () => { + it('works with no logo', async () => { + const result = await getFooterTemplate({}); + expect(result).toBe(getFooter()); + }); + it('works with a plain logo', async () => { + const logo = 'http://example.com/favico.ico'; + const result = await getFooterTemplate({ logo }); + expect(result).toBe(getFooter(logo)); + }); + it('works with an html logo', async () => { + const logo = '"/>${title} +`.trimStart(); +} + +function getFooter(logo?: string): string { + const hasLogo = !!logo; + + if (!hasLogo) { + logo = defFooterLogo(); + } + return ` + +
+ + +${getPoweredBy(hasLogo)}
+  of  +
+
+`.trimStart(); +} + +function getPoweredBy(hasLogo: boolean): string { + if (!hasLogo) return ''; + return `
Powered by Elastic
\n`; +} + +function defFooterLogo(): string { + return defaultFooterLogo!; +} diff --git a/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.ts b/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.ts index 7034dac76cfca..179e21ce94265 100644 --- a/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.ts +++ b/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.ts @@ -8,12 +8,27 @@ import { i18n } from '@kbn/i18n'; import fs from 'fs/promises'; import path from 'path'; -import Handlebars from 'handlebars'; +import Handlebars, { TemplateDelegate } from '@kbn/handlebars'; import { assetPath } from '../../../constants'; -async function compileTemplate(pathToTemplate: string): Promise> { +// see: https://handlebarsjs.com/guide/builtin-helpers.html +const HBCompileOptions = { + knownHelpersOnly: true, + knownHelpers: { + helperMissing: false, + blockHelperMissing: false, + each: false, + if: true, + unless: false, + with: false, + log: false, + lookup: false, + }, +}; + +async function compileTemplate(pathToTemplate: string): Promise> { const contentsBuffer = await fs.readFile(pathToTemplate); - return Handlebars.compile(contentsBuffer.toString()); + return Handlebars.compileAST(contentsBuffer.toString(), HBCompileOptions); } interface HeaderTemplateInput { @@ -30,7 +45,7 @@ export async function getHeaderTemplate({ title }: GetHeaderArgs): Promise { +export async function getDefaultFooterLogo(): Promise { const logoBuffer = await fs.readFile(path.resolve(assetPath, 'img', 'logo-grey.png')); return `data:image/png;base64,${logoBuffer.toString('base64')}`; } diff --git a/x-pack/platform/plugins/shared/screenshotting/tsconfig.json b/x-pack/platform/plugins/shared/screenshotting/tsconfig.json index 200c6d9c2592e..67363ecf6ffde 100644 --- a/x-pack/platform/plugins/shared/screenshotting/tsconfig.json +++ b/x-pack/platform/plugins/shared/screenshotting/tsconfig.json @@ -26,6 +26,7 @@ "@kbn/core-plugins-server", "@kbn/task-manager-plugin", "@kbn/screenshotting-server", + "@kbn/handlebars", ], "exclude": [ "target/**/*",