445 lines
9.8 KiB
Rust
445 lines
9.8 KiB
Rust
use std::{
|
|
ffi::OsStr,
|
|
path::{
|
|
Component,
|
|
Path,
|
|
},
|
|
};
|
|
|
|
use maplit::*;
|
|
use tokio::runtime::Runtime;
|
|
|
|
#[test]
|
|
fn pretty_print_bytes () {
|
|
for (input_after, expected_before, expected_after) in vec! [
|
|
(1, "0 B", "1 B"),
|
|
(1024, "1023 B", "1 KiB"),
|
|
(1024 + 512, "1 KiB", "2 KiB"),
|
|
(1023 * 1024 + 512, "1023 KiB", "1 MiB"),
|
|
((1024 + 512) * 1024, "1 MiB", "2 MiB"),
|
|
(1023 * 1024 * 1024 + 512 * 1024, "1023 MiB", "1 GiB"),
|
|
((1024 + 512) * 1024 * 1024, "1 GiB", "2 GiB"),
|
|
|
|
].into_iter () {
|
|
let actual = super::pretty_print_bytes (input_after - 1);
|
|
assert_eq! (&actual, expected_before);
|
|
|
|
let actual = super::pretty_print_bytes (input_after);
|
|
assert_eq! (&actual, expected_after);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn i_hate_paths () {
|
|
let mut components = Path::new ("/home/user").components ();
|
|
|
|
assert_eq! (components.next (), Some (Component::RootDir));
|
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home"))));
|
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user"))));
|
|
assert_eq! (components.next (), None);
|
|
|
|
let mut components = Path::new ("./home/user").components ();
|
|
|
|
assert_eq! (components.next (), Some (Component::CurDir));
|
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home"))));
|
|
assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user"))));
|
|
assert_eq! (components.next (), None);
|
|
|
|
let mut components = Path::new (".").components ();
|
|
|
|
assert_eq! (components.next (), Some (Component::CurDir));
|
|
assert_eq! (components.next (), None);
|
|
}
|
|
|
|
#[test]
|
|
fn file_server () {
|
|
use std::path::PathBuf;
|
|
|
|
#[cfg (test)]
|
|
use always_equal::test::AlwaysEqual;
|
|
|
|
use ptth_core::{
|
|
http_serde::Method,
|
|
};
|
|
use super::*;
|
|
|
|
tracing_subscriber::fmt ().try_init ().ok ();
|
|
let rt = Runtime::new ().expect ("Can't create runtime");
|
|
|
|
rt.block_on (async {
|
|
use super::internal::FileRoots;
|
|
|
|
let file_server_root = PathBuf::from ("./");
|
|
let dirs = Default::default ();
|
|
|
|
let roots = FileRoots {
|
|
files: &file_server_root,
|
|
dirs: &dirs,
|
|
};
|
|
|
|
let headers = Default::default ();
|
|
|
|
{
|
|
use internal::Response::*;
|
|
use crate::file_server::FileServerError;
|
|
|
|
let bad_passwords_path = "/files/src/bad_passwords.txt";
|
|
|
|
for (uri_path, expected) in vec! [
|
|
("/", Root),
|
|
("/files", NotFound),
|
|
("/files/?", InvalidQuery),
|
|
("/files/src", Redirect ("src/".to_string ())),
|
|
("/files/src/?", InvalidQuery),
|
|
(bad_passwords_path, ServeFile (internal::ServeFileParams {
|
|
send_body: true,
|
|
range: range::ValidParsed {
|
|
range: 0..1_048_576,
|
|
range_requested: false,
|
|
},
|
|
file: AlwaysEqual::testing_blank (),
|
|
})),
|
|
("/files/test/test.md", ServeFile (internal::ServeFileParams {
|
|
send_body: true,
|
|
range: range::ValidParsed {
|
|
range: 0..144,
|
|
range_requested: false,
|
|
},
|
|
file: AlwaysEqual::testing_blank (),
|
|
})),
|
|
] {
|
|
let resp = internal::serve_all (
|
|
roots,
|
|
Method::Get,
|
|
uri_path,
|
|
&headers,
|
|
None
|
|
).await;
|
|
|
|
assert_eq! (resp.expect ("This block only tests Ok (_) responses"), expected);
|
|
}
|
|
|
|
for (uri_path, checker) in vec! [
|
|
("/ ", |e| match e {
|
|
FileServerError::InvalidUri (_) => (),
|
|
e => panic! ("Expected InvalidUri, got {:?}", e),
|
|
}),
|
|
] {
|
|
let resp = internal::serve_all (
|
|
roots,
|
|
Method::Get,
|
|
uri_path,
|
|
&headers,
|
|
None
|
|
).await;
|
|
|
|
checker (resp.unwrap_err ());
|
|
}
|
|
|
|
let resp = internal::serve_all (
|
|
roots,
|
|
Method::Get,
|
|
bad_passwords_path,
|
|
&hashmap! {
|
|
"range".into () => b"bytes=0-2000000".to_vec (),
|
|
},
|
|
None
|
|
).await;
|
|
|
|
assert_eq! (resp.expect ("Should be Ok (_)"), RangeNotSatisfiable (1_048_576));
|
|
|
|
let resp = internal::serve_all (
|
|
roots,
|
|
Method::Head,
|
|
bad_passwords_path,
|
|
&headers,
|
|
None
|
|
).await;
|
|
|
|
assert_eq! (resp.expect ("Should be Ok (_)"), ServeFile (internal::ServeFileParams {
|
|
send_body: false,
|
|
range: range::ValidParsed {
|
|
range: 0..1_048_576,
|
|
range_requested: false,
|
|
},
|
|
file: AlwaysEqual::testing_blank (),
|
|
}));
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn parse_uri () {
|
|
assert! (http::Uri::from_maybe_shared ("/").is_ok ());
|
|
}
|
|
|
|
#[test]
|
|
fn serve_file_decision () {
|
|
use ptth_core::http_serde::StatusCode;
|
|
use super::{
|
|
ServeFileInput,
|
|
ServeFileOutput,
|
|
};
|
|
|
|
for (input, expected) in vec! [
|
|
// Regular HEAD requests
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: None,
|
|
client_wants_body: false,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: None,
|
|
client_wants_body: false,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
|
|
// Regular GET requests
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: None,
|
|
client_wants_body: true,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::Ok,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: None,
|
|
client_wants_body: true,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::PartialContent,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
|
|
// HEAD requests where we pull a valid etag from the FS
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: false,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: false,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: true,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::Ok,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: None,
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: true,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::PartialContent,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
|
|
// Client has an expected ETag but we can't pull the real one for
|
|
// some reason
|
|
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: None,
|
|
client_wants_body: false,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: None,
|
|
client_wants_body: false,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: None,
|
|
client_wants_body: true,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::Ok,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: None,
|
|
client_wants_body: true,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::PartialContent,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
|
|
// File changed on disk since the client last saw it
|
|
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: false,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: false,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NoContent,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: true,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::Ok,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_1"),
|
|
actual_etag: Some (b"bogus_2".to_vec ()),
|
|
client_wants_body: true,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::PartialContent,
|
|
should_send_body: true,
|
|
}
|
|
),
|
|
|
|
// The ETags match, and we can tell the client to use their cache
|
|
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_3"),
|
|
actual_etag: Some (b"bogus_3".to_vec ()),
|
|
client_wants_body: false,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NotModified,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_3"),
|
|
actual_etag: Some (b"bogus_3".to_vec ()),
|
|
client_wants_body: false,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NotModified,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_3"),
|
|
actual_etag: Some (b"bogus_3".to_vec ()),
|
|
client_wants_body: true,
|
|
range_requested: false,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NotModified,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
(
|
|
ServeFileInput {
|
|
if_none_match: Some (b"bogus_3"),
|
|
actual_etag: Some (b"bogus_3".to_vec ()),
|
|
client_wants_body: true,
|
|
range_requested: true,
|
|
},
|
|
ServeFileOutput {
|
|
status_code: StatusCode::NotModified,
|
|
should_send_body: false,
|
|
}
|
|
),
|
|
].into_iter () {
|
|
let actual = super::serve_file_decision (&input);
|
|
assert_eq! (actual, expected, "{:?}", input);
|
|
}
|
|
}
|