├── BuildTools ├── Empty.swift ├── Package.swift └── Package.resolved ├── fastlane ├── metadata │ ├── en-US │ │ ├── name.txt │ │ ├── apple_tv_privacy_policy.txt │ │ ├── subtitle.txt │ │ ├── support_url.txt │ │ ├── marketing_url.txt │ │ ├── privacy_url.txt │ │ ├── keywords.txt │ │ ├── release_notes.txt │ │ └── promotional_text.txt │ ├── copyright.txt │ ├── primary_first_sub_category.txt │ ├── review_information │ │ ├── phone_number.txt │ │ ├── demo_user.txt │ │ ├── demo_password.txt │ │ ├── first_name.txt │ │ ├── last_name.txt │ │ ├── email_address.txt │ │ └── notes.txt │ ├── primary_category.txt │ ├── primary_second_sub_category.txt │ ├── secondary_category.txt │ ├── secondary_first_sub_category.txt │ └── secondary_second_sub_category.txt ├── Appfile ├── Deliverfile.swift ├── Fastfile ├── README.md ├── Snapfile └── screenshots │ └── README.txt ├── Gemfile ├── icon.png ├── icon.psd ├── settings_icon.psd ├── ec3730 ├── Assets.xcassets │ ├── Contents.json │ ├── More.imageset │ │ ├── dots.pdf │ │ └── Contents.json │ ├── at.imageset │ │ ├── at-sign.pdf │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── icon.png │ │ ├── icon-40.png │ │ ├── icon-72.png │ │ ├── icon-76.png │ │ ├── icon@2x.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72@2x.png │ │ ├── icon-76@2x.png │ │ ├── icon-small.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-small-50.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── ios-marketing.png │ │ ├── icon-small-50@2x.png │ │ ├── notification-icon@2x.png │ │ ├── notification-icon@3x.png │ │ ├── notification-icon~ipad.png │ │ └── notification-icon~ipad@2x.png │ ├── Connected.imageset │ │ ├── wifi.pdf │ │ └── Contents.json │ ├── star.imageset │ │ ├── star-rate.pdf │ │ └── Contents.json │ ├── Network.imageset │ │ ├── world-2.pdf │ │ └── Contents.json │ ├── More_selected.imageset │ │ ├── dots.pdf │ │ └── Contents.json │ ├── Ping.imageset │ │ ├── direction-56.pdf │ │ └── Contents.json │ ├── Source.imageset │ │ ├── window-dev.pdf │ │ └── Contents.json │ ├── Disconnected.imageset │ │ ├── wifi-off.pdf │ │ └── Contents.json │ ├── twitter.imageset │ │ ├── logo-twitter.pdf │ │ └── Contents.json │ ├── Connected_selected.imageset │ │ ├── wifi.pdf │ │ └── Contents.json │ ├── Device.imageset │ │ ├── phone-camera-back.pdf │ │ └── Contents.json │ ├── Host.imageset │ │ ├── icons8-website-30.png │ │ ├── icons8-website-60.png │ │ ├── icons8-website-90.png │ │ └── Contents.json │ ├── Network_selected.imageset │ │ ├── world-2.pdf │ │ └── Contents.json │ ├── Settings.imageset │ │ ├── settings-gear.pdf │ │ └── Contents.json │ ├── AkhmadDark.appiconset │ │ ├── Source 2_dark.png │ │ └── Contents.json │ ├── Ping_selected.imageset │ │ ├── direction-56.pdf │ │ └── Contents.json │ ├── Source_selected.imageset │ │ ├── window-dev.pdf │ │ └── Contents.json │ ├── AkhmadLight.appiconset │ │ ├── Source 2_light.png │ │ └── Contents.json │ ├── Disconnected_selected.imageset │ │ ├── wifi-off.pdf │ │ └── Contents.json │ ├── Settings_selected.imageset │ │ ├── settings-gear.pdf │ │ └── Contents.json │ ├── Device_selected.imageset │ │ ├── phone-camera-back.pdf │ │ └── Contents.json │ ├── AppIconThumb.imageset │ │ └── Contents.json │ ├── AkhmadDarkThumb.imageset │ │ └── Contents.json │ └── AkhmadLightThumb.imageset │ │ └── Contents.json ├── DemoData │ ├── GoogleWebRiskSectionModel.json │ ├── WhoIsXmlCategorizationSectionModel.json │ ├── Utility.swift │ ├── WhoIsXmlGeoLocationSectionModel.json │ ├── WhoisXmlReputationSectionModel.json │ └── WhoIsXmlContactsSectionModel.json ├── Extensions │ ├── Interface.swift │ ├── UIAlertController.swift │ ├── Reachability.swift │ ├── Error.swift │ ├── Encodable+dictionary.swift │ ├── UITextView.swift │ ├── SimpleAppIcons+NetUtils.swift │ ├── UIView.swift │ ├── UIImage+Codable.swift │ └── UserDefaults.swift ├── Views │ ├── CopyCell │ │ ├── NewCopyCellProtocol.swift │ │ ├── CopyCellDetailStyle.swift │ │ ├── CopyCellMultipleTypesView.swift │ │ ├── CopyCellChevronView.swift │ │ ├── CopyCellContentView.swift │ │ ├── CopyCellSingleItemRowView.swift │ │ ├── CopyCellStyleConfig.swift │ │ └── CopyCellToggleableItemRowView.swift │ ├── Host │ │ ├── HostModelWrapperView.swift │ │ ├── HostViewSectionFocusView.swift │ │ ├── HostViewSectionContent.swift │ │ ├── HostResult.swift │ │ ├── HostBarView.swift │ │ └── HostSectionOrganizerView.swift │ ├── Source │ │ ├── Snap │ │ │ ├── SnapPoint.swift │ │ │ └── SnapState.swift │ │ ├── RunestoneView.swift │ │ └── WebWrapperView.swift │ ├── TappedText.swift │ ├── FSDisclosureGroup.swift │ ├── ShareSheetView.swift │ ├── WebkitOverlayView.swift │ ├── Interface │ │ ├── InterfaceConnectionBarView.swift │ │ └── InterfaceListView.swift │ ├── UIViewControllerView.swift │ └── Device │ │ └── DeviceInfoSectionView.swift ├── Models │ ├── Device │ │ ├── DeviceInfoSectionModel.swift │ │ ├── Sections │ │ │ ├── FingerprintInfoModel.swift │ │ │ ├── DataUsageInfoModel.swift │ │ │ ├── ProcessInfoModel.swift │ │ │ └── CarrierInfoModel.swift │ │ └── DeviceInfoModel.swift │ ├── FingerprintModel.swift │ ├── Sections │ │ ├── LocalDnsModel.swift │ │ ├── WhoisXmlReputationSectionModel.swift │ │ └── UrlParsedModel.swift │ └── ReachabilityModel.swift ├── Modifiers │ └── PaddingListModifier.swift ├── Data Feeds │ ├── Google │ │ ├── GoogleWebRiskRecord.swift │ │ └── GoogleWebRiskCellManager.swift │ ├── DataFeed.swift │ ├── DataFeedPurchaseProtocol.swift │ ├── CoreData │ │ └── NetUtilsCoreData.xcdatamodeld │ │ │ └── Usage.xcdatamodel │ │ │ └── contents │ ├── DataFeedService.swift │ ├── WhoisXML │ │ ├── WhoIsXmlGeoLoactionService.swift │ │ ├── WhoIsXmlCategorizationService.swift │ │ ├── WhoisXMLDnsService.swift │ │ ├── WhoisXmlContactsService.swift │ │ ├── WhoIsXmlGeoLocationResult.swift │ │ └── WhoisXmlDnsCells.swift │ ├── DataFeedEndpoint.swift │ ├── DataFeedCells.swift │ ├── DataFeedSubscription.swift │ ├── URL Parse │ │ └── UrlParsedFeed.swift │ ├── DataFeedErrors.swift │ ├── DataFeedSubscriptionCellManager.swift │ └── LocalDns │ │ └── LocalDns.swift ├── Protocols │ └── InAppPurchaseUpdateDelegate.swift ├── Persistence │ ├── HostData.swift │ ├── HostDataGroup.swift │ └── Persistence.swift ├── ec3730.entitlements ├── DefaultsSwitch.swift ├── Controllers │ └── EZPanel.swift ├── TimedCache.swift ├── CenterTextTableViewCell.swift ├── LoadingCell.swift ├── CopyLabel.swift ├── WiFiSSID.swift ├── CopyCell.swift ├── CopyDetailCell.swift ├── IAPFooterView.swift ├── Info.plist └── CellManager.swift ├── External └── Themes │ ├── Sources │ ├── RunestoneOneDarkTheme │ │ ├── Colors.xcassets │ │ │ ├── Contents.json │ │ │ ├── OneDarkAqua.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkBlue.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkGreen.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkPurple.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkRed.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkYellow.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkBackground.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkComment.colorset │ │ │ │ └── Contents.json │ │ │ ├── OneDarkCurrentLine.colorset │ │ │ │ └── Contents.json │ │ │ └── OneDarkForeground.colorset │ │ │ │ └── Contents.json │ │ └── OneDarkTheme.swift │ ├── RunestoneTomorrowTheme │ │ ├── Colors.xcassets │ │ │ ├── Contents.json │ │ │ ├── TomorrowAqua.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowBlue.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowRed.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowComment.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowGreen.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowOrange.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowPurple.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowYellow.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowBackground.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowCurrentLine.colorset │ │ │ │ └── Contents.json │ │ │ └── TomorrowForeground.colorset │ │ │ │ └── Contents.json │ │ └── TomorrowTheme.swift │ ├── RunestoneTomorrowNightTheme │ │ ├── Colors.xcassets │ │ │ ├── Contents.json │ │ │ ├── TomorrowNightAqua.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightBlue.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightRed.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightComment.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightGreen.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightOrange.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightPurple.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightYellow.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightBackground.colorset │ │ │ │ └── Contents.json │ │ │ ├── TomorrowNightCurrentLine.colorset │ │ │ │ └── Contents.json │ │ │ └── TomorrowNightForeground.colorset │ │ │ │ └── Contents.json │ │ └── TomorrowNightTheme.swift │ ├── RunestoneThemeCommon │ │ ├── EditorTheme.swift │ │ └── HighlightName.swift │ └── RunestonePlainTextTheme │ │ └── PlainTextTheme.swift │ ├── .gitignore │ ├── README.md │ └── Package.swift ├── ec3730.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ec3730.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── twodayslate.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── ec3730Tests ├── Info.plist └── ec3730Tests.swift ├── ec3730UITests └── Info.plist ├── .swiftformat ├── Makefile ├── README.md ├── .swiftlint.yml └── .gitignore /BuildTools/Empty.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/name.txt: -------------------------------------------------------------------------------- 1 | NetUtils 2 | -------------------------------------------------------------------------------- /fastlane/metadata/copyright.txt: -------------------------------------------------------------------------------- 1 | Zachary Gorak 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/phone_number.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/apple_tv_privacy_policy.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/subtitle.txt: -------------------------------------------------------------------------------- 1 | Network Utilities 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_category.txt: -------------------------------------------------------------------------------- 1 | PRODUCTIVITY 2 | -------------------------------------------------------------------------------- /fastlane/metadata/primary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/demo_user.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_category.txt: -------------------------------------------------------------------------------- 1 | UTILITIES 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_first_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/secondary_second_sub_category.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/demo_password.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/first_name.txt: -------------------------------------------------------------------------------- 1 | Zachary 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/last_name.txt: -------------------------------------------------------------------------------- 1 | Gorak 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/support_url.txt: -------------------------------------------------------------------------------- 1 | http://zac.gorak.us/ios 2 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/marketing_url.txt: -------------------------------------------------------------------------------- 1 | http://zac.gorak.us/ios 2 | -------------------------------------------------------------------------------- /fastlane/metadata/review_information/email_address.txt: -------------------------------------------------------------------------------- 1 | zac@gorak.us 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/icon.png -------------------------------------------------------------------------------- /icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/icon.psd -------------------------------------------------------------------------------- /fastlane/metadata/en-US/privacy_url.txt: -------------------------------------------------------------------------------- 1 | http://zac.gorak.us/ios/privacy.html 2 | -------------------------------------------------------------------------------- /settings_icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/settings_icon.psd -------------------------------------------------------------------------------- /fastlane/metadata/review_information/notes.txt: -------------------------------------------------------------------------------- 1 | Source code: https://github.com/twodayslate/NetUtils 2 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /fastlane/metadata/en-US/keywords.txt: -------------------------------------------------------------------------------- 1 | networking, utilities, ping, source, reachability, whois, dns, ip, ipv4, ipv6, web, risk, cyber 2 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/More.imageset/dots.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/More.imageset/dots.pdf -------------------------------------------------------------------------------- /fastlane/metadata/en-US/release_notes.txt: -------------------------------------------------------------------------------- 1 | - Bug fixes and improvements 2 | 3 | Reviews are welcomed and appreciated - thank you for the support! -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/at.imageset/at-sign.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/at.imageset/at-sign.pdf -------------------------------------------------------------------------------- /fastlane/metadata/en-US/promotional_text.txt: -------------------------------------------------------------------------------- 1 | The most unified & consistent WHOIS, DNS, and reputation lookups in the App Store - now with Web Risk! 2 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Connected.imageset/wifi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Connected.imageset/wifi.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/star.imageset/star-rate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/star.imageset/star-rate.pdf -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Network.imageset/world-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Network.imageset/world-2.pdf -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-72.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/More_selected.imageset/dots.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/More_selected.imageset/dots.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Ping.imageset/direction-56.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Ping.imageset/direction-56.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Source.imageset/window-dev.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Source.imageset/window-dev.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-small.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Disconnected.imageset/wifi-off.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Disconnected.imageset/wifi-off.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/twitter.imageset/logo-twitter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/twitter.imageset/logo-twitter.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-small-50.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/ios-marketing.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Connected_selected.imageset/wifi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Connected_selected.imageset/wifi.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Device.imageset/phone-camera-back.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Device.imageset/phone-camera-back.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Host.imageset/icons8-website-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Host.imageset/icons8-website-30.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Host.imageset/icons8-website-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Host.imageset/icons8-website-60.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Host.imageset/icons8-website-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Host.imageset/icons8-website-90.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Network_selected.imageset/world-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Network_selected.imageset/world-2.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Settings.imageset/settings-gear.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Settings.imageset/settings-gear.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AkhmadDark.appiconset/Source 2_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AkhmadDark.appiconset/Source 2_dark.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Ping_selected.imageset/direction-56.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Ping_selected.imageset/direction-56.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Source_selected.imageset/window-dev.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Source_selected.imageset/window-dev.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AkhmadLight.appiconset/Source 2_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AkhmadLight.appiconset/Source 2_light.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Disconnected_selected.imageset/wifi-off.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Disconnected_selected.imageset/wifi-off.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Settings_selected.imageset/settings-gear.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Settings_selected.imageset/settings-gear.pdf -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Device_selected.imageset/phone-camera-back.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/Device_selected.imageset/phone-camera-back.pdf -------------------------------------------------------------------------------- /ec3730/DemoData/GoogleWebRiskSectionModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "threat": { 3 | "threatTypes": [ 4 | "MALWARE" 5 | ], 6 | "expireTime": "2019-07-17T15:01:23.045123456Z" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/NetUtils/master/ec3730/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png -------------------------------------------------------------------------------- /ec3730/Extensions/Interface.swift: -------------------------------------------------------------------------------- 1 | import NetUtils 2 | 3 | extension Interface: Identifiable { 4 | public var id: Int { 5 | "\(name)\(address ?? "")\(debugDescription)".hashValue 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ec3730.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /External/Themes/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/NewCopyCellProtocol.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | protocol NewCopyCellProtocol: View, Hashable, Identifiable { 4 | typealias Shareable = Codable & Hashable 5 | var shareable: any Shareable { get } 6 | } 7 | -------------------------------------------------------------------------------- /ec3730.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneThemeCommon/EditorTheme.swift: -------------------------------------------------------------------------------- 1 | import Runestone 2 | import UIKit 3 | 4 | public protocol EditorTheme: Runestone.Theme { 5 | var backgroundColor: UIColor { get } 6 | var userInterfaceStyle: UIUserInterfaceStyle { get } 7 | } 8 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Device.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "phone-camera-back.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AppIconThumb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "settings_icon.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Device_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "phone-camera-back.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AkhmadDarkThumb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Source 2_dark.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AkhmadLightThumb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Source 2_light.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ec3730/Extensions/UIAlertController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/12/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIAlertController {} 13 | -------------------------------------------------------------------------------- /ec3730.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /External/Themes/README.md: -------------------------------------------------------------------------------- 1 | # Themes 2 | 3 | Contains the themes used by the example project. The following themes are included: 4 | 5 | - One Dark 6 | - Plain Text 7 | - Tomorrow 8 | - Tomorrow Night 9 | 10 | The RunestoneThemeCommon package includes the types used across all of the themes and the example project. 11 | -------------------------------------------------------------------------------- /ec3730.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AkhmadDark.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Source 2_dark.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/AkhmadLight.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Source 2_light.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Connected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wifi.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/at.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "at-sign.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "star-rate.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Models/Device/DeviceInfoSectionModel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | class DeviceInfoSectionModel: ObservableObject, Identifiable { 4 | var title: String = "" 5 | @MainActor @Published var enabled: Bool = false 6 | @MainActor @Published var rows = [CopyCellType]() 7 | 8 | @MainActor func reload() async {} 9 | } 10 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/More.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "dots.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Network.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "world-2.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Ping.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "direction-56.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Source.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "window-dev.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Connected_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wifi.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Disconnected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wifi-off.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/More_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "dots.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Network_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "world-2.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-gear.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/twitter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo-twitter.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Ping_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "direction-56.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Source_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "window-dev.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Extensions/Reachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reachability.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 9/3/18. 6 | // Copyright © 2018 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Reachability 11 | 12 | extension Reachability { 13 | static let shared = (try? Reachability())! 14 | } 15 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Disconnected_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wifi-off.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Settings_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings-gear.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | app_identifier("com.twodayslate.ec3730") # The bundle identifier of your app 2 | apple_id("zac@gorak.us") # Your Apple email address 3 | 4 | itc_team_id("106450805") # App Store Connect Team ID 5 | team_id("C6L3992RFB") # Developer Portal Team ID 6 | 7 | # For more information about the Appfile, see: 8 | # https://docs.fastlane.tools/advanced/#appfile 9 | -------------------------------------------------------------------------------- /ec3730/Extensions/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/13/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Error { 12 | var localized: LocalizedError? { 13 | self as? LocalizedError 14 | } 15 | 16 | var title: String { 17 | "Error" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ec3730/DemoData/WhoIsXmlCategorizationSectionModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "as": 3 | {"asn":15169,"domain":"https://about.google/intl/en/","name":"GOOGLE","route":"2607:f8b0::/32","type":"Content"}, 4 | "domainName":"google.com", 5 | "categories":[ 6 | {"confidence":1,"id":50,"name":"Search Engines"} 7 | ], 8 | "createdDate":"1997-09-15T07:00:00+00:00", 9 | "websiteResponded":true 10 | } 11 | -------------------------------------------------------------------------------- /ec3730/Views/Host/HostModelWrapperView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @available(iOS 15.0, *) 4 | /** 5 | This is a wrapper view to ensure that the gien view has the shared HostViewModel shared environment object 6 | */ 7 | struct HostModelWrapperView: View { 8 | var view: Content 9 | 10 | var body: some View { 11 | view.environmentObject(HostViewModel.shared) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/CopyCellDetailStyle.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | enum CopyCellDetailStyle { 4 | case gray 5 | case accent 6 | case label 7 | 8 | var color: Color { 9 | switch self { 10 | case .gray: 11 | return .gray 12 | case .accent: 13 | return .accentColor 14 | case .label: 15 | return Color(UIColor.label) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BuildTools/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "BuildTools", 6 | platforms: [.macOS(.v10_11)], 7 | dependencies: [ 8 | .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.0"), 9 | .package(url: "https://github.com/realm/SwiftLint.git", .upToNextMajor(from: "0.47.1")), 10 | ], 11 | targets: [.target(name: "BuildTools", path: "")] 12 | ) 13 | -------------------------------------------------------------------------------- /ec3730/Modifiers/PaddingListModifier.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PaddingListModifier: ViewModifier { 4 | let padding: [(Edge.Set, CGFloat?)] 5 | func body(content: Content) -> some View { 6 | if let first = padding.first { 7 | content 8 | .padding(first.0, first.1) 9 | .modifier(PaddingListModifier(padding: Array(padding.dropFirst()))) 10 | } else { 11 | content 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /fastlane/Deliverfile.swift: -------------------------------------------------------------------------------- 1 | // The Deliverfile allows you to store various App Store Connect metadata 2 | // For more information, check out the docs 3 | // https://docs.fastlane.tools/actions/deliver/ 4 | 5 | // In general, you can use the options available 6 | // fastlane deliver --help 7 | 8 | // Remove the // in front of the line to enable the option 9 | 10 | class Deliverfile: DeliverfileProtocol { 11 | //var username: String { return "" } 12 | //var appIdentifier: String? { return "" } 13 | } 14 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkAqua.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.761", 9 | "green" : "0.714", 10 | "red" : "0.337" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.937", 9 | "green" : "0.686", 10 | "red" : "0.380" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.475", 9 | "green" : "0.765", 10 | "red" : "0.596" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkPurple.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.867", 9 | "green" : "0.471", 10 | "red" : "0.776" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.459", 9 | "green" : "0.424", 10 | "red" : "0.878" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.482", 9 | "green" : "0.753", 10 | "red" : "0.898" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowAqua.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.624", 9 | "green" : "0.600", 10 | "red" : "0.243" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.682", 9 | "green" : "0.443", 10 | "red" : "0.259" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.161", 9 | "green" : "0.157", 10 | "red" : "0.784" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.204", 9 | "green" : "0.173", 10 | "red" : "0.157" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkComment.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.529", 9 | "green" : "0.490", 10 | "red" : "0.471" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkCurrentLine.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.255", 9 | "green" : "0.224", 10 | "red" : "0.212" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/Colors.xcassets/OneDarkForeground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.749", 9 | "green" : "0.698", 10 | "red" : "0.671" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowComment.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.549", 9 | "green" : "0.565", 10 | "red" : "0.557" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.549", 10 | "red" : "0.443" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowOrange.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.122", 9 | "green" : "0.529", 10 | "red" : "0.961" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowPurple.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.659", 9 | "green" : "0.349", 10 | "red" : "0.537" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.718", 10 | "red" : "0.918" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightAqua.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.718", 9 | "green" : "0.745", 10 | "red" : "0.541" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.745", 9 | "green" : "0.635", 10 | "red" : "0.506" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.400", 9 | "green" : "0.400", 10 | "red" : "0.800" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowCurrentLine.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.937", 9 | "green" : "0.937", 10 | "red" : "0.937" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/Colors.xcassets/TomorrowForeground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.298", 9 | "green" : "0.302", 10 | "red" : "0.302" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/Google/GoogleWebRiskRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleWebRiskRecord.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/17/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct GoogleWebRiskRecordWrapper: Codable { 12 | var threat: GoogleWebRiskRecord? 13 | var error: String? 14 | } 15 | 16 | struct GoogleWebRiskRecord: Codable { 17 | var threatTypes: [GoogleWebRisk.ThreatTypes] 18 | var expireTime: Date 19 | } 20 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightComment.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.588", 9 | "green" : "0.596", 10 | "red" : "0.588" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.408", 9 | "green" : "0.741", 10 | "red" : "0.710" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightOrange.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.373", 9 | "green" : "0.576", 10 | "red" : "0.871" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightPurple.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.733", 9 | "green" : "0.580", 10 | "red" : "0.698" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.455", 9 | "green" : "0.776", 10 | "red" : "0.941" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/CopyCellMultipleTypesView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CopyCellMultipleTypesView: View { 4 | var title: String 5 | var contents: [CopyCellType] 6 | 7 | @Binding var expanded: Bool 8 | 9 | var body: some View { 10 | DisclosureGroup(isExpanded: $expanded, content: { 11 | ForEach(contents) { content in 12 | content 13 | } 14 | }, label: { 15 | Text(title) 16 | }) 17 | .padding() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.129", 9 | "green" : "0.122", 10 | "red" : "0.114" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightCurrentLine.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.180", 9 | "green" : "0.165", 10 | "red" : "0.157" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/Colors.xcassets/TomorrowNightForeground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.776", 9 | "green" : "0.784", 10 | "red" : "0.773" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ec3730/Extensions/Encodable+dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Encodable.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 12/29/22. 6 | // Copyright © 2022 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Encodable { 12 | var dictionary: [String: Any]? { 13 | guard let data = try? JSONEncoder().encode(self) else { return nil } 14 | return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ec3730/Extensions/UITextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/28/18. 6 | // Copyright © 2018 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UITextView { 13 | func scrollToBottom() { 14 | if !text.isEmpty { 15 | let location = text.count - 1 16 | let bottom = NSRange(location: location, length: 1) 17 | scrollRangeToVisible(bottom) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ec3730/Assets.xcassets/Host.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-website-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-website-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-website-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/CopyCellChevronView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CopyCellChevronView: View { 4 | var body: some View { 5 | Image(systemName: "chevron.right") 6 | .font(.system(size: 14, weight: .semibold)) 7 | // .imageScale(.small) 8 | .foregroundColor(Color(UIColor.systemGray3)) 9 | /* 10 | height 14 11 | color: 12 | "0.9999999403953552", 13 | "0.9999999403953552", 14 | "0.9999999403953552" 15 | */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/CopyCellContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CopyCellContentView: View { 4 | var content: String 5 | let style: CopyCellStyleConfig 6 | 7 | var body: some View { 8 | HStack(alignment: .center) { 9 | Spacer() 10 | Text(content).foregroundColor(style.detailStyle.color) 11 | if style.chevron { 12 | CopyCellChevronView() 13 | } 14 | } 15 | .modifier(PaddingListModifier(padding: style.padding)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ec3730/DemoData/Utility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utility.swift 3 | // ec3730 4 | // 5 | // Created by Ahmad Azam on 29/05/2022. 6 | // Copyright © 2022 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func loadJson(filename fileName: String) -> Data? { 12 | var result: Data? 13 | if let url = Bundle.main.url(forResource: fileName, withExtension: "json") { 14 | do { 15 | result = try Data(contentsOf: url) 16 | } catch { 17 | print("error:\(error)") 18 | } 19 | } 20 | return result 21 | } 22 | -------------------------------------------------------------------------------- /ec3730/Extensions/SimpleAppIcons+NetUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleAppIcons+NetUtils.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 9/24/23. 6 | // Copyright © 2023 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import SimpleCommon 10 | 11 | extension SimpleAppIcon { 12 | static let dark = SimpleAppIcon(alternateIconName: "AkhmadDark", assetName: "AkhmadDarkThumb") 13 | static let light = SimpleAppIcon(alternateIconName: nil, assetName: "AkhmadLightThumb") 14 | static let legacy = SimpleAppIcon(alternateIconName: "AppIcon", assetName: "AppIconThumb") 15 | } 16 | -------------------------------------------------------------------------------- /ec3730/Extensions/UIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/14/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIView { 13 | func roundCorners(corners: UIRectCorner, radius: CGFloat) { 14 | let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 15 | let mask = CAShapeLayer() 16 | mask.path = path.cgPath 17 | layer.mask = mask 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ec3730/Views/Source/Snap/SnapPoint.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public enum SnapPoint { 4 | case height(CGFloat) 5 | case paddingToTop(CGFloat) 6 | case fraction(CGFloat) 7 | } 8 | 9 | extension SnapPoint: ExpressibleByIntegerLiteral { 10 | public init(integerLiteral value: CGFloat.IntegerLiteralType) { 11 | self = .height(CGFloat(integerLiteral: value)) 12 | } 13 | } 14 | 15 | extension SnapPoint: ExpressibleByFloatLiteral { 16 | public init(floatLiteral value: CGFloat.FloatLiteralType) { 17 | self = .height(CGFloat(floatLiteral: value)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/CopyCellSingleItemRowView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CopyCellSingleItemRowView: View { 4 | var title: String 5 | var content: String 6 | let style: CopyCellStyleConfig 7 | 8 | var body: some View { 9 | HStack(alignment: .center) { 10 | Text(title) 11 | Spacer() 12 | Text(content).foregroundColor(style.detailStyle.color) 13 | if style.chevron { 14 | CopyCellChevronView() 15 | } 16 | } 17 | .modifier(PaddingListModifier(padding: style.padding)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ec3730/Protocols/InAppPurchaseUpdateDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppPurchaseUpdateDelegate.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/12/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyStoreKit 11 | 12 | protocol DataFeedInAppPurchaseUpdateDelegate { 13 | func didUpdateInAppPurchase(_ for: DataFeed, error: Error?, purchaseResult: PurchaseResult?, restoreResults: RestoreResults?, verifySubscriptionResult: VerifySubscriptionResult?, verifyPurchaseResult: VerifyPurchaseResult?, retrieveResults: RetrieveResults?) 14 | } 15 | -------------------------------------------------------------------------------- /ec3730/Views/Source/RunestoneView.swift: -------------------------------------------------------------------------------- 1 | import Runestone 2 | import SwiftUI 3 | 4 | struct RunestoneView: UIViewRepresentable { 5 | @Binding var text: String 6 | @Binding var textView: TextView 7 | 8 | func makeUIView(context _: Context) -> TextView { 9 | let view = textView 10 | view.backgroundColor = .clear 11 | view.isEditable = false 12 | view.contentInset = .init(top: 0, left: 8, bottom: 0, right: 8) 13 | return view 14 | } 15 | 16 | func updateUIView(_ uiView: TextView, context _: Context) { 17 | uiView.text = text 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeed.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFeed.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 9/26/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import StoreKit 11 | import SwiftyStoreKit 12 | 13 | protocol DataFeedSingleton: DataFeed { 14 | static var current: Self { get } 15 | static var session: URLSession { get } 16 | } 17 | 18 | protocol DataFeed: AnyObject { 19 | var name: String { get } 20 | var userKey: String? { get set } 21 | 22 | var webpage: URL { get } 23 | 24 | typealias Endpoints = DataFeedEndpoint 25 | } 26 | -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/CopyCellStyleConfig.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CopyCellStyleConfig { 4 | var detailStyle: CopyCellDetailStyle 5 | var padding: [(Edge.Set, CGFloat?)] = [(Edge.Set.all, nil)] 6 | var chevron: Bool = false 7 | 8 | static let gray: Self = .init(detailStyle: .gray) 9 | // A Style for a cell in an multiple cell 10 | static let expandable: Self = .init( 11 | detailStyle: .label, 12 | padding: [ 13 | ([.leading, .trailing], nil), 14 | (.top, 4), 15 | ] 16 | ) 17 | static let chevron: Self = .init(detailStyle: .gray, chevron: true) 18 | } 19 | -------------------------------------------------------------------------------- /ec3730/Views/CopyCell/CopyCellToggleableItemRowView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CopyCellToggleableItemRowView: View { 4 | var title: String 5 | var contents: [String] 6 | let style: CopyCellStyleConfig 7 | 8 | var body: some View { 9 | HStack(alignment: .center) { 10 | Text(self.title) 11 | Spacer() 12 | TappedText(content: contents) 13 | .foregroundColor(style.detailStyle.color) 14 | 15 | if style.chevron { 16 | CopyCellChevronView() 17 | } 18 | } 19 | .modifier(PaddingListModifier(padding: style.padding)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeedPurchaseProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFeedPurchaseProtocol.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/17/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import StoreKit 11 | import SwiftyStoreKit 12 | 13 | protocol DataFeedPurchaseProtocol: DataFeed { 14 | var paid: Bool { get } 15 | var owned: Bool { get } 16 | 17 | var defaultProduct: SKProduct? { get } 18 | 19 | func restore(completion block: ((RestoreResults) -> Void)?) 20 | func verify(completion block: ((Error?) -> Void)?) 21 | func retrieve(completion block: ((Error?) -> Void)?) 22 | } 23 | -------------------------------------------------------------------------------- /ec3730/Views/TappedText.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TappedText: View { 4 | @State private var selectedTextIndex: Int = 0 5 | 6 | var content: [String] 7 | 8 | var body: some View { 9 | Text(content[selectedTextIndex]) 10 | .onTapGesture { 11 | let temp = selectedTextIndex + 1 12 | 13 | selectedTextIndex = temp >= content.count ? 0 : temp 14 | } 15 | } 16 | } 17 | 18 | #if DEBUG 19 | struct TappedTextPreview: PreviewProvider { 20 | static var previews: some View { 21 | TappedText(content: (1 ... 5).map { "Content \($0)" }) 22 | } 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /ec3730/Persistence/HostData.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | public class HostData: NSManagedObject { 4 | @NSManaged public var service: String 5 | @NSManaged public var data: Data 6 | @NSManaged public var date: Date 7 | 8 | convenience init(context: NSManagedObjectContext, service: Service, data: Data) { 9 | guard let entity = NSEntityDescription.entity(forEntityName: "HostDataEntity", in: context) else { 10 | fatalError("No entity named HostData") 11 | } 12 | self.init(entity: entity, insertInto: context) 13 | 14 | self.service = service.name 15 | self.data = data 16 | date = Date() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ec3730/Views/Host/HostViewSectionFocusView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct HostViewSectionFocusView: View { 4 | @ObservedObject var model: HostSectionModel 5 | var url: URL 6 | var date: Date 7 | var body: some View { 8 | EZPanel { 9 | VStack(spacing: 0) { 10 | ScrollView { 11 | HostViewSectionContent(sectionModel: model, canQuery: true) 12 | }.safeAreaInset(edge: .bottom) { 13 | HostBarView(url: url, date: date) 14 | } 15 | } 16 | .navigationTitle(model.service.name) 17 | .navigationBarTitleDisplayMode(.inline) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | desc "Description of what the lane does" 20 | lane :custom_lane do 21 | # add actions here: https://docs.fastlane.tools/actions 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /ec3730/DemoData/WhoIsXmlGeoLocationSectionModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "ip": "8.8.8.8", 3 | "location": { 4 | "country": "US", 5 | "region": "California", 6 | "city": "Mountain View", 7 | "lat": 37.38605, 8 | "lng": -122.08385, 9 | "postalCode": "94035", 10 | "timezone": "-07:00", 11 | "geonameId": 5375480 12 | }, 13 | "domains": ["clnbhft.tk", "dijnjshr.ga", "dijnjshr.tk", "essenza.ma", "europarcrental.ma"], 14 | "as": { 15 | "asn": 15169, 16 | "name": "GOOGLE", 17 | "route": "8.8.8.0\/24", 18 | "domain": "https:\/\/about.google\/intl\/en\/", 19 | "type": "Content" 20 | }, 21 | "isp": "Google LLC", 22 | "connectionType": "" 23 | } 24 | 25 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/CoreData/NetUtilsCoreData.xcdatamodeld/Usage.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ec3730Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ec3730UITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ec3730/ec3730.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.com.twodayslate.netutils 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | com.apple.developer.networking.wifi-info 16 | 17 | com.apple.security.app-sandbox 18 | 19 | com.apple.security.network.client 20 | 21 | com.apple.security.personal-information.location 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ec3730/Models/Device/Sections/FingerprintInfoModel.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import DeviceKit 3 | import SwiftUI 4 | 5 | class FingerprintInfoModel: DeviceInfoSectionModel { 6 | @MainActor var models = [FingerPrintModel]() 7 | 8 | override init() { 9 | super.init() 10 | title = "Fingerprints" 11 | } 12 | 13 | @MainActor func attachModel(model: FingerPrintModel) async { 14 | if !models.contains(model) { 15 | models.append(model) 16 | await reload() 17 | } 18 | } 19 | 20 | @MainActor override func reload() async { 21 | enabled = models.count > 0 22 | rows.removeAll() 23 | 24 | for (i, model) in models.enumerated() { 25 | rows.append(.row(title: "Fingerprint \(i)", content: model.fingerprint ?? "-")) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ec3730/Views/FSDisclosureGroup.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct FSDisclosureGroup: View where Label: View, Content: View { 4 | @Binding var isExpanded: Bool 5 | var content: () -> Content 6 | var label: () -> Label 7 | 8 | var body: some View { 9 | VStack(alignment: .leading, spacing: 0.0) { 10 | Button(action: { 11 | withAnimation { 12 | self.isExpanded.toggle() 13 | } 14 | }, label: { 15 | HStack(alignment: .center) { 16 | label() 17 | Image(systemName: "chevron.down").rotationEffect(self.isExpanded ? .degrees(0.0) : .degrees(-90.0)).padding() 18 | } 19 | }) 20 | 21 | if self.isExpanded { 22 | content() 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ## iOS 17 | 18 | ### ios custom_lane 19 | 20 | ```sh 21 | [bundle exec] fastlane ios custom_lane 22 | ``` 23 | 24 | Description of what the lane does 25 | 26 | ---- 27 | 28 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 29 | 30 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 31 | 32 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 33 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # file options 2 | 3 | --exclude fastlane 4 | --exclude .build 5 | --exclude .swiftpm 6 | 7 | # format options 8 | 9 | --allman false 10 | --binarygrouping 4,8 11 | --commas always 12 | --comments indent 13 | --decimalgrouping 3,6 14 | --elseposition same-line 15 | --empty void 16 | --exponentcase lowercase 17 | --exponentgrouping disabled 18 | --fractiongrouping disabled 19 | --header ignore 20 | --hexgrouping 4,8 21 | --hexliteralcase uppercase 22 | --ifdef indent 23 | --indent 4 24 | --indentcase false 25 | --importgrouping testable-bottom 26 | --linebreaks lf 27 | --maxwidth none 28 | --octalgrouping 4,8 29 | --operatorfunc spaced 30 | --patternlet hoist 31 | --ranges spaced 32 | --self remove 33 | --semicolons inline 34 | --stripunusedargs always 35 | --swiftversion 5.5 36 | --trimwhitespace always 37 | --wraparguments preserve 38 | --wrapcollections preserve 39 | 40 | # rules 41 | --disable wrapMultilineStatementBraces 42 | --enable isEmpty -------------------------------------------------------------------------------- /ec3730/DefaultsSwitch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISwitch.swift 3 | // acft 4 | // 5 | // Created by Zachary Gorak on 5/30/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class DefaultsSwitch: UISwitch { 13 | private var defaultKey: String 14 | 15 | public init(forKey key: String) { 16 | defaultKey = key 17 | super.init(frame: CGRect.zero) 18 | 19 | isOn = UserDefaults.standard.bool(forKey: defaultKey) 20 | addTarget(self, action: #selector(toggle), for: .valueChanged) 21 | } 22 | 23 | @objc private func toggle(_ sender: DefaultsSwitch) { 24 | UserDefaults.standard.set(sender.isOn, forKey: sender.defaultKey) 25 | UserDefaults.standard.synchronize() 26 | } 27 | 28 | @available(*, unavailable) 29 | required init?(coder _: NSCoder) { 30 | fatalError("init(coder:) has not been implemented") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ec3730/Controllers/EZPanel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | /** 3 | An simple NavigationView that has an X in the top right 4 | */ 5 | struct EZPanel: View where Content: View { 6 | let content: () -> Content 7 | @Environment(\.presentationMode) var presentationMode: Binding 8 | 9 | init(@ViewBuilder content: @escaping () -> Content) { 10 | self.content = content 11 | } 12 | 13 | var body: some View { 14 | NavigationView { 15 | content().navigationBarItems(trailing: Button(action: { self.presentationMode.wrappedValue.dismiss() }) { 16 | Image(systemName: "xmark.circle.fill").foregroundColor(Color(UIColor.systemGray3)) 17 | }) 18 | } 19 | } 20 | } 21 | 22 | #if DEBUG 23 | struct EZPanel_preview: PreviewProvider { 24 | static var previews: some View { 25 | EZPanel { 26 | Text("Hello World") 27 | } 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeedService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFeedService.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/16/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Support for more than one service for a given Data Feed 12 | protocol DataFeedService: DataFeed { 13 | var totalUsage: Int { get } 14 | var services: [Service] { get } 15 | } 16 | 17 | extension DataFeedService { 18 | func clearUsage(completion block: (() -> Void)? = nil) { 19 | services.forEach { $0.clearUsage(completion: block) } 20 | } 21 | 22 | var totalUsage: Int { 23 | services.reduce(0) { $0 + $1.usage } 24 | } 25 | 26 | var usageToday: Int { 27 | services.reduce(0) { $0 + $1.usageToday } 28 | } 29 | 30 | var usageThisMonth: Int { 31 | services.reduce(0) { $0 + $1.usageMonth } 32 | } 33 | 34 | var usageThisYear: Int { 35 | services.reduce(0) { $0 + $1.usageYear } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCE_ROOT:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 2 | 3 | .PHONY: help 4 | help: 5 | @echo "Usage make [target]" 6 | @echo "Targets:" 7 | @echo " bootstrap Install necessary programs and requirements." 8 | @echo " help Print this message and exit." 9 | @echo " format Format and lint project." 10 | @echo " snapshot Use fastlane to take and deliver snapshots." 11 | @echo " open Open workspace." 12 | 13 | .PHONY: bootstrap 14 | bootstrap: 15 | gem install bundler 16 | bundle install 17 | 18 | .PHONY: format lint 19 | lint: format 20 | format: 21 | cd BuildTools && swift run -c release swiftformat --config "${SOURCE_ROOT}/.swiftformat" "${SOURCE_ROOT}" 22 | cd BuildTools && swift run -c release swiftlint --config "${SOURCE_ROOT}/.swiftlint.yml" "${SOURCE_ROOT}" --fix 23 | 24 | .PHONY: snapshot 25 | snapshot: 26 | fastlane snapshot && fastlane deliver --overwrite_screenshots 27 | 28 | .PHONY: open 29 | open: 30 | xed . 31 | 32 | .DEFAULT_GOAL := format 33 | -------------------------------------------------------------------------------- /ec3730/TimedCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimedCache.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/12/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class TimedCache { 12 | public var expirationInterval: TimeInterval 13 | private var data = [String: Any?]() 14 | 15 | init(expiresIn interval: TimeInterval) { 16 | expirationInterval = interval 17 | } 18 | 19 | public func add(_ object: Any?, for key: String) { 20 | data[key] = object 21 | DispatchQueue.main.async { 22 | Timer.scheduledTimer(withTimeInterval: self.expirationInterval, repeats: false) { _ in 23 | self.data.removeValue(forKey: key) 24 | } 25 | } 26 | } 27 | 28 | public func value(for key: String) -> T? { 29 | guard let value = data[key] as? T else { 30 | return nil 31 | } 32 | 33 | // XXX: invalidate and update the timer? 34 | 35 | return value 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ec3730/Extensions/UIImage+Codable.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | enum UIImageDecodingError: Error { 4 | case unableToCreateImage 5 | case unableToGetImageData 6 | } 7 | 8 | public extension Decodable where Self: UIImage { 9 | init(from decoder: Decoder) throws { 10 | let container = try decoder.singleValueContainer() 11 | let data = try container.decode(Data.self) 12 | if let image = Self(data: data) { 13 | self = image 14 | } 15 | throw UIImageDecodingError.unableToCreateImage 16 | } 17 | } 18 | 19 | public extension Encodable where Self: UIImage { 20 | func encode(to encoder: Encoder) throws { 21 | var container = encoder.singleValueContainer() 22 | if let data = pngData() { 23 | try container.encode(data) 24 | } else if let data = jpegData(compressionQuality: 1.0) { 25 | try container.encode(data) 26 | } 27 | throw UIImageDecodingError.unableToGetImageData 28 | } 29 | } 30 | 31 | extension UIImage: Codable {} 32 | -------------------------------------------------------------------------------- /ec3730/Views/ShareSheetView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import UIKit 3 | 4 | struct ShareSheetView: UIViewControllerRepresentable { 5 | typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void 6 | 7 | let activityItems: [Any] 8 | let applicationActivities: [UIActivity]? = nil 9 | let excludedActivityTypes: [UIActivity.ActivityType]? = nil 10 | let callback: Callback? = nil 11 | 12 | func makeUIViewController(context _: Context) -> UIActivityViewController { 13 | let controller = UIActivityViewController( 14 | activityItems: activityItems, 15 | applicationActivities: applicationActivities ?? [] 16 | ) 17 | controller.excludedActivityTypes = excludedActivityTypes 18 | controller.completionWithItemsHandler = callback 19 | return controller 20 | } 21 | 22 | func updateUIViewController(_: UIActivityViewController, context _: Context) { 23 | // nothing to do here 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ec3730Tests/ec3730Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ec3730Tests.swift 3 | // ec3730Tests 4 | // 5 | // Created by Zachary Gorak on 8/22/18. 6 | // Copyright © 2018 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ec3730 11 | 12 | class EC3730Tests: XCTestCase { 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | measure { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ec3730.xcodeproj/xcuserdata/twodayslate.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ec3730.xcscheme 8 | 9 | orderHint 10 | 8 11 | 12 | ec3730.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | ec3730UITests.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 11 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | F6C8D8A0212E5AE900309373 26 | 27 | primary 28 | 29 | 30 | F6C8D8B6212E5AEB00309373 31 | 32 | primary 33 | 34 | 35 | F6C8D8C1212E5AEB00309373 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ec3730/Views/WebkitOverlayView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import WebKit 3 | 4 | struct WebkitOverlayView: UIViewRepresentable { 5 | @ObservedObject var model: FingerPrintModel 6 | 7 | func makeUIView(context: Context) -> WKWebView { 8 | let webView = WKWebView() 9 | webView.frame = .init(x: 0, y: 0, width: 1, height: 1) 10 | webView.alpha = 0.0005 11 | webView.navigationDelegate = context.coordinator 12 | DispatchQueue.main.async { 13 | model.reload(webView) 14 | } 15 | return webView 16 | } 17 | 18 | func updateUIView(_: WKWebView, context _: Context) { 19 | // no-op 20 | } 21 | 22 | func makeCoordinator() -> Coordinator { 23 | Coordinator(model) 24 | } 25 | 26 | class Coordinator: NSObject, WKNavigationDelegate { 27 | @ObservedObject var model: FingerPrintModel 28 | 29 | init(_ model: FingerPrintModel) { 30 | self.model = model 31 | } 32 | 33 | func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { 34 | model.onFinish(model, webView) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ec3730/Models/FingerprintModel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import WebKit 3 | 4 | @MainActor 5 | class FingerPrintModel: ObservableObject { 6 | var url: URL 7 | var _onFinish: (FingerPrintModel, WKWebView) -> Void 8 | weak var parent: DeviceInfoModel? 9 | 10 | @Published var fingerprint: String? 11 | @Published var didFinish = false 12 | 13 | init(_ url: URL, onFinish: @escaping (FingerPrintModel, WKWebView) -> Void) { 14 | self.url = url 15 | _onFinish = onFinish 16 | } 17 | 18 | func reload(_ webview: WKWebView) { 19 | fingerprint = nil 20 | webview.load(URLRequest(url: url)) 21 | } 22 | 23 | func onFinish(_ model: FingerPrintModel, _ webview: WKWebView) { 24 | didFinish = true 25 | _onFinish(model, webview) 26 | } 27 | 28 | func update(fingerprint: String) async { 29 | self.fingerprint = fingerprint 30 | await parent?.reloadFingerprints() 31 | } 32 | } 33 | 34 | extension FingerPrintModel: Equatable { 35 | static func == (lhs: FingerPrintModel, rhs: FingerPrintModel) -> Bool { 36 | lhs.url == rhs.url 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /fastlane/Snapfile: -------------------------------------------------------------------------------- 1 | # Uncomment the lines below you want to change by removing the # in the beginning 2 | 3 | # A list of devices you want to take the screenshots from 4 | # `xcrun simctl list devices available` 5 | devices([ 6 | "iPhone 8 Plus", 7 | "iPhone SE (3rd generation)", 8 | "iPhone 13 Pro Max", 9 | # xcrun simctl create "iPad Pro (12.9-inch) (2nd generation)" "com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---2nd-generation-" 10 | "iPad Pro (12.9-inch) (2nd generation)", 11 | "iPad Pro (12.9-inch) (5th generation)" 12 | ]) 13 | 14 | languages([ 15 | "en-US" 16 | ]) 17 | 18 | # The name of the scheme which contains the UI Tests 19 | scheme("ec3730") 20 | 21 | # Where should the resulting screenshots be stored? 22 | output_directory("./fastlane/screenshots") 23 | 24 | # remove the '#' to clear all previously generated screenshots before creating new ones 25 | clear_previous_screenshots(true) 26 | 27 | # Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments 28 | # launch_arguments(["-favColor red"]) 29 | 30 | # For more information about all available options run 31 | # fastlane action snapshot 32 | -------------------------------------------------------------------------------- /ec3730/Views/Host/HostViewSectionContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostViewSectionContent.swift 3 | // ec3730 4 | // 5 | // Created by Ahmad Azam on 22/05/2022. 6 | // Copyright © 2022 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct HostViewSectionContent: View { 12 | @ObservedObject var sectionModel: HostSectionModel 13 | var canQuery: Bool 14 | 15 | var body: some View { 16 | LazyVStack(alignment: .leading, spacing: 0) { 17 | if let storeModel = self.sectionModel.storeModel { 18 | if self.canQuery { 19 | // Need se-0309 20 | ForEach(self.sectionModel.content) { row in 21 | row 22 | } 23 | } else { 24 | PurchaseCellView(model: storeModel, sectionModel: sectionModel) 25 | } 26 | } else { 27 | ForEach(self.sectionModel.content) { row in 28 | row 29 | } 30 | } 31 | } 32 | .listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0)) 33 | .background(Color(UIColor.systemBackground)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/WhoisXML/WhoIsXmlGeoLoactionService.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | class WhoIsXmlGeoLocationService: WhoisXMLService { 5 | override func endpoint(_ userData: [String: Any?]?) -> DataFeedEndpoint? { 6 | guard let userData = userData, let userInput = userData["domain"] as? String, let domain = userInput.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { 7 | return nil 8 | } 9 | 10 | var params = [URLQueryItem(name: "domainName", value: domain), 11 | URLQueryItem(name: "outputFormat", value: "JSON"), 12 | URLQueryItem(name: "type", value: "_all"), 13 | URLQueryItem(name: "api", value: "whoisXmlIpGeo"), 14 | URLQueryItem(name: "identifierForVendor", value: UIDevice.current.identifierForVendor?.uuidString), 15 | URLQueryItem(name: "bundleIdentifier", value: Bundle.main.bundleIdentifier)] 16 | if let key = WhoisXml.current.userKey { 17 | params.append(URLQueryItem(name: "apiKey", value: key)) 18 | } 19 | return WhoisXml.Endpoint(host: "api.netutils.workers.dev", path: "/api/v1", queryItems: params) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetUtils 2 | 3 | The all-in-one Network Utility application! 4 | 5 | NetUtils is a Network Utility application that can do a whole lot, including: 6 | - Network connectivity status 7 | - Network interface information 8 | - WiFi information 9 | - VPN information 10 | - Host information 11 | - WHOIS information from Whois XML API (subscription required) 12 | - DNS information from Whois XML API (subscription required) 13 | - Google Web Risk Information 14 | - Ping utility 15 | - View page source 16 | 17 | ## Requirements 18 | 19 | ### API Keys 20 | 21 | In order to use/build this project you will have to create an Api Key `enum`. An example is below: 22 | 23 | ```swift 24 | struct ApiKey { 25 | let name: String 26 | let key: String 27 | 28 | static var inApp: ApiKey { 29 | return ApiKey(name: "In-App Purchases", key: "my_key_here") 30 | } 31 | } 32 | ``` 33 | 34 | This is necessary to support for In-App purchases properly. Service related keys are stored on the [Cloudflare Workers®](https://www.cloudflare.com/products/cloudflare-workers/) server. 35 | 36 | ### Releasing 37 | 38 | [fastlane](https://fastlane.tools/) is used for screenshots. 39 | 40 | ## Credits 41 | 42 | Icons by [Nucleo](https://nucleoapp.com/) -------------------------------------------------------------------------------- /ec3730/DemoData/WhoisXmlReputationSectionModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "mode" : "fast", 3 | "reputationScore" : 83.400000000000006, 4 | "testResults" : [ 5 | { 6 | "test" : "WHOIS Domain check", 7 | "testCode" : 93, 8 | "warningCodes" : [ 9 | 2009 10 | ], 11 | "warnings" : [ 12 | "Owner details are publicly available" 13 | ] 14 | }, 15 | { 16 | "test" : "Malware databases check", 17 | "testCode" : 82, 18 | "warningCodes" : [ 19 | 4002 20 | ], 21 | "warnings" : [ 22 | "Phishing" 23 | ] 24 | }, 25 | { 26 | "test" : "SSL certificate validity", 27 | "testCode" : 87, 28 | "warningCodes" : [ 29 | 6001 30 | ], 31 | "warnings" : [ 32 | "Recently obtained certificate, valid from 2022-05-04 16:26:51" 33 | ] 34 | }, 35 | { 36 | "test" : "SSL vulnerabilities", 37 | "testCode" : 88, 38 | "warningCodes" : [ 39 | 6015, 40 | 6019, 41 | 6021 42 | ], 43 | "warnings" : [ 44 | "HTTP Strict Transport Security not set", 45 | "TLSA record not configured or configured wrong", 46 | "OCSP stapling not configured" 47 | ] 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /ec3730/Models/Device/Sections/DataUsageInfoModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataUsageInfoModel.swift 3 | // ec3730 4 | // 5 | // Created by Ahmad Azam on 21/08/2022. 6 | // Copyright © 2022 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | class DataUsageInfoModel: DeviceInfoSectionModel { 12 | override init() { 13 | super.init() 14 | title = "Data Usage" 15 | } 16 | 17 | @MainActor override func reload() async { 18 | enabled = true 19 | SystemDataUsage.reload() 20 | rows.removeAll() 21 | rows.append(.multiple(title: "Wifi", contents: [ 22 | .row(title: "Sent", content: SystemDataUsage.wifiSent, style: .expandable), 23 | .row(title: "Received", content: SystemDataUsage.wifiReceived, style: .expandable), 24 | .row(title: "Total", content: SystemDataUsage.wifiTotal, style: .expandable), 25 | ])) 26 | rows.append(.multiple(title: "Cellular", contents: [ 27 | .row(title: "Sent", content: SystemDataUsage.wwanSent, style: .expandable), 28 | .row(title: "Received", content: SystemDataUsage.wwanReceived, style: .expandable), 29 | .row(title: "Total", content: SystemDataUsage.wwanTotal, style: .expandable), 30 | ])) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ec3730/Extensions/UserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults.swift 3 | // acft 4 | // 5 | // Created by Zachary Gorak on 5/30/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension UserDefaults { 12 | enum NetUtils { 13 | enum Keys { 14 | public static var hideScrollbars: String { "hide_scrollbars" } 15 | /// Key to enable/disable resource thumbnails 16 | public static var resourceThumbnails: String { "resource_thumbnails" } 17 | /// Key to enable/disable the calculator save result animation 18 | public static var saveCalculatorResultAnimation: String { "save_result_animation" } 19 | /// Key to enable/disable smart rotation lock for media 20 | public static var smartRotationLock: String { "landscape_videos" } 21 | public static func keyFor(dataFeed: DataFeed) -> String { 22 | "feed." + dataFeed.name.lowercased().replacingOccurrences(of: " ", with: ".") + ".key" 23 | } 24 | 25 | public static func keyFor(service: Service) -> String { 26 | "service." + service.name.lowercased().replacingOccurrences(of: " ", with: ".") + ".usage.key" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ec3730/CenterTextTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CenterTextTableViewCell.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/22/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class CenterTextTableViewCell: UITableViewCell { 13 | var centerLabel = UILabel() 14 | 15 | init() { 16 | super.init(style: .default, reuseIdentifier: "center") 17 | 18 | centerLabel.textAlignment = .center 19 | centerLabel.translatesAutoresizingMaskIntoConstraints = false 20 | 21 | let stack = UIStackView() 22 | stack.translatesAutoresizingMaskIntoConstraints = false 23 | stack.addArrangedSubview(centerLabel) 24 | contentView.addSubview(stack) 25 | 26 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 27 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 28 | } 29 | 30 | @available(*, unavailable) 31 | required init?(coder _: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeedEndpoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFeedEndpoint.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/10/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class DataFeedEndpoint { 12 | var schema: String 13 | var host: String 14 | var path: String 15 | var queryItems: [URLQueryItem] 16 | 17 | init(schema: String = "https", host: String, path: String = "", queryItems: [URLQueryItem] = []) { 18 | self.schema = schema 19 | self.host = host 20 | self.path = path 21 | self.queryItems = queryItems 22 | } 23 | 24 | func with(schema: String? = nil, host: String? = nil, path: String? = nil, queryItems: [URLQueryItem]? = nil) -> DataFeedEndpoint { 25 | DataFeedEndpoint(schema: schema ?? self.schema, 26 | host: host ?? self.host, 27 | path: path ?? self.path, 28 | queryItems: queryItems ?? self.queryItems) 29 | } 30 | 31 | var url: URL? { 32 | var components = URLComponents() 33 | components.scheme = schema 34 | components.host = host 35 | components.path = path 36 | components.queryItems = queryItems 37 | return components.url 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /fastlane/screenshots/README.txt: -------------------------------------------------------------------------------- 1 | ## Screenshots Naming Rules 2 | 3 | Put all screenshots you want to use inside the folder of its language (e.g. `en-US`). 4 | The device type will automatically be recognized using the image resolution. 5 | 6 | The screenshots can be named whatever you want, but keep in mind they are sorted 7 | alphabetically, in a human-friendly way. See https://github.com/fastlane/fastlane/pull/18200 for more details. 8 | 9 | ### Exceptions 10 | 11 | #### iPad Pro (3rd Gen) 12.9" 12 | 13 | Since iPad Pro (3rd Gen) 12.9" and iPad Pro (2nd Gen) 12.9" have the same image 14 | resolution, screenshots of the iPad Pro (3rd gen) 12.9" must contain either the 15 | string `iPad Pro (12.9-inch) (3rd generation)`, `IPAD_PRO_3GEN_129`, or `ipadPro129` 16 | (App Store Connect's internal naming of the display family for the 3rd generation iPad Pro) 17 | in its filename to be assigned the correct display family and to be uploaded to 18 | the correct screenshot slot in your app's metadata. 19 | 20 | ### Other Platforms 21 | 22 | #### Apple TV 23 | 24 | Apple TV screenshots should be stored in a subdirectory named `appleTV` with language 25 | folders inside of it. 26 | 27 | #### iMessage 28 | 29 | iMessage screenshots, like the Apple TV ones, should also be stored in a subdirectory 30 | named `iMessage`, with language folders inside of it. 31 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/WhoisXML/WhoIsXmlCategorizationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhoIsXmlCategorizationService.swift 3 | // ec3730 4 | // 5 | // Created by admin on 04/11/22. 6 | // Copyright © 2022 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class WhoIsXmlCategorizationService: WhoisXMLService { 13 | override func endpoint(_ userData: [String: Any?]?) -> DataFeedEndpoint? { 14 | guard let userData = userData, let userInput = userData["url"] as? URL, let url_string = userInput.host?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { 15 | return nil 16 | } 17 | 18 | var params = [URLQueryItem(name: "url", value: url_string), 19 | URLQueryItem(name: "outputFormat", value: "JSON"), 20 | URLQueryItem(name: "api", value: "whoisXmlWebsiteCategorization"), 21 | URLQueryItem(name: "identifierForVendor", value: UIDevice.current.identifierForVendor?.uuidString), 22 | URLQueryItem(name: "bundleIdentifier", value: Bundle.main.bundleIdentifier)] 23 | if let key = WhoisXml.current.userKey { 24 | params.append(URLQueryItem(name: "apiKey", value: key)) 25 | } 26 | return WhoisXml.Endpoint(host: "api.netutils.workers.dev", path: "/api/v3", queryItems: params) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ec3730/DemoData/WhoIsXmlContactsSectionModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "companyNames": [ 3 | "Play Store", 4 | "Morgan Calderini", 5 | "Google LLC", 6 | "Google Analytics a Google Analytics", 7 | "Cloud Build", 8 | "NASA Camp", 9 | "Grand Canyon National Park", 10 | "Devon Island Canada" 11 | ], 12 | "countryCode": "US", 13 | "domainName": "google.com", 14 | "emails": [ 15 | { 16 | "description": "", 17 | "email": "investor-relations@abc.xyz" 18 | }, 19 | { 20 | "description": "", 21 | "email": "press@google.com" 22 | }, 23 | { 24 | "description": "", 25 | "email": "Tina@ink-42.com" 26 | }, 27 | { 28 | "description": "", 29 | "email": "joe@yourcompany.com" 30 | } 31 | ], 32 | "meta": { 33 | "description": "", 34 | "title": "Google" 35 | }, 36 | "phones": [ 37 | { 38 | "callHours": "", 39 | "description": "", 40 | "phoneNumber": "650 253-0000" 41 | } 42 | ], 43 | "postalAddresses": [], 44 | "socialLinks": { 45 | "facebook": "", 46 | "instagram": "", 47 | "linkedIn": "", 48 | "twitter": "" 49 | }, 50 | "websiteResponded": true 51 | } 52 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeedCells.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFeedCells.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 9/26/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class DataFeedCells { 13 | var feeds: [DataFeed] { 14 | [WhoisXml.current, GoogleWebRisk.current] 15 | } 16 | 17 | var subscriptions: [DataFeedSubscription] { 18 | // swiftlint:disable:next force_cast 19 | feeds.filter { $0 is DataFeedSubscription } as! [DataFeedSubscription] 20 | } 21 | 22 | var oneTimes: [DataFeedOneTimePurchase] { 23 | // swiftlint:disable:next force_cast 24 | feeds.filter { $0 is DataFeedOneTimePurchase } as! [DataFeedOneTimePurchase] 25 | } 26 | 27 | var purchases: [DataFeedPurchaseProtocol] { 28 | // swiftlint:disable:next force_cast 29 | feeds.filter { $0 is DataFeedPurchaseProtocol } as! [DataFeedPurchaseProtocol] 30 | } 31 | 32 | var cells: [DataFeedCell] { 33 | let whoisCell = DataFeedCell(subscriber: WhoisXml.current) 34 | whoisCell.descriptionText.text = "Unlocks WHOIS, DNS, and Reputation Lookup" 35 | 36 | let webRisk = DataFeedCell(subscriber: GoogleWebRisk.current) 37 | webRisk.descriptionText.text = "Unlocks detection of malicious URLs" 38 | 39 | return [whoisCell, webRisk] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/WhoisXML/WhoisXMLDnsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhoisXMLDnsService.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/16/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class WhoisXMLDnsService: WhoisXMLService { 13 | override func endpoint(_ userData: [String: Any?]?) -> DataFeedEndpoint? { 14 | guard let userData = userData, let userInput = userData["domain"] as? String, let domain = userInput.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { 15 | return nil 16 | } 17 | 18 | var params = [URLQueryItem(name: "domainName", value: domain), 19 | URLQueryItem(name: "outputFormat", value: "JSON"), 20 | URLQueryItem(name: "type", value: "_all"), 21 | URLQueryItem(name: "api", value: "whoisXml"), 22 | URLQueryItem(name: "identifierForVendor", value: UIDevice.current.identifierForVendor?.uuidString), 23 | URLQueryItem(name: "bundleIdentifier", value: Bundle.main.bundleIdentifier)] 24 | 25 | if let key = WhoisXml.current.userKey { 26 | params.append(URLQueryItem(name: "apiKey", value: key)) 27 | } 28 | 29 | return WhoisXml.Endpoint(host: "api.netutils.workers.dev", path: "/whoisserver/DNSService", queryItems: params) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/WhoisXML/WhoisXmlContactsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhoisXmlContactsService.swift 3 | // ec3730 4 | // 5 | // Created by Jaspreet Singh on 18/10/22. 6 | // Copyright © 2022 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import UIKit 12 | 13 | class WhoisXmlContactsService: WhoisXMLService { 14 | override func endpoint(_ userData: [String: Any?]?) -> DataFeedEndpoint? { 15 | guard let userData = userData, let userInput = userData["domain"] as? String, let domain = userInput.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { 16 | return nil 17 | } 18 | 19 | var params = [URLQueryItem(name: "domainName", value: domain), 20 | URLQueryItem(name: "outputFormat", value: "JSON"), 21 | URLQueryItem(name: "type", value: "_all"), 22 | URLQueryItem(name: "api", value: "whoisXmlWebsiteContact"), 23 | URLQueryItem(name: "identifierForVendor", value: UIDevice.current.identifierForVendor?.uuidString), 24 | URLQueryItem(name: "bundleIdentifier", value: Bundle.main.bundleIdentifier)] 25 | if let key = WhoisXml.current.userKey { 26 | params.append(URLQueryItem(name: "apiKey", value: key)) 27 | } 28 | return WhoisXml.Endpoint(host: "api.netutils.workers.dev", path: "/api/v1", queryItems: params) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneThemeCommon/HighlightName.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if DEBUG 4 | private var previousUnrecognizedHighlightNames: [String] = [] 5 | #endif 6 | 7 | public enum HighlightName: String { 8 | case comment 9 | case function 10 | case keyword 11 | case number 12 | case `operator` 13 | case property 14 | case punctuation 15 | case string 16 | case variableBuiltin = "variable.builtin" 17 | 18 | public init?(_ rawHighlightName: String) { 19 | var comps = rawHighlightName.split(separator: ".") 20 | while !comps.isEmpty { 21 | let candidateRawHighlightName = comps.joined(separator: ".") 22 | if let highlightName = Self(rawValue: candidateRawHighlightName) { 23 | self = highlightName 24 | return 25 | } 26 | comps.removeLast() 27 | } 28 | #if DEBUG 29 | if !previousUnrecognizedHighlightNames.contains(rawHighlightName) { 30 | previousUnrecognizedHighlightNames.append(rawHighlightName) 31 | print("Unrecognized highlight name: '\(rawHighlightName)'." 32 | + " Add the highlight name to HighlightName.swift if you want to add support for syntax highlighting it." 33 | + " This message will only be shown once per highlight name.") 34 | } 35 | #endif 36 | return nil 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ec3730/Persistence/HostDataGroup.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | public class HostDataGroup: NSManagedObject, Identifiable { 4 | @NSManaged public var date: Date 5 | @NSManaged public var url: URL 6 | // would be great if this was an ordered set but obj-c and nsmanaged doesn't like that 7 | @NSManaged public var results: Set 8 | 9 | convenience init(context: NSManagedObjectContext, url: URL, data: Set = Set()) { 10 | guard let entity = NSEntityDescription.entity(forEntityName: "HostDataGroup", in: context) else { 11 | fatalError("No entity named HostDataGroup") 12 | } 13 | self.init(entity: entity, insertInto: context) 14 | 15 | results = data 16 | self.url = url 17 | date = Date() 18 | } 19 | } 20 | 21 | extension HostDataGroup { 22 | // ❇️ The @FetchRequest property wrapper in the ContentView will call this function 23 | static func fetchAllRequest(limit: Int? = nil) -> NSFetchRequest { 24 | let request: NSFetchRequest = HostDataGroup.fetchRequest() as! NSFetchRequest 25 | if let limit { 26 | request.fetchLimit = limit 27 | } 28 | // ❇️ The @FetchRequest property wrapper in the ContentView requires a sort descriptor 29 | request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)] 30 | 31 | return request 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ec3730/Models/Sections/LocalDnsModel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @MainActor 4 | class LocalDnsModel: HostSectionModel { 5 | required convenience init() { 6 | self.init(LocalDns.current, service: LocalDns.lookupService) 7 | } 8 | 9 | override func configure(with data: Data?) throws -> Data? { 10 | reset() 11 | 12 | guard let data = data else { 13 | return nil 14 | } 15 | 16 | let addresses = try JSONDecoder().decode([String].self, from: data) 17 | 18 | return try configure(addresses: addresses) 19 | } 20 | 21 | func configure(addresses: [String]) throws -> Data { 22 | reset() 23 | 24 | let copyData = try JSONEncoder().encode(addresses) 25 | latestData = copyData 26 | dataToCopy = String(data: copyData, encoding: .utf8) 27 | 28 | for address in addresses { 29 | content.append(.row(title: "Address", content: address)) 30 | } 31 | 32 | return copyData 33 | } 34 | 35 | @discardableResult 36 | override func query(url: URL? = nil) async throws -> Data { 37 | reset() 38 | 39 | guard let host = url?.host else { 40 | throw URLError(URLError.badURL) 41 | } 42 | latestQueriedUrl = url 43 | latestQueryDate = .now 44 | 45 | let addresses: [String] = try await service.query(["host": host]) 46 | 47 | return try configure(addresses: addresses) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ec3730/Views/Host/HostResult.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import Foundation 3 | import SwiftUI 4 | 5 | struct HostResult: View { 6 | @State var shouldShare: Bool = false 7 | // @ObservedObject var model: HostViewModel 8 | 9 | @ObservedObject var group: HostDataGroup 10 | 11 | init(_ group: HostDataGroup) { 12 | self.group = group 13 | } 14 | 15 | var body: some View { 16 | VStack(alignment: .leading, spacing: 0) { 17 | ScrollView { 18 | LazyVStack(alignment: .leading, spacing: 0) { 19 | ForEach(Array(self.group.results.sorted(by: { $0.date < $1.date })), id: \.self) { result in 20 | if let data = HostSectionModel.configure(with: result, group: group) { 21 | HostResultSection(data: data) 22 | } else { 23 | VStack { 24 | Text("Error - please contact the developer") 25 | Text("\(result)") 26 | } 27 | } 28 | } 29 | } 30 | }.safeAreaInset(edge: .bottom) { 31 | HostBarView(url: group.url, date: group.date) 32 | } 33 | } 34 | .background(Color(UIColor.systemGroupedBackground)) 35 | .padding(.top, 0.15) 36 | .navigationTitle(self.group.url.host ?? "Unknown Host") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ec3730/Views/Interface/InterfaceConnectionBarView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct InterfaceConnectionBarView: View { 4 | @ObservedObject var model: ReachabilityModel 5 | 6 | var body: some View { 7 | VStack(alignment: .leading, spacing: 0.0) { 8 | Divider() 9 | HStack(alignment: .center) { 10 | Group { 11 | connectionText 12 | } 13 | .fixedSize(horizontal: false, vertical: true) 14 | .multilineTextAlignment(.leading) 15 | Spacer(minLength: 8) 16 | Toggle(isOn: .constant(model.connectionAvailable), label: { 17 | connectionText 18 | }) 19 | .labelsHidden() 20 | .disabled(true) 21 | }.padding(.horizontal).padding([.vertical], 6) 22 | }.background(VisualEffectView(effect: UIBlurEffect(style: .systemMaterial)).ignoresSafeArea(.all, edges: .horizontal)).ignoresSafeArea() 23 | } 24 | 25 | @ViewBuilder var connectionText: some View { 26 | switch model.connection { 27 | case .cellular: 28 | Text("Connected via cellular") 29 | case .wifi: 30 | if let ssid = model.ssid { 31 | Text("Connected via WiFi on \(ssid)") 32 | } else { 33 | Text("Connected via WiFi") 34 | } 35 | case .unavailable: 36 | Text("Network is unavailable") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /External/Themes/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Themes", 8 | platforms: [.iOS(.v14)], 9 | products: [ 10 | .library(name: "RunestoneTomorrowTheme", targets: ["RunestoneTomorrowTheme"]), 11 | .library(name: "RunestoneTomorrowNightTheme", targets: ["RunestoneTomorrowNightTheme"]), 12 | .library(name: "RunestoneOneDarkTheme", targets: ["RunestoneOneDarkTheme"]), 13 | .library(name: "RunestonePlainTextTheme", targets: ["RunestonePlainTextTheme"]), 14 | .library(name: "RunestoneThemeCommon", targets: ["RunestoneThemeCommon"]), 15 | ], 16 | dependencies: [ 17 | .package(url: "https://github.com/simonbs/Runestone", from: "0.1.2"), 18 | ], 19 | targets: [ 20 | .target(name: "RunestoneTomorrowTheme", dependencies: ["Runestone", "RunestoneThemeCommon"], resources: [.process("Colors.xcassets")]), 21 | .target(name: "RunestoneTomorrowNightTheme", dependencies: ["Runestone", "RunestoneThemeCommon"], resources: [.process("Colors.xcassets")]), 22 | .target(name: "RunestoneOneDarkTheme", dependencies: ["Runestone", "RunestoneThemeCommon"], resources: [.process("Colors.xcassets")]), 23 | .target(name: "RunestonePlainTextTheme", dependencies: ["Runestone", "RunestoneThemeCommon"]), 24 | .target(name: "RunestoneThemeCommon", dependencies: ["Runestone"]), 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /ec3730/Models/Device/DeviceInfoModel.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import Foundation 3 | 4 | /* 5 | case 1: return "Memory" 6 | case 2: return "JavaScriptCore" 7 | case 3: return "Fingerprints" 8 | case 4: 9 | return "Cellular Providers" 10 | */ 11 | 12 | @MainActor 13 | class DeviceInfoModel: ObservableObject { 14 | @Published var sections: [DeviceInfoSectionModel] = [ 15 | UIDeviceInfoModel(), 16 | ProcessInfoModel(), 17 | MemoryInfoModel(), 18 | JavaScriptInfoModel(), 19 | CarrierInfoModel(), 20 | FingerprintInfoModel(), 21 | DataUsageInfoModel(), 22 | ] 23 | 24 | func reload() async { 25 | objectWillChange.send() 26 | for section in sections { 27 | section.objectWillChange.send() 28 | await section.reload() 29 | } 30 | } 31 | 32 | func reloadFingerprints() async { 33 | guard let finger = sections.first(where: { $0 as? FingerprintInfoModel != nil }) as? FingerprintInfoModel else { 34 | return 35 | } 36 | objectWillChange.send() 37 | finger.objectWillChange.send() 38 | await finger.reload() 39 | } 40 | 41 | func attachFingerprint(model: FingerPrintModel) async { 42 | guard let finger = sections.first(where: { $0 as? FingerprintInfoModel != nil }) as? FingerprintInfoModel else { 43 | return 44 | } 45 | objectWillChange.send() 46 | model.parent = self 47 | await finger.attachModel(model: model) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeedSubscription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFeedSubscription.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/16/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import StoreKit 11 | import SwiftyStoreKit 12 | 13 | protocol DataFeedSubscription: DataFeedPurchaseProtocol { 14 | /// If the API is a paid API or if the user can submit their own API key 15 | 16 | var subscriptions: [Subscription] { get } 17 | } 18 | 19 | extension DataFeedSubscription { 20 | public func restore(completion block: ((RestoreResults) -> Void)? = nil) { 21 | SwiftyStoreKit.restorePurchases(atomically: true) { results in 22 | for sub in self.subscriptions { 23 | sub.restore() 24 | block?(results) 25 | } 26 | } 27 | } 28 | 29 | public func verifySubscriptions(completion block: ((Error?) -> Void)? = nil) { 30 | switch subscriptions.count { 31 | case 0: 32 | block?(nil) 33 | case 1: 34 | subscriptions.first?.verifySubscription { _ in 35 | block?(nil) 36 | } 37 | case 2: 38 | subscriptions[0].verifySubscription { _ in 39 | self.subscriptions[1].verifySubscription { _ in 40 | block?(nil) 41 | } 42 | } 43 | default: 44 | for sub in subscriptions { 45 | sub.verifySubscription() 46 | } 47 | block?(nil) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ec3730/LoadingCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingCell.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/12/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class LoadingCell: UITableViewCell { 13 | var spinner: UIActivityIndicatorView 14 | 15 | init(reuseIdentifier: String?) { 16 | spinner = UIActivityIndicatorView() 17 | if #available(iOS 13.0, *) { 18 | spinner.style = .medium 19 | } else { 20 | spinner.style = .gray 21 | } 22 | 23 | super.init(style: .default, reuseIdentifier: reuseIdentifier) 24 | 25 | let stack = UIStackView() 26 | stack.distribution = .equalCentering 27 | stack.translatesAutoresizingMaskIntoConstraints = false 28 | contentView.addSubview(stack) 29 | 30 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 31 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 32 | 33 | spinner.translatesAutoresizingMaskIntoConstraints = false 34 | stack.addArrangedSubview(spinner) 35 | spinner.startAnimating() 36 | } 37 | 38 | convenience init() { 39 | self.init(reuseIdentifier: "loading") 40 | } 41 | 42 | @available(*, unavailable) 43 | required init?(coder _: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/URL Parse/UrlParsedFeed.swift: -------------------------------------------------------------------------------- 1 | import AddressURL 2 | import CloudKit 3 | import CoreData 4 | import Foundation 5 | import KeychainAccess 6 | import StoreKit 7 | import SwiftyStoreKit 8 | 9 | final class URLParsedFeed: DataFeedSingleton { 10 | var name: String = "Parse URL" 11 | 12 | var webpage: URL = .init(string: "https://zac.gorak.us/")! 13 | 14 | public var userKey: String? 15 | 16 | public static var current: URLParsedFeed = .init() 17 | 18 | static var session = URLSession.shared 19 | 20 | var services: [Service] = { 21 | [URLParsedFeed.lookupService] 22 | }() 23 | } 24 | 25 | extension URLParsedFeed: DataFeedService { 26 | var totalUsage: Int { 27 | services.reduce(0) { $0 + $1.usage } 28 | } 29 | 30 | public static var lookupService: URLLookupService = .init() 31 | 32 | class URLLookupService: Service { 33 | var name: String = "Parse URL" 34 | var description = "Parse URL" 35 | 36 | func endpoint(_: [String: Any?]?) -> DataFeedEndpoint? { 37 | nil 38 | } 39 | 40 | func query(_ userData: [String: Any?]?, completion block: ((Error?, T?) -> Void)?) { 41 | guard let host = userData?["url"] as? T else { 42 | block?(URLError(.badURL), nil) 43 | return 44 | } 45 | 46 | block?(nil, host) 47 | } 48 | 49 | func query(_ userData: [String: Any?]?) async throws -> T { 50 | guard let host = userData?["url"] as? T else { 51 | throw URLError(.badURL) 52 | } 53 | 54 | return host 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestonePlainTextTheme/PlainTextTheme.swift: -------------------------------------------------------------------------------- 1 | import Runestone 2 | import RunestoneThemeCommon 3 | import UIKit 4 | 5 | public final class PlainTextTheme: EditorTheme { 6 | public let backgroundColor: UIColor = .white 7 | public let userInterfaceStyle: UIUserInterfaceStyle = .light 8 | 9 | public let font: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 10 | public let textColor: UIColor = .black 11 | 12 | public let gutterBackgroundColor: UIColor = .white 13 | public let gutterHairlineColor: UIColor = .white 14 | 15 | public let lineNumberColor: UIColor = .black.withAlphaComponent(0.5) 16 | public let lineNumberFont: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 17 | 18 | public let selectedLineBackgroundColor: UIColor = .black.withAlphaComponent(0.07) 19 | public let selectedLinesLineNumberColor: UIColor = .black 20 | public let selectedLinesGutterBackgroundColor: UIColor = .black.withAlphaComponent(0.07) 21 | 22 | public let invisibleCharactersColor: UIColor = .black.withAlphaComponent(0.5) 23 | 24 | public let pageGuideHairlineColor: UIColor = .black.withAlphaComponent(0.1) 25 | public let pageGuideBackgroundColor: UIColor = .black.withAlphaComponent(0.06) 26 | 27 | public let markedTextBackgroundColor: UIColor = .black.withAlphaComponent(0.1) 28 | public let markedTextBackgroundCornerRadius: CGFloat = 4 29 | 30 | public init() {} 31 | 32 | public func textColor(for _: String) -> UIColor? { 33 | nil 34 | } 35 | 36 | public func fontTraits(for rawHighlightName: String) -> FontTraits { 37 | if let highlightName = HighlightName(rawHighlightName), highlightName == .keyword { 38 | return .bold 39 | } else { 40 | return [] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/Google/GoogleWebRiskCellManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleWebRiskCellManager.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/17/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class GoogleWebRiskCellManager: CellManager { 13 | override func askForMoney() { 14 | if let purchase = dataFeed as? DataFeedPurchaseProtocol { 15 | if !purchase.owned { 16 | let locked = WhoisLockedTableViewCell(purchase, heading: "Unlock Google Web Risk Detection", subheading: "Detect malicious URLs and unsafe web resources") 17 | locked.iapDelegate = self 18 | cells = [locked] 19 | } 20 | } 21 | } 22 | 23 | public var currentRecord: GoogleWebRiskRecordWrapper? 24 | func configure(_ record: GoogleWebRiskRecordWrapper?) { 25 | stopLoading() 26 | guard let record = record else { 27 | return 28 | } 29 | 30 | currentRecord = record 31 | 32 | cells = [] 33 | 34 | if let threats = currentRecord?.threat { 35 | for threat in threats.threatTypes { 36 | let cell = UITableViewCell(style: .default, reuseIdentifier: threat.rawValue) 37 | cell.textLabel?.text = threat.description 38 | cells.append(cell) 39 | } 40 | } else { 41 | let noRisks = UITableViewCell(style: .default, reuseIdentifier: "no_risks") 42 | noRisks.textLabel?.text = "No risks detected" 43 | cells.append(noRisks) 44 | } 45 | } 46 | 47 | override func reload() { 48 | if let prod = dataFeed as? DataFeedPurchaseProtocol { 49 | if prod.owned { 50 | configure(currentRecord) 51 | } else { 52 | askForMoney() 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ec3730/CopyLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CopyLabel.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/12/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class CopyLabel: UILabel { 13 | @objc func copyText(_: AnyObject) { 14 | UIPasteboard.general.string = text 15 | } 16 | 17 | @objc func copyAction(_ sender: UIGestureRecognizer) { 18 | guard let label = sender.view as? CopyLabel else { 19 | return 20 | } 21 | 22 | let showPasswordItem = UIMenuItem(title: "Copy", action: #selector(label.copyText(_:))) 23 | 24 | // https://stackoverflow.com/questions/38472461/ios-create-copy-paste-like-popover-uimenucontroller-in-uitableview 25 | UIMenuController.shared.menuItems?.removeAll() 26 | UIMenuController.shared.menuItems = [showPasswordItem] 27 | UIMenuController.shared.update() 28 | 29 | label.becomeFirstResponder() 30 | 31 | // NotificationCenter.default.addObserver(self, selector: #selector(highLightText(_:)), name: UIMenuController.didShowMenuNotification, object: nil) 32 | // NotificationCenter.default.addObserver(self, selector: #selector(deselectText(_:)), name: UIMenuController.didHideMenuNotification, object: nil) 33 | 34 | let menu = UIMenuController.shared 35 | 36 | // let rect = timeLabel.textRect(forBounds: timeLabel.frame, limitedToNumberOfLines: 0) 37 | // copyMenu.setTargetRect(rect, in: self) 38 | // menu.setTargetRect(rect, in: timeLabel) 39 | menu.showMenu(from: label.superview!, rect: label.frame) 40 | } 41 | 42 | override var canBecomeFirstResponder: Bool { 43 | true 44 | } 45 | 46 | override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { 47 | action == #selector(copyAction(_:)) || super.canPerformAction(action, withSender: sender) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ec3730/Views/UIViewControllerView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct UIViewControllerView: UIViewControllerRepresentable { 4 | func makeCoordinator() -> Coordinator { 5 | Coordinator() 6 | } 7 | 8 | var controller: Wrapper 9 | 10 | init(_ controller: @escaping @autoclosure () -> Wrapper) { 11 | self.controller = controller() 12 | } 13 | 14 | func makeUIViewController(context: Context) -> Wrapper { 15 | DispatchQueue.main.async { 16 | updateTitle(controller) 17 | } 18 | context.coordinator.parentObserver = controller.observe(\.parent, changeHandler: { vc, _ in 19 | updateTitle(vc) 20 | }) 21 | 22 | return controller 23 | } 24 | 25 | func updateUIViewController(_ vc: Wrapper, context _: Context) { 26 | updateTitle(vc) 27 | } 28 | 29 | private func updateTitle(_ vc: Wrapper) { 30 | vc.parent?.title = vc.title 31 | vc.parent?.navigationItem.title = vc.navigationItem.title 32 | vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems 33 | } 34 | 35 | class Coordinator { 36 | var parentObserver: NSKeyValueObservation? 37 | } 38 | } 39 | 40 | /// - SeeAlso: https://betterprogramming.pub/how-to-use-uiviewrepresentable-with-swiftui-7295bfec312b 41 | struct Anything: UIViewRepresentable { 42 | typealias Updater = (Wrapper, Context) -> Void 43 | 44 | var makeView: () -> Wrapper 45 | var update: (Wrapper, Context) -> Void 46 | 47 | init(_ makeView: @escaping @autoclosure () -> Wrapper, 48 | updater update: @escaping (Wrapper) -> Void) { 49 | self.makeView = makeView 50 | self.update = { view, _ in update(view) } 51 | } 52 | 53 | func makeUIView(context _: Context) -> Wrapper { 54 | makeView() 55 | } 56 | 57 | func updateUIView(_ view: Wrapper, context: Context) { 58 | update(view, context) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ec3730/Views/Device/DeviceInfoSectionView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct DeviceInfoSectionView: View { 4 | var section: DeviceInfoSectionModel 5 | @AppStorage var isExpanded: Bool 6 | @State var focused = false 7 | 8 | init(section: DeviceInfoSectionModel) { 9 | self.section = section 10 | _isExpanded = AppStorage(wrappedValue: true, "\(section.title).deviceinfo.isExpanded") 11 | } 12 | 13 | var body: some View { 14 | FSDisclosureGroup(isExpanded: $isExpanded, content: { 15 | VStack(spacing: 0) { 16 | ForEach(section.rows) { row in 17 | row 18 | } 19 | } 20 | .cornerRadius(6) 21 | }, label: { 22 | HStack(alignment: .center) { 23 | Text(section.title).font(.headline).padding() 24 | Spacer() 25 | } 26 | }) 27 | .background(Color(UIColor.systemGroupedBackground)) 28 | .contextMenu { 29 | Button(action: { 30 | withAnimation { 31 | self.isExpanded.toggle() 32 | } 33 | }, label: { 34 | Label(self.isExpanded ? "Collapse" : "Expand", systemImage: self.isExpanded ? "rectangle.compress.vertical" : "rectangle.expand.vertical") 35 | }) 36 | Button(action: { 37 | withAnimation { 38 | self.focused.toggle() 39 | } 40 | }, label: { 41 | Label("Focus", systemImage: "rectangle.and.text.magnifyingglass") 42 | }) 43 | } 44 | .sheet(isPresented: $focused, content: { 45 | EZPanel(content: { 46 | ScrollView { 47 | ForEach(section.rows) { row in 48 | row 49 | } 50 | } 51 | .navigationTitle(section.title) 52 | .navigationBarTitleDisplayMode(.inline) 53 | }) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # By default, SwiftLint uses a set of sensible default rules you can adjust: 2 | disabled_rules: # rule identifiers turned on by default to exclude from running 3 | - colon 4 | - comma 5 | - control_statement 6 | - trailing_comma 7 | opt_in_rules: # some rules are turned off by default, so you need to opt-in 8 | - empty_count # Find all the available rules by running: `swiftlint rules` 9 | 10 | # Alternatively, specify all rules explicitly by uncommenting this option: 11 | # only_rules: # delete `disabled_rules` & `opt_in_rules` if using this 12 | # - empty_parameters 13 | # - vertical_whitespace 14 | included: 15 | - ec3730 16 | - ec3730Tests 17 | - ec3730UITests 18 | excluded: # paths to ignore during linting. Takes precedence over `included`. 19 | - fastlane 20 | - "**/.build/" 21 | # Exclude files with a wildcard 22 | analyzer_rules: # Rules run by `swiftlint analyze` 23 | - explicit_self 24 | 25 | # configurable rules can be customized from this configuration file 26 | # binary rules can set their severity level 27 | force_cast: warning # implicitly 28 | force_try: 29 | severity: warning # explicitly 30 | # rules that have both warning and error levels, can set just the warning level 31 | # implicitly 32 | line_length: 110 33 | # they can set both implicitly with an array 34 | type_body_length: 35 | - 300 # warning 36 | - 400 # error 37 | # or they can set both explicitly 38 | file_length: 39 | warning: 500 40 | error: 1000 41 | # naming rules can set warnings/errors for min_length and max_length 42 | # additionally they can set excluded names 43 | type_name: 44 | min_length: 4 # only warning 45 | max_length: # warning and error 46 | warning: 40 47 | error: 50 48 | excluded: iPhone # excluded via string 49 | allowed_symbols: ["_"] # these are allowed in type names 50 | identifier_name: 51 | min_length: # only min_length 52 | error: 4 # only error 53 | excluded: # excluded via string array 54 | - id 55 | - URL 56 | - GlobalAPIKey 57 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging) 58 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeedErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhoisErrors.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/13/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum DataFeedError: Error { 12 | case invalidUrl 13 | case invalidProduct(id: String) 14 | case empty // No data 15 | case lowBalance(balance: Int) 16 | case parse // unable to parse data 17 | case `nil` // found and unexpected 18 | 19 | case custom(description: String, reason: String?, suggestion: String?, help: String?) 20 | 21 | init(_ with: DataFeedError) { 22 | self = with 23 | } 24 | } 25 | 26 | extension DataFeedError: LocalizedError { 27 | /// A localized message describing what error occurred. 28 | var errorDescription: String? { 29 | switch self { 30 | case let .invalidProduct(id): 31 | return "Invalid product identifier \"\(id)\"" 32 | case let .custom(desc, _, _, _): 33 | return desc 34 | default: 35 | return nil 36 | } 37 | } 38 | 39 | /// A localized message describing the reason for the failure. 40 | var failureReason: String? { 41 | switch self { 42 | case let .custom(_, reason, _, _): 43 | return reason 44 | default: 45 | return nil 46 | } 47 | } 48 | 49 | /// A localized message describing how one might recover from the failure. 50 | var recoverySuggestion: String? { 51 | switch self { 52 | case let .custom(_, _, suggestion, _): 53 | return suggestion 54 | default: 55 | return nil 56 | } 57 | } 58 | 59 | /// A localized message providing "help" text if the user requests help. 60 | var helpAnchor: String? { 61 | switch self { 62 | case let .custom(_, _, _, help): 63 | return help 64 | default: 65 | return nil 66 | } 67 | } 68 | } 69 | 70 | extension DataFeedError: RecoverableError { 71 | func attemptRecovery(optionIndex _: Int) -> Bool { 72 | false 73 | } 74 | 75 | var recoveryOptions: [String] { 76 | [] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/DataFeedSubscriptionCellManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFeedSubscriptionCellManager.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/15/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class DataFeedSubscriptionCell: UITableViewCell { 13 | var subscription: Subscription 14 | init(_ subscription: Subscription) { 15 | self.subscription = subscription 16 | super.init(style: .value1, reuseIdentifier: subscription.identifier) 17 | 18 | textLabel?.text = self.subscription.product?.subscriptionPeriod?.unit.localizedAdjectiveDescription 19 | detailTextLabel?.text = self.subscription.product?.localizedPrice 20 | 21 | if subscription.isSubscribed { 22 | accessoryType = .checkmark 23 | } 24 | } 25 | 26 | @available(*, unavailable) 27 | required init?(coder _: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | } 31 | 32 | class DataFeedOneTimeCell: UITableViewCell { 33 | var product: OneTimePurchase 34 | init(_ product: OneTimePurchase) { 35 | self.product = product 36 | super.init(style: .value1, reuseIdentifier: product.identifier) 37 | 38 | textLabel?.text = "One-Time Purchase" 39 | detailTextLabel?.text = self.product.product?.localizedPrice 40 | } 41 | 42 | @available(*, unavailable) 43 | required init?(coder _: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | } 47 | 48 | class DataFeedSubscriptionCellManager { 49 | let subscriber: DataFeed 50 | 51 | var subscriptionCells = [DataFeedSubscriptionCell]() 52 | var oneTimePurchaseCell: DataFeedOneTimeCell? 53 | 54 | init(subscriber: DataFeed) { 55 | self.subscriber = subscriber 56 | 57 | if let subscriptions = self.subscriber as? DataFeedSubscription { 58 | for (_, sub) in subscriptions.subscriptions.enumerated() { 59 | subscriptionCells.append(DataFeedSubscriptionCell(sub)) 60 | } 61 | } 62 | 63 | if let oneTime = self.subscriber as? DataFeedOneTimePurchase { 64 | oneTimePurchaseCell = DataFeedOneTimeCell(oneTime.oneTime) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ec3730/Persistence/Persistence.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | import Foundation 3 | 4 | struct PersistenceController { 5 | static let shared = PersistenceController() 6 | 7 | static var preview: PersistenceController = { 8 | let result = PersistenceController(inMemory: true) 9 | let viewContext = result.container.viewContext 10 | do { 11 | try viewContext.save() 12 | } catch { 13 | // Replace this implementation with code to handle the error appropriately. 14 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 15 | let nsError = error as NSError 16 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)") 17 | } 18 | return result 19 | }() 20 | 21 | let container: NSPersistentCloudKitContainer 22 | 23 | init(inMemory: Bool = false) { 24 | container = NSPersistentCloudKitContainer(name: "DataModel") 25 | if inMemory { 26 | container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") 27 | } 28 | container.loadPersistentStores(completionHandler: { _, error in 29 | if let error = error as NSError? { 30 | // Replace this implementation with code to handle the error appropriately. 31 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 32 | 33 | /* 34 | Typical reasons for an error here include: 35 | * The parent directory does not exist, cannot be created, or disallows writing. 36 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 37 | * The device is out of space. 38 | * The store could not be migrated to the current model version. 39 | Check the error message to determine what the actual problem was. 40 | */ 41 | fatalError("Unresolved error \(error), \(error.userInfo)") 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ec3730/WiFiSSID.swift: -------------------------------------------------------------------------------- 1 | // https://github.com/HackingGate/iOS13-WiFi-Info 2 | 3 | import Foundation 4 | import SystemConfiguration.CaptiveNetwork 5 | 6 | func getSSID() -> String? { 7 | var ssid: String? 8 | if let interfaces = CNCopySupportedInterfaces() as NSArray? { 9 | for interface in interfaces { 10 | // swiftlint:disable:next force_cast 11 | if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? { 12 | // Can also see BSSID and SSIDDATA 13 | ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String 14 | break 15 | } 16 | } 17 | } 18 | return ssid 19 | } 20 | 21 | open class WiFi { 22 | // swiftlint:disable:next large_tuple 23 | public class func ssidInfo() -> (interface: String?, ssid: String?, info: [String: Any?]?)? { 24 | if let interfaces = CNCopySupportedInterfaces() as NSArray? { 25 | for interfaceKey in interfaces { 26 | // swiftlint:disable:next force_cast 27 | if let interfaceInfo = CNCopyCurrentNetworkInfo(interfaceKey as! CFString) as NSDictionary? { 28 | let interface = interfaceKey as? String 29 | let ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String 30 | let info = interfaceInfo as? [String: Any?] 31 | return (interface, ssid, info) 32 | } 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | // completion block wrapper for ssidInfo 39 | public class func ssid(completion block: ((String?, String?, [String: Any?]?) -> Void)? = nil) -> String? { 40 | if let (interface, ssid, info) = WiFi.ssidInfo() { 41 | block?(interface, ssid, info) 42 | return ssid 43 | } else { 44 | block?(nil, nil, nil) 45 | return nil 46 | } 47 | } 48 | 49 | public class func proxySettings(for interface: String) -> [String: Any?]? { 50 | let cfDict = CFNetworkCopySystemProxySettings() 51 | let nsDict = cfDict!.takeRetainedValue() as NSDictionary 52 | guard let keys = nsDict["__SCOPED__"] as? NSDictionary else { 53 | return nil 54 | } 55 | 56 | return keys.object(forKey: interface) as? [String: Any?] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ec3730/Models/Sections/WhoisXmlReputationSectionModel.swift: -------------------------------------------------------------------------------- 1 | import Cache 2 | import SwiftUI 3 | 4 | class WhoisXmlReputationSectionModel: HostSectionModel { 5 | required convenience init() { 6 | self.init(WhoisXml.current, service: WhoisXml.reputationService) 7 | storeModel = StoreKitModel.whois 8 | } 9 | 10 | @MainActor 11 | override func configure(with data: Data) throws -> Data? { 12 | reset() 13 | 14 | let result = try JSONDecoder().decode(WhoisXmlReputationRecord.self, from: data) 15 | 16 | return try configure(with: result) 17 | } 18 | 19 | @MainActor 20 | func configure(with record: WhoisXmlReputationRecord) throws -> Data { 21 | reset() 22 | 23 | let copyData = try JSONEncoder().encode(record) 24 | latestData = copyData 25 | dataToCopy = String(data: copyData, encoding: .utf8) 26 | 27 | if let score = record.reputationScore { 28 | content.append(.row(title: "Score", content: "\(score)")) 29 | } else { 30 | content.append(.row(title: "Score", content: "-")) 31 | } 32 | content.append(.row(title: "Mode", content: "\(record.mode ?? "-")")) 33 | 34 | guard let tests = record.testResults else { 35 | return copyData 36 | } 37 | 38 | for test in tests { 39 | var rows = [CopyCellType]() 40 | for warning in test.warnings { 41 | rows.append(.row(title: "", content: warning, style: .expandable)) 42 | } 43 | content.append(.multiple(title: test.test, contents: rows)) 44 | } 45 | 46 | return copyData 47 | } 48 | 49 | @discardableResult 50 | override func query(url: URL? = nil) async throws -> Data { 51 | reset() 52 | 53 | guard let host = url?.host else { 54 | throw URLError(.badURL) 55 | } 56 | latestQueriedUrl = url 57 | latestQueryDate = .now 58 | 59 | guard dataFeed.userKey != nil || storeModel?.owned ?? false else { 60 | throw MoreStoreKitError.NotPurchased 61 | } 62 | 63 | let response: WhoisXmlReputationRecord = try await WhoisXml.reputationService.query([ 64 | "domain": host, 65 | "mode": "fast", 66 | "minimumBalance": 25, 67 | ]) 68 | 69 | return try configure(with: response) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ec3730/CopyCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CopyDetailCell.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/12/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class CopyCell: UITableViewCell { 13 | var titleLabel: CopyLabel? 14 | var detailLabel: CopyLabel? 15 | var stack: UIStackView 16 | 17 | init(title: String, detail: String? = nil) { 18 | stack = UIStackView() 19 | super.init(style: .default, reuseIdentifier: title) 20 | 21 | stack.spacing = 10.0 22 | stack.axis = .horizontal 23 | stack.translatesAutoresizingMaskIntoConstraints = false 24 | stack.distribution = .fill 25 | contentView.addSubview(stack) 26 | 27 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 28 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 29 | 30 | titleLabel = CopyLabel() 31 | titleLabel?.text = title 32 | titleLabel?.font = UITableViewCell(style: .value1, reuseIdentifier: "reused").textLabel?.font 33 | titleLabel?.textColor = UITableViewCell(style: .value1, reuseIdentifier: "reused").textLabel?.textColor 34 | // titleLabel?.font = UIFont.boldSystemFont(ofSize: UIFont.systemFontSize) 35 | 36 | detailLabel = CopyLabel() 37 | detailLabel?.text = detail 38 | detailLabel?.textAlignment = .right 39 | detailLabel?.font = UITableViewCell(style: .value1, reuseIdentifier: "reused").detailTextLabel?.font 40 | detailLabel?.textColor = UITableViewCell(style: .value1, reuseIdentifier: "reused").detailTextLabel?.textColor 41 | detailLabel?.adjustsFontSizeToFitWidth = true 42 | 43 | let hold = UILongPressGestureRecognizer(target: titleLabel!, action: #selector(titleLabel!.copyAction(_:))) 44 | hold.isEnabled = true 45 | titleLabel?.isUserInteractionEnabled = true 46 | titleLabel?.addGestureRecognizer(hold) 47 | 48 | stack.addArrangedSubview(titleLabel!) 49 | stack.addArrangedSubview(detailLabel!) 50 | 51 | titleLabel?.widthAnchor.constraint(greaterThanOrEqualTo: stack.widthAnchor, multiplier: 0.25).isActive = true 52 | } 53 | 54 | @available(*, unavailable) 55 | required init?(coder _: NSCoder) { 56 | fatalError("init(coder:) has not been implemented") 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ec3730/Views/Source/WebWrapperView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import WebKit 3 | 4 | struct WebWrapperView: UIViewRepresentable { 5 | @Binding var webView: WKWebView 6 | @Binding var source: String 7 | @Binding var url: URL 8 | @Binding var isLoading: Bool 9 | 10 | func makeUIView(context: Context) -> WKWebView { 11 | let view = webView 12 | view.navigationDelegate = context.coordinator 13 | return view 14 | } 15 | 16 | func updateUIView(_: WKWebView, context _: Context) { 17 | // no-op 18 | } 19 | 20 | func makeCoordinator() -> Coordintor { 21 | Coordintor(self) 22 | } 23 | 24 | class Coordintor: NSObject, WKNavigationDelegate { 25 | var wrapper: WebWrapperView 26 | var webView: WKWebView? 27 | 28 | init(_ wrapper: WebWrapperView) { 29 | self.wrapper = wrapper 30 | super.init() 31 | } 32 | 33 | private func setJavascript(completion block: (() -> Void)? = nil) { 34 | webView?.evaluateJavaScript("document.documentElement.outerHTML", completionHandler: { [self] source, error in 35 | 36 | guard error == nil else { 37 | block?() 38 | return 39 | } 40 | 41 | guard let source = source as? String else { 42 | block?() 43 | return 44 | } 45 | Task { @MainActor in 46 | self.wrapper.source = source 47 | block?() 48 | } 49 | }) 50 | } 51 | 52 | func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { 53 | if let url = webView.url { 54 | wrapper.url = url 55 | } 56 | self.webView = webView 57 | setJavascript { 58 | self.wrapper.isLoading = false 59 | } 60 | } 61 | 62 | func webView(_ webView: WKWebView, didFail _: WKNavigation!, withError _: Error) { 63 | if let url = webView.url { 64 | wrapper.url = url 65 | } 66 | self.webView = webView 67 | setJavascript { 68 | self.wrapper.isLoading = false 69 | } 70 | } 71 | 72 | func webView(_ webView: WKWebView, didStartProvisionalNavigation _: WKNavigation!) { 73 | DispatchQueue.main.async { 74 | self.wrapper.source.removeAll() 75 | } 76 | self.webView = webView 77 | wrapper.isLoading = true 78 | wrapper.source = "" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ec3730/Views/Host/HostBarView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct HostBarView: View { 4 | @State var text = "" 5 | var url: URL 6 | var date: Date 7 | @State var showInfo = false 8 | var body: some View { 9 | VStack(alignment: .leading, spacing: 0.0) { 10 | Divider() 11 | HStack(alignment: .center) { 12 | // it would be great if this could be a .bottomBar toolbar but it is too buggy 13 | TextField("", text: $text, prompt: Text(url.absoluteString)) 14 | .truncationMode(.middle) 15 | .disabled(true) 16 | .textFieldStyle(RoundedBorderTextFieldStyle()) 17 | .keyboardType(.URL) 18 | 19 | Button(action: { 20 | showInfo.toggle() 21 | }, label: { 22 | Image(systemName: "info.circle") 23 | }) 24 | } 25 | .padding(.horizontal) 26 | .padding([.vertical], 6) 27 | 28 | HStack(alignment: .center) { 29 | Spacer() 30 | Text(date.ISO8601Format()) 31 | .font(.footnote) 32 | .foregroundColor(Color(UIColor.separator)) 33 | .padding([.bottom], 6) 34 | .contextMenu { 35 | Button(action: { 36 | UIPasteboard.general.string = date.ISO8601Format() 37 | }, label: { 38 | Label("Copy", systemImage: "doc.on.doc") 39 | }) 40 | } 41 | Spacer() 42 | } 43 | } 44 | .confirmationDialog("Information", isPresented: $showInfo) { 45 | Button(action: { 46 | UIPasteboard.general.string = url.absoluteString 47 | }, label: { 48 | Label("Copy URL", systemImage: "doc.on.doc") 49 | }) 50 | Button(action: { 51 | UIPasteboard.general.string = date.ISO8601Format() 52 | }, label: { 53 | Label("Copy Date", systemImage: "doc.on.doc") 54 | }) 55 | }.background( 56 | VisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) 57 | .ignoresSafeArea() 58 | ) 59 | } 60 | } 61 | 62 | #if DEBUG 63 | struct HostBarView_preview: PreviewProvider { 64 | static var previews: some View { 65 | Group { 66 | HostBarView(url: URL(staticString: "https://google.com"), date: Date.now) 67 | } 68 | } 69 | } 70 | #endif 71 | -------------------------------------------------------------------------------- /ec3730/Views/Host/HostSectionOrganizerView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @available(iOS 15.0, *) 4 | struct HostSectionOrganizerView: View { 5 | @EnvironmentObject var model: HostViewModel 6 | 7 | @Environment(\.editMode) var mode 8 | 9 | var body: some View { 10 | List { 11 | Section(header: 12 | Text("Visible") 13 | ) { 14 | ForEach(model.sections) { section in 15 | Text("\(section.sectionModel.service.name)") 16 | }.onMove { indexSet, offset in 17 | print(indexSet, indexSet.first ?? "", offset) 18 | withAnimation { 19 | self.model.objectWillChange.send() 20 | self.model.sections.move(fromOffsets: indexSet, toOffset: offset) 21 | } 22 | } 23 | // would be great if instead of delete it said hide 24 | .onDelete { indexSet in 25 | print(indexSet, indexSet.first ?? "") 26 | if let index = indexSet.first { 27 | let section = model.sections[index] 28 | withAnimation { 29 | self.model.hidden.append(section.sectionModel.service.name) 30 | } 31 | } 32 | } 33 | } 34 | 35 | if self.mode?.wrappedValue.isEditing ?? true || !model.hidden.isEmpty { 36 | Section(header: Text("Hidden")) { 37 | ForEach(model.hidden, id: \.self) { section in 38 | Text(section) 39 | } 40 | // would be great if instead of delete it said 41 | // unhide or show 42 | .onDelete { indexSet in 43 | if let index = indexSet.first { 44 | let section = model.hidden[index] 45 | withAnimation { 46 | self.model.hidden.removeAll(where: { $0 == section }) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | }.toolbar { 54 | EditButton() 55 | }.navigationTitle("Section Order") 56 | } 57 | } 58 | 59 | @available(iOS 15.0, *) 60 | struct HostSectionOrganizerView_Previews: PreviewProvider { 61 | static var previews: some View { 62 | Group { 63 | NavigationView { 64 | HostSectionOrganizerView() 65 | } 66 | }.previewLayout(.sizeThatFits) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ec3730/CopyDetailCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CopyDetailCell.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/12/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class CopyDetailCell: UITableViewCell { 13 | var titleLabel: UILabel? 14 | var detailLabel: CopyLabel? 15 | var stack: UIStackView 16 | 17 | init(title: String, detail: String) { 18 | stack = UIStackView() 19 | super.init(style: .default, reuseIdentifier: title) 20 | 21 | stack.spacing = 10.0 22 | stack.axis = .horizontal 23 | stack.translatesAutoresizingMaskIntoConstraints = false 24 | stack.distribution = .fill 25 | contentView.addSubview(stack) 26 | 27 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 28 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 29 | 30 | titleLabel = UILabel() 31 | titleLabel?.text = title 32 | titleLabel?.font = UITableViewCell(style: .value1, reuseIdentifier: "reused").textLabel?.font 33 | titleLabel?.textColor = UITableViewCell(style: .value1, reuseIdentifier: "reused").textLabel?.textColor 34 | // titleLabel?.font = UIFont.boldSystemFont(ofSize: UIFont.systemFontSize) 35 | 36 | detailLabel = CopyLabel() 37 | detailLabel?.text = detail 38 | detailLabel?.textAlignment = .right 39 | detailLabel?.font = UITableViewCell(style: .value1, reuseIdentifier: "reused").detailTextLabel?.font 40 | detailLabel?.textColor = UITableViewCell(style: .value1, reuseIdentifier: "reused").detailTextLabel?.textColor 41 | detailLabel?.adjustsFontSizeToFitWidth = true 42 | 43 | let hold = UILongPressGestureRecognizer(target: detailLabel!, action: #selector(detailLabel!.copyAction(_:))) 44 | hold.isEnabled = true 45 | detailLabel?.isUserInteractionEnabled = true 46 | detailLabel?.addGestureRecognizer(hold) 47 | 48 | stack.addArrangedSubview(titleLabel!) 49 | stack.addArrangedSubview(detailLabel!) 50 | 51 | titleLabel?.widthAnchor.constraint(greaterThanOrEqualTo: stack.widthAnchor, multiplier: 0.25).isActive = true 52 | } 53 | 54 | @available(*, unavailable) 55 | required init?(coder _: NSCoder) { 56 | fatalError("init(coder:) has not been implemented") 57 | } 58 | 59 | func addRow(_ row: ContactCellRow) { 60 | stack.addArrangedSubview(row) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/WhoisXML/WhoIsXmlGeoLocationResult.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - WhoIsXmlGeoLocationResult 4 | 5 | struct WhoIsXmlGeoLocationResult: Codable { 6 | let ip: String? 7 | let location: Location? 8 | let domains: [String]? 9 | let geoLocationModelClassAs: GeoLocationAs? 10 | let isp, connectionType: String? 11 | 12 | enum CodingKeys: String, CodingKey { 13 | case ip, location, domains 14 | case geoLocationModelClassAs = "as" 15 | case isp, connectionType 16 | } 17 | } 18 | 19 | extension WhoIsXmlGeoLocationResult { 20 | init(data: Data) throws { 21 | self = try newJSONDecoder().decode(WhoIsXmlGeoLocationResult.self, from: data) 22 | } 23 | 24 | init(_ json: String, using encoding: String.Encoding = .utf8) throws { 25 | guard let data = json.data(using: encoding) else { 26 | throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil) 27 | } 28 | try self.init(data: data) 29 | } 30 | 31 | init(fromURL url: URL) throws { 32 | try self.init(data: try Data(contentsOf: url)) 33 | } 34 | 35 | func with(ip: String? = nil, 36 | location: Location? = nil, 37 | domains: [String]? = nil, 38 | geoLocationModelClassAs: GeoLocationAs? = nil, 39 | isp: String? = nil, 40 | connectionType: String? = nil) -> WhoIsXmlGeoLocationResult { 41 | WhoIsXmlGeoLocationResult(ip: ip ?? self.ip, 42 | location: location ?? self.location, 43 | domains: domains ?? self.domains, 44 | geoLocationModelClassAs: geoLocationModelClassAs ?? self.geoLocationModelClassAs, 45 | isp: isp ?? self.isp, 46 | connectionType: connectionType ?? self.connectionType) 47 | } 48 | 49 | func jsonData() throws -> Data { 50 | try newJSONEncoder().encode(self) 51 | } 52 | 53 | func jsonString(encoding: String.Encoding = .utf8) throws -> String? { 54 | String(data: try jsonData(), encoding: encoding) 55 | } 56 | } 57 | 58 | // MARK: - GeoLocationAs 59 | 60 | struct GeoLocationAs: Codable { 61 | let asn: Int? 62 | let name, route: String? 63 | let domain: String? 64 | let type: String? 65 | } 66 | 67 | // MARK: - Location 68 | 69 | struct Location: Codable { 70 | let country, region, city: String? 71 | let lat, lng: Double? 72 | let postalCode, timezone: String? 73 | let geonameID: Int? 74 | 75 | enum CodingKeys: String, CodingKey { 76 | case country, region, city, lat, lng, postalCode, timezone 77 | case geonameID = "geonameId" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/LocalDns/LocalDns.swift: -------------------------------------------------------------------------------- 1 | import AddressURL 2 | import CloudKit 3 | import CoreData 4 | import Foundation 5 | import KeychainAccess 6 | import StoreKit 7 | import SwiftyStoreKit 8 | 9 | final class LocalDns: DataFeedSingleton { 10 | var name: String = "Simple IP Lookup" 11 | 12 | var webpage: URL = .init(string: "https://zac.gorak.us/")! 13 | 14 | public var userKey: String? 15 | 16 | public static var current: LocalDns = .init() 17 | 18 | static var session = URLSession.shared 19 | 20 | var services: [Service] = { 21 | [LocalDns.lookupService] 22 | }() 23 | } 24 | 25 | extension LocalDns: DataFeedService { 26 | var totalUsage: Int { 27 | services.reduce(0) { $0 + $1.usage } 28 | } 29 | 30 | public static var lookupService: IPLookupService = .init() 31 | 32 | class IPLookupService: Service { 33 | var name: String = "Simple IP Lookup" 34 | var description = "Simple IP Lookup" 35 | 36 | var cache = TimedCache(expiresIn: 60) 37 | 38 | func endpoint(_: [String: Any?]?) -> DataFeedEndpoint? { 39 | nil 40 | } 41 | 42 | func query(_ userData: [String: Any?]?, completion block: ((Error?, T?) -> Void)?) { 43 | guard let host = userData?["host"] as? String else { 44 | block?(URLError(.badURL), nil) 45 | return 46 | } 47 | 48 | DNSResolver.resolve(host: host) { error, addresses in 49 | guard error == nil else { 50 | block?(error, nil) 51 | return 52 | } 53 | guard let addresses = addresses as? T else { 54 | block?(URLError(.badURL), nil) 55 | return 56 | } 57 | 58 | block?(nil, addresses) 59 | } 60 | } 61 | 62 | func query(_ userData: [String: Any?]?) async throws -> T { 63 | guard let host = userData?["host"] as? String else { 64 | throw URLError(.badURL) 65 | } 66 | 67 | return try await withCheckedThrowingContinuation { continuation in 68 | DNSResolver.resolve(host: host) { error, addresses in 69 | if let error { 70 | continuation.resume(with: .failure(error)) 71 | return 72 | } 73 | guard let addresses = addresses as? T else { 74 | continuation.resume(with: .failure(URLError(.badURL))) 75 | return 76 | } 77 | 78 | continuation.resume(with: .success(addresses)) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ec3730/Models/Device/Sections/ProcessInfoModel.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import DeviceKit 3 | import MachO 4 | import SwiftUI 5 | 6 | extension ProcessInfo.ThermalState: CustomStringConvertible { 7 | public var description: String { 8 | switch self { 9 | case .critical: 10 | return "Critical" 11 | case .nominal: 12 | return "Nominal" 13 | case .fair: 14 | return "Fair" 15 | case .serious: 16 | return "Serious" 17 | @unknown default: 18 | return "Unknown" 19 | } 20 | } 21 | } 22 | 23 | class ProcessInfoModel: DeviceInfoSectionModel { 24 | override init() { 25 | super.init() 26 | title = "Process Information" 27 | } 28 | 29 | @MainActor override func reload() async { 30 | enabled = true 31 | rows.removeAll() 32 | 33 | let info = ProcessInfo.processInfo 34 | 35 | rows.append(.row(title: "Proces Name", content: info.processName)) 36 | rows.append(.row(title: "Active Processors", content: "\(info.activeProcessorCount)")) 37 | rows.append(.row(title: "Hostname", content: "\(info.hostName)")) 38 | rows.append(.row(title: "Process Arguments", content: "\(info.arguments.joined(separator: " "))")) 39 | rows.append(.row(title: "Low Power Mode", content: info.isLowPowerModeEnabled ? "Enabled" : "Disabled")) 40 | rows.append(.toggleableRow(title: "Physical Memory", contents: [ 41 | "\(info.physicalMemory) bytes", 42 | "\(String(format: "%0.1f", Double(info.physicalMemory) / 1024.0)) kiB", 43 | "\(String(format: "%0.1f", Double(info.physicalMemory) / 1024.0 / 1024.0)) MiB", 44 | "\(String(format: "%0.1f", Double(info.physicalMemory) / 1024.0 / 1024.0 / 1024.0)) GiB", 45 | ])) 46 | rows.append(.row(title: "Globally Unique String", content: "\(info.globallyUniqueString)")) 47 | rows.append(.row(title: "OS Version", content: info.operatingSystemVersionString)) 48 | rows.append(.row(title: "System Uptime", content: "\(info.systemUptime)")) 49 | rows.append(.row(title: "Is Mac Catalyst App", content: info.isMacCatalystApp ? "Yes" : "No")) 50 | rows.append(.row(title: "Is iOS App on Mac", content: info.isiOSAppOnMac ? "Yes" : "No")) 51 | rows.append(.row(title: "Prcoess Identifier (PID)", content: "\(info.processIdentifier)")) 52 | rows.append(.row(title: "Thermal State", content: info.thermalState.description)) 53 | 54 | func getArchitecture() -> NSString { 55 | let info = NXGetLocalArchInfo() 56 | return NSString(utf8String: (info?.pointee.description)!)! 57 | } 58 | rows.append(.row(title: "Architecture", content: getArchitecture() as String)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ec3730/IAPFooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IAPFootView.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 10/15/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class IAPFooterView: UITableViewHeaderFooterView { 13 | let label = UITextView() 14 | 15 | /// https://developer.apple.com/design/human-interface-guidelines/subscriptions/overview/ 16 | static func legaleeze(color: UIColor = .systemGray2) -> NSMutableAttributedString { 17 | // swiftlint:disable line_length 18 | let text = """ 19 | Payment will be charged to your Apple ID account at the confirmation of purchase. The subscription automatically renews unless it is canceled at least 24 hours before the end of the current period. Your account will be charged for renewal within 24 hours prior to the end of the current period. You can manage and cancel your subscriptions by going to your App Store account settings after purchase. 20 | """ 21 | 22 | let string = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.foregroundColor: color]) 23 | 24 | let privacy = NSAttributedString(string: " Privacy Policy", attributes: [NSAttributedString.Key.link: "https://zac.gorak.us/ios/privacy"]) 25 | string.append(privacy) 26 | string.append(NSAttributedString(string: " & ", attributes: [NSAttributedString.Key.foregroundColor: color])) 27 | 28 | let terms = NSAttributedString(string: "Terms of Use", attributes: [NSAttributedString.Key.link: "https://zac.gorak.us/ios/terms"]) 29 | 30 | string.append(terms) 31 | 32 | return string 33 | } 34 | 35 | init() { 36 | super.init(reuseIdentifier: "IAPFooterView") 37 | 38 | label.isEditable = false 39 | label.isScrollEnabled = false 40 | label.translatesAutoresizingMaskIntoConstraints = false 41 | label.linkTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.systemGray] 42 | label.backgroundColor = .clear 43 | 44 | let stack = UIStackView() 45 | stack.translatesAutoresizingMaskIntoConstraints = false 46 | stack.addArrangedSubview(label) 47 | 48 | label.attributedText = IAPFooterView.legaleeze() 49 | 50 | contentView.addSubview(stack) 51 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 52 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 53 | } 54 | 55 | @available(*, unavailable) 56 | required init?(coder _: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /BuildTools/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CollectionConcurrencyKit", 6 | "repositoryURL": "https://github.com/JohnSundell/CollectionConcurrencyKit.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "b4f23e24b5a1bff301efc5e70871083ca029ff95", 10 | "version": "0.2.0" 11 | } 12 | }, 13 | { 14 | "package": "SourceKitten", 15 | "repositoryURL": "https://github.com/jpsim/SourceKitten.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "b5f9bb749057dd396e93f97956bef64672bc2a04", 19 | "version": "0.33.0" 20 | } 21 | }, 22 | { 23 | "package": "swift-argument-parser", 24 | "repositoryURL": "https://github.com/apple/swift-argument-parser.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "9f39744e025c7d377987f30b03770805dcb0bcd1", 28 | "version": "1.1.4" 29 | } 30 | }, 31 | { 32 | "package": "SwiftSyntax", 33 | "repositoryURL": "https://github.com/apple/swift-syntax.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "0b6c22b97f8e9320bca62e82cdbee601cf37ad3f", 37 | "version": "0.50600.1" 38 | } 39 | }, 40 | { 41 | "package": "SwiftFormat", 42 | "repositoryURL": "https://github.com/nicklockwood/SwiftFormat", 43 | "state": { 44 | "branch": null, 45 | "revision": "de82a49c5db5b679e06fa031f89552353926317b", 46 | "version": "0.50.2" 47 | } 48 | }, 49 | { 50 | "package": "SwiftLint", 51 | "repositoryURL": "https://github.com/realm/SwiftLint.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "57dc1c9532d660ff547dd8ba2176ad82c1175787", 55 | "version": "0.49.1" 56 | } 57 | }, 58 | { 59 | "package": "SwiftyTextTable", 60 | "repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", 64 | "version": "0.9.0" 65 | } 66 | }, 67 | { 68 | "package": "SWXMLHash", 69 | "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "4d0f62f561458cbe1f732171e625f03195151b60", 73 | "version": "7.0.1" 74 | } 75 | }, 76 | { 77 | "package": "Yams", 78 | "repositoryURL": "https://github.com/jpsim/Yams.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "01835dc202670b5bb90d07f3eae41867e9ed29f6", 82 | "version": "5.0.1" 83 | } 84 | } 85 | ] 86 | }, 87 | "version": 1 88 | } 89 | -------------------------------------------------------------------------------- /ec3730/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | NetUtils 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | NetUtils 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationQueriesSchemes 24 | 25 | twitter 26 | 27 | LSRequiresIPhoneOS 28 | 29 | NSAppTransportSecurity 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | 34 | NSLocationWhenInUseUsageDescription 35 | Location is needed to fetch network information. 36 | UIApplicationSceneManifest 37 | 38 | UIApplicationSupportsMultipleScenes 39 | 40 | 41 | UIBackgroundModes 42 | 43 | remote-notification 44 | 45 | UILaunchStoryboardName 46 | LaunchScreen 47 | UIRequiredDeviceCapabilities 48 | 49 | armv7 50 | 51 | UIStatusBarTintParameters 52 | 53 | UINavigationBar 54 | 55 | Style 56 | UIBarStyleDefault 57 | Translucent 58 | 59 | 60 | 61 | UISupportedInterfaceOrientations 62 | 63 | UIInterfaceOrientationPortrait 64 | UIInterfaceOrientationLandscapeLeft 65 | UIInterfaceOrientationLandscapeRight 66 | UIInterfaceOrientationPortraitUpsideDown 67 | 68 | UISupportedInterfaceOrientations~ipad 69 | 70 | UIInterfaceOrientationPortrait 71 | UIInterfaceOrientationPortraitUpsideDown 72 | UIInterfaceOrientationLandscapeLeft 73 | UIInterfaceOrientationLandscapeRight 74 | 75 | UTExportedTypeDeclarations 76 | 77 | 78 | UTTypeDescription 79 | HostView Header View 80 | UTTypeIconFiles 81 | 82 | UTTypeIdentifier 83 | com.twodayslate.netutils.hostview.header 84 | UTTypeTagSpecification 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /ec3730/Models/Sections/UrlParsedModel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @MainActor 4 | class UrlParsedModel: HostSectionModel { 5 | required convenience init() { 6 | self.init(URLParsedFeed.current, service: URLParsedFeed.lookupService) 7 | } 8 | 9 | override func configure(with data: Data?) throws -> Data? { 10 | reset() 11 | 12 | guard let data = data else { 13 | return nil 14 | } 15 | 16 | let url = try JSONDecoder().decode(URL.self, from: data) 17 | 18 | return try configure(url: url) 19 | } 20 | 21 | func configure(url: URL) throws -> Data { 22 | reset() 23 | 24 | let copyData = try JSONEncoder().encode(url) 25 | latestData = copyData 26 | dataToCopy = String(data: copyData, encoding: .utf8) 27 | 28 | if let host = url.host { 29 | content.append(.row(title: "Host", content: host)) 30 | } 31 | if let port = url.port { 32 | content.append(.row(title: "Port", content: "\(port)")) 33 | } 34 | if let scheme = url.scheme { 35 | content.append(.row(title: "Scheme", content: scheme)) 36 | } 37 | if let fragment = url.fragment { 38 | content.append(.row(title: "Fragment", content: fragment)) 39 | } 40 | if let user = url.user { 41 | content.append(.row(title: "User", content: user)) 42 | } 43 | if let password = url.password { 44 | content.append(.row(title: "Password", content: password)) 45 | } 46 | content.append(.row(title: "Path", content: url.path)) 47 | if !url.pathComponents.isEmpty { 48 | content.append(.multiple(title: "Path Components", contents: url.pathComponents.map { .content($0, style: .gray) })) 49 | } 50 | content.append(.row(title: "Path Extension", content: url.pathExtension)) 51 | content.append(.row(title: "Last Path Component", content: url.lastPathComponent)) 52 | if let query = url.query { 53 | content.append(.row(title: "Query", content: query)) 54 | } 55 | if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) { 56 | if let queryItems = components.queryItems { 57 | var items = [CopyCellType]() 58 | for item in queryItems { 59 | items.append(.row(title: item.name, content: item.value ?? "")) 60 | } 61 | content.append(.multiple(title: "Query Items", contents: items)) 62 | } 63 | } 64 | 65 | return copyData 66 | } 67 | 68 | @discardableResult 69 | override func query(url: URL? = nil) async throws -> Data { 70 | reset() 71 | 72 | guard let url else { 73 | throw URLError(URLError.badURL) 74 | } 75 | latestQueriedUrl = url 76 | latestQueryDate = .now 77 | 78 | return try configure(url: url) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ec3730/Models/ReachabilityModel.swift: -------------------------------------------------------------------------------- 1 | import CoreLocation 2 | import Foundation 3 | import NetUtils 4 | import Reachability 5 | import SwiftUI 6 | import SystemConfiguration.CaptiveNetwork 7 | import UIKit 8 | 9 | class ReachabilityModel: NSObject, ObservableObject { 10 | var reachability = Reachability.shared 11 | 12 | @Published var connection: Reachability.Connection = .unavailable 13 | @Published var authorization: CLAuthorizationStatus = .notDetermined 14 | 15 | @Published var ssid: String? 16 | @Published var wifiInterface: String? 17 | @Published var wifiInterfaceInfo: [String: Any?]? 18 | 19 | @Published var interfaces = [Interface]() 20 | 21 | let locationManager = CLLocationManager() 22 | 23 | var connectionAvailable: Bool { 24 | connection == .wifi || connection == .cellular 25 | } 26 | 27 | override init() { 28 | super.init() 29 | NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: Reachability.shared) 30 | do { 31 | try Reachability.shared.startNotifier() 32 | } catch { 33 | print("could not start reachability notifier") 34 | } 35 | } 36 | 37 | @objc private func reachabilityChanged(note _: Notification) { 38 | DispatchQueue.main.async { 39 | self.update() 40 | } 41 | } 42 | 43 | @objc @MainActor private func update() { 44 | objectWillChange.send() 45 | interfaces = Interface.allInterfaces() 46 | connection = reachability.connection 47 | ssid = nil 48 | wifiInterface = nil 49 | wifiInterfaceInfo = nil 50 | 51 | switch connection { 52 | case .wifi: 53 | let status = locationManager.authorizationStatus 54 | if status == .authorizedWhenInUse || status == .authorizedAlways { 55 | if let (optionalInterface, optionalSsid, optionalInfo) = WiFi.ssidInfo() { 56 | ssid = optionalSsid 57 | wifiInterface = optionalInterface 58 | wifiInterfaceInfo = optionalInfo 59 | } 60 | } else { 61 | // XXX: add information prompt before the actual request 62 | locationManager.requestWhenInUseAuthorization() 63 | // XXX: This goes away after a second or two?... why? 64 | } 65 | default: 66 | break 67 | } 68 | } 69 | 70 | @MainActor func reload() { 71 | update() 72 | } 73 | } 74 | 75 | extension ReachabilityModel: CLLocationManagerDelegate { 76 | func locationManager(_: CLLocationManager, didChangeAuthorization auth: CLAuthorizationStatus) { 77 | switch auth { 78 | case .authorized, .authorizedAlways, .authorizedWhenInUse: 79 | Task { 80 | await update() 81 | } 82 | default: 83 | break 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ec3730/Views/Interface/InterfaceListView.swift: -------------------------------------------------------------------------------- 1 | import NetUtils 2 | import SwiftUI 3 | 4 | struct InterfaceListView: View { 5 | @StateObject var model = ReachabilityModel() 6 | 7 | @State var upEnabled = true 8 | @State var downEnabled = true 9 | 10 | var body: some View { 11 | VStack(spacing: 0) { 12 | ScrollView { 13 | VStack(spacing: 0) { 14 | let enabled = model.interfaces.filter(\.isUp) 15 | FSDisclosureGroup(isExpanded: $upEnabled, content: { 16 | VStack(spacing: 0) { 17 | ForEach(enabled) { interface in 18 | row(for: interface) 19 | .listRowInsets(.none) 20 | } 21 | } 22 | .cornerRadius(6) 23 | }, label: { 24 | HStack(alignment: .center) { 25 | Text("Enabled (Up)").font(.headline).padding() 26 | Spacer() 27 | } 28 | }) 29 | 30 | let disabled = model.interfaces.filter { !$0.isUp } 31 | FSDisclosureGroup(isExpanded: $downEnabled, content: { 32 | VStack(spacing: 0) { 33 | ForEach(disabled) { interface in 34 | row(for: interface) 35 | .listRowInsets(.none) 36 | } 37 | } 38 | .cornerRadius(6) 39 | }, label: { 40 | HStack(alignment: .center) { 41 | Text("Disabled (Down)").font(.headline).padding() 42 | Spacer() 43 | } 44 | }) 45 | } 46 | } 47 | .padding(.top, 0.15) 48 | .layoutPriority(1.0) 49 | InterfaceConnectionBarView(model: model) 50 | } 51 | .background(Color(UIColor.systemGroupedBackground)) 52 | .onAppear { 53 | model.reload() 54 | } 55 | .listStyle(.plain) 56 | .navigationBarTitleDisplayMode(.inline) 57 | .navigationTitle("Interfaces") 58 | .toolbar { 59 | ToolbarItem(placement: .navigationBarTrailing) { 60 | Button(action: { 61 | model.reload() 62 | }, label: { 63 | Label("Reload", systemImage: "arrow.clockwise") 64 | }) 65 | } 66 | } 67 | } 68 | 69 | func row(for interface: Interface) -> some View { 70 | NavigationLink(destination: { 71 | InterfaceView(model: model, interface: interface) 72 | }, label: { 73 | CopyCellType.row(title: interface.name, content: interface.address ?? "-", style: .chevron) 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # API / key files 2 | 3 | Api.swift 4 | */Api.swift 5 | Keys.swift 6 | */Keys.swift 7 | ApiKeys.swift 8 | */ApiKeys.swift 9 | APIKeys.swift 10 | */APIKeys.swift 11 | 12 | /screenshots 13 | 14 | .DS_Store 15 | # Xcode 16 | # 17 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 18 | 19 | ## User settings 20 | xcuserdata/ 21 | 22 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 23 | *.xcscmblueprint 24 | *.xccheckout 25 | 26 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 27 | build/ 28 | DerivedData/ 29 | *.moved-aside 30 | *.pbxuser 31 | !default.pbxuser 32 | *.mode1v3 33 | !default.mode1v3 34 | *.mode2v3 35 | !default.mode2v3 36 | *.perspectivev3 37 | !default.perspectivev3 38 | 39 | # Xcode 40 | # 41 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 42 | 43 | ## Build generated 44 | build/ 45 | DerivedData/ 46 | 47 | ## Various settings 48 | *.pbxuser 49 | !default.pbxuser 50 | *.mode1v3 51 | !default.mode1v3 52 | *.mode2v3 53 | !default.mode2v3 54 | *.perspectivev3 55 | !default.perspectivev3 56 | xcuserdata/ 57 | 58 | ## Other 59 | *.moved-aside 60 | *.xccheckout 61 | *.xcscmblueprint 62 | 63 | ## Obj-C/Swift specific 64 | *.hmap 65 | *.ipa 66 | *.dSYM.zip 67 | *.dSYM 68 | 69 | ## Playgrounds 70 | timeline.xctimeline 71 | playground.xcworkspace 72 | 73 | # Swift Package Manager 74 | # 75 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 76 | # Packages/ 77 | # Package.pins 78 | # Package.resolved 79 | .build/ 80 | BuildTools/.build 81 | BuildTools/.swiftpm 82 | 83 | # CocoaPods 84 | # 85 | # We recommend against adding the Pods directory to your .gitignore. However 86 | # you should judge for yourself, the pros and cons are mentioned at: 87 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 88 | # 89 | # Pods/ 90 | # 91 | # Add this line if you want to avoid checking in source code from the Xcode workspace 92 | # *.xcworkspace 93 | 94 | # Carthage 95 | # 96 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 97 | # Carthage/Checkouts 98 | 99 | Carthage/Build 100 | 101 | # fastlane 102 | # 103 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 104 | # screenshots whenever they are needed. 105 | # For more information about the recommended setup visit: 106 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 107 | 108 | fastlane/report.xml 109 | fastlane/Preview.html 110 | fastlane/screenshots/**/*.png 111 | fastlane/test_output 112 | 113 | # Code Injection 114 | # 115 | # After new code Injection tools there's a generated folder /iOSInjectionProject 116 | # https://github.com/johnno1962/injectionforxcode 117 | 118 | iOSInjectionProject/ 119 | -------------------------------------------------------------------------------- /ec3730/CellManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellManager.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/20/19. 6 | // Copyright © 2019 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyStoreKit 11 | import UIKit 12 | 13 | open class CellManager { 14 | public var cells = [UITableViewCell]() 15 | var iapDelegate: DataFeedInAppPurchaseUpdateDelegate? 16 | 17 | var isCollapsed: Bool = false 18 | 19 | internal var privateIsLoading: Bool = false 20 | public var isLoading: Bool { 21 | privateIsLoading 22 | } 23 | 24 | var dataFeed: DataFeed 25 | var service: Service 26 | 27 | init(_ feed: DataFeed, service: Service) { 28 | dataFeed = feed 29 | self.service = service 30 | cells.append(LoadingCell()) 31 | 32 | if let prod = feed as? DataFeedPurchaseProtocol { 33 | prod.verify { error in 34 | self.didUpdateInAppPurchase(self.dataFeed, error: error, purchaseResult: nil, restoreResults: nil, verifySubscriptionResult: nil, verifyPurchaseResult: nil, retrieveResults: nil) 35 | } 36 | } 37 | } 38 | 39 | open func askForMoney() { 40 | fatalError("Must override") 41 | } 42 | 43 | open func startLoading() { 44 | privateIsLoading = true 45 | 46 | if let paid = dataFeed as? DataFeedPurchaseProtocol { 47 | if paid.owned { 48 | let cell = LoadingCell() 49 | cell.spinner.startAnimating() 50 | cell.separatorInset.right = .greatestFiniteMagnitude 51 | cells = [cell] 52 | } else { 53 | privateIsLoading = false 54 | askForMoney() 55 | } 56 | } 57 | } 58 | 59 | open func stopLoading() { 60 | privateIsLoading = false 61 | 62 | if let paid = dataFeed as? DataFeedPurchaseProtocol { 63 | if paid.owned { 64 | cells.removeAll() 65 | } else { 66 | askForMoney() 67 | } 68 | } 69 | } 70 | 71 | open func reload() { 72 | fatalError("Must override") 73 | } 74 | } 75 | 76 | extension CellManager: DataFeedInAppPurchaseUpdateDelegate { 77 | func didUpdateInAppPurchase(_ feed: DataFeed, error: Error?, purchaseResult: PurchaseResult?, restoreResults: RestoreResults?, verifySubscriptionResult: VerifySubscriptionResult?, verifyPurchaseResult: VerifyPurchaseResult?, retrieveResults: RetrieveResults?) { 78 | if let paid = dataFeed as? DataFeedPurchaseProtocol { 79 | if paid.owned { 80 | cells.removeAll() 81 | } else { 82 | askForMoney() 83 | } 84 | } 85 | 86 | // swiftlint:disable:next line_length 87 | iapDelegate?.didUpdateInAppPurchase(feed, error: error, purchaseResult: purchaseResult, restoreResults: restoreResults, verifySubscriptionResult: verifySubscriptionResult, verifyPurchaseResult: verifyPurchaseResult, retrieveResults: retrieveResults) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ec3730/Data Feeds/WhoisXML/WhoisXmlDnsCells.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftyStoreKit 3 | import UIKit 4 | 5 | class WhoisXmlDnsCellManager: CellManager { 6 | override func askForMoney() { 7 | if !WhoisXml.current.owned { 8 | let locked = WhoisLockedTableViewCell(WhoisXml.current, heading: "Unlock DNS Lookup", subheading: "Our hosted DNS Lookup provides the records associated with a domain") 9 | locked.iapDelegate = self 10 | cells = [locked] 11 | } 12 | } 13 | 14 | public var currentRecords: [DNSRecords]? 15 | func configure(_ records: [DNSRecords]?) { 16 | stopLoading() 17 | guard let records = records else { 18 | return 19 | } 20 | 21 | currentRecords = records 22 | 23 | cells = [] 24 | 25 | for record in records { 26 | let cell = ContactCell(reuseIdentifier: record.rawText, title: record.dnsType) 27 | cell.addRow(ContactCellRow(title: "name", detail: record.name)) 28 | cell.addRow(ContactCellRow(title: "ttl", detail: "\(record.ttl)")) 29 | cell.addRow(ContactCellRow(title: "RRset Type", detail: "\(record.rRsetType)")) 30 | if let admin = record.admin { 31 | cell.addRow(ContactCellRow(title: "Admin", detail: admin)) 32 | } 33 | if let host = record.host { 34 | cell.addRow(ContactCellRow(title: "Host", detail: host)) 35 | } 36 | if let address = record.address { 37 | cell.addRow(ContactCellRow(title: "Address", detail: address)) 38 | } 39 | if let strings = record.strings { 40 | let row = ContactCellRow(title: "Strings", detail: strings.joined(separator: "\n")) 41 | row.detailLabel.numberOfLines = 0 42 | row.detailLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize) 43 | cell.addRow(row) 44 | } 45 | if let expire = record.expire { 46 | cell.addRow(ContactCellRow(title: "Expire", detail: "\(expire)")) 47 | } 48 | if let value = record.minimum { 49 | cell.addRow(ContactCellRow(title: "Minimum", detail: "\(value)")) 50 | } 51 | if let value = record.refresh { 52 | cell.addRow(ContactCellRow(title: "Refresh", detail: "\(value)")) 53 | } 54 | if let value = record.retry { 55 | cell.addRow(ContactCellRow(title: "Retry", detail: "\(value)")) 56 | } 57 | if let value = record.serial { 58 | cell.addRow(ContactCellRow(title: "Serial", detail: "\(value)")) 59 | } 60 | 61 | cells.append(cell) 62 | } 63 | } 64 | 65 | override func reload() { 66 | if let prod = dataFeed as? DataFeedPurchaseProtocol { 67 | if prod.owned { 68 | configure(currentRecords) 69 | } else { 70 | askForMoney() 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneOneDarkTheme/OneDarkTheme.swift: -------------------------------------------------------------------------------- 1 | import Runestone 2 | import RunestoneThemeCommon 3 | import UIKit 4 | 5 | public final class OneDarkTheme: EditorTheme { 6 | public let backgroundColor = UIColor(namedInModule: "OneDarkBackground") 7 | public let userInterfaceStyle: UIUserInterfaceStyle = .dark 8 | 9 | public let font: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 10 | public let textColor = UIColor(namedInModule: "OneDarkForeground") 11 | 12 | public let gutterBackgroundColor = UIColor(namedInModule: "OneDarkCurrentLine") 13 | public let gutterHairlineColor: UIColor = .opaqueSeparator 14 | 15 | public let lineNumberColor = UIColor(namedInModule: "OneDarkForeground").withAlphaComponent(0.5) 16 | public let lineNumberFont: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 17 | 18 | public let selectedLineBackgroundColor = UIColor(namedInModule: "OneDarkCurrentLine") 19 | public let selectedLinesLineNumberColor = UIColor(namedInModule: "OneDarkForeground") 20 | public let selectedLinesGutterBackgroundColor: UIColor = .clear 21 | 22 | public let invisibleCharactersColor = UIColor(namedInModule: "OneDarkForeground").withAlphaComponent(0.7) 23 | 24 | public let pageGuideHairlineColor = UIColor(namedInModule: "OneDarkForeground") 25 | public let pageGuideBackgroundColor = UIColor(namedInModule: "OneDarkCurrentLine") 26 | 27 | public let markedTextBackgroundColor = UIColor(namedInModule: "OneDarkForeground").withAlphaComponent(0.1) 28 | public let markedTextBackgroundCornerRadius: CGFloat = 4 29 | 30 | public init() {} 31 | 32 | public func textColor(for rawHighlightName: String) -> UIColor? { 33 | guard let highlightName = HighlightName(rawHighlightName) else { 34 | return nil 35 | } 36 | switch highlightName { 37 | case .comment: 38 | return UIColor(namedInModule: "OneDarkComment") 39 | case .operator, .punctuation: 40 | return UIColor(namedInModule: "OneDarkForeground").withAlphaComponent(0.75) 41 | case .property: 42 | return UIColor(namedInModule: "OneDarkAqua") 43 | case .function: 44 | return UIColor(namedInModule: "OneDarkBlue") 45 | case .string: 46 | return UIColor(namedInModule: "OneDarkGreen") 47 | case .number: 48 | return UIColor(namedInModule: "OneDarkYellow") 49 | case .keyword: 50 | return UIColor(namedInModule: "OneDarkPurple") 51 | case .variableBuiltin: 52 | return UIColor(namedInModule: "OneDarkRed") 53 | } 54 | } 55 | 56 | public func fontTraits(for rawHighlightName: String) -> FontTraits { 57 | if let highlightName = HighlightName(rawHighlightName), highlightName == .keyword { 58 | return .bold 59 | } else { 60 | return [] 61 | } 62 | } 63 | } 64 | 65 | private extension UIColor { 66 | convenience init(namedInModule name: String) { 67 | self.init(named: name, in: .module, compatibleWith: nil)! 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowTheme/TomorrowTheme.swift: -------------------------------------------------------------------------------- 1 | import Runestone 2 | import RunestoneThemeCommon 3 | import UIKit 4 | 5 | public final class TomorrowTheme: EditorTheme { 6 | public let backgroundColor = UIColor(namedInModule: "TomorrowBackground") 7 | public let userInterfaceStyle: UIUserInterfaceStyle = .light 8 | 9 | public let font: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 10 | public let textColor = UIColor(namedInModule: "TomorrowForeground") 11 | 12 | public let gutterBackgroundColor = UIColor(namedInModule: "TomorrowCurrentLine") 13 | public let gutterHairlineColor = UIColor(namedInModule: "TomorrowComment") 14 | 15 | public let lineNumberColor = UIColor(namedInModule: "TomorrowForeground").withAlphaComponent(0.5) 16 | public let lineNumberFont: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 17 | 18 | public let selectedLineBackgroundColor = UIColor(namedInModule: "TomorrowCurrentLine") 19 | public let selectedLinesLineNumberColor = UIColor(namedInModule: "TomorrowForeground") 20 | public let selectedLinesGutterBackgroundColor: UIColor = .clear 21 | 22 | public let invisibleCharactersColor = UIColor(namedInModule: "TomorrowForeground").withAlphaComponent(0.7) 23 | 24 | public let pageGuideHairlineColor = UIColor(namedInModule: "TomorrowForeground") 25 | public let pageGuideBackgroundColor = UIColor(namedInModule: "TomorrowCurrentLine") 26 | 27 | public let markedTextBackgroundColor = UIColor(namedInModule: "TomorrowForeground").withAlphaComponent(0.1) 28 | public let markedTextBackgroundCornerRadius: CGFloat = 4 29 | 30 | public init() {} 31 | 32 | public func textColor(for rawHighlightName: String) -> UIColor? { 33 | guard let highlightName = HighlightName(rawHighlightName) else { 34 | return nil 35 | } 36 | switch highlightName { 37 | case .comment: 38 | return UIColor(namedInModule: "TomorrowComment") 39 | case .operator, .punctuation: 40 | return UIColor(namedInModule: "TomorrowForeground").withAlphaComponent(0.75) 41 | case .property: 42 | return UIColor(namedInModule: "TomorrowAqua") 43 | case .function: 44 | return UIColor(namedInModule: "TomorrowBlue") 45 | case .string: 46 | return UIColor(namedInModule: "TomorrowGreen") 47 | case .number: 48 | return UIColor(namedInModule: "TomorrowOrange") 49 | case .keyword: 50 | return UIColor(namedInModule: "TomorrowPurple") 51 | case .variableBuiltin: 52 | return UIColor(namedInModule: "TomorrowRed") 53 | } 54 | } 55 | 56 | public func fontTraits(for rawHighlightName: String) -> FontTraits { 57 | if let highlightName = HighlightName(rawHighlightName), highlightName == .keyword { 58 | return .bold 59 | } else { 60 | return [] 61 | } 62 | } 63 | } 64 | 65 | private extension UIColor { 66 | convenience init(namedInModule name: String) { 67 | self.init(named: name, in: .module, compatibleWith: nil)! 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ec3730/Views/Source/Snap/SnapState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol SnapState: Equatable { 4 | associatedtype Visible 5 | 6 | // State postfix was added until we upgrade to Swift 5.3 and get 7 | // Enum cases as protocol witnesses 8 | static var largeState: Self { get } 9 | static var invisibleState: Self { get } 10 | 11 | var visible: Visible? { get } 12 | } 13 | 14 | public enum ModalSnapState: SnapState { 15 | public enum Visible { 16 | case large 17 | } 18 | 19 | public static let largeState: ModalSnapState = .large 20 | public static let invisibleState: ModalSnapState = .invisible 21 | 22 | case large 23 | case invisible 24 | 25 | public var visible: Visible? { 26 | switch self { 27 | case .large: 28 | return .large 29 | case .invisible: 30 | return nil 31 | } 32 | } 33 | } 34 | 35 | public enum OvercastSnapState: SnapState { 36 | public enum Visible { 37 | case large 38 | case tiny 39 | } 40 | 41 | public static let largeState: OvercastSnapState = .large 42 | public static let invisibleState: OvercastSnapState = .invisible 43 | 44 | case large 45 | case tiny 46 | case invisible 47 | 48 | public var visible: Visible? { 49 | switch self { 50 | case .large: 51 | return .large 52 | case .tiny: 53 | return .tiny 54 | case .invisible: 55 | return nil 56 | } 57 | } 58 | } 59 | 60 | public enum AppleMapsSnapState: SnapState { 61 | public enum Visible { 62 | case large 63 | case medium 64 | case tiny 65 | } 66 | 67 | public static let largeState: AppleMapsSnapState = .large 68 | public static let invisibleState: AppleMapsSnapState = .invisible 69 | 70 | case large 71 | case medium 72 | case tiny 73 | case invisible 74 | 75 | public var visible: Visible? { 76 | switch self { 77 | case .large: 78 | return .large 79 | case .medium: 80 | return .medium 81 | case .tiny: 82 | return .tiny 83 | case .invisible: 84 | return nil 85 | } 86 | } 87 | } 88 | 89 | public enum NetUtilsSnapState: SnapState { 90 | public enum Visible { 91 | case full 92 | case large 93 | case medium 94 | case tiny 95 | } 96 | 97 | public static let largeState: Self = .large 98 | public static let invisibleState: Self = .invisible 99 | 100 | case full 101 | case large 102 | case medium 103 | case tiny 104 | case invisible 105 | 106 | public var visible: Visible? { 107 | switch self { 108 | case .full: 109 | return .full 110 | case .large: 111 | return .large 112 | case .medium: 113 | return .medium 114 | case .tiny: 115 | return .tiny 116 | case .invisible: 117 | return nil 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ec3730/Models/Device/Sections/CarrierInfoModel.swift: -------------------------------------------------------------------------------- 1 | import CoreTelephony 2 | import SwiftUI 3 | 4 | class CarrierInfoModel: DeviceInfoSectionModel { 5 | private var getProviderTask: Task? 6 | private var networkInfo: CTTelephonyNetworkInfo? 7 | private var providers: [String: CTCarrier]? 8 | 9 | override init() { 10 | super.init() 11 | title = "Cellular Providers" 12 | 13 | NotificationCenter.default.addObserver(self, selector: #selector(reload), name: NSNotification.Name.CTServiceRadioAccessTechnologyDidChange, object: nil) 14 | } 15 | 16 | @MainActor private func setEnabled(_ completion: (@MainActor() -> Void)? = nil) { 17 | getProviderTask?.cancel() 18 | getProviderTask = Task.detached(priority: .userInitiated) { 19 | self.networkInfo = CTTelephonyNetworkInfo() 20 | self.providers = self.networkInfo?.serviceSubscriberCellularProviders 21 | 22 | Task { @MainActor [weak self] in 23 | self?.objectWillChange.send() 24 | self?.enabled = (self?.providers?.count ?? 0) > 0 25 | completion?() 26 | } 27 | } 28 | } 29 | 30 | func asyncSetEnabled() async { 31 | await withCheckedContinuation { continuation in 32 | Task { 33 | await setEnabled { 34 | continuation.resume() 35 | } 36 | } 37 | } 38 | } 39 | 40 | @MainActor private func setRows() async { 41 | rows.removeAll() 42 | 43 | guard enabled, let networkInfo = networkInfo, let providers = providers else { 44 | return 45 | } 46 | 47 | if let value = networkInfo.dataServiceIdentifier { 48 | rows.append(.row(title: "Data Service Identifier", content: value)) 49 | } 50 | 51 | if let value = networkInfo.serviceCurrentRadioAccessTechnology { 52 | for item in value { 53 | rows.append(.row(title: "Data Service \(item.key) Radio Access Technology", content: item.value)) 54 | } 55 | } 56 | 57 | for (i, carrier) in providers.enumerated() { 58 | if let name = carrier.value.carrierName { 59 | rows.append(.row(title: "Provider \(i) Carrier Name", content: name)) 60 | } 61 | rows.append(.row(title: "Provider \(i) Service", content: carrier.key)) 62 | rows.append(.row(title: "Provider \(i) Allows VOIP", content: carrier.value.allowsVOIP ? "Yes" : "No")) 63 | if let value = carrier.value.isoCountryCode { 64 | rows.append(.row(title: "Provider \(i) ISO Country Code", content: value)) 65 | } 66 | if let value = carrier.value.mobileCountryCode { 67 | rows.append(.row(title: "Provider \(i) Mobile Country Code", content: value)) 68 | } 69 | if let value = carrier.value.mobileNetworkCode { 70 | rows.append(.row(title: "Provider \(i) Mobile Network Code", content: value)) 71 | } 72 | } 73 | } 74 | 75 | @objc @MainActor override func reload() async { 76 | await asyncSetEnabled() 77 | await setRows() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /External/Themes/Sources/RunestoneTomorrowNightTheme/TomorrowNightTheme.swift: -------------------------------------------------------------------------------- 1 | import Runestone 2 | import RunestoneThemeCommon 3 | import UIKit 4 | 5 | public final class TomorrowNightTheme: EditorTheme { 6 | public let backgroundColor = UIColor(namedInModule: "TomorrowNightBackground") 7 | public let userInterfaceStyle: UIUserInterfaceStyle = .dark 8 | 9 | public let font: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 10 | public let textColor = UIColor(namedInModule: "TomorrowNightForeground") 11 | 12 | public let gutterBackgroundColor = UIColor(namedInModule: "TomorrowNightCurrentLine") 13 | public let gutterHairlineColor = UIColor(namedInModule: "TomorrowNightComment") 14 | 15 | public let lineNumberColor = UIColor(namedInModule: "TomorrowNightForeground").withAlphaComponent(0.5) 16 | public let lineNumberFont: UIFont = .monospacedSystemFont(ofSize: 14, weight: .regular) 17 | 18 | public let selectedLineBackgroundColor = UIColor(namedInModule: "TomorrowNightCurrentLine") 19 | public let selectedLinesLineNumberColor = UIColor(namedInModule: "TomorrowNightForeground") 20 | public let selectedLinesGutterBackgroundColor: UIColor = .clear 21 | 22 | public let invisibleCharactersColor = UIColor(namedInModule: "TomorrowNightForeground").withAlphaComponent(0.7) 23 | 24 | public let pageGuideHairlineColor = UIColor(namedInModule: "TomorrowNightForeground") 25 | public let pageGuideBackgroundColor = UIColor(namedInModule: "TomorrowNightCurrentLine") 26 | 27 | public let markedTextBackgroundColor = UIColor(namedInModule: "TomorrowNightForeground").withAlphaComponent(0.1) 28 | public let markedTextBackgroundCornerRadius: CGFloat = 4 29 | 30 | public init() {} 31 | 32 | public func textColor(for rawHighlightName: String) -> UIColor? { 33 | guard let highlightName = HighlightName(rawHighlightName) else { 34 | return nil 35 | } 36 | switch highlightName { 37 | case .comment: 38 | return UIColor(namedInModule: "TomorrowNightComment") 39 | case .operator, .punctuation: 40 | return UIColor(namedInModule: "TomorrowNightForeground").withAlphaComponent(0.75) 41 | case .property: 42 | return UIColor(namedInModule: "TomorrowNightAqua") 43 | case .function: 44 | return UIColor(namedInModule: "TomorrowNightBlue") 45 | case .string: 46 | return UIColor(namedInModule: "TomorrowNightGreen") 47 | case .number: 48 | return UIColor(namedInModule: "TomorrowNightOrange") 49 | case .keyword: 50 | return UIColor(namedInModule: "TomorrowNightPurple") 51 | case .variableBuiltin: 52 | return UIColor(namedInModule: "TomorrowNightRed") 53 | } 54 | } 55 | 56 | public func fontTraits(for rawHighlightName: String) -> FontTraits { 57 | if let highlightName = HighlightName(rawHighlightName), highlightName == .keyword { 58 | return .bold 59 | } else { 60 | return [] 61 | } 62 | } 63 | } 64 | 65 | private extension UIColor { 66 | convenience init(namedInModule name: String) { 67 | self.init(named: name, in: .module, compatibleWith: nil)! 68 | } 69 | } 70 | --------------------------------------------------------------------------------