mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Migrate to plist_macro crate for utils
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -1076,6 +1076,7 @@ dependencies = [
|
||||
"obfstr",
|
||||
"openssl",
|
||||
"plist",
|
||||
"plist-macro",
|
||||
"rand 0.9.2",
|
||||
"reqwest",
|
||||
"rsa",
|
||||
@@ -1122,6 +1123,7 @@ dependencies = [
|
||||
"idevice",
|
||||
"ns-keyed-archive",
|
||||
"plist",
|
||||
"plist-macro",
|
||||
"sha2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1730,6 +1732,15 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plist-macro"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb72007326fe20721ef27304fcf2d1bd5877b92d13dbd8df735fd33407e31c2a"
|
||||
dependencies = [
|
||||
"plist",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plist_ffi"
|
||||
version = "0.1.6"
|
||||
|
||||
@@ -21,6 +21,7 @@ tokio-openssl = { version = "0.6", optional = true }
|
||||
openssl = { version = "0.10", optional = true }
|
||||
|
||||
plist = { version = "1.8" }
|
||||
plist-macro = { version = "0.1" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
ns-keyed-archive = { version = "0.1.4", optional = true }
|
||||
crossfire = { version = "2.1", optional = true }
|
||||
|
||||
334
idevice/src/cursor.rs
Normal file
334
idevice/src/cursor.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Cursor<'a> {
|
||||
inner: &'a [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
/// Creates a new cursor
|
||||
pub fn new(inner: &'a [u8]) -> Self {
|
||||
Self { inner, pos: 0 }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
pub fn at_end(&self) -> bool {
|
||||
self.pos == self.inner.len()
|
||||
}
|
||||
|
||||
pub fn read(&mut self, to_read: usize) -> Option<&'a [u8]> {
|
||||
// Check if the end of the slice (self.pos + to_read) is beyond the buffer length
|
||||
if self
|
||||
.pos
|
||||
.checked_add(to_read)
|
||||
.is_none_or(|end_pos| end_pos > self.inner.len())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// The end of the slice is self.pos + to_read
|
||||
let end_pos = self.pos + to_read;
|
||||
let res = Some(&self.inner[self.pos..end_pos]);
|
||||
self.pos = end_pos;
|
||||
res
|
||||
}
|
||||
|
||||
pub fn back(&mut self, to_back: usize) {
|
||||
let to_back = if to_back > self.pos {
|
||||
self.pos
|
||||
} else {
|
||||
to_back
|
||||
};
|
||||
|
||||
self.pos -= to_back;
|
||||
}
|
||||
|
||||
/// True if actually all zeroes
|
||||
pub fn read_assert_zero(&mut self, to_read: usize) -> Option<()> {
|
||||
let bytes = self.read(to_read)?;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
for b in bytes.iter() {
|
||||
if *b > 0 {
|
||||
eprintln!("Zero read contained non-zero values!");
|
||||
eprintln!("{bytes:02X?}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn read_to(&mut self, end: usize) -> Option<&'a [u8]> {
|
||||
if end > self.inner.len() {
|
||||
return None;
|
||||
}
|
||||
let res = Some(&self.inner[self.pos..end]);
|
||||
self.pos = end;
|
||||
res
|
||||
}
|
||||
|
||||
pub fn peek_to(&mut self, end: usize) -> Option<&'a [u8]> {
|
||||
if end > self.inner.len() {
|
||||
return None;
|
||||
}
|
||||
Some(&self.inner[self.pos..end])
|
||||
}
|
||||
|
||||
pub fn peek(&self, to_read: usize) -> Option<&'a [u8]> {
|
||||
if self
|
||||
.pos
|
||||
.checked_add(to_read)
|
||||
.is_none_or(|end_pos| end_pos > self.inner.len())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let end_pos = self.pos + to_read;
|
||||
Some(&self.inner[self.pos..end_pos])
|
||||
}
|
||||
|
||||
pub fn reveal(&self, surrounding: usize) {
|
||||
let len = self.inner.len();
|
||||
|
||||
if self.pos > len {
|
||||
println!("Cursor is past end of buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
let start = self.pos.saturating_sub(surrounding);
|
||||
let end = (self.pos + surrounding + 1).min(len);
|
||||
|
||||
// HEADER
|
||||
println!("Reveal around pos {} ({} bytes):", self.pos, surrounding);
|
||||
|
||||
// --- HEX LINE ---
|
||||
print!("Hex: ");
|
||||
for i in start..end {
|
||||
if i == self.pos {
|
||||
print!("[{:02X}] ", self.inner[i]);
|
||||
} else {
|
||||
print!("{:02X} ", self.inner[i]);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// --- ASCII LINE ---
|
||||
print!("Ascii: ");
|
||||
for i in start..end {
|
||||
let b = self.inner[i];
|
||||
let c = if b.is_ascii_graphic() || b == b' ' {
|
||||
b as char
|
||||
} else {
|
||||
'.'
|
||||
};
|
||||
|
||||
if i == self.pos {
|
||||
print!("[{}] ", c);
|
||||
} else {
|
||||
print!("{} ", c);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// --- OFFSET LINE ---
|
||||
print!("Offset: ");
|
||||
for i in start..end {
|
||||
let off = i as isize - self.pos as isize;
|
||||
if i == self.pos {
|
||||
print!("[{}] ", off);
|
||||
} else {
|
||||
print!("{:<3} ", off);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn remaining(&mut self) -> &'a [u8] {
|
||||
let res = &self.inner[self.pos..];
|
||||
self.pos = self.inner.len();
|
||||
res
|
||||
}
|
||||
|
||||
pub fn read_u8(&mut self) -> Option<u8> {
|
||||
if self.pos == self.inner.len() {
|
||||
return None;
|
||||
}
|
||||
let res = Some(self.inner[self.pos]);
|
||||
self.pos += 1;
|
||||
res
|
||||
}
|
||||
|
||||
pub fn read_le_u16(&mut self) -> Option<u16> {
|
||||
const SIZE: usize = 2;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u16::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_u16(&mut self) -> Option<u16> {
|
||||
const SIZE: usize = 2;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u16::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_le_u32(&mut self) -> Option<u32> {
|
||||
const SIZE: usize = 4;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u32::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_u32(&mut self) -> Option<u32> {
|
||||
const SIZE: usize = 4;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u32::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_le_u64(&mut self) -> Option<u64> {
|
||||
const SIZE: usize = 8;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u64::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_u64(&mut self) -> Option<u64> {
|
||||
const SIZE: usize = 8;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u64::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_le_u128(&mut self) -> Option<u128> {
|
||||
const SIZE: usize = 16;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u128::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_u128(&mut self) -> Option<u128> {
|
||||
const SIZE: usize = 16;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(u128::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_le_f32(&mut self) -> Option<f32> {
|
||||
const SIZE: usize = 4;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(f32::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_f32(&mut self) -> Option<f32> {
|
||||
const SIZE: usize = 4;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(f32::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_i8(&mut self) -> Option<i8> {
|
||||
if self.pos == self.inner.len() {
|
||||
return None;
|
||||
}
|
||||
let res = Some(self.inner[self.pos]).map(|x| x as i8);
|
||||
self.pos += 1;
|
||||
res
|
||||
}
|
||||
|
||||
pub fn read_le_i16(&mut self) -> Option<i16> {
|
||||
const SIZE: usize = 2;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i16::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_i16(&mut self) -> Option<i16> {
|
||||
const SIZE: usize = 2;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i16::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_le_i32(&mut self) -> Option<i32> {
|
||||
const SIZE: usize = 4;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i32::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_i32(&mut self) -> Option<i32> {
|
||||
const SIZE: usize = 4;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i32::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_le_i64(&mut self) -> Option<i64> {
|
||||
const SIZE: usize = 8;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i64::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_i64(&mut self) -> Option<i64> {
|
||||
const SIZE: usize = 8;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i64::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_le_i128(&mut self) -> Option<i128> {
|
||||
const SIZE: usize = 16;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i128::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn read_be_i128(&mut self) -> Option<i128> {
|
||||
const SIZE: usize = 16;
|
||||
let bytes = self.read(SIZE)?;
|
||||
let bytes: [u8; SIZE] = bytes.try_into().unwrap();
|
||||
Some(i128::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
pub fn take_2(&mut self) -> Option<[u8; 2]> {
|
||||
let bytes = self.read(2)?;
|
||||
Some(bytes.to_owned().try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn take_3(&mut self) -> Option<[u8; 3]> {
|
||||
let bytes = self.read(3)?;
|
||||
Some(bytes.to_owned().try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn take_4(&mut self) -> Option<[u8; 4]> {
|
||||
let bytes = self.read(4)?;
|
||||
Some(bytes.to_owned().try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn take_8(&mut self) -> Option<[u8; 8]> {
|
||||
let bytes = self.read(8)?;
|
||||
Some(bytes.to_owned().try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn take_20(&mut self) -> Option<[u8; 20]> {
|
||||
let bytes = self.read(20)?;
|
||||
Some(bytes.to_owned().try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn take_32(&mut self) -> Option<[u8; 32]> {
|
||||
let bytes = self.read(32)?;
|
||||
Some(bytes.to_owned().try_into().unwrap())
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
#[cfg(all(feature = "pair", feature = "rustls"))]
|
||||
mod ca;
|
||||
pub mod cursor;
|
||||
mod obfuscation;
|
||||
pub mod pairing_file;
|
||||
pub mod plist_macro;
|
||||
pub mod provider;
|
||||
#[cfg(feature = "rustls")]
|
||||
mod sni;
|
||||
@@ -18,7 +19,6 @@ pub mod tss;
|
||||
pub mod tunneld;
|
||||
#[cfg(feature = "usbmuxd")]
|
||||
pub mod usbmuxd;
|
||||
mod util;
|
||||
pub mod utils;
|
||||
#[cfg(feature = "xpc")]
|
||||
pub mod xpc;
|
||||
@@ -29,6 +29,7 @@ pub use services::*;
|
||||
#[cfg(feature = "xpc")]
|
||||
pub use xpc::RemoteXpcClient;
|
||||
|
||||
use plist_macro::{plist, pretty_print_dictionary, pretty_print_plist};
|
||||
use provider::{IdeviceProvider, RsdProvider};
|
||||
#[cfg(feature = "rustls")]
|
||||
use rustls::{crypto::CryptoProvider, pki_types::ServerName};
|
||||
@@ -40,8 +41,6 @@ use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
pub use util::{pretty_print_dictionary, pretty_print_plist};
|
||||
|
||||
use crate::services::lockdown::LockdownClient;
|
||||
|
||||
/// A trait combining all required characteristics for a device communication socket
|
||||
@@ -192,7 +191,7 @@ impl Idevice {
|
||||
/// # Errors
|
||||
/// Returns `IdeviceError` if communication fails or response is invalid
|
||||
pub async fn get_type(&mut self) -> Result<String, IdeviceError> {
|
||||
let req = crate::plist!({
|
||||
let req = plist!({
|
||||
"Label": self.label.clone(),
|
||||
"Request": "QueryType",
|
||||
});
|
||||
@@ -212,7 +211,7 @@ impl Idevice {
|
||||
/// # Errors
|
||||
/// Returns `IdeviceError` if the protocol sequence isn't followed correctly
|
||||
pub async fn rsd_checkin(&mut self) -> Result<(), IdeviceError> {
|
||||
let req = crate::plist!({
|
||||
let req = plist!({
|
||||
"Label": self.label.clone(),
|
||||
"ProtocolVersion": "2",
|
||||
"Request": "RSDCheckin",
|
||||
|
||||
15
idevice/src/obfuscation.rs
Normal file
15
idevice/src/obfuscation.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Jackson Coxson
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! obf {
|
||||
($lit:literal) => {{
|
||||
#[cfg(feature = "obfuscate")]
|
||||
{
|
||||
std::borrow::Cow::Owned(obfstr::obfstr!($lit).to_string())
|
||||
}
|
||||
#[cfg(not(feature = "obfuscate"))]
|
||||
{
|
||||
std::borrow::Cow::Borrowed($lit)
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -1,714 +0,0 @@
|
||||
// Jackson Coxson
|
||||
// Ported from serde's json!
|
||||
|
||||
/// Construct a `plist::Value` from a JSON-like literal.
|
||||
///
|
||||
/// ```
|
||||
/// # use idevice::plist;
|
||||
/// #
|
||||
/// let value = plist!({
|
||||
/// "code": 200,
|
||||
/// "success": true,
|
||||
/// "payload": {
|
||||
/// "features": [
|
||||
/// "serde",
|
||||
/// "plist"
|
||||
/// ],
|
||||
/// "homepage": null
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Variables or expressions can be interpolated into the plist literal. Any type
|
||||
/// interpolated into an array element or object value must implement `Into<plist::Value>`.
|
||||
/// If the conversion fails, the `plist!` macro will panic.
|
||||
///
|
||||
/// ```
|
||||
/// # use idevice::plist;
|
||||
/// #
|
||||
/// let code = 200;
|
||||
/// let features = vec!["serde", "plist"];
|
||||
///
|
||||
/// let value = plist!({
|
||||
/// "code": code,
|
||||
/// "success": code == 200,
|
||||
/// "payload": {
|
||||
/// features[0]: features[1]
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Trailing commas are allowed inside both arrays and objects.
|
||||
///
|
||||
/// ```
|
||||
/// # use idevice::plist;
|
||||
/// #
|
||||
/// let value = plist!([
|
||||
/// "notice",
|
||||
/// "the",
|
||||
/// "trailing",
|
||||
/// "comma -->",
|
||||
/// ]);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! plist {
|
||||
// Force: dictionary out
|
||||
(dict { $($tt:tt)+ }) => {{
|
||||
let mut object = plist::Dictionary::new();
|
||||
$crate::plist_internal!(@object object () ($($tt)+) ($($tt)+));
|
||||
object
|
||||
}};
|
||||
|
||||
// Force: value out (explicit, though default already does this)
|
||||
(value { $($tt:tt)+ }) => {
|
||||
$crate::plist_internal!({ $($tt)+ })
|
||||
};
|
||||
|
||||
// Force: raw vec of plist::Value out
|
||||
(array [ $($tt:tt)+ ]) => {
|
||||
$crate::plist_internal!(@array [] $($tt)+)
|
||||
};
|
||||
|
||||
// Hide distracting implementation details from the generated rustdoc.
|
||||
($($plist:tt)+) => {
|
||||
$crate::plist_internal!($($plist)+)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! plist_internal {
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TT muncher for parsing the inside of an array [...]. Produces a vec![...]
|
||||
// of the elements.
|
||||
//
|
||||
// Must be invoked as: plist_internal!(@array [] $($tt)*)
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Done with trailing comma.
|
||||
(@array [$($elems:expr,)*]) => {
|
||||
vec![$($elems,)*]
|
||||
};
|
||||
|
||||
// Done without trailing comma.
|
||||
(@array [$($elems:expr),*]) => {
|
||||
vec![$($elems),*]
|
||||
};
|
||||
|
||||
// Next element is `null`.
|
||||
(@array [$($elems:expr,)*] null $($rest:tt)*) => {
|
||||
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(null)] $($rest)*)
|
||||
};
|
||||
|
||||
// Next element is `true`.
|
||||
(@array [$($elems:expr,)*] true $($rest:tt)*) => {
|
||||
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(true)] $($rest)*)
|
||||
};
|
||||
|
||||
// Next element is `false`.
|
||||
(@array [$($elems:expr,)*] false $($rest:tt)*) => {
|
||||
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!(false)] $($rest)*)
|
||||
};
|
||||
|
||||
// Next element is an array.
|
||||
(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
|
||||
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!([$($array)*])] $($rest)*)
|
||||
};
|
||||
|
||||
// Next element is a map.
|
||||
(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
|
||||
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!({$($map)*})] $($rest)*)
|
||||
};
|
||||
|
||||
// Next element is an expression followed by comma.
|
||||
(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
|
||||
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!($next),] $($rest)*)
|
||||
};
|
||||
|
||||
// Last element is an expression with no trailing comma.
|
||||
(@array [$($elems:expr,)*] $last:expr) => {
|
||||
$crate::plist_internal!(@array [$($elems,)* $crate::plist_internal!($last)])
|
||||
};
|
||||
|
||||
// Comma after the most recent element.
|
||||
(@array [$($elems:expr),*] , $($rest:tt)*) => {
|
||||
$crate::plist_internal!(@array [$($elems,)*] $($rest)*)
|
||||
};
|
||||
|
||||
// Unexpected token after most recent element.
|
||||
(@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => {
|
||||
$crate::plist_unexpected!($unexpected)
|
||||
};
|
||||
|
||||
(@array [$($elems:expr,)*] ? $maybe:expr , $($rest:tt)*) => {
|
||||
if let Some(__v) = $crate::plist_macro::plist_maybe($maybe) {
|
||||
$crate::plist_internal!(@array [$($elems,)* __v,] $($rest)*)
|
||||
} else {
|
||||
$crate::plist_internal!(@array [$($elems,)*] $($rest)*)
|
||||
}
|
||||
};
|
||||
(@array [$($elems:expr,)*] ? $maybe:expr) => {
|
||||
if let Some(__v) = $crate::plist_macro::plist_maybe($maybe) {
|
||||
$crate::plist_internal!(@array [$($elems,)* __v])
|
||||
} else {
|
||||
$crate::plist_internal!(@array [$($elems,)*])
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TT muncher for parsing the inside of an object {...}. Each entry is
|
||||
// inserted into the given map variable.
|
||||
//
|
||||
// Must be invoked as: plist_internal!(@object $map () ($($tt)*) ($($tt)*))
|
||||
//
|
||||
// We require two copies of the input tokens so that we can match on one
|
||||
// copy and trigger errors on the other copy.
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Done.
|
||||
(@object $object:ident () () ()) => {};
|
||||
|
||||
// Insert the current entry followed by trailing comma.
|
||||
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
|
||||
let _ = $object.insert(($($key)+).into(), $value);
|
||||
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
|
||||
};
|
||||
|
||||
// Current entry followed by unexpected token.
|
||||
(@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
|
||||
$crate::plist_unexpected!($unexpected);
|
||||
};
|
||||
|
||||
// Insert the last entry without trailing comma.
|
||||
(@object $object:ident [$($key:tt)+] ($value:expr)) => {
|
||||
let _ = $object.insert(($($key)+).into(), $value);
|
||||
};
|
||||
|
||||
// Next value is `null`.
|
||||
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(null)) $($rest)*);
|
||||
};
|
||||
|
||||
// Next value is `true`.
|
||||
(@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(true)) $($rest)*);
|
||||
};
|
||||
|
||||
// Next value is `false`.
|
||||
(@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!(false)) $($rest)*);
|
||||
};
|
||||
|
||||
// Next value is an array.
|
||||
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!([$($array)*])) $($rest)*);
|
||||
};
|
||||
|
||||
// Next value is a map.
|
||||
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!({$($map)*})) $($rest)*);
|
||||
};
|
||||
|
||||
// Optional insert with trailing comma: key?: expr,
|
||||
(@object $object:ident ($($key:tt)+) (:? $value:expr , $($rest:tt)*) $copy:tt) => {
|
||||
if let Some(__v) = $crate::plist_macro::plist_maybe($value) {
|
||||
let _ = $object.insert(($($key)+).into(), __v);
|
||||
}
|
||||
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
|
||||
};
|
||||
|
||||
// Optional insert, last entry: key?: expr
|
||||
(@object $object:ident ($($key:tt)+) (:? $value:expr) $copy:tt) => {
|
||||
if let Some(__v) = $crate::plist_macro::plist_maybe($value) {
|
||||
let _ = $object.insert(($($key)+).into(), __v);
|
||||
}
|
||||
};
|
||||
|
||||
(@object $object:ident () ( :< $value:expr , $($rest:tt)*) $copy:tt) => {
|
||||
{
|
||||
let __v = $crate::plist_internal!($value);
|
||||
let __dict = $crate::plist_macro::IntoPlistDict::into_plist_dict(__v);
|
||||
for (__k, __val) in __dict {
|
||||
let _ = $object.insert(__k, __val);
|
||||
}
|
||||
}
|
||||
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
|
||||
};
|
||||
|
||||
// Merge: last entry `:< expr`
|
||||
(@object $object:ident () ( :< $value:expr ) $copy:tt) => {
|
||||
{
|
||||
let __v = $crate::plist_internal!($value);
|
||||
let __dict = $crate::plist_macro::IntoPlistDict::into_plist_dict(__v);
|
||||
for (__k, __val) in __dict {
|
||||
let _ = $object.insert(__k, __val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Optional merge: `:< ? expr,` — only merge if Some(...)
|
||||
(@object $object:ident () ( :< ? $value:expr , $($rest:tt)*) $copy:tt) => {
|
||||
if let Some(__dict) = $crate::plist_macro::maybe_into_dict($value) {
|
||||
for (__k, __val) in __dict {
|
||||
let _ = $object.insert(__k, __val);
|
||||
}
|
||||
}
|
||||
$crate::plist_internal!(@object $object () ($($rest)*) ($($rest)*));
|
||||
};
|
||||
|
||||
// Optional merge: last entry `:< ? expr`
|
||||
(@object $object:ident () ( :< ? $value:expr ) $copy:tt) => {
|
||||
if let Some(__dict) = $crate::plist_macro::maybe_into_dict($value) {
|
||||
for (__k, __val) in __dict {
|
||||
let _ = $object.insert(__k, __val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Next value is an expression followed by comma.
|
||||
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value)) , $($rest)*);
|
||||
};
|
||||
|
||||
// Last value is an expression with no trailing comma.
|
||||
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object [$($key)+] ($crate::plist_internal!($value)));
|
||||
};
|
||||
|
||||
// Missing value for last entry. Trigger a reasonable error message.
|
||||
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
|
||||
// "unexpected end of macro invocation"
|
||||
$crate::plist_internal!();
|
||||
};
|
||||
|
||||
// Missing colon and value for last entry. Trigger a reasonable error
|
||||
// message.
|
||||
(@object $object:ident ($($key:tt)+) () $copy:tt) => {
|
||||
// "unexpected end of macro invocation"
|
||||
$crate::plist_internal!();
|
||||
};
|
||||
|
||||
// Misplaced colon. Trigger a reasonable error message.
|
||||
(@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
|
||||
// Takes no arguments so "no rules expected the token `:`".
|
||||
$crate::plist_unexpected!($colon);
|
||||
};
|
||||
|
||||
// Found a comma inside a key. Trigger a reasonable error message.
|
||||
(@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
|
||||
// Takes no arguments so "no rules expected the token `,`".
|
||||
$crate::plist_unexpected!($comma);
|
||||
};
|
||||
|
||||
// Key is fully parenthesized. This avoids clippy double_parens false
|
||||
// positives because the parenthesization may be necessary here.
|
||||
(@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
|
||||
};
|
||||
|
||||
// Refuse to absorb colon token into key expression.
|
||||
(@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
|
||||
$crate::plist_expect_expr_comma!($($unexpected)+);
|
||||
};
|
||||
|
||||
// Munch a token into the current key.
|
||||
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
|
||||
$crate::plist_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// The main implementation.
|
||||
//
|
||||
// Must be invoked as: plist_internal!($($plist)+)
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
(null) => {
|
||||
plist::Value::String("".to_string()) // plist doesn't have null, use empty string or consider other representation
|
||||
};
|
||||
|
||||
(true) => {
|
||||
plist::Value::Boolean(true)
|
||||
};
|
||||
|
||||
(false) => {
|
||||
plist::Value::Boolean(false)
|
||||
};
|
||||
|
||||
([]) => {
|
||||
plist::Value::Array(vec![])
|
||||
};
|
||||
|
||||
([ $($tt:tt)+ ]) => {
|
||||
plist::Value::Array($crate::plist_internal!(@array [] $($tt)+))
|
||||
};
|
||||
|
||||
({}) => {
|
||||
plist::Value::Dictionary(plist::Dictionary::new())
|
||||
};
|
||||
|
||||
({ $($tt:tt)+ }) => {
|
||||
plist::Value::Dictionary({
|
||||
let mut object = plist::Dictionary::new();
|
||||
$crate::plist_internal!(@object object () ($($tt)+) ($($tt)+));
|
||||
object
|
||||
})
|
||||
};
|
||||
|
||||
// Any type that can be converted to plist::Value: numbers, strings, variables etc.
|
||||
// Must be below every other rule.
|
||||
($other:expr) => {
|
||||
$crate::plist_macro::plist_to_value($other)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! plist_unexpected {
|
||||
() => {};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! plist_expect_expr_comma {
|
||||
($e:expr , $($tt:tt)*) => {};
|
||||
}
|
||||
|
||||
// Helper function to convert various types to plist::Value
|
||||
#[doc(hidden)]
|
||||
pub fn plist_to_value<T: PlistConvertible>(value: T) -> plist::Value {
|
||||
value.to_plist_value()
|
||||
}
|
||||
|
||||
// Trait for types that can be converted to plist::Value
|
||||
pub trait PlistConvertible {
|
||||
fn to_plist_value(self) -> plist::Value;
|
||||
}
|
||||
|
||||
// Implementations for common types
|
||||
impl PlistConvertible for plist::Value {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for String {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::String(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for &str {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for i16 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Integer(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for i32 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Integer(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for i64 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Integer(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for u16 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Integer((self as i64).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for u32 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Integer((self as i64).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for u64 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Integer((self as i64).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for f32 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Real(self as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for f64 {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Real(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for bool {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Boolean(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PlistConvertible for std::borrow::Cow<'a, str> {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::String(self.into_owned())
|
||||
}
|
||||
}
|
||||
impl PlistConvertible for Vec<u8> {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Data(self)
|
||||
}
|
||||
}
|
||||
impl PlistConvertible for &[u8] {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Data(self.to_vec())
|
||||
}
|
||||
}
|
||||
impl PlistConvertible for std::time::SystemTime {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Date(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PlistConvertible> PlistConvertible for Vec<T> {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PlistConvertible + Clone> PlistConvertible for &[T] {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Array(
|
||||
self.iter()
|
||||
.map(|item| item.clone().to_plist_value())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PlistConvertible + Clone, const N: usize> PlistConvertible for [T; N] {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Array(self.into_iter().map(|item| item.to_plist_value()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PlistConvertible + Clone, const N: usize> PlistConvertible for &[T; N] {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Array(
|
||||
self.iter()
|
||||
.map(|item| item.clone().to_plist_value())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PlistConvertible for plist::Dictionary {
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
plist::Value::Dictionary(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> PlistConvertible for std::collections::HashMap<K, V>
|
||||
where
|
||||
K: Into<String>,
|
||||
V: PlistConvertible,
|
||||
{
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
let mut dict = plist::Dictionary::new();
|
||||
for (key, value) in self {
|
||||
dict.insert(key.into(), value.to_plist_value());
|
||||
}
|
||||
plist::Value::Dictionary(dict)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> PlistConvertible for std::collections::BTreeMap<K, V>
|
||||
where
|
||||
K: Into<String>,
|
||||
V: PlistConvertible,
|
||||
{
|
||||
fn to_plist_value(self) -> plist::Value {
|
||||
let mut dict = plist::Dictionary::new();
|
||||
for (key, value) in self {
|
||||
dict.insert(key.into(), value.to_plist_value());
|
||||
}
|
||||
plist::Value::Dictionary(dict)
|
||||
}
|
||||
}
|
||||
|
||||
// Treat plain T as Some(T) and Option<T> as-is.
|
||||
pub trait MaybePlist {
|
||||
fn into_option_value(self) -> Option<plist::Value>;
|
||||
}
|
||||
|
||||
impl<T: PlistConvertible> MaybePlist for T {
|
||||
fn into_option_value(self) -> Option<plist::Value> {
|
||||
Some(self.to_plist_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PlistConvertible> MaybePlist for Option<T> {
|
||||
fn into_option_value(self) -> Option<plist::Value> {
|
||||
self.map(|v| v.to_plist_value())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn plist_maybe<T: MaybePlist>(v: T) -> Option<plist::Value> {
|
||||
v.into_option_value()
|
||||
}
|
||||
|
||||
// Convert things into a Dictionary we can merge.
|
||||
pub trait IntoPlistDict {
|
||||
fn into_plist_dict(self) -> plist::Dictionary;
|
||||
}
|
||||
|
||||
impl IntoPlistDict for plist::Dictionary {
|
||||
fn into_plist_dict(self) -> plist::Dictionary {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPlistDict for plist::Value {
|
||||
fn into_plist_dict(self) -> plist::Dictionary {
|
||||
match self {
|
||||
plist::Value::Dictionary(d) => d,
|
||||
other => panic!("plist :< expects a dictionary, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> IntoPlistDict for std::collections::HashMap<K, V>
|
||||
where
|
||||
K: Into<String>,
|
||||
V: PlistConvertible,
|
||||
{
|
||||
fn into_plist_dict(self) -> plist::Dictionary {
|
||||
let mut d = plist::Dictionary::new();
|
||||
for (k, v) in self {
|
||||
d.insert(k.into(), v.to_plist_value());
|
||||
}
|
||||
d
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> IntoPlistDict for std::collections::BTreeMap<K, V>
|
||||
where
|
||||
K: Into<String>,
|
||||
V: PlistConvertible,
|
||||
{
|
||||
fn into_plist_dict(self) -> plist::Dictionary {
|
||||
let mut d = plist::Dictionary::new();
|
||||
for (k, v) in self {
|
||||
d.insert(k.into(), v.to_plist_value());
|
||||
}
|
||||
d
|
||||
}
|
||||
}
|
||||
|
||||
// Optional version: T or Option<T>.
|
||||
pub trait MaybeIntoPlistDict {
|
||||
fn into_option_plist_dict(self) -> Option<plist::Dictionary>;
|
||||
}
|
||||
impl<T: IntoPlistDict> MaybeIntoPlistDict for T {
|
||||
fn into_option_plist_dict(self) -> Option<plist::Dictionary> {
|
||||
Some(self.into_plist_dict())
|
||||
}
|
||||
}
|
||||
impl<T: IntoPlistDict> MaybeIntoPlistDict for Option<T> {
|
||||
fn into_option_plist_dict(self) -> Option<plist::Dictionary> {
|
||||
self.map(|t| t.into_plist_dict())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn maybe_into_dict<T: MaybeIntoPlistDict>(v: T) -> Option<plist::Dictionary> {
|
||||
v.into_option_plist_dict()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_plist_macro_basic() {
|
||||
let value = plist!({
|
||||
"name": "test",
|
||||
"count": 42,
|
||||
"active": true,
|
||||
"items": ["a", ?"b", "c"]
|
||||
});
|
||||
|
||||
if let plist::Value::Dictionary(dict) = value {
|
||||
assert_eq!(
|
||||
dict.get("name"),
|
||||
Some(&plist::Value::String("test".to_string()))
|
||||
);
|
||||
assert_eq!(dict.get("count"), Some(&plist::Value::Integer(42.into())));
|
||||
assert_eq!(dict.get("active"), Some(&plist::Value::Boolean(true)));
|
||||
} else {
|
||||
panic!("Expected dictionary");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plist_macro_with_variables() {
|
||||
let name = "dynamic";
|
||||
let count = 100;
|
||||
let items = vec!["x", "y"];
|
||||
let none: Option<u64> = None;
|
||||
|
||||
let to_merge = plist!({
|
||||
"reee": "cool beans"
|
||||
});
|
||||
let maybe_merge = Some(plist!({
|
||||
"yeppers": "what did I say about yeppers",
|
||||
"replace me": 2,
|
||||
}));
|
||||
let value = plist!({
|
||||
"name": name,
|
||||
"count": count,
|
||||
"items": items,
|
||||
"omit me":? none,
|
||||
"keep me":? Some(123),
|
||||
"replace me": 1,
|
||||
:< to_merge,
|
||||
:<? maybe_merge
|
||||
});
|
||||
|
||||
if let plist::Value::Dictionary(dict) = value {
|
||||
assert_eq!(
|
||||
dict.get("name"),
|
||||
Some(&plist::Value::String("dynamic".to_string()))
|
||||
);
|
||||
assert_eq!(dict.get("count"), Some(&plist::Value::Integer(100.into())));
|
||||
assert!(dict.get("omit me").is_none());
|
||||
assert_eq!(
|
||||
dict.get("keep me"),
|
||||
Some(&plist::Value::Integer(123.into()))
|
||||
);
|
||||
assert_eq!(
|
||||
dict.get("reee"),
|
||||
Some(&plist::Value::String("cool beans".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
dict.get("yeppers"),
|
||||
Some(&plist::Value::String(
|
||||
"what did I say about yeppers".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
dict.get("replace me"),
|
||||
Some(&plist::Value::Integer(2.into()))
|
||||
);
|
||||
} else {
|
||||
panic!("Expected dictionary");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use plist_macro::plist_to_xml_bytes;
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
|
||||
@@ -216,7 +217,7 @@ impl<R: ReadWrite> AppServiceClient<R> {
|
||||
"user": {
|
||||
"active": true,
|
||||
},
|
||||
"platformSpecificOptions": plist::Value::Data(crate::util::plist_to_xml_bytes(&platform_options.unwrap_or_default())),
|
||||
"platformSpecificOptions": plist::Value::Data(plist_to_xml_bytes(&platform_options.unwrap_or_default())),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
//! - Handle cryptographic signing operations
|
||||
|
||||
use plist::Value;
|
||||
use plist_macro::plist_to_xml_bytes;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::{IdeviceError, util::plist_to_xml_bytes};
|
||||
use crate::IdeviceError;
|
||||
|
||||
/// TSS client version string sent in requests
|
||||
const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2";
|
||||
@@ -30,7 +31,7 @@ impl TSSRequest {
|
||||
/// - Client version string
|
||||
/// - Random UUID for request identification
|
||||
pub fn new() -> Self {
|
||||
let inner = crate::plist!(dict {
|
||||
let inner = plist_macro::plist!(dict {
|
||||
"@HostPlatformInfo": "mac",
|
||||
"@VersionInfo": TSS_CLIENT_VERSION_STRING,
|
||||
"@UUID": uuid::Uuid::new_v4().to_string().to_uppercase()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use crate::util::plist_to_xml_bytes;
|
||||
use plist_macro::plist_to_xml_bytes;
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
//! Utility Functions
|
||||
//!
|
||||
//! Provides helper functions for working with Apple's Property List (PLIST) format,
|
||||
//! including serialization and pretty-printing utilities.
|
||||
#![allow(dead_code)] // functions might not be used by all features
|
||||
|
||||
use plist::Value;
|
||||
|
||||
/// Converts a PLIST dictionary to XML-formatted bytes
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `p` - The PLIST dictionary to serialize
|
||||
///
|
||||
/// # Returns
|
||||
/// A byte vector containing the XML representation
|
||||
///
|
||||
/// # Panics
|
||||
/// Will panic if serialization fails (should only happen with invalid data)
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// let mut dict = plist::Dictionary::new();
|
||||
/// dict.insert("key".into(), "value".into());
|
||||
/// let xml_bytes = plist_to_xml_bytes(&dict);
|
||||
/// ```
|
||||
pub fn plist_to_xml_bytes(p: &plist::Dictionary) -> Vec<u8> {
|
||||
let buf = Vec::new();
|
||||
let mut writer = std::io::BufWriter::new(buf);
|
||||
plist::to_writer_xml(&mut writer, &p).unwrap();
|
||||
|
||||
writer.into_inner().unwrap()
|
||||
}
|
||||
|
||||
/// Pretty-prints a PLIST value with indentation
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `p` - The PLIST value to format
|
||||
///
|
||||
/// # Returns
|
||||
/// A formatted string representation
|
||||
pub fn pretty_print_plist(p: &Value) -> String {
|
||||
print_plist(p, 0)
|
||||
}
|
||||
|
||||
/// Pretty-prints a PLIST dictionary with key-value pairs
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `dict` - The dictionary to format
|
||||
///
|
||||
/// # Returns
|
||||
/// A formatted string representation with newlines and indentation
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// let mut dict = plist::Dictionary::new();
|
||||
/// dict.insert("name".into(), "John".into());
|
||||
/// dict.insert("age".into(), 30.into());
|
||||
/// println!("{}", pretty_print_dictionary(&dict));
|
||||
/// ```
|
||||
pub fn pretty_print_dictionary(dict: &plist::Dictionary) -> String {
|
||||
let items: Vec<String> = dict
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}: {}", k, print_plist(v, 2)))
|
||||
.collect();
|
||||
format!("{{\n{}\n}}", items.join(",\n"))
|
||||
}
|
||||
|
||||
/// Internal recursive function for printing PLIST values with indentation
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `p` - The PLIST value to format
|
||||
/// * `indentation` - Current indentation level
|
||||
///
|
||||
/// # Returns
|
||||
/// Formatted string representation
|
||||
fn print_plist(p: &Value, indentation: usize) -> String {
|
||||
let indent = " ".repeat(indentation);
|
||||
match p {
|
||||
Value::Array(vec) => {
|
||||
let items: Vec<String> = vec
|
||||
.iter()
|
||||
.map(|v| {
|
||||
format!(
|
||||
"{}{}",
|
||||
" ".repeat(indentation + 2),
|
||||
print_plist(v, indentation + 2)
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
format!("[\n{}\n{}]", items.join(",\n"), indent)
|
||||
}
|
||||
Value::Dictionary(dict) => {
|
||||
let items: Vec<String> = dict
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
format!(
|
||||
"{}{}: {}",
|
||||
" ".repeat(indentation + 2),
|
||||
k,
|
||||
print_plist(v, indentation + 2)
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
format!("{{\n{}\n{}}}", items.join(",\n"), indent)
|
||||
}
|
||||
Value::Boolean(b) => format!("{b}"),
|
||||
Value::Data(vec) => {
|
||||
let len = vec.len();
|
||||
let preview: String = vec
|
||||
.iter()
|
||||
.take(20)
|
||||
.map(|b| format!("{b:02X}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
if len > 20 {
|
||||
format!("Data({preview}... Len: {len})")
|
||||
} else {
|
||||
format!("Data({preview} Len: {len})")
|
||||
}
|
||||
}
|
||||
Value::Date(date) => format!("Date({})", date.to_xml_format()),
|
||||
Value::Real(f) => format!("{f}"),
|
||||
Value::Integer(i) => format!("{i}"),
|
||||
Value::String(s) => format!("\"{s}\""),
|
||||
Value::Uid(_uid) => "Uid(?)".to_string(),
|
||||
_ => "Unknown".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! obf {
|
||||
($lit:literal) => {{
|
||||
#[cfg(feature = "obfuscate")]
|
||||
{
|
||||
std::borrow::Cow::Owned(obfstr::obfstr!($lit).to_string())
|
||||
}
|
||||
#[cfg(not(feature = "obfuscate"))]
|
||||
{
|
||||
std::borrow::Cow::Borrowed($lit)
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::{io::Cursor, path::Path};
|
||||
|
||||
use async_zip::base::read::seek::ZipFileReader;
|
||||
use futures::AsyncReadExt as _;
|
||||
use plist_macro::plist;
|
||||
use std::{io::Cursor, path::Path};
|
||||
use tokio::io::{AsyncBufRead, AsyncSeek, BufReader};
|
||||
|
||||
use crate::{
|
||||
IdeviceError, IdeviceService,
|
||||
afc::{AfcClient, opcode::AfcFopenMode},
|
||||
plist,
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use plist_macro::plist;
|
||||
use std::{
|
||||
ffi::CString,
|
||||
io::{BufRead, Cursor, Read},
|
||||
@@ -169,7 +170,7 @@ impl XPCObject {
|
||||
plist::Value::Dictionary(dict)
|
||||
}
|
||||
Self::FileTransfer { msg_id, data } => {
|
||||
crate::plist!({
|
||||
plist!({
|
||||
"msg_id": *msg_id,
|
||||
"data": data.to_plist(),
|
||||
})
|
||||
|
||||
@@ -157,6 +157,7 @@ sha2 = { version = "0.10" }
|
||||
ureq = { version = "3" }
|
||||
clap = { version = "4.5" }
|
||||
plist = { version = "1.7" }
|
||||
plist-macro = { version = "0.1" }
|
||||
ns-keyed-archive = "0.1.2"
|
||||
uuid = "1.16"
|
||||
futures-util = { version = "0.3" }
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
use clap::{Arg, Command, arg};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, companion_proxy::CompanionProxy,
|
||||
core_device_proxy::CoreDeviceProxy, pretty_print_dictionary, pretty_print_plist,
|
||||
rsd::RsdHandshake,
|
||||
core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake,
|
||||
};
|
||||
use plist_macro::{pretty_print_dictionary, pretty_print_plist};
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command, arg};
|
||||
use idevice::{IdeviceService, lockdown::LockdownClient, pretty_print_plist};
|
||||
use idevice::{IdeviceService, lockdown::LockdownClient};
|
||||
use plist::Value;
|
||||
use plist_macro::pretty_print_plist;
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
use std::{io::Write, path::PathBuf};
|
||||
|
||||
use clap::{Arg, Command, arg, value_parser};
|
||||
use idevice::{
|
||||
IdeviceService, lockdown::LockdownClient, mobile_image_mounter::ImageMounter,
|
||||
pretty_print_plist,
|
||||
};
|
||||
use idevice::{IdeviceService, lockdown::LockdownClient, mobile_image_mounter::ImageMounter};
|
||||
use plist_macro::pretty_print_plist;
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, pretty_print_dictionary,
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy,
|
||||
restore_service::RestoreServiceClient, rsd::RsdHandshake,
|
||||
};
|
||||
use plist_macro::pretty_print_dictionary;
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user