mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 19:45:34 +08:00
refactor(database): scope untrusted restore to N/N-1
This commit is contained in:
@@ -1322,7 +1322,9 @@ impl UntrustedScratch {
|
||||
scratch.connection.authorizer(
|
||||
None::<fn(rusqlite::hooks::AuthContext<'_>) -> rusqlite::hooks::Authorization>,
|
||||
);
|
||||
result.map_err(|error| AppError::Database(format!("execute SQL import: {error}")))?;
|
||||
result.map_err(|error| {
|
||||
AppError::InvalidInput(format!("execute untrusted SQL import: {error}"))
|
||||
})?;
|
||||
scratch.finish_input()
|
||||
}
|
||||
|
||||
@@ -1355,6 +1357,10 @@ impl UntrustedScratch {
|
||||
"restore schema version {version} is newer than supported {SCHEMA_VERSION}"
|
||||
)));
|
||||
}
|
||||
// Gate obsolete backups before schema inspection, sanitizing DDL, or
|
||||
// migration dispatch. LocalUpgrade never enters this scratch path and
|
||||
// retains the complete historical in-place migration chain.
|
||||
super::migration_source::require_supported_untrusted_restore_version(version)?;
|
||||
self.drop_untrusted_executable_objects()?;
|
||||
self.connection
|
||||
.set_db_config(DbConfig::SQLITE_DBCONFIG_ENABLE_TRIGGER, true)
|
||||
@@ -1829,16 +1835,19 @@ fn canonical_user_tables(
|
||||
let mut statement = conn
|
||||
.prepare(
|
||||
"SELECT name FROM sqlite_schema
|
||||
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
|
||||
WHERE type = 'table'
|
||||
ORDER BY name",
|
||||
)
|
||||
.map_err(|error| AppError::Database(error.to_string()))?;
|
||||
let tables = statement
|
||||
.query_map([], |row| row.get::<_, String>(0))
|
||||
.map_err(|error| AppError::Database(error.to_string()))?
|
||||
.collect::<Result<std::collections::BTreeSet<_>, _>>()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|error| AppError::Database(error.to_string()))?;
|
||||
Ok(tables)
|
||||
Ok(tables
|
||||
.into_iter()
|
||||
.filter(|name| !super::is_sqlite_internal_table_name(name))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn assert_restore_policy_coverage(
|
||||
@@ -2444,15 +2453,16 @@ impl Database {
|
||||
let name: String = row.get(1).map_err(|e| AppError::Database(e.to_string()))?;
|
||||
let sql: String = row.get(3).map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 跳过 SQLite 内部对象(如 sqlite_sequence)
|
||||
if name.starts_with("sqlite_") {
|
||||
// Skip only the exact internal objects owned by this SQLite build.
|
||||
// Prefix matching would misclassify names such as `sqliteX`.
|
||||
if super::is_sqlite_internal_table_name(&name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
output.push_str(&sql);
|
||||
output.push_str(";\n");
|
||||
|
||||
if obj_type == "table" && !name.starts_with("sqlite_") {
|
||||
if obj_type == "table" && !super::is_sqlite_internal_table_name(&name) {
|
||||
tables.push(name);
|
||||
}
|
||||
}
|
||||
@@ -4008,9 +4018,8 @@ mod tests {
|
||||
})?;
|
||||
let _home_guard = TestHomeGuard::set(test_home.path());
|
||||
|
||||
// The source-spec authority supports exactly v1..v17. A legacy v0
|
||||
// label must fail before migration DDL instead of being interpreted
|
||||
// through the permissive local-upgrade path.
|
||||
// A legacy v0 label must fail at the N/N-1 gate before migration DDL
|
||||
// instead of being interpreted through the local-upgrade path.
|
||||
for (entry_index, entry_point) in [RestoreEntryPoint::Sql, RestoreEntryPoint::Binary]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
@@ -4042,9 +4051,8 @@ mod tests {
|
||||
.expect_err("untrusted v0 is outside the declared source-spec range");
|
||||
assert!(
|
||||
matches!(error, AppError::InvalidInput(_))
|
||||
&& error
|
||||
.to_string()
|
||||
.contains("unsupported restore user_version 0"),
|
||||
&& error.to_string().contains("user_version=0")
|
||||
&& error.to_string().contains("备份版本过旧"),
|
||||
"v0 must fail at source recognition via entry {entry_index}: {error:?}"
|
||||
);
|
||||
}
|
||||
@@ -4095,10 +4103,10 @@ mod tests {
|
||||
assert_eq!(sentinel, (None, 0, 0, 0));
|
||||
}
|
||||
|
||||
// Every supported version is materialized from its exact source spec.
|
||||
// Both supported versions are materialized from their exact source specs.
|
||||
// Each public entry must preserve a real Pi provider and its endpoint;
|
||||
// this cannot pass by stamping a current database with an old version.
|
||||
for version in 1..=SCHEMA_VERSION {
|
||||
// this cannot pass by stamping a v17 database with a v16 label.
|
||||
for version in (SCHEMA_VERSION - 1)..=SCHEMA_VERSION {
|
||||
for (entry_index, entry_point) in [RestoreEntryPoint::Sql, RestoreEntryPoint::Binary]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
@@ -4128,11 +4136,7 @@ mod tests {
|
||||
.contains(&format!("\"migrationVersion\":{version}")),
|
||||
"Pi settings payload was not preserved for v{version}"
|
||||
);
|
||||
assert_eq!(
|
||||
restored.1,
|
||||
if version == 1 { "1.0" } else { "1.25" },
|
||||
"provider migration sentinel v{version}"
|
||||
);
|
||||
assert_eq!(restored.1, "1.25", "provider migration sentinel v{version}");
|
||||
assert_eq!(
|
||||
restored.2,
|
||||
format!("https://pi-endpoint-v{version}.example/v1"),
|
||||
|
||||
Reference in New Issue
Block a user