diff --git a/backbone.js b/backbone.js index 5bfd974cd..822566a9c 100644 --- a/backbone.js +++ b/backbone.js @@ -1764,13 +1764,21 @@ // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be - // treated as `null` to normalize cross-browser behavior. + // treated as `null` to normalize cross-browser behavior. A malformed + // percent-encoding (e.g. `%foo`) would otherwise throw a `URIError` and + // crash the router (#3440); such parameters fall back to the raw value. _extractParameters: function(route, fragment) { var params = route.exec(fragment).slice(1); return _.map(params, function(param, i) { // Don't decode the search params. if (i === params.length - 1) return param || null; - return param ? decodeURIComponent(param) : null; + if (!param) return null; + try { + return decodeURIComponent(param); + } catch (e) { + if (e instanceof URIError) return param; + throw e; + } }); } diff --git a/test/router.js b/test/router.js index 9b5bdbdf2..91a790fcf 100644 --- a/test/router.js +++ b/test/router.js @@ -958,6 +958,19 @@ Backbone.history.start({pushState: true}); }); + QUnit.test('#3440 - Malformed param does not throw URIError.', function(assert) { + assert.expect(2); + var myRouter = new Backbone.Router; + var route = /^search\/([^\/]+)$/; + var params; + // Should not throw on malformed percent-encoding. + params = myRouter._extractParameters(route, 'search/malformed%query'); + assert.ok(params, 'extract did not throw'); + // The malformed value falls back to the raw, undecoded string so the + // route can still match and the application can handle it. + assert.strictEqual(params[0], 'malformed%query'); + }); + QUnit.test('Router#execute receives callback, args, name.', function(assert) { assert.expect(3); location.replace('http://example.com#foo/123/bar?x=y');