@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
236 lines (232 loc) • 8.67 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const debugBuild = require('../debug-build.js');
const debugLogger = require('./debug-logger.js');
const defaultPiiToCollectionOptions = require('./data-collection/defaultPiiToCollectionOptions.js');
const filteringSnippets = require('./data-collection/filtering-snippets.js');
const filterKeyValueData = require('./data-collection/filterKeyValueData.js');
const timer = require('./timer.js');
const MAX_BODY_BYTE_LENGTH = 1024 * 1024;
const TEXT_CONTENT_TYPES = [
"text/",
"application/json",
"application/x-www-form-urlencoded",
"application/xml",
"application/graphql"
];
function getMaxBodyByteLength(maxRequestBodySize) {
if (maxRequestBodySize === "small") return 1e3;
if (maxRequestBodySize === "medium") return 1e4;
return MAX_BODY_BYTE_LENGTH;
}
function winterCGHeadersToDict(winterCGHeaders) {
const headers = {};
try {
winterCGHeaders.forEach((value, key) => {
if (typeof value === "string") {
headers[key] = value;
}
});
} catch {
}
return headers;
}
function headersToDict(reqHeaders) {
const headers = /* @__PURE__ */ Object.create(null);
try {
Object.entries(reqHeaders).forEach(([key, value]) => {
if (typeof value === "string") {
headers[key] = value;
} else if (typeof value === "number") {
headers[key] = String(value);
}
});
} catch {
}
return headers;
}
function winterCGRequestToRequestData(req) {
const headers = winterCGHeadersToDict(req.headers);
return {
method: req.method,
url: req.url,
query_string: extractQueryParamsFromUrl(req.url),
headers
// TODO: Can we extract body data from the request?
};
}
function isTextualContentType(contentType) {
if (!contentType) {
return false;
}
const lowerContentType = contentType.toLowerCase();
return TEXT_CONTENT_TYPES.some((type) => lowerContentType.includes(type));
}
async function captureBodyFromWinterCGRequest(request, isolationScope, maxRequestBodySize) {
try {
const contentType = request.headers.get("content-type");
if (!isTextualContentType(contentType)) {
debugBuild.DEBUG_BUILD && debugLogger.debug.log("Skipping body capture for non-textual content type:", contentType);
return;
}
if (!request.body) {
return;
}
const contentLength = request.headers.get("content-length");
const maxBodySize = getMaxBodyByteLength(maxRequestBodySize);
if (contentLength) {
const length = parseInt(contentLength, 10);
if (!isNaN(length) && length > MAX_BODY_BYTE_LENGTH) {
debugBuild.DEBUG_BUILD && debugLogger.debug.log("Skipping body capture: body too large", length);
return;
}
}
const clonedRequest = request.clone();
const bodyPromise = clonedRequest.text();
const timeoutPromise = new Promise((resolve) => {
timer.safeUnref(setTimeout(() => resolve(null), 2e3));
});
const body = await Promise.race([bodyPromise, timeoutPromise]);
if (body === null) {
debugBuild.DEBUG_BUILD && debugLogger.debug.log("Timeout reading request body");
return;
}
if (!body) {
return;
}
const encoder = new TextEncoder();
const bytes = encoder.encode(body);
const bodyByteLength = bytes.length;
let truncatedBody;
if (bodyByteLength > maxBodySize) {
const decoder = new TextDecoder();
truncatedBody = `${decoder.decode(bytes.slice(0, maxBodySize - 3))}...`;
} else {
truncatedBody = body;
}
isolationScope.setSDKProcessingMetadata({ normalizedRequest: { data: truncatedBody } });
debugBuild.DEBUG_BUILD && debugLogger.debug.log("Captured request body:", bodyByteLength, "bytes");
} catch (error) {
debugBuild.DEBUG_BUILD && debugLogger.debug.error("Error capturing request body:", error);
}
}
function httpRequestToRequestData(request) {
const headers = request.headers || {};
const forwardedHost = typeof headers["x-forwarded-host"] === "string" ? headers["x-forwarded-host"] : void 0;
const host = forwardedHost || (typeof headers.host === "string" ? headers.host : void 0);
const forwardedProto = typeof headers["x-forwarded-proto"] === "string" ? headers["x-forwarded-proto"] : void 0;
const protocol = forwardedProto || request.protocol || (request.socket?.encrypted ? "https" : "http");
const url = request.url || "";
const absoluteUrl = getAbsoluteUrl({
url,
host,
protocol
});
const data = request.body || void 0;
const cookies = request.cookies;
return {
url: absoluteUrl,
method: request.method,
query_string: extractQueryParamsFromUrl(url),
headers: headersToDict(headers),
cookies,
data
};
}
function getAbsoluteUrl({
url,
protocol,
host
}) {
if (url?.startsWith("http")) {
return url;
}
if (url && host) {
return `${protocol}://${host}${url}`;
}
return void 0;
}
function httpHeadersToSpanAttributes(headers, dataCollection = false, lifecycle = "request") {
const resolvedDataCollection = typeof dataCollection === "boolean" ? defaultPiiToCollectionOptions.defaultPiiToCollectionOptions(dataCollection) : dataCollection;
const headerBehavior = lifecycle === "request" ? resolvedDataCollection.httpHeaders.request : resolvedDataCollection.httpHeaders.response;
const cookieBehavior = resolvedDataCollection.cookies;
const prefix = `http.${lifecycle}.header.`;
const spanAttributes = {};
try {
const regularHeaders = {};
for (const [key, value] of Object.entries(headers)) {
if (value == null) {
continue;
}
const lowerKey = key.toLowerCase();
const isCookieHeader = lowerKey === "cookie" || lowerKey === "set-cookie";
if (isCookieHeader) {
if (cookieBehavior === false) {
continue;
}
if (typeof value === "string" && value !== "") {
const parsed = parseCookieHeader(value, lowerKey === "set-cookie");
const filtered = filterKeyValueData.filterKeyValueData(parsed, cookieBehavior, filteringSnippets.SENSITIVE_COOKIE_NAME_SNIPPETS);
for (const [cookieKey, cookieValue] of Object.entries(filtered)) {
spanAttributes[`${prefix}${normalizeAttributeKey(lowerKey)}.${normalizeAttributeKey(cookieKey)}`] = cookieValue;
}
} else {
spanAttributes[`${prefix}${normalizeAttributeKey(lowerKey)}`] = filteringSnippets.FILTERED_VALUE;
}
} else {
if (headerBehavior === false) {
continue;
}
if (Array.isArray(value)) {
regularHeaders[lowerKey] = value.map((v) => v != null ? String(v) : v).join(";");
} else if (typeof value === "string") {
regularHeaders[lowerKey] = value;
}
}
}
if (headerBehavior !== false) {
const filtered = filterKeyValueData.filterKeyValueData(regularHeaders, headerBehavior);
for (const [headerKey, headerValue] of Object.entries(filtered)) {
spanAttributes[`${prefix}${normalizeAttributeKey(headerKey)}`] = headerValue;
}
}
} catch {
}
return spanAttributes;
}
function normalizeAttributeKey(key) {
return key.replace(/-/g, "_");
}
function parseCookieHeader(value, isSetCookie) {
const semicolonIndex = value.indexOf(";");
const cookieString = isSetCookie && semicolonIndex !== -1 ? value.substring(0, semicolonIndex) : value;
const cookies = isSetCookie ? [cookieString] : cookieString.split("; ");
const result = {};
for (const cookie of cookies) {
const equalSignIndex = cookie.indexOf("=");
const cookieKey = (equalSignIndex !== -1 ? cookie.substring(0, equalSignIndex) : cookie).toLowerCase();
const cookieValue = equalSignIndex !== -1 ? cookie.substring(equalSignIndex + 1) : "";
result[cookieKey] = cookieValue;
}
return result;
}
function extractQueryParamsFromUrl(url) {
if (!url) {
return;
}
try {
const queryParams = new URL(url, "http://s.io").search.slice(1);
return queryParams.length ? queryParams : void 0;
} catch {
return void 0;
}
}
exports.MAX_BODY_BYTE_LENGTH = MAX_BODY_BYTE_LENGTH;
exports.captureBodyFromWinterCGRequest = captureBodyFromWinterCGRequest;
exports.extractQueryParamsFromUrl = extractQueryParamsFromUrl;
exports.getMaxBodyByteLength = getMaxBodyByteLength;
exports.headersToDict = headersToDict;
exports.httpHeadersToSpanAttributes = httpHeadersToSpanAttributes;
exports.httpRequestToRequestData = httpRequestToRequestData;
exports.winterCGHeadersToDict = winterCGHeadersToDict;
exports.winterCGRequestToRequestData = winterCGRequestToRequestData;
//# sourceMappingURL=request.js.map