mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Initial implementation for house arrest
This commit is contained in:
@@ -61,6 +61,7 @@ crashreportcopymobile = ["afc"]
|
||||
debug_proxy = []
|
||||
dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
|
||||
heartbeat = ["tokio/macros", "tokio/time"]
|
||||
house_arrest = ["afc"]
|
||||
installation_proxy = []
|
||||
springboardservices = []
|
||||
misagent = []
|
||||
|
||||
133
idevice/src/services/house_arrest.rs
Normal file
133
idevice/src/services/house_arrest.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
//! iOS Device HouseArrest Service Abstraction
|
||||
//!
|
||||
//! The HouseArrest service allows access to the container and Documents directory of apps
|
||||
//! installed on an iOS device. This is typically used for file transfer and inspection of
|
||||
//! app-specific data during development or diagnostics.
|
||||
|
||||
use plist::{Dictionary, Value};
|
||||
|
||||
use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
|
||||
|
||||
use super::afc::AfcClient;
|
||||
|
||||
/// Client for interacting with the iOS HouseArrest service
|
||||
///
|
||||
/// HouseArrest is used to expose the container or Documents directory of an app to a host machine
|
||||
/// over AFC (Apple File Conduit).
|
||||
pub struct HouseArrestClient {
|
||||
/// The underlying device connection with the HouseArrest service
|
||||
pub idevice: Idevice,
|
||||
}
|
||||
|
||||
impl IdeviceService for HouseArrestClient {
|
||||
/// Returns the name of the HouseArrest service as registered with lockdownd
|
||||
fn service_name() -> &'static str {
|
||||
"com.apple.mobile.house_arrest"
|
||||
}
|
||||
|
||||
/// Establishes a connection to the HouseArrest service
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `provider` - Device connection provider
|
||||
///
|
||||
/// # Returns
|
||||
/// A connected `HouseArrestClient` instance
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `IdeviceError` if any step of the connection process fails
|
||||
///
|
||||
/// # Process
|
||||
/// 1. Connect to the lockdownd service
|
||||
/// 2. Start a lockdown session
|
||||
/// 3. Request the HouseArrest service
|
||||
/// 4. Connect to the returned service port
|
||||
/// 5. Start TLS if required by the service
|
||||
async fn connect(
|
||||
provider: &dyn crate::provider::IdeviceProvider,
|
||||
) -> Result<Self, IdeviceError> {
|
||||
let mut lockdown = LockdownClient::connect(provider).await?;
|
||||
lockdown
|
||||
.start_session(&provider.get_pairing_file().await?)
|
||||
.await?;
|
||||
|
||||
let (port, ssl) = lockdown.start_service(Self::service_name()).await?;
|
||||
|
||||
let mut idevice = provider.connect(port).await?;
|
||||
if ssl {
|
||||
idevice
|
||||
.start_session(&provider.get_pairing_file().await?)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Self { idevice })
|
||||
}
|
||||
}
|
||||
|
||||
impl HouseArrestClient {
|
||||
/// Creates a new HouseArrest client from an existing device connection
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `idevice` - A pre-established device connection with the HouseArrest service
|
||||
pub fn new(idevice: Idevice) -> Self {
|
||||
Self { idevice }
|
||||
}
|
||||
|
||||
/// Requests access to the app's full container (Documents, Library, etc.) over AFC
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `bundle_id` - The bundle identifier of the target app (e.g., "com.example.MyApp")
|
||||
///
|
||||
/// # Returns
|
||||
/// An `AfcClient` for accessing the container of the specified app
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `IdeviceError` if the request or AFC setup fails
|
||||
pub async fn vend_container(
|
||||
self,
|
||||
bundle_id: impl Into<String>,
|
||||
) -> Result<AfcClient, IdeviceError> {
|
||||
let bundle_id = bundle_id.into();
|
||||
self.vend(bundle_id, "VendContainer".into()).await
|
||||
}
|
||||
|
||||
/// Requests access to the app's Documents directory over AFC
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `bundle_id` - The bundle identifier of the target app (e.g., "com.example.MyApp")
|
||||
///
|
||||
/// # Returns
|
||||
/// An `AfcClient` for accessing the Documents directory of the specified app
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `IdeviceError` if the request or AFC setup fails
|
||||
pub async fn vend_documents(
|
||||
self,
|
||||
bundle_id: impl Into<String>,
|
||||
) -> Result<AfcClient, IdeviceError> {
|
||||
let bundle_id = bundle_id.into();
|
||||
self.vend(bundle_id, "VendDocuments".into()).await
|
||||
}
|
||||
|
||||
/// Sends a HouseArrest command to expose a specific directory over AFC
|
||||
///
|
||||
/// This is an internal method used by `vend_container` and `vend_documents`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `bundle_id` - App bundle identifier
|
||||
/// * `cmd` - Command to send ("VendContainer" or "VendDocuments")
|
||||
///
|
||||
/// # Returns
|
||||
/// A connected `AfcClient` instance
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `IdeviceError` if the request or AFC setup fails
|
||||
async fn vend(mut self, bundle_id: String, cmd: String) -> Result<AfcClient, IdeviceError> {
|
||||
let mut req = Dictionary::new();
|
||||
req.insert("Command".into(), cmd.into());
|
||||
req.insert("Identifier".into(), bundle_id.into());
|
||||
self.idevice.send_plist(Value::Dictionary(req)).await?;
|
||||
self.idevice.read_plist().await?;
|
||||
|
||||
Ok(AfcClient::new(self.idevice))
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ pub mod debug_proxy;
|
||||
pub mod dvt;
|
||||
#[cfg(feature = "heartbeat")]
|
||||
pub mod heartbeat;
|
||||
#[cfg(feature = "house_arrest")]
|
||||
pub mod house_arrest;
|
||||
#[cfg(feature = "installation_proxy")]
|
||||
pub mod installation_proxy;
|
||||
pub mod lockdown;
|
||||
|
||||
Reference in New Issue
Block a user