226 lines
8.3 KiB
JavaScript
226 lines
8.3 KiB
JavaScript
var {
|
|
_optionalChain
|
|
} = require('@sentry/utils');
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
const url = require('url');
|
|
const nodeVersion = require('../../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.URL(requestArgs[0]));
|
|
} else if (requestArgs[0] instanceof url.URL) {
|
|
requestOptions = urlToOptions(requestArgs[0]);
|
|
} else {
|
|
requestOptions = requestArgs[0];
|
|
|
|
try {
|
|
const parsed = new url.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 (nodeVersion.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 };
|
|
}
|
|
|
|
exports.cleanSpanDescription = cleanSpanDescription;
|
|
exports.extractRawUrl = extractRawUrl;
|
|
exports.extractUrl = extractUrl;
|
|
exports.normalizeRequestArgs = normalizeRequestArgs;
|
|
exports.urlToOptions = urlToOptions;
|
|
//# sourceMappingURL=http.js.map
|