diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts new file mode 100644 index 0000000000000..85e002b805e46 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { withoutControlCharacters } from './string_utils'; + +describe('ensureSingleLineString()', () => { + test('works with plain ole strings', () => { + expect(withoutControlCharacters('')).toEqual(''); + expect(withoutControlCharacters(' a b c ')).toEqual(' a b c '); + }); + + test('works with multiple control characters', () => { + expect(withoutControlCharacters(' \r \n ')).toEqual(' ; ; '); + expect(withoutControlCharacters('\r \n ')).toEqual('; ; '); + expect(withoutControlCharacters(' \r \n')).toEqual(' ; ;'); + expect(withoutControlCharacters('\r \n')).toEqual('; ;'); + }); + + test('works with /00-/1F, except tab', () => { + for (let c = 0; c <= 0x1f; c++) { + if (c === 0x09) { + expect(withoutControlCharacters(String.fromCharCode(c))).toEqual('\t'); + } else { + expect(withoutControlCharacters(String.fromCharCode(c))).toEqual(';'); + } + } + expect(withoutControlCharacters(String.fromCharCode(0x20))).toEqual(' '); + }); + + test('works with /7F-/9F', () => { + expect(withoutControlCharacters(String.fromCharCode(0x7e))).toEqual('~'); + for (let c = 0x7f; c <= 0x9f; c++) { + expect(withoutControlCharacters(String.fromCharCode(c))).toEqual(';'); + } + const nbsp = String.fromCharCode(0xa0); + expect(withoutControlCharacters(nbsp)).toEqual(nbsp); + }); + + test('works with UCS newlines', () => { + expect(withoutControlCharacters('\u2027')).toEqual('\u2027'); + expect(withoutControlCharacters('\u2028')).toEqual(';'); + expect(withoutControlCharacters('\u2029')).toEqual(';'); + expect(withoutControlCharacters('\u202A')).toEqual('\u202A'); + }); +}); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.ts new file mode 100644 index 0000000000000..d0e3504677307 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// see: https://en.wikipedia.org/wiki/Unicode_control_characters +// but don't include tabs (0x09), they're fine +const CONTROL_CHAR_PATTERN = /[\x00-\x08]|[\x0A-\x1F]|[\x7F-\x9F]|[\u2028-\u2029]/g; + +// replaces control characters in string with ;, but leaves tabs +export function withoutControlCharacters(s: string): string { + return s.replace(CONTROL_CHAR_PATTERN, ';'); +} diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts index 76e639c994834..c59ddf97017fd 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -98,6 +98,6 @@ describe('execute()', () => { config: {}, secrets: {}, }); - expect(mockedLogger.info).toHaveBeenCalledWith('message text here'); + expect(mockedLogger.info).toHaveBeenCalledWith('server-log: message text here'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts index c2af29051d8dd..0edf409e4d46c 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts @@ -10,6 +10,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '../../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; +import { withoutControlCharacters } from './lib/string_utils'; + +const ACTION_NAME = 'server-log'; // params definition @@ -17,21 +20,24 @@ export type ActionParamsType = TypeOf; const ParamsSchema = schema.object({ message: schema.string(), - level: schema.oneOf([ - schema.literal('trace'), - schema.literal('debug'), - schema.literal('info'), - schema.literal('warn'), - schema.literal('error'), - schema.literal('fatal'), - ]), + level: schema.oneOf( + [ + schema.literal('trace'), + schema.literal('debug'), + schema.literal('info'), + schema.literal('warn'), + schema.literal('error'), + schema.literal('fatal'), + ], + { defaultValue: 'info' } + ), }); // action type definition export function getActionType({ logger }: { logger: Logger }): ActionType { return { id: '.server-log', - name: 'server-log', + name: ACTION_NAME, validate: { params: ParamsSchema, }, @@ -48,8 +54,9 @@ async function executor( const actionId = execOptions.actionId; const params = execOptions.params as ActionParamsType; + const sanitizedMessage = withoutControlCharacters(params.message); try { - logger[params.level](params.message); + logger[params.level](`${ACTION_NAME}: ${sanitizedMessage}`); } catch (err) { const message = i18n.translate('xpack.actions.builtin.serverLog.errorLoggingErrorMessage', { defaultMessage: 'error logging message', diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts index d55b30b8e30ea..e8bc5d60a697b 100644 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ b/x-pack/legacy/plugins/actions/server/plugin.ts @@ -59,7 +59,7 @@ export class Plugin { private licenseState: LicenseState | null = null; constructor(initializerContext: ActionsPluginInitializerContext) { - this.logger = initializerContext.logger.get('plugins', 'alerting'); + this.logger = initializerContext.logger.get('plugins', 'actions'); this.config$ = initializerContext.config.create(); this.kibana$ = initializerContext.config.kibana$; }