Skip to content

Commit 06d573f

Browse files
authored
fix(tracing): Baggage parsing fails when input is not of type string (#5276)
This patch attempts to fix a bug reported in #5250 where the input parameter for `parseBaggageString` was not a string and hence the `string.split` function call would throw an error. We now check for the type of the input and make the function * return an empty baggage object if the input is neither a string, nor an array. In this case we log a warning to get more information on what is actually passed in. * parse the baggage string if the input is a string * join and then split the array if the input is an array Additionally, the parser now ignores empty or blank baggage entries (e.g. `foo=bar, ,,alice=bob`). Added tests for parsing the new data types
1 parent e855e76 commit 06d573f

File tree

5 files changed

+65
-16
lines changed

5 files changed

+65
-16
lines changed

packages/node/src/integrations/http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ function _createWrappedRequestMethodFactory(
123123
`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to ${requestUrl}: `,
124124
);
125125

126-
const headerBaggageString = requestOptions.headers && (requestOptions.headers.baggage as string);
126+
const headerBaggageString = requestOptions.headers && requestOptions.headers.baggage;
127127

128128
requestOptions.headers = {
129129
...requestOptions.headers,

packages/types/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type {} from './globals';
3636
export type { Hub } from './hub';
3737
export type { Integration, IntegrationClass } from './integration';
3838
export type { Mechanism } from './mechanism';
39-
export type { ExtractedNodeRequestData, Primitive, WorkerLocation } from './misc';
39+
export type { ExtractedNodeRequestData, HttpHeaderValue, Primitive, WorkerLocation } from './misc';
4040
export type { ClientOptions, Options } from './options';
4141
export type { Package } from './package';
4242
export type { PolymorphicEvent } from './polymorphics';

packages/types/src/misc.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,5 @@ export interface WorkerLocation {
6363
}
6464

6565
export type Primitive = number | string | boolean | bigint | symbol | null | undefined;
66+
67+
export type HttpHeaderValue = string | string[] | number | null;

packages/utils/src/baggage.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Baggage, BaggageObj, TraceparentData } from '@sentry/types';
2+
import { HttpHeaderValue } from '@sentry/types';
23

4+
import { isString } from './is';
35
import { logger } from './logger';
46

57
export const BAGGAGE_HEADER_NAME = 'baggage';
@@ -89,9 +91,28 @@ export function serializeBaggage(baggage: Baggage): string {
8991
}, baggage[1]);
9092
}
9193

92-
/** Parse a baggage header from a string and return a Baggage object */
93-
export function parseBaggageString(inputBaggageString: string): Baggage {
94-
return inputBaggageString.split(',').reduce(
94+
/** Parse a baggage header from a string or a string array and return a Baggage object */
95+
export function parseBaggageHeader(inputBaggageValue: HttpHeaderValue): Baggage {
96+
// Adding this check here because we got reports of this function failing due to the input value
97+
// not being a string. This debug log might help us determine what's going on here.
98+
if ((!Array.isArray(inputBaggageValue) && !isString(inputBaggageValue)) || typeof inputBaggageValue === 'number') {
99+
__DEBUG_BUILD__ &&
100+
logger.warn(
101+
'[parseBaggageHeader] Received input value of incompatible type: ',
102+
typeof inputBaggageValue,
103+
inputBaggageValue,
104+
);
105+
106+
// Gonna early-return an empty baggage object so that we don't fail later on
107+
return createBaggage({}, '');
108+
}
109+
110+
const baggageEntries = (isString(inputBaggageValue) ? inputBaggageValue : inputBaggageValue.join(','))
111+
.split(',')
112+
.map(entry => entry.trim())
113+
.filter(entry => entry !== '');
114+
115+
return baggageEntries.reduce(
95116
([baggageObj, baggageString], curr) => {
96117
const [key, val] = curr.split('=');
97118
if (SENTRY_BAGGAGE_KEY_PREFIX_REGEX.test(key)) {
@@ -122,16 +143,17 @@ export function parseBaggageString(inputBaggageString: string): Baggage {
122143
* it would only affect parts of the sentry baggage (@see Baggage interface).
123144
*
124145
* @param incomingBaggage the baggage header of the incoming request that might contain sentry entries
125-
* @param headerBaggageString possibly existing baggage header string added from a third party to request headers
146+
* @param thirdPartyBaggageHeader possibly existing baggage header string or string[] added from a third
147+
* party to the request headers
126148
*
127149
* @return a merged and serialized baggage string to be propagated with the outgoing request
128150
*/
129-
export function mergeAndSerializeBaggage(incomingBaggage?: Baggage, headerBaggageString?: string): string {
130-
if (!incomingBaggage && !headerBaggageString) {
151+
export function mergeAndSerializeBaggage(incomingBaggage?: Baggage, thirdPartyBaggageHeader?: HttpHeaderValue): string {
152+
if (!incomingBaggage && !thirdPartyBaggageHeader) {
131153
return '';
132154
}
133155

134-
const headerBaggage = (headerBaggageString && parseBaggageString(headerBaggageString)) || undefined;
156+
const headerBaggage = (thirdPartyBaggageHeader && parseBaggageHeader(thirdPartyBaggageHeader)) || undefined;
135157
const thirdPartyHeaderBaggage = headerBaggage && getThirdPartyBaggage(headerBaggage);
136158

137159
const finalBaggage = createBaggage(
@@ -150,14 +172,14 @@ export function mergeAndSerializeBaggage(incomingBaggage?: Baggage, headerBaggag
150172
*
151173
* Extracted this logic to a function because it's duplicated in a lot of places.
152174
*
153-
* @param rawBaggageString
175+
* @param rawBaggageValue
154176
* @param sentryTraceHeader
155177
*/
156178
export function parseBaggageSetMutability(
157-
rawBaggageString: string | false | undefined | null,
179+
rawBaggageValue: HttpHeaderValue | false | undefined,
158180
sentryTraceHeader: TraceparentData | string | false | undefined | null,
159181
): Baggage {
160-
const baggage = parseBaggageString(rawBaggageString || '');
182+
const baggage = parseBaggageHeader(rawBaggageValue || '');
161183
if (!isSentryBaggageEmpty(baggage) || (sentryTraceHeader && isSentryBaggageEmpty(baggage))) {
162184
setBaggageImmutable(baggage);
163185
}

packages/utils/test/baggage.test.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
isBaggageMutable,
88
isSentryBaggageEmpty,
99
mergeAndSerializeBaggage,
10+
parseBaggageHeader,
1011
parseBaggageSetMutability,
11-
parseBaggageString,
1212
serializeBaggage,
1313
setBaggageImmutable,
1414
setBaggageValue,
@@ -97,9 +97,10 @@ describe('Baggage', () => {
9797
});
9898
});
9999

100-
describe('parseBaggageString', () => {
100+
describe('parseBaggageHeader', () => {
101101
it.each([
102102
['parses an empty string', '', createBaggage({})],
103+
['parses a blank string', ' ', createBaggage({})],
103104
[
104105
'parses sentry values into baggage',
105106
'sentry-environment=production,sentry-release=10.0.2',
@@ -113,8 +114,32 @@ describe('Baggage', () => {
113114
'userId=alice,serverNode=DF%2028,isProduction=false',
114115
),
115116
],
116-
])('%s', (_: string, baggageString, baggage) => {
117-
expect(parseBaggageString(baggageString)).toEqual(baggage);
117+
[
118+
'parses arbitrary baggage headers from string with empty and blank entries',
119+
'userId=alice, serverNode=DF%2028 , isProduction=false, ,,sentry-environment=production,,sentry-release=10.0.2',
120+
createBaggage(
121+
{ environment: 'production', release: '10.0.2' },
122+
'userId=alice,serverNode=DF%2028,isProduction=false',
123+
),
124+
],
125+
[
126+
'parses a string array',
127+
['userId=alice', 'sentry-environment=production', 'foo=bar'],
128+
createBaggage({ environment: 'production' }, 'userId=alice,foo=bar'),
129+
],
130+
[
131+
'parses a string array with items containing multiple entries',
132+
['userId=alice, userName=bob', 'sentry-environment=production,sentry-release=1.0.1', 'foo=bar'],
133+
createBaggage({ environment: 'production', release: '1.0.1' }, 'userId=alice,userName=bob,foo=bar'),
134+
],
135+
[
136+
'parses a string array with empty/blank entries',
137+
['', 'sentry-environment=production,sentry-release=1.0.1', ' ', 'foo=bar'],
138+
createBaggage({ environment: 'production', release: '1.0.1' }, 'foo=bar'),
139+
],
140+
['ignorese other input types than string and string[]', 42, createBaggage({}, '')],
141+
])('%s', (_: string, baggageValue, baggage) => {
142+
expect(parseBaggageHeader(baggageValue)).toEqual(baggage);
118143
});
119144
});
120145

0 commit comments

Comments
 (0)