/ node_modules / router / index.js
index.js
  1  /*!
  2   * router
  3   * Copyright(c) 2013 Roman Shtylman
  4   * Copyright(c) 2014-2022 Douglas Christopher Wilson
  5   * MIT Licensed
  6   */
  7  
  8  'use strict'
  9  
 10  /**
 11   * Module dependencies.
 12   * @private
 13   */
 14  
 15  const isPromise = require('is-promise')
 16  const Layer = require('./lib/layer')
 17  const { METHODS } = require('node:http')
 18  const parseUrl = require('parseurl')
 19  const Route = require('./lib/route')
 20  const debug = require('debug')('router')
 21  const deprecate = require('depd')('router')
 22  
 23  /**
 24   * Module variables.
 25   * @private
 26   */
 27  
 28  const slice = Array.prototype.slice
 29  const flatten = Array.prototype.flat
 30  const methods = METHODS.map((method) => method.toLowerCase())
 31  
 32  /**
 33   * Expose `Router`.
 34   */
 35  
 36  module.exports = Router
 37  
 38  /**
 39   * Expose `Route`.
 40   */
 41  
 42  module.exports.Route = Route
 43  
 44  /**
 45   * Initialize a new `Router` with the given `options`.
 46   *
 47   * @param {object} [options]
 48   * @return {Router} which is a callable function
 49   * @public
 50   */
 51  
 52  function Router (options) {
 53    if (!(this instanceof Router)) {
 54      return new Router(options)
 55    }
 56  
 57    const opts = options || {}
 58  
 59    function router (req, res, next) {
 60      router.handle(req, res, next)
 61    }
 62  
 63    // inherit from the correct prototype
 64    Object.setPrototypeOf(router, this)
 65  
 66    router.caseSensitive = opts.caseSensitive
 67    router.mergeParams = opts.mergeParams
 68    router.params = {}
 69    router.strict = opts.strict
 70    router.stack = []
 71  
 72    return router
 73  }
 74  
 75  /**
 76   * Router prototype inherits from a Function.
 77   */
 78  
 79  /* istanbul ignore next */
 80  Router.prototype = function () {}
 81  
 82  /**
 83   * Map the given param placeholder `name`(s) to the given callback.
 84   *
 85   * Parameter mapping is used to provide pre-conditions to routes
 86   * which use normalized placeholders. For example a _:user_id_ parameter
 87   * could automatically load a user's information from the database without
 88   * any additional code.
 89   *
 90   * The callback uses the same signature as middleware, the only difference
 91   * being that the value of the placeholder is passed, in this case the _id_
 92   * of the user. Once the `next()` function is invoked, just like middleware
 93   * it will continue on to execute the route, or subsequent parameter functions.
 94   *
 95   * Just like in middleware, you must either respond to the request or call next
 96   * to avoid stalling the request.
 97   *
 98   *  router.param('user_id', function(req, res, next, id){
 99   *    User.find(id, function(err, user){
100   *      if (err) {
101   *        return next(err)
102   *      } else if (!user) {
103   *        return next(new Error('failed to load user'))
104   *      }
105   *      req.user = user
106   *      next()
107   *    })
108   *  })
109   *
110   * @param {string} name
111   * @param {function} fn
112   * @public
113   */
114  
115  Router.prototype.param = function param (name, fn) {
116    if (!name) {
117      throw new TypeError('argument name is required')
118    }
119  
120    if (typeof name !== 'string') {
121      throw new TypeError('argument name must be a string')
122    }
123  
124    if (!fn) {
125      throw new TypeError('argument fn is required')
126    }
127  
128    if (typeof fn !== 'function') {
129      throw new TypeError('argument fn must be a function')
130    }
131  
132    let params = this.params[name]
133  
134    if (!params) {
135      params = this.params[name] = []
136    }
137  
138    params.push(fn)
139  
140    return this
141  }
142  
143  /**
144   * Dispatch a req, res into the router.
145   *
146   * @private
147   */
148  
149  Router.prototype.handle = function handle (req, res, callback) {
150    if (!callback) {
151      throw new TypeError('argument callback is required')
152    }
153  
154    debug('dispatching %s %s', req.method, req.url)
155  
156    let idx = 0
157    let methods
158    const protohost = getProtohost(req.url) || ''
159    let removed = ''
160    const self = this
161    let slashAdded = false
162    let sync = 0
163    const paramcalled = {}
164  
165    // middleware and routes
166    const stack = this.stack
167  
168    // manage inter-router variables
169    const parentParams = req.params
170    const parentUrl = req.baseUrl || ''
171    let done = restore(callback, req, 'baseUrl', 'next', 'params')
172  
173    // setup next layer
174    req.next = next
175  
176    // for options requests, respond with a default if nothing else responds
177    if (req.method === 'OPTIONS') {
178      methods = []
179      done = wrap(done, generateOptionsResponder(res, methods))
180    }
181  
182    // setup basic req values
183    req.baseUrl = parentUrl
184    req.originalUrl = req.originalUrl || req.url
185  
186    next()
187  
188    function next (err) {
189      let layerError = err === 'route'
190        ? null
191        : err
192  
193      // remove added slash
194      if (slashAdded) {
195        req.url = req.url.slice(1)
196        slashAdded = false
197      }
198  
199      // restore altered req.url
200      if (removed.length !== 0) {
201        req.baseUrl = parentUrl
202        req.url = protohost + removed + req.url.slice(protohost.length)
203        removed = ''
204      }
205  
206      // signal to exit router
207      if (layerError === 'router') {
208        setImmediate(done, null)
209        return
210      }
211  
212      // no more matching layers
213      if (idx >= stack.length) {
214        setImmediate(done, layerError)
215        return
216      }
217  
218      // max sync stack
219      if (++sync > 100) {
220        return setImmediate(next, err)
221      }
222  
223      // get pathname of request
224      const path = getPathname(req)
225  
226      if (path == null) {
227        return done(layerError)
228      }
229  
230      // find next matching layer
231      let layer
232      let match
233      let route
234  
235      while (match !== true && idx < stack.length) {
236        layer = stack[idx++]
237        match = matchLayer(layer, path)
238        route = layer.route
239  
240        if (typeof match !== 'boolean') {
241          // hold on to layerError
242          layerError = layerError || match
243        }
244  
245        if (match !== true) {
246          continue
247        }
248  
249        if (!route) {
250          // process non-route handlers normally
251          continue
252        }
253  
254        if (layerError) {
255          // routes do not match with a pending error
256          match = false
257          continue
258        }
259  
260        const method = req.method
261        const hasMethod = route._handlesMethod(method)
262  
263        // build up automatic options response
264        if (!hasMethod && method === 'OPTIONS' && methods) {
265          methods.push.apply(methods, route._methods())
266        }
267  
268        // don't even bother matching route
269        if (!hasMethod && method !== 'HEAD') {
270          match = false
271        }
272      }
273  
274      // no match
275      if (match !== true) {
276        return done(layerError)
277      }
278  
279      // store route for dispatch on change
280      if (route) {
281        req.route = route
282      }
283  
284      // Capture one-time layer values
285      req.params = self.mergeParams
286        ? mergeParams(layer.params, parentParams)
287        : layer.params
288      const layerPath = layer.path
289  
290      // this should be done for the layer
291      processParams(self.params, layer, paramcalled, req, res, function (err) {
292        if (err) {
293          next(layerError || err)
294        } else if (route) {
295          layer.handleRequest(req, res, next)
296        } else {
297          trimPrefix(layer, layerError, layerPath, path)
298        }
299  
300        sync = 0
301      })
302    }
303  
304    function trimPrefix (layer, layerError, layerPath, path) {
305      if (layerPath.length !== 0) {
306        // Validate path is a prefix match
307        if (layerPath !== path.substring(0, layerPath.length)) {
308          next(layerError)
309          return
310        }
311  
312        // Validate path breaks on a path separator
313        const c = path[layerPath.length]
314        if (c && c !== '/') {
315          next(layerError)
316          return
317        }
318  
319        // Trim off the part of the url that matches the route
320        // middleware (.use stuff) needs to have the path stripped
321        debug('trim prefix (%s) from url %s', layerPath, req.url)
322        removed = layerPath
323        req.url = protohost + req.url.slice(protohost.length + removed.length)
324  
325        // Ensure leading slash
326        if (!protohost && req.url[0] !== '/') {
327          req.url = '/' + req.url
328          slashAdded = true
329        }
330  
331        // Setup base URL (no trailing slash)
332        req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
333          ? removed.substring(0, removed.length - 1)
334          : removed)
335      }
336  
337      debug('%s %s : %s', layer.name, layerPath, req.originalUrl)
338  
339      if (layerError) {
340        layer.handleError(layerError, req, res, next)
341      } else {
342        layer.handleRequest(req, res, next)
343      }
344    }
345  }
346  
347  /**
348   * Use the given middleware function, with optional path, defaulting to "/".
349   *
350   * Use (like `.all`) will run for any http METHOD, but it will not add
351   * handlers for those methods so OPTIONS requests will not consider `.use`
352   * functions even if they could respond.
353   *
354   * The other difference is that _route_ path is stripped and not visible
355   * to the handler function. The main effect of this feature is that mounted
356   * handlers can operate without any code changes regardless of the "prefix"
357   * pathname.
358   *
359   * @public
360   */
361  
362  Router.prototype.use = function use (handler) {
363    let offset = 0
364    let path = '/'
365  
366    // default path to '/'
367    // disambiguate router.use([handler])
368    if (typeof handler !== 'function') {
369      let arg = handler
370  
371      while (Array.isArray(arg) && arg.length !== 0) {
372        arg = arg[0]
373      }
374  
375      // first arg is the path
376      if (typeof arg !== 'function') {
377        offset = 1
378        path = handler
379      }
380    }
381  
382    const callbacks = flatten.call(slice.call(arguments, offset), Infinity)
383  
384    if (callbacks.length === 0) {
385      throw new TypeError('argument handler is required')
386    }
387  
388    for (let i = 0; i < callbacks.length; i++) {
389      const fn = callbacks[i]
390  
391      if (typeof fn !== 'function') {
392        throw new TypeError('argument handler must be a function')
393      }
394  
395      // add the middleware
396      debug('use %o %s', path, fn.name || '<anonymous>')
397  
398      const layer = new Layer(path, {
399        sensitive: this.caseSensitive,
400        strict: false,
401        end: false
402      }, fn)
403  
404      layer.route = undefined
405  
406      this.stack.push(layer)
407    }
408  
409    return this
410  }
411  
412  /**
413   * Create a new Route for the given path.
414   *
415   * Each route contains a separate middleware stack and VERB handlers.
416   *
417   * See the Route api documentation for details on adding handlers
418   * and middleware to routes.
419   *
420   * @param {string} path
421   * @return {Route}
422   * @public
423   */
424  
425  Router.prototype.route = function route (path) {
426    const route = new Route(path)
427  
428    const layer = new Layer(path, {
429      sensitive: this.caseSensitive,
430      strict: this.strict,
431      end: true
432    }, handle)
433  
434    function handle (req, res, next) {
435      route.dispatch(req, res, next)
436    }
437  
438    layer.route = route
439  
440    this.stack.push(layer)
441    return route
442  }
443  
444  // create Router#VERB functions
445  methods.concat('all').forEach(function (method) {
446    Router.prototype[method] = function (path) {
447      const route = this.route(path)
448      route[method].apply(route, slice.call(arguments, 1))
449      return this
450    }
451  })
452  
453  /**
454   * Generate a callback that will make an OPTIONS response.
455   *
456   * @param {OutgoingMessage} res
457   * @param {array} methods
458   * @private
459   */
460  
461  function generateOptionsResponder (res, methods) {
462    return function onDone (fn, err) {
463      if (err || methods.length === 0) {
464        return fn(err)
465      }
466  
467      trySendOptionsResponse(res, methods, fn)
468    }
469  }
470  
471  /**
472   * Get pathname of request.
473   *
474   * @param {IncomingMessage} req
475   * @private
476   */
477  
478  function getPathname (req) {
479    try {
480      return parseUrl(req).pathname
481    } catch (err) {
482      return undefined
483    }
484  }
485  
486  /**
487   * Get get protocol + host for a URL.
488   *
489   * @param {string} url
490   * @private
491   */
492  
493  function getProtohost (url) {
494    if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {
495      return undefined
496    }
497  
498    const searchIndex = url.indexOf('?')
499    const pathLength = searchIndex !== -1
500      ? searchIndex
501      : url.length
502    const fqdnIndex = url.substring(0, pathLength).indexOf('://')
503  
504    return fqdnIndex !== -1
505      ? url.substring(0, url.indexOf('/', 3 + fqdnIndex))
506      : undefined
507  }
508  
509  /**
510   * Match path to a layer.
511   *
512   * @param {Layer} layer
513   * @param {string} path
514   * @private
515   */
516  
517  function matchLayer (layer, path) {
518    try {
519      return layer.match(path)
520    } catch (err) {
521      return err
522    }
523  }
524  
525  /**
526   * Merge params with parent params
527   *
528   * @private
529   */
530  
531  function mergeParams (params, parent) {
532    if (typeof parent !== 'object' || !parent) {
533      return params
534    }
535  
536    // make copy of parent for base
537    const obj = Object.assign({}, parent)
538  
539    // simple non-numeric merging
540    if (!(0 in params) || !(0 in parent)) {
541      return Object.assign(obj, params)
542    }
543  
544    let i = 0
545    let o = 0
546  
547    // determine numeric gap in params
548    while (i in params) {
549      i++
550    }
551  
552    // determine numeric gap in parent
553    while (o in parent) {
554      o++
555    }
556  
557    // offset numeric indices in params before merge
558    for (i--; i >= 0; i--) {
559      params[i + o] = params[i]
560  
561      // create holes for the merge when necessary
562      if (i < o) {
563        delete params[i]
564      }
565    }
566  
567    return Object.assign(obj, params)
568  }
569  
570  /**
571   * Process any parameters for the layer.
572   *
573   * @private
574   */
575  
576  function processParams (params, layer, called, req, res, done) {
577    // captured parameters from the layer, keys and values
578    const keys = layer.keys
579  
580    // fast track
581    if (!keys || keys.length === 0) {
582      return done()
583    }
584  
585    let i = 0
586    let paramIndex = 0
587    let key
588    let paramVal
589    let paramCallbacks
590    let paramCalled
591  
592    // process params in order
593    // param callbacks can be async
594    function param (err) {
595      if (err) {
596        return done(err)
597      }
598  
599      if (i >= keys.length) {
600        return done()
601      }
602  
603      paramIndex = 0
604      key = keys[i++]
605      paramVal = req.params[key]
606      paramCallbacks = params[key]
607      paramCalled = called[key]
608  
609      if (paramVal === undefined || !paramCallbacks) {
610        return param()
611      }
612  
613      // param previously called with same value or error occurred
614      if (paramCalled && (paramCalled.match === paramVal ||
615        (paramCalled.error && paramCalled.error !== 'route'))) {
616        // restore value
617        req.params[key] = paramCalled.value
618  
619        // next param
620        return param(paramCalled.error)
621      }
622  
623      called[key] = paramCalled = {
624        error: null,
625        match: paramVal,
626        value: paramVal
627      }
628  
629      paramCallback()
630    }
631  
632    // single param callbacks
633    function paramCallback (err) {
634      const fn = paramCallbacks[paramIndex++]
635  
636      // store updated value
637      paramCalled.value = req.params[key]
638  
639      if (err) {
640        // store error
641        paramCalled.error = err
642        param(err)
643        return
644      }
645  
646      if (!fn) return param()
647  
648      try {
649        const ret = fn(req, res, paramCallback, paramVal, key)
650        if (isPromise(ret)) {
651          if (!(ret instanceof Promise)) {
652            deprecate('parameters that are Promise-like are deprecated, use a native Promise instead')
653          }
654  
655          ret.then(null, function (error) {
656            paramCallback(error || new Error('Rejected promise'))
657          })
658        }
659      } catch (e) {
660        paramCallback(e)
661      }
662    }
663  
664    param()
665  }
666  
667  /**
668   * Restore obj props after function
669   *
670   * @private
671   */
672  
673  function restore (fn, obj) {
674    const props = new Array(arguments.length - 2)
675    const vals = new Array(arguments.length - 2)
676  
677    for (let i = 0; i < props.length; i++) {
678      props[i] = arguments[i + 2]
679      vals[i] = obj[props[i]]
680    }
681  
682    return function () {
683      // restore vals
684      for (let i = 0; i < props.length; i++) {
685        obj[props[i]] = vals[i]
686      }
687  
688      return fn.apply(this, arguments)
689    }
690  }
691  
692  /**
693   * Send an OPTIONS response.
694   *
695   * @private
696   */
697  
698  function sendOptionsResponse (res, methods) {
699    const options = Object.create(null)
700  
701    // build unique method map
702    for (let i = 0; i < methods.length; i++) {
703      options[methods[i]] = true
704    }
705  
706    // construct the allow list
707    const allow = Object.keys(options).sort().join(', ')
708  
709    // send response
710    res.setHeader('Allow', allow)
711    res.setHeader('Content-Length', Buffer.byteLength(allow))
712    res.setHeader('Content-Type', 'text/plain')
713    res.setHeader('X-Content-Type-Options', 'nosniff')
714    res.end(allow)
715  }
716  
717  /**
718   * Try to send an OPTIONS response.
719   *
720   * @private
721   */
722  
723  function trySendOptionsResponse (res, methods, next) {
724    try {
725      sendOptionsResponse(res, methods)
726    } catch (err) {
727      next(err)
728    }
729  }
730  
731  /**
732   * Wrap a function
733   *
734   * @private
735   */
736  
737  function wrap (old, fn) {
738    return function proxy () {
739      const args = new Array(arguments.length + 1)
740  
741      args[0] = old
742      for (let i = 0, len = arguments.length; i < len; i++) {
743        args[i + 1] = arguments[i]
744      }
745  
746      fn.apply(this, args)
747    }
748  }