aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/router/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/router/index.js')
-rw-r--r--node_modules/router/index.js748
1 files changed, 748 insertions, 0 deletions
diff --git a/node_modules/router/index.js b/node_modules/router/index.js
new file mode 100644
index 0000000..4358aeb
--- /dev/null
+++ b/node_modules/router/index.js
@@ -0,0 +1,748 @@
+/*!
+ * router
+ * Copyright(c) 2013 Roman Shtylman
+ * Copyright(c) 2014-2022 Douglas Christopher Wilson
+ * MIT Licensed
+ */
+
+'use strict'
+
+/**
+ * Module dependencies.
+ * @private
+ */
+
+const isPromise = require('is-promise')
+const Layer = require('./lib/layer')
+const { METHODS } = require('node:http')
+const parseUrl = require('parseurl')
+const Route = require('./lib/route')
+const debug = require('debug')('router')
+const deprecate = require('depd')('router')
+
+/**
+ * Module variables.
+ * @private
+ */
+
+const slice = Array.prototype.slice
+const flatten = Array.prototype.flat
+const methods = METHODS.map((method) => method.toLowerCase())
+
+/**
+ * Expose `Router`.
+ */
+
+module.exports = Router
+
+/**
+ * Expose `Route`.
+ */
+
+module.exports.Route = Route
+
+/**
+ * Initialize a new `Router` with the given `options`.
+ *
+ * @param {object} [options]
+ * @return {Router} which is a callable function
+ * @public
+ */
+
+function Router (options) {
+ if (!(this instanceof Router)) {
+ return new Router(options)
+ }
+
+ const opts = options || {}
+
+ function router (req, res, next) {
+ router.handle(req, res, next)
+ }
+
+ // inherit from the correct prototype
+ Object.setPrototypeOf(router, this)
+
+ router.caseSensitive = opts.caseSensitive
+ router.mergeParams = opts.mergeParams
+ router.params = {}
+ router.strict = opts.strict
+ router.stack = []
+
+ return router
+}
+
+/**
+ * Router prototype inherits from a Function.
+ */
+
+/* istanbul ignore next */
+Router.prototype = function () {}
+
+/**
+ * Map the given param placeholder `name`(s) to the given callback.
+ *
+ * Parameter mapping is used to provide pre-conditions to routes
+ * which use normalized placeholders. For example a _:user_id_ parameter
+ * could automatically load a user's information from the database without
+ * any additional code.
+ *
+ * The callback uses the same signature as middleware, the only difference
+ * being that the value of the placeholder is passed, in this case the _id_
+ * of the user. Once the `next()` function is invoked, just like middleware
+ * it will continue on to execute the route, or subsequent parameter functions.
+ *
+ * Just like in middleware, you must either respond to the request or call next
+ * to avoid stalling the request.
+ *
+ * router.param('user_id', function(req, res, next, id){
+ * User.find(id, function(err, user){
+ * if (err) {
+ * return next(err)
+ * } else if (!user) {
+ * return next(new Error('failed to load user'))
+ * }
+ * req.user = user
+ * next()
+ * })
+ * })
+ *
+ * @param {string} name
+ * @param {function} fn
+ * @public
+ */
+
+Router.prototype.param = function param (name, fn) {
+ if (!name) {
+ throw new TypeError('argument name is required')
+ }
+
+ if (typeof name !== 'string') {
+ throw new TypeError('argument name must be a string')
+ }
+
+ if (!fn) {
+ throw new TypeError('argument fn is required')
+ }
+
+ if (typeof fn !== 'function') {
+ throw new TypeError('argument fn must be a function')
+ }
+
+ let params = this.params[name]
+
+ if (!params) {
+ params = this.params[name] = []
+ }
+
+ params.push(fn)
+
+ return this
+}
+
+/**
+ * Dispatch a req, res into the router.
+ *
+ * @private
+ */
+
+Router.prototype.handle = function handle (req, res, callback) {
+ if (!callback) {
+ throw new TypeError('argument callback is required')
+ }
+
+ debug('dispatching %s %s', req.method, req.url)
+
+ let idx = 0
+ let methods
+ const protohost = getProtohost(req.url) || ''
+ let removed = ''
+ const self = this
+ let slashAdded = false
+ let sync = 0
+ const paramcalled = {}
+
+ // middleware and routes
+ const stack = this.stack
+
+ // manage inter-router variables
+ const parentParams = req.params
+ const parentUrl = req.baseUrl || ''
+ let done = restore(callback, req, 'baseUrl', 'next', 'params')
+
+ // setup next layer
+ req.next = next
+
+ // for options requests, respond with a default if nothing else responds
+ if (req.method === 'OPTIONS') {
+ methods = []
+ done = wrap(done, generateOptionsResponder(res, methods))
+ }
+
+ // setup basic req values
+ req.baseUrl = parentUrl
+ req.originalUrl = req.originalUrl || req.url
+
+ next()
+
+ function next (err) {
+ let layerError = err === 'route'
+ ? null
+ : err
+
+ // remove added slash
+ if (slashAdded) {
+ req.url = req.url.slice(1)
+ slashAdded = false
+ }
+
+ // restore altered req.url
+ if (removed.length !== 0) {
+ req.baseUrl = parentUrl
+ req.url = protohost + removed + req.url.slice(protohost.length)
+ removed = ''
+ }
+
+ // signal to exit router
+ if (layerError === 'router') {
+ setImmediate(done, null)
+ return
+ }
+
+ // no more matching layers
+ if (idx >= stack.length) {
+ setImmediate(done, layerError)
+ return
+ }
+
+ // max sync stack
+ if (++sync > 100) {
+ return setImmediate(next, err)
+ }
+
+ // get pathname of request
+ const path = getPathname(req)
+
+ if (path == null) {
+ return done(layerError)
+ }
+
+ // find next matching layer
+ let layer
+ let match
+ let route
+
+ while (match !== true && idx < stack.length) {
+ layer = stack[idx++]
+ match = matchLayer(layer, path)
+ route = layer.route
+
+ if (typeof match !== 'boolean') {
+ // hold on to layerError
+ layerError = layerError || match
+ }
+
+ if (match !== true) {
+ continue
+ }
+
+ if (!route) {
+ // process non-route handlers normally
+ continue
+ }
+
+ if (layerError) {
+ // routes do not match with a pending error
+ match = false
+ continue
+ }
+
+ const method = req.method
+ const hasMethod = route._handlesMethod(method)
+
+ // build up automatic options response
+ if (!hasMethod && method === 'OPTIONS' && methods) {
+ methods.push.apply(methods, route._methods())
+ }
+
+ // don't even bother matching route
+ if (!hasMethod && method !== 'HEAD') {
+ match = false
+ }
+ }
+
+ // no match
+ if (match !== true) {
+ return done(layerError)
+ }
+
+ // store route for dispatch on change
+ if (route) {
+ req.route = route
+ }
+
+ // Capture one-time layer values
+ req.params = self.mergeParams
+ ? mergeParams(layer.params, parentParams)
+ : layer.params
+ const layerPath = layer.path
+
+ // this should be done for the layer
+ processParams(self.params, layer, paramcalled, req, res, function (err) {
+ if (err) {
+ next(layerError || err)
+ } else if (route) {
+ layer.handleRequest(req, res, next)
+ } else {
+ trimPrefix(layer, layerError, layerPath, path)
+ }
+
+ sync = 0
+ })
+ }
+
+ function trimPrefix (layer, layerError, layerPath, path) {
+ if (layerPath.length !== 0) {
+ // Validate path is a prefix match
+ if (layerPath !== path.substring(0, layerPath.length)) {
+ next(layerError)
+ return
+ }
+
+ // Validate path breaks on a path separator
+ const c = path[layerPath.length]
+ if (c && c !== '/') {
+ next(layerError)
+ return
+ }
+
+ // Trim off the part of the url that matches the route
+ // middleware (.use stuff) needs to have the path stripped
+ debug('trim prefix (%s) from url %s', layerPath, req.url)
+ removed = layerPath
+ req.url = protohost + req.url.slice(protohost.length + removed.length)
+
+ // Ensure leading slash
+ if (!protohost && req.url[0] !== '/') {
+ req.url = '/' + req.url
+ slashAdded = true
+ }
+
+ // Setup base URL (no trailing slash)
+ req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
+ ? removed.substring(0, removed.length - 1)
+ : removed)
+ }
+
+ debug('%s %s : %s', layer.name, layerPath, req.originalUrl)
+
+ if (layerError) {
+ layer.handleError(layerError, req, res, next)
+ } else {
+ layer.handleRequest(req, res, next)
+ }
+ }
+}
+
+/**
+ * Use the given middleware function, with optional path, defaulting to "/".
+ *
+ * Use (like `.all`) will run for any http METHOD, but it will not add
+ * handlers for those methods so OPTIONS requests will not consider `.use`
+ * functions even if they could respond.
+ *
+ * The other difference is that _route_ path is stripped and not visible
+ * to the handler function. The main effect of this feature is that mounted
+ * handlers can operate without any code changes regardless of the "prefix"
+ * pathname.
+ *
+ * @public
+ */
+
+Router.prototype.use = function use (handler) {
+ let offset = 0
+ let path = '/'
+
+ // default path to '/'
+ // disambiguate router.use([handler])
+ if (typeof handler !== 'function') {
+ let arg = handler
+
+ while (Array.isArray(arg) && arg.length !== 0) {
+ arg = arg[0]
+ }
+
+ // first arg is the path
+ if (typeof arg !== 'function') {
+ offset = 1
+ path = handler
+ }
+ }
+
+ const callbacks = flatten.call(slice.call(arguments, offset), Infinity)
+
+ if (callbacks.length === 0) {
+ throw new TypeError('argument handler is required')
+ }
+
+ for (let i = 0; i < callbacks.length; i++) {
+ const fn = callbacks[i]
+
+ if (typeof fn !== 'function') {
+ throw new TypeError('argument handler must be a function')
+ }
+
+ // add the middleware
+ debug('use %o %s', path, fn.name || '<anonymous>')
+
+ const layer = new Layer(path, {
+ sensitive: this.caseSensitive,
+ strict: false,
+ end: false
+ }, fn)
+
+ layer.route = undefined
+
+ this.stack.push(layer)
+ }
+
+ return this
+}
+
+/**
+ * Create a new Route for the given path.
+ *
+ * Each route contains a separate middleware stack and VERB handlers.
+ *
+ * See the Route api documentation for details on adding handlers
+ * and middleware to routes.
+ *
+ * @param {string} path
+ * @return {Route}
+ * @public
+ */
+
+Router.prototype.route = function route (path) {
+ const route = new Route(path)
+
+ const layer = new Layer(path, {
+ sensitive: this.caseSensitive,
+ strict: this.strict,
+ end: true
+ }, handle)
+
+ function handle (req, res, next) {
+ route.dispatch(req, res, next)
+ }
+
+ layer.route = route
+
+ this.stack.push(layer)
+ return route
+}
+
+// create Router#VERB functions
+methods.concat('all').forEach(function (method) {
+ Router.prototype[method] = function (path) {
+ const route = this.route(path)
+ route[method].apply(route, slice.call(arguments, 1))
+ return this
+ }
+})
+
+/**
+ * Generate a callback that will make an OPTIONS response.
+ *
+ * @param {OutgoingMessage} res
+ * @param {array} methods
+ * @private
+ */
+
+function generateOptionsResponder (res, methods) {
+ return function onDone (fn, err) {
+ if (err || methods.length === 0) {
+ return fn(err)
+ }
+
+ trySendOptionsResponse(res, methods, fn)
+ }
+}
+
+/**
+ * Get pathname of request.
+ *
+ * @param {IncomingMessage} req
+ * @private
+ */
+
+function getPathname (req) {
+ try {
+ return parseUrl(req).pathname
+ } catch (err) {
+ return undefined
+ }
+}
+
+/**
+ * Get get protocol + host for a URL.
+ *
+ * @param {string} url
+ * @private
+ */
+
+function getProtohost (url) {
+ if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {
+ return undefined
+ }
+
+ const searchIndex = url.indexOf('?')
+ const pathLength = searchIndex !== -1
+ ? searchIndex
+ : url.length
+ const fqdnIndex = url.substring(0, pathLength).indexOf('://')
+
+ return fqdnIndex !== -1
+ ? url.substring(0, url.indexOf('/', 3 + fqdnIndex))
+ : undefined
+}
+
+/**
+ * Match path to a layer.
+ *
+ * @param {Layer} layer
+ * @param {string} path
+ * @private
+ */
+
+function matchLayer (layer, path) {
+ try {
+ return layer.match(path)
+ } catch (err) {
+ return err
+ }
+}
+
+/**
+ * Merge params with parent params
+ *
+ * @private
+ */
+
+function mergeParams (params, parent) {
+ if (typeof parent !== 'object' || !parent) {
+ return params
+ }
+
+ // make copy of parent for base
+ const obj = Object.assign({}, parent)
+
+ // simple non-numeric merging
+ if (!(0 in params) || !(0 in parent)) {
+ return Object.assign(obj, params)
+ }
+
+ let i = 0
+ let o = 0
+
+ // determine numeric gap in params
+ while (i in params) {
+ i++
+ }
+
+ // determine numeric gap in parent
+ while (o in parent) {
+ o++
+ }
+
+ // offset numeric indices in params before merge
+ for (i--; i >= 0; i--) {
+ params[i + o] = params[i]
+
+ // create holes for the merge when necessary
+ if (i < o) {
+ delete params[i]
+ }
+ }
+
+ return Object.assign(obj, params)
+}
+
+/**
+ * Process any parameters for the layer.
+ *
+ * @private
+ */
+
+function processParams (params, layer, called, req, res, done) {
+ // captured parameters from the layer, keys and values
+ const keys = layer.keys
+
+ // fast track
+ if (!keys || keys.length === 0) {
+ return done()
+ }
+
+ let i = 0
+ let paramIndex = 0
+ let key
+ let paramVal
+ let paramCallbacks
+ let paramCalled
+
+ // process params in order
+ // param callbacks can be async
+ function param (err) {
+ if (err) {
+ return done(err)
+ }
+
+ if (i >= keys.length) {
+ return done()
+ }
+
+ paramIndex = 0
+ key = keys[i++]
+ paramVal = req.params[key]
+ paramCallbacks = params[key]
+ paramCalled = called[key]
+
+ if (paramVal === undefined || !paramCallbacks) {
+ return param()
+ }
+
+ // param previously called with same value or error occurred
+ if (paramCalled && (paramCalled.match === paramVal ||
+ (paramCalled.error && paramCalled.error !== 'route'))) {
+ // restore value
+ req.params[key] = paramCalled.value
+
+ // next param
+ return param(paramCalled.error)
+ }
+
+ called[key] = paramCalled = {
+ error: null,
+ match: paramVal,
+ value: paramVal
+ }
+
+ paramCallback()
+ }
+
+ // single param callbacks
+ function paramCallback (err) {
+ const fn = paramCallbacks[paramIndex++]
+
+ // store updated value
+ paramCalled.value = req.params[key]
+
+ if (err) {
+ // store error
+ paramCalled.error = err
+ param(err)
+ return
+ }
+
+ if (!fn) return param()
+
+ try {
+ const ret = fn(req, res, paramCallback, paramVal, key)
+ if (isPromise(ret)) {
+ if (!(ret instanceof Promise)) {
+ deprecate('parameters that are Promise-like are deprecated, use a native Promise instead')
+ }
+
+ ret.then(null, function (error) {
+ paramCallback(error || new Error('Rejected promise'))
+ })
+ }
+ } catch (e) {
+ paramCallback(e)
+ }
+ }
+
+ param()
+}
+
+/**
+ * Restore obj props after function
+ *
+ * @private
+ */
+
+function restore (fn, obj) {
+ const props = new Array(arguments.length - 2)
+ const vals = new Array(arguments.length - 2)
+
+ for (let i = 0; i < props.length; i++) {
+ props[i] = arguments[i + 2]
+ vals[i] = obj[props[i]]
+ }
+
+ return function () {
+ // restore vals
+ for (let i = 0; i < props.length; i++) {
+ obj[props[i]] = vals[i]
+ }
+
+ return fn.apply(this, arguments)
+ }
+}
+
+/**
+ * Send an OPTIONS response.
+ *
+ * @private
+ */
+
+function sendOptionsResponse (res, methods) {
+ const options = Object.create(null)
+
+ // build unique method map
+ for (let i = 0; i < methods.length; i++) {
+ options[methods[i]] = true
+ }
+
+ // construct the allow list
+ const allow = Object.keys(options).sort().join(', ')
+
+ // send response
+ res.setHeader('Allow', allow)
+ res.setHeader('Content-Length', Buffer.byteLength(allow))
+ res.setHeader('Content-Type', 'text/plain')
+ res.setHeader('X-Content-Type-Options', 'nosniff')
+ res.end(allow)
+}
+
+/**
+ * Try to send an OPTIONS response.
+ *
+ * @private
+ */
+
+function trySendOptionsResponse (res, methods, next) {
+ try {
+ sendOptionsResponse(res, methods)
+ } catch (err) {
+ next(err)
+ }
+}
+
+/**
+ * Wrap a function
+ *
+ * @private
+ */
+
+function wrap (old, fn) {
+ return function proxy () {
+ const args = new Array(arguments.length + 1)
+
+ args[0] = old
+ for (let i = 0, len = arguments.length; i < len; i++) {
+ args[i + 1] = arguments[i]
+ }
+
+ fn.apply(this, args)
+ }
+}