mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Verifies Sources don’t contain duplicate bundle IDs
AltStore assumes all apps have unique bundle IDs per source. Weird bugs can occur when this is not the case (such as merging multiple store listings into one), so we now verify upfront whether source contains duplicate bundle IDs before saving.
This commit is contained in:
@@ -415,10 +415,13 @@ extension AppManager
|
||||
switch result
|
||||
{
|
||||
case .success(let source): fetchedSources.insert(source)
|
||||
case .failure(let error):
|
||||
case .failure(let nsError as NSError):
|
||||
let source = managedObjectContext.object(with: source.objectID) as! Source
|
||||
source.error = (error as NSError).sanitizedForSerialization()
|
||||
let title = String(format: NSLocalizedString("Unable to Refresh “%@” Source", comment: ""), source.name)
|
||||
|
||||
let error = nsError.withLocalizedTitle(title)
|
||||
errors[source] = error
|
||||
source.error = error.sanitizedForSerialization()
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
|
||||
@@ -12,6 +12,41 @@ import CoreData
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
extension SourceError
|
||||
{
|
||||
enum Code: Int, ALTErrorCode
|
||||
{
|
||||
typealias Error = SourceError
|
||||
|
||||
case unsupported
|
||||
case duplicateBundleID
|
||||
}
|
||||
|
||||
static func unsupported(_ source: Source) -> SourceError { SourceError(code: .unsupported, source: source) }
|
||||
static func duplicateBundleID(_ bundleID: String, source: Source) -> SourceError { SourceError(code: .duplicateBundleID, source: source, duplicateBundleID: bundleID) }
|
||||
}
|
||||
|
||||
struct SourceError: ALTLocalizedError
|
||||
{
|
||||
var code: Code
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
@Managed var source: Source
|
||||
var duplicateBundleID: String?
|
||||
|
||||
var errorFailureReason: String {
|
||||
switch self.code
|
||||
{
|
||||
case .unsupported: return String(format: NSLocalizedString("The source “%@” is not supported by this version of AltStore.", comment: ""), self.$source.name)
|
||||
case .duplicateBundleID:
|
||||
let bundleIDFragment = self.duplicateBundleID.map { String(format: NSLocalizedString("the bundle identifier %@", comment: ""), $0) } ?? NSLocalizedString("the same bundle identifier", comment: "")
|
||||
let failureReason = String(format: NSLocalizedString("The source “%@” contains multiple apps with %@.", comment: ""), self.$source.name, bundleIDFragment)
|
||||
return failureReason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc(FetchSourceOperation)
|
||||
final class FetchSourceOperation: ResultOperation<Source>
|
||||
{
|
||||
@@ -78,6 +113,8 @@ final class FetchSourceOperation: ResultOperation<Source>
|
||||
let source = try decoder.decode(Source.self, from: data)
|
||||
let identifier = source.identifier
|
||||
|
||||
try self.verify(source)
|
||||
|
||||
try childContext.save()
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
@@ -105,3 +142,23 @@ final class FetchSourceOperation: ResultOperation<Source>
|
||||
dataTask.resume()
|
||||
}
|
||||
}
|
||||
|
||||
private extension FetchSourceOperation
|
||||
{
|
||||
func verify(_ source: Source) throws
|
||||
{
|
||||
#if !BETA
|
||||
if let trustedSourceIDs = UserDefaults.shared.trustedSourceIDs
|
||||
{
|
||||
guard trustedSourceIDs.contains(source.identifier) || source.identifier == Source.altStoreIdentifier else { throw SourceError(code: .unsupported, source: source) }
|
||||
}
|
||||
#endif
|
||||
|
||||
var bundleIDs = Set<String>()
|
||||
for app in source.apps
|
||||
{
|
||||
guard !bundleIDs.contains(app.bundleIdentifier) else { throw SourceError.duplicateBundleID(app.bundleIdentifier, source: source) }
|
||||
bundleIDs.insert(app.bundleIdentifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,14 @@ private extension SourcesViewController
|
||||
{
|
||||
case .success: break
|
||||
case .failure(OperationError.cancelled): break
|
||||
case .failure(let error): self.present(error)
|
||||
|
||||
case .failure(var error as SourceError):
|
||||
let title = String(format: NSLocalizedString("“%@” could not be added to AltStore.", comment: ""), error.$source.name)
|
||||
error.errorTitle = title
|
||||
self.present(error)
|
||||
|
||||
case .failure(let error as NSError):
|
||||
self.present(error.withLocalizedTitle(NSLocalizedString("Unable to Add Source", comment: "")))
|
||||
}
|
||||
|
||||
self.collectionView.reloadSections([Section.trusted.rawValue])
|
||||
@@ -263,10 +270,6 @@ private extension SourcesViewController
|
||||
let sourceName = source.name
|
||||
let managedObjectContext = source.managedObjectContext
|
||||
|
||||
#if !BETA
|
||||
guard let trustedSourceIDs = UserDefaults.shared.trustedSourceIDs, trustedSourceIDs.contains(source.identifier) else { throw SourceError(code: .unsupported, source: source) }
|
||||
#endif
|
||||
|
||||
// Hide warning when adding a featured trusted source.
|
||||
let message = isTrusted ? nil : NSLocalizedString("Make sure to only add sources that you trust.", comment: "")
|
||||
|
||||
@@ -311,9 +314,10 @@ private extension SourcesViewController
|
||||
}
|
||||
|
||||
let nsError = error as NSError
|
||||
let message = nsError.userInfo[NSDebugDescriptionErrorKey] as? String ?? nsError.localizedRecoverySuggestion
|
||||
let title = nsError.localizedTitle // OK if nil.
|
||||
let message = [nsError.localizedDescription, nsError.localizedDebugDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
|
||||
|
||||
let alertController = UIAlertController(title: error.localizedDescription, message: message, preferredStyle: .alert)
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user