mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 23:03:27 +01:00
[AltServer] Supports Install/Remove provisioning profiles requests
Stuff I shoulda committed
This commit is contained in:
@@ -29,6 +29,11 @@ public extension Bundle
|
||||
return infoPlistURL
|
||||
}
|
||||
|
||||
var provisioningProfileURL: URL {
|
||||
let infoPlistURL = self.bundleURL.appendingPathComponent("embedded.mobileprovision")
|
||||
return infoPlistURL
|
||||
}
|
||||
|
||||
var certificateURL: URL {
|
||||
let infoPlistURL = self.bundleURL.appendingPathComponent("ALTCertificate.p12")
|
||||
return infoPlistURL
|
||||
|
||||
58
AltKit/CodableServerError.swift
Normal file
58
AltKit/CodableServerError.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// CodableServerError.swift
|
||||
// AltKit
|
||||
//
|
||||
// Created by Riley Testut on 3/5/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
|
||||
extension ALTServerError.Code: Codable {}
|
||||
|
||||
struct CodableServerError: Codable
|
||||
{
|
||||
var error: ALTServerError {
|
||||
return ALTServerError(self.errorCode, userInfo: self.userInfo ?? [:])
|
||||
}
|
||||
|
||||
private var errorCode: ALTServerError.Code
|
||||
private var userInfo: [String: String]?
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case errorCode
|
||||
case userInfo
|
||||
}
|
||||
|
||||
init(error: ALTServerError)
|
||||
{
|
||||
self.errorCode = error.code
|
||||
|
||||
let userInfo = error.userInfo.compactMapValues { $0 as? String }
|
||||
if !userInfo.isEmpty
|
||||
{
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws
|
||||
{
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let errorCode = try container.decode(Int.self, forKey: .errorCode)
|
||||
self.errorCode = ALTServerError.Code(rawValue: errorCode) ?? .unknown
|
||||
|
||||
let userInfo = try container.decodeIfPresent([String: String].self, forKey: .userInfo)
|
||||
self.userInfo = userInfo
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws
|
||||
{
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.error.code.rawValue, forKey: .errorCode)
|
||||
try container.encodeIfPresent(self.userInfo, forKey: .userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
extern NSErrorDomain const AltServerErrorDomain;
|
||||
extern NSErrorDomain const AltServerInstallationErrorDomain;
|
||||
|
||||
extern NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey;
|
||||
extern NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey;
|
||||
|
||||
typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError)
|
||||
{
|
||||
ALTServerErrorUnknown = 0,
|
||||
@@ -32,7 +35,11 @@ typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError)
|
||||
ALTServerErrorUnknownResponse = 12,
|
||||
|
||||
ALTServerErrorInvalidAnisetteData = 13,
|
||||
ALTServerErrorPluginNotFound = 14
|
||||
ALTServerErrorPluginNotFound = 14,
|
||||
|
||||
ALTServerErrorProfileInstallFailed = 15,
|
||||
ALTServerErrorProfileCopyFailed = 16,
|
||||
ALTServerErrorProfileRemoveFailed = 17,
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
NSErrorDomain const AltServerErrorDomain = @"com.rileytestut.AltServer";
|
||||
NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServer.Installation";
|
||||
|
||||
NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode";
|
||||
NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdentifier";
|
||||
|
||||
@implementation NSError (ALTServerError)
|
||||
|
||||
+ (void)load
|
||||
@@ -73,7 +76,41 @@ NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServ
|
||||
|
||||
case ALTServerErrorPluginNotFound:
|
||||
return NSLocalizedString(@"Could not connect to Mail plug-in. Please make sure Mail is running and that you've enabled the plug-in in Mail's preferences, then try again.", @"");
|
||||
|
||||
case ALTServerErrorProfileInstallFailed:
|
||||
return [self underlyingProfileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not install profile", "")];
|
||||
|
||||
case ALTServerErrorProfileCopyFailed:
|
||||
return [self underlyingProfileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not copy provisioning profiles", "")];
|
||||
|
||||
case ALTServerErrorProfileRemoveFailed:
|
||||
return [self underlyingProfileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not remove profile", "")];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)underlyingProfileErrorLocalizedDescriptionWithBaseDescription:(NSString *)baseDescription
|
||||
{
|
||||
NSMutableString *localizedDescription = [NSMutableString string];
|
||||
|
||||
NSString *bundleID = self.userInfo[ALTProvisioningProfileBundleIDErrorKey];
|
||||
if (bundleID)
|
||||
{
|
||||
[localizedDescription appendFormat:NSLocalizedString(@"%@ “%@”", @""), baseDescription, bundleID];
|
||||
}
|
||||
else
|
||||
{
|
||||
[localizedDescription appendString:baseDescription];
|
||||
}
|
||||
|
||||
NSString *underlyingErrorCode = self.userInfo[ALTUnderlyingErrorCodeErrorKey];
|
||||
if (underlyingErrorCode)
|
||||
{
|
||||
[localizedDescription appendFormat:@" (%@)", underlyingErrorCode];
|
||||
}
|
||||
|
||||
[localizedDescription appendString:@"."];
|
||||
|
||||
return localizedDescription;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,9 +11,6 @@ import AltSign
|
||||
|
||||
public let ALTServerServiceType = "_altserver._tcp"
|
||||
|
||||
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
|
||||
extension ALTServerError.Code: Codable {}
|
||||
|
||||
protocol ServerMessageProtocol: Codable
|
||||
{
|
||||
var version: Int { get }
|
||||
@@ -25,6 +22,8 @@ public enum ServerRequest: Decodable
|
||||
case anisetteData(AnisetteDataRequest)
|
||||
case prepareApp(PrepareAppRequest)
|
||||
case beginInstallation(BeginInstallationRequest)
|
||||
case installProvisioningProfiles(InstallProvisioningProfilesRequest)
|
||||
case removeProvisioningProfiles(RemoveProvisioningProfilesRequest)
|
||||
case unknown(identifier: String, version: Int)
|
||||
|
||||
var identifier: String {
|
||||
@@ -33,6 +32,8 @@ public enum ServerRequest: Decodable
|
||||
case .anisetteData(let request): return request.identifier
|
||||
case .prepareApp(let request): return request.identifier
|
||||
case .beginInstallation(let request): return request.identifier
|
||||
case .installProvisioningProfiles(let request): return request.identifier
|
||||
case .removeProvisioningProfiles(let request): return request.identifier
|
||||
case .unknown(let identifier, _): return identifier
|
||||
}
|
||||
}
|
||||
@@ -43,6 +44,8 @@ public enum ServerRequest: Decodable
|
||||
case .anisetteData(let request): return request.version
|
||||
case .prepareApp(let request): return request.version
|
||||
case .beginInstallation(let request): return request.version
|
||||
case .installProvisioningProfiles(let request): return request.version
|
||||
case .removeProvisioningProfiles(let request): return request.version
|
||||
case .unknown(_, let version): return version
|
||||
}
|
||||
}
|
||||
@@ -73,6 +76,14 @@ public enum ServerRequest: Decodable
|
||||
case "BeginInstallationRequest":
|
||||
let request = try BeginInstallationRequest(from: decoder)
|
||||
self = .beginInstallation(request)
|
||||
|
||||
case "InstallProvisioningProfilesRequest":
|
||||
let request = try InstallProvisioningProfilesRequest(from: decoder)
|
||||
self = .installProvisioningProfiles(request)
|
||||
|
||||
case "RemoveProvisioningProfilesRequest":
|
||||
let request = try RemoveProvisioningProfilesRequest(from: decoder)
|
||||
self = .removeProvisioningProfiles(request)
|
||||
|
||||
default:
|
||||
self = .unknown(identifier: identifier, version: version)
|
||||
@@ -84,6 +95,8 @@ public enum ServerResponse: Decodable
|
||||
{
|
||||
case anisetteData(AnisetteDataResponse)
|
||||
case installationProgress(InstallationProgressResponse)
|
||||
case installProvisioningProfiles(InstallProvisioningProfilesResponse)
|
||||
case removeProvisioningProfiles(RemoveProvisioningProfilesResponse)
|
||||
case error(ErrorResponse)
|
||||
case unknown(identifier: String, version: Int)
|
||||
|
||||
@@ -92,6 +105,8 @@ public enum ServerResponse: Decodable
|
||||
{
|
||||
case .anisetteData(let response): return response.identifier
|
||||
case .installationProgress(let response): return response.identifier
|
||||
case .installProvisioningProfiles(let response): return response.identifier
|
||||
case .removeProvisioningProfiles(let response): return response.identifier
|
||||
case .error(let response): return response.identifier
|
||||
case .unknown(let identifier, _): return identifier
|
||||
}
|
||||
@@ -102,6 +117,8 @@ public enum ServerResponse: Decodable
|
||||
{
|
||||
case .anisetteData(let response): return response.version
|
||||
case .installationProgress(let response): return response.version
|
||||
case .installProvisioningProfiles(let response): return response.version
|
||||
case .removeProvisioningProfiles(let response): return response.version
|
||||
case .error(let response): return response.version
|
||||
case .unknown(_, let version): return version
|
||||
}
|
||||
@@ -129,6 +146,14 @@ public enum ServerResponse: Decodable
|
||||
case "InstallationProgressResponse":
|
||||
let response = try InstallationProgressResponse(from: decoder)
|
||||
self = .installationProgress(response)
|
||||
|
||||
case "InstallProvisioningProfilesResponse":
|
||||
let response = try InstallProvisioningProfilesResponse(from: decoder)
|
||||
self = .installProvisioningProfiles(response)
|
||||
|
||||
case "RemoveProvisioningProfilesResponse":
|
||||
let response = try RemoveProvisioningProfilesResponse(from: decoder)
|
||||
self = .removeProvisioningProfiles(response)
|
||||
|
||||
case "ErrorResponse":
|
||||
let response = try ErrorResponse(from: decoder)
|
||||
@@ -140,6 +165,28 @@ public enum ServerResponse: Decodable
|
||||
}
|
||||
}
|
||||
|
||||
// _Don't_ provide generic SuccessResponse, as that would prevent us
|
||||
// from easily changing response format for a request in the future.
|
||||
public struct ErrorResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 2
|
||||
public var identifier = "ErrorResponse"
|
||||
|
||||
public var error: ALTServerError {
|
||||
return self.serverError?.error ?? ALTServerError(self.errorCode)
|
||||
}
|
||||
private var serverError: CodableServerError?
|
||||
|
||||
// Legacy (v1)
|
||||
private var errorCode: ALTServerError.Code
|
||||
|
||||
public init(error: ALTServerError)
|
||||
{
|
||||
self.serverError = CodableServerError(error: error)
|
||||
self.errorCode = error.code
|
||||
}
|
||||
}
|
||||
|
||||
public struct AnisetteDataRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
@@ -223,22 +270,6 @@ public struct BeginInstallationRequest: ServerMessageProtocol
|
||||
}
|
||||
}
|
||||
|
||||
public struct ErrorResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "ErrorResponse"
|
||||
|
||||
public var error: ALTServerError {
|
||||
return ALTServerError(self.errorCode)
|
||||
}
|
||||
private var errorCode: ALTServerError.Code
|
||||
|
||||
public init(error: ALTServerError)
|
||||
{
|
||||
self.errorCode = error.code
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstallationProgressResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
@@ -251,3 +282,96 @@ public struct InstallationProgressResponse: ServerMessageProtocol
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstallProvisioningProfilesRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "InstallProvisioningProfilesRequest"
|
||||
|
||||
public var udid: String
|
||||
public var provisioningProfiles: Set<ALTProvisioningProfile>
|
||||
|
||||
// If activeProfiles is non-nil, then AltServer should remove all profiles except active ones.
|
||||
public var activeProfiles: Set<String>?
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case identifier
|
||||
case version
|
||||
case udid
|
||||
case provisioningProfiles
|
||||
case activeProfiles
|
||||
}
|
||||
|
||||
public init(udid: String, provisioningProfiles: Set<ALTProvisioningProfile>, activeProfiles: Set<String>?)
|
||||
{
|
||||
self.udid = udid
|
||||
self.provisioningProfiles = provisioningProfiles
|
||||
self.activeProfiles = activeProfiles
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws
|
||||
{
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.version = try container.decode(Int.self, forKey: .version)
|
||||
self.identifier = try container.decode(String.self, forKey: .identifier)
|
||||
self.udid = try container.decode(String.self, forKey: .udid)
|
||||
|
||||
let rawProvisioningProfiles = try container.decode([Data].self, forKey: .provisioningProfiles)
|
||||
let provisioningProfiles = try rawProvisioningProfiles.map { (data) -> ALTProvisioningProfile in
|
||||
guard let profile = ALTProvisioningProfile(data: data) else {
|
||||
throw DecodingError.dataCorruptedError(forKey: CodingKeys.provisioningProfiles, in: container, debugDescription: "Could not parse provisioning profile from data.")
|
||||
}
|
||||
return profile
|
||||
}
|
||||
|
||||
self.provisioningProfiles = Set(provisioningProfiles)
|
||||
self.activeProfiles = try container.decodeIfPresent(Set<String>.self, forKey: .activeProfiles)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws
|
||||
{
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.version, forKey: .version)
|
||||
try container.encode(self.identifier, forKey: .identifier)
|
||||
try container.encode(self.udid, forKey: .udid)
|
||||
|
||||
try container.encode(self.provisioningProfiles.map { $0.data }, forKey: .provisioningProfiles)
|
||||
try container.encodeIfPresent(self.activeProfiles, forKey: .activeProfiles)
|
||||
}
|
||||
}
|
||||
|
||||
public struct InstallProvisioningProfilesResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "InstallProvisioningProfilesResponse"
|
||||
|
||||
public init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoveProvisioningProfilesRequest: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "RemoveProvisioningProfilesRequest"
|
||||
|
||||
public var udid: String
|
||||
public var bundleIdentifiers: Set<String>
|
||||
|
||||
public init(udid: String, bundleIdentifiers: Set<String>)
|
||||
{
|
||||
self.udid = udid
|
||||
self.bundleIdentifiers = bundleIdentifiers
|
||||
}
|
||||
}
|
||||
|
||||
public struct RemoveProvisioningProfilesResponse: ServerMessageProtocol
|
||||
{
|
||||
public var version = 1
|
||||
public var identifier = "RemoveProvisioningProfilesResponse"
|
||||
|
||||
public init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,8 @@ extension ALTServerError
|
||||
case let error as ALTServerError: self = error
|
||||
case is DecodingError: self = ALTServerError(.invalidRequest)
|
||||
case is EncodingError: self = ALTServerError(.invalidResponse)
|
||||
default:
|
||||
assertionFailure("Caught unknown error type")
|
||||
self = ALTServerError(.unknown)
|
||||
case let error as NSError:
|
||||
self = ALTServerError(.unknown, userInfo: error.userInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,7 +265,15 @@ private extension ConnectionManager
|
||||
case .success(.prepareApp(let request)):
|
||||
self.handlePrepareAppRequest(request, for: connection)
|
||||
|
||||
case .success:
|
||||
case .success(.beginInstallation): break
|
||||
|
||||
case .success(.installProvisioningProfiles(let request)):
|
||||
self.handleInstallProvisioningProfilesRequest(request, for: connection)
|
||||
|
||||
case .success(.removeProvisioningProfiles(let request)):
|
||||
self.handleRemoveProvisioningProfilesRequest(request, for: connection)
|
||||
|
||||
case .success(.unknown):
|
||||
let response = ErrorResponse(error: ALTServerError(.unknownRequest))
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent unknown request response with result:", result)
|
||||
@@ -435,6 +442,56 @@ private extension ConnectionManager
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: ClientConnection)
|
||||
{
|
||||
let removeInactiveProfiles = (request.activeProfiles != nil)
|
||||
ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles, removeInactiveProvisioningProfiles: removeInactiveProfiles) { (errors) in
|
||||
|
||||
if let error = errors.values.first
|
||||
{
|
||||
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", errors)
|
||||
|
||||
let errorResponse = ErrorResponse(error: ALTServerError(error))
|
||||
connection.send(errorResponse, shouldDisconnect: true) { (result) in
|
||||
print("Sent install profiles error response with result:", result)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
|
||||
|
||||
let response = InstallProvisioningProfilesResponse()
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent install profiles response to \(connection) with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: ClientConnection)
|
||||
{
|
||||
ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (errors) in
|
||||
if let error = errors.values.first
|
||||
{
|
||||
print("Failed to remove profiles \(request.bundleIdentifiers):", errors)
|
||||
|
||||
let errorResponse = ErrorResponse(error: ALTServerError(error))
|
||||
connection.send(errorResponse, shouldDisconnect: true) { (result) in
|
||||
print("Sent remove profiles error response with result:", result)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Removed profiles:", request.bundleIdentifiers)
|
||||
|
||||
let response = RemoveProvisioningProfilesResponse()
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent remove profiles error response to \(connection) with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConnectionManager
|
||||
|
||||
@@ -29,6 +29,9 @@ extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification
|
||||
/* App Installation */
|
||||
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
||||
|
||||
- (void)installProvisioningProfiles:(NSSet<ALTProvisioningProfile *> *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet<NSString *> *)activeProvisioningProfiles removeInactiveProvisioningProfiles:(BOOL)removeInactiveProvisioningProfiles completionHandler:(void (^)(NSDictionary<ALTProvisioningProfile *, NSError *> *errors))completionHandler;
|
||||
- (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet<NSString *> *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(NSDictionary<NSString *, NSError *> *errors))completionHandler;
|
||||
|
||||
/* Connections */
|
||||
- (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
|
||||
- (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
|
||||
|
||||
@@ -114,19 +114,11 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
|
||||
continue;
|
||||
}
|
||||
|
||||
plist_t pdata = plist_new_data((const char *)provisioningProfile.data.bytes, provisioningProfile.data.length);
|
||||
|
||||
if (misagent_install(mis, pdata) == MISAGENT_E_SUCCESS)
|
||||
NSError *installError = nil;
|
||||
if (![self installProvisioningProfile:preferredProfile misagent:mis error:&installError])
|
||||
{
|
||||
NSLog(@"Reinstalled profile: %@", provisioningProfile.UUID);
|
||||
error = installError;
|
||||
}
|
||||
else
|
||||
{
|
||||
int code = misagent_get_status_code(mis);
|
||||
NSLog(@"Failed to reinstall provisioning profile %@. (%@)", provisioningProfile.UUID, @(code));
|
||||
}
|
||||
|
||||
plist_free(pdata);
|
||||
}
|
||||
|
||||
[[NSFileManager defaultManager] removeItemAtURL:removedProfilesDirectoryURL error:nil];
|
||||
@@ -296,50 +288,17 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
|
||||
{
|
||||
return finish(error);
|
||||
}
|
||||
|
||||
plist_t rawProfiles = NULL;
|
||||
|
||||
if (misagent_copy_all(mis, &rawProfiles) != MISAGENT_E_SUCCESS)
|
||||
NSArray<ALTProvisioningProfile *> *provisioningProfiles = [self copyProvisioningProfilesWithClient:mis error:&error];
|
||||
if (provisioningProfiles == nil)
|
||||
{
|
||||
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
return finish(error);
|
||||
}
|
||||
|
||||
// For some reason, libplist now fails to parse `rawProfiles` correctly.
|
||||
// Specifically, it no longer recognizes the nodes in the plist array as "data" nodes.
|
||||
// However, if we encode it as XML then decode it again, it'll work ¯\_(ツ)_/¯
|
||||
char *plistXML = nullptr;
|
||||
uint32_t plistLength = 0;
|
||||
plist_to_xml(rawProfiles, &plistXML, &plistLength);
|
||||
|
||||
plist_t profiles = NULL;
|
||||
plist_from_xml(plistXML, plistLength, &profiles);
|
||||
|
||||
free(plistXML);
|
||||
|
||||
uint32_t profileCount = plist_array_get_size(profiles);
|
||||
for (int i = 0; i < profileCount; i++)
|
||||
|
||||
for (ALTProvisioningProfile *provisioningProfile in provisioningProfiles)
|
||||
{
|
||||
plist_t profile = plist_array_get_item(profiles, i);
|
||||
if (plist_get_node_type(profile) != PLIST_DATA)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
char *bytes = NULL;
|
||||
uint64_t length = 0;
|
||||
|
||||
plist_get_data_val(profile, &bytes, &length);
|
||||
if (bytes == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES];
|
||||
ALTProvisioningProfile *provisioningProfile = [[ALTProvisioningProfile alloc] initWithData:data];
|
||||
|
||||
if (![provisioningProfile isFreeProvisioningProfile])
|
||||
{
|
||||
NSLog(@"Ignoring: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -365,20 +324,12 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
|
||||
NSLog(@"Failed to copy profile to temporary URL. %@", copyError);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (misagent_remove(mis, provisioningProfile.UUID.UUIDString.lowercaseString.UTF8String) == MISAGENT_E_SUCCESS)
|
||||
|
||||
if (![self removeProvisioningProfile:provisioningProfile misagent:mis error:&error])
|
||||
{
|
||||
NSLog(@"Removed provisioning profile: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
int code = misagent_get_status_code(mis);
|
||||
NSLog(@"Failed to remove provisioning profile %@ (Team: %@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier, @(code));
|
||||
return finish(error);
|
||||
}
|
||||
}
|
||||
|
||||
plist_free(rawProfiles);
|
||||
plist_free(profiles);
|
||||
|
||||
lockdownd_client_free(client);
|
||||
client = NULL;
|
||||
@@ -547,6 +498,313 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
|
||||
return success;
|
||||
}
|
||||
|
||||
#pragma mark - Provisioning Profiles -
|
||||
|
||||
- (void)installProvisioningProfiles:(NSSet<ALTProvisioningProfile *> *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(NSSet<NSString *> *)activeProvisioningProfiles removeInactiveProvisioningProfiles:(BOOL)removeInactiveProvisioningProfiles completionHandler:(void (^)(NSDictionary<ALTProvisioningProfile *, NSError *> *errors))completionHandler;
|
||||
{
|
||||
dispatch_async(self.installationQueue, ^{
|
||||
__block idevice_t device = NULL;
|
||||
__block lockdownd_client_t client = NULL;
|
||||
__block afc_client_t afc = NULL;
|
||||
__block misagent_client_t mis = NULL;
|
||||
__block lockdownd_service_descriptor_t service = NULL;
|
||||
|
||||
void (^finish)(NSDictionary<ALTProvisioningProfile *, NSError *> *, NSError *) = ^(NSDictionary *installationErrors, NSError *error) {
|
||||
lockdownd_service_descriptor_free(service);
|
||||
misagent_client_free(mis);
|
||||
afc_client_free(afc);
|
||||
lockdownd_client_free(client);
|
||||
idevice_free(device);
|
||||
|
||||
if (installationErrors)
|
||||
{
|
||||
completionHandler(installationErrors);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableDictionary *installationErrors = [NSMutableDictionary dictionary];
|
||||
for (ALTProvisioningProfile *profile in provisioningProfiles)
|
||||
{
|
||||
installationErrors[profile] = error;
|
||||
}
|
||||
|
||||
completionHandler(installationErrors);
|
||||
}
|
||||
};
|
||||
|
||||
/* Find Device */
|
||||
if (idevice_new(&device, udid.UTF8String) != IDEVICE_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Device */
|
||||
if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Misagent */
|
||||
if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray<ALTProvisioningProfile *> *installedProvisioningProfiles = [self copyProvisioningProfilesWithClient:mis error:&error];
|
||||
if (provisioningProfiles == nil)
|
||||
{
|
||||
return finish(nil, error);
|
||||
}
|
||||
|
||||
for (ALTProvisioningProfile *provisioningProfile in installedProvisioningProfiles)
|
||||
{
|
||||
if (![provisioningProfile isFreeProvisioningProfile])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOL removeProfile = NO;
|
||||
|
||||
for (ALTProvisioningProfile *profile in provisioningProfiles)
|
||||
{
|
||||
if ([profile.bundleIdentifier isEqualToString:provisioningProfile.bundleIdentifier])
|
||||
{
|
||||
// Remove previous provisioning profile before installing new one.
|
||||
removeProfile = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removeInactiveProvisioningProfiles && ![activeProvisioningProfiles containsObject:provisioningProfile.bundleIdentifier])
|
||||
{
|
||||
// Remove all non-active provisioning profiles to remain under 3 app limit for free developer accounts.
|
||||
removeProfile = YES;
|
||||
}
|
||||
|
||||
if (removeProfile)
|
||||
{
|
||||
if (![self removeProvisioningProfile:provisioningProfile misagent:mis error:&error])
|
||||
{
|
||||
return finish(nil, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableDictionary<ALTProvisioningProfile *, NSError *> *profileErrors = [NSMutableDictionary dictionary];
|
||||
|
||||
for (ALTProvisioningProfile *provisioningProfile in provisioningProfiles)
|
||||
{
|
||||
NSError *error = nil;
|
||||
if (![self installProvisioningProfile:provisioningProfile misagent:mis error:&error])
|
||||
{
|
||||
profileErrors[provisioningProfile] = error;
|
||||
}
|
||||
}
|
||||
|
||||
finish(profileErrors, nil);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet<NSString *> *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(NSDictionary<NSString *, NSError *> *errors))completionHandler
|
||||
{
|
||||
dispatch_async(self.installationQueue, ^{
|
||||
__block idevice_t device = NULL;
|
||||
__block lockdownd_client_t client = NULL;
|
||||
__block afc_client_t afc = NULL;
|
||||
__block misagent_client_t mis = NULL;
|
||||
__block lockdownd_service_descriptor_t service = NULL;
|
||||
|
||||
void (^finish)(NSDictionary<NSString *, NSError *> *, NSError *) = ^(NSDictionary *installationErrors, NSError *error) {
|
||||
lockdownd_service_descriptor_free(service);
|
||||
misagent_client_free(mis);
|
||||
afc_client_free(afc);
|
||||
lockdownd_client_free(client);
|
||||
idevice_free(device);
|
||||
|
||||
if (installationErrors)
|
||||
{
|
||||
completionHandler(installationErrors);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableDictionary *installationErrors = [NSMutableDictionary dictionary];
|
||||
for (NSString *bundleID in bundleIdentifiers)
|
||||
{
|
||||
installationErrors[bundleID] = error;
|
||||
}
|
||||
|
||||
completionHandler(installationErrors);
|
||||
}
|
||||
};
|
||||
|
||||
/* Find Device */
|
||||
if (idevice_new(&device, udid.UTF8String) != IDEVICE_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Device */
|
||||
if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Misagent */
|
||||
if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray<ALTProvisioningProfile *> *provisioningProfiles = [self copyProvisioningProfilesWithClient:mis error:&error];
|
||||
if (provisioningProfiles == nil)
|
||||
{
|
||||
return finish(nil, error);
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSString *, NSError *> *profileErrors = [NSMutableDictionary dictionary];
|
||||
|
||||
/* Remove provisioning profiles */
|
||||
for (ALTProvisioningProfile *provisioningProfile in provisioningProfiles)
|
||||
{
|
||||
if (![bundleIdentifiers containsObject:provisioningProfile.bundleIdentifier])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (![self removeProvisioningProfile:provisioningProfile misagent:mis error:&error])
|
||||
{
|
||||
profileErrors[provisioningProfile.bundleIdentifier] = error;
|
||||
}
|
||||
}
|
||||
|
||||
finish(profileErrors, nil);
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)installProvisioningProfile:(ALTProvisioningProfile *)provisioningProfile misagent:(misagent_client_t)mis error:(NSError **)error
|
||||
{
|
||||
plist_t pdata = plist_new_data((const char *)provisioningProfile.data.bytes, provisioningProfile.data.length);
|
||||
|
||||
misagent_error_t result = misagent_install(mis, pdata);
|
||||
plist_free(pdata);
|
||||
|
||||
if (result == MISAGENT_E_SUCCESS)
|
||||
{
|
||||
NSLog(@"Installed profile: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier);
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Failed to install provisioning profile %@ (Team: %@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier, @(result));
|
||||
|
||||
if (error)
|
||||
{
|
||||
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileInstallFailed userInfo:@{ALTUnderlyingErrorCodeErrorKey: [@(result) description], ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier}];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)removeProvisioningProfile:(ALTProvisioningProfile *)provisioningProfile misagent:(misagent_client_t)mis error:(NSError **)error
|
||||
{
|
||||
misagent_error_t result = misagent_remove(mis, provisioningProfile.UUID.UUIDString.lowercaseString.UTF8String);
|
||||
if (result == MISAGENT_E_SUCCESS)
|
||||
{
|
||||
NSLog(@"Removed provisioning profile: %@ (Team: %@)", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier);
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Failed to remove provisioning profile %@ (Team: %@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.teamIdentifier, @(result));
|
||||
|
||||
if (error)
|
||||
{
|
||||
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileRemoveFailed userInfo:@{ALTUnderlyingErrorCodeErrorKey: [@(result) description], ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier}];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable NSArray<ALTProvisioningProfile *> *)copyProvisioningProfilesWithClient:(misagent_client_t)mis error:(NSError **)error
|
||||
{
|
||||
plist_t rawProfiles = NULL;
|
||||
misagent_error_t result = misagent_copy_all(mis, &rawProfiles);
|
||||
if (result != MISAGENT_E_SUCCESS)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileCopyFailed userInfo:@{ALTUnderlyingErrorCodeErrorKey: [@(result) description]}];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Copy all provisioning profiles */
|
||||
|
||||
// For some reason, libplist now fails to parse `rawProfiles` correctly.
|
||||
// Specifically, it no longer recognizes the nodes in the plist array as "data" nodes.
|
||||
// However, if we encode it as XML then decode it again, it'll work ¯\_(ツ)_/¯
|
||||
char *plistXML = nullptr;
|
||||
uint32_t plistLength = 0;
|
||||
plist_to_xml(rawProfiles, &plistXML, &plistLength);
|
||||
|
||||
plist_t profiles = NULL;
|
||||
plist_from_xml(plistXML, plistLength, &profiles);
|
||||
|
||||
free(plistXML);
|
||||
|
||||
NSMutableArray<ALTProvisioningProfile *> *provisioningProfiles = [NSMutableArray array];
|
||||
|
||||
uint32_t profileCount = plist_array_get_size(profiles);
|
||||
for (int i = 0; i < profileCount; i++)
|
||||
{
|
||||
plist_t profile = plist_array_get_item(profiles, i);
|
||||
if (plist_get_node_type(profile) != PLIST_DATA)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
char *bytes = NULL;
|
||||
uint64_t length = 0;
|
||||
|
||||
plist_get_data_val(profile, &bytes, &length);
|
||||
if (bytes == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES];
|
||||
ALTProvisioningProfile *provisioningProfile = [[ALTProvisioningProfile alloc] initWithData:data];
|
||||
if (provisioningProfile == nil)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
[provisioningProfiles addObject:provisioningProfile];
|
||||
}
|
||||
|
||||
plist_free(rawProfiles);
|
||||
plist_free(profiles);
|
||||
|
||||
return provisioningProfiles;
|
||||
}
|
||||
|
||||
#pragma mark - Connections -
|
||||
|
||||
- (void)startWiredConnectionToDevice:(ALTDevice *)altDevice completionHandler:(void (^)(ALTWiredConnection * _Nullable, NSError * _Nullable))completionHandler
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478B2284C4C300981D42 /* AppIconImageView.swift */; };
|
||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478E2284C8F900981D42 /* Button.swift */; };
|
||||
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */; };
|
||||
BFD44606241188C400EAB90A /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
|
||||
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BD322A0800A000B7ED1 /* ServerManager.swift */; };
|
||||
BFD52C0122A1A9CB000B7ED1 /* ptrarray.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */; };
|
||||
BFD52C0222A1A9CB000B7ED1 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE622A1A9CA000B7ED1 /* base64.c */; };
|
||||
@@ -490,6 +491,7 @@
|
||||
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
||||
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
||||
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
||||
BFD44605241188C300EAB90A /* CodableServerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableServerError.swift; sourceTree = "<group>"; };
|
||||
BFD52BD222A06EFB000B7ED1 /* AltKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AltKit.h; sourceTree = "<group>"; };
|
||||
BFD52BD322A0800A000B7ED1 /* ServerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerManager.swift; sourceTree = "<group>"; };
|
||||
BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ptrarray.c; path = Dependencies/libplist/src/ptrarray.c; sourceTree = SOURCE_ROOT; };
|
||||
@@ -667,6 +669,7 @@
|
||||
BFBAC8852295C90300587369 /* Result+Conveniences.swift */,
|
||||
BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */,
|
||||
BF1E3128229F474900370A3C /* ServerProtocol.swift */,
|
||||
BFD44605241188C300EAB90A /* CodableServerError.swift */,
|
||||
BF1E314822A060F400370A3C /* NSError+ALTServerError.h */,
|
||||
BF1E314922A060F400370A3C /* NSError+ALTServerError.m */,
|
||||
BF718BC723C919CC00A89F2D /* CFNotificationName+AltStore.h */,
|
||||
@@ -1566,6 +1569,7 @@
|
||||
BF1E315922A061FB00370A3C /* Bundle+AltStore.swift in Sources */,
|
||||
BF1E315822A061F900370A3C /* Result+Conveniences.swift in Sources */,
|
||||
BF718BC923C919E300A89F2D /* CFNotificationName+AltStore.m in Sources */,
|
||||
BFD44606241188C400EAB90A /* CodableServerError.swift in Sources */,
|
||||
BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
Reference in New Issue
Block a user