From 8c81ae3c318f2a4fe49b9138b333ddf447364639 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) # Conflicts: # x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.ts # x-pack/platform/plugins/shared/screenshotting/tsconfig.json --- .../browsers/chromium/templates/index.test.ts | 124 ++++++++++++++++++ .../browsers/chromium/templates/index.ts | 78 +++++++++++ .../shared/screenshotting/tsconfig.json | 34 +++++ 3 files changed, 236 insertions(+) create mode 100644 x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.test.ts create mode 100644 x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.ts create mode 100644 x-pack/platform/plugins/shared/screenshotting/tsconfig.json 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 new file mode 100644 index 0000000000000..179e21ce94265 --- /dev/null +++ b/x-pack/platform/plugins/shared/screenshotting/server/browsers/chromium/templates/index.ts @@ -0,0 +1,78 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import fs from 'fs/promises'; +import path from 'path'; +import Handlebars, { TemplateDelegate } from '@kbn/handlebars'; +import { assetPath } from '../../../constants'; + +// 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.compileAST(contentsBuffer.toString(), HBCompileOptions); +} + +interface HeaderTemplateInput { + title: string; +} +interface GetHeaderArgs { + title: string; +} + +export async function getHeaderTemplate({ title }: GetHeaderArgs): Promise { + const template = await compileTemplate( + path.resolve(__dirname, './header.handlebars.html') + ); + return template({ title }); +} + +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')}`; +} + +interface FooterTemplateInput { + base64FooterLogo: string; + hasCustomLogo: boolean; + poweredByElasticCopy: string; +} + +interface GetFooterArgs { + logo?: string; +} + +export async function getFooterTemplate({ logo }: GetFooterArgs): Promise { + const template = await compileTemplate( + path.resolve(__dirname, './footer.handlebars.html') + ); + const hasCustomLogo = Boolean(logo); + return template({ + base64FooterLogo: hasCustomLogo ? logo! : await getDefaultFooterLogo(), + hasCustomLogo, + poweredByElasticCopy: i18n.translate( + 'xpack.screenshotting.exportTypes.printablePdf.footer.logoDescription', + { + defaultMessage: 'Powered by Elastic', + } + ), + }); +} diff --git a/x-pack/platform/plugins/shared/screenshotting/tsconfig.json b/x-pack/platform/plugins/shared/screenshotting/tsconfig.json new file mode 100644 index 0000000000000..67363ecf6ffde --- /dev/null +++ b/x-pack/platform/plugins/shared/screenshotting/tsconfig.json @@ -0,0 +1,34 @@ +{ + "extends": "../../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "../../../../../typings/**/*" + ], + "kbn_references": [ + "@kbn/core", + { "path": "../../../../../src/setup_node_env/tsconfig.json" }, + "@kbn/expressions-plugin", + "@kbn/screenshot-mode-plugin", + "@kbn/cloud-plugin", + "@kbn/utility-types", + "@kbn/logging", + "@kbn/std", + "@kbn/i18n", + "@kbn/utils", + "@kbn/core-logging-server-mocks", + "@kbn/logging-mocks", + "@kbn/core-http-server", + "@kbn/core-plugins-server", + "@kbn/task-manager-plugin", + "@kbn/screenshotting-server", + "@kbn/handlebars", + ], + "exclude": [ + "target/**/*", + ] +}