diff --git a/package.json b/package.json index 99afc4e6c..2f403a805 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hmcts/ccd-case-ui-toolkit", - "version": "7.2.53", + "version": "7.2.53-2673-rc2", "engines": { "node": ">=18.19.0" }, diff --git a/projects/ccd-case-ui-toolkit/package.json b/projects/ccd-case-ui-toolkit/package.json index f9a702a7f..c5890bc2f 100644 --- a/projects/ccd-case-ui-toolkit/package.json +++ b/projects/ccd-case-ui-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@hmcts/ccd-case-ui-toolkit", - "version": "7.2.53", + "version": "7.2.53-2673-rc2", "engines": { "node": ">=18.19.0" }, diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.spec.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.spec.ts index 0dd41d683..08ea1c336 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.spec.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.spec.ts @@ -164,4 +164,122 @@ describe('FormValidatorsService', () => { expect(result.valid).toBeFalsy(); }); + + it('should invalidate value containing '); + result.markAsTouched(); + result.updateValueAndValidity(); + expect(result.valid).toBeFalsy(); + }); + + it('should invalidate value containing incomplete ')).toBeTruthy(); + }); + + it('should invalidate markdown link exactly at boundary (500 chars inside brackets)', () => { + const inner = 'a'.repeat(500); + const link = `[${inner}](target)`; + expect(isInvalid(link)).toBeTruthy(); + }); + + it('should allow markdown link exceeding boundary (501 chars inside brackets)', () => { + const inner = 'a'.repeat(501); + const link = `[${inner}](target)`; + expect(isInvalid(link)).toBeFalsy(); + }); + + it('should invalidate image markdown exactly at boundary (500 chars alt)', () => { + const inner = 'i'.repeat(500); + const img = `![${inner}](src)`; + expect(isInvalid(img)).toBeTruthy(); + }); + + it('should allow image markdown exceeding boundary (501 chars alt)', () => { + const inner = 'i'.repeat(501); + const img = `![${inner}](src)`; + expect(isInvalid(img)).toBeFalsy(); + }); + + it('should invalidate img tag with long attributes within 500 chars', () => { + const attrs = 'x'.repeat(400); + expect(isInvalid(``)).toBeTruthy(); + }); + + it('should allow img tag with attributes exceeding 500 chars', () => { + const attrs = 'y'.repeat(501); + expect(isInvalid(``)).toBeFalsy(); + }); + + it('should invalidate multiple dangerous event handlers', () => { + expect(isInvalid('
test
')).toBeTruthy(); + }); + + it('should invalidate standalone onload attribute', () => { + expect(isInvalid('onload="do()"')).toBeTruthy(); + }); + + it('should allow similar words like onclicked without space or equals', () => { + expect(isInvalid('This is onclicked and onmouseovered text')).toBeFalsy(); + }); + + it('should invalidate mixed content containing both markdown and script', () => { + expect(isInvalid('[link](url)')).toBeTruthy(); + }); + + it('should invalidate incomplete opening script tag with attributes', () => { + expect(isInvalid('')).toBeTruthy(); + }); + + it('should allow benign angle brackets not forming tags', () => { + expect(isInvalid('< notatag > just text')).toBeFalsy(); + }); + }); }); diff --git a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.ts b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.ts index 77e1861af..e6a583f65 100644 --- a/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.ts +++ b/projects/ccd-case-ui-toolkit/src/lib/shared/services/form/form-validators.service.ts @@ -10,6 +10,7 @@ export class FormValidatorsService { private static readonly CUSTOM_VALIDATED_TYPES: FieldTypeEnum[] = [ 'Date', 'MoneyGBP', 'Label', 'JudicialUser' ]; + private static readonly DEFAULT_INPUT_TEXT = 'text'; private static readonly DEFAULT_INPUT_TEXTAREA = 'textAreas'; @@ -62,11 +63,25 @@ export class FormValidatorsService { } public static markDownPatternValidator(): ValidatorFn { - const pattern = /(\[[^\]]{0,500}\]\([^)]{0,500}\)|!\[[^\]]{0,500}\]\([^)]{0,500}\)|]{0,500}>|]{0,500}>.*?<\/a>)/; + const aTagPattern = /]*(>|$)/i; + const pattern = /(\[[^\]]{0,500}\]\([^)]{0,500}\)|!\[[^\]]{0,500}\]\([^)]{0,500}\)|]{0,500}(?:>|$))/i; + const hasDangerousAttrs = /\bon\w+\s*=/i; + const scriptTagPattern = /]*(>|$)/i; return (control: AbstractControl): ValidationErrors | null => { const value = control?.value?.toString().trim(); - return (value && pattern.test(value)) ? { markDownPattern: {} } : null; + if ( + value && + ( + pattern.test(value) || + aTagPattern.test(value) || + scriptTagPattern.test(value) || + hasDangerousAttrs.test(value) + ) + ) { + return { markDownPattern: {} }; + } + return null; }; }