Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): set correct mimetype for asset protocol streams, #5203 #5536

Merged
merged 4 commits into from Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/asset-protocol-streaming-mime-type.md
@@ -0,0 +1,5 @@
---
"tauri": "patch"
---

Set the correct mimetype when streaming files through `asset:` protocol
14 changes: 7 additions & 7 deletions core/tauri-codegen/src/context.rs
Expand Up @@ -57,7 +57,7 @@ fn map_core_assets(
let mut hasher = Sha256::new();
hasher.update(&script);
let hash = hasher.finalize();
scripts.push(format!("'sha256-{}'", base64::encode(&hash)));
scripts.push(format!("'sha256-{}'", base64::encode(hash)));
}
csp_hashes
.inline_scripts
Expand All @@ -76,7 +76,7 @@ fn map_core_assets(
let hash = hasher.finalize();
csp_hashes
.styles
.push(format!("'sha256-{}'", base64::encode(&hash)));
.push(format!("'sha256-{}'", base64::encode(hash)));
}
}

Expand Down Expand Up @@ -457,7 +457,7 @@ fn ico_icon<P: AsRef<Path>>(
path: P,
) -> Result<TokenStream, EmbeddedAssetsError> {
let path = path.as_ref();
let bytes = std::fs::read(&path)
let bytes = std::fs::read(path)
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
.to_vec();
let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))
Expand Down Expand Up @@ -485,7 +485,7 @@ fn ico_icon<P: AsRef<Path>>(

fn raw_icon<P: AsRef<Path>>(out_dir: &Path, path: P) -> Result<TokenStream, EmbeddedAssetsError> {
let path = path.as_ref();
let bytes = std::fs::read(&path)
let bytes = std::fs::read(path)
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
.to_vec();

Expand All @@ -507,7 +507,7 @@ fn png_icon<P: AsRef<Path>>(
path: P,
) -> Result<TokenStream, EmbeddedAssetsError> {
let path = path.as_ref();
let bytes = std::fs::read(&path)
let bytes = std::fs::read(path)
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e))
.to_vec();
let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
Expand Down Expand Up @@ -537,13 +537,13 @@ fn write_if_changed(out_path: &Path, data: &[u8]) -> std::io::Result<()> {
use std::fs::File;
use std::io::Write;

if let Ok(curr) = std::fs::read(&out_path) {
if let Ok(curr) = std::fs::read(out_path) {
if curr == data {
return Ok(());
}
}

let mut out_file = File::create(&out_path)?;
let mut out_file = File::create(out_path)?;
out_file.write_all(data)
}

Expand Down
4 changes: 2 additions & 2 deletions core/tauri-utils/src/config.rs
Expand Up @@ -2981,7 +2981,7 @@ mod build {

tokens.append_all(match self {
Self::App(path) => {
let path = path_buf_lit(&path);
let path = path_buf_lit(path);
quote! { #prefix::App(#path) }
}
Self::External(url) => {
Expand Down Expand Up @@ -3211,7 +3211,7 @@ mod build {
quote! { #prefix::OfflineInstaller { silent: #silent } }
}
Self::FixedRuntime { path } => {
let path = path_buf_lit(&path);
let path = path_buf_lit(path);
quote! { #prefix::FixedRuntime { path: #path } }
}
})
Expand Down
8 changes: 5 additions & 3 deletions core/tauri/src/api/file/extract.rs
Expand Up @@ -15,7 +15,7 @@ pub enum ArchiveReader<R: Read + Seek> {
/// A plain reader.
Plain(R),
/// A GZ- compressed reader (decoder).
GzCompressed(flate2::read::GzDecoder<R>),
GzCompressed(Box<flate2::read::GzDecoder<R>>),
}

impl<R: Read + Seek> Read for ArchiveReader<R> {
Expand Down Expand Up @@ -161,7 +161,9 @@ impl<'a, R: Read + Seek> Extract<'a, R> {
};
Extract {
reader: match compression {
Some(Compression::Gz) => ArchiveReader::GzCompressed(flate2::read::GzDecoder::new(reader)),
Some(Compression::Gz) => {
ArchiveReader::GzCompressed(Box::new(flate2::read::GzDecoder::new(reader)))
}
_ => ArchiveReader::Plain(reader),
},
archive_format,
Expand Down Expand Up @@ -248,7 +250,7 @@ impl<'a, R: Read + Seek> Extract<'a, R> {
fs::create_dir_all(&out_path)?;
} else {
if let Some(out_path_parent) = out_path.parent() {
fs::create_dir_all(&out_path_parent)?;
fs::create_dir_all(out_path_parent)?;
}
let mut out_file = fs::File::create(&out_path)?;
io::copy(&mut file, &mut out_file)?;
Expand Down
2 changes: 1 addition & 1 deletion core/tauri/src/api/file/file_move.rs
Expand Up @@ -98,7 +98,7 @@ fn walkdir_and_copy(source: &path::Path, dest: &path::Path) -> crate::api::Resul

let element = entry?;
let metadata = element.metadata()?;
let destination = dest.join(element.path().strip_prefix(&source)?);
let destination = dest.join(element.path().strip_prefix(source)?);

// we make sure it's a directory and destination doesnt exist
if metadata.is_dir() && !&destination.exists() {
Expand Down
4 changes: 2 additions & 2 deletions core/tauri/src/api/process/command.rs
Expand Up @@ -436,7 +436,7 @@ mod test {
#[test]
fn test_cmd_output() {
// create a command to run cat.
let cmd = Command::new("cat").args(&["test/api/test.txt"]);
let cmd = Command::new("cat").args(["test/api/test.txt"]);
let (mut rx, _) = cmd.spawn().unwrap();

crate::async_runtime::block_on(async move {
Expand All @@ -458,7 +458,7 @@ mod test {
#[test]
// test the failure case
fn test_cmd_fail() {
let cmd = Command::new("cat").args(&["test/api/"]);
let cmd = Command::new("cat").args(["test/api/"]);
let (mut rx, _) = cmd.spawn().unwrap();

crate::async_runtime::block_on(async move {
Expand Down
2 changes: 1 addition & 1 deletion core/tauri/src/endpoints/http.rs
Expand Up @@ -103,7 +103,7 @@ impl Cmd {
} = value
{
if crate::api::file::SafePathBuf::new(path.clone()).is_err()
|| !scopes.fs.is_allowed(&path)
|| !scopes.fs.is_allowed(path)
{
return Err(crate::Error::PathNotAllowed(path.clone()).into_anyhow());
}
Expand Down
111 changes: 88 additions & 23 deletions core/tauri/src/manager.rs
Expand Up @@ -541,23 +541,39 @@ impl<R: Runtime> WindowManager<R> {
.get("range")
.and_then(|r| r.to_str().map(|r| r.to_string()).ok())
{
let (headers, status_code, data) = crate::async_runtime::safe_block_on(async move {
let mut headers = HashMap::new();
let mut buf = Vec::new();
#[derive(Default)]
struct RangeMetadata {
file: Option<tokio::fs::File>,
range: Option<crate::runtime::http::HttpRange>,
metadata: Option<std::fs::Metadata>,
headers: HashMap<&'static str, String>,
status_code: u16,
body: Vec<u8>,
}

let mut range_metadata = crate::async_runtime::safe_block_on(async move {
let mut data = RangeMetadata::default();
// open the file
let mut file = match tokio::fs::File::open(path_.clone()).await {
Ok(file) => file,
Err(e) => {
debug_eprintln!("Failed to open asset: {}", e);
return (headers, 404, buf);
data.status_code = 404;
return data;
}
};
// Get the file size
let file_size = match file.metadata().await {
Ok(metadata) => metadata.len(),
Ok(metadata) => {
let len = metadata.len();
data.metadata.replace(metadata);
len
}
Err(e) => {
debug_eprintln!("Failed to read asset metadata: {}", e);
return (headers, 404, buf);
data.file.replace(file);
data.status_code = 404;
return data;
}
};
// parse the range
Expand All @@ -572,13 +588,16 @@ impl<R: Runtime> WindowManager<R> {
Ok(r) => r,
Err(e) => {
debug_eprintln!("Failed to parse range {}: {:?}", range, e);
return (headers, 400, buf);
data.file.replace(file);
data.status_code = 400;
return data;
}
};

// FIXME: Support multiple ranges
// let support only 1 range for now
let status_code = if let Some(range) = range.first() {
if let Some(range) = range.first() {
data.range.replace(*range);
let mut real_length = range.length;
// prevent max_length;
// specially on webview2
Expand All @@ -592,38 +611,84 @@ impl<R: Runtime> WindowManager<R> {
// who should be skipped on the header
let last_byte = range.start + real_length - 1;

headers.insert("Connection", "Keep-Alive".into());
headers.insert("Accept-Ranges", "bytes".into());
headers.insert("Content-Length", real_length.to_string());
headers.insert(
data.headers.insert("Connection", "Keep-Alive".into());
data.headers.insert("Accept-Ranges", "bytes".into());
data
.headers
.insert("Content-Length", real_length.to_string());
data.headers.insert(
"Content-Range",
format!("bytes {}-{}/{}", range.start, last_byte, file_size),
);

if let Err(e) = file.seek(std::io::SeekFrom::Start(range.start)).await {
debug_eprintln!("Failed to seek file to {}: {}", range.start, e);
return (headers, 422, buf);
data.file.replace(file);
data.status_code = 422;
return data;
}

if let Err(e) = file.take(real_length).read_to_end(&mut buf).await {
let mut f = file.take(real_length);
let r = f.read_to_end(&mut data.body).await;
file = f.into_inner();
data.file.replace(file);

if let Err(e) = r {
debug_eprintln!("Failed read file: {}", e);
return (headers, 422, buf);
data.status_code = 422;
return data;
}
// partial content
206
data.status_code = 206;
} else {
200
};
data.status_code = 200;
}

(headers, status_code, buf)
data
});

for (k, v) in headers {
for (k, v) in range_metadata.headers {
response = response.header(k, v);
}

let mime_type = MimeType::parse(&data, &path);
response.mimetype(&mime_type).status(status_code).body(data)
let mime_type = if let (Some(mut file), Some(metadata), Some(range)) = (
range_metadata.file,
range_metadata.metadata,
range_metadata.range,
) {
// if we're already reading the beginning of the file, we do not need to re-read it
if range.start == 0 {
MimeType::parse(&range_metadata.body, &path)
} else {
let (status, bytes) = crate::async_runtime::safe_block_on(async move {
let mut status = None;
if let Err(e) = file.rewind().await {
debug_eprintln!("Failed to rewind file: {}", e);
status.replace(422);
(status, Vec::with_capacity(0))
} else {
// taken from https://docs.rs/infer/0.9.0/src/infer/lib.rs.html#240-251
let limit = std::cmp::min(metadata.len(), 8192) as usize + 1;
let mut bytes = Vec::with_capacity(limit);
if let Err(e) = file.take(8192).read_to_end(&mut bytes).await {
debug_eprintln!("Failed read file: {}", e);
status.replace(422);
}
(status, bytes)
}
});
if let Some(s) = status {
range_metadata.status_code = s;
}
MimeType::parse(&bytes, &path)
}
} else {
MimeType::parse(&range_metadata.body, &path)
};
response
.mimetype(&mime_type)
.status(range_metadata.status_code)
.body(range_metadata.body)
} else {
match crate::async_runtime::safe_block_on(async move { tokio::fs::read(path_).await }) {
Ok(data) => {
Expand Down Expand Up @@ -1066,7 +1131,7 @@ impl<R: Runtime> WindowManager<R> {
// ignore "index.html" just to simplify the url
if path.to_str() != Some("index.html") {
url
.join(&*path.to_string_lossy())
.join(&path.to_string_lossy())
.map_err(crate::Error::InvalidUrl)
// this will never fail
.unwrap()
Expand Down
12 changes: 6 additions & 6 deletions core/tauri/src/scope/fs.rs
Expand Up @@ -148,9 +148,9 @@ impl Scope {
let mut list = self.allowed_patterns.lock().unwrap();

// allow the directory to be read
push_pattern(&mut list, &path, escaped_pattern)?;
push_pattern(&mut list, path, escaped_pattern)?;
// allow its files and subdirectories to be read
push_pattern(&mut list, &path, |p| {
push_pattern(&mut list, path, |p| {
escaped_pattern_with(p, if recursive { "**" } else { "*" })
})?;
}
Expand All @@ -165,7 +165,7 @@ impl Scope {
let path = path.as_ref();
push_pattern(
&mut self.allowed_patterns.lock().unwrap(),
&path,
path,
escaped_pattern,
)?;
self.trigger(Event::PathAllowed(path.to_path_buf()));
Expand All @@ -181,9 +181,9 @@ impl Scope {
let mut list = self.forbidden_patterns.lock().unwrap();

// allow the directory to be read
push_pattern(&mut list, &path, escaped_pattern)?;
push_pattern(&mut list, path, escaped_pattern)?;
// allow its files and subdirectories to be read
push_pattern(&mut list, &path, |p| {
push_pattern(&mut list, path, |p| {
escaped_pattern_with(p, if recursive { "**" } else { "*" })
})?;
}
Expand All @@ -198,7 +198,7 @@ impl Scope {
let path = path.as_ref();
push_pattern(
&mut self.forbidden_patterns.lock().unwrap(),
&path,
path,
escaped_pattern,
)?;
self.trigger(Event::PathForbidden(path.to_path_buf()));
Expand Down
4 changes: 2 additions & 2 deletions core/tauri/src/scope/shell.rs
Expand Up @@ -306,8 +306,8 @@ impl Scope {
// The prevention of argument escaping is handled by the usage of std::process::Command::arg by
// the `open` dependency. This behavior should be re-confirmed during upgrades of `open`.
match with.map(Program::name) {
Some(program) => ::open::with(&path, program),
None => ::open::that(&path),
Some(program) => ::open::with(path, program),
None => ::open::that(path),
}
.map_err(Into::into)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/commands/main.rs
Expand Up @@ -177,7 +177,7 @@ async fn async_stateful_command_with_result(
state: State<'_, MyState>,
) -> Result<String, MyError> {
println!("{:?} {:?}", the_argument, state.inner());
Ok(the_argument.unwrap_or_else(|| "".to_string()))
Ok(the_argument.unwrap_or_default())
}

// Non-Ident command function arguments
Expand Down
2 changes: 2 additions & 0 deletions examples/streaming/README.md
Expand Up @@ -3,3 +3,5 @@
A simple Tauri Application showcase the streaming functionality.

To execute run the following on the root directory of the repository: `cargo run --example streaming`.

By default the example uses a custom URI scheme protocol. To use the builtin `asset` protocol, run `cargo run --example streaming --features protocol-asset`.