diff --git a/README.md b/README.md index d7c1462..8d0793f 100644 --- a/README.md +++ b/README.md @@ -27,33 +27,33 @@ It allows you to outsource working with a url (mapping, parsing, stringifying) a ### Example ```js -import React from 'react'; -import ReactDOM from 'react-dom'; -import Mapper from 'url-mapper'; -import { CoreApp, ComponentA, ComponentB, Component404 } from './components'; +import React from 'react' +import ReactDOM from 'react-dom' +import Mapper from 'url-mapper' +import { CoreApp, ComponentA, ComponentB, Component404 } from './components' -const urlMapper = Mapper(); +const urlMapper = Mapper() var matchedRoute = urlMapper.map('/bar/baz/:42', { // routable part of url '/foo/:id': ComponentA, '/bar/:list/:itemId': ComponentB, '*': Component404 -}); +}) if (matchedRoute) { - const Component = matchedRoute.match; // ComponentB - const props = matchedRoute.values; // { list: 'baz', itemId: 42 } + const Component = matchedRoute.match // ComponentB + const props = matchedRoute.values // { list: 'baz', itemId: 42 } ReactDOM.render( - ); + ) } ``` -See [`cerebral-router`](https://github.com/cerebral/cerebral-router) as an example of building your own router solution on top of `url-mapper`. -Also see [example at Tonic Sandbox](https://tonicdev.com/npm/url-mapper) to try it right in your browser. +See [`@cerebral/router`](https://github.com/cerebral/cerebral/blob/next/packages/node_modules/%40cerebral/router) as an example of building your own router solution on top of `url-mapper`. +Also see [example at Runkit Sandbox](https://npm.runkit.com/url-mapper) to try it right in your browser. ## API @@ -65,8 +65,8 @@ At top level the `url-mapper` module exports a factory which returns default imp ##### Usage ```js -var urlMapper = require('url-mapper'); -var mapper = urlMapper(options); +var urlMapper = require('url-mapper') +var mapper = urlMapper(options) ``` ##### Arguments @@ -116,7 +116,7 @@ You still can manage your routes in `location.hash` but don't provide `#` symbol ##### Usage -`mapper.parse(route, url)`; +`mapper.parse(route, url)` ##### Arguments @@ -136,7 +136,7 @@ Query part parsed with `URLON` module if { query: true } option was passed to fa ##### Usage -`mapper.stringify(route, values)`; +`mapper.stringify(route, values)` ##### Arguments @@ -156,7 +156,7 @@ Properties not defined in route are stringified to query part using `URLON` modu ##### Usage -`mapper.map(url, routes)`; +`mapper.map(url, routes)` ##### Arguments @@ -184,8 +184,8 @@ If you don't like default route definition format or converting algorithms, feel ##### Usage ```js -var urlMapper = require('url-mapper/mapper'); -var mapper = urlMapper(compileFn, options); +var urlMapper = require('url-mapper/mapper') +var mapper = urlMapper(compileFn, options) ``` ##### Arguments diff --git a/compileRoute.js b/compileRoute.js index 6e0b474..f64afd9 100644 --- a/compileRoute.js +++ b/compileRoute.js @@ -2,6 +2,18 @@ var URLON = require('urlon') var pathToRegexp = require('path-to-regexp') +function getKeyName (key) { + return key.name.toString() +} + +// loose escaping for segment part +// see: https://github.com/pillarjs/path-to-regexp/pull/75 +function encodeSegment (str) { + return encodeURI(str).replace(/[/?#'"]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) +} + function compileRoute (route, options) { var re var compiled @@ -9,7 +21,7 @@ function compileRoute (route, options) { var querySeparator = options.querySeparator || '?' re = pathToRegexp(route, keys) - keys = keys.map(function (key) { return key.name.toString() }) + keys = keys.map(getKeyName) compiled = pathToRegexp.compile(route) return { @@ -23,7 +35,7 @@ function compileRoute (route, options) { if (~path.indexOf(querySeparator)) { if (options.query) { - var queryString = '_' + path.slice(path.indexOf(querySeparator) + querySeparator.length) + var queryString = '$' + path.slice(path.indexOf(querySeparator) + querySeparator.length) result = URLON.parse(queryString) } path = path.split(querySeparator)[0] @@ -35,11 +47,9 @@ function compileRoute (route, options) { for (var i = 1; i < match.length; ++i) { var key = keys[i - 1] var value = match[i] && decodeURIComponent(match[i]) - if (value && value[0] === ':') { - result[key] = URLON.parse(value) - } else { - result[key] = value - } + result[key] = (value && value[0] === ':') + ? URLON.parse(value) + : value } return result @@ -58,26 +68,31 @@ function compileRoute (route, options) { break case 'object': - throw new Error('URL Mapper - objects are not allowed to be stringified as part of path') + if (values[key]) { + throw new Error('URL Mapper - objects are not allowed to be stringified as part of path') + } else { // null + pathParams[key] = URLON.stringify(values[key]) + } + break default: pathParams[key] = values[key] } } else { - if (typeof values[key] !== 'undefined') queryParams[key] = values[key] + queryParams[key] = values[key] } }) - var path = compiled(pathParams) + var path = compiled(pathParams, { encode: encodeSegment }) var queryString = '' if (options.query) { if (Object.keys(queryParams).length) { - queryString = querySeparator + URLON.stringify(queryParams).slice(1) + queryString = URLON.stringify(queryParams).slice(1) } } - return path + queryString + return path + (queryString ? querySeparator + queryString : '') } } } diff --git a/example.js b/example.js index b6a5c7b..c17f38d 100644 --- a/example.js +++ b/example.js @@ -4,10 +4,12 @@ var object = { foo: 'bar', bar: true, baz: { - foo: true, + foo: false, bar: 2, - baz: ['foo', 'bar', 'baz'], - e: '' + baz: ['foo', 'bar', 'baz', true, false, undefined, null], + qux: '', + quux: null, + garply: undefined } } diff --git a/package.json b/package.json index 26f2114..486da61 100644 --- a/package.json +++ b/package.json @@ -28,21 +28,21 @@ "url": "https://github.com/cerebral/url-mapper/issues" }, "homepage": "https://github.com/cerebral/url-mapper#readme", - "tonicExampleFilename": "example.js", + "runkitExampleFilename": "example.js", "devDependencies": { "commitizen": "^2.5.0", - "coveralls": "^2.11.4", + "coveralls": "^3.0.0", "cz-customizable": "^2.7.0", "ghooks": "^1.0.3", "istanbul": "^0.4.0", - "nodeunit": "^0.9.1", + "nodeunit": "^0.11.2", "semantic-release": "^4.3.5", - "standard": "^6.0.7", + "standard": "^10.0.3", "validate-commit-msg": "^2.3.1" }, "dependencies": { - "urlon": "^2.0.0", - "path-to-regexp": "^1.2.1" + "urlon": "^3.0.1", + "path-to-regexp": "^2.1.0" }, "config": { "commitizen": { diff --git a/tests/test.js b/tests/test.js index a526184..52d8fbb 100644 --- a/tests/test.js +++ b/tests/test.js @@ -253,13 +253,28 @@ module.exports = { var object = { foo: true, bar: false, - baz: 42 + baz: 42, + qux: null } // URLON-like notation - var url = '/%3Atrue/%3Afalse/%3A42' + var url = '/:true/:false/:42/:null' - test.equal(mapper.stringify('/:foo/:bar/:baz', object), url) - test.deepEqual(mapper.parse('/:foo/:bar/:baz', url), object) + test.equal(mapper.stringify('/:foo/:bar/:baz/:qux', object), url) + test.deepEqual(mapper.parse('/:foo/:bar/:baz/:qux', url), object) + + test.done() + }, + + 'should properly escape unsafe symbols in segments': function (test) { + var mapper = urlMapper() + var object = { + foo: 'foo/?#\'"bar' + } + // URLON-like notation + var url = '/foo%2F%3F%23%27%22bar' + + test.equal(mapper.stringify('/:foo', object), url) + test.deepEqual(mapper.parse('/:foo', url), object) test.done() },