Initial Commit
261
main/AppDelegate.swift
Normal file
@@ -0,0 +1,261 @@
|
||||
import UIKit
|
||||
import NetworkExtension
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
if UserDefaults.standard.bool(forKey: "kill_proxy") {
|
||||
UserDefaults.standard.set(false, forKey: "kill_proxy")
|
||||
disableDNS()
|
||||
} else {
|
||||
postDNSState()
|
||||
}
|
||||
|
||||
if UserDefaults.standard.bool(forKey: "kill_db") {
|
||||
UserDefaults.standard.set(false, forKey: "kill_db")
|
||||
SQLiteDatabase.destroyDatabase(path: DB_PATH)
|
||||
}
|
||||
do {
|
||||
let db = try SQLiteDatabase.open(path: DB_PATH)
|
||||
try db.createTable(table: DNSQuery.self)
|
||||
} catch {}
|
||||
|
||||
// loadVPN { self.startVPN() }
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
postDNSState()
|
||||
}
|
||||
|
||||
func setProxyEnabled(_ newState: Bool) {
|
||||
// DNS:
|
||||
if newState != managerDNS.isEnabled {
|
||||
newState ? enableDNS() : disableDNS()
|
||||
}
|
||||
// VPN:
|
||||
// let con = self.managerVPN?.connection
|
||||
// if newState != (con?.status == NEVPNStatus.connected) {
|
||||
// self.updateVPN {
|
||||
// self.managerVPN?.isEnabled = newState
|
||||
// newState ? try? con?.startVPNTunnel() : con?.stopVPNTunnel()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
// MARK: DNS
|
||||
|
||||
let managerDNS = NEDNSProxyManager.shared()
|
||||
|
||||
private func enableDNS() {
|
||||
updateDNS {
|
||||
self.managerDNS.localizedDescription = "GlassDNS"
|
||||
let proto = NEDNSProxyProviderProtocol()
|
||||
proto.providerBundleIdentifier = "de.uni-bamberg.psi.AppCheck.DNS"
|
||||
self.managerDNS.providerProtocol = proto
|
||||
self.managerDNS.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
private func disableDNS() {
|
||||
updateDNS {
|
||||
self.managerDNS.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
private func updateDNS(_ body: @escaping () -> Void) {
|
||||
managerDNS.loadFromPreferences { (error) in
|
||||
guard error == nil else { return }
|
||||
body()
|
||||
self.managerDNS.saveToPreferences { (error) in
|
||||
self.postDNSState()
|
||||
guard error == nil else { return }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func postDNSState() {
|
||||
managerDNS.loadFromPreferences {_ in
|
||||
NotificationCenter.default.post(name: .init("ChangedStateGlassDNS"), object: self.managerDNS.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: VPN
|
||||
|
||||
/*var managerVPN: NETunnelProviderManager?
|
||||
|
||||
private func loadVPN(_ finally: @escaping () -> Void) {
|
||||
NETunnelProviderManager.loadAllFromPreferences { managers, error in
|
||||
if managers?.count ?? 0 > 0 {
|
||||
managers?.forEach({ mgr in
|
||||
if let proto = (mgr.protocolConfiguration as? NETunnelProviderProtocol) {
|
||||
if proto.providerBundleIdentifier == "de.uni-bamberg.psi.AppCheck.Tunnel" {
|
||||
// self.managerVPN = mgr
|
||||
mgr.removeFromPreferences()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if self.managerVPN != nil {
|
||||
finally()
|
||||
} else {
|
||||
let mgr = NETunnelProviderManager()
|
||||
mgr.localizedDescription = "GlassTunnel"
|
||||
let proto = NETunnelProviderProtocol()
|
||||
proto.providerBundleIdentifier = "de.uni-bamberg.psi.AppCheck.Tunnel"
|
||||
proto.serverAddress = "127.0.0.1"
|
||||
// proto.username = "none"
|
||||
// proto.proxySettings = NEProxySettings()
|
||||
// proto.proxySettings?.httpEnabled = true
|
||||
// proto.proxySettings?.httpsEnabled = true
|
||||
// proto.authenticationMethod = .sharedSecret
|
||||
// proto.sharedSecretReference = try! VPNKeychain.persistentReferenceFor(service: "GlassTunnel", account:"none", password: "none".data(using: String.Encoding.utf8)!)
|
||||
mgr.protocolConfiguration = proto
|
||||
mgr.isEnabled = true
|
||||
self.managerVPN = mgr
|
||||
mgr.saveToPreferences { (error) in
|
||||
guard error == nil else {
|
||||
NSLog("VPN: save error: \(String(describing: error))")
|
||||
return
|
||||
}
|
||||
finally()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startVPN() {
|
||||
updateVPN {
|
||||
do {
|
||||
try self.managerVPN?.connection.startVPNTunnel()
|
||||
} catch {
|
||||
print("VPN: start error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateVPN(_ body: @escaping () -> Void) {
|
||||
self.managerVPN?.loadFromPreferences { (error) in
|
||||
guard error == nil else {
|
||||
return
|
||||
}
|
||||
body()
|
||||
self.managerVPN?.saveToPreferences { (error) in
|
||||
guard error == nil else {
|
||||
NSLog("VPN: save error: \(String(describing: error))")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// MARK: VPNKeychain
|
||||
/*
|
||||
/// Utility routines for working with the keychain.
|
||||
|
||||
enum VPNKeychain {
|
||||
/// Returns a persistent reference for a generic password keychain item, adding it to
|
||||
/// (or updating it in) the keychain if necessary.
|
||||
///
|
||||
/// This delegates the work to two helper routines depending on whether the item already
|
||||
/// exists in the keychain or not.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - service: The service name for the item.
|
||||
/// - account: The account for the item.
|
||||
/// - password: The desired password.
|
||||
/// - Returns: A persistent reference to the item.
|
||||
/// - Throws: Any error returned by the Security framework.
|
||||
|
||||
static func persistentReferenceFor(service: String, account: String, password: Data) throws -> Data {
|
||||
var copyResult: CFTypeRef? = nil
|
||||
let err = SecItemCopyMatching([
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccount: account,
|
||||
kSecReturnPersistentRef: true,
|
||||
kSecReturnData: true
|
||||
] as NSDictionary, ©Result)
|
||||
switch err {
|
||||
case errSecSuccess:
|
||||
return try self.persistentReferenceByUpdating(copyResult: copyResult!, service: service, account: account, password: password)
|
||||
case errSecItemNotFound:
|
||||
return try self.persistentReferenceByAdding(service: service, account:account, password: password)
|
||||
default:
|
||||
try throwOSStatus(err)
|
||||
// `throwOSStatus(_:)` only returns in the `errSecSuccess` case. We know we're
|
||||
// not in that case but the compiler can't figure that out, alas.
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a persistent reference for a generic password keychain item by updating it
|
||||
/// in the keychain if necessary.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - copyResult: The result from the `SecItemCopyMatching` done by `persistentReferenceFor(service:account:password:)`.
|
||||
/// - service: The service name for the item.
|
||||
/// - account: The account for the item.
|
||||
/// - password: The desired password.
|
||||
/// - Returns: A persistent reference to the item.
|
||||
/// - Throws: Any error returned by the Security framework.
|
||||
|
||||
private static func persistentReferenceByUpdating(copyResult: CFTypeRef, service: String, account: String, password: Data) throws -> Data {
|
||||
let copyResult = copyResult as! [String:Any]
|
||||
let persistentRef = copyResult[kSecValuePersistentRef as String] as! NSData as Data
|
||||
let currentPassword = copyResult[kSecValueData as String] as! NSData as Data
|
||||
if password != currentPassword {
|
||||
let err = SecItemUpdate([
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccount: account,
|
||||
] as NSDictionary, [
|
||||
kSecValueData: password
|
||||
] as NSDictionary)
|
||||
try throwOSStatus(err)
|
||||
}
|
||||
return persistentRef
|
||||
}
|
||||
|
||||
/// Returns a persistent reference for a generic password keychain item by adding it to
|
||||
/// the keychain.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - service: The service name for the item.
|
||||
/// - account: The account for the item.
|
||||
/// - password: The desired password.
|
||||
/// - Returns: A persistent reference to the item.
|
||||
/// - Throws: Any error returned by the Security framework.
|
||||
|
||||
private static func persistentReferenceByAdding(service: String, account: String, password: Data) throws -> Data {
|
||||
var addResult: CFTypeRef? = nil
|
||||
let err = SecItemAdd([
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrService: service,
|
||||
kSecAttrAccount: account,
|
||||
kSecValueData: password,
|
||||
kSecReturnPersistentRef: true,
|
||||
] as NSDictionary, &addResult)
|
||||
try throwOSStatus(err)
|
||||
return addResult! as! NSData as Data
|
||||
}
|
||||
|
||||
/// Throws an error if a Security framework call has failed.
|
||||
///
|
||||
/// - Parameter err: The error to check.
|
||||
|
||||
private static func throwOSStatus(_ err: OSStatus) throws {
|
||||
guard err == errSecSuccess else {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: Int(err), userInfo: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
129
main/AppInfoType.swift
Normal file
@@ -0,0 +1,129 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
private let fm = FileManager.default
|
||||
private let documentsDir = try! fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
private let bundleInfoDir = documentsDir.appendingPathComponent("bundleInfo", isDirectory:true)
|
||||
|
||||
|
||||
struct AppInfoType : Decodable {
|
||||
var id: String
|
||||
var name: String?
|
||||
var seller: String?
|
||||
var imageURL: URL?
|
||||
private var remoteImgURL: String?
|
||||
private var cache: Bool?
|
||||
private let localJSON: URL
|
||||
private let localImgURL: URL
|
||||
|
||||
static func initWorkingDir() {
|
||||
try? fm.createDirectory(at: bundleInfoDir, withIntermediateDirectories: true, attributes: nil)
|
||||
// print("init dir: \(bundleInfoDir)")
|
||||
}
|
||||
|
||||
init(id: String) {
|
||||
self.id = id
|
||||
if id == "" {
|
||||
name = "–?–"
|
||||
cache = true
|
||||
localJSON = URL(fileURLWithPath: "")
|
||||
localImgURL = localJSON
|
||||
} else {
|
||||
localJSON = bundleInfoDir.appendingPathComponent("\(id).json")
|
||||
localImgURL = bundleInfoDir.appendingPathComponent("\(id).img")
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
||||
mutating func reload() {
|
||||
if fm.fileExists(atPath: localImgURL.path) {
|
||||
imageURL = localImgURL
|
||||
}
|
||||
guard name == nil, seller == nil,
|
||||
fm.fileExists(atPath: localJSON.path),
|
||||
let attr = try? fm.attributesOfItem(atPath: localJSON.path),
|
||||
attr[FileAttributeKey.size] as! UInt64 > 0 else
|
||||
{
|
||||
// process json only if attributes not set yet,
|
||||
// OR json doesn't exist, OR json is empty
|
||||
return
|
||||
}
|
||||
(name, seller, remoteImgURL) = parseJSON(localJSON)
|
||||
|
||||
if remoteImgURL == nil || imageURL != nil {
|
||||
cache = true
|
||||
}
|
||||
}
|
||||
|
||||
func getImage() -> UIImage? {
|
||||
if let img = imageURL, let data = try? Data(contentsOf: img) {
|
||||
return UIImage(data: data, scale: 2.0)
|
||||
} else if id.hasPrefix("com.apple.") {
|
||||
return appIconApple
|
||||
} else {
|
||||
return appIconUnknown
|
||||
}
|
||||
}
|
||||
|
||||
private func parseJSON(_ location: URL) -> (name: String?, seller: String?, image: String?) {
|
||||
do {
|
||||
let data = try Data.init(contentsOf: location)
|
||||
if
|
||||
let json = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) as? [String: Any],
|
||||
let resAll = json["results"] as? [Any],
|
||||
let res = resAll.first as? [String: Any]
|
||||
{
|
||||
let name = res["trackName"] as? String // trackCensoredName
|
||||
let seller = res["sellerName"] as? String // artistName
|
||||
let image = res["artworkUrl60"] as? String // artworkUrl100
|
||||
return (name, seller, image)
|
||||
} else if id.hasPrefix("com.apple.") {
|
||||
return (String(id.dropFirst(10)), "Apple Inc.", nil)
|
||||
}
|
||||
} catch {}
|
||||
return (nil, nil, nil)
|
||||
}
|
||||
|
||||
mutating func updateIfNeeded(_ updateClosure: () -> Void) {
|
||||
guard cache == nil,
|
||||
let safeId = id.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
|
||||
return
|
||||
}
|
||||
cache = false // meaning: hasn't downloaded yet, but is about to do
|
||||
// print("downloading \(id)")
|
||||
_ = downloadURL("https://itunes.apple.com/lookup?bundleId=\(safeId)", toFile: localJSON).flatMap{
|
||||
// print("downloading \(id) done.")
|
||||
reload()
|
||||
updateClosure()
|
||||
return downloadURL(remoteImgURL, toFile: localImgURL)
|
||||
}.map{
|
||||
// print("downloading \(id) image done.")
|
||||
reload()
|
||||
updateClosure()
|
||||
}
|
||||
}
|
||||
|
||||
enum NetworkError: Error {
|
||||
case url
|
||||
}
|
||||
|
||||
private func downloadURL(_ urlStr: String?, toFile: URL) -> Result<Void, Error> {
|
||||
guard let urlStr = urlStr, let url = URL(string: urlStr) else {
|
||||
return .failure(NetworkError.url)
|
||||
}
|
||||
var result: Result<Void, Error>!
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
URLSession.shared.downloadTask(with: url) { location, response, error in
|
||||
if let loc = location {
|
||||
try? fm.removeItem(at: toFile)
|
||||
try? fm.moveItem(at: loc, to: toFile)
|
||||
result = .success(())
|
||||
} else {
|
||||
result = .failure(error!)
|
||||
}
|
||||
semaphore.signal()
|
||||
}.resume()
|
||||
_ = semaphore.wait(wallTimeout: .distantFuture)
|
||||
return result
|
||||
}
|
||||
}
|
||||
BIN
main/Assets.xcassets/AppIcon.appiconset/Artwork.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
116
main/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_40x40.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_60x60.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_58x58.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_87x87.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_80x80.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_120x120.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_120x120.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_180x180.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_20x20.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40x40.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_29x29.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_58x58.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40x40.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_80x80.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_76x76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_152x152.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_167x167.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Artwork.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_120x120.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_152x152.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_167x167.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_180x180.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_20x20.png
Normal file
|
After Width: | Height: | Size: 258 B |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_29x29.png
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_40x40.png
Normal file
|
After Width: | Height: | Size: 452 B |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_58x58.png
Normal file
|
After Width: | Height: | Size: 593 B |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_60x60.png
Normal file
|
After Width: | Height: | Size: 636 B |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_76x76.png
Normal file
|
After Width: | Height: | Size: 779 B |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_80x80.png
Normal file
|
After Width: | Height: | Size: 774 B |
BIN
main/Assets.xcassets/AppIcon.appiconset/icon_87x87.png
Normal file
|
After Width: | Height: | Size: 837 B |
6
main/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
36
main/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="LaunchIcon.png" translatesAutoresizingMaskIntoConstraints="NO" id="bF5-Lq-Xqf">
|
||||
<rect key="frame" x="186" y="427" width="42" height="42"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="52.173913043478265" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchIcon.png" width="128" height="128"/>
|
||||
</resources>
|
||||
</document>
|
||||
204
main/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,204 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RcB-4v-fd4">
|
||||
<device id="retina4_0" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Services-->
|
||||
<scene sceneID="MN1-aZ-cZt">
|
||||
<objects>
|
||||
<tableViewController id="pdd-aM-sKl" customClass="TVCApps" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="60" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="kj3-8X-TyT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||
<textView key="tableHeaderView" clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" editable="NO" selectable="NO" id="QWn-iX-27k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="501"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<accessibilityTraits key="traits" staticText="YES" notEnabled="YES"/>
|
||||
<bool key="isElement" value="YES"/>
|
||||
</accessibility>
|
||||
<string key="text">AppCheck helps you identify which applications communicate with third parties. It does so by logging DNS network requests. AppCheck learns only the destination addresses, not the actual data that is exchanged.
|
||||
|
||||
For improved readability, AppCheck shows domain names in reversed order (org.example instead of example.org)
|
||||
|
||||
Requests to the same domain in quick succession cannot be monitored due to DNS caching (counters may be inaccurate).
|
||||
|
||||
Your data belongs to you. Therefore, monitoring and analysis take place on your device only. The app does not share any data with us or any other third-party. There is one exception to this rule: To display app icons, AppCheck sends the Bundle ID (e.g., com.apple.Music) of all monitored apps to `itunes.apple.com`.
|
||||
|
||||
⒈ Tap the red button in the upper right corner to start the DNS proxy. The proxy is only accessible locally to apps on this device.
|
||||
⒉ The proxy monitors DNS requests in the background.
|
||||
⒊ Use your apps as usual.
|
||||
⒋ Come back to AppCheck to see the results.</string>
|
||||
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="AppBundleCell" textLabel="d1D-Z8-ddD" detailTextLabel="8LO-2o-dzK" imageView="zHu-V9-cbv" rowHeight="60" style="IBUITableViewCellStyleSubtitle" id="Jcl-oS-Z3B">
|
||||
<rect key="frame" x="0.0" y="529" width="320" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Jcl-oS-Z3B" id="B7M-RN-vvx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="d1D-Z8-ddD">
|
||||
<rect key="frame" x="15" y="10" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8LO-2o-dzK">
|
||||
<rect key="frame" x="15" y="33.5" width="44" height="14.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" id="zHu-V9-cbv">
|
||||
<rect key="frame" x="16" y="0.0" width="60" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="WcC-nb-Vf5" kind="show" id="Z7t-Jy-aNR"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="pdd-aM-sKl" id="4fX-iP-7Oa"/>
|
||||
<outlet property="delegate" destination="pdd-aM-sKl" id="3RN-az-SYU"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Services" id="nY5-jL-QT9">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="trash" id="PPO-Wv-RWf">
|
||||
<connections>
|
||||
<action selector="clickToolbarLeft:" destination="pdd-aM-sKl" id="iFj-4n-lvg"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" tag="-1" title="<State>" id="SfA-a1-N1w">
|
||||
<connections>
|
||||
<action selector="clickToolbarRight:" destination="pdd-aM-sKl" id="zgS-Wn-apQ"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="welcomeMessage" destination="QWn-iX-27k" id="jA3-Dz-7zu"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="jfx-iA-E0v" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="742.5" y="167.95774647887325"/>
|
||||
</scene>
|
||||
<!--Requests-->
|
||||
<scene sceneID="ZCV-Yx-jjW">
|
||||
<objects>
|
||||
<tableViewController id="WcC-nb-Vf5" customClass="TVCRequests" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="nRF-dc-dC2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="RequestCell" textLabel="Rnk-SP-UHm" detailTextLabel="ovQ-lJ-hWJ" style="IBUITableViewCellStyleValue1" id="uv0-9B-Zbb">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="uv0-9B-Zbb" id="6vH-Du-gCg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Rnk-SP-UHm">
|
||||
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ovQ-lJ-hWJ">
|
||||
<rect key="frame" x="260" y="12" width="44" height="20.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" systemColor="tertiaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.29999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="h7Z-Qr-pJ5" kind="show" id="TPa-Zn-eOs"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="WcC-nb-Vf5" id="szM-iI-Jgi"/>
|
||||
<outlet property="delegate" destination="WcC-nb-Vf5" id="sBd-BW-Wg6"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Requests" prompt="com.app.Example" id="TvD-8U-F05"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Gdi-Xi-JUL" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1448" y="168"/>
|
||||
</scene>
|
||||
<!--Occurrences-->
|
||||
<scene sceneID="ws3-sK-l8m">
|
||||
<objects>
|
||||
<tableViewController id="h7Z-Qr-pJ5" customClass="TVCRequestDetails" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="4ms-FO-Fge">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="RequestDetailCell" textLabel="J2P-mU-Vad" style="IBUITableViewCellStyleDefault" id="ZCA-Dz-i92">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ZCA-Dz-i92" id="nxe-48-jAQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" enabled="NO" adjustsFontSizeToFit="NO" id="J2P-mU-Vad">
|
||||
<rect key="frame" x="16" y="0.0" width="288" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="h7Z-Qr-pJ5" id="fyW-Av-fWY"/>
|
||||
<outlet property="delegate" destination="h7Z-Qr-pJ5" id="gBq-jA-u5V"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Occurrences" prompt="com.domain.network.cdn" id="bys-2u-rHs"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="UxH-PH-KQy" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2147" y="168"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="bDO-X1-bCe">
|
||||
<objects>
|
||||
<navigationController id="RcB-4v-fd4" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="HWd-73-m8j">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="pdd-aM-sKl" kind="relationship" relationship="rootViewController" id="oMe-a0-xN7"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="8j4-AX-JBN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="40" y="168"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
78
main/BundleIcon.swift
Normal file
@@ -0,0 +1,78 @@
|
||||
import UIKit
|
||||
|
||||
let appIconApple = generateAppleIcon()
|
||||
let appIconUnknown = generateUnknownIcon()
|
||||
|
||||
func generateAppleIcon() -> UIImage? {
|
||||
let rect = CGRect(x: 0, y: 0, width: 30, height: 30)
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
|
||||
|
||||
// #colorLiteral(red: 0.5843137503, green: 0.8235294223, blue: 0.4196078479, alpha: 1).setFill()
|
||||
// UIBezierPath(roundedRect: rect, cornerRadius: 0).fill()
|
||||
// print("drawing")
|
||||
let fs = 36 as CGFloat
|
||||
let hFont = UIFont.systemFont(ofSize: fs)
|
||||
var attrib = [
|
||||
NSAttributedString.Key.font: hFont,
|
||||
NSAttributedString.Key.foregroundColor: UIColor.gray
|
||||
]
|
||||
|
||||
let str = "" as NSString
|
||||
let actualHeight = str.size(withAttributes: attrib).height
|
||||
attrib[NSAttributedString.Key.font] = hFont.withSize(fs * fs / actualHeight)
|
||||
|
||||
let strW = str.size(withAttributes: attrib).width
|
||||
str.draw(at: CGPoint(x: (rect.size.width - strW) / 2.0, y: -3), withAttributes: attrib)
|
||||
|
||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return img
|
||||
}
|
||||
|
||||
func generateUnknownIcon() -> UIImage? {
|
||||
let rect = CGRect(x: 0, y: 0, width: 30, height: 30)
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
let lineWidth: CGFloat = 0.5
|
||||
let corner: CGFloat = 6.75
|
||||
let c = corner / CGFloat.pi + lineWidth/2
|
||||
let sz: CGFloat = rect.height
|
||||
let m = sz / 2
|
||||
let r1 = 0.2 * sz, r2 = sqrt(2 * r1 * r1)
|
||||
|
||||
// diagonal
|
||||
context.lineFromTo(x1: c, y1: c, x2: sz-c, y2: sz-c)
|
||||
context.lineFromTo(x1: c, y1: sz-c, x2: sz-c, y2: c)
|
||||
// horizontal
|
||||
context.lineFromTo(x1: 0, y1: m, x2: sz, y2: m)
|
||||
context.lineFromTo(x1: 0, y1: m + r1, x2: sz, y2: m + r1)
|
||||
context.lineFromTo(x1: 0, y1: m - r1, x2: sz, y2: m - r1)
|
||||
// vertical
|
||||
context.lineFromTo(x1: m, y1: 0, x2: m, y2: sz)
|
||||
context.lineFromTo(x1: m + r1, y1: 0, x2: m + r1, y2: sz)
|
||||
context.lineFromTo(x1: m - r1, y1: 0, x2: m - r1, y2: sz)
|
||||
// circles
|
||||
context.addEllipse(in: CGRect(x: m - r1, y: m - r1, width: 2*r1, height: 2*r1))
|
||||
context.addEllipse(in: CGRect(x: m - r2, y: m - r2, width: 2*r2, height: 2*r2))
|
||||
let r3 = CGRect(x: c, y: c, width: sz - 2*c, height: sz - 2*c)
|
||||
context.addEllipse(in: r3)
|
||||
context.addRect(r3)
|
||||
|
||||
UIColor.clear.setFill()
|
||||
UIColor.gray.setStroke()
|
||||
let rounded = UIBezierPath(roundedRect: rect.insetBy(dx: lineWidth/2, dy: lineWidth/2), cornerRadius: corner)
|
||||
rounded.lineWidth = lineWidth
|
||||
rounded.stroke()
|
||||
|
||||
let img = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return img
|
||||
}
|
||||
|
||||
extension CGContext {
|
||||
func lineFromTo(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) {
|
||||
self.move(to: CGPoint(x: x1, y: y1))
|
||||
self.addLine(to: CGPoint(x: x2, y: y2))
|
||||
}
|
||||
}
|
||||
45
main/Info.plist
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
main/LaunchIcon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
23
main/ProxyState.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
import UIKit
|
||||
|
||||
enum ProxyState {
|
||||
case stopped, unkown, running
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
class DotState: UIImage {
|
||||
var status: ProxyState = .stopped // { didSet { }}
|
||||
|
||||
override func draw(in rect: CGRect) {
|
||||
let pt = CGPoint(x: rect.midX, y: rect.midY)
|
||||
let r = min(rect.size.width, rect.size.height) / 2.0 * 0.6
|
||||
switch status {
|
||||
case .stopped: #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1).setFill()
|
||||
case .unkown: #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1).setFill()
|
||||
case .running: #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1).setFill()
|
||||
}
|
||||
UIBezierPath(arcCenter: pt, radius: r, startAngle: 0, endAngle: 10, clockwise: true).fill()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
main/Settings.bundle/Root.plist
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>StringsTable</key>
|
||||
<string>Root</string>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Kill proxy on startup</string>
|
||||
<key>Key</key>
|
||||
<string>kill_proxy</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Delete DB on startup</string>
|
||||
<key>Key</key>
|
||||
<string>kill_db</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
main/Settings.bundle/en.lproj/Root.strings
Normal file
139
main/TVCApps.swift
Normal file
@@ -0,0 +1,139 @@
|
||||
import UIKit
|
||||
|
||||
|
||||
|
||||
class TVCApps: UITableViewController {
|
||||
|
||||
private let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
private var dataSource: [AppInfoType] = []
|
||||
@IBOutlet private var welcomeMessage: UITextView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.welcomeMessage.frame.size.height = 0
|
||||
AppInfoType.initWorkingDir()
|
||||
|
||||
NotificationCenter.default.addObserver(forName: .init("ChangedStateGlassDNS"), object: nil, queue: OperationQueue.main) { [weak self] notification in
|
||||
// let stateView = self.navigationItem.rightBarButtonItem?.customView as? ProxyStateView
|
||||
// stateView?.status = (notification.object as! Bool ? .running : .stopped)
|
||||
// let active = notification.object as! Bool
|
||||
self?.changeState(notification.object as! Bool)
|
||||
}
|
||||
// pull-to-refresh
|
||||
// tableView.refreshControl = UIRefreshControl()
|
||||
// tableView.refreshControl?.addTarget(self, action: #selector(reloadDataSource(_:)), for: .valueChanged)
|
||||
// performSelector(inBackground: #selector(reloadDataSource(_:)), with: nil)
|
||||
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] _ in
|
||||
self?.reloadDataSource(nil)
|
||||
}
|
||||
// navigationItem.leftBarButtonItem?.title = "\u{2699}\u{0000FE0E}☰"
|
||||
// navigationItem.leftBarButtonItem?.setTitleTextAttributes([NSAttributedString.Key.font : UIFont.systemFont(ofSize: 32)], for: .normal)
|
||||
}
|
||||
|
||||
@IBAction func clickToolbarLeft(_ sender: Any) {
|
||||
let alert = UIAlertController(title: "Clear results?",
|
||||
message: "You are about to delete all results that have been logged in the past. Continue?", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
alert.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { [weak self] _ in
|
||||
try? SQLiteDatabase.open(path: DB_PATH).destroyContent()
|
||||
self?.reloadDataSource(nil)
|
||||
}))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func clickToolbarRight(_ sender: Any) {
|
||||
let inactive = (self.navigationItem.rightBarButtonItem?.tag == 0)
|
||||
let alert = UIAlertController(title: "\(inactive ? "En" : "Dis")able Proxy?",
|
||||
message: "The DNS proxy is currently \(inactive ? "dis" : "en")abled, do you want to proceed and \(inactive ? "en" : "dis")able logging?", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||
alert.addAction(UIAlertAction(title: inactive ? "Enable" : "Disable", style: .default, handler: { [weak self] _ in
|
||||
self?.changeState(inactive)
|
||||
self?.appDelegate.setProxyEnabled(inactive)
|
||||
}))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func changeState(_ active: Bool) {
|
||||
let stateView = self.navigationItem.rightBarButtonItem
|
||||
if stateView?.tag == 0 && !active,
|
||||
stateView?.tag == 1 && active {
|
||||
return // don't need to change, already correct state
|
||||
}
|
||||
stateView?.tag = (active ? 1 : 0)
|
||||
stateView?.title = (active ? "Active" : "Inactive")
|
||||
stateView?.tintColor = (active ? .systemGreen : .systemRed)
|
||||
// let newButton = UIBarButtonItem(barButtonSystemItem: (active ? .pause : .play), target: self, action: #selector(clickToolbarRight(_:)))
|
||||
// newButton.tintColor = (active ? .systemRed : .systemGreen)
|
||||
// newButton.tag = (active ? 1 : 0)
|
||||
// self.navigationItem.setRightBarButton(newButton, animated: true)
|
||||
}
|
||||
|
||||
private func updateCellAt(_ index: Int) {
|
||||
DispatchQueue.main.async {
|
||||
guard index >= 0 else {
|
||||
self.welcomeMessage.frame.size.height = (self.dataSource.count == 0 ? self.view.frame.size.height : 0)
|
||||
self.tableView.reloadData()
|
||||
return
|
||||
}
|
||||
if let idx = self.tableView.indexPathsForVisibleRows?.first(where: { indexPath -> Bool in
|
||||
indexPath.row == index
|
||||
}) {
|
||||
self.tableView.reloadRows(at: [idx], with: .automatic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let index = tableView.indexPathForSelectedRow?.row {
|
||||
let info = dataSource[index]
|
||||
segue.destination.navigationItem.prompt = info.name ?? info.id
|
||||
(segue.destination as? TVCRequests)?.appBundleId = info.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Delegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return dataSource.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "AppBundleCell")!
|
||||
let appBundle = dataSource[indexPath.row]
|
||||
cell.textLabel?.text = appBundle.name ?? appBundle.id
|
||||
cell.detailTextLabel?.text = appBundle.seller
|
||||
|
||||
cell.imageView?.image = appBundle.getImage()
|
||||
cell.imageView?.layer.cornerRadius = 6.75
|
||||
cell.imageView?.layer.masksToBounds = true
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Data Source
|
||||
|
||||
@objc private func reloadDataSource(_ sender : Any?) {
|
||||
DispatchQueue.global().async {
|
||||
self.dataSource = self.sqliteAppList().map { AppInfoType(id: $0) } // will load from HD
|
||||
self.updateCellAt(-1)
|
||||
for i in self.dataSource.indices {
|
||||
self.dataSource[i].updateIfNeeded { [weak self] in
|
||||
self?.updateCellAt(i)
|
||||
}
|
||||
}
|
||||
if let refreshControl = sender as? UIRefreshControl {
|
||||
refreshControl.endRefreshing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sqliteAppList() -> [String] {
|
||||
// return ["unkown", "com.apple.Fitness", "com.apple.AppStore", "com.apple.store.Jolly", "com.apple.supportapp", "com.apple.TVRemote", "com.apple.Bridge", "com.apple.calculator", "com.apple.mobilecal", "com.apple.camera", "com.apple.classroom", "com.apple.clips", "com.apple.mobiletimer", "com.apple.compass", "com.apple.MobileAddressBook", "com.apple.facetime", "com.apple.appleseed.FeedbackAssistant", "com.apple.mobileme.fmf1", "com.apple.mobileme.fmip1", "com.apple.findmy", "com.apple.DocumentsApp", "com.apple.gamecenter", "com.apple.mobilegarageband", "com.apple.Health", "com.apple.Antimony", "com.apple.Home", "com.apple.iBooks", "com.apple.iCloudDriveApp", "com.apple.iMovie", "com.apple.itunesconnect.mobile", "com.apple.MobileStore", "com.apple.itunesu", "com.apple.Keynote", "com.apple.musicapps.remote", "com.apple.mobilemail", "com.apple.Maps", "com.apple.measure", "com.apple.MobileSMS", "com.apple.Music", "com.apple.musicmemos", "com.apple.news", "com.apple.mobilenotes", "com.apple.Numbers", "com.apple.Pages", "com.apple.mobilephone", "com.apple.Photo-Booth", "com.apple.mobileslideshow", "com.apple.Playgrounds", "com.apple.podcasts", "com.apple.reminders", "com.apple.Remote", "com.apple.mobilesafari", "com.apple.Preferences", "is.workflow.my.app", "com.apple.shortcuts", "com.apple.SiriViewService", "com.apple.stocks", "com.apple.tips", "com.apple.movietrailers", "com.apple.tv", "com.apple.videos", "com.apple.VoiceMemos", "com.apple.Passbook", "com.apple.weather", "com.apple.wwdc"]
|
||||
// return ["com.apple.backupd", "com.apple.searchd", "com.apple.SafariBookmarksSyncAgent", "com.apple.AppStore", "com.apple.mobilemail", "com.apple.iBooks", "com.apple.icloud.searchpartyd", "com.apple.ap.adprivacyd", "com.apple.bluetoothd", "com.apple.commcentermobilehelper", "com.apple", "com.apple.coreidv.coreidvd", "com.apple.online-auth-agent", "com.apple.tipsd", "com.apple.wifid", "com.apple.captiveagent", "com.apple.pipelined", "com.apple.bird", "com.apple.amfid", "com.apple.nsurlsessiond", "com.apple.Preferences", "com.apple.sharingd", "com.apple.UserEventAgent", "com.apple.healthappd"]
|
||||
guard let db = try? SQLiteDatabase.open(path: DB_PATH) else {
|
||||
return []
|
||||
}
|
||||
return db.appList()
|
||||
}
|
||||
}
|
||||
24
main/TVCRequestDetails.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
import UIKit
|
||||
|
||||
class TVCRequestDetails: UITableViewController {
|
||||
|
||||
public var dataSource: [Int64] = []
|
||||
private let dateFormatter = DateFormatter()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return dataSource.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "RequestDetailCell")!
|
||||
let intVal = dataSource[indexPath.row]
|
||||
let date = Date.init(timeIntervalSince1970: Double(intVal))
|
||||
cell.textLabel?.text = dateFormatter.string(from: date)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
64
main/TVCRequests.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
import UIKit
|
||||
|
||||
class TVCRequests: UITableViewController {
|
||||
|
||||
public var appBundleId: String? = nil
|
||||
private var dataSource: [(String, [Int64])] = []
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// pull-to-refresh
|
||||
tableView.refreshControl = UIRefreshControl()
|
||||
tableView.refreshControl?.addTarget(self, action: #selector(reloadDataSource(_:)), for: .valueChanged)
|
||||
performSelector(inBackground: #selector(reloadDataSource(_:)), with: nil)
|
||||
NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] _ in
|
||||
self?.reloadDataSource(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func reloadDataSource(_ sender : Any?) {
|
||||
// dataSource = [("hi", [1, 2]), ("there", [2, 4, 8, 1580472632]), ("dude", [1, 2, 3])]
|
||||
// return ()
|
||||
dataSource = []
|
||||
guard let bundleId = appBundleId, let db = try? SQLiteDatabase.open(path: DB_PATH) else {
|
||||
return
|
||||
}
|
||||
var list: [String: [Int64]] = [:]
|
||||
db.dnsQueriesForApp(appIdentifier: bundleId as NSString) { query in
|
||||
let x = query.dns.split(separator: ".").reversed().joined(separator: ".")
|
||||
if list[x] == nil {
|
||||
list[x] = []
|
||||
}
|
||||
list[x]?.append(query.ts)
|
||||
}
|
||||
dataSource = list.sorted{ $0.0 < $1.0 }
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.reloadData()
|
||||
if let refreshControl = sender as? UIRefreshControl {
|
||||
refreshControl.endRefreshing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let index = tableView.indexPathForSelectedRow?.row {
|
||||
let info = dataSource[index]
|
||||
segue.destination.navigationItem.prompt = info.0
|
||||
(segue.destination as? TVCRequestDetails)?.dataSource = info.1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Data Source
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return dataSource.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "RequestCell")!
|
||||
let info = dataSource[indexPath.row]
|
||||
cell.textLabel?.text = info.0
|
||||
cell.detailTextLabel?.text = "\(info.1.count)"
|
||||
return cell
|
||||
}
|
||||
}
|
||||
18
main/main.entitlements
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>dns-proxy</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.vpn.api</key>
|
||||
<array>
|
||||
<string>allow-vpn</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.de.uni-bamberg.psi.AppCheck</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||