Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 30 additions & 44 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ tokio = "1"
url = "2.5"
uuid = { version = "1", features = ["v4"] }
windows-sys = "0.61.2"
zip = { version = "8", default-features = false, features = ["deflate"] }
zip = { version = "9.0.5", default-features = false, features = ["deflate"] }
33 changes: 32 additions & 1 deletion crates/codex-theme-engine/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,14 @@ fn extract_and_place(
let entry = archive
.by_index(index)
.map_err(|e| err(format!("读取压缩包条目失败: {e}")))?;
let entry_name = entry
.name()
.map_err(|e| err(format!("读取压缩包条目名称失败: {e}")))?;
let Some(relative) = entry
.enclosed_name()
.and_then(|p| safe_entry_path(p.to_string_lossy().as_ref()))
else {
return Err(err(format!("主题包含非法路径条目: {}", entry.name())));
return Err(err(format!("主题包含非法路径条目: {entry_name}")));
};
if entry.is_dir() {
std::fs::create_dir_all(staging.join(&relative))
Expand Down Expand Up @@ -221,6 +224,34 @@ mod tests {
assert!(error.contains("theme.json"), "{error}");
}

#[test]
fn imports_legacy_cp437_entry_names() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("themes");
let skin = tmp.path().join("cp437-name.codexskin");
let mut entries = minimal_entries("cp437-name");
entries.push(("cafe.bin".to_string(), b"legacy filename".to_vec()));
let refs: Vec<(&str, &[u8])> =
entries.iter().map(|(n, b)| (n.as_str(), b.as_slice())).collect();
write_zip(&skin, &refs);
let mut bytes = std::fs::read(&skin).unwrap();
let name = b"cafe.bin";
let mut replaced = 0;
for index in 0..=bytes.len() - name.len() {
if bytes[index..index + name.len()] == *name {
bytes[index + 3] = 0x82; // CP437 encodes é as one byte.
replaced += 1;
}
}
assert_eq!(replaced, 2, "update local and central directory names");
std::fs::write(&skin, bytes).unwrap();
assert_eq!(import_codexskin(&skin, &root).unwrap().id, "cp437-name");
assert_eq!(
std::fs::read(root.join("cp437-name/café.bin")).unwrap(),
b"legacy filename"
);
}

#[test]
fn rejects_invalid_package_content() {
let tmp = tempfile::tempdir().unwrap();
Expand Down
5 changes: 4 additions & 1 deletion crates/codex-win-engine/src/app_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ pub fn read_codex_app_version_from_msix(path: &Path) -> Option<String> {
let Ok(mut entry) = archive.by_index(index) else {
continue;
};
let normalized = entry.name().replace('\\', "/").to_ascii_lowercase();
let normalized = match entry.name() {
Ok(name) => name.replace('\\', "/").to_ascii_lowercase(),
Err(_) => continue,
};
if normalized != "resources/app.asar" && !normalized.ends_with("/resources/app.asar") {
continue;
}
Expand Down
37 changes: 15 additions & 22 deletions crates/codex-win-engine/src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ mod tests {
}

#[test]
fn download_with_progress_reports_final_size() {
fn download_rejects_file_urls() {
if hidden_command(curl_exe())
.arg("--version")
.output()
Expand All @@ -618,29 +618,22 @@ mod tests {
return;
}

let root =
std::env::temp_dir().join(format!("codex-win-engine-progress-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let root = std::env::temp_dir().join(format!(
"codex-win-engine-file-url-{}",
uuid::Uuid::new_v4()
));
std::fs::create_dir_all(&root).unwrap();
let source = root.join("source.bin");
let dest = root.join("dest.bin");
let bytes = vec![0x4d; 1024 * 1024];
std::fs::write(&source, &bytes).unwrap();

let seen = std::sync::Mutex::new(Vec::new());
let result = download_to_with_progress(&file_url(&source), &dest, &|downloaded| {
seen.lock().unwrap().push(downloaded);
});
if let Err(EngineError::Io(message)) = &result {
if message.contains("Protocol \"file\" disabled") {
let _ = std::fs::remove_dir_all(&root);
return;
}
}
result.unwrap();

assert_eq!(std::fs::read(&dest).unwrap(), bytes);
assert!(seen.lock().unwrap().contains(&(1024 * 1024)));
let _ = std::fs::remove_dir_all(&root);
std::fs::write(&source, b"local file must not be downloaded").unwrap();

// Production downloads and redirects are HTTPS-only. A file:// fixture
// cannot exercise successful transfer progress through this path.
let error = download_to_with_progress(&file_url(&source), &dest, &|_| {})
.unwrap_err();
let message = error.to_string().to_ascii_lowercase();
assert!(message.contains("protocol") && message.contains("file"), "{error}");
assert!(!dest.exists(), "a rejected URL must not create the destination");
std::fs::remove_dir_all(root).unwrap();
}
}
36 changes: 35 additions & 1 deletion crates/codex-win-engine/src/portable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,10 @@ fn extract_msix(msix_path: &Path, dest: &Path) -> Result<String, EngineError> {
let mut file = zip
.by_index(idx)
.map_err(|e| EngineError::Msix(format!("read zip entry {idx}: {e}")))?;
let zip_name = file.name().to_string();
let zip_name = file
.name()
.map_err(|e| EngineError::Msix(format!("decode zip entry {idx} name: {e}")))?
.into_owned();
let (decoded_components, was_percent_encoded) = decode_zip_entry_path(&zip_name)?;
if file.is_dir() {
continue;
Expand Down Expand Up @@ -2549,6 +2552,37 @@ mod tests {
zip.finish().unwrap();
}

#[test]
fn extracts_legacy_cp437_zip_entry_names() {
let root = temp_test_dir("cp437-zip-entry-name");
let msix = root.join("codex.msix");
let extracted = root.join("extracted");
fs::create_dir_all(&extracted).unwrap();
let payload = b"legacy filename";
write_block_mapped_msix(
&msix,
&[("café.bin", payload.len() as u64)],
&[("cafe.bin", payload)],
);
let mut bytes = fs::read(&msix).unwrap();
let name = b"cafe.bin";
let mut replaced = 0;
for index in 0..=bytes.len() - name.len() {
if bytes[index..index + name.len()] == *name {
bytes[index + 3] = 0x82; // CP437 encodes é as one byte.
replaced += 1;
}
}
assert_eq!(replaced, 2, "update local and central directory names");
fs::write(&msix, bytes).unwrap();
extract_msix(&msix, &extracted).unwrap();
assert_eq!(
fs::read(extracted.join("café.bin")).unwrap(),
b"legacy filename"
);
fs::remove_dir_all(root).unwrap();
}

#[test]
fn extracts_percent_encoded_payload_to_block_map_logical_paths() {
let root = temp_test_dir("block-map-logical-paths");
Expand Down
5 changes: 4 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ tauri-plugin-autostart = "2"
tauri-plugin-dialog = "2.7.1"
tauri-plugin-log = "2"
tauri-plugin-single-instance = "2"
tauri-plugin-updater = "2"
# Releases use raw signed NSIS executables on Windows. Keep TLS and proxy
# support without the legacy ZIP installer parser; macOS tar.gz support is
# unconditional in the updater's macOS dependencies.
tauri-plugin-updater = { version = "2", default-features = false, features = ["rustls-tls", "system-proxy"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["sync", "time"] }
url.workspace = true
Expand Down
Loading
Loading