diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index f9f91415e18c..ca71d13873ce 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -1,5 +1,5 @@ import { Event, EventProcessor, Hub, Integration, StackFrame } from '@sentry/types'; -import { getEventDescription, isMatchingPattern, logger } from '@sentry/utils'; +import { getEventDescription, logger, stringMatchesSomePattern } from '@sentry/utils'; // "Script error." is hard coded into browsers for errors that it can't read. // this is the result of a script being pulled in from an external domain and CORS. @@ -107,9 +107,7 @@ function _isIgnoredError(event: Event, ignoreErrors?: Array): b return false; } - return _getPossibleEventMessages(event).some(message => - ignoreErrors.some(pattern => isMatchingPattern(message, pattern)), - ); + return _getPossibleEventMessages(event).some(message => stringMatchesSomePattern(message, ignoreErrors)); } function _isDeniedUrl(event: Event, denyUrls?: Array): boolean { @@ -118,7 +116,7 @@ function _isDeniedUrl(event: Event, denyUrls?: Array): boolean return false; } const url = _getEventFilterUrl(event); - return !url ? false : denyUrls.some(pattern => isMatchingPattern(url, pattern)); + return !url ? false : stringMatchesSomePattern(url, denyUrls); } function _isAllowedUrl(event: Event, allowUrls?: Array): boolean { @@ -127,7 +125,7 @@ function _isAllowedUrl(event: Event, allowUrls?: Array): boolea return true; } const url = _getEventFilterUrl(event); - return !url ? true : allowUrls.some(pattern => isMatchingPattern(url, pattern)); + return !url ? true : stringMatchesSomePattern(url, allowUrls); } function _getPossibleEventMessages(event: Event): string[] { diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 634b13db1930..e4da4195f54e 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -3,9 +3,9 @@ import { EventProcessor, Integration, Span, TracePropagationTargets } from '@sen import { dynamicSamplingContextToSentryBaggageHeader, fill, - isMatchingPattern, logger, parseSemver, + stringMatchesSomePattern, } from '@sentry/utils'; import * as http from 'http'; import * as https from 'https'; @@ -169,9 +169,7 @@ function _createWrappedRequestMethodFactory( return headersUrlMap[url]; } - headersUrlMap[url] = tracingOptions.tracePropagationTargets.some(tracePropagationTarget => - isMatchingPattern(url, tracePropagationTarget), - ); + headersUrlMap[url] = stringMatchesSomePattern(url, tracingOptions.tracePropagationTargets); return headersUrlMap[url]; }; diff --git a/packages/tracing/src/browser/request.ts b/packages/tracing/src/browser/request.ts index bfcd5e4146d7..c3ebc1e00240 100644 --- a/packages/tracing/src/browser/request.ts +++ b/packages/tracing/src/browser/request.ts @@ -5,7 +5,7 @@ import { BAGGAGE_HEADER_NAME, dynamicSamplingContextToSentryBaggageHeader, isInstanceOf, - isMatchingPattern, + stringMatchesSomePattern, } from '@sentry/utils'; import { getActiveTransaction, hasTracingEnabled } from '../utils'; @@ -123,8 +123,7 @@ export function instrumentOutgoingRequests(_options?: Partial true; const shouldAttachHeaders = (url: string): boolean => - tracingOrigins.some(origin => isMatchingPattern(url, origin)) || - tracePropagationTargets.some(origin => isMatchingPattern(url, origin)); + stringMatchesSomePattern(url, tracingOrigins) || stringMatchesSomePattern(url, tracePropagationTargets); const spans: Record = {}; diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index 2b9259e5bade..90b76b6f4621 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -84,11 +84,18 @@ export function safeJoin(input: any[], delimiter?: string): string { } /** - * Checks if the value matches a regex or includes the string - * @param value The string value to be checked against - * @param pattern Either a regex or a string that must be contained in value + * Checks if the given value matches a regex or string + * + * @param value The string to test + * @param pattern Either a regex or a string against which `value` will be matched + * @param requireExactStringMatch If true, `value` must match `pattern` exactly. If false, `value` will match + * `pattern` if it contains `pattern`. Only applies to string-type patterns. */ -export function isMatchingPattern(value: string, pattern: RegExp | string): boolean { +export function isMatchingPattern( + value: string, + pattern: RegExp | string, + requireExactStringMatch: boolean = false, +): boolean { if (!isString(value)) { return false; } @@ -96,12 +103,31 @@ export function isMatchingPattern(value: string, pattern: RegExp | string): bool if (isRegExp(pattern)) { return pattern.test(value); } - if (typeof pattern === 'string') { - return value.indexOf(pattern) !== -1; + if (isString(pattern)) { + return requireExactStringMatch ? value === pattern : value.includes(pattern); } + return false; } +/** + * Test the given string against an array of strings and regexes. By default, string matching is done on a + * substring-inclusion basis rather than a strict equality basis + * + * @param testString The string to test + * @param patterns The patterns against which to test the string + * @param requireExactStringMatch If true, `testString` must match one of the given string patterns exactly in order to + * count. If false, `testString` will match a string pattern if it contains that pattern. + * @returns + */ +export function stringMatchesSomePattern( + testString: string, + patterns: Array = [], + requireExactStringMatch: boolean = false, +): boolean { + return patterns.some(pattern => isMatchingPattern(testString, pattern, requireExactStringMatch)); +} + /** * Given a string, escape characters which have meaning in the regex grammar, such that the result is safe to feed to * `new RegExp()`. diff --git a/packages/utils/test/string.test.ts b/packages/utils/test/string.test.ts index 316328d9ee13..bb49a7833a11 100644 --- a/packages/utils/test/string.test.ts +++ b/packages/utils/test/string.test.ts @@ -1,4 +1,4 @@ -import { isMatchingPattern, truncate } from '../src/string'; +import { isMatchingPattern, stringMatchesSomePattern, truncate } from '../src/string'; describe('truncate()', () => { test('it works as expected', () => { @@ -18,13 +18,31 @@ describe('truncate()', () => { }); describe('isMatchingPattern()', () => { - test('match using string substring', () => { + test('match using string substring if `requireExactStringMatch` not given', () => { expect(isMatchingPattern('foobar', 'foobar')).toEqual(true); expect(isMatchingPattern('foobar', 'foo')).toEqual(true); expect(isMatchingPattern('foobar', 'bar')).toEqual(true); expect(isMatchingPattern('foobar', 'nope')).toEqual(false); }); + test('match using string substring if `requireExactStringMatch` is `false`', () => { + expect(isMatchingPattern('foobar', 'foobar', false)).toEqual(true); + expect(isMatchingPattern('foobar', 'foo', false)).toEqual(true); + expect(isMatchingPattern('foobar', 'bar', false)).toEqual(true); + expect(isMatchingPattern('foobar', 'nope', false)).toEqual(false); + }); + + test('match using exact string match if `requireExactStringMatch` is `true`', () => { + expect(isMatchingPattern('foobar', 'foobar', true)).toEqual(true); + expect(isMatchingPattern('foobar', 'foo', true)).toEqual(false); + expect(isMatchingPattern('foobar', 'nope', true)).toEqual(false); + }); + + test('matches when `value` constains `pattern` but not vice-versa', () => { + expect(isMatchingPattern('foobar', 'foo')).toEqual(true); + expect(isMatchingPattern('foobar', 'foobarbaz')).toEqual(false); + }); + test('match using regexp test', () => { expect(isMatchingPattern('foobar', /^foo/)).toEqual(true); expect(isMatchingPattern('foobar', /foo/)).toEqual(true); @@ -45,3 +63,48 @@ describe('isMatchingPattern()', () => { expect(isMatchingPattern([] as any, 'foo')).toEqual(false); }); }); + +describe('stringMatchesSomePattern()', () => { + test('match using string substring if `requireExactStringMatch` not given', () => { + expect(stringMatchesSomePattern('foobar', ['foobar', 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('foobar', ['foo', 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('foobar', ['baz', 'nope'])).toEqual(false); + }); + + test('match using string substring if `requireExactStringMatch` is `false`', () => { + expect(stringMatchesSomePattern('foobar', ['foobar', 'nope'], false)).toEqual(true); + expect(stringMatchesSomePattern('foobar', ['foo', 'nope'], false)).toEqual(true); + expect(stringMatchesSomePattern('foobar', ['baz', 'nope'], false)).toEqual(false); + }); + + test('match using exact string match if `requireExactStringMatch` is `true`', () => { + expect(stringMatchesSomePattern('foobar', ['foobar', 'nope'], true)).toEqual(true); + expect(stringMatchesSomePattern('foobar', ['foo', 'nope'], true)).toEqual(false); + expect(stringMatchesSomePattern('foobar', ['baz', 'nope'], true)).toEqual(false); + }); + + test('matches when `testString` constains a pattern but not vice-versa', () => { + expect(stringMatchesSomePattern('foobar', ['foo', 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('foobar', ['foobarbaz', 'nope'])).toEqual(false); + }); + + test('match using regexp test', () => { + expect(stringMatchesSomePattern('foobar', [/^foo/, 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('foobar', [/foo/, 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('foobar', [/b.{1}r/, 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('foobar', [/^foo$/, 'nope'])).toEqual(false); + }); + + test('should match empty pattern as true', () => { + expect(stringMatchesSomePattern('foo', ['', 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('bar', ['', 'nope'])).toEqual(true); + expect(stringMatchesSomePattern('', ['', 'nope'])).toEqual(true); + }); + + test('should bail out with false when given non-string value', () => { + expect(stringMatchesSomePattern(null as any, ['foo', 'nope'])).toEqual(false); + expect(stringMatchesSomePattern(undefined as any, ['foo', 'nope'])).toEqual(false); + expect(stringMatchesSomePattern({} as any, ['foo', 'nope'])).toEqual(false); + expect(stringMatchesSomePattern([] as any, ['foo', 'nope'])).toEqual(false); + }); +});