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

macOS 12 crash report handling #431

Merged
merged 16 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
4B677439255DBEB800025BD8 /* HTTPSUpgradeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B677430255DBEB800025BD8 /* HTTPSUpgradeStore.swift */; };
4B677442255DBEEA00025BD8 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B677440255DBEEA00025BD8 /* Database.swift */; };
4B67744B255DBF3A00025BD8 /* BloomFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B677449255DBF3A00025BD8 /* BloomFilter.cpp */; };
4B70C00127B0793D000386ED /* DuckDuckGo-ExampleCrash.ips in Resources */ = {isa = PBXBuildFile; fileRef = 4B70BFFF27B0793D000386ED /* DuckDuckGo-ExampleCrash.ips */; };
4B70C00227B0793D000386ED /* CrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B70C00027B0793D000386ED /* CrashReportTests.swift */; };
4B723E0526B0003E00E14D75 /* DataImportMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DFF26B0003E00E14D75 /* DataImportMocks.swift */; };
4B723E0626B0003E00E14D75 /* CSVParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723E0026B0003E00E14D75 /* CSVParserTests.swift */; };
4B723E0726B0003E00E14D75 /* CSVImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723E0126B0003E00E14D75 /* CSVImporterTests.swift */; };
Expand Down Expand Up @@ -768,6 +770,8 @@
4B677449255DBF3A00025BD8 /* BloomFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BloomFilter.cpp; path = Submodules/bloom_cpp/src/BloomFilter.cpp; sourceTree = SOURCE_ROOT; };
4B67744A255DBF3A00025BD8 /* BloomFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BloomFilter.hpp; path = Submodules/bloom_cpp/src/BloomFilter.hpp; sourceTree = SOURCE_ROOT; };
4B677454255DC18000025BD8 /* Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bridging.h; sourceTree = "<group>"; };
4B70BFFF27B0793D000386ED /* DuckDuckGo-ExampleCrash.ips */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "DuckDuckGo-ExampleCrash.ips"; sourceTree = "<group>"; };
4B70C00027B0793D000386ED /* CrashReportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReportTests.swift; sourceTree = "<group>"; };
4B723DEB26B0002B00E14D75 /* DataImport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataImport.swift; sourceTree = "<group>"; };
4B723DED26B0002B00E14D75 /* DataImport.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = DataImport.storyboard; sourceTree = "<group>"; };
4B723DEE26B0002B00E14D75 /* DataImportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataImportViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1678,6 +1682,23 @@
name = bloom_cpp;
sourceTree = "<group>";
};
4B70BFFD27B0793D000386ED /* Crash Reports */ = {
isa = PBXGroup;
children = (
4B70BFFE27B0793D000386ED /* Example Crash Reports */,
4B70C00027B0793D000386ED /* CrashReportTests.swift */,
);
path = "Crash Reports";
sourceTree = "<group>";
};
4B70BFFE27B0793D000386ED /* Example Crash Reports */ = {
isa = PBXGroup;
children = (
4B70BFFF27B0793D000386ED /* DuckDuckGo-ExampleCrash.ips */,
);
path = "Example Crash Reports";
sourceTree = "<group>";
};
4B723DEA26B0002B00E14D75 /* Data Import */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2398,6 +2419,7 @@
85F69B3A25EDE7F800978E59 /* Common */,
85AC3B1525D9BBFA00C7D2AA /* Configuration */,
4B82E9B725B6A04B00656FE7 /* ContentBlocker */,
4B70BFFD27B0793D000386ED /* Crash Reports */,
4B723E0226B0003E00E14D75 /* Data Export */,
B683097A274DCFE3004B46BB /* Database */,
4B723DFE26B0003E00E14D75 /* Data Import */,
Expand Down Expand Up @@ -3749,6 +3771,7 @@
B69B50542726CD8100758A2B /* atb-with-update.json in Resources */,
B69B50522726CD8100758A2B /* atb.json in Resources */,
4BB99D0B26FE1A7B001E4761 /* Bookmarks in Resources */,
4B70C00127B0793D000386ED /* DuckDuckGo-ExampleCrash.ips in Resources */,
4B8AC94126B49BEE00879451 /* key4.db in Resources */,
B67C6C422654BF49006C872E /* DuckDuckGo-Symbol.jpg in Resources */,
B69B50552726CD8100758A2B /* invalid.json in Resources */,
Expand Down Expand Up @@ -4384,6 +4407,7 @@
B6106BB326A7F4AA0013B453 /* GeolocationServiceMock.swift in Sources */,
4B8AC93D26B49BE600879451 /* FirefoxLoginReaderTests.swift in Sources */,
4B723E0526B0003E00E14D75 /* DataImportMocks.swift in Sources */,
4B70C00227B0793D000386ED /* CrashReportTests.swift in Sources */,
85AC3B1725D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift in Sources */,
B693955D26F19CD70015B914 /* DownloadListStoreTests.swift in Sources */,
4B0511E7262CAB3700F6079C /* UserDefaultsWrapperUtilities.swift in Sources */,
Expand Down
53 changes: 50 additions & 3 deletions DuckDuckGo/Crash Reports/Model/CrashReport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,19 @@ import Foundation

#if OUT_OF_APPSTORE

struct CrashReport {
protocol CrashReport {

static var fileExtension: String { get }

var url: URL { get }
var content: String? { get }
var contentData: Data? { get }

}

struct LegacyCrashReport: CrashReport {

static let fileExtension = "crash"

static let headerItemsToFilter = [
"Anonymous UUID:",
Expand All @@ -33,8 +45,8 @@ struct CrashReport {
try? String(contentsOf: url)
.components(separatedBy: "\n")
.filter({ line in
for headerItemToFilder in Self.headerItemsToFilter {
if line.hasPrefix(headerItemToFilder) { return false }
for headerItemToFilter in Self.headerItemsToFilter {
if line.hasPrefix(headerItemToFilter) { return false }
}
return true
})
Expand All @@ -47,4 +59,39 @@ struct CrashReport {

}

struct JSONCrashReport: CrashReport {

static let fileExtension = "ips"

static let headerItemsToFilter = [
"sleepWakeUUID",
"deviceIdentifierForVendor",
"rolloutId"
]

let url: URL

var content: String? {
guard var fileContents = try? String(contentsOf: url) else {
return nil
}

for itemToFilter in Self.headerItemsToFilter {
let patternToReplace = "\"\(itemToFilter)\"\\s*:\\s*\"[^\"]*\""
let redactedKeyValuePair = "\"\(itemToFilter)\":\"<removed>\""

fileContents = fileContents.replacingOccurrences(of: patternToReplace,
with: redactedKeyValuePair,
options: .regularExpression)
}

return fileContents
}

var contentData: Data? {
content?.data(using: .utf8)
}

}

#endif
13 changes: 11 additions & 2 deletions DuckDuckGo/Crash Reports/Model/CrashReportReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ final class CrashReportReader {
.filter({ isCrashReportPath($0) &&
belongsToThisApp($0) &&
isFile(at: $0, newerThan: lastCheckDate) })
.map({ CrashReport(url: $0) })
.compactMap(crashReport(from:))
}

private func isCrashReportPath(_ path: URL) -> Bool {
return path.pathExtension == "crash"
let validExtensions = [LegacyCrashReport.fileExtension, JSONCrashReport.fileExtension]
return validExtensions.contains(path.pathExtension)
}

private func belongsToThisApp(_ path: URL) -> Bool {
Expand All @@ -58,6 +59,14 @@ final class CrashReportReader {

return creationDate > lastCheckDate && creationDate < Date()
}

private func crashReport(from url: URL) -> CrashReport? {
switch url.pathExtension {
case LegacyCrashReport.fileExtension: return LegacyCrashReport(url: url)
case JSONCrashReport.fileExtension: return JSONCrashReport(url: url)
default: return nil
}
}

}

Expand Down
60 changes: 60 additions & 0 deletions Unit Tests/Crash Reports/CrashReportTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// CrashReportTests.swift
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import XCTest
@testable import DuckDuckGo_Privacy_Browser

class CrashReportTests: XCTestCase {

func testWhenParsingIPSCrashReports_ThenCrashReportDataDoesNotIncludeIdentifyingInformation() {
let bundle = Bundle(for: CrashReportTests.self)
let url = bundle.resourceURL!.appendingPathComponent("DuckDuckGo-ExampleCrash.ips")

let report = JSONCrashReport(url: url)

XCTAssertNotNil(report.content)
XCTAssertNotNil(report.contentData)

// Verify that the content includes the slice ID
XCTAssertTrue(report.content!.contains("7fc6ff2c-a85d-3116-96d9-9368ec955ba1"))

// Verify that the content does not include the sleepWakeUUID anywhere in the report
XCTAssertFalse(report.content!.contains("2384290E-F858-4024-9488-11D3FF94B4DD"))

// Verify that the content does not include the device identitier
XCTAssertFalse(report.content!.contains("483B097A-A969-596F-9F2A-357347BB1DEC"))

// Verify that the content does not include any experiment rollout identifiers
XCTAssertFalse(report.content!.contains("602ad4dac86151000cf27e46"))
XCTAssertFalse(report.content!.contains("5fc94383418129005b4e9ae0"))
XCTAssertFalse(report.content!.contains("5ffde50ce2aacd000d47a95f"))
XCTAssertFalse(report.content!.contains("60da5e84ab0ca017dace9abf"))
XCTAssertFalse(report.content!.contains("607844aa04477260f58a8077"))
XCTAssertFalse(report.content!.contains("601d9415f79519000ccd4b69"))

// Verify that the sleepWakeUUID is definitely empty
XCTAssert(report.content!.contains(#"sleepWakeUUID":"<removed>"#))
XCTAssert(report.content!.contains(#"deviceIdentifierForVendor":"<removed>"#))
}

private func ipsCrashURL() -> URL {
let bundle = Bundle(for: CrashReportTests.self)
return bundle.resourceURL!.appendingPathComponent("DuckDuckGo-ExampleCrash.ips")
}

}
Loading