Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add NSNull handling to sentry_sanitize #4760

Merged
merged 5 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Use strlcpy to save session replay info path (#4740)
- `sentryReplayUnmask` and `sentryReplayUnmask` preventing interaction (#4749)
- Missing `SentryCrashExceptionApplication` implementation for non-macOS target (#4759)
- Add `NSNull` handling to `sentry_sanitize` (#4760)

### Improvements

Expand Down
20 changes: 14 additions & 6 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -787,11 +787,13 @@
A8AFFCD42907E0CA00967CD7 /* SentryRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8AFFCD32907E0CA00967CD7 /* SentryRequestTests.swift */; };
A8F17B2E2901765900990B25 /* SentryRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B2D2901765900990B25 /* SentryRequest.m */; };
A8F17B342902870300990B25 /* SentryHttpStatusCodeRange.m in Sources */ = {isa = PBXBuildFile; fileRef = A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */; };
D41909972D490BE5002B83D0 /* SentryNSDictionarySanitize+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */; };
D42E48592D48FC9700D251BC /* SentryNSDictionarySanitizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */; };
D48724DB2D352597005DE483 /* SentryTraceOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DA2D352591005DE483 /* SentryTraceOrigin.swift */; };
D48724E22D354D16005DE483 /* SentryTraceOriginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724E12D354D16005DE483 /* SentryTraceOriginTests.swift */; };
D48E8B8B2D3E79610032E35E /* SentryTraceOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48E8B8A2D3E79610032E35E /* SentryTraceOrigin.swift */; };
D48724DD2D354939005DE483 /* SentrySpanOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DC2D354934005DE483 /* SentrySpanOperation.swift */; };
D48724E02D3549CA005DE483 /* SentrySpanOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DF2D3549C6005DE483 /* SentrySpanOperationTests.swift */; };
D48724E22D354D16005DE483 /* SentryTraceOriginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724E12D354D16005DE483 /* SentryTraceOriginTests.swift */; };
D48E8B8B2D3E79610032E35E /* SentryTraceOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48E8B8A2D3E79610032E35E /* SentryTraceOrigin.swift */; };
D48E8B9D2D3E82AC0032E35E /* SentrySpanOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48E8B9C2D3E82AC0032E35E /* SentrySpanOperation.swift */; };
D4AF00212D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */; };
D4AF00232D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */; };
Expand Down Expand Up @@ -1891,11 +1893,14 @@
A8AFFCD32907E0CA00967CD7 /* SentryRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRequestTests.swift; sourceTree = "<group>"; };
A8F17B2D2901765900990B25 /* SentryRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryRequest.m; sourceTree = "<group>"; };
A8F17B332902870300990B25 /* SentryHttpStatusCodeRange.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryHttpStatusCodeRange.m; sourceTree = "<group>"; };
D41909922D48FFF6002B83D0 /* SentryNSDictionarySanitize+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryNSDictionarySanitize+Tests.h"; sourceTree = "<group>"; };
D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryNSDictionarySanitize+Tests.m"; sourceTree = "<group>"; };
D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSDictionarySanitizeTests.swift; sourceTree = "<group>"; };
D48724DA2D352591005DE483 /* SentryTraceOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceOrigin.swift; sourceTree = "<group>"; };
D48724E12D354D16005DE483 /* SentryTraceOriginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceOriginTests.swift; sourceTree = "<group>"; };
D48E8B8A2D3E79610032E35E /* SentryTraceOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceOrigin.swift; sourceTree = "<group>"; };
D48724DC2D354934005DE483 /* SentrySpanOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanOperation.swift; sourceTree = "<group>"; };
D48724DF2D3549C6005DE483 /* SentrySpanOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanOperationTests.swift; sourceTree = "<group>"; };
D48724E12D354D16005DE483 /* SentryTraceOriginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceOriginTests.swift; sourceTree = "<group>"; };
D48E8B8A2D3E79610032E35E /* SentryTraceOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceOrigin.swift; sourceTree = "<group>"; };
D48E8B9C2D3E82AC0032E35E /* SentrySpanOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanOperation.swift; sourceTree = "<group>"; };
D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzling.m; sourceTree = "<group>"; };
D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSFileManagerSwizzling.h; path = include/SentryNSFileManagerSwizzling.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2581,6 +2586,8 @@
630436151EC0AD3100C4D3FA /* SentryNSDataCompressionTests.m */,
63EED6C22237989300E02400 /* SentryOptionsTest.m */,
7B569DFF2590EEF600B653FC /* SentryScope+Equality.m */,
D41909922D48FFF6002B83D0 /* SentryNSDictionarySanitize+Tests.h */,
D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */,
632331F52404FFA8008D91D6 /* SentryScopeTests.m */,
7B0002312477F0520035FEF1 /* SentrySessionTests.m */,
63AA75951EB8AEDB00D153DE /* SentryTests.m */,
Expand Down Expand Up @@ -2991,6 +2998,7 @@
7B6438AD26A710E6000D0F65 /* Categories */ = {
isa = PBXGroup;
children = (
D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */,
7B6438A626A70DDB000D0F65 /* UIViewControllerSentryTests.swift */,
7B0DC73328869BF40039995F /* NSMutableDictionarySentryTests.swift */,
);
Expand Down Expand Up @@ -3634,8 +3642,6 @@
8ECC674325C23A1F000E2BF6 /* SentrySpanContext.m */,
622C08D929E554B9002571D4 /* SentrySpanContext+Private.h */,
8E4E7C7325DAAB49006AB9E2 /* SentrySpanProtocol.h */,
7B3B83712833832B0001FDEB /* SentrySpanOperations.h */,
622C08D729E546F4002571D4 /* SentryTraceOrigins.h */,
8E4E7C6C25DAAAFE006AB9E2 /* SentrySpan.h */,
84A789092C0E9F5800FF0803 /* SentrySpan+Private.h */,
8EC3AE7925CA23B600E7591A /* SentrySpan.m */,
Expand Down Expand Up @@ -5102,6 +5108,7 @@
7BC6EC10255C3F560059822A /* SentryMechanismTests.swift in Sources */,
63FE721B20DA66EC00CDBAE8 /* Container+DeepSearch_Tests.m in Sources */,
8EA05EED267C2AB200C82B30 /* SentryNetworkTrackerTests.swift in Sources */,
D41909972D490BE5002B83D0 /* SentryNSDictionarySanitize+Tests.m in Sources */,
7BA840A024A1EC6E00B718AA /* SentrySDKTests.swift in Sources */,
D8AE48BF2C578D540092A2A6 /* SentryLog.swift in Sources */,
D8F6A24E288553A800320515 /* SentryPredicateDescriptorTests.swift in Sources */,
Expand All @@ -5112,6 +5119,7 @@
7BC6EC08255C36DE0059822A /* SentryStacktraceTests.swift in Sources */,
D88817DD26D72BA500BF2251 /* SentryTraceStateTests.swift in Sources */,
7B26BBFB24C0A66D00A79CCC /* SentrySdkInfoNilTests.m in Sources */,
D42E48592D48FC9700D251BC /* SentryNSDictionarySanitizeTests.swift in Sources */,
7B984A9F28E572AF001F4BEE /* CrashReport.swift in Sources */,
D4AF00252D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m in Sources */,
7BED3576266F7BFF00EAA70D /* TestSentryCrashWrapper.m in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Sources/Sentry/SentryNSDictionarySanitize.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
return nil;
}

if ([dictionary isEqual:[NSNull null]]) {
return nil;
}

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (id rawKey in dictionary.allKeys) {
id rawValue = [dictionary objectForKey:rawKey];
Expand Down
145 changes: 145 additions & 0 deletions Tests/SentryTests/Categories/SentryNSDictionarySanitizeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
@testable import Sentry
import SentryTestUtils
import XCTest

class SentryNSDictionarySanitizeTests: XCTestCase {
func testSentrySanitize_dictionaryIsNil_shouldReturnNil() {
// Arrange
let dict: [String: Any]? = nil
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertNil(sanitized)
}

func testSentrySanitize_dictionaryIsNSNull_shouldReturnNil() {
// Act
let sanitized = sentry_sanitize_with_nsnull()
// Assert
XCTAssertNil(sanitized)
}

func testSentrySanitize_dictionaryIsEmpty_shouldReturnEmptyDictionary() {
// Arrange
let dict = [String: Any]()
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?.count, 0)
}

func testSentrySanitize_dictionaryKeyIsString_shouldUseKey() {
// Arrange
let dict = ["key": "value"]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?["key"] as? String, "value")
}

func testSentrySanitize_dictionaryKeyIsNotString_shouldUseKeyDescriptionAsKey() {
// Arrange
let dict: [AnyHashable: Any] = [
1: "number value",
Float(0.123456789): "float value",
Double(9.87654321): "double value",
Date(timeIntervalSince1970: 1_234): "date value"
]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?.count, 4)
XCTAssertEqual(sanitized?["1"] as? String, "number value")
XCTAssertEqual(sanitized?["0.1234568"] as? String, "float value")
XCTAssertEqual(sanitized?["9.876543209999999"] as? String, "double value")
XCTAssertEqual(sanitized?["1970-01-01 00:20:34 +0000"] as? String, "date value")
}

func testSentrySanitize_dictionaryKeyIsBoolean_willCollideWithNumberKey() {
// This test is only added for locking down the expected behaviour.
// The key `true` is bridged to a `_NSCFBoolean` which is a type alias
// for `CFBoolean` which is defined as `1` for `true` and `0` for `false`.
// Therefore any boolean will be casted to a number and treated equally.

// Arrange
let dict: [AnyHashable: Any] = [
1: "number value",
true: "bool value"
]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?.count, 1)
// The order is not deterministic, so it can be either one.
let value = sanitized?["1"] as? String
XCTAssertTrue(value == "number value" || value == "bool value")
}

func testSentrySanitize_keyStartsWithSentryIdentifier_shouldIgnoreValue() {
// Arrange
let dict = ["__sentry_key": "value", "__sentry": "value 2", "key": "value 3"]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?.count, 1)
XCTAssertEqual(sanitized?["key"] as? String, "value 3")
}

func testSentrySanitize_dictionaryValueIsString_shouldUseValue() {
// Arrange
let dict = ["key": "value"]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?["key"] as? String, "value")
}

func testSentrySanitize_dictionaryValueIsNumber_shouldUseValueDescription() {
// Arrange
let dict = ["key": 123]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?["key"] as? Int, 123)
}

func testSentrySanitize_dictionaryValueIsDictionary_shouldSanitizeValue() {
// Arrange
let dict = ["key": ["__sentry": "value 1", "key": "value 2"]]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?["key"] as? [String: String], ["key": "value 2"])
}

func testSentrySanitize_dictionaryValueIsArray_shouldSanitizeEveryElement() {
// Arrange
let dict = ["key": ["value", "value 2"]]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?["key"] as? [String], ["value", "value 2"])
}

func testSentrySanitize_dictionaryValueIsDate_shouldUseISO8601FormatAsValue() {
// Arrange
let date = Date(timeIntervalSince1970: 1_234)
let dict = ["key": date]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
XCTAssertEqual(sanitized?["key"] as? String, "1970-01-01T00:20:34.000Z")
}

func testSentrySanitize_dictionaryValueIsOtherType_shouldUseObjectDescriptionAsValue() throws {
// Arrange
let dict = ["key": NSObject()]
// Act
let sanitized = sentry_sanitize(dict)
// Assert
let value = try XCTUnwrap(sanitized?["key"] as? String)
let regex = try NSRegularExpression(pattern: "^<NSObject: 0x[0-9a-f]+>$")
let result = regex.matches(in: value, range: NSRange(location: 0, length: value.count))
XCTAssertFalse(result.isEmpty)
}
}
7 changes: 7 additions & 0 deletions Tests/SentryTests/SentryNSDictionarySanitize+Tests.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

NSDictionary *_Nullable sentry_sanitize_with_nsnull(void);

NS_ASSUME_NONNULL_END
7 changes: 7 additions & 0 deletions Tests/SentryTests/SentryNSDictionarySanitize+Tests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "SentryNSDictionarySanitize.h"

NSDictionary *_Nullable sentry_sanitize_with_nsnull(void)
{
// Cast [NSNull null] to NSDictionary to avoid compiler warnings/errors
return sentry_sanitize((NSDictionary *)[NSNull null]);
}
2 changes: 2 additions & 0 deletions Tests/SentryTests/SentryTests-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@
#import "SentryMigrateSessionInit.h"
#import "SentryMsgPackSerializer.h"
#import "SentryNSDataUtils.h"
#import "SentryNSDictionarySanitize+Tests.h"
#import "SentryNSDictionarySanitize.h"
#import "SentryNSError.h"
#import "SentryNSNotificationCenterWrapper.h"
#import "SentryNSProcessInfoWrapper.h"
Expand Down
Loading