diff --git a/index.compiler.spec.js b/index.compiler.spec.js index e85b5045..f4f252b4 100644 --- a/index.compiler.spec.js +++ b/index.compiler.spec.js @@ -802,6 +802,8 @@ describe('links', () => { }); it('should sanitize links containing JS expressions', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + render(compiler('[foo](javascript:doSomethingBad)')); expect(root.innerHTML).toMatchInlineSnapshot(` @@ -811,9 +813,45 @@ describe('links', () => { `); + + expect(console.warn).toHaveBeenCalled(); + }); + + it('should sanitize links containing encoded JS expressions', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + + render(compiler('[foo](javascript%3AdoSomethingBad)')); + + expect(root.innerHTML).toMatchInlineSnapshot(` + + + foo + + +`); + + expect(console.warn).toHaveBeenCalled(); + }); + + it('should sanitize links containing padded JS expressions', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + + render(compiler('[foo]( javascript%3AdoSomethingBad)')); + + expect(root.innerHTML).toMatchInlineSnapshot(` + + + foo + + +`); + + expect(console.warn).toHaveBeenCalled(); }); it('should sanitize links containing invalid characters', () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + render(compiler('[foo](https://google.com/%AF)')); expect(root.innerHTML).toMatchInlineSnapshot(` @@ -823,6 +861,7 @@ describe('links', () => { `); + expect(console.warn).toHaveBeenCalled(); }); it('should handle a link with a URL in the text', () => { diff --git a/index.js b/index.js index ee7b4ac6..0bb196b4 100644 --- a/index.js +++ b/index.js @@ -604,14 +604,26 @@ function reactFor(outputFunc) { function sanitizeUrl(url) { try { - const prot = decodeURIComponent(url) - .replace(/[^A-Z0-9/:]/gi, '') - .toLowerCase(); + const decoded = decodeURIComponent(url); + + if (decoded.match(/^\s*javascript:/i)) { + if (process.env.NODE_ENV !== 'production') { + console.warn( + 'Anchor URL contains an unsafe JavaScript expression, it will not be rendered.', + decoded + ); + } - if (prot.indexOf('javascript:') === 0) { return null; } } catch (e) { + if (process.env.NODE_ENV !== 'production') { + console.warn( + 'Anchor URL could not be decoded due to malformed syntax or characters, it will not be rendered.', + url + ); + } + // decodeURIComponent sometimes throws a URIError // See `decodeURIComponent('a%AFc');` // http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception @@ -667,6 +679,7 @@ function parseCaptureInline(capture, parse, state) { function captureNothing() { return {}; } + function renderNothing() { return null; } @@ -677,11 +690,8 @@ function ruleOutput(rules) { }; } -function cx() { - return Array.prototype.slice - .call(arguments) - .filter(Boolean) - .join(' '); +function cx(...args) { + return args.filter(Boolean).join(' '); } function get(src, path, fb) {