Add support for listing, downloading, installing, and uninstalling provisioning profiles.
diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m
index 06eb792..af74ca8 100644
--- a/src/ios-deploy/ios-deploy.m
+++ b/src/ios-deploy/ios-deploy.m
@@ -96,6 +96,13 @@
 int AMDServiceConnectionReceive(ServiceConnRef con, void * data, size_t size);
 uint64_t AMDServiceConnectionReceiveMessage(ServiceConnRef con, CFPropertyListRef message, CFPropertyListFormat *format);
 uint64_t AMDServiceConnectionSendMessage(ServiceConnRef con, CFPropertyListRef message, CFPropertyListFormat format);
+CFArrayRef AMDeviceCopyProvisioningProfiles(AMDeviceRef device);
+int AMDeviceInstallProvisioningProfile(AMDeviceRef device, void *profile);
+int AMDeviceRemoveProvisioningProfile(AMDeviceRef device, CFStringRef uuid);
+CFStringRef MISProvisioningProfileGetValue(void *profile, CFStringRef key);
+CFDictionaryRef MISProfileCopyPayload(void *profile);
+void *MISProfileCreateWithData(int zero, CFDataRef data);
+int MISProfileWriteToFile(void *profile, CFStringRef dest_path);
 
 bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, debugserver_only = false, detect_only = false, install = true, uninstall = false, no_wifi = false;
 bool faster_path_search = false;
@@ -117,6 +124,8 @@
 char *list_root = NULL;
 const char * custom_script_path = NULL;
 char *symbols_download_directory = NULL;
+char *profile_uuid = NULL;
+char *profile_path = NULL;
 int _timeout = 0;
 int _detectDeadlockTimeout = 0;
 bool _json_output = false;
@@ -1844,6 +1853,146 @@
     check_error(AMDeviceDisconnect(device));
 }
 
+void replace_dict_date_with_absolute_time(CFMutableDictionaryRef dict, CFStringRef key) {
+    CFDateRef date = CFDictionaryGetValue(dict, key);
+    CFAbsoluteTime absolute_date = CFDateGetAbsoluteTime(date);
+    CFNumberRef absolute_date_ref = CFNumberCreate(NULL, kCFNumberDoubleType, &absolute_date);
+    CFDictionaryReplaceValue(dict, key, absolute_date_ref);
+    CFRelease(absolute_date_ref);
+}
+
+void list_provisioning_profiles(AMDeviceRef device) {
+    connect_and_start_session(device);
+    CFArrayRef device_provisioning_profiles = AMDeviceCopyProvisioningProfiles(device);
+
+    CFIndex provisioning_profiles_count = CFArrayGetCount(device_provisioning_profiles);
+    CFMutableArrayRef serializable_provisioning_profiles =
+        CFArrayCreateMutable(NULL, provisioning_profiles_count, &kCFTypeArrayCallBacks);
+
+    for (CFIndex i = 0; i < provisioning_profiles_count; i++) {
+        void *device_provisioning_profile =
+            (void *)CFArrayGetValueAtIndex(device_provisioning_profiles, i);
+        CFDictionaryRef serializable_provisioning_profile;
+
+        if (verbose) {
+            // Fetch the entire provisioning profile and copy it into a mutable dictionary so we can
+            // process it as needed.
+            CFDictionaryRef immutable_profile_dict =
+                MISProfileCopyPayload(device_provisioning_profile);
+            CFMutableDictionaryRef mutable_profile_dict =
+                CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, immutable_profile_dict);
+            CFRelease(immutable_profile_dict);
+
+            // Remove binary values from the output since they aren't readable and add a whole lot
+            // of noise to the output.
+            CFDictionaryRemoveValue(mutable_profile_dict, CFSTR("DER-Encoded-Profile"));
+            CFDictionaryRemoveValue(mutable_profile_dict, CFSTR("DeveloperCertificates"));
+            if (_json_output) {
+                // JSON output can't have certain CFDate objects so convert dates into stringified
+                // CFAbsoluteTime's respectively.
+                replace_dict_date_with_absolute_time(mutable_profile_dict, CFSTR("ExpirationDate"));
+                replace_dict_date_with_absolute_time(mutable_profile_dict, CFSTR("CreationDate"));
+            }
+            serializable_provisioning_profile = mutable_profile_dict;
+        } else {
+            // We're not in verbose mode so pluck out only particularly interesting keys.
+            CFStringRef uuid = MISProvisioningProfileGetValue(device_provisioning_profile, "UUID");
+            CFStringRef name = MISProvisioningProfileGetValue(device_provisioning_profile, "Name");
+            CFDateRef date =
+                MISProvisioningProfileGetValue(device_provisioning_profile, "ExpirationDate");
+            CFAbsoluteTime absolute_date = CFDateGetAbsoluteTime(date);
+            CFNumberRef absolute_date_ref =
+                CFNumberCreate(NULL, kCFNumberDoubleType, &absolute_date);
+
+            CFStringRef keys[] = {CFSTR("Name"), CFSTR("UUID"), CFSTR("ExpirationDate")};
+            CFTypeRef values[] = {name, uuid, absolute_date_ref};
+            CFIndex size = sizeof(keys) / sizeof(CFStringRef);
+            serializable_provisioning_profile = CFDictionaryCreate(
+                NULL, (const void **)&keys, (const void **)&values, size,
+                &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+            CFRelease(absolute_date_ref);
+        }
+
+        CFArrayAppendValue(serializable_provisioning_profiles, serializable_provisioning_profile);
+        CFRelease(serializable_provisioning_profile);
+    }
+    CFRelease(device_provisioning_profiles);
+
+    if (_json_output) {
+        NSLogJSON(@{
+            @"Event" : @"ListProvisioningProfiles",
+            @"Profiles" : (NSArray *)serializable_provisioning_profiles
+        });
+    } else {
+        NSLogOut(@"%@", serializable_provisioning_profiles);
+    }
+    CFRelease(serializable_provisioning_profiles);
+}
+
+void install_provisioning_profile(AMDeviceRef device) {
+    if (!profile_path) {
+        on_error(@"no path to provisioning profile specified");
+    }
+
+    size_t file_size = 0;
+    void *file_content = read_file_to_memory(profile_path, &file_size);
+    CFDataRef profile_data = CFDataCreate(NULL, file_content, file_size);
+    void *profile = MISProfileCreateWithData(0, profile_data);
+    connect_and_start_session(device);
+    check_error(AMDeviceInstallProvisioningProfile(device, profile));
+
+    free(file_content);
+    CFRelease(profile_data);
+    CFRelease(profile);
+}
+
+void uninstall_provisioning_profile(AMDeviceRef device) {
+    if (!profile_uuid) {
+        on_error(@"no profile UUID specified via --profile-uuid");
+    }
+
+    CFStringRef uuid = CFStringCreateWithCString(NULL, profile_uuid, kCFStringEncodingUTF8);
+    connect_and_start_session(device);
+    check_error(AMDeviceRemoveProvisioningProfile(device, uuid));
+    CFRelease(uuid);
+}
+
+void download_provisioning_profile(AMDeviceRef device) {
+    if (!profile_uuid) {
+        on_error(@"no profile UUID specified via --profile-uuid");
+    } else if (!profile_path) {
+        on_error(@"no download path specified");
+    }
+
+    connect_and_start_session(device);
+    CFArrayRef device_provisioning_profiles = AMDeviceCopyProvisioningProfiles(device);
+    CFIndex provisioning_profiles_count = CFArrayGetCount(device_provisioning_profiles);
+    CFStringRef uuid = CFStringCreateWithCString(NULL, profile_uuid, kCFStringEncodingUTF8);
+    bool found_matching_uuid = false;
+
+    for (CFIndex i = 0; i < provisioning_profiles_count; i++) {
+        void *profile = (void *)CFArrayGetValueAtIndex(device_provisioning_profiles, i);
+        CFStringRef other_uuid = MISProvisioningProfileGetValue(profile, "UUID");
+        found_matching_uuid = CFStringCompare(uuid, other_uuid, 0) == kCFCompareEqualTo;
+
+        if (found_matching_uuid) {
+            NSLogVerbose(@"Writing %@ to %s", MISProvisioningProfileGetValue(profile, "Name"),
+                         profile_path);
+            CFStringRef dst_path =
+                CFStringCreateWithCString(NULL, profile_path, kCFStringEncodingUTF8);
+            check_error(MISProfileWriteToFile(profile, dst_path));
+            CFRelease(dst_path);
+            break;
+        }
+    }
+
+    CFRelease(uuid);
+    CFRelease(device_provisioning_profiles);
+    if (!found_matching_uuid) {
+        on_error(@"Did not find provisioning profile with UUID %x on device", profile_uuid);
+    }
+}
+
 void list_bundle_id(AMDeviceRef device)
 {
     connect_and_start_session(device);
@@ -2446,6 +2595,14 @@
             get_battery_level(device);
         } else if (strcmp("symbols", command) == 0) {
             download_device_symbols(device);
+        } else if (strcmp("list_profiles", command) == 0) {
+            list_provisioning_profiles(device);
+        } else if (strcmp("install_profile", command) == 0) {
+            install_provisioning_profile(device);
+        } else if (strcmp("uninstall_profile", command) == 0) {
+            uninstall_provisioning_profile(device);
+        } else if (strcmp("download_profile", command) == 0) {
+            download_provisioning_profile(device);
         }
         exit(0);
     }
@@ -2707,6 +2864,11 @@
         @"  --custom-script <script>     path to custom python script to execute in lldb\n"
         @"  --custom-command <command>   specify additional lldb commands to execute\n"
         @"  --faster-path-search         use alternative logic to find the device support paths faster\n",
+        @"  -P, --list_profiles          list all provisioning profiles on device\n"
+        @"  --profile-uuid <uuid>        the UUID of the provisioning profile to target, use with other profile commands\n"
+        @"  --profile-download <path>    download a provisioning profile (requires --profile-uuid)\n"
+        @"  --profile-install <file>     install a provisioning profile\n"
+        @"  --profile-uninstall          uninstall a provisioning profile (requires --profile-uuid)\n",
         [NSString stringWithUTF8String:app]);
 }
 
@@ -2764,9 +2926,14 @@
         { "non-recursively", no_argument, NULL, 'F'},
         { "key", optional_argument, NULL, 'k' },
         { "symbols", required_argument, NULL, 'S' },
+        { "list_profiles", no_argument, NULL, 'P' },
         { "custom-script", required_argument, NULL, 1001},
         { "custom-command", required_argument, NULL, 1002},
         { "faster-path-search", no_argument, NULL, 1003},
+        { "profile-install", required_argument, NULL, 1004},
+        { "profile-uninstall", no_argument, NULL, 1005},
+        { "profile-download", required_argument, NULL, 1006},
+        { "profile-uuid", required_argument, NULL, 1007},
         { NULL, 0, NULL, 0 },
     };
     int ch;
@@ -2925,6 +3092,27 @@
         case 1003:
             faster_path_search = true;
             break;
+        case 1004:
+            command_only = true;
+            command = "install_profile";
+            profile_path = optarg;
+            break;
+        case 1005:
+            command_only = true;
+            command = "uninstall_profile";
+            break;
+        case 1006:
+            command_only = true;
+            command = "download_profile";
+            profile_path = optarg;
+            break;
+        case 1007:
+            profile_uuid = optarg;
+            break;
+        case 'P':
+            command_only = true;
+            command = "list_profiles";
+            break;
         case 'k':
             if (!keys) keys = [[NSMutableArray alloc] init];
             [keys addObject: [NSString stringWithUTF8String:optarg]];