749 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			749 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!
 | |
|  * 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)
 | |
|   }
 | |
| }
 |