Keep mobile storage paths container-relative
This commit is contained in:
@@ -608,16 +608,23 @@ impl Config {
|
|||||||
if !vault.is_dir() {
|
if !vault.is_dir() {
|
||||||
return Err(ConfigError::VaultIsNotDirectory { path: vault });
|
return Err(ConfigError::VaultIsNotDirectory { path: vault });
|
||||||
}
|
}
|
||||||
let vault = vault
|
|
||||||
.to_str()
|
|
||||||
.ok_or(ConfigError::InvalidField { field: "vault" })?;
|
|
||||||
let mut document = self.document.clone();
|
let mut document = self.document.clone();
|
||||||
let root = document
|
let root = document
|
||||||
.as_table_mut()
|
.as_table_mut()
|
||||||
.ok_or_else(|| ConfigError::Malformed {
|
.ok_or_else(|| ConfigError::Malformed {
|
||||||
path: self.source.clone(),
|
path: self.source.clone(),
|
||||||
})?;
|
})?;
|
||||||
root.insert("vault".to_owned(), toml::Value::String(vault.to_owned()));
|
if vault != self.vault {
|
||||||
|
let base = self
|
||||||
|
.source
|
||||||
|
.parent()
|
||||||
|
.ok_or(ConfigError::InvalidField { field: "source" })?;
|
||||||
|
let stored = vault.strip_prefix(base).unwrap_or(&vault);
|
||||||
|
root.insert(
|
||||||
|
"vault".to_owned(),
|
||||||
|
toml::Value::String(path_text(stored, "vault")?),
|
||||||
|
);
|
||||||
|
}
|
||||||
root.insert(
|
root.insert(
|
||||||
"default_key".to_owned(),
|
"default_key".to_owned(),
|
||||||
toml::Value::String(settings.default_key),
|
toml::Value::String(settings.default_key),
|
||||||
@@ -1152,7 +1159,7 @@ struct RawGitRemote {
|
|||||||
|
|
||||||
fn validate_config(
|
fn validate_config(
|
||||||
source: PathBuf,
|
source: PathBuf,
|
||||||
document: toml::Value,
|
mut document: toml::Value,
|
||||||
raw: RawConfig,
|
raw: RawConfig,
|
||||||
) -> Result<Config, ConfigError> {
|
) -> Result<Config, ConfigError> {
|
||||||
let base = source
|
let base = source
|
||||||
@@ -1162,6 +1169,7 @@ fn validate_config(
|
|||||||
if vault.exists() && !vault.is_dir() {
|
if vault.exists() && !vault.is_dir() {
|
||||||
return Err(ConfigError::VaultIsNotDirectory { path: vault });
|
return Err(ConfigError::VaultIsNotDirectory { path: vault });
|
||||||
}
|
}
|
||||||
|
make_local_path_relative(&mut document, base, &vault, "vault")?;
|
||||||
|
|
||||||
let key_material = resolve_required_path(base, raw.key_material, "key_material")?;
|
let key_material = resolve_required_path(base, raw.key_material, "key_material")?;
|
||||||
if !key_material.exists() {
|
if !key_material.exists() {
|
||||||
@@ -1176,6 +1184,7 @@ fn validate_config(
|
|||||||
}
|
}
|
||||||
let key_material = fs::canonicalize(&key_material)
|
let key_material = fs::canonicalize(&key_material)
|
||||||
.map_err(|_| ConfigError::InvalidKeyMaterial { path: key_material })?;
|
.map_err(|_| ConfigError::InvalidKeyMaterial { path: key_material })?;
|
||||||
|
make_local_path_relative(&mut document, base, &key_material, "key_material")?;
|
||||||
|
|
||||||
let default_key = raw
|
let default_key = raw
|
||||||
.default_key
|
.default_key
|
||||||
@@ -1276,7 +1285,54 @@ fn resolve_required_path(
|
|||||||
if value.as_os_str().is_empty() {
|
if value.as_os_str().is_empty() {
|
||||||
return Err(ConfigError::InvalidField { field });
|
return Err(ConfigError::InvalidField { field });
|
||||||
}
|
}
|
||||||
Ok(resolve_path(base, &value))
|
let resolved = resolve_path(base, &value);
|
||||||
|
if resolved.exists() || !value.is_absolute() {
|
||||||
|
return Ok(resolved);
|
||||||
|
}
|
||||||
|
Ok(relocated_mobile_path(base, &resolved, field).unwrap_or(resolved))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relocated_mobile_path(base: &Path, path: &Path, field: &'static str) -> Option<PathBuf> {
|
||||||
|
let leaf = match field {
|
||||||
|
"vault" => "vault",
|
||||||
|
"key_material" => "keys",
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let suffix = Path::new("Library/Application Support/ironstorage").join(leaf);
|
||||||
|
let is_stale_mobile_path = [
|
||||||
|
Path::new("/private/var/mobile/Containers/Data/Application"),
|
||||||
|
Path::new("/var/mobile/Containers/Data/Application"),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|prefix| {
|
||||||
|
path.strip_prefix(prefix).is_ok_and(|relative| {
|
||||||
|
let mut components = relative.components();
|
||||||
|
components.next().is_some() && components.as_path() == suffix
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let relocated = base.join(leaf);
|
||||||
|
(is_stale_mobile_path && relocated.exists()).then_some(relocated)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_local_path_relative(
|
||||||
|
document: &mut toml::Value,
|
||||||
|
base: &Path,
|
||||||
|
path: &Path,
|
||||||
|
field: &'static str,
|
||||||
|
) -> Result<(), ConfigError> {
|
||||||
|
let Ok(relative) = path.strip_prefix(base) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let root = document
|
||||||
|
.as_table_mut()
|
||||||
|
.ok_or_else(|| ConfigError::Malformed {
|
||||||
|
path: base.join(CONFIG_FILE),
|
||||||
|
})?;
|
||||||
|
root.insert(
|
||||||
|
field.to_owned(),
|
||||||
|
toml::Value::String(path_text(relative, field)?),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_text(path: &Path, field: &'static str) -> Result<String, ConfigError> {
|
fn path_text(path: &Path, field: &'static str) -> Result<String, ConfigError> {
|
||||||
@@ -1649,4 +1705,31 @@ mod tests {
|
|||||||
("ALICE", Some(1_789_000_000), MobileTab::Preferences)
|
("ALICE", Some(1_789_000_000), MobileTab::Preferences)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stale_ios_container_path_follows_the_current_container() {
|
||||||
|
let temporary = tempfile::tempdir().expect("temporary directory");
|
||||||
|
let vault = temporary.path().join("vault");
|
||||||
|
let keys = temporary.path().join("keys");
|
||||||
|
let source = temporary.path().join("config.toml");
|
||||||
|
fs::create_dir(&vault).expect("vault");
|
||||||
|
fs::create_dir(&keys).expect("keys");
|
||||||
|
fs::write(
|
||||||
|
&source,
|
||||||
|
r#"vault = "/private/var/mobile/Containers/Data/Application/OLD-CONTAINER/Library/Application Support/ironstorage/vault"
|
||||||
|
default_key = "ALICE"
|
||||||
|
key_material = "keys"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("config");
|
||||||
|
|
||||||
|
let config = Config::load(Some(&source)).expect("recover moved container");
|
||||||
|
assert_eq!(config.vault(), fs::canonicalize(vault).expect("vault path"));
|
||||||
|
config
|
||||||
|
.update_mobile_tab(MobileTab::Preferences)
|
||||||
|
.expect("persist recovered config");
|
||||||
|
let contents = fs::read_to_string(source).expect("read config");
|
||||||
|
assert!(contents.contains("vault = \"vault\""));
|
||||||
|
assert!(!contents.contains("OLD-CONTAINER"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -842,6 +842,9 @@ mod tests {
|
|||||||
.as_str(),
|
.as_str(),
|
||||||
ALICE_FINGERPRINT
|
ALICE_FINGERPRINT
|
||||||
);
|
);
|
||||||
|
let config = std::fs::read_to_string(fixture.config.source())?;
|
||||||
|
assert!(config.contains("vault = \"vault\""));
|
||||||
|
assert!(config.contains("key_material = \"keys\""));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,8 +873,8 @@ mod tests {
|
|||||||
std::fs::write(
|
std::fs::write(
|
||||||
&config_path,
|
&config_path,
|
||||||
format!(
|
format!(
|
||||||
"vault = {:?}\ndefault_key = {:?}\nkey_material = {:?}\n",
|
"vault = \"vault\"\ndefault_key = {:?}\nkey_material = \"keys\"\n",
|
||||||
vault, ALICE_FINGERPRINT, keys
|
ALICE_FINGERPRINT
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
let config = Config::load(Some(&config_path))?;
|
let config = Config::load(Some(&config_path))?;
|
||||||
|
|||||||
Reference in New Issue
Block a user