Skip to main content

actr_pack/
load.rs

1use std::io::Cursor;
2
3use crate::error::PackError;
4use crate::manifest::PackageManifest;
5use crate::util::read_zip_entry;
6
7/// Read the manifest from an .actr package without full verification.
8pub fn read_manifest(actr_bytes: &[u8]) -> Result<PackageManifest, PackError> {
9    let manifest_str = read_manifest_raw(actr_bytes)?;
10    PackageManifest::from_toml(&manifest_str)
11}
12
13/// Read the raw manifest TOML string from an .actr package.
14///
15/// Returns the exact bytes stored in the package as a UTF-8 string,
16/// preserving the original text for signing purposes.
17pub fn read_manifest_raw(actr_bytes: &[u8]) -> Result<String, PackError> {
18    let cursor = Cursor::new(actr_bytes);
19    let mut archive = zip::ZipArchive::new(cursor)?;
20
21    let manifest_bytes =
22        read_zip_entry(&mut archive, "manifest.toml").map_err(|_| PackError::ManifestNotFound)?;
23
24    String::from_utf8(manifest_bytes)
25        .map_err(|e| PackError::ManifestParseError(format!("manifest is not valid UTF-8: {e}")))
26}
27
28/// Load the binary bytes from an .actr package.
29///
30/// Reads the manifest to determine the binary path, then extracts the binary.
31pub fn load_binary(actr_bytes: &[u8]) -> Result<Vec<u8>, PackError> {
32    let cursor = Cursor::new(actr_bytes);
33    let mut archive = zip::ZipArchive::new(cursor)?;
34
35    // Read manifest to get binary path
36    let manifest_bytes =
37        read_zip_entry(&mut archive, "manifest.toml").map_err(|_| PackError::ManifestNotFound)?;
38    let manifest_str = std::str::from_utf8(&manifest_bytes)
39        .map_err(|e| PackError::ManifestParseError(format!("manifest is not valid UTF-8: {e}")))?;
40    let manifest = PackageManifest::from_toml(manifest_str)?;
41
42    read_zip_entry(&mut archive, &manifest.binary.path)
43        .map_err(|_| PackError::BinaryNotFound(manifest.binary.path.clone()))
44}
45
46/// Read the raw 64-byte Ed25519 signature from an .actr package.
47pub fn read_signature(actr_bytes: &[u8]) -> Result<Vec<u8>, PackError> {
48    let cursor = Cursor::new(actr_bytes);
49    let mut archive = zip::ZipArchive::new(cursor)?;
50    let sig =
51        read_zip_entry(&mut archive, "manifest.sig").map_err(|_| PackError::SignatureNotFound)?;
52    if sig.len() != 64 {
53        return Err(PackError::SignatureVerificationFailed(format!(
54            "manifest.sig must be exactly 64 bytes, got {}",
55            sig.len()
56        )));
57    }
58    Ok(sig)
59}
60
61/// Read manifest.lock.toml from an .actr package (if present).
62///
63/// Returns None if the package does not contain a lock file.
64pub fn read_lock_file(actr_bytes: &[u8]) -> Result<Option<Vec<u8>>, PackError> {
65    let cursor = Cursor::new(actr_bytes);
66    let mut archive = zip::ZipArchive::new(cursor)?;
67    match read_zip_entry(&mut archive, "manifest.lock.toml") {
68        Ok(bytes) => Ok(Some(bytes)),
69        Err(_) => Ok(None),
70    }
71}
72
73/// Read a single JS glue file from the `resources/` directory in an .actr package.
74///
75/// Returns the first `.js` file under `resources/` that is NOT named
76/// `actor.sw.js` (which is the Service Worker runtime itself, not guest
77/// wasm-bindgen glue). Returns `Ok(None)` when the package has no eligible
78/// glue script.
79///
80/// Used by the Web runtime to extract wasm-bindgen glue after signature
81/// verification.
82pub fn read_glue_js(actr_bytes: &[u8]) -> Result<Option<String>, PackError> {
83    use std::io::Read;
84
85    let cursor = Cursor::new(actr_bytes);
86    let mut archive = zip::ZipArchive::new(cursor)?;
87
88    let target = archive
89        .file_names()
90        .find(|name| {
91            name.starts_with("resources/")
92                && name.ends_with(".js")
93                && !name.ends_with("actor.sw.js")
94        })
95        .map(|s| s.to_string());
96
97    let Some(name) = target else {
98        return Ok(None);
99    };
100
101    let mut file = archive
102        .by_name(&name)
103        .map_err(|e| PackError::InvalidPackage(format!("open {}: {}", name, e)))?;
104    let mut content = String::new();
105    file.read_to_string(&mut content)
106        .map_err(|e| PackError::InvalidPackage(format!("read {}: {}", name, e)))?;
107    Ok(Some(content))
108}
109
110/// Read all proto files from the `proto/` directory in an .actr package.
111///
112/// Returns a list of (filename, content) pairs.
113/// Returns an empty vec if the package has no proto files.
114pub fn read_proto_files(actr_bytes: &[u8]) -> Result<Vec<(String, Vec<u8>)>, PackError> {
115    let cursor = Cursor::new(actr_bytes);
116    let archive = zip::ZipArchive::new(cursor)?;
117
118    let proto_names: Vec<String> = archive
119        .file_names()
120        .filter(|name| name.starts_with("proto/") && name.len() > "proto/".len())
121        .map(|s| s.to_string())
122        .collect();
123
124    let mut result = Vec::new();
125    // Re-open archive for reading (can't borrow mutably while iterating names)
126    let cursor2 = Cursor::new(actr_bytes);
127    let mut archive2 = zip::ZipArchive::new(cursor2)?;
128
129    for full_path in proto_names {
130        let filename = full_path
131            .strip_prefix("proto/")
132            .unwrap_or(&full_path)
133            .to_string();
134        let content = read_zip_entry(&mut archive2, &full_path)?;
135        result.push((filename, content));
136    }
137
138    Ok(result)
139}