Socket
Socket
Sign inDemoInstall

find-my-way

Package Overview
Dependencies
Maintainers
2
Versions
112
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

find-my-way - npm Package Compare versions

Comparing version 5.6.0 to 6.0.0

test/custom-querystring-parser.test.js

146

handler_storage.js
'use strict'
const assert = require('assert')
const deepEqual = require('fast-deep-equal')

@@ -8,38 +7,48 @@

constructor () {
this.unconstrainedHandler = null // optimized reference to the handler that will match most of the time
this.constraints = []
this.handlers = [] // unoptimized list of handler objects for which the fast matcher function will be compiled
this.hasConstraints = false
this.compiledHandler = null
this.unconstrainedHandler = null // optimized reference to the handler that will match most of the time
this.constrainedHandlerStores = null
}
getHandler (constraints) {
return this.handlers.filter(handler => deepEqual(constraints, handler.constraints))[0]
hasHandler (constraints) {
return this.handlers.find(handler => deepEqual(constraints, handler.constraints)) !== undefined
}
// This is the hot path for node handler finding -- change with care!
getMatchingHandler (constrainer, derivedConstraints) {
getMatchingHandler (derivedConstraints) {
if (derivedConstraints === undefined) {
return this.unconstrainedHandler
}
return this._getHandlerMatchingConstraints(derivedConstraints)
}
if (this.hasConstraints) {
// This node is constrained, use the performant precompiled constraint matcher
return this._getHandlerMatchingConstraints(constrainer, derivedConstraints)
addHandler (handler, params, store, constrainer, constraints) {
const handlerObject = {
handler,
params,
constraints,
store: store || null,
_createParamsObject: this._compileCreateParamsObject(params)
}
// This node doesn't have any handlers that are constrained, so it's handlers probably match. Some requests have constraint values that *must* match however, like version, so check for those before returning it.
if (!derivedConstraints.__hasMustMatchValues) {
return this.unconstrainedHandler
if (Object.keys(constraints).length === 0) {
this.unconstrainedHandler = handlerObject
}
return null
}
for (const constraint of Object.keys(constraints)) {
if (!this.constraints.includes(constraint)) {
if (constraint === 'version') {
// always check the version constraint first as it is the most selective
this.constraints.unshift(constraint)
} else {
this.constraints.push(constraint)
}
}
}
addHandler (handler, params, store, constraints) {
if (!handler) return
assert(!this.getHandler(constraints), `There is already a handler with constraints '${JSON.stringify(constraints)}' and method '${this.method}'`)
if (this.handlers.length >= 32) {
throw new Error('find-my-way supports a maximum of 32 route handlers per node when there are constraints, limit reached')
}
const handlerObject = { handler, params, constraints, store: store || null, paramsLength: params.length }
this.handlers.push(handlerObject)

@@ -49,23 +58,15 @@ // Sort the most constrained handlers to the front of the list of handlers so they are tested first.

if (Object.keys(constraints).length > 0) {
this.hasConstraints = true
} else {
this.unconstrainedHandler = handlerObject
}
this._compileGetHandlerMatchingConstraints(constrainer, constraints)
}
if (this.hasConstraints && this.handlers.length > 32) {
throw new Error('find-my-way supports a maximum of 32 route handlers per node when there are constraints, limit reached')
_compileCreateParamsObject (params) {
const lines = []
for (let i = 0; i < params.length; i++) {
lines.push(`'${params[i]}': paramsArray[${i}]`)
}
// Note that the fancy constraint handler matcher needs to be recompiled now that the list of handlers has changed
// This lazy compilation means we don't do the compile until the first time the route match is tried, which doesn't waste time re-compiling every time a new handler is added
this.compiledHandler = null
return new Function('paramsArray', `return {${lines.join(',')}}`) // eslint-disable-line
}
// Slot for the compiled constraint matching function
_getHandlerMatchingConstraints (constrainer, derivedConstraints) {
if (this.compiledHandler === null) {
this.compiledHandler = this._compileGetHandlerMatchingConstraints(constrainer)
}
return this.compiledHandler(derivedConstraints)
_getHandlerMatchingConstraints () {
return null
}

@@ -76,19 +77,12 @@

// The store's implementation comes from the strategies provided to the Router.
_buildConstraintStore (constrainer, constraint) {
const store = constrainer.newStoreForConstraint(constraint)
_buildConstraintStore (store, constraint) {
for (let i = 0; i < this.handlers.length; i++) {
const handler = this.handlers[i]
const mustMatchValue = handler.constraints[constraint]
if (typeof mustMatchValue !== 'undefined') {
let indexes = store.get(mustMatchValue)
if (!indexes) {
indexes = 0
}
const constraintValue = handler.constraints[constraint]
if (constraintValue !== undefined) {
let indexes = store.get(constraintValue) || 0
indexes |= 1 << i // set the i-th bit for the mask because this handler is constrained by this value https://stackoverflow.com/questions/1436438/how-do-you-set-clear-and-toggle-a-single-bit-in-javascrip
store.set(mustMatchValue, indexes)
store.set(constraintValue, indexes)
}
}
return store
}

@@ -98,6 +92,7 @@

_constrainedIndexBitmask (constraint) {
let mask = 0b0
let mask = 0
for (let i = 0; i < this.handlers.length; i++) {
const handler = this.handlers[i]
if (handler.constraints && constraint in handler.constraints) {
const constraintValue = handler.constraints[constraint]
if (constraintValue !== undefined) {
mask |= 1 << i

@@ -117,23 +112,16 @@ }

this.constrainedHandlerStores = {}
let constraints = new Set()
for (const handler of this.handlers) {
for (const key of Object.keys(handler.constraints)) {
constraints.add(key)
}
}
constraints = Array.from(constraints)
const lines = []
// always check the version constraint first as it is the most selective
constraints.sort((a, b) => a === 'version' ? 1 : 0)
for (const constraint of this.constraints) {
const store = constrainer.newStoreForConstraint(constraint)
this.constrainedHandlerStores[constraint] = store
for (const constraint of constraints) {
this.constrainedHandlerStores[constraint] = this._buildConstraintStore(constrainer, constraint)
this._buildConstraintStore(store, constraint)
}
const lines = []
lines.push(`
let candidates = 0b${'1'.repeat(this.handlers.length)}
let candidates = ${(1 << this.handlers.length) - 1}
let mask, matches
`)
for (const constraint of constraints) {
for (const constraint of this.constraints) {
// Setup the mask for indexes this constraint applies to. The mask bits are set to 1 for each position if the constraint applies.

@@ -147,8 +135,11 @@ lines.push(`

// If there is a constraint value, get the matching indexes bitmap from the store, and mask it down to only the indexes this constraint applies to, and then bitwise and with the candidates list to leave only matching candidates left.
const strategy = constrainer.strategies[constraint]
const matchMask = strategy.mustMatchWhenDerived ? 'matches' : '(matches | mask)'
lines.push(`
if (typeof value === "undefined") {
if (value === undefined) {
candidates &= mask
} else {
matches = this.constrainedHandlerStores.${constraint}.get(value) || 0
candidates &= (matches | mask)
candidates &= ${matchMask}
}

@@ -158,12 +149,17 @@ if (candidates === 0) return null;

}
// Return the first handler who's bit is set in the candidates https://stackoverflow.com/questions/18134985/how-to-find-index-of-first-set-bit
lines.push(`
const handler = this.handlers[Math.floor(Math.log2(candidates))]
if (handler && derivedConstraints.__hasMustMatchValues && handler === this.unconstrainedHandler) {
return null;
// There are some constraints that can be derived and marked as "must match", where if they are derived, they only match routes that actually have a constraint on the value, like the SemVer version constraint.
// An example: a request comes in for version 1.x, and this node has a handler that matches the path, but there's no version constraint. For SemVer, the find-my-way semantics do not match this handler to that request.
// This function is used by Nodes with handlers to match when they don't have any constrained routes to exclude request that do have must match derived constraints present.
for (const constraint in constrainer.strategies) {
const strategy = constrainer.strategies[constraint]
if (strategy.mustMatchWhenDerived && !this.constraints.includes(constraint)) {
lines.push(`if (derivedConstraints.${constraint} !== undefined) return null`)
}
}
return handler;
`)
return new Function('derivedConstraints', lines.join('\n')) // eslint-disable-line
// Return the first handler who's bit is set in the candidates https://stackoverflow.com/questions/18134985/how-to-find-index-of-first-set-bit
lines.push('return this.handlers[Math.floor(Math.log2(candidates))]')
this._getHandlerMatchingConstraints = new Function('derivedConstraints', lines.join('\n')) // eslint-disable-line
}

@@ -170,0 +166,0 @@ }

@@ -57,4 +57,5 @@ import { IncomingMessage, ServerResponse } from 'http';

params: { [k: string]: string | undefined },
store: any
) => void;
store: any,
searchParams: { [k: string]: string }
) => any;

@@ -114,2 +115,3 @@ interface ConstraintStrategy<V extends HTTPVersion, T = string> {

store: any;
searchParams: { [k: string]: string };
}

@@ -142,3 +144,2 @@

): void;
off(

@@ -154,3 +155,3 @@ method: HTTPMethod | HTTPMethod[],

ctx?: Context
): void;
): any;

@@ -167,2 +168,5 @@ find(

hasConstraintStrategy(strategyName: string): boolean;
addConstraintStrategy(constraintStrategy: ConstraintStrategy<V>): void;
all: ShortHandRoute<V>;

@@ -169,0 +173,0 @@

@@ -30,2 +30,3 @@ 'use strict'

const http = require('http')
const querystring = require('querystring')
const isRegexSafe = require('safe-regex2')

@@ -36,3 +37,3 @@ const deepEqual = require('fast-deep-equal')

const Constrainer = require('./lib/constrainer')
const sanitizeUrl = require('./lib/url-sanitizer')
const { safeDecodeURI, safeDecodeURIComponent } = require('./lib/url-sanitizer')

@@ -78,5 +79,7 @@ const httpMethods = http.METHODS

if (opts.decodeUriParameters) {
assert(typeof opts.decodeUriParameters === 'function', 'decodeUriParameters must be a function')
this.decodeUriParameters = opts.decodeUriParameters
if (opts.querystringParser) {
assert(typeof opts.querystringParser === 'function', 'querystringParser must be a function')
this.querystringParser = opts.querystringParser
} else {
this.querystringParser = (query) => query === '' ? {} : querystring.parse(query)
}

@@ -173,2 +176,8 @@

if (path.charCodeAt(i) === 37) {
// We need to encode % char to prevent double decoding
path = path.slice(0, i + 1) + '25' + path.slice(i + 1)
continue
}
const isParametricNode = path.charCodeAt(i) === 58

@@ -260,6 +269,15 @@ const isWildcardNode = path.charCodeAt(i) === 42

assert(!currentNode.handlerStorage.getHandler(constraints), `Method '${method}' already declared for route '${path}' with constraints '${JSON.stringify(constraints)}'`)
currentNode.handlerStorage.addHandler(handler, params, store, constraints)
assert(!currentNode.handlerStorage.hasHandler(constraints), `Method '${method}' already declared for route '${path}' with constraints '${JSON.stringify(constraints)}'`)
currentNode.handlerStorage.addHandler(handler, params, store, this.constrainer, constraints)
}
Router.prototype.hasConstraintStrategy = function (strategyName) {
return this.constrainer.hasConstraintStrategy(strategyName)
}
Router.prototype.addConstraintStrategy = function (constraints) {
this.constrainer.addConstraintStrategy(constraints)
this._rebuild(this.routes)
}
Router.prototype.reset = function reset () {

@@ -312,9 +330,3 @@ this.trees = {}

const newRoutes = this.routes.filter((route) => method !== route.method || path !== route.path || !matcher(route.opts.constraints))
this.reset()
for (const route of newRoutes) {
const { method, path, opts, handler, store } = route
this._on(method, path, opts, handler, store)
this.routes.push({ method, path, opts, handler, store })
}
this._rebuild(newRoutes)
}

@@ -326,4 +338,4 @@

return ctx === undefined
? handle.handler(req, res, handle.params, handle.store)
: handle.handler.call(ctx, req, res, handle.params, handle.store)
? handle.handler(req, res, handle.params, handle.store, handle.searchParams)
: handle.handler.call(ctx, req, res, handle.params, handle.store, handle.searchParams)
}

@@ -340,5 +352,8 @@

let sanitizedUrl
let querystring
try {
sanitizedUrl = sanitizeUrl(path, this.decodeUriParameters)
sanitizedUrl = safeDecodeURI(path)
path = sanitizedUrl.path
querystring = sanitizedUrl.querystring
} catch (error) {

@@ -352,2 +367,4 @@ return this._onBadUrl(path)

const originPath = path
if (this.caseSensitive === false) {

@@ -367,18 +384,10 @@ path = path.toLowerCase()

if (pathIndex === pathLen) {
const handle = currentNode.handlerStorage.getMatchingHandler(this.constrainer, derivedConstraints)
const handle = currentNode.handlerStorage.getMatchingHandler(derivedConstraints)
if (handle !== null && handle !== undefined) {
const paramsObj = {}
if (handle.paramsLength > 0) {
const paramNames = handle.params
for (let i = 0; i < handle.paramsLength; i++) {
paramsObj[paramNames[i]] = params[i]
}
}
if (handle !== null) {
return {
handler: handle.handler,
params: paramsObj,
store: handle.store
store: handle.store,
params: handle._createParamsObject(params),
searchParams: this.querystringParser(querystring)
}

@@ -451,12 +460,14 @@ }

if (currentNode.kind === NODE_TYPES.WILDCARD) {
const decoded = sanitizedUrl.sliceParameter(pathIndex)
if (decoded === null) {
return this._onBadUrl(path.slice(pathIndex))
let param = originPath.slice(pathIndex)
const firstPercentIndex = param.indexOf('%')
if (firstPercentIndex !== -1) {
param = safeDecodeURIComponent(param, firstPercentIndex)
}
if (decoded.length > maxParamLength) {
if (param.length > maxParamLength) {
return null
}
params.push(decoded)
params.push(param)
pathIndex = pathLen

@@ -468,31 +479,33 @@ continue

let paramEndIndex = pathIndex
let firstPercentIndex = -1
for (; paramEndIndex < pathLen; paramEndIndex++) {
if (path.charCodeAt(paramEndIndex) === 47) {
const charCode = path.charCodeAt(paramEndIndex)
if (charCode === 47) {
break
} else if (firstPercentIndex === -1 && charCode === 37) {
firstPercentIndex = paramEndIndex - pathIndex
}
}
const decoded = sanitizedUrl.sliceParameter(pathIndex, paramEndIndex)
if (decoded === null) {
return this._onBadUrl(path.slice(pathIndex, paramEndIndex))
let param = originPath.slice(pathIndex, paramEndIndex)
if (firstPercentIndex !== -1) {
param = safeDecodeURIComponent(param, firstPercentIndex)
}
if (currentNode.isRegex) {
const matchedParameters = currentNode.regex.exec(decoded)
if (matchedParameters === null) {
return null
}
const matchedParameters = currentNode.regex.exec(param)
if (matchedParameters === null) continue
for (let i = 1; i < matchedParameters.length; i++) {
const param = matchedParameters[i]
if (param.length > maxParamLength) {
const matchedParam = matchedParameters[i]
if (matchedParam.length > maxParamLength) {
return null
}
params.push(param)
params.push(matchedParam)
}
} else {
if (decoded.length > maxParamLength) {
if (param.length > maxParamLength) {
return null
}
params.push(decoded)
params.push(param)
}

@@ -505,2 +518,12 @@

Router.prototype._rebuild = function (routes) {
this.reset()
for (const route of routes) {
const { method, path, opts, handler, store } = route
this._on(method, path, opts, handler, store)
this.routes.push({ method, path, opts, handler, store })
}
}
Router.prototype._defaultRoute = function (req, res, ctx) {

@@ -507,0 +530,0 @@ if (this.defaultRoute !== null) {

@@ -18,15 +18,4 @@ 'use strict'

if (customStrategies) {
var kCustomStrategies = Object.keys(customStrategies)
var strategy
for (var i = 0; i < kCustomStrategies.length; i++) {
strategy = customStrategies[kCustomStrategies[i]]
assert(typeof strategy.name === 'string' && strategy.name !== '', 'strategy.name is required.')
assert(strategy.storage && typeof strategy.storage === 'function', 'strategy.storage function is required.')
assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', 'strategy.deriveConstraint function is required.')
strategy.isCustom = true
this.strategies[strategy.name] = strategy
if (strategy.mustMatchWhenDerived) {
this.noteUsage({ [kCustomStrategies[i]]: strategy })
}
for (const strategy of Object.values(customStrategies)) {
this.addConstraintStrategy(strategy)
}

@@ -36,2 +25,31 @@ }

hasConstraintStrategy (strategyName) {
const customConstraintStrategy = this.strategies[strategyName]
if (customConstraintStrategy !== undefined) {
return customConstraintStrategy.isCustom || this.strategiesInUse.has(strategyName)
}
return false
}
addConstraintStrategy (strategy) {
assert(typeof strategy.name === 'string' && strategy.name !== '', 'strategy.name is required.')
assert(strategy.storage && typeof strategy.storage === 'function', 'strategy.storage function is required.')
assert(strategy.deriveConstraint && typeof strategy.deriveConstraint === 'function', 'strategy.deriveConstraint function is required.')
if (this.strategies[strategy.name] && this.strategies[strategy.name].isCustom) {
throw new Error(`There already exists a custom constraint with the name ${strategy.name}.`)
}
if (this.strategiesInUse.has(strategy.name)) {
throw new Error(`There already exists a route with ${strategy.name} constraint.`)
}
strategy.isCustom = true
this.strategies[strategy.name] = strategy
if (strategy.mustMatchWhenDerived) {
this.noteUsage({ [strategy.name]: strategy })
}
}
deriveConstraints (req, ctx) {

@@ -83,9 +101,4 @@ return undefined

const lines = [`
const derivedConstraints = {
__hasMustMatchValues: false,
`]
const lines = ['return {']
const mustMatchKeys = []
for (const key of this.strategiesInUse) {

@@ -105,6 +118,2 @@ const strategy = this.strategies[key]

}
if (strategy.mustMatchWhenDerived) {
mustMatchKeys.push(key)
}
}

@@ -114,10 +123,2 @@

// There are some constraints that can be derived and marked as "must match", where if they are derived, they only match routes that actually have a constraint on the value, like the SemVer version constraint.
// An example: a request comes in for version 1.x, and this node has a handler that matches the path, but there's no version constraint. For SemVer, the find-my-way semantics do not match this handler to that request.
// This function is used by Nodes with handlers to match when they don't have any constrained routes to exclude request that do have must match derived constraints present.
if (mustMatchKeys.length > 0) {
lines.push(`derivedConstraints.__hasMustMatchValues = !!(${(mustMatchKeys.map(key => `derivedConstraints.${key}`).join(' || '))})`)
}
lines.push('return derivedConstraints')
this.deriveConstraints = new Function('req', 'ctx', lines.join('\n')).bind(this) // eslint-disable-line

@@ -124,0 +125,0 @@ }

@@ -252,3 +252,3 @@ 'use strict'

function flattenNode (flattened, node, method) {
if (node.handlerStorage.handlers.length > 0) {
if (node.handlerStorage.handlers.length !== 0) {
flattened.nodes.push({ method, node })

@@ -255,0 +255,0 @@ }

'use strict'
const fastDecode = require('fast-decode-uri-component')
// It must spot all the chars where decodeURIComponent(x) !== decodeURI(x)
// The chars are: # $ & + , / : ; = ? @
const uriComponentsCharMap = new Array(53).fill(0x00)
uriComponentsCharMap[50] = new Array(103).fill(0x00)
uriComponentsCharMap[50][51] = true // # '%23'
uriComponentsCharMap[50][52] = true // $ '%24'
uriComponentsCharMap[50][54] = true // & '%26'
uriComponentsCharMap[50][66] = true // + '%2B'
uriComponentsCharMap[50][98] = true // + '%2b'
uriComponentsCharMap[50][67] = true // , '%2C'
uriComponentsCharMap[50][99] = true // , '%2c'
uriComponentsCharMap[50][70] = true // / '%2F'
uriComponentsCharMap[50][102] = true // / '%2f'
function decodeComponentChar (highCharCode, lowCharCode) {
if (highCharCode === 50) {
if (lowCharCode === 53) return '%'
uriComponentsCharMap[51] = new Array(103).fill(0x00)
uriComponentsCharMap[51][65] = true // : '%3A'
uriComponentsCharMap[51][97] = true // : '%3a'
uriComponentsCharMap[51][66] = true // ; '%3B'
uriComponentsCharMap[51][98] = true // ; '%3b'
uriComponentsCharMap[51][68] = true // = '%3D'
uriComponentsCharMap[51][100] = true // = '%3d'
uriComponentsCharMap[51][70] = true // ? '%3F'
uriComponentsCharMap[51][102] = true // ? '%3f'
if (lowCharCode === 51) return '#'
if (lowCharCode === 52) return '$'
if (lowCharCode === 54) return '&'
if (lowCharCode === 66) return '+'
if (lowCharCode === 98) return '+'
if (lowCharCode === 67) return ','
if (lowCharCode === 99) return ','
if (lowCharCode === 70) return '/'
if (lowCharCode === 102) return '/'
return null
}
if (highCharCode === 51) {
if (lowCharCode === 65) return ':'
if (lowCharCode === 97) return ':'
if (lowCharCode === 66) return ';'
if (lowCharCode === 98) return ';'
if (lowCharCode === 68) return '='
if (lowCharCode === 100) return '='
if (lowCharCode === 70) return '?'
if (lowCharCode === 102) return '?'
return null
}
if (highCharCode === 52 && lowCharCode === 48) {
return '@'
}
return null
}
uriComponentsCharMap[52] = new Array(49).fill(0x00)
uriComponentsCharMap[52][48] = true // @ '%40'
function sanitizeUrl (url, customDecoder) {
let originPath = url
function safeDecodeURI (path) {
let shouldDecode = false
let containsEncodedComponents = false
let highChar
let lowChar
for (var i = 0, len = url.length; i < len; i++) {
var charCode = url.charCodeAt(i)
let querystring = ''
if (shouldDecode && !containsEncodedComponents) {
if (highChar === 0 && uriComponentsCharMap[charCode]) {
highChar = charCode
lowChar = 0x00
} else if (highChar && lowChar === 0 && uriComponentsCharMap[highChar][charCode]) {
containsEncodedComponents = true
for (let i = 1; i < path.length; i++) {
const charCode = path.charCodeAt(i)
if (charCode === 37) {
const highCharCode = path.charCodeAt(i + 1)
const lowCharCode = path.charCodeAt(i + 2)
if (decodeComponentChar(highCharCode, lowCharCode) === null) {
shouldDecode = true
} else {
highChar = undefined
lowChar = undefined
// %25 - encoded % char. We need to encode one more time to prevent double decoding
if (highCharCode === 50 && lowCharCode === 53) {
shouldDecode = true
path = path.slice(0, i + 1) + '25' + path.slice(i + 1)
i += 2
}
i += 2
}
}
// Some systems do not follow RFC and separate the path and query
// string with a `;` character (code 59), e.g. `/foo;jsessionid=123456`.
// Thus, we need to split on `;` as well as `?` and `#`.
if (charCode === 63 || charCode === 59 || charCode === 35) {
originPath = url.slice(0, i)
} else if (charCode === 63 || charCode === 59 || charCode === 35) {
querystring = path.slice(i + 1)
path = path.slice(0, i)
break
} else if (charCode === 37) {
shouldDecode = true
highChar = 0x00
}
}
const decoded = shouldDecode ? decodeURI(originPath) : originPath
const decodedPath = shouldDecode ? decodeURI(path) : path
return { path: decodedPath, querystring }
}
return {
path: decoded,
originPath,
sliceParameter: containsEncodedComponents
? sliceAndDecode
: slicePath
}
function safeDecodeURIComponent (uriComponent, startIndex) {
let decoded = ''
let lastIndex = startIndex
function sliceAndDecode (from, to) {
return (customDecoder || fastDecode)(this.originPath.slice(from, to))
for (let i = startIndex; i < uriComponent.length; i++) {
if (uriComponent.charCodeAt(i) === 37) {
const highCharCode = uriComponent.charCodeAt(i + 1)
const lowCharCode = uriComponent.charCodeAt(i + 2)
const decodedChar = decodeComponentChar(highCharCode, lowCharCode)
decoded += uriComponent.slice(lastIndex, i) + decodedChar
lastIndex = i + 3
}
}
return uriComponent.slice(0, startIndex) + decoded + uriComponent.slice(lastIndex)
}
module.exports = sanitizeUrl
function slicePath (from, to) {
return this.path.slice(from, to)
}
module.exports = { safeDecodeURI, safeDecodeURIComponent }
{
"name": "find-my-way",
"version": "5.6.0",
"version": "6.0.0",
"description": "Crazy fast http radix based router",

@@ -50,3 +50,2 @@ "main": "index.js",

"dependencies": {
"fast-decode-uri-component": "^1.0.1",
"fast-deep-equal": "^3.1.3",

@@ -53,0 +52,0 @@ "safe-regex2": "^2.0.0"

@@ -99,2 +99,17 @@ # find-my-way

The default query string parser that find-my-way uses is the Node.js's core querystring module. You can change this default setting by passing the option querystringParser and use a custom one, such as [qs](https://www.npmjs.com/package/qs).
```js
const qs = require('qs')
const router = require('find-my-way')({
querystringParser: str => qs.parse(str)
})
router.on('GET', '/', (req, res, params, store, searchParams) => {
assert.equal(searchParams, { foo: 'bar', baz: 'faz' })
})
router.lookup({ method: 'GET', url: '/?foo=bar&baz=faz' }, null)
```
You can assign a `buildPrettyMeta` function to sanitize a route's `store` object to use with the `prettyPrint` functions. This function should accept a single object and return an object.

@@ -154,4 +169,6 @@

Custom constraining strategies can be added and are matched against incoming requests while trying to maintain `find-my-way`'s high performance. To register a new type of constraint, you must add a new constraint strategy that knows how to match values to handlers, and that knows how to get the constraint value from a request. Register strategies when constructing a router:
Custom constraining strategies can be added and are matched against incoming requests while trying to maintain `find-my-way`'s high performance. To register a new type of constraint, you must add a new constraint strategy that knows how to match values to handlers, and that knows how to get the constraint value from a request. Register strategies when constructing a router or use the addConstraintStrategy method.
Add a custom constrain strategy when constructing a router:
```js

@@ -180,2 +197,28 @@ const customResponseTypeStrategy = {

Add a custom constraint strategy using the addConstraintStrategy method:
```js
const customResponseTypeStrategy = {
// strategy name for referencing in the route handler `constraints` options
name: 'accept',
// storage factory for storing routes in the find-my-way route tree
storage: function () {
let handlers = {}
return {
get: (type) => { return handlers[type] || null },
set: (type, store) => { handlers[type] = store }
}
},
// function to get the value of the constraint from each incoming request
deriveConstraint: (req, ctx) => {
return req.headers['accept']
},
// optional flag marking if handlers without constraints can match requests that have a value for this constraint
mustMatchWhenDerived: true
}
const router = FindMyWay();
router.addConstraintStrategy(customResponseTypeStrategy);
```
Once a custom constraint strategy is registered, routes can be added that are constrained using it:

@@ -233,3 +276,3 @@

```js
router.on('GET', '/example', (req, res, params) => {
router.on('GET', '/example', (req, res, params, store, searchParams) => {
// your code

@@ -326,16 +369,2 @@ })

If your routes' parameters are not Basic Latin charaters, you may face a performance drop. To avoid it, you can customize the way the parameters are parsed by passing a custom decoding function. Read more about it [here](https://github.com/delvedor/find-my-way/pull/211).
The parsing function is called when the request URL contains one or more encoded special characters: `# $ & + , / : ; = ? @`.
```js
const router = require('find-my-way')({
decodeUriParameters: (stringToDecode) => {
// called when the request URL contains st least one special character: `# $ & + , / : ; = ? @`
return decodeURIComponent(stringToDecode)
}
})
```
By default, this module relies on [fast-decode-uri-component`](https://www.npmjs.com/package/fast-decode-uri-component) to parse the encoded path parameters.
<a name="match-order"></a>

@@ -342,0 +371,0 @@ ##### Match order

@@ -40,2 +40,19 @@ 'use strict'

test('A route could support multiple versions (find) / 1 (add strategy outside constructor)', t => {
t.plan(5)
const findMyWay = FindMyWay()
findMyWay.addConstraintStrategy(customVersioning)
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, noop)
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, noop)
t.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=2' }))
t.ok(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=3' }))
t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=4' }))
t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=5' }))
t.notOk(findMyWay.find('GET', '/', { version: 'application/vnd.example.api+json;version=6' }))
})
test('Overriding default strategies uses the custom deriveConstraint function', t => {

@@ -65,1 +82,52 @@ t.plan(2)

})
test('Overriding default strategies uses the custom deriveConstraint function (add strategy outside constructor)', t => {
t.plan(2)
const findMyWay = FindMyWay()
findMyWay.addConstraintStrategy(customVersioning)
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=2' } }, (req, res, params) => {
t.equal(req.headers.accept, 'application/vnd.example.api+json;version=2')
})
findMyWay.on('GET', '/', { constraints: { version: 'application/vnd.example.api+json;version=3' } }, (req, res, params) => {
t.equal(req.headers.accept, 'application/vnd.example.api+json;version=3')
})
findMyWay.lookup({
method: 'GET',
url: '/',
headers: { accept: 'application/vnd.example.api+json;version=2' }
})
findMyWay.lookup({
method: 'GET',
url: '/',
headers: { accept: 'application/vnd.example.api+json;version=3' }
})
})
test('Overriding custom strategies throws as error (add strategy outside constructor)', t => {
t.plan(1)
const findMyWay = FindMyWay()
findMyWay.addConstraintStrategy(customVersioning)
t.throws(() => findMyWay.addConstraintStrategy(customVersioning),
'There already exists a custom constraint with the name version.'
)
})
test('Overriding default strategies after defining a route with constraint', t => {
t.plan(1)
const findMyWay = FindMyWay()
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, () => {})
t.throws(() => findMyWay.addConstraintStrategy(customVersioning),
'There already exists a route with version constraint.'
)
})

@@ -40,2 +40,17 @@ 'use strict'

test('A route could support a custom constraint strategy (add strategy outside constructor)', t => {
t.plan(3)
const findMyWay = FindMyWay()
findMyWay.addConstraintStrategy(customHeaderConstraint)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, alpha)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, beta)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl' }).handler, alpha)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'wget' }).handler, beta)
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome' }))
})
test('A route could support a custom constraint strategy while versioned', t => {

@@ -63,2 +78,26 @@ t.plan(8)

test('A route could support a custom constraint strategy while versioned (add strategy outside constructor)', t => {
t.plan(8)
const findMyWay = FindMyWay()
findMyWay.addConstraintStrategy(customHeaderConstraint)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0' } }, alpha)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0' } }, beta)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '2.0.0' } }, gamma)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget', version: '3.0.0' } }, delta)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' }).handler, alpha)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' }).handler, beta)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '2.x' }).handler, gamma)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '3.x' }).handler, delta)
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'wget', version: '1.x' }))
})
test('A route could support a custom constraint strategy while versioned and host constrained', t => {

@@ -85,2 +124,25 @@ t.plan(9)

test('A route could support a custom constraint strategy while versioned and host constrained (add strategy outside constructor)', t => {
t.plan(9)
const findMyWay = FindMyWay()
findMyWay.addConstraintStrategy(customHeaderConstraint)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '1.0.0', host: 'fastify.io' } }, alpha)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'fastify.io' } }, beta)
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl', version: '2.0.0', host: 'example.io' } }, delta)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'fastify.io' }).handler, alpha)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'fastify.io' }).handler, beta)
t.equal(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x', host: 'example.io' }).handler, delta)
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'chrome', version: '1.x' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '2.x' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '3.x', host: 'fastify.io' }))
t.notOk(findMyWay.find('GET', '/', { requestedBy: 'curl', version: '1.x', host: 'example.io' }))
})
test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived and there are no other routes', t => {

@@ -106,2 +168,21 @@ t.plan(1)

test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived and there are no other routes (add strategy outside constructor)', t => {
t.plan(1)
const findMyWay = FindMyWay({
defaultRoute (req, res) {
t.pass()
}
})
findMyWay.addConstraintStrategy({
...customHeaderConstraint,
mustMatchWhenDerived: true
})
findMyWay.on('GET', '/', {}, () => t.fail())
findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null)
})
test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived when there are constrained routes', t => {

@@ -129,2 +210,23 @@ t.plan(1)

test('Custom constraint strategies can set mustMatchWhenDerived flag to true which prevents matches to unconstrained routes when a constraint is derived when there are constrained routes (add strategy outside constructor)', t => {
t.plan(1)
const findMyWay = FindMyWay({
defaultRoute (req, res) {
t.pass()
}
})
findMyWay.addConstraintStrategy({
...customHeaderConstraint,
mustMatchWhenDerived: true
})
findMyWay.on('GET', '/', {}, () => t.fail())
findMyWay.on('GET', '/', { constraints: { requestedBy: 'curl' } }, () => t.fail())
findMyWay.on('GET', '/', { constraints: { requestedBy: 'wget' } }, () => t.fail())
findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null)
})
test('Custom constraint strategies can set mustMatchWhenDerived flag to false which allows matches to unconstrained routes when a constraint is derived', t => {

@@ -149,1 +251,30 @@ t.plan(1)

})
test('Custom constraint strategies can set mustMatchWhenDerived flag to false which allows matches to unconstrained routes when a constraint is derived (add strategy outside constructor)', t => {
t.plan(1)
const findMyWay = FindMyWay({
defaultRoute (req, res) {
t.pass()
}
})
findMyWay.addConstraintStrategy({
...customHeaderConstraint,
mustMatchWhenDerived: true
})
findMyWay.on('GET', '/', {}, () => t.pass())
findMyWay.lookup({ method: 'GET', url: '/', headers: { 'user-agent': 'node' } }, null)
})
test('Has constraint strategy method test', t => {
t.plan(2)
const findMyWay = FindMyWay()
t.same(findMyWay.hasConstraintStrategy(customHeaderConstraint.name), false)
findMyWay.addConstraintStrategy(customHeaderConstraint)
t.same(findMyWay.hasConstraintStrategy(customHeaderConstraint.name), true)
})

@@ -99,3 +99,3 @@ 'use strict'

t.notOk(findMyWay.find('GET', '/', { version: '1.x', __hasMustMatchValues: true }))
t.notOk(findMyWay.find('GET', '/', { version: '1.x' }))
})

@@ -102,0 +102,0 @@

@@ -62,3 +62,3 @@ 'use strict'

t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta)
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha)
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null)
})

@@ -76,3 +76,3 @@

t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta)
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha)
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null)
})

@@ -90,4 +90,23 @@

t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '1.0.0' }).handler, beta)
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }).handler, alpha)
t.equal(findMyWay.find('GET', '/', { host: 'fastify.io', version: '2.0.0' }), null)
t.equal(findMyWay.find('GET', '/', { host: 'example.io' }).handler, gamma)
})
test('Has constraint strategy method test', t => {
t.plan(6)
const findMyWay = FindMyWay()
t.same(findMyWay.hasConstraintStrategy('version'), false)
t.same(findMyWay.hasConstraintStrategy('host'), false)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io' } }, () => {})
t.same(findMyWay.hasConstraintStrategy('version'), false)
t.same(findMyWay.hasConstraintStrategy('host'), true)
findMyWay.on('GET', '/', { constraints: { host: 'fastify.io', version: '1.0.0' } }, () => {})
t.same(findMyWay.hasConstraintStrategy('version'), true)
t.same(findMyWay.hasConstraintStrategy('host'), true)
})

@@ -29,5 +29,5 @@ 'use strict'

router1.on('GET', '/', { constraints: { secret: 'alpha' } }, () => {})
router1.find('GET', '/', { constraints: { secret: 'alpha' } })
router1.find('GET', '/', { secret: 'alpha' })
t.pass('constraints is not overrided')
})

@@ -438,3 +438,3 @@ 'use strict'

findMyWay.find('GET', '/test'),
{ handler: fn, params: {}, store: null }
{ handler: fn, params: {}, store: null, searchParams: {} }
)

@@ -452,3 +452,3 @@ })

findMyWay.find('GET', '/test/hello'),
{ handler: fn, params: { id: 'hello' }, store: null }
{ handler: fn, params: { id: 'hello' }, store: null, searchParams: {} }
)

@@ -476,3 +476,3 @@ })

findMyWay.find('GET', '/test/he%2Fllo'),
{ handler: fn, params: { id: 'he/llo' }, store: null }
{ handler: fn, params: { id: 'he/llo' }, store: null, searchParams: {} }
)

@@ -490,3 +490,3 @@ })

findMyWay.find('GET', '/test/he%2Fllo'),
{ handler: fn, params: { '*': 'he/llo' }, store: null }
{ handler: fn, params: { '*': 'he/llo' }, store: null, searchParams: {} }
)

@@ -493,0 +493,0 @@ })

@@ -8,6 +8,7 @@ 'use strict'

test('should sanitize the url - query', t => {
t.plan(1)
t.plan(2)
const findMyWay = FindMyWay()
findMyWay.on('GET', '/test', (req, res, params) => {
findMyWay.on('GET', '/test', (req, res, params, store, query) => {
t.same(query, { hello: 'world' })
t.ok('inside the handler')

@@ -20,6 +21,7 @@ })

test('should sanitize the url - hash', t => {
t.plan(1)
t.plan(2)
const findMyWay = FindMyWay()
findMyWay.on('GET', '/test', (req, res, params) => {
findMyWay.on('GET', '/test', (req, res, params, store, query) => {
t.same(query, { hello: '' })
t.ok('inside the handler')

@@ -32,6 +34,7 @@ })

test('handles path and query separated by ;', t => {
t.plan(1)
t.plan(2)
const findMyWay = FindMyWay()
findMyWay.on('GET', '/test', (req, res, params) => {
findMyWay.on('GET', '/test', (req, res, params, store, query) => {
t.same(query, { jsessionid: '123456' })
t.ok('inside the handler')

@@ -38,0 +41,0 @@ })

@@ -28,3 +28,4 @@ 'use strict'

params: {},
store: { hello: 'world' }
store: { hello: 'world' },
searchParams: {}
})

@@ -31,0 +32,0 @@ })

@@ -54,3 +54,3 @@ import { expectType } from 'tsd'

expectType<void>(router.lookup(http1Req, http1Res))
expectType<any>(router.lookup(http1Req, http1Res))
expectType<Router.FindResult<Router.HTTPVersion.V1> | null>(router.find('GET', '/'))

@@ -70,2 +70,19 @@ expectType<Router.FindResult<Router.HTTPVersion.V1> | null>(router.find('GET', '/', {}))

{
const constraints: { [key: string]: Router.ConstraintStrategy<Router.HTTPVersion.V2, string> } = {
foo: {
name: 'foo',
mustMatchWhenDerived: true,
storage () {
return {
get (version) { return handler },
set (version, handler) {},
del (version) {},
empty () {}
}
},
deriveConstraint(req) { return '1.0.0' },
validate(value) { if (typeof value === "string") { throw new Error("invalid")} }
}
}
let handler: Router.Handler<Router.HTTPVersion.V2>

@@ -79,18 +96,3 @@ const router = Router<Router.HTTPVersion.V2>({

onBadUrl (path, http1Req, http1Res) {},
constraints: {
foo: {
name: 'foo',
mustMatchWhenDerived: true,
storage () {
return {
get (version) { return handler },
set (version, handler) {},
del (version) {},
empty () {}
}
},
deriveConstraint(req) { return '1.0.0' },
validate(value) { if (typeof value === "string") { throw new Error("invalid")} }
}
}
constraints
})

@@ -105,2 +107,4 @@ expectType<Router.Instance<Router.HTTPVersion.V2>>(router)

expectType<void>(router.addConstraintStrategy(constraints.foo))
expectType<void>(router.get('/', () => {}))

@@ -114,3 +118,3 @@ expectType<void>(router.get('/', { constraints: { version: '1.0.0' }}, () => {}))

expectType<void>(router.lookup(http2Req, http2Res))
expectType<any>(router.lookup(http2Req, http2Res))
expectType<Router.FindResult<Router.HTTPVersion.V2> | null>(router.find('GET', '/', {}))

@@ -148,2 +152,3 @@ expectType<Router.FindResult<Router.HTTPVersion.V2> | null>(router.find('GET', '/', {version: '1.0.0', host: 'fastify.io'}))

}
const storageWithObject = customConstraintWithObject.storage()

@@ -150,0 +155,0 @@ const acceptAndContentType: AcceptAndContentType = { accept: 'application/json', contentType: 'application/xml' }

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc