diff --git a/src/ui/ResultLink/ResultLink.ts b/src/ui/ResultLink/ResultLink.ts index ef683d5aa0..e5f038fe63 100644 --- a/src/ui/ResultLink/ResultLink.ts +++ b/src/ui/ResultLink/ResultLink.ts @@ -487,7 +487,8 @@ export class ResultLink extends Component { private getResultUri(): string { if (this.options.hrefTemplate) { - return StringUtils.buildStringTemplateFromResult(this.options.hrefTemplate, this.result); + const uri = StringUtils.buildStringTemplateFromResult(this.options.hrefTemplate, this.result); + return this.filterProtocol(uri); } if (this.options.field == undefined && this.options.openInOutlook) { diff --git a/unitTests/ui/ResultLinkTest.ts b/unitTests/ui/ResultLinkTest.ts index 4271f04fad..ea8f7ee369 100644 --- a/unitTests/ui/ResultLinkTest.ts +++ b/unitTests/ui/ResultLinkTest.ts @@ -126,49 +126,56 @@ export function ResultLinkTest() { }); describe('exposes hrefTemplate', () => { + it('when the href starts with a protocol that enables XSS, it returns an empty string', () => { + const hrefTemplate = 'javascript:alert(1)'; + test = Mock.optionsResultComponentSetup(ResultLink, { hrefTemplate: hrefTemplate }, fakeResult); + test.cmp.openLinkInNewWindow(); + expect(window.open).toHaveBeenCalledWith('', jasmine.anything()); + }); + it('should not modify the href template if there are no field specified', () => { - let hrefTemplate = 'test'; + let hrefTemplate = 'http://test'; test = Mock.optionsResultComponentSetup(ResultLink, { hrefTemplate: hrefTemplate }, fakeResult); test.cmp.openLinkInNewWindow(); expect(window.open).toHaveBeenCalledWith(hrefTemplate, jasmine.anything()); }); it('should replace fields in the href template by the results equivalent', () => { - let hrefTemplate = '${title}'; + let hrefTemplate = 'http://${title}'; test = Mock.optionsResultComponentSetup(ResultLink, { hrefTemplate: hrefTemplate }, fakeResult); test.cmp.openLinkInNewWindow(); - expect(window.open).toHaveBeenCalledWith(fakeResult.title, jasmine.anything()); + expect(window.open).toHaveBeenCalledWith(`http://${fakeResult.title}`, jasmine.anything()); }); it('should support nested values in result', () => { - let hrefTemplate = '${raw.number}'; + let hrefTemplate = 'http://${raw.number}'; test = Mock.optionsResultComponentSetup(ResultLink, { hrefTemplate: hrefTemplate }, fakeResult); test.cmp.openLinkInNewWindow(); - expect(window.open).toHaveBeenCalledWith(fakeResult.raw['number'].toString(), jasmine.anything()); + expect(window.open).toHaveBeenCalledWith(`http://${fakeResult.raw['number'].toString()}`, jasmine.anything()); }); it('should not parse standalone accolades', () => { - let hrefTemplate = '${raw.number}{test}'; + let hrefTemplate = 'http://${raw.number}{test}'; test = Mock.optionsResultComponentSetup(ResultLink, { hrefTemplate: hrefTemplate }, fakeResult); test.cmp.openLinkInNewWindow(); - expect(window.open).toHaveBeenCalledWith(fakeResult.raw['number'] + '{test}', jasmine.anything()); + expect(window.open).toHaveBeenCalledWith(`http://${fakeResult.raw['number']}{test}`, jasmine.anything()); }); it('should support external fields', () => { window['Coveo']['test'] = 'testExternal'; - let hrefTemplate = '${Coveo.test}'; + let hrefTemplate = 'http://${Coveo.test}'; test = Mock.optionsResultComponentSetup(ResultLink, { hrefTemplate: hrefTemplate }, fakeResult); test.cmp.openLinkInNewWindow(); - expect(window.open).toHaveBeenCalledWith('testExternal', jasmine.anything()); + expect(window.open).toHaveBeenCalledWith('http://testExternal', jasmine.anything()); window['Coveo']['test'] = undefined; }); it('should support nested external fields with more than 2 keys', () => { window['Coveo']['test'] = { key: 'testExternal' }; - let hrefTemplate = '${Coveo.test.key}'; + let hrefTemplate = 'http://${Coveo.test.key}'; test = Mock.optionsResultComponentSetup(ResultLink, { hrefTemplate: hrefTemplate }, fakeResult); test.cmp.openLinkInNewWindow(); - expect(window.open).toHaveBeenCalledWith('testExternal', jasmine.anything()); + expect(window.open).toHaveBeenCalledWith('http://testExternal', jasmine.anything()); window['Coveo']['test'] = undefined; }); });