@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
217 lines (214 loc) • 9.16 kB
JavaScript
import { getIsolationScope } from '../currentScopes.js';
import { defineIntegration } from '../integration.js';
import { SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS } from '../semanticAttributes.js';
import { parseCookieHeader, cookiePairsToRecord } from '../utils/cookie.js';
import { SENSITIVE_COOKIE_NAME_SNIPPETS } from '../utils/data-collection/filtering-snippets.js';
import { filterKeyValueData } from '../utils/data-collection/filterKeyValueData.js';
import { isLocalhostRequest } from '../utils/localhost.js';
import { filterQueryParams } from '../utils/data-collection/filterQueryParams.js';
import { filterUrlQuery } from '../utils/data-collection/filterUrlQuery.js';
import { filterCookiePairs, httpHeadersToSpanAttributes } from '../utils/request.js';
import { getUrlQuery } from '../utils/url.js';
import { getClientIPAddress, ipHeaderNames } from '../vendor/getIpAddress.js';
import { safeSetSpanJSONAttributes } from '../tracing/spans/captureSpan.js';
import { URL_FULL, URL_QUERY } from '@sentry/conventions/attributes';
const INTEGRATION_NAME = "RequestData";
const _requestDataIntegration = ((options = {}) => {
function resolveRequestDataOptions(client) {
const dataCollection = client.getDataCollectionOptions();
const include = {
// oxlint-disable-next-line typescript/no-deprecated
cookies: options.include?.cookies ?? dataCollection.cookies !== false,
// Always attach body data that's already on the scope — dataCollection.httpBodies gates write-time, not read-time
// oxlint-disable-next-line typescript/no-deprecated
data: options.include?.data ?? true,
// oxlint-disable-next-line typescript/no-deprecated
headers: options.include?.headers ?? dataCollection.httpHeaders.request !== false,
// oxlint-disable-next-line typescript/no-deprecated
ip: options.include?.ip ?? dataCollection.userInfo,
// oxlint-disable-next-line typescript/no-deprecated
query_string: options.include?.query_string ?? dataCollection.urlQueryParams !== false,
// No dataCollection equivalent — URL is always included
// oxlint-disable-next-line typescript/no-deprecated
url: options.include?.url ?? true
};
return {
include,
dataCollection: {
...dataCollection,
cookies: resolveFilteringBehavior(include.cookies, dataCollection.cookies),
httpHeaders: {
...dataCollection.httpHeaders,
request: resolveFilteringBehavior(include.headers, dataCollection.httpHeaders.request)
},
urlQueryParams: resolveFilteringBehavior(include.query_string, dataCollection.urlQueryParams)
}
};
}
return {
name: INTEGRATION_NAME,
processEvent(event, _hint, client) {
const { sdkProcessingMetadata = {} } = event;
const { normalizedRequest, ipAddress } = sdkProcessingMetadata;
if (!normalizedRequest) {
return event;
}
const { include, dataCollection } = resolveRequestDataOptions(client);
addNormalizedRequestDataToEvent(event, normalizedRequest, { ipAddress }, include, dataCollection);
return event;
},
processSpan(span) {
const { user, sdkProcessingMetadata } = getIsolationScope().getScopeData();
safeSetSpanJSONAttributes(span, {
"sentry.is_localhost": isLocalhostSpan(sdkProcessingMetadata, user.ip_address)
});
},
processSegmentSpan(span, client) {
const { sdkProcessingMetadata = {} } = getIsolationScope().getScopeData();
const { normalizedRequest, ipAddress } = sdkProcessingMetadata;
if (!normalizedRequest) {
return;
}
const { include, dataCollection } = resolveRequestDataOptions(client);
addNormalizedRequestDataToSpan(span, normalizedRequest, ipAddress, include, dataCollection);
}
};
});
const localhostByRequest = /* @__PURE__ */ new WeakMap();
function isLocalhostSpan(sdkProcessingMetadata, scopeUserIpAddress) {
const { normalizedRequest, ipAddress } = sdkProcessingMetadata;
if (!normalizedRequest) {
return isLocalhostRequest(void 0, ipAddress || scopeUserIpAddress);
}
const cached = localhostByRequest.get(normalizedRequest);
if (cached !== void 0) {
return cached;
}
const headers = normalizedRequest.headers;
const clientIpAddress = headers && getClientIPAddress(headers) || ipAddress || scopeUserIpAddress;
const isLocalhost = isLocalhostRequest(normalizedRequest, clientIpAddress);
localhostByRequest.set(normalizedRequest, isLocalhost);
return isLocalhost;
}
const requestDataIntegration = defineIntegration(_requestDataIntegration);
function addNormalizedRequestDataToEvent(event, req, additionalData, include, dataCollection) {
const requestData = extractNormalizedRequestData(req, include);
if (requestData.cookies) {
requestData.cookies = filterKeyValueData(
requestData.cookies,
dataCollection.cookies,
SENSITIVE_COOKIE_NAME_SNIPPETS
);
}
if (requestData.headers) {
requestData.headers = filterKeyValueData(requestData.headers, dataCollection.httpHeaders.request);
}
if (requestData.query_string) {
requestData.query_string = normalizeAndFilterQueryString(requestData.query_string, dataCollection.urlQueryParams);
}
if (requestData.url) {
requestData.url = filterUrlQuery(requestData.url, dataCollection.urlQueryParams);
}
event.request = {
...event.request,
...requestData
};
if (include.ip) {
const ip = req.headers && getClientIPAddress(req.headers) || additionalData.ipAddress;
if (ip) {
event.user = {
...event.user,
ip_address: ip
};
}
}
}
function addNormalizedRequestDataToSpan(span, normalizedRequest, ipAddress, include, dataCollection) {
const requestData = extractNormalizedRequestData(normalizedRequest, include);
const attributes = {};
if (requestData.url) {
attributes[URL_FULL] = filterUrlQuery(requestData.url, dataCollection.urlQueryParams);
}
if (requestData.method) {
attributes["http.request.method"] = requestData.method;
}
if (requestData.query_string) {
attributes[URL_QUERY] = normalizeAndFilterQueryString(requestData.query_string, dataCollection.urlQueryParams);
}
safeSetSpanJSONAttributes(span, attributes);
if (include.cookies) {
const cookieHeader = normalizedRequest.headers?.cookie;
const cookiePairs = normalizedRequest.cookies ? Object.entries(normalizedRequest.cookies) : cookieHeader ? parseCookieHeader(cookieHeader, "cookie") : [];
if (cookiePairs.length > 0) {
safeSetSpanJSONAttributes(span, {
"http.request.header.cookie": filterCookiePairs(cookiePairs, dataCollection.cookies)
});
}
}
if (requestData.headers) {
const headerAttributes = httpHeadersToSpanAttributes(requestData.headers, dataCollection, "request");
safeSetSpanJSONAttributes(span, headerAttributes);
}
if (requestData.data != null) {
const serialized = typeof requestData.data === "string" ? requestData.data : JSON.stringify(requestData.data);
if (serialized) {
safeSetSpanJSONAttributes(span, { "http.request.body.data": serialized });
}
}
if (include.ip) {
const ip = normalizedRequest.headers && getClientIPAddress(normalizedRequest.headers) || ipAddress || void 0;
if (ip) {
safeSetSpanJSONAttributes(span, { [SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: ip });
}
}
}
function extractNormalizedRequestData(normalizedRequest, include) {
const requestData = {};
const headers = { ...normalizedRequest.headers };
if (include.headers) {
requestData.headers = headers;
if (!include.cookies) {
delete headers.cookie;
}
if (!include.ip) {
const ipHeaderNamesLower = new Set(ipHeaderNames.map((name) => name.toLowerCase()));
for (const key of Object.keys(headers)) {
if (ipHeaderNamesLower.has(key.toLowerCase())) {
delete headers[key];
}
}
}
}
requestData.method = normalizedRequest.method;
if (include.url) {
requestData.url = normalizedRequest.url;
}
if (include.cookies) {
const cookies = normalizedRequest.cookies || (headers?.cookie ? cookiePairsToRecord(parseCookieHeader(headers.cookie, "cookie")) : void 0);
requestData.cookies = cookies || {};
}
if (include.query_string) {
requestData.query_string = normalizedRequest.query_string;
}
if (include.data) {
requestData.data = normalizedRequest.data;
}
return requestData;
}
function resolveFilteringBehavior(isIncluded, behavior) {
return isIncluded && behavior === false ? true : behavior;
}
function normalizeAndFilterQueryString(queryString, behavior) {
const normalized = normalizeQueryString(queryString);
return normalized ? filterQueryParams(normalized, behavior) : void 0;
}
function normalizeQueryString(queryString) {
if (typeof queryString === "string") {
return getUrlQuery(queryString);
}
const pairs = Array.isArray(queryString) ? queryString : Object.entries(queryString);
const normalized = new URLSearchParams(pairs).toString();
return normalized || void 0;
}
export { requestDataIntegration };
//# sourceMappingURL=requestdata.js.map