Skip to content
Open
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
4 changes: 4 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ env_logger = { version = "0.11.5", default-features = false }
field-offset = "0.3.3"
filetime = "0.2.18"
flate2 = "1.0.34"
zstd = "0.13.2"
fnv = "1.0.3"
fs_extra = { version = "1.3.0" }
futures = "0.3.30"
Expand Down
2 changes: 2 additions & 0 deletions lib/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,15 @@ reqwest = { workspace = true, default-features = false, features = [
"json",
"multipart",
"gzip",
"zstd",
] }

[target.'cfg(any(target_arch = "riscv64", target_arch = "loongarch64"))'.dependencies]
reqwest = { workspace = true, default-features = false, features = [
"native-tls",
"json",
"multipart",
"zstd",
] }


Expand Down
5 changes: 4 additions & 1 deletion lib/cli/src/commands/package/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,10 @@ impl PackageDownload {

let b = client
.get(download_url)
.header(http::header::ACCEPT, "application/webc");
.header(http::header::ACCEPT, "application/webc")
// NOTE: reqwest handles gzip/zstd decoding the respone body
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling errors in comment: "respone" should be "response", and there's an extra space between "decoding" and "the".

Suggested change
// NOTE: reqwest handles gzip/zstd decoding the respone body
// NOTE: reqwest handles gzip/zstd decoding the response body

Copilot uses AI. Check for mistakes.
// automatically when the relevant features are enabled.
.header(http::header::ACCEPT_ENCODING, "gzip, zstd");

pb.println(format!(
"{} {DOWNLOADING_PACKAGE_EMOJI}Downloading package {ident} ...",
Expand Down
2 changes: 2 additions & 0 deletions lib/wasix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ bytecheck.workspace = true
blake3.workspace = true
petgraph.workspace = true
lz4_flex.workspace = true
flate2.workspace = true
rayon = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
js-sys = { workspace = true, optional = true }
Expand Down Expand Up @@ -152,6 +153,7 @@ windows-sys = { workspace = true, features = [

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
terminal_size.workspace = true
zstd.workspace = true

[dev-dependencies]
wasmer = { path = "../api", version = "=7.0.0-alpha.2", default-features = false, features = [
Expand Down
59 changes: 57 additions & 2 deletions lib/wasix/src/runtime/package_loader/builtin_loader.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::{
collections::HashMap,
fmt::Write as _,
io::{ErrorKind, Write as _},
io::{ErrorKind, Read, Write as _},
path::PathBuf,
sync::{Arc, RwLock},
};

use anyhow::{Context, Error};
use anyhow::{Context, Error, bail};
use bytes::Bytes;
use http::{HeaderMap, Method};
use tempfile::NamedTempFile;
Expand Down Expand Up @@ -284,6 +284,8 @@ impl BuiltinPackageLoader {
}

let body = response.body.context("package download failed")?;
let body = Self::decode_response_body(&response.headers, body)
.context("package download failed: could not decode response body")?;
tracing::debug!(%url, "package_download_succeeded");

let body = bytes::Bytes::from(body);
Expand All @@ -298,6 +300,12 @@ impl BuiltinPackageLoader {
headers.insert("Accept", "application/webc".parse().unwrap());
headers.insert("User-Agent", USER_AGENT.parse().unwrap());

// Accept compressed responses.
// NOTE: gzip and zstd decoding is available on native platforms.
// In browser platforms, the fetch implementation should automatically
// handle decoding of gzip/zstd responses transparently.
headers.insert(http::header::ACCEPT_ENCODING, "gzip, zstd".parse().unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the qvalue weights to prefer ZSTD if possible?
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Encoding#q


if url.has_authority()
&& let Some(token) = self.tokens.get(url.authority())
{
Expand All @@ -317,6 +325,53 @@ impl BuiltinPackageLoader {

headers
}

fn decode_response_body(headers: &HeaderMap, body: Vec<u8>) -> Result<Vec<u8>, anyhow::Error> {
let encodings = match headers.get(http::header::CONTENT_ENCODING) {
Some(header) => header
.to_str()
.context("invalid content-encoding header")?
.split(',')
.map(|encoding| encoding.trim().to_ascii_lowercase())
.filter(|encoding| !encoding.is_empty())
.collect::<Vec<_>>(),
None => Vec::new(),
};

if encodings.is_empty() || (encodings.len() == 1 && encodings[0] == "identity") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly_one from Itertools?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at_most_one might work well.

return Ok(body);
}

let mut reader: Box<dyn Read> = Box::new(std::io::Cursor::new(body));
for encoding in encodings.iter().rev() {
match encoding.as_str() {
"gzip" => {
reader = Box::new(flate2::read::GzDecoder::new(reader));
}
"zstd" => {
#[cfg(not(target_arch = "wasm32"))]
{
reader = Box::new(
zstd::stream::read::Decoder::new(reader)
.context("failed to initialize zstd decoder")?,
);
}
#[cfg(target_arch = "wasm32")]
{
bail!("zstd content-encoding is not supported on wasm32");
}
}
"identity" => {}
other => bail!("unsupported content-encoding: {other}"),
}
}

let mut decoded = Vec::new();
reader
.read_to_end(&mut decoded)
.context("failed to decode response body")?;
Ok(decoded)
}
Comment on lines +329 to +374
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new decode_response_body function lacks test coverage. Consider adding tests to verify:

  1. Decoding of gzip-encoded responses
  2. Decoding of zstd-encoded responses (on non-wasm32 platforms)
  3. Handling of multiple encodings (e.g., "gzip, zstd")
  4. Handling of the "identity" encoding
  5. Error handling for unsupported encodings
  6. Error handling for invalid content-encoding headers

Copilot uses AI. Check for mistakes.
}

impl Default for BuiltinPackageLoader {
Expand Down
Loading