http.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. var {
  2. _optionalChain
  3. } = require('@sentry/utils');
  4. Object.defineProperty(exports, '__esModule', { value: true });
  5. const url = require('url');
  6. const nodeVersion = require('../../nodeVersion.js');
  7. /**
  8. * Assembles a URL that's passed to the users to filter on.
  9. * It can include raw (potentially PII containing) data, which we'll allow users to access to filter
  10. * but won't include in spans or breadcrumbs.
  11. *
  12. * @param requestOptions RequestOptions object containing the component parts for a URL
  13. * @returns Fully-formed URL
  14. */
  15. // TODO (v8): This function should include auth, query and fragment (it's breaking, so we need to wait for v8)
  16. function extractRawUrl(requestOptions) {
  17. const { protocol, hostname, port } = parseRequestOptions(requestOptions);
  18. const path = requestOptions.path ? requestOptions.path : '/';
  19. return `${protocol}//${hostname}${port}${path}`;
  20. }
  21. /**
  22. * Assemble a URL to be used for breadcrumbs and spans.
  23. *
  24. * @param requestOptions RequestOptions object containing the component parts for a URL
  25. * @returns Fully-formed URL
  26. */
  27. function extractUrl(requestOptions) {
  28. const { protocol, hostname, port } = parseRequestOptions(requestOptions);
  29. const path = requestOptions.pathname || '/';
  30. // always filter authority, see https://develop.sentry.dev/sdk/data-handling/#structuring-data
  31. const authority = requestOptions.auth ? redactAuthority(requestOptions.auth) : '';
  32. return `${protocol}//${authority}${hostname}${port}${path}`;
  33. }
  34. function redactAuthority(auth) {
  35. const [user, password] = auth.split(':');
  36. return `${user ? '[Filtered]' : ''}:${password ? '[Filtered]' : ''}@`;
  37. }
  38. /**
  39. * Handle various edge cases in the span description (for spans representing http(s) requests).
  40. *
  41. * @param description current `description` property of the span representing the request
  42. * @param requestOptions Configuration data for the request
  43. * @param Request Request object
  44. *
  45. * @returns The cleaned description
  46. */
  47. function cleanSpanDescription(
  48. description,
  49. requestOptions,
  50. request,
  51. ) {
  52. // nothing to clean
  53. if (!description) {
  54. return description;
  55. }
  56. // eslint-disable-next-line prefer-const
  57. let [method, requestUrl] = description.split(' ');
  58. // superagent sticks the protocol in a weird place (we check for host because if both host *and* protocol are missing,
  59. // we're likely dealing with an internal route and this doesn't apply)
  60. if (requestOptions.host && !requestOptions.protocol) {
  61. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
  62. requestOptions.protocol = _optionalChain([(request ), 'optionalAccess', _ => _.agent, 'optionalAccess', _2 => _2.protocol]); // worst comes to worst, this is undefined and nothing changes
  63. // This URL contains the filtered authority ([filtered]:[filtered]@example.com) but no fragment or query params
  64. requestUrl = extractUrl(requestOptions);
  65. }
  66. // internal routes can end up starting with a triple slash rather than a single one
  67. if (_optionalChain([requestUrl, 'optionalAccess', _3 => _3.startsWith, 'call', _4 => _4('///')])) {
  68. requestUrl = requestUrl.slice(2);
  69. }
  70. return `${method} ${requestUrl}`;
  71. }
  72. // the node types are missing a few properties which node's `urlToOptions` function spits out
  73. /**
  74. * Convert a URL object into a RequestOptions object.
  75. *
  76. * Copied from Node's internals (where it's used in http(s).request() and http(s).get()), modified only to use the
  77. * RequestOptions type above.
  78. *
  79. * See https://github.com/nodejs/node/blob/master/lib/internal/url.js.
  80. */
  81. function urlToOptions(url) {
  82. const options = {
  83. protocol: url.protocol,
  84. hostname:
  85. typeof url.hostname === 'string' && url.hostname.startsWith('[') ? url.hostname.slice(1, -1) : url.hostname,
  86. hash: url.hash,
  87. search: url.search,
  88. pathname: url.pathname,
  89. path: `${url.pathname || ''}${url.search || ''}`,
  90. href: url.href,
  91. };
  92. if (url.port !== '') {
  93. options.port = Number(url.port);
  94. }
  95. if (url.username || url.password) {
  96. options.auth = `${url.username}:${url.password}`;
  97. }
  98. return options;
  99. }
  100. /**
  101. * Normalize inputs to `http(s).request()` and `http(s).get()`.
  102. *
  103. * Legal inputs to `http(s).request()` and `http(s).get()` can take one of ten forms:
  104. * [ RequestOptions | string | URL ],
  105. * [ RequestOptions | string | URL, RequestCallback ],
  106. * [ string | URL, RequestOptions ], and
  107. * [ string | URL, RequestOptions, RequestCallback ].
  108. *
  109. * This standardizes to one of two forms: [ RequestOptions ] and [ RequestOptions, RequestCallback ]. A similar thing is
  110. * done as the first step of `http(s).request()` and `http(s).get()`; this just does it early so that we can interact
  111. * with the args in a standard way.
  112. *
  113. * @param requestArgs The inputs to `http(s).request()` or `http(s).get()`, as an array.
  114. *
  115. * @returns Equivalent args of the form [ RequestOptions ] or [ RequestOptions, RequestCallback ].
  116. */
  117. function normalizeRequestArgs(
  118. httpModule,
  119. requestArgs,
  120. ) {
  121. let callback, requestOptions;
  122. // pop off the callback, if there is one
  123. if (typeof requestArgs[requestArgs.length - 1] === 'function') {
  124. callback = requestArgs.pop() ;
  125. }
  126. // create a RequestOptions object of whatever's at index 0
  127. if (typeof requestArgs[0] === 'string') {
  128. requestOptions = urlToOptions(new url.URL(requestArgs[0]));
  129. } else if (requestArgs[0] instanceof url.URL) {
  130. requestOptions = urlToOptions(requestArgs[0]);
  131. } else {
  132. requestOptions = requestArgs[0];
  133. try {
  134. const parsed = new url.URL(
  135. requestOptions.path || '',
  136. `${requestOptions.protocol || 'http:'}//${requestOptions.hostname}`,
  137. );
  138. requestOptions = {
  139. pathname: parsed.pathname,
  140. search: parsed.search,
  141. hash: parsed.hash,
  142. ...requestOptions,
  143. };
  144. } catch (e) {
  145. // ignore
  146. }
  147. }
  148. // if the options were given separately from the URL, fold them in
  149. if (requestArgs.length === 2) {
  150. requestOptions = { ...requestOptions, ...requestArgs[1] };
  151. }
  152. // Figure out the protocol if it's currently missing
  153. if (requestOptions.protocol === undefined) {
  154. // Worst case we end up populating protocol with undefined, which it already is
  155. /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
  156. // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it.
  157. // Because of that, we cannot rely on `httpModule` to provide us with valid protocol,
  158. // as it will always return `http`, even when using `https` module.
  159. //
  160. // See test/integrations/http.test.ts for more details on Node <=v8 protocol issue.
  161. if (nodeVersion.NODE_VERSION.major > 8) {
  162. requestOptions.protocol =
  163. _optionalChain([(_optionalChain([httpModule, 'optionalAccess', _5 => _5.globalAgent]) ), 'optionalAccess', _6 => _6.protocol]) ||
  164. _optionalChain([(requestOptions.agent ), 'optionalAccess', _7 => _7.protocol]) ||
  165. _optionalChain([(requestOptions._defaultAgent ), 'optionalAccess', _8 => _8.protocol]);
  166. } else {
  167. requestOptions.protocol =
  168. _optionalChain([(requestOptions.agent ), 'optionalAccess', _9 => _9.protocol]) ||
  169. _optionalChain([(requestOptions._defaultAgent ), 'optionalAccess', _10 => _10.protocol]) ||
  170. _optionalChain([(_optionalChain([httpModule, 'optionalAccess', _11 => _11.globalAgent]) ), 'optionalAccess', _12 => _12.protocol]);
  171. }
  172. /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any */
  173. }
  174. // return args in standardized form
  175. if (callback) {
  176. return [requestOptions, callback];
  177. } else {
  178. return [requestOptions];
  179. }
  180. }
  181. function parseRequestOptions(requestOptions)
  182. {
  183. const protocol = requestOptions.protocol || '';
  184. const hostname = requestOptions.hostname || requestOptions.host || '';
  185. // Don't log standard :80 (http) and :443 (https) ports to reduce the noise
  186. // Also don't add port if the hostname already includes a port
  187. const port =
  188. !requestOptions.port || requestOptions.port === 80 || requestOptions.port === 443 || /^(.*):(\d+)$/.test(hostname)
  189. ? ''
  190. : `:${requestOptions.port}`;
  191. return { protocol, hostname, port };
  192. }
  193. exports.cleanSpanDescription = cleanSpanDescription;
  194. exports.extractRawUrl = extractRawUrl;
  195. exports.extractUrl = extractUrl;
  196. exports.normalizeRequestArgs = normalizeRequestArgs;
  197. exports.urlToOptions = urlToOptions;
  198. //# sourceMappingURL=http.js.map