import { _optionalChain } from '@sentry/utils'; import { URL } from 'url'; import { NODE_VERSION } from '../../nodeVersion.js'; /** * Assembles a URL that's passed to the users to filter on. * It can include raw (potentially PII containing) data, which we'll allow users to access to filter * but won't include in spans or breadcrumbs. * * @param requestOptions RequestOptions object containing the component parts for a URL * @returns Fully-formed URL */ // TODO (v8): This function should include auth, query and fragment (it's breaking, so we need to wait for v8) function extractRawUrl(requestOptions) { const { protocol, hostname, port } = parseRequestOptions(requestOptions); const path = requestOptions.path ? requestOptions.path : '/'; return `${protocol}//${hostname}${port}${path}`; } /** * Assemble a URL to be used for breadcrumbs and spans. * * @param requestOptions RequestOptions object containing the component parts for a URL * @returns Fully-formed URL */ function extractUrl(requestOptions) { const { protocol, hostname, port } = parseRequestOptions(requestOptions); const path = requestOptions.pathname || '/'; // always filter authority, see https://develop.sentry.dev/sdk/data-handling/#structuring-data const authority = requestOptions.auth ? redactAuthority(requestOptions.auth) : ''; return `${protocol}//${authority}${hostname}${port}${path}`; } function redactAuthority(auth) { const [user, password] = auth.split(':'); return `${user ? '[Filtered]' : ''}:${password ? '[Filtered]' : ''}@`; } /** * Handle various edge cases in the span description (for spans representing http(s) requests). * * @param description current `description` property of the span representing the request * @param requestOptions Configuration data for the request * @param Request Request object * * @returns The cleaned description */ function cleanSpanDescription( description, requestOptions, request, ) { // nothing to clean if (!description) { return description; } // eslint-disable-next-line prefer-const let [method, requestUrl] = description.split(' '); // superagent sticks the protocol in a weird place (we check for host because if both host *and* protocol are missing, // we're likely dealing with an internal route and this doesn't apply) if (requestOptions.host && !requestOptions.protocol) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any requestOptions.protocol = _optionalChain([(request ), 'optionalAccess', _ => _.agent, 'optionalAccess', _2 => _2.protocol]); // worst comes to worst, this is undefined and nothing changes // This URL contains the filtered authority ([filtered]:[filtered]@example.com) but no fragment or query params requestUrl = extractUrl(requestOptions); } // internal routes can end up starting with a triple slash rather than a single one if (_optionalChain([requestUrl, 'optionalAccess', _3 => _3.startsWith, 'call', _4 => _4('///')])) { requestUrl = requestUrl.slice(2); } return `${method} ${requestUrl}`; } // the node types are missing a few properties which node's `urlToOptions` function spits out /** * Convert a URL object into a RequestOptions object. * * Copied from Node's internals (where it's used in http(s).request() and http(s).get()), modified only to use the * RequestOptions type above. * * See https://github.com/nodejs/node/blob/master/lib/internal/url.js. */ function urlToOptions(url) { const options = { protocol: url.protocol, hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[') ? url.hostname.slice(1, -1) : url.hostname, hash: url.hash, search: url.search, pathname: url.pathname, path: `${url.pathname || ''}${url.search || ''}`, href: url.href, }; if (url.port !== '') { options.port = Number(url.port); } if (url.username || url.password) { options.auth = `${url.username}:${url.password}`; } return options; } /** * Normalize inputs to `http(s).request()` and `http(s).get()`. * * Legal inputs to `http(s).request()` and `http(s).get()` can take one of ten forms: * [ RequestOptions | string | URL ], * [ RequestOptions | string | URL, RequestCallback ], * [ string | URL, RequestOptions ], and * [ string | URL, RequestOptions, RequestCallback ]. * * This standardizes to one of two forms: [ RequestOptions ] and [ RequestOptions, RequestCallback ]. A similar thing is * done as the first step of `http(s).request()` and `http(s).get()`; this just does it early so that we can interact * with the args in a standard way. * * @param requestArgs The inputs to `http(s).request()` or `http(s).get()`, as an array. * * @returns Equivalent args of the form [ RequestOptions ] or [ RequestOptions, RequestCallback ]. */ function normalizeRequestArgs( httpModule, requestArgs, ) { let callback, requestOptions; // pop off the callback, if there is one if (typeof requestArgs[requestArgs.length - 1] === 'function') { callback = requestArgs.pop() ; } // create a RequestOptions object of whatever's at index 0 if (typeof requestArgs[0] === 'string') { requestOptions = urlToOptions(new URL(requestArgs[0])); } else if (requestArgs[0] instanceof URL) { requestOptions = urlToOptions(requestArgs[0]); } else { requestOptions = requestArgs[0]; try { const parsed = new URL( requestOptions.path || '', `${requestOptions.protocol || 'http:'}//${requestOptions.hostname}`, ); requestOptions = { pathname: parsed.pathname, search: parsed.search, hash: parsed.hash, ...requestOptions, }; } catch (e) { // ignore } } // if the options were given separately from the URL, fold them in if (requestArgs.length === 2) { requestOptions = { ...requestOptions, ...requestArgs[1] }; } // Figure out the protocol if it's currently missing if (requestOptions.protocol === undefined) { // Worst case we end up populating protocol with undefined, which it already is /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */ // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it. // Because of that, we cannot rely on `httpModule` to provide us with valid protocol, // as it will always return `http`, even when using `https` module. // // See test/integrations/http.test.ts for more details on Node <=v8 protocol issue. if (NODE_VERSION.major > 8) { requestOptions.protocol = _optionalChain([(_optionalChain([httpModule, 'optionalAccess', _5 => _5.globalAgent]) ), 'optionalAccess', _6 => _6.protocol]) || _optionalChain([(requestOptions.agent ), 'optionalAccess', _7 => _7.protocol]) || _optionalChain([(requestOptions._defaultAgent ), 'optionalAccess', _8 => _8.protocol]); } else { requestOptions.protocol = _optionalChain([(requestOptions.agent ), 'optionalAccess', _9 => _9.protocol]) || _optionalChain([(requestOptions._defaultAgent ), 'optionalAccess', _10 => _10.protocol]) || _optionalChain([(_optionalChain([httpModule, 'optionalAccess', _11 => _11.globalAgent]) ), 'optionalAccess', _12 => _12.protocol]); } /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */ } // return args in standardized form if (callback) { return [requestOptions, callback]; } else { return [requestOptions]; } } function parseRequestOptions(requestOptions) { const protocol = requestOptions.protocol || ''; const hostname = requestOptions.hostname || requestOptions.host || ''; // Don't log standard :80 (http) and :443 (https) ports to reduce the noise // Also don't add port if the hostname already includes a port const port = !requestOptions.port || requestOptions.port === 80 || requestOptions.port === 443 || /^(.*):(\d+)$/.test(hostname) ? '' : `:${requestOptions.port}`; return { protocol, hostname, port }; } export { cleanSpanDescription, extractRawUrl, extractUrl, normalizeRequestArgs, urlToOptions }; //# sourceMappingURL=http.js.map