azure-devops-mcp

by RyanCardin15
Verified
/*! * 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') /** * 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') } 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 continue } } // 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 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) } 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 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)) { 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) } }