Skip to content

Commit 8f2120f

Browse files
authored
feat: Add build type for adhoc (#6044)
* feat: Add build type for adhoc * Add changelog entry * fix: Update property list serialization options for compatibility with visionOS * Improvements from pair programming with @philipphofmann * Add comments
1 parent 588dd7c commit 8f2120f

File tree

5 files changed

+286
-11
lines changed

5 files changed

+286
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
- Lazily CharacterSet only once in SentryBaggageSerialization (#5871)
2121
- Structured Logging: Log `SentrySDK.logger` calls to `SentrySDKLog` (#5991)
22+
- The build type in the app context now differentiates between `enterprise` and `adhoc` (#6044)
2223

2324
## 8.54.1-alpha.1
2425

Sentry.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,8 @@
10371037
F49D419A2DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49D41992DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift */; };
10381038
F49D419C2DEA30C300D9244E /* SentryCrashExceptionApplicationHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = F49D419B2DEA30B800D9244E /* SentryCrashExceptionApplicationHelper.h */; };
10391039
F49D419E2DEA3D0600D9244E /* SentryCrashExceptionApplicationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F49D419D2DEA3D0300D9244E /* SentryCrashExceptionApplicationHelper.m */; };
1040+
F4A930232E65FDBF006DA6EF /* SentryMobileProvisionParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A930222E65FDAF006DA6EF /* SentryMobileProvisionParser.swift */; };
1041+
F4A930252E661856006DA6EF /* SentryMobileProvisionParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A930242E661856006DA6EF /* SentryMobileProvisionParserTests.swift */; };
10401042
F4AACD612E01ACE800DDDD1E /* SentryCrashDynamicLinkerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F4AACD602E01ACE800DDDD1E /* SentryCrashDynamicLinkerTests.m */; };
10411043
F4DC35562E1FFD620077CE89 /* SentryVideoFrameProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4DC35552E1FFD610077CE89 /* SentryVideoFrameProcessor.swift */; };
10421044
F4DC35582E1FFE1F0077CE89 /* SentryVideoFrameProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4DC35572E1FFE1B0077CE89 /* SentryVideoFrameProcessorTests.swift */; };
@@ -2380,6 +2382,8 @@
23802382
F49D41992DEA2FB000D9244E /* SentryCrashExceptionApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashExceptionApplicationTests.swift; sourceTree = "<group>"; };
23812383
F49D419B2DEA30B800D9244E /* SentryCrashExceptionApplicationHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryCrashExceptionApplicationHelper.h; path = include/SentryCrashExceptionApplicationHelper.h; sourceTree = "<group>"; };
23822384
F49D419D2DEA3D0300D9244E /* SentryCrashExceptionApplicationHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashExceptionApplicationHelper.m; sourceTree = "<group>"; };
2385+
F4A930222E65FDAF006DA6EF /* SentryMobileProvisionParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMobileProvisionParser.swift; sourceTree = "<group>"; };
2386+
F4A930242E661856006DA6EF /* SentryMobileProvisionParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMobileProvisionParserTests.swift; sourceTree = "<group>"; };
23832387
F4AACD602E01ACE800DDDD1E /* SentryCrashDynamicLinkerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashDynamicLinkerTests.m; sourceTree = "<group>"; };
23842388
F4DC35552E1FFD610077CE89 /* SentryVideoFrameProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryVideoFrameProcessor.swift; sourceTree = "<group>"; };
23852389
F4DC35572E1FFE1B0077CE89 /* SentryVideoFrameProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryVideoFrameProcessorTests.swift; sourceTree = "<group>"; };
@@ -2596,6 +2600,7 @@
25962600
621D9F2D2B9B030E003D94DE /* Helper */ = {
25972601
isa = PBXGroup;
25982602
children = (
2603+
F4A930222E65FDAF006DA6EF /* SentryMobileProvisionParser.swift */,
25992604
F4FE9DBC2E621F100014FED5 /* SentryRandom.swift */,
26002605
F48F767B2E60B555009D4E7D /* SentryNSTimerFactory.swift */,
26012606
FABE8E162E307A7C0040809A /* Dependencies.swift */,
@@ -3570,6 +3575,7 @@
35703575
7BD7299B24654CD500EA3610 /* Helper */ = {
35713576
isa = PBXGroup;
35723577
children = (
3578+
F4A930242E661856006DA6EF /* SentryMobileProvisionParserTests.swift */,
35733579
D4F7BD7C2E4373BB004A2D77 /* SentryLevelMapperTests.swift */,
35743580
D8AE48BE2C578D540092A2A6 /* SentrySDKLog.swift */,
35753581
849AC3FF29E0C1FF00889C16 /* SentryFormatterTests.swift */,
@@ -5897,6 +5903,7 @@
58975903
63FE711720DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.c in Sources */,
58985904
FA3A42722E1C5F9B00A08C39 /* SentryNSNotificationCenterWrapper.swift in Sources */,
58995905
63FE70CB20DA4C1000CDBAE8 /* SentryCrashReportFixer.c in Sources */,
5906+
F4A930232E65FDBF006DA6EF /* SentryMobileProvisionParser.swift in Sources */,
59005907
63FE710F20DA4C1000CDBAE8 /* SentryCrashNSErrorUtil.m in Sources */,
59015908
8ECC674925C23A20000E2BF6 /* SentrySpanId.m in Sources */,
59025909
6344DDB51EC309E000D9160D /* SentryCrashReportSink.m in Sources */,
@@ -6159,6 +6166,7 @@
61596166
63FE721A20DA66EC00CDBAE8 /* SentryCrashSysCtl_Tests.m in Sources */,
61606167
7B88F30424BC8E6500ADF90A /* SentrySerializationTests.swift in Sources */,
61616168
843FB3432D156B9900558F18 /* SentryFeedbackTests.swift in Sources */,
6169+
F4A930252E661856006DA6EF /* SentryMobileProvisionParserTests.swift in Sources */,
61626170
623E16C32D6C57C000CF1625 /* SentryANRTypeTests.swift in Sources */,
61636171
D80694C42B7CC9AE00B820E6 /* SentryReplayEventTests.swift in Sources */,
61646172
7B34721728086A9D0041F047 /* SentrySwizzleWrapperTests.swift in Sources */,

Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.m

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#import "SentryLogC.h"
3838

3939
#import "SentryDefines.h"
40+
#import "SentrySwift.h"
4041

4142
#import <CommonCrypto/CommonDigest.h>
4243
#include <mach-o/dyld.h>
@@ -460,15 +461,6 @@
460461
return isAppStoreReceipt && receiptExists;
461462
}
462463

463-
/**
464-
* Check if the app has an embdded.mobileprovision file in the bundle.
465-
*/
466-
static bool
467-
hasEmbeddedMobileProvision(void)
468-
{
469-
return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"] != nil;
470-
}
471-
472464
static const char *
473465
getBuildType(void)
474466
{
@@ -478,8 +470,9 @@
478470
if (isDebugBuild()) {
479471
return "debug";
480472
}
481-
if (hasEmbeddedMobileProvision()) {
482-
return "enterprise";
473+
SentryMobileProvisionParser *parser = [[SentryMobileProvisionParser alloc] init];
474+
if ([parser hasEmbeddedMobileProvisionProfile]) {
475+
return [parser mobileProvisionProfileProvisionsAllDevices] ? "enterprise" : "adhoc";
483476
}
484477
if (isTestBuild()) {
485478
return "test";
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
@objc @_spi(Private)
2+
public class SentryMobileProvisionParser: NSObject {
3+
private var provisionsAllDevices: Bool = false
4+
private var embeddedProfilePath: String?
5+
6+
// If the profile provisions all devices, it indicates Enterprise distribution
7+
@objc
8+
public var mobileProvisionProfileProvisionsAllDevices: Bool {
9+
return provisionsAllDevices
10+
}
11+
12+
// This convenience initializer exists so we can use it from ObjC.
13+
// Functions with Optional parameters (used for testing) are not available to ObjC
14+
@objc
15+
convenience override public init() {
16+
self.init(nil)
17+
}
18+
19+
public init(_ path: String?) {
20+
super.init()
21+
embeddedProfilePath = path ?? Bundle.main.path(forResource: "embedded", ofType: "mobileprovision")
22+
guard let embeddedProfilePath else {
23+
SentrySDKLog.debug("Couldn't find a embedded mobileprovision profile")
24+
return
25+
}
26+
parseProfileFromPath(embeddedProfilePath)
27+
}
28+
29+
@objc
30+
public func hasEmbeddedMobileProvisionProfile() -> Bool {
31+
embeddedProfilePath != nil
32+
}
33+
34+
private func parseProfileFromPath(_ path: String) {
35+
guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
36+
SentrySDKLog.debug("Failed to read embedded mobileprovision profile at path \(path)")
37+
return
38+
}
39+
40+
// The file is a CMS (PKCS#7) container with a plist embedded inside as text.
41+
// Convert to Latin-1 to preserve the bytes -> string even if not UTF-8.
42+
guard let payload = String(data: fileData, encoding: .isoLatin1),
43+
let startRange = payload.range(of: "<plist"),
44+
let endRange = payload.range(of: "</plist>") else {
45+
SentrySDKLog.debug("Failed to parse embedded mobileprovision profile")
46+
return
47+
}
48+
49+
let plistRange = startRange.lowerBound ..< payload.index(endRange.upperBound, offsetBy: 0)
50+
let plistString = String(payload[plistRange])
51+
guard let plistData = plistString.data(using: .utf8) else { return }
52+
53+
var format = PropertyListSerialization.PropertyListFormat.xml
54+
55+
#if swift(>=5.9) && os(visionOS)
56+
let options = 0
57+
#else
58+
let options: PropertyListSerialization.ReadOptions = []
59+
#endif
60+
61+
guard let obj = try? PropertyListSerialization.propertyList(from: plistData,
62+
options: options,
63+
format: &format),
64+
let dict = obj as? [String: Any] else {
65+
return
66+
}
67+
provisionsAllDevices = dict["ProvisionsAllDevices"] as? Bool ?? false
68+
}
69+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
@_spi(Private) @testable import Sentry
2+
@_spi(Private) import SentryTestUtils
3+
import XCTest
4+
5+
class SentryMobileProvisionParserTests: XCTestCase {
6+
// MARK: - Valid Content Tests
7+
8+
func testInitWithValidEnterpriseContent() throws {
9+
let content = """
10+
<?xml version="1.0" encoding="UTF-8"?>
11+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
12+
<plist version="1.0">
13+
<dict>
14+
<key>ProvisionsAllDevices</key>
15+
<true/>
16+
<key>AppIDName</key>
17+
<string>Enterprise App</string>
18+
</dict>
19+
</plist>
20+
"""
21+
try createFileAndAssert(content) { path in
22+
let parser = SentryMobileProvisionParser(path)
23+
XCTAssertTrue(parser.mobileProvisionProfileProvisionsAllDevices)
24+
}
25+
}
26+
27+
func testInitWithValidAdhocContent() throws {
28+
let content = """
29+
<?xml version="1.0" encoding="UTF-8"?>
30+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
31+
<plist version="1.0">
32+
<dict>
33+
<key>ProvisionsAllDevices</key>
34+
<false/>
35+
<key>AppIDName</key>
36+
<string>Adhoc App</string>
37+
<ProvisionedDevices>
38+
</ProvisionedDevices>
39+
</dict>
40+
</plist>
41+
"""
42+
43+
try createFileAndAssert(content) { path in
44+
let parser = SentryMobileProvisionParser(path)
45+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices)
46+
}
47+
}
48+
49+
func testInitWithContentWithoutProvisionsAllDevicesKey() throws {
50+
let content = """
51+
<?xml version="1.0" encoding="UTF-8"?>
52+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
53+
<plist version="1.0">
54+
<dict>
55+
<key>AppIDName</key>
56+
<string>No Provisions All Devices Key</string>
57+
<ProvisionedDevices>
58+
</ProvisionedDevices>
59+
</dict>
60+
</plist>
61+
"""
62+
63+
try createFileAndAssert(content) { path in
64+
let parser = SentryMobileProvisionParser(path)
65+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices)
66+
}
67+
}
68+
69+
func testInitWithExtraDataInMobileprovisionfile() throws {
70+
let content = """
71+
This is not a valid plist content
72+
<plist>
73+
<dict>
74+
<key>ProvisionsAllDevices</key>
75+
<true/>
76+
</dict>
77+
</plist>
78+
This isn't valid content either
79+
"""
80+
81+
// The parser scans for the plist inside the profile, so it should find it
82+
try createFileAndAssert(content) { path in
83+
let parser = SentryMobileProvisionParser(path)
84+
XCTAssertTrue(parser.mobileProvisionProfileProvisionsAllDevices)
85+
}
86+
}
87+
88+
// MARK: - Invalid Content Tests
89+
90+
func testInitWithEmptyContent() throws {
91+
try createFileAndAssert("") { path in
92+
let parser = SentryMobileProvisionParser(path)
93+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices) // Should default to false when parsing fails
94+
}
95+
}
96+
97+
func testInitWithMalformedPlistContent() throws {
98+
let content = """
99+
<plist>
100+
<dict>
101+
<key>ProvisionsAllDevices</key>
102+
<true/>
103+
</dict>
104+
"""
105+
106+
try createFileAndAssert(content) { path in
107+
let parser = SentryMobileProvisionParser(path)
108+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices) // Should default to false when parsing fails
109+
}
110+
}
111+
112+
func testInitWithMissingPlistTags() throws {
113+
let content = """
114+
<dict>
115+
<key>ProvisionsAllDevices</key>
116+
<true/>
117+
</dict>
118+
"""
119+
120+
try createFileAndAssert(content) { path in
121+
let parser = SentryMobileProvisionParser(path)
122+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices) // Should default to false when parsing fails
123+
}
124+
}
125+
126+
// MARK: - Edge Cases
127+
128+
func testProvisionsAllDevicesWithNonBooleanValue() throws {
129+
let content = """
130+
<?xml version="1.0" encoding="UTF-8"?>
131+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
132+
<plist version="1.0">
133+
<dict>
134+
<key>ProvisionsAllDevices</key>
135+
<string>true</string>
136+
</dict>
137+
</plist>
138+
"""
139+
140+
try createFileAndAssert(content) { path in
141+
let parser = SentryMobileProvisionParser(path)
142+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices) // Should default to false when value is not boolean
143+
}
144+
}
145+
146+
func testProvisionsAllDevicesWithArrayValue() throws {
147+
let content = """
148+
<?xml version="1.0" encoding="UTF-8"?>
149+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
150+
<plist version="1.0">
151+
<dict>
152+
<key>ProvisionsAllDevices</key>
153+
<array>
154+
<string>true</string>
155+
</array>
156+
</dict>
157+
</plist>
158+
"""
159+
160+
try createFileAndAssert(content) { path in
161+
let parser = SentryMobileProvisionParser(path)
162+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices) // Should default to false when value is not boolean
163+
}
164+
}
165+
166+
func testPathDoesNotExist() throws {
167+
let parser = SentryMobileProvisionParser("/randomPath.xml")
168+
XCTAssertFalse(parser.mobileProvisionProfileProvisionsAllDevices)
169+
}
170+
171+
func testContentWithEmojiAndJapaneseCharacters() throws {
172+
// Create content with UTF-8 characters that cannot be represented in Latin-1
173+
// These include emojis, Chinese characters, and other Unicode characters
174+
let content = """
175+
<?xml version="1.0" encoding="UTF-8"?>
176+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
177+
<plist version="1.0">
178+
<dict>
179+
<key>ProvisionsAllDevices</key>
180+
<true/>
181+
<key>AppIDName</key>
182+
<string>Test App with 中文 characters and 🚀 emojis</string>
183+
</dict>
184+
</plist>
185+
"""
186+
187+
try createFileAndAssert(content) { path in
188+
let parser = SentryMobileProvisionParser(path)
189+
// Should default to false when Latin-1 conversion fails and plist cannot be extracted
190+
XCTAssertTrue(parser.mobileProvisionProfileProvisionsAllDevices)
191+
}
192+
}
193+
194+
// MARK: - Utils
195+
private func createFileAndAssert(_ content: String, at fileName: String = #function, assertBlock: ((String) throws -> Void)) throws {
196+
let tmpPath = FileManager.default.temporaryDirectory.path
197+
let path = "\(tmpPath)\(fileName).tmp"
198+
try content.write(toFile: path, atomically: true, encoding: .utf8)
199+
200+
try assertBlock(path)
201+
202+
try FileManager.default.removeItem(atPath: path)
203+
}
204+
}

0 commit comments

Comments
 (0)