Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
a74924e
Branches
Tags
main
apple-crates
icloud
src
cloudkit.rs
Refactor and unify a lot of things for another project and implement airdrop support
Dadoum
commited
a74924e
at 2026-10-25 22:15:58
cloudkit.rs
Blame
History
Raw
use crate::account::{CloudKitToken, DSID, MmeAuthToken}; use adi::proxy::{ADIError, ADIResult}; use grandslam::AccountHTTPSession; use reqwest::{Method, RequestBuilder}; use serde::Deserialize; use thiserror::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Database { Private, Shared, Public, } impl Database { pub fn as_str(self) -> &'static str { match self { Database::Private => "private", Database::Shared => "shared", Database::Public => "public", } } } #[derive(Debug, Clone, Copy)] pub struct Container<'lt> { pub identifier: &'lt str, pub bundle_id: &'lt str, pub database: Database, } /// The native Drive container; its records still require PCS decryption. pub const CLOUD_DOCS: Container<'static> = Container { identifier: "com.apple.CloudDocs", bundle_id: "com.apple.clouddocs", database: Database::Private, }; #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(transparent)] pub struct UserID(String); impl From<String> for UserID { fn from(value: String) -> Self { UserID(value) } } impl AsRef<str> for UserID { fn as_ref(&self) -> &str { &self.0 } } #[derive(Debug, Error)] pub enum ContainerInitError { #[error("Cannot generate device authentication data: {0}")] Anisette(#[from] ADIError), #[error("CloudKit initialization failed: {0}")] Network(#[from] reqwest::Error), #[error("Cannot parse CloudKit initialization: {0}")] Parsing(#[from] serde_json::Error), #[error("CloudKit returned an empty user identifier")] EmptyUserID, } #[derive(Deserialize)] struct CKAppInitResponse { #[serde(rename = "cloudKitUserId")] user_id: UserID, } impl Container<'_> { /// Resolve the container-scoped user ID using the iCloud setup credential. /// This does not create zones, recover keys, or register for push notifications. pub async fn initialize( &self, session: &AccountHTTPSession<'_, '_>, ds_id: &DSID, token: &MmeAuthToken, ) -> Result<UserID, ContainerInitError> { let request = session.anisette_request_builder( Method::POST, "https://setup.icloud.com/setup/ck/v1/ckAppInit", )?; let response = self .add_request_headers(request) .header("Accept", "application/json") .basic_auth(ds_id.as_ref(), Some(token.as_ref())) .query(&[("container", self.identifier)]) .send() .await? .error_for_status()? .bytes() .await?; let response: CKAppInitResponse = serde_json::from_slice(&response)?; if response.user_id.0.is_empty() { Err(ContainerInitError::EmptyUserID) } else { Ok(response.user_id) } } fn add_request_headers(&self, request: RequestBuilder) -> RequestBuilder { request .header("X-CloudKit-BundleId", self.bundle_id) .header("X-CloudKit-ContainerId", self.identifier) .header("X-CloudKit-DatabaseScope", self.database.as_str()) .header("X-CloudKit-Environment", "production") .header( "X-Apple-Request-UUID", uuid::Uuid::new_v4().to_string().to_uppercase(), ) } } /// A native CloudKit request context. MMe/PET credentials are not retained here. /// /// Operation bodies use CloudKit's delimited protobuf protocol; this is not the /// developer-facing CloudKit JSON API. Drive records and transfers are not yet /// implemented by this crate. pub struct CloudKitSession<'lt, 'adi, 'container> { pub http_session: AccountHTTPSession<'lt, 'adi>, pub container: Container<'container>, pub user_id: UserID, pub token: CloudKitToken, } impl<'lt, 'adi, 'container> CloudKitSession<'lt, 'adi, 'container> { pub fn new( http_session: AccountHTTPSession<'lt, 'adi>, container: Container<'container>, user_id: UserID, token: CloudKitToken, ) -> Self { CloudKitSession { http_session, container, user_id, token, } } pub fn simple_request_builder(&self, method: Method, url: &str) -> RequestBuilder { self.http_session.simple_request_builder(method, url) } pub fn anisette_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> { self.http_session.anisette_request_builder(method, url) } pub fn account_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> { self.http_session.account_request_builder(method, url) } pub fn cloudkit_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> { Ok(self .container .add_request_headers(self.anisette_request_builder(method, url)?) .header("X-CloudKit-UserId", self.user_id.as_ref()) .header("X-CloudKit-AuthToken", self.token.as_ref()) .header("Accept", "application/x-protobuf") .header( "Content-Type", "application/x-protobuf; messageType=RequestOperation; delimited=true", )) } }