@quatrain/messaging 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,9 +4,11 @@
4
4
  export declare class MessageFormatter {
5
5
  /**
6
6
  * Cleans all HTML tags from the title string.
7
+ * This uses a robust, RegExp-free state loop to prevent any risk of Regular Expression
8
+ * Denial of Service (ReDoS) or catastrophic backtracking, fully satisfying SonarQube security rules.
7
9
  *
8
- * @param title - The raw subject line.
9
- * @returns The formatted title string.
10
+ * @param title - The raw subject line containing potential HTML tags.
11
+ * @returns The formatted title string with all HTML tags stripped out.
10
12
  */
11
13
  static formatTitle(title: string): string;
12
14
  /**
@@ -11,12 +11,28 @@ const mustache_1 = __importDefault(require("mustache"));
11
11
  class MessageFormatter {
12
12
  /**
13
13
  * Cleans all HTML tags from the title string.
14
+ * This uses a robust, RegExp-free state loop to prevent any risk of Regular Expression
15
+ * Denial of Service (ReDoS) or catastrophic backtracking, fully satisfying SonarQube security rules.
14
16
  *
15
- * @param title - The raw subject line.
16
- * @returns The formatted title string.
17
+ * @param title - The raw subject line containing potential HTML tags.
18
+ * @returns The formatted title string with all HTML tags stripped out.
17
19
  */
18
20
  static formatTitle(title) {
19
- return title.replace(/<[^>]*>/gi, '');
21
+ let result = '';
22
+ let inTag = false;
23
+ for (let i = 0; i < title.length; i++) {
24
+ const char = title[i];
25
+ if (char === '<') {
26
+ inTag = true;
27
+ }
28
+ else if (char === '>') {
29
+ inTag = false;
30
+ }
31
+ else if (!inTag) {
32
+ result += char;
33
+ }
34
+ }
35
+ return result;
20
36
  }
21
37
  /**
22
38
  * Renders the Mustache layout with provided contextual variables.
@@ -26,7 +42,7 @@ class MessageFormatter {
26
42
  * @returns Parsed output string.
27
43
  */
28
44
  static formatBody(body, data) {
29
- return mustache_1.default.render(body, data);
45
+ return mustache_1.default.render(body, data || {});
30
46
  }
31
47
  }
32
48
  exports.MessageFormatter = MessageFormatter;
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const Messaging_1 = require("./Messaging");
13
13
  const MockMessagingAdapter_1 = require("./MockMessagingAdapter");
14
+ const MessageFormatter_1 = require("./MessageFormatter");
14
15
  describe('Messaging', () => {
15
16
  let mockAdapter1;
16
17
  let mockAdapter2;
@@ -255,4 +256,39 @@ describe('Messaging', () => {
255
256
  expect(Messaging_1.Messaging.logger).toBeDefined();
256
257
  });
257
258
  });
259
+ describe('MessageFormatter', () => {
260
+ describe('formatTitle', () => {
261
+ it('should strip single HTML tags correctly', () => {
262
+ const raw = 'Hello <b>World</b>!';
263
+ const result = MessageFormatter_1.MessageFormatter.formatTitle(raw);
264
+ expect(result).toBe('Hello World!');
265
+ });
266
+ it('should strip multiple nested and unclosed tags correctly', () => {
267
+ const raw = '<div><p>Welcome to <span>Quatrain</span></p></div>';
268
+ const result = MessageFormatter_1.MessageFormatter.formatTitle(raw);
269
+ expect(result).toBe('Welcome to Quatrain');
270
+ });
271
+ it('should return plain text unchanged', () => {
272
+ const raw = 'Plain text without any tags.';
273
+ const result = MessageFormatter_1.MessageFormatter.formatTitle(raw);
274
+ expect(result).toBe(raw);
275
+ });
276
+ it('should handle empty string correctly', () => {
277
+ expect(MessageFormatter_1.MessageFormatter.formatTitle('')).toBe('');
278
+ });
279
+ });
280
+ describe('formatBody', () => {
281
+ it('should interpolate Mustache variables correctly', () => {
282
+ const template = 'Hello {{firstname}} {{lastname}}!';
283
+ const data = { firstname: 'John', lastname: 'Doe' };
284
+ const result = MessageFormatter_1.MessageFormatter.formatBody(template, data);
285
+ expect(result).toBe('Hello John Doe!');
286
+ });
287
+ it('should render original template when no data is provided', () => {
288
+ const template = 'Hello {{firstname}}!';
289
+ const result = MessageFormatter_1.MessageFormatter.formatBody(template);
290
+ expect(result).toBe('Hello !');
291
+ });
292
+ });
293
+ });
258
294
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quatrain/messaging",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "license": "AGPL-3.0-only",
5
5
  "description": "Messaging adapters commons",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "author": "Quatrain Développement SAS <developers@quatrain.com>",
22
22
  "dependencies": {
23
- "@quatrain/core": "^1.2.6",
23
+ "@quatrain/core": "^1.2.11",
24
24
  "mustache": "^4.2.0"
25
25
  },
26
26
  "devDependencies": {
@@ -6,12 +6,26 @@ import Mustache from 'mustache'
6
6
  export class MessageFormatter {
7
7
  /**
8
8
  * Cleans all HTML tags from the title string.
9
+ * This uses a robust, RegExp-free state loop to prevent any risk of Regular Expression
10
+ * Denial of Service (ReDoS) or catastrophic backtracking, fully satisfying SonarQube security rules.
9
11
  *
10
- * @param title - The raw subject line.
11
- * @returns The formatted title string.
12
+ * @param title - The raw subject line containing potential HTML tags.
13
+ * @returns The formatted title string with all HTML tags stripped out.
12
14
  */
13
- static formatTitle(title: string) {
14
- return title.replace(/<[^>]*>/gi, '')
15
+ static formatTitle(title: string): string {
16
+ let result = ''
17
+ let inTag = false
18
+ for (let i = 0; i < title.length; i++) {
19
+ const char = title[i]
20
+ if (char === '<') {
21
+ inTag = true
22
+ } else if (char === '>') {
23
+ inTag = false
24
+ } else if (!inTag) {
25
+ result += char
26
+ }
27
+ }
28
+ return result
15
29
  }
16
30
 
17
31
  /**
@@ -22,6 +36,6 @@ export class MessageFormatter {
22
36
  * @returns Parsed output string.
23
37
  */
24
38
  static formatBody(body: string, data?: {}) {
25
- return Mustache.render(body, data)
39
+ return Mustache.render(body, data || {})
26
40
  }
27
41
  }
@@ -2,6 +2,7 @@ import { Messaging, MessagingParameters } from './Messaging'
2
2
  import { MockMessagingAdapter } from './MockMessagingAdapter'
3
3
  import { MessagingRecipient } from './types/MessagingRecipient'
4
4
  import { NotificationMessage } from './types/NotificationMessage'
5
+ import { MessageFormatter } from './MessageFormatter'
5
6
 
6
7
  describe('Messaging', () => {
7
8
  let mockAdapter1: MockMessagingAdapter
@@ -315,4 +316,45 @@ describe('Messaging', () => {
315
316
  expect(Messaging.logger).toBeDefined()
316
317
  })
317
318
  })
319
+
320
+ describe('MessageFormatter', () => {
321
+ describe('formatTitle', () => {
322
+ it('should strip single HTML tags correctly', () => {
323
+ const raw = 'Hello <b>World</b>!'
324
+ const result = MessageFormatter.formatTitle(raw)
325
+ expect(result).toBe('Hello World!')
326
+ })
327
+
328
+ it('should strip multiple nested and unclosed tags correctly', () => {
329
+ const raw = '<div><p>Welcome to <span>Quatrain</span></p></div>'
330
+ const result = MessageFormatter.formatTitle(raw)
331
+ expect(result).toBe('Welcome to Quatrain')
332
+ })
333
+
334
+ it('should return plain text unchanged', () => {
335
+ const raw = 'Plain text without any tags.'
336
+ const result = MessageFormatter.formatTitle(raw)
337
+ expect(result).toBe(raw)
338
+ })
339
+
340
+ it('should handle empty string correctly', () => {
341
+ expect(MessageFormatter.formatTitle('')).toBe('')
342
+ })
343
+ })
344
+
345
+ describe('formatBody', () => {
346
+ it('should interpolate Mustache variables correctly', () => {
347
+ const template = 'Hello {{firstname}} {{lastname}}!'
348
+ const data = { firstname: 'John', lastname: 'Doe' }
349
+ const result = MessageFormatter.formatBody(template, data)
350
+ expect(result).toBe('Hello John Doe!')
351
+ })
352
+
353
+ it('should render original template when no data is provided', () => {
354
+ const template = 'Hello {{firstname}}!'
355
+ const result = MessageFormatter.formatBody(template)
356
+ expect(result).toBe('Hello !')
357
+ })
358
+ })
359
+ })
318
360
  })