2023-05-11 16:04:18 -05:00
//
// V e r i f i c a t i o n E r r o r . s w i f t
// A l t S t o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 5 / 1 1 / 2 3 .
// C o p y r i g h t © 2 0 2 3 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import AltStoreCore
import AltSign
extension VerificationError
{
enum Code : Int , ALTErrorCode , CaseIterable
{
typealias Error = VerificationError
// L e g a c y
2024-12-07 17:45:09 +05:30
// c a s e p r i v a t e E n t i t l e m e n t s = 0
2023-05-11 16:04:18 -05:00
case mismatchedBundleIdentifiers = 1
case iOSVersionNotSupported = 2
2023-05-11 17:02:20 -05:00
case mismatchedHash = 3
2023-05-11 17:47:03 -05:00
case mismatchedVersion = 4
2023-05-18 15:55:26 -05:00
case mismatchedBuildVersion = 5
2023-05-12 18:26:24 -05:00
case undeclaredPermissions = 6
2023-05-15 15:13:13 -05:00
case addedPermissions = 7
2023-05-11 16:04:18 -05:00
}
2024-12-07 17:45:09 +05:30
// s t a t i c f u n c p r i v a t e E n t i t l e m e n t s ( _ e n t i t l e m e n t s : [ S t r i n g : A n y ] , a p p : A L T A p p l i c a t i o n ) - > V e r i f i c a t i o n E r r o r {
// V e r i f i c a t i o n E r r o r ( c o d e : . p r i v a t e E n t i t l e m e n t s , a p p : a p p , e n t i t l e m e n t s : e n t i t l e m e n t s )
// }
2023-05-11 16:04:18 -05:00
static func mismatchedBundleIdentifiers ( sourceBundleID : String , app : ALTApplication ) -> VerificationError {
VerificationError ( code : . mismatchedBundleIdentifiers , app : app , sourceBundleID : sourceBundleID )
}
static func iOSVersionNotSupported ( app : AppProtocol , osVersion : OperatingSystemVersion = ProcessInfo . processInfo . operatingSystemVersion , requiredOSVersion : OperatingSystemVersion ? ) -> VerificationError {
VerificationError ( code : . iOSVersionNotSupported , app : app , deviceOSVersion : osVersion , requiredOSVersion : requiredOSVersion )
}
2023-05-11 17:02:20 -05:00
static func mismatchedHash ( _ hash : String , expectedHash : String , app : AppProtocol ) -> VerificationError {
VerificationError ( code : . mismatchedHash , app : app , hash : hash , expectedHash : expectedHash )
}
2023-05-11 17:47:03 -05:00
static func mismatchedVersion ( _ version : String , expectedVersion : String , app : AppProtocol ) -> VerificationError {
VerificationError ( code : . mismatchedVersion , app : app , version : version , expectedVersion : expectedVersion )
}
2023-05-12 18:26:24 -05:00
2023-05-18 15:55:26 -05:00
static func mismatchedBuildVersion ( _ version : String , expectedVersion : String , app : AppProtocol ) -> VerificationError {
VerificationError ( code : . mismatchedBuildVersion , app : app , version : version , expectedVersion : expectedVersion )
}
2023-05-12 18:26:24 -05:00
static func undeclaredPermissions ( _ permissions : [ any ALTAppPermission ] , app : AppProtocol ) -> VerificationError {
VerificationError ( code : . undeclaredPermissions , app : app , permissions : permissions )
}
2023-05-15 15:13:13 -05:00
2023-05-26 14:41:40 -05:00
static func addedPermissions ( _ permissions : [ any ALTAppPermission ] , appVersion : AppVersion ) -> VerificationError {
VerificationError ( code : . addedPermissions , app : appVersion , permissions : permissions )
2023-05-15 15:13:13 -05:00
}
2023-05-11 16:04:18 -05:00
}
struct VerificationError : ALTLocalizedError
{
let code : Code
var errorTitle : String ?
var errorFailure : String ?
@ Managed var app : AppProtocol ?
var sourceBundleID : String ?
var deviceOSVersion : OperatingSystemVersion ?
var requiredOSVersion : OperatingSystemVersion ?
2023-05-11 17:02:20 -05:00
@ UserInfoValue var hash : String ?
@ UserInfoValue var expectedHash : String ?
2023-05-11 17:47:03 -05:00
@ UserInfoValue var version : String ?
@ UserInfoValue var expectedVersion : String ?
2023-05-12 18:26:24 -05:00
@ UserInfoValue
var permissions : [ any ALTAppPermission ] ?
2023-05-11 16:04:18 -05:00
var errorDescription : String ? {
// TODO: M a k e t h i s a u t o m a t i c s o m e h o w w i t h A L T L o c a l i z e d E r r o r
guard self . errorFailure = = nil else { return nil }
switch self . code
{
case . iOSVersionNotSupported :
guard let deviceOSVersion else { break }
var failureReason = self . errorFailureReason
if self . app = = nil
{
// f a i l u r e R e a s o n d o e s n o t s t a r t w i t h a p p n a m e , s o m a k e f i r s t l e t t e r l o w e r c a s e .
let firstLetter = failureReason . prefix ( 1 ) . lowercased ( )
failureReason = firstLetter + failureReason . dropFirst ( )
}
let localizedDescription = String ( format : NSLocalizedString ( " This device is running iOS %@, but %@ " , comment : " " ) , deviceOSVersion . stringValue , failureReason )
return localizedDescription
default : break
}
return self . errorFailureReason
}
var errorFailureReason : String {
switch self . code
{
2024-12-07 17:45:09 +05:30
// c a s e . p r i v a t e E n t i t l e m e n t s :
// l e t a p p N a m e = s e l f . $ a p p . n a m e ? ? N S L o c a l i z e d S t r i n g ( " T h e a p p " , c o m m e n t : " " )
// r e t u r n S t r i n g ( f o r m a t t e d : " “ % @ ” r e q u i r e s p r i v a t e p e r m i s s i o n s . " , a p p N a m e )
2023-05-11 16:04:18 -05:00
case . mismatchedBundleIdentifiers :
if let appBundleID = self . $ app . bundleIdentifier , let bundleID = self . sourceBundleID
{
return String ( format : NSLocalizedString ( " The bundle ID “%@” does not match the one specified by the source (“%@”). " , comment : " " ) , appBundleID , bundleID )
}
else
{
return NSLocalizedString ( " The bundle ID does not match the one specified by the source. " , comment : " " )
}
case . iOSVersionNotSupported :
let appName = self . $ app . name ? ? NSLocalizedString ( " The app " , comment : " " )
let deviceOSVersion = self . deviceOSVersion ? ? ProcessInfo . processInfo . operatingSystemVersion
guard let requiredOSVersion else {
return String ( format : NSLocalizedString ( " %@ does not support iOS %@. " , comment : " " ) , appName , deviceOSVersion . stringValue )
}
if deviceOSVersion > requiredOSVersion
{
// D e v i c e O S v e r s i o n i s h i g h e r t h a n m a x i m u m s u p p o r t e d O S v e r s i o n .
let failureReason = String ( format : NSLocalizedString ( " %@ requires iOS %@ or earlier. " , comment : " " ) , appName , requiredOSVersion . stringValue )
return failureReason
}
else
{
// D e v i c e O S v e r s i o n i s l o w e r t h a n m i n i m u m s u p p o r t e d O S v e r s i o n .
let failureReason = String ( format : NSLocalizedString ( " %@ requires iOS %@ or later. " , comment : " " ) , appName , requiredOSVersion . stringValue )
return failureReason
}
2023-05-11 17:02:20 -05:00
case . mismatchedHash :
let appName = self . $ app . name ? ? NSLocalizedString ( " the downloaded app " , comment : " " )
return String ( format : NSLocalizedString ( " The SHA-256 hash of %@ does not match the hash specified by the source. " , comment : " " ) , appName )
2023-05-11 17:47:03 -05:00
case . mismatchedVersion :
let appName = self . $ app . name ? ? NSLocalizedString ( " the app " , comment : " " )
2025-01-29 02:38:27 +05:30
return String ( format : NSLocalizedString ( " The downloaded version of %@ does not match the version specified by the source. \n Expected version: %@ \n Found version: %@ " , comment : " " ) , appName , expectedVersion ? ? " nil " , version ? ? " nil " )
2023-05-12 18:26:24 -05:00
2023-05-18 15:55:26 -05:00
case . mismatchedBuildVersion :
let appName = self . $ app . name ? ? NSLocalizedString ( " the app " , comment : " " )
2025-01-29 02:38:27 +05:30
return String ( format : NSLocalizedString ( " The downloaded version of %@ does not match the build number specified by the source. \n Expected version: %@ \n Found version: %@ " , comment : " " ) , appName , expectedVersion ? ? " nil " , version ? ? " nil " )
2023-05-18 15:55:26 -05:00
2023-05-12 18:26:24 -05:00
case . undeclaredPermissions :
let appName = self . $ app . name ? ? NSLocalizedString ( " The app " , comment : " " )
return String ( format : NSLocalizedString ( " %@ requires additional permissions not specified by the source. " , comment : " " ) , appName )
2023-05-15 15:13:13 -05:00
case . addedPermissions :
2023-05-26 14:41:40 -05:00
let appName : String
let installedVersion : String ?
if let appVersion = self . app as ? AppVersion
{
let ( name , version , previousVersion ) = self . $ app . perform { _ in ( appVersion . name , appVersion . localizedVersion , appVersion . app ? . installedApp ? . localizedVersion ) }
appName = name + " \( version ) "
installedVersion = previousVersion . map { " ( \( name ) \( $0 ) ) " } // I n c l u d e a p p n a m e b e c a u s e i t l o o k s w e i r d t o i n c l u d e b u i l d # i n d o u b l e p a r e n t h e s e s w i t h o u t i t .
}
else
{
appName = self . $ app . name ? ? NSLocalizedString ( " The app " , comment : " " )
installedVersion = nil
}
let baseMessage = String ( format : NSLocalizedString ( " %@ requires more permissions than the version that is already installed " , comment : " " ) , appName )
let failureReason = [ baseMessage , installedVersion ] . compactMap { $0 } . joined ( separator : " " ) + " . "
return failureReason
2023-05-12 18:26:24 -05:00
}
}
var recoverySuggestion : String ? {
switch self . code
{
case . undeclaredPermissions :
2023-05-26 14:41:40 -05:00
guard let permissionsDescription else { return nil }
2023-05-12 18:26:24 -05:00
2024-12-14 18:23:33 -05:00
let baseMessage = NSLocalizedString ( " These permissions must be declared by the source in order for SideStore to install this app: " , comment : " " )
2023-05-26 14:41:40 -05:00
let recoverySuggestion = [ baseMessage , permissionsDescription ] . joined ( separator : " \n \n " )
return recoverySuggestion
2023-05-12 18:26:24 -05:00
2023-05-26 14:41:40 -05:00
case . addedPermissions :
let recoverySuggestion = self . permissionsDescription
2023-05-12 18:26:24 -05:00
return recoverySuggestion
default : return nil
2023-05-11 16:04:18 -05:00
}
}
}
2023-05-26 14:41:40 -05:00
private extension VerificationError
{
var permissionsDescription : String ? {
guard let permissions , ! permissions . isEmpty else { return nil }
let permissionsByType = Dictionary ( grouping : permissions ) { $0 . type }
2023-05-26 14:58:52 -05:00
let permissionSections = [ ALTAppPermissionType . entitlement , . privacy ] . compactMap { ( type ) -> String ? in
2023-05-26 14:41:40 -05:00
guard let permissions = permissionsByType [ type ] else { return nil }
// " P r i v a c y : "
var sectionText = " \( type . localizedName ? ? type . rawValue ) : \n "
// S o r t p e r m i s s i o n s + j o i n i n t o s i n g l e s t r i n g .
let sortedList = permissions . map { permission -> String in
if let localizedName = permission . localizedName
{
// " E n t i t l e m e n t N a m e ( c o m . a p p l e . e n t i t l e m e n t . n a m e ) "
return " \( localizedName ) ( \( permission . rawValue ) ) "
}
else
{
// " c o m . a p p l e . e n t i t l e m e n t . n a m e "
return permission . rawValue
}
}
. sorted { $0 . localizedStandardCompare ( $1 ) = = . orderedAscending } // C a s e - i n s e n s i t i v e s o r t i n g
. joined ( separator : " \n " )
sectionText += sortedList
return sectionText
}
let permissionsDescription = permissionSections . joined ( separator : " \n \n " )
return permissionsDescription
}
}