123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- Object.defineProperty(exports, '__esModule', { value: true });
- const cookie = require('./cookie.js');
- const debugBuild = require('./debug-build.js');
- const is = require('./is.js');
- const logger = require('./logger.js');
- const normalize = require('./normalize.js');
- const url = require('./url.js');
- const DEFAULT_INCLUDES = {
- ip: false,
- request: true,
- transaction: true,
- user: true,
- };
- const DEFAULT_REQUEST_INCLUDES = ['cookies', 'data', 'headers', 'method', 'query_string', 'url'];
- const DEFAULT_USER_INCLUDES = ['id', 'username', 'email'];
- /**
- * Sets parameterized route as transaction name e.g.: `GET /users/:id`
- * Also adds more context data on the transaction from the request
- */
- function addRequestDataToTransaction(
- transaction,
- req,
- deps,
- ) {
- if (!transaction) return;
- // eslint-disable-next-line deprecation/deprecation
- if (!transaction.metadata.source || transaction.metadata.source === 'url') {
- // Attempt to grab a parameterized route off of the request
- const [name, source] = extractPathForTransaction(req, { path: true, method: true });
- transaction.updateName(name);
- // TODO: SEMANTIC_ATTRIBUTE_SENTRY_SOURCE is in core, align this once we merge utils & core
- // eslint-disable-next-line deprecation/deprecation
- transaction.setMetadata({ source });
- }
- transaction.setAttribute('url', req.originalUrl || req.url);
- if (req.baseUrl) {
- transaction.setAttribute('baseUrl', req.baseUrl);
- }
- // TODO: We need to rewrite this to a flat format?
- // eslint-disable-next-line deprecation/deprecation
- transaction.setData('query', extractQueryParams(req, deps));
- }
- /**
- * Extracts a complete and parameterized path from the request object and uses it to construct transaction name.
- * If the parameterized transaction name cannot be extracted, we fall back to the raw URL.
- *
- * Additionally, this function determines and returns the transaction name source
- *
- * eg. GET /mountpoint/user/:id
- *
- * @param req A request object
- * @param options What to include in the transaction name (method, path, or a custom route name to be
- * used instead of the request's route)
- *
- * @returns A tuple of the fully constructed transaction name [0] and its source [1] (can be either 'route' or 'url')
- */
- function extractPathForTransaction(
- req,
- options = {},
- ) {
- const method = req.method && req.method.toUpperCase();
- let path = '';
- let source = 'url';
- // Check to see if there's a parameterized route we can use (as there is in Express)
- if (options.customRoute || req.route) {
- path = options.customRoute || `${req.baseUrl || ''}${req.route && req.route.path}`;
- source = 'route';
- }
- // Otherwise, just take the original URL
- else if (req.originalUrl || req.url) {
- path = url.stripUrlQueryAndFragment(req.originalUrl || req.url || '');
- }
- let name = '';
- if (options.method && method) {
- name += method;
- }
- if (options.method && options.path) {
- name += ' ';
- }
- if (options.path && path) {
- name += path;
- }
- return [name, source];
- }
- /** JSDoc */
- function extractTransaction(req, type) {
- switch (type) {
- case 'path': {
- return extractPathForTransaction(req, { path: true })[0];
- }
- case 'handler': {
- return (req.route && req.route.stack && req.route.stack[0] && req.route.stack[0].name) || '<anonymous>';
- }
- case 'methodPath':
- default: {
- // if exist _reconstructedRoute return that path instead of route.path
- const customRoute = req._reconstructedRoute ? req._reconstructedRoute : undefined;
- return extractPathForTransaction(req, { path: true, method: true, customRoute })[0];
- }
- }
- }
- /** JSDoc */
- function extractUserData(
- user
- ,
- keys,
- ) {
- const extractedUser = {};
- const attributes = Array.isArray(keys) ? keys : DEFAULT_USER_INCLUDES;
- attributes.forEach(key => {
- if (user && key in user) {
- extractedUser[key] = user[key];
- }
- });
- return extractedUser;
- }
- /**
- * Normalize data from the request object, accounting for framework differences.
- *
- * @param req The request object from which to extract data
- * @param options.include An optional array of keys to include in the normalized data. Defaults to
- * DEFAULT_REQUEST_INCLUDES if not provided.
- * @param options.deps Injected, platform-specific dependencies
- * @returns An object containing normalized request data
- */
- function extractRequestData(
- req,
- options
- ,
- ) {
- const { include = DEFAULT_REQUEST_INCLUDES, deps } = options || {};
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const requestData = {};
- // headers:
- // node, express, koa, nextjs: req.headers
- const headers = (req.headers || {})
- ;
- // method:
- // node, express, koa, nextjs: req.method
- const method = req.method;
- // host:
- // express: req.hostname in > 4 and req.host in < 4
- // koa: req.host
- // node, nextjs: req.headers.host
- // Express 4 mistakenly strips off port number from req.host / req.hostname so we can't rely on them
- // See: https://github.com/expressjs/express/issues/3047#issuecomment-236653223
- // Also: https://github.com/getsentry/sentry-javascript/issues/1917
- const host = headers.host || req.hostname || req.host || '<no host>';
- // protocol:
- // node, nextjs: <n/a>
- // express, koa: req.protocol
- const protocol = req.protocol === 'https' || (req.socket && req.socket.encrypted) ? 'https' : 'http';
- // url (including path and query string):
- // node, express: req.originalUrl
- // koa, nextjs: req.url
- const originalUrl = req.originalUrl || req.url || '';
- // absolute url
- const absoluteUrl = originalUrl.startsWith(protocol) ? originalUrl : `${protocol}://${host}${originalUrl}`;
- include.forEach(key => {
- switch (key) {
- case 'headers': {
- requestData.headers = headers;
- // Remove the Cookie header in case cookie data should not be included in the event
- if (!include.includes('cookies')) {
- delete (requestData.headers ).cookie;
- }
- break;
- }
- case 'method': {
- requestData.method = method;
- break;
- }
- case 'url': {
- requestData.url = absoluteUrl;
- break;
- }
- case 'cookies': {
- // cookies:
- // node, express, koa: req.headers.cookie
- // vercel, sails.js, express (w/ cookie middleware), nextjs: req.cookies
- requestData.cookies =
- // TODO (v8 / #5257): We're only sending the empty object for backwards compatibility, so the last bit can
- // come off in v8
- req.cookies || (headers.cookie && cookie.parseCookie(headers.cookie)) || {};
- break;
- }
- case 'query_string': {
- // query string:
- // node: req.url (raw)
- // express, koa, nextjs: req.query
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- requestData.query_string = extractQueryParams(req, deps);
- break;
- }
- case 'data': {
- if (method === 'GET' || method === 'HEAD') {
- break;
- }
- // body data:
- // express, koa, nextjs: req.body
- //
- // when using node by itself, you have to read the incoming stream(see
- // https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know
- // where they're going to store the final result, so they'll have to capture this data themselves
- if (req.body !== undefined) {
- requestData.data = is.isString(req.body) ? req.body : JSON.stringify(normalize.normalize(req.body));
- }
- break;
- }
- default: {
- if ({}.hasOwnProperty.call(req, key)) {
- requestData[key] = (req )[key];
- }
- }
- }
- });
- return requestData;
- }
- /**
- * Add data from the given request to the given event
- *
- * @param event The event to which the request data will be added
- * @param req Request object
- * @param options.include Flags to control what data is included
- * @param options.deps Injected platform-specific dependencies
- * @returns The mutated `Event` object
- */
- function addRequestDataToEvent(
- event,
- req,
- options,
- ) {
- const include = {
- ...DEFAULT_INCLUDES,
- ...(options && options.include),
- };
- if (include.request) {
- const extractedRequestData = Array.isArray(include.request)
- ? extractRequestData(req, { include: include.request, deps: options && options.deps })
- : extractRequestData(req, { deps: options && options.deps });
- event.request = {
- ...event.request,
- ...extractedRequestData,
- };
- }
- if (include.user) {
- const extractedUser = req.user && is.isPlainObject(req.user) ? extractUserData(req.user, include.user) : {};
- if (Object.keys(extractedUser).length) {
- event.user = {
- ...event.user,
- ...extractedUser,
- };
- }
- }
- // client ip:
- // node, nextjs: req.socket.remoteAddress
- // express, koa: req.ip
- if (include.ip) {
- const ip = req.ip || (req.socket && req.socket.remoteAddress);
- if (ip) {
- event.user = {
- ...event.user,
- ip_address: ip,
- };
- }
- }
- if (include.transaction && !event.transaction) {
- // TODO do we even need this anymore?
- // TODO make this work for nextjs
- event.transaction = extractTransaction(req, include.transaction);
- }
- return event;
- }
- function extractQueryParams(
- req,
- deps,
- ) {
- // url (including path and query string):
- // node, express: req.originalUrl
- // koa, nextjs: req.url
- let originalUrl = req.originalUrl || req.url || '';
- if (!originalUrl) {
- return;
- }
- // The `URL` constructor can't handle internal URLs of the form `/some/path/here`, so stick a dummy protocol and
- // hostname on the beginning. Since the point here is just to grab the query string, it doesn't matter what we use.
- if (originalUrl.startsWith('/')) {
- originalUrl = `http://dogs.are.great${originalUrl}`;
- }
- try {
- return (
- req.query ||
- (typeof URL !== 'undefined' && new URL(originalUrl).search.slice(1)) ||
- // In Node 8, `URL` isn't in the global scope, so we have to use the built-in module from Node
- (deps && deps.url && deps.url.parse(originalUrl).query) ||
- undefined
- );
- } catch (e2) {
- return undefined;
- }
- }
- /**
- * Transforms a `Headers` object that implements the `Web Fetch API` (https://developer.mozilla.org/en-US/docs/Web/API/Headers) into a simple key-value dict.
- * The header keys will be lower case: e.g. A "Content-Type" header will be stored as "content-type".
- */
- // TODO(v8): Make this function return undefined when the extraction fails.
- function winterCGHeadersToDict(winterCGHeaders) {
- const headers = {};
- try {
- winterCGHeaders.forEach((value, key) => {
- if (typeof value === 'string') {
- // We check that value is a string even though it might be redundant to make sure prototype pollution is not possible.
- headers[key] = value;
- }
- });
- } catch (e) {
- debugBuild.DEBUG_BUILD &&
- logger.logger.warn('Sentry failed extracting headers from a request object. If you see this, please file an issue.');
- }
- return headers;
- }
- /**
- * Converts a `Request` object that implements the `Web Fetch API` (https://developer.mozilla.org/en-US/docs/Web/API/Headers) into the format that the `RequestData` integration understands.
- */
- function winterCGRequestToRequestData(req) {
- const headers = winterCGHeadersToDict(req.headers);
- return {
- method: req.method,
- url: req.url,
- headers,
- };
- }
- exports.DEFAULT_USER_INCLUDES = DEFAULT_USER_INCLUDES;
- exports.addRequestDataToEvent = addRequestDataToEvent;
- exports.addRequestDataToTransaction = addRequestDataToTransaction;
- exports.extractPathForTransaction = extractPathForTransaction;
- exports.extractRequestData = extractRequestData;
- exports.winterCGHeadersToDict = winterCGHeadersToDict;
- exports.winterCGRequestToRequestData = winterCGRequestToRequestData;
- //# sourceMappingURL=requestdata.js.map
|