xh/tests/cases/download.rs
2025-04-14 10:57:27 +08:00

431 lines
11 KiB
Rust

use std::{
fs::{self, OpenOptions},
io::Write,
};
use predicates::str::contains;
use tempfile::tempdir;
use crate::prelude::*;
#[test]
fn download() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.body("file contents\n".into())
.unwrap()
});
let outfile = dir.path().join("outfile");
get_command()
.arg("--download")
.arg("--output")
.arg(&outfile)
.arg(server.base_url())
.assert()
.success();
assert_eq!(fs::read_to_string(&outfile).unwrap(), "file contents\n");
}
#[test]
fn accept_encoding_not_modifiable_in_download_mode() {
let server = server::http(|req| async move {
assert_eq!(req.headers()["accept-encoding"], "identity");
hyper::Response::builder()
.body(r#"{"ids":[1,2,3]}"#.into())
.unwrap()
});
let dir = tempdir().unwrap();
get_command()
.current_dir(&dir)
.args([&server.base_url(), "--download", "accept-encoding:gzip"])
.assert()
.success();
}
#[test]
fn download_generated_filename() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header("Content-Type", "application/json")
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.url("/foo/bar/")])
.current_dir(&dir)
.assert()
.success();
get_command()
.args(["--download", &server.url("/foo/bar/")])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("bar.json")).unwrap(),
"file"
);
assert_eq!(
fs::read_to_string(dir.path().join("bar.json-1")).unwrap(),
"file"
);
}
#[test]
fn download_supplied_filename() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header("Content-Disposition", r#"attachment; filename="foo.bar""#)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("foo.bar")).unwrap(),
"file"
);
}
#[test]
fn download_supplied_unicode_filename() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header("Content-Disposition", r#"attachment; filename="😀.bar""#)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("😀.bar")).unwrap(),
"file"
);
}
#[test]
fn download_support_filename_rfc_5987() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header(
"Content-Disposition",
r#"attachment; filename*=UTF-8''abcd1234.txt"#,
)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("abcd1234.txt")).unwrap(),
"file"
);
}
#[test]
fn download_support_filename_rfc_5987_percent_encoded() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header(
"Content-Disposition",
r#"attachment; filename*=UTF-8''%E6%B5%8B%E8%AF%95.txt"#,
)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("测试.txt")).unwrap(),
"file"
);
}
#[test]
fn download_support_filename_rfc_5987_percent_encoded_with_iso_8859_1() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header(
"Content-Disposition",
r#"attachment; filename*=iso-8859-1'en'%A3%20rates.txt"#,
)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("£ rates.txt")).unwrap(),
"file"
);
}
#[test]
fn download_filename_star_with_high_priority() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header(
"Content-Disposition",
r#"attachment; filename="fallback.txt"; filename*=UTF-8''%E6%B5%8B%E8%AF%95.txt"#,
)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("测试.txt")).unwrap(),
"file"
);
}
#[test]
fn download_supplied_unquoted_filename() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header("Content-Disposition", r#"attachment; filename=foo bar baz"#)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("foo bar baz")).unwrap(),
"file"
);
}
#[test]
fn download_filename_with_directory_traversal() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header(
"Content-Disposition",
r#"attachment; filename="foo/baz/bar""#,
)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("foo_baz_bar")).unwrap(),
"file"
);
}
#[cfg(windows)]
#[test]
fn download_filename_with_windows_directory_traversal() {
let dir = tempdir().unwrap();
let server = server::http(|_req| async move {
hyper::Response::builder()
.header(
"Content-Disposition",
r#"attachment; filename="foo\baz\bar""#,
)
.body("file".into())
.unwrap()
});
get_command()
.args(["--download", &server.base_url()])
.current_dir(&dir)
.assert()
.success();
assert_eq!(
fs::read_to_string(dir.path().join("foo_baz_bar")).unwrap(),
"file"
);
}
// TODO: test implicit download filenames
// For this we have to pretend the output is a tty
// This intersects with both #41 and #59
#[test]
fn it_can_resume_a_download() {
let server = server::http(|req| async move {
assert_eq!(req.headers()[hyper::header::RANGE], "bytes=5-");
hyper::Response::builder()
.status(206)
.header(hyper::header::CONTENT_RANGE, "bytes 5-11/12")
.body(" world\n".into())
.unwrap()
});
let dir = tempfile::tempdir().unwrap();
let filename = dir.path().join("input.txt");
OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&filename)
.unwrap()
.write_all(b"Hello")
.unwrap();
get_command()
.arg("--download")
.arg("--continue")
.arg("--output")
.arg(&filename)
.arg(server.base_url())
.assert()
.success();
assert_eq!(fs::read_to_string(&filename).unwrap(), "Hello world\n");
}
#[test]
fn it_can_resume_a_download_with_one_byte() {
let server = server::http(|req| async move {
assert_eq!(req.headers()[hyper::header::RANGE], "bytes=5-");
hyper::Response::builder()
.status(206)
.header(hyper::header::CONTENT_RANGE, "bytes 5-5/6")
.body("!".into())
.unwrap()
});
let dir = tempfile::tempdir().unwrap();
let filename = dir.path().join("input.txt");
OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&filename)
.unwrap()
.write_all(b"Hello")
.unwrap();
get_command()
.arg("--download")
.arg("--continue")
.arg("--output")
.arg(&filename)
.arg(server.base_url())
.assert()
.success();
assert_eq!(fs::read_to_string(&filename).unwrap(), "Hello!");
}
#[test]
fn it_rejects_incorrect_content_range_headers() {
let server = server::http(|req| async move {
assert_eq!(req.headers()[hyper::header::RANGE], "bytes=5-");
hyper::Response::builder()
.status(206)
.header(hyper::header::CONTENT_RANGE, "bytes 6-10/11")
.body("world\n".into())
.unwrap()
});
let dir = tempfile::tempdir().unwrap();
let filename = dir.path().join("input.txt");
OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&filename)
.unwrap()
.write_all(b"Hello")
.unwrap();
get_command()
.arg("--download")
.arg("--continue")
.arg("--output")
.arg(&filename)
.arg(server.base_url())
.assert()
.failure()
.stderr(contains("Content-Range has wrong start"));
}
#[test]
fn it_refuses_to_combine_continue_and_range() {
let server = server::http(|req| async move {
assert_eq!(req.headers()[hyper::header::RANGE], "bytes=20-30");
hyper::Response::builder()
.status(206)
.header(hyper::header::CONTENT_RANGE, "bytes 20-30/100")
.body("lorem ipsum".into())
.unwrap()
});
let dir = tempfile::tempdir().unwrap();
let filename = dir.path().join("input.txt");
OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&filename)
.unwrap()
.write_all(b"Hello")
.unwrap();
get_command()
.arg("--download")
.arg("--continue")
.arg("--output")
.arg(&filename)
.arg(server.base_url())
.arg("Range:bytes=20-30")
.assert()
.success()
.stderr(contains("warning: --continue can't be used with"));
assert_eq!(fs::read_to_string(&filename).unwrap(), "lorem ipsum");
}