mirror of https://github.com/fafhrd91/actix-web
resolve conversation1
This commit is contained in:
parent
c55a37a6da
commit
bdd485e965
|
|
@ -151,6 +151,7 @@ impl Files {
|
|||
Ok(canon_dir) => canon_dir,
|
||||
Err(_) => {
|
||||
log::error!("Specified path is not a directory: {:?}", orig_dir);
|
||||
// Preserve original path so requests don't fall back to CWD.
|
||||
orig_dir
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,9 +115,9 @@ impl FilesService {
|
|||
|
||||
/// Show index listing for a directory.
|
||||
///
|
||||
/// Uses the directory where the path was found as the base directory for index listing.
|
||||
fn show_index(&self, req: ServiceRequest, path: PathBuf) -> ServiceResponse {
|
||||
let dir = Directory::new(path.clone(), path);
|
||||
/// Uses the provided base directory for calculating relative paths in the index listing.
|
||||
fn show_index(&self, req: ServiceRequest, base: PathBuf, path: PathBuf) -> ServiceResponse {
|
||||
let dir = Directory::new(base, path);
|
||||
|
||||
let (req, _) = req.into_parts();
|
||||
|
||||
|
|
@ -175,91 +175,93 @@ impl Service<ServiceRequest> for FilesService {
|
|||
}
|
||||
|
||||
// Try to find file in multiple directories
|
||||
let mut found_path = None;
|
||||
let mut last_err = None;
|
||||
|
||||
for directory in &this.directories {
|
||||
let path = directory.join(&path_on_disk);
|
||||
match path.canonicalize() {
|
||||
Ok(_) => {
|
||||
found_path = Some(path);
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
// Keep track of the last error
|
||||
last_err = Some(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let path = match found_path {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
// If all directories failed, use the last error
|
||||
let err = last_err.unwrap_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::NotFound, "File not found")
|
||||
});
|
||||
return this.handle_err(err, req).await;
|
||||
}
|
||||
};
|
||||
|
||||
// Try serving pre-compressed file even if the uncompressed file doesn't exist yet.
|
||||
// Still handle directories (index/listing) through the normal branch below.
|
||||
if this.try_compressed && !path.is_dir() {
|
||||
if let Some((named_file, encoding)) = find_compressed(&req, &path).await {
|
||||
return Ok(this.serve_named_file_with_encoding(req, named_file, encoding));
|
||||
}
|
||||
}
|
||||
|
||||
if path.is_dir() {
|
||||
if this.redirect_to_slash
|
||||
&& !req.path().ends_with('/')
|
||||
&& (this.index.is_some() || this.show_index)
|
||||
{
|
||||
let redirect_to = format!("{}/", req.path());
|
||||
|
||||
let response = if this.with_permanent_redirect {
|
||||
HttpResponse::PermanentRedirect()
|
||||
} else {
|
||||
HttpResponse::TemporaryRedirect()
|
||||
}
|
||||
.insert_header((header::LOCATION, redirect_to))
|
||||
.finish();
|
||||
|
||||
return Ok(req.into_response(response));
|
||||
}
|
||||
|
||||
match this.index {
|
||||
Some(ref index) => {
|
||||
let named_path = path.join(index);
|
||||
if this.try_compressed {
|
||||
if let Some((named_file, encoding)) =
|
||||
find_compressed(&req, &named_path).await
|
||||
// Try serving pre-compressed file even if the uncompressed file doesn't exist yet.
|
||||
// Still handle directories (index/listing) through the normal branch below.
|
||||
if this.try_compressed {
|
||||
if let Ok(metadata) = path.metadata() {
|
||||
if !metadata.is_dir() {
|
||||
if let Some((named_file, encoding)) = find_compressed(&req, &path).await
|
||||
{
|
||||
return Ok(
|
||||
this.serve_named_file_with_encoding(req, named_file, encoding)
|
||||
);
|
||||
}
|
||||
}
|
||||
// fallback to the uncompressed version
|
||||
match NamedFile::open_async(named_path).await {
|
||||
Ok(named_file) => Ok(this.serve_named_file(req, named_file)),
|
||||
Err(_) if this.show_index => Ok(this.show_index(req, path)),
|
||||
Err(err) => this.handle_err(err, req).await,
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path is a directory
|
||||
if path.is_dir() {
|
||||
// Handle directory logic inline to avoid multiple iterations
|
||||
if this.redirect_to_slash
|
||||
&& !req.path().ends_with('/')
|
||||
&& (this.index.is_some() || this.show_index)
|
||||
{
|
||||
let redirect_to = format!("{}/", req.path());
|
||||
|
||||
let response = if this.with_permanent_redirect {
|
||||
HttpResponse::PermanentRedirect()
|
||||
} else {
|
||||
HttpResponse::TemporaryRedirect()
|
||||
}
|
||||
.insert_header((header::LOCATION, redirect_to))
|
||||
.finish();
|
||||
|
||||
return Ok(req.into_response(response));
|
||||
}
|
||||
|
||||
match this.index {
|
||||
Some(ref index) => {
|
||||
let named_path = path.join(index);
|
||||
if this.try_compressed {
|
||||
if let Some((named_file, encoding)) =
|
||||
find_compressed(&req, &named_path).await
|
||||
{
|
||||
return Ok(this.serve_named_file_with_encoding(
|
||||
req, named_file, encoding,
|
||||
));
|
||||
}
|
||||
}
|
||||
// fallback to the uncompressed version
|
||||
match NamedFile::open_async(named_path).await {
|
||||
Ok(named_file) => return Ok(this.serve_named_file(req, named_file)),
|
||||
Err(_) if this.show_index => {
|
||||
return Ok(this.show_index(req, directory.clone(), path))
|
||||
}
|
||||
Err(err) => last_err = Some(err),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// No index file configured, check if we should show directory listing
|
||||
if this.show_index {
|
||||
return Ok(this.show_index(req, directory.clone(), path));
|
||||
}
|
||||
return Ok(ServiceResponse::from_err(
|
||||
FilesError::IsDirectory,
|
||||
req.into_parts().0,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Try to open the file
|
||||
match NamedFile::open_async(&path).await {
|
||||
Ok(named_file) => return Ok(this.serve_named_file(req, named_file)),
|
||||
Err(err) => {
|
||||
last_err = Some(err);
|
||||
}
|
||||
}
|
||||
None if this.show_index => Ok(this.show_index(req, path)),
|
||||
None => Ok(ServiceResponse::from_err(
|
||||
FilesError::IsDirectory,
|
||||
req.into_parts().0,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
match NamedFile::open_async(&path).await {
|
||||
Ok(named_file) => Ok(this.serve_named_file(req, named_file)),
|
||||
Err(err) => this.handle_err(err, req).await,
|
||||
}
|
||||
}
|
||||
|
||||
// If all directories failed, use the last error
|
||||
let err = last_err
|
||||
.unwrap_or_else(|| io::Error::new(io::ErrorKind::NotFound, "File not found"));
|
||||
return this.handle_err(err, req).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,3 +272,101 @@ async fn test_multiple_directories_iterator() {
|
|||
let _ = std::fs::remove_dir_all("./tests/test3");
|
||||
let _ = std::fs::remove_dir_all("./tests/test4");
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_multiple_directories_with_index_file() {
|
||||
// Create test directories
|
||||
std::fs::create_dir_all("./tests/test_index1").unwrap();
|
||||
std::fs::create_dir_all("./tests/test_index2").unwrap();
|
||||
|
||||
// Create test files - only second directory has index.html
|
||||
std::fs::write("./tests/test_index1/other.txt", "Other file").unwrap();
|
||||
std::fs::write(
|
||||
"./tests/test_index2/index.html",
|
||||
"<html>Index from test2</html>",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Test multiple directories with index_file - index.html only exists in second directory
|
||||
let srv = test::init_service(
|
||||
App::new().service(
|
||||
Files::new_from_array("/", &["./tests/test_index1", "./tests/test_index2"])
|
||||
.index_file("index.html"),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Request / should find index.html in second directory
|
||||
let req = TestRequest::with_uri("/").to_request();
|
||||
let res = test::call_service(&srv, req).await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let body = test::read_body(res).await;
|
||||
assert_eq!(&body[..], b"<html>Index from test2</html>");
|
||||
|
||||
// Clean up
|
||||
let _ = std::fs::remove_dir_all("./tests/test_index1");
|
||||
let _ = std::fs::remove_dir_all("./tests/test_index2");
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_multiple_directories_try_compressed() {
|
||||
use actix_web::body::MessageBody;
|
||||
|
||||
// Create test directories
|
||||
std::fs::create_dir_all("./tests/test_compress1").unwrap();
|
||||
std::fs::create_dir_all("./tests/test_compress2").unwrap();
|
||||
|
||||
// Create test files:
|
||||
// - First directory has only uncompressed file
|
||||
// - Second directory has both uncompressed and compressed files
|
||||
std::fs::copy("./tests/utf8.txt", "./tests/test_compress1/utf8.txt").unwrap();
|
||||
std::fs::copy("./tests/utf8.txt", "./tests/test_compress2/other.txt").unwrap();
|
||||
std::fs::copy("./tests/utf8.txt.gz", "./tests/test_compress2/other.txt.gz").unwrap();
|
||||
|
||||
let other_txt_gz_len = std::fs::metadata("./tests/test_compress2/other.txt.gz")
|
||||
.unwrap()
|
||||
.len();
|
||||
|
||||
// Test multiple directories with try_compressed
|
||||
let srv = test::init_service(
|
||||
App::new().service(
|
||||
Files::new_from_array("/", &["./tests/test_compress1", "./tests/test_compress2"])
|
||||
.try_compressed(),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Request /utf8.txt - first directory has it uncompressed, should return uncompressed
|
||||
let mut req = TestRequest::with_uri("/utf8.txt").to_request();
|
||||
req.headers_mut().insert(
|
||||
header::ACCEPT_ENCODING,
|
||||
header::HeaderValue::from_static("gzip"),
|
||||
);
|
||||
let res = test::call_service(&srv, req).await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
// First directory has utf8.txt but no .gz version, so no content-encoding
|
||||
assert_eq!(res.headers().get(header::CONTENT_ENCODING), None);
|
||||
|
||||
// Request /other.txt - second directory has both, should return compressed
|
||||
let mut req = TestRequest::with_uri("/other.txt").to_request();
|
||||
req.headers_mut().insert(
|
||||
header::ACCEPT_ENCODING,
|
||||
header::HeaderValue::from_static("gzip"),
|
||||
);
|
||||
let res = test::call_service(&srv, req).await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers().get(header::CONTENT_ENCODING),
|
||||
Some(&HeaderValue::from_static("gzip")),
|
||||
);
|
||||
assert_eq!(
|
||||
res.into_body().size(),
|
||||
actix_web::body::BodySize::Sized(other_txt_gz_len),
|
||||
);
|
||||
|
||||
// Clean up
|
||||
let _ = std::fs::remove_dir_all("./tests/test_compress1");
|
||||
let _ = std::fs::remove_dir_all("./tests/test_compress2");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue