UNPKG

mockttp

Version:

Mock HTTP server for testing HTTP clients and stubbing webservices

132 lines (115 loc) 5.35 kB
import * as _ from 'lodash'; import { MaybePromise } from '@httptoolkit/util'; import { RuleParameterReference } from './rule-parameters'; import { CADefinition } from './passthrough-handling-definitions'; /** * A ProxySetting is a specific proxy setting to use, which is passed to a proxy agent * who will manage creating a socket for the request (directly, or tunnelled, or whatever). */ export interface ProxySetting { /** * The URL for the proxy to forward traffic through. * * This can be any URL supported by https://www.npmjs.com/package/proxy-agent. * For example: http://..., socks5://..., pac+http://... */ proxyUrl: string; /** * A list of no-proxy values, matching hosts' traffic should *not* be proxied. * * This is a common proxy feature, but unfortunately isn't standardized. See * https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/ for some * background. This implementation is intended to match Curl's behaviour, and * any differences are a bug. * * The currently supported formats are: * - example.com (matches domain and all subdomains) * - example.com:443 (matches domain and all subdomains, but only on that port) * - 10.0.0.1 (matches IP, but only when used directly - does not resolve domains) * * Some other formats (e.g. leading dots or *.) will work, but the leading * characters are ignored. More formats may be added in future, e.g. CIDR ranges. * To maximize compatibility with values used elsewhere, unrecognized formats * will generally be ignored, but may match in unexpected ways. */ noProxy?: string[]; /** * CAs to trust for HTTPS connections to the proxy. Ignored if the connection to * the proxy is not HTTPS. If not specified, this will default to the Node * defaults, or you can override them here completely. * * This sets the complete list of trusted CAs, and is mutually exclusive with the * `additionalTrustedCAs` option, which adds additional CAs (but also trusts the * Node default CAs too). * * This should be specified as either a { cert: string | Buffer } object or a * { certPath: string } object (to read the cert from disk). */ trustedCAs?: Array<CADefinition>; /** * Extra CAs to trust for HTTPS connections to the proxy. Ignored if the connection * to the proxy is not HTTPS. * * This appends to the list of trusted CAs, and is mutually exclusive with the * `trustedCAs` option, which completely overrides the list of CAs. */ additionalTrustedCAs?: Array<CADefinition>; } /** * A ProxySettingSource is a way to calculate the ProxySetting for a given request. It * may be a fixed ProxySetting value, or a callback to get ProxySetting values, or an * array of sources, which should be iterated to get the first usable value */ export type ProxySettingSource = | ProxySetting | ProxySettingCallback | Array<ProxySettingSource> | undefined; export type ProxySettingCallbackParams = { hostname: string }; export type ProxySettingCallback = (params: ProxySettingCallbackParams) => MaybePromise<ProxySetting | undefined>; /** * A ProxyConfig is externally provided config that specifies a ProxySettingSource. * It might be a ProxySettingSource itself, or it might include references to rule * parameters, which must be dereferenced to make it usable as a ProxySettingSource. */ export type ProxyConfig = | ProxySettingSource | RuleParameterReference<ProxySettingSource> | Array<ProxySettingSource | RuleParameterReference<ProxySettingSource>>; export async function getProxySetting( configSource: ProxySettingSource, params: ProxySettingCallbackParams ) { if (_.isFunction(configSource)) return configSource(params); else if (_.isArray(configSource)) { let result: ProxySetting | undefined; for (let configArrayOption of configSource) { result = await getProxySetting(configArrayOption, params); if (result) break; } return result; } else return configSource; } export const matchesNoProxy = (hostname: string, portNum: number, noProxyValues: string[] | undefined) => { if (!noProxyValues || noProxyValues.length === 0) return false; // Skip everything in the common case. const port = portNum.toString(); const hostParts = hostname.split('.').reverse(); return noProxyValues.some((noProxy) => { const [noProxyHost, noProxyPort] = noProxy.split(':') as [string, string | undefined]; let noProxyParts = noProxyHost.split('.').reverse(); const lastPart = noProxyParts[noProxyParts.length - 1]; if (lastPart === '' || lastPart === '*') { noProxyParts = noProxyParts.slice(0, -1); } if (noProxyPort && port !== noProxyPort) return false; for (let i = 0; i < noProxyParts.length; i++) { let noProxyPart = noProxyParts[i]; let hostPart = hostParts[i]; if (hostPart === undefined) return false; // No-proxy is longer than hostname if (noProxyPart !== hostPart) return false; // Mismatch } // If we run out of no-proxy parts with no mismatch then we've matched return true; }); }