Buckets:
| /* | |
| Char codes: | |
| '!': 33 - ! | |
| '#': 35 - %23 | |
| '$': 36 - %24 | |
| '%': 37 - %25 | |
| '&': 38 - %26 | |
| ''': 39 - ' | |
| '(': 40 - ( | |
| ')': 41 - ) | |
| '*': 42 - * | |
| '+': 43 - %2B | |
| ',': 44 - %2C | |
| '-': 45 - - | |
| '.': 46 - . | |
| '/': 47 - %2F | |
| ':': 58 - %3A | |
| ';': 59 - %3B | |
| '=': 61 - %3D | |
| '?': 63 - %3F | |
| '@': 64 - %40 | |
| '_': 95 - _ | |
| '~': 126 - ~ | |
| */ | |
| const assert = require('node:assert') | |
| const querystring = require('fast-querystring') | |
| const isRegexSafe = require('safe-regex2') | |
| const deepEqual = require('fast-deep-equal') | |
| const { prettyPrintTree } = require('./lib/pretty-print') | |
| const { StaticNode, NODE_TYPES } = require('./lib/node') | |
| const Constrainer = require('./lib/constrainer') | |
| const httpMethods = require('./lib/http-methods') | |
| const httpMethodStrategy = require('./lib/strategies/http-method') | |
| const { safeDecodeURI, safeDecodeURIComponent } = require('./lib/url-sanitizer') | |
| const FULL_PATH_REGEXP = /^https?:\/\/.*?\// | |
| const OPTIONAL_PARAM_REGEXP = /(\/:[^/()]*?)\?(\/?)/ | |
| const ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g | |
| const REMOVE_DUPLICATE_SLASHES_REGEXP = /\/\/+/g | |
| if (!isRegexSafe(FULL_PATH_REGEXP)) { | |
| throw new Error('the FULL_PATH_REGEXP is not safe, update this module') | |
| } | |
| if (!isRegexSafe(OPTIONAL_PARAM_REGEXP)) { | |
| throw new Error('the OPTIONAL_PARAM_REGEXP is not safe, update this module') | |
| } | |
| if (!isRegexSafe(ESCAPE_REGEXP)) { | |
| throw new Error('the ESCAPE_REGEXP is not safe, update this module') | |
| } | |
| if (!isRegexSafe(REMOVE_DUPLICATE_SLASHES_REGEXP)) { | |
| throw new Error('the REMOVE_DUPLICATE_SLASHES_REGEXP is not safe, update this module') | |
| } | |
| function Router (opts) { | |
| if (!(this instanceof Router)) { | |
| return new Router(opts) | |
| } | |
| opts = opts || {} | |
| this._opts = opts | |
| if (opts.defaultRoute) { | |
| assert(typeof opts.defaultRoute === 'function', 'The default route must be a function') | |
| this.defaultRoute = opts.defaultRoute | |
| } else { | |
| this.defaultRoute = null | |
| } | |
| if (opts.onBadUrl) { | |
| assert(typeof opts.onBadUrl === 'function', 'The bad url handler must be a function') | |
| this.onBadUrl = opts.onBadUrl | |
| } else { | |
| this.onBadUrl = null | |
| } | |
| if (opts.buildPrettyMeta) { | |
| assert(typeof opts.buildPrettyMeta === 'function', 'buildPrettyMeta must be a function') | |
| this.buildPrettyMeta = opts.buildPrettyMeta | |
| } else { | |
| this.buildPrettyMeta = defaultBuildPrettyMeta | |
| } | |
| if (opts.querystringParser) { | |
| assert(typeof opts.querystringParser === 'function', 'querystringParser must be a function') | |
| this.querystringParser = opts.querystringParser | |
| } else { | |
| this.querystringParser = (query) => query.length === 0 ? {} : querystring.parse(query) | |
| } | |
| this.caseSensitive = opts.caseSensitive === undefined ? true : opts.caseSensitive | |
| this.ignoreTrailingSlash = opts.ignoreTrailingSlash || false | |
| this.ignoreDuplicateSlashes = opts.ignoreDuplicateSlashes || false | |
| this.maxParamLength = opts.maxParamLength || 100 | |
| this.allowUnsafeRegex = opts.allowUnsafeRegex || false | |
| this.constrainer = new Constrainer(opts.constraints) | |
| this.useSemicolonDelimiter = opts.useSemicolonDelimiter || false | |
| this.routes = [] | |
| this.trees = {} | |
| } | |
| Router.prototype.on = function on (method, path, opts, handler, store) { | |
| if (typeof opts === 'function') { | |
| if (handler !== undefined) { | |
| store = handler | |
| } | |
| handler = opts | |
| opts = {} | |
| } | |
| // path validation | |
| assert(typeof path === 'string', 'Path should be a string') | |
| assert(path.length > 0, 'The path could not be empty') | |
| assert(path[0] === '/' || path[0] === '*', 'The first character of a path should be `/` or `*`') | |
| // handler validation | |
| assert(typeof handler === 'function', 'Handler should be a function') | |
| // path ends with optional parameter | |
| const optionalParamMatch = path.match(OPTIONAL_PARAM_REGEXP) | |
| if (optionalParamMatch) { | |
| assert(path.length === optionalParamMatch.index + optionalParamMatch[0].length, 'Optional Parameter needs to be the last parameter of the path') | |
| const pathFull = path.replace(OPTIONAL_PARAM_REGEXP, '$1$2') | |
| const pathOptional = path.replace(OPTIONAL_PARAM_REGEXP, '$2') || '/' | |
| this.on(method, pathFull, opts, handler, store) | |
| this.on(method, pathOptional, opts, handler, store) | |
| return | |
| } | |
| const route = path | |
| if (this.ignoreDuplicateSlashes) { | |
| path = removeDuplicateSlashes(path) | |
| } | |
| if (this.ignoreTrailingSlash) { | |
| path = trimLastSlash(path) | |
| } | |
| const methods = Array.isArray(method) ? method : [method] | |
| for (const method of methods) { | |
| assert(typeof method === 'string', 'Method should be a string') | |
| assert(httpMethods.includes(method), `Method '${method}' is not an http method.`) | |
| this._on(method, path, opts, handler, store, route) | |
| } | |
| } | |
| Router.prototype._on = function _on (method, path, opts, handler, store) { | |
| let constraints = {} | |
| if (opts.constraints !== undefined) { | |
| assert(typeof opts.constraints === 'object' && opts.constraints !== null, 'Constraints should be an object') | |
| if (Object.keys(opts.constraints).length !== 0) { | |
| constraints = opts.constraints | |
| } | |
| } | |
| this.constrainer.validateConstraints(constraints) | |
| // Let the constrainer know if any constraints are being used now | |
| this.constrainer.noteUsage(constraints) | |
| // Boot the tree for this method if it doesn't exist yet | |
| if (this.trees[method] === undefined) { | |
| this.trees[method] = new StaticNode('/') | |
| } | |
| let pattern = path | |
| if (pattern === '*' && this.trees[method].prefix.length !== 0) { | |
| const currentRoot = this.trees[method] | |
| this.trees[method] = new StaticNode('') | |
| this.trees[method].staticChildren['/'] = currentRoot | |
| } | |
| let currentNode = this.trees[method] | |
| let parentNodePathIndex = currentNode.prefix.length | |
| const params = [] | |
| for (let i = 0; i <= pattern.length; i++) { | |
| if (pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) === 58) { | |
| // It's a double colon | |
| i++ | |
| continue | |
| } | |
| const isParametricNode = pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) !== 58 | |
| const isWildcardNode = pattern.charCodeAt(i) === 42 | |
| if (isParametricNode || isWildcardNode || (i === pattern.length && i !== parentNodePathIndex)) { | |
| let staticNodePath = pattern.slice(parentNodePathIndex, i) | |
| if (!this.caseSensitive) { | |
| staticNodePath = staticNodePath.toLowerCase() | |
| } | |
| staticNodePath = staticNodePath.replaceAll('::', ':') | |
| staticNodePath = staticNodePath.replaceAll('%', '%25') | |
| // add the static part of the route to the tree | |
| currentNode = currentNode.createStaticChild(staticNodePath) | |
| } | |
| if (isParametricNode) { | |
| let isRegexNode = false | |
| let isParamSafe = true | |
| let backtrack = '' | |
| const regexps = [] | |
| let lastParamStartIndex = i + 1 | |
| for (let j = lastParamStartIndex; ; j++) { | |
| const charCode = pattern.charCodeAt(j) | |
| const isRegexParam = charCode === 40 | |
| const isStaticPart = charCode === 45 || charCode === 46 | |
| const isEndOfNode = charCode === 47 || j === pattern.length | |
| if (isRegexParam || isStaticPart || isEndOfNode) { | |
| const paramName = pattern.slice(lastParamStartIndex, j) | |
| params.push(paramName) | |
| isRegexNode = isRegexNode || isRegexParam || isStaticPart | |
| if (isRegexParam) { | |
| const endOfRegexIndex = getClosingParenthensePosition(pattern, j) | |
| const regexString = pattern.slice(j, endOfRegexIndex + 1) | |
| if (!this.allowUnsafeRegex) { | |
| assert(isRegexSafe(new RegExp(regexString)), `The regex '${regexString}' is not safe!`) | |
| } | |
| regexps.push(trimRegExpStartAndEnd(regexString)) | |
| j = endOfRegexIndex + 1 | |
| isParamSafe = true | |
| } else { | |
| regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`) | |
| isParamSafe = false | |
| } | |
| const staticPartStartIndex = j | |
| for (; j < pattern.length; j++) { | |
| const charCode = pattern.charCodeAt(j) | |
| if (charCode === 47) break | |
| if (charCode === 58) { | |
| const nextCharCode = pattern.charCodeAt(j + 1) | |
| if (nextCharCode === 58) j++ | |
| else break | |
| } | |
| } | |
| let staticPart = pattern.slice(staticPartStartIndex, j) | |
| if (staticPart) { | |
| staticPart = staticPart.replaceAll('::', ':') | |
| staticPart = staticPart.replaceAll('%', '%25') | |
| regexps.push(backtrack = escapeRegExp(staticPart)) | |
| } | |
| lastParamStartIndex = j + 1 | |
| if (isEndOfNode || pattern.charCodeAt(j) === 47 || j === pattern.length) { | |
| const nodePattern = isRegexNode ? '()' + staticPart : staticPart | |
| const nodePath = pattern.slice(i, j) | |
| pattern = pattern.slice(0, i + 1) + nodePattern + pattern.slice(j) | |
| i += nodePattern.length | |
| const regex = isRegexNode ? new RegExp('^' + regexps.join('') + '$') : null | |
| currentNode = currentNode.createParametricChild(regex, staticPart || null, nodePath) | |
| parentNodePathIndex = i + 1 | |
| break | |
| } | |
| } | |
| } | |
| } else if (isWildcardNode) { | |
| // add the wildcard parameter | |
| params.push('*') | |
| currentNode = currentNode.createWildcardChild() | |
| parentNodePathIndex = i + 1 | |
| if (i !== pattern.length - 1) { | |
| throw new Error('Wildcard must be the last character in the route') | |
| } | |
| } | |
| } | |
| if (!this.caseSensitive) { | |
| pattern = pattern.toLowerCase() | |
| } | |
| if (pattern === '*') { | |
| pattern = '/*' | |
| } | |
| for (const existRoute of this.routes) { | |
| const routeConstraints = existRoute.opts.constraints || {} | |
| if ( | |
| existRoute.method === method && | |
| existRoute.pattern === pattern && | |
| deepEqual(routeConstraints, constraints) | |
| ) { | |
| throw new Error(`Method '${method}' already declared for route '${pattern}' with constraints '${JSON.stringify(constraints)}'`) | |
| } | |
| } | |
| const route = { method, path, pattern, params, opts, handler, store } | |
| this.routes.push(route) | |
| currentNode.addRoute(route, this.constrainer) | |
| } | |
| Router.prototype.hasRoute = function hasRoute (method, path, constraints) { | |
| const route = this.findRoute(method, path, constraints) | |
| return route !== null | |
| } | |
| Router.prototype.findRoute = function findNode (method, path, constraints = {}) { | |
| if (this.trees[method] === undefined) { | |
| return null | |
| } | |
| let pattern = path | |
| let currentNode = this.trees[method] | |
| let parentNodePathIndex = currentNode.prefix.length | |
| const params = [] | |
| for (let i = 0; i <= pattern.length; i++) { | |
| if (pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) === 58) { | |
| // It's a double colon | |
| i++ | |
| continue | |
| } | |
| const isParametricNode = pattern.charCodeAt(i) === 58 && pattern.charCodeAt(i + 1) !== 58 | |
| const isWildcardNode = pattern.charCodeAt(i) === 42 | |
| if (isParametricNode || isWildcardNode || (i === pattern.length && i !== parentNodePathIndex)) { | |
| let staticNodePath = pattern.slice(parentNodePathIndex, i) | |
| if (!this.caseSensitive) { | |
| staticNodePath = staticNodePath.toLowerCase() | |
| } | |
| staticNodePath = staticNodePath.replaceAll('::', ':') | |
| staticNodePath = staticNodePath.replaceAll('%', '%25') | |
| // add the static part of the route to the tree | |
| currentNode = currentNode.getStaticChild(staticNodePath) | |
| if (currentNode === null) { | |
| return null | |
| } | |
| } | |
| if (isParametricNode) { | |
| let isRegexNode = false | |
| let isParamSafe = true | |
| let backtrack = '' | |
| const regexps = [] | |
| let lastParamStartIndex = i + 1 | |
| for (let j = lastParamStartIndex; ; j++) { | |
| const charCode = pattern.charCodeAt(j) | |
| const isRegexParam = charCode === 40 | |
| const isStaticPart = charCode === 45 || charCode === 46 | |
| const isEndOfNode = charCode === 47 || j === pattern.length | |
| if (isRegexParam || isStaticPart || isEndOfNode) { | |
| const paramName = pattern.slice(lastParamStartIndex, j) | |
| params.push(paramName) | |
| isRegexNode = isRegexNode || isRegexParam || isStaticPart | |
| if (isRegexParam) { | |
| const endOfRegexIndex = getClosingParenthensePosition(pattern, j) | |
| const regexString = pattern.slice(j, endOfRegexIndex + 1) | |
| if (!this.allowUnsafeRegex) { | |
| assert(isRegexSafe(new RegExp(regexString)), `The regex '${regexString}' is not safe!`) | |
| } | |
| regexps.push(trimRegExpStartAndEnd(regexString)) | |
| j = endOfRegexIndex + 1 | |
| isParamSafe = false | |
| } else { | |
| regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`) | |
| isParamSafe = false | |
| } | |
| const staticPartStartIndex = j | |
| for (; j < pattern.length; j++) { | |
| const charCode = pattern.charCodeAt(j) | |
| if (charCode === 47) break | |
| if (charCode === 58) { | |
| const nextCharCode = pattern.charCodeAt(j + 1) | |
| if (nextCharCode === 58) j++ | |
| else break | |
| } | |
| } | |
| let staticPart = pattern.slice(staticPartStartIndex, j) | |
| if (staticPart) { | |
| staticPart = staticPart.replaceAll('::', ':') | |
| staticPart = staticPart.replaceAll('%', '%25') | |
| regexps.push(backtrack = escapeRegExp(staticPart)) | |
| } | |
| lastParamStartIndex = j + 1 | |
| if (isEndOfNode || pattern.charCodeAt(j) === 47 || j === pattern.length) { | |
| const nodePattern = isRegexNode ? '()' + staticPart : staticPart | |
| const nodePath = pattern.slice(i, j) | |
| pattern = pattern.slice(0, i + 1) + nodePattern + pattern.slice(j) | |
| i += nodePattern.length | |
| const regex = isRegexNode ? new RegExp('^' + regexps.join('') + '$') : null | |
| currentNode = currentNode.getParametricChild(regex, staticPart || null, nodePath) | |
| if (currentNode === null) { | |
| return null | |
| } | |
| parentNodePathIndex = i + 1 | |
| break | |
| } | |
| } | |
| } | |
| } else if (isWildcardNode) { | |
| // add the wildcard parameter | |
| params.push('*') | |
| currentNode = currentNode.getWildcardChild() | |
| parentNodePathIndex = i + 1 | |
| if (i !== pattern.length - 1) { | |
| throw new Error('Wildcard must be the last character in the route') | |
| } | |
| } | |
| } | |
| if (!this.caseSensitive) { | |
| pattern = pattern.toLowerCase() | |
| } | |
| for (const existRoute of this.routes) { | |
| const routeConstraints = existRoute.opts.constraints || {} | |
| if ( | |
| existRoute.method === method && | |
| existRoute.pattern === pattern && | |
| deepEqual(routeConstraints, constraints) | |
| ) { | |
| return { | |
| handler: existRoute.handler, | |
| store: existRoute.store, | |
| params: existRoute.params | |
| } | |
| } | |
| } | |
| return null | |
| } | |
| 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 () { | |
| this.trees = {} | |
| this.routes = [] | |
| } | |
| Router.prototype.off = function off (method, path, constraints) { | |
| // path validation | |
| assert(typeof path === 'string', 'Path should be a string') | |
| assert(path.length > 0, 'The path could not be empty') | |
| assert(path[0] === '/' || path[0] === '*', 'The first character of a path should be `/` or `*`') | |
| // options validation | |
| assert( | |
| typeof constraints === 'undefined' || | |
| (typeof constraints === 'object' && !Array.isArray(constraints) && constraints !== null), | |
| 'Constraints should be an object or undefined.') | |
| // path ends with optional parameter | |
| const optionalParamMatch = path.match(OPTIONAL_PARAM_REGEXP) | |
| if (optionalParamMatch) { | |
| assert(path.length === optionalParamMatch.index + optionalParamMatch[0].length, 'Optional Parameter needs to be the last parameter of the path') | |
| const pathFull = path.replace(OPTIONAL_PARAM_REGEXP, '$1$2') | |
| const pathOptional = path.replace(OPTIONAL_PARAM_REGEXP, '$2') | |
| this.off(method, pathFull, constraints) | |
| this.off(method, pathOptional, constraints) | |
| return | |
| } | |
| if (this.ignoreDuplicateSlashes) { | |
| path = removeDuplicateSlashes(path) | |
| } | |
| if (this.ignoreTrailingSlash) { | |
| path = trimLastSlash(path) | |
| } | |
| const methods = Array.isArray(method) ? method : [method] | |
| for (const method of methods) { | |
| this._off(method, path, constraints) | |
| } | |
| } | |
| Router.prototype._off = function _off (method, path, constraints) { | |
| // method validation | |
| assert(typeof method === 'string', 'Method should be a string') | |
| assert(httpMethods.includes(method), `Method '${method}' is not an http method.`) | |
| function matcherWithoutConstraints (route) { | |
| return method !== route.method || path !== route.path | |
| } | |
| function matcherWithConstraints (route) { | |
| return matcherWithoutConstraints(route) || !deepEqual(constraints, route.opts.constraints || {}) | |
| } | |
| const predicate = constraints ? matcherWithConstraints : matcherWithoutConstraints | |
| // Rebuild tree without the specific route | |
| const newRoutes = this.routes.filter(predicate) | |
| this._rebuild(newRoutes) | |
| } | |
| Router.prototype.lookup = function lookup (req, res, ctx, done) { | |
| if (typeof ctx === 'function') { | |
| done = ctx | |
| ctx = undefined | |
| } | |
| if (done === undefined) { | |
| const constraints = this.constrainer.deriveConstraints(req, ctx) | |
| const handle = this.find(req.method, req.url, constraints) | |
| return this.callHandler(handle, req, res, ctx) | |
| } | |
| this.constrainer.deriveConstraints(req, ctx, (err, constraints) => { | |
| if (err !== null) { | |
| done(err) | |
| return | |
| } | |
| try { | |
| const handle = this.find(req.method, req.url, constraints) | |
| const result = this.callHandler(handle, req, res, ctx) | |
| done(null, result) | |
| } catch (err) { | |
| done(err) | |
| } | |
| }) | |
| } | |
| Router.prototype.callHandler = function callHandler (handle, req, res, ctx) { | |
| if (handle === null) return this._defaultRoute(req, res, ctx) | |
| return ctx === undefined | |
| ? handle.handler(req, res, handle.params, handle.store, handle.searchParams) | |
| : handle.handler.call(ctx, req, res, handle.params, handle.store, handle.searchParams) | |
| } | |
| Router.prototype.find = function find (method, path, derivedConstraints) { | |
| let currentNode = this.trees[method] | |
| if (currentNode === undefined) return null | |
| if (path.charCodeAt(0) !== 47) { // 47 is '/' | |
| path = path.replace(FULL_PATH_REGEXP, '/') | |
| } | |
| // This must be run before sanitizeUrl as the resulting function | |
| // .sliceParameter must be constructed with same URL string used | |
| // throughout the rest of this function. | |
| if (this.ignoreDuplicateSlashes) { | |
| path = removeDuplicateSlashes(path) | |
| } | |
| let sanitizedUrl | |
| let querystring | |
| let shouldDecodeParam | |
| try { | |
| sanitizedUrl = safeDecodeURI(path, this.useSemicolonDelimiter) | |
| path = sanitizedUrl.path | |
| querystring = sanitizedUrl.querystring | |
| shouldDecodeParam = sanitizedUrl.shouldDecodeParam | |
| } catch (error) { | |
| return this._onBadUrl(path) | |
| } | |
| if (this.ignoreTrailingSlash) { | |
| path = trimLastSlash(path) | |
| } | |
| const originPath = path | |
| if (this.caseSensitive === false) { | |
| path = path.toLowerCase() | |
| } | |
| const maxParamLength = this.maxParamLength | |
| let pathIndex = currentNode.prefix.length | |
| const params = [] | |
| const pathLen = path.length | |
| const brothersNodesStack = [] | |
| while (true) { | |
| if (pathIndex === pathLen && currentNode.isLeafNode) { | |
| const handle = currentNode.handlerStorage.getMatchingHandler(derivedConstraints) | |
| if (handle !== null) { | |
| return { | |
| handler: handle.handler, | |
| store: handle.store, | |
| params: handle._createParamsObject(params), | |
| searchParams: this.querystringParser(querystring) | |
| } | |
| } | |
| } | |
| let node = currentNode.getNextNode(path, pathIndex, brothersNodesStack, params.length) | |
| if (node === null) { | |
| if (brothersNodesStack.length === 0) { | |
| return null | |
| } | |
| const brotherNodeState = brothersNodesStack.pop() | |
| pathIndex = brotherNodeState.brotherPathIndex | |
| params.splice(brotherNodeState.paramsCount) | |
| node = brotherNodeState.brotherNode | |
| } | |
| currentNode = node | |
| // static route | |
| if (currentNode.kind === NODE_TYPES.STATIC) { | |
| pathIndex += currentNode.prefix.length | |
| continue | |
| } | |
| if (currentNode.kind === NODE_TYPES.WILDCARD) { | |
| let param = originPath.slice(pathIndex) | |
| if (shouldDecodeParam) { | |
| param = safeDecodeURIComponent(param) | |
| } | |
| params.push(param) | |
| pathIndex = pathLen | |
| continue | |
| } | |
| // parametric node | |
| let paramEndIndex = originPath.indexOf('/', pathIndex) | |
| if (paramEndIndex === -1) { | |
| paramEndIndex = pathLen | |
| } | |
| let param = originPath.slice(pathIndex, paramEndIndex) | |
| if (shouldDecodeParam) { | |
| param = safeDecodeURIComponent(param) | |
| } | |
| if (currentNode.isRegex) { | |
| const matchedParameters = currentNode.regex.exec(param) | |
| if (matchedParameters === null) continue | |
| for (let i = 1; i < matchedParameters.length; i++) { | |
| const matchedParam = matchedParameters[i] | |
| if (matchedParam.length > maxParamLength) { | |
| return null | |
| } | |
| params.push(matchedParam) | |
| } | |
| } else { | |
| if (param.length > maxParamLength) { | |
| return null | |
| } | |
| params.push(param) | |
| } | |
| pathIndex = paramEndIndex | |
| } | |
| } | |
| 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) | |
| } | |
| } | |
| Router.prototype._defaultRoute = function (req, res, ctx) { | |
| if (this.defaultRoute !== null) { | |
| return ctx === undefined | |
| ? this.defaultRoute(req, res) | |
| : this.defaultRoute.call(ctx, req, res) | |
| } else { | |
| res.statusCode = 404 | |
| res.end() | |
| } | |
| } | |
| Router.prototype._onBadUrl = function (path) { | |
| if (this.onBadUrl === null) { | |
| return null | |
| } | |
| const onBadUrl = this.onBadUrl | |
| return { | |
| handler: (req, res, ctx) => onBadUrl(path, req, res), | |
| params: {}, | |
| store: null | |
| } | |
| } | |
| Router.prototype.prettyPrint = function (options = {}) { | |
| const method = options.method | |
| options.buildPrettyMeta = this.buildPrettyMeta.bind(this) | |
| let tree = null | |
| if (method === undefined) { | |
| const { version, host, ...constraints } = this.constrainer.strategies | |
| constraints[httpMethodStrategy.name] = httpMethodStrategy | |
| const mergedRouter = new Router({ ...this._opts, constraints }) | |
| const mergedRoutes = this.routes.map(route => { | |
| const constraints = { | |
| ...route.opts.constraints, | |
| [httpMethodStrategy.name]: route.method | |
| } | |
| return { ...route, method: 'MERGED', opts: { constraints } } | |
| }) | |
| mergedRouter._rebuild(mergedRoutes) | |
| tree = mergedRouter.trees.MERGED | |
| } else { | |
| tree = this.trees[method] | |
| } | |
| if (tree == null) return '(empty tree)' | |
| return prettyPrintTree(tree, options) | |
| } | |
| for (const i in httpMethods) { | |
| /* eslint no-prototype-builtins: "off" */ | |
| if (!httpMethods.hasOwnProperty(i)) continue | |
| const m = httpMethods[i] | |
| const methodName = m.toLowerCase() | |
| Router.prototype[methodName] = function (path, handler, store) { | |
| return this.on(m, path, handler, store) | |
| } | |
| } | |
| Router.prototype.all = function (path, handler, store) { | |
| this.on(httpMethods, path, handler, store) | |
| } | |
| Router.sanitizeUrlPath = function sanitizeUrlPath (url, useSemicolonDelimiter) { | |
| const decoded = safeDecodeURI(url, useSemicolonDelimiter) | |
| if (decoded.shouldDecodeParam) { | |
| return safeDecodeURIComponent(decoded.path) | |
| } | |
| return decoded.path | |
| } | |
| module.exports = Router | |
| function escapeRegExp (string) { | |
| return string.replace(ESCAPE_REGEXP, '\\$&') | |
| } | |
| function removeDuplicateSlashes (path) { | |
| return path.indexOf('//') !== -1 ? path.replace(REMOVE_DUPLICATE_SLASHES_REGEXP, '/') : path | |
| } | |
| function trimLastSlash (path) { | |
| if (path.length > 1 && path.charCodeAt(path.length - 1) === 47) { | |
| return path.slice(0, -1) | |
| } | |
| return path | |
| } | |
| function trimRegExpStartAndEnd (regexString) { | |
| // removes chars that marks start "^" and end "$" of regexp | |
| if (regexString.charCodeAt(1) === 94) { | |
| regexString = regexString.slice(0, 1) + regexString.slice(2) | |
| } | |
| if (regexString.charCodeAt(regexString.length - 2) === 36) { | |
| regexString = regexString.slice(0, regexString.length - 2) + regexString.slice(regexString.length - 1) | |
| } | |
| return regexString | |
| } | |
| function getClosingParenthensePosition (path, idx) { | |
| // `path.indexOf()` will always return the first position of the closing parenthese, | |
| // but it's inefficient for grouped or wrong regexp expressions. | |
| // see issues #62 and #63 for more info | |
| let parentheses = 1 | |
| while (idx < path.length) { | |
| idx++ | |
| // ignore skipped chars "\" | |
| if (path.charCodeAt(idx) === 92) { | |
| idx++ | |
| continue | |
| } | |
| if (path.charCodeAt(idx) === 41) { | |
| parentheses-- | |
| } else if (path.charCodeAt(idx) === 40) { | |
| parentheses++ | |
| } | |
| if (!parentheses) return idx | |
| } | |
| throw new TypeError('Invalid regexp expression in "' + path + '"') | |
| } | |
| function defaultBuildPrettyMeta (route) { | |
| // buildPrettyMeta function must return an object, which will be parsed into key/value pairs for display | |
| if (!route) return {} | |
| if (!route.store) return {} | |
| return Object.assign({}, route.store) | |
| } | |
Xet Storage Details
- Size:
- 25.6 kB
- Xet hash:
- 3790f487940e9bffc16d302b2d3fce9f2f543af8fd54b8099ed8dc53c67010fb
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.