use super::*;
pub(crate) fn parse_s3_path(path: &str) -> Result<(Option<&str>, Option<&str>), RuntimeError> {
let path = path.trim_start_matches('/');
if path.is_empty() {
return Ok((None, None));
}
let (bucket, key) = path.split_once('/').unwrap_or((path, ""));
if bucket.is_empty() {
return Err(RuntimeError::InvalidBucketName(path.to_string()));
}
if key.is_empty() {
Ok((Some(bucket), None))
} else {
Ok((Some(bucket), Some(key)))
}
}
pub(crate) fn resolve_s3_request_target(
request: &S3HttpRequest,
) -> Result<S3RequestTarget, RuntimeError> {
if let Some(bucket) = header(&request.headers, "host").and_then(virtual_host_bucket) {
if !validate_bucket_name(&bucket) {
return Err(RuntimeError::InvalidBucketName(bucket));
}
let key = percent_decode(request.path.trim_start_matches('/'));
return Ok(S3RequestTarget {
bucket: Some(bucket),
key: if key.is_empty() {
None
} else {
Some(key.to_string())
},
});
}
let (bucket, key) = parse_s3_path(&request.path)?;
Ok(S3RequestTarget {
bucket: bucket.map(str::to_string),
key: key.map(percent_decode),
})
}
pub(crate) fn resolve_s3_website_request_target(
request: &S3HttpRequest,
) -> Result<Option<S3RequestTarget>, RuntimeError> {
let Some(bucket) = header(&request.headers, "host").and_then(website_host_bucket) else {
return Ok(None);
};
if !validate_bucket_name(&bucket) {
return Err(RuntimeError::InvalidBucketName(bucket));
}
let key = percent_decode(request.path.trim_start_matches('/'));
Ok(Some(S3RequestTarget {
bucket: Some(bucket),
key: if key.is_empty() { None } else { Some(key) },
}))
}
pub(crate) fn virtual_host_bucket(host: &str) -> Option<String> {
let host = host
.split_once(':')
.map(|(host, _)| host)
.unwrap_or(host)
.trim_end_matches('.');
for suffix in [".s3.bucketwarden.test", ".s3.localhost", ".localhost"] {
if let Some(bucket) = host.strip_suffix(suffix) {
if !bucket.is_empty() && !bucket.contains('.') {
return Some(bucket.to_string());
}
}
}
None
}
pub(crate) fn website_host_bucket(host: &str) -> Option<String> {
let host = host
.split_once(':')
.map(|(host, _)| host)
.unwrap_or(host)
.trim_end_matches('.');
for suffix in [
".s3-website.bucketwarden.test",
".s3-website.localhost",
".website.localhost",
] {
if let Some(bucket) = host.strip_suffix(suffix) {
if !bucket.is_empty() && !bucket.contains('.') {
return Some(bucket.to_string());
}
}
}
None
}
pub(crate) fn is_directory_bucket_control_host(host: &str) -> bool {
let host = host
.split_once(':')
.map(|(host, _)| host)
.unwrap_or(host)
.trim_end_matches('.');
host.starts_with("s3express-control.")
}
pub(crate) fn is_object_lambda_host(host: &str) -> bool {
let host = host
.split_once(':')
.map(|(host, _)| host)
.unwrap_or(host)
.trim_end_matches('.');
host.contains(".s3-object-lambda.") || host.ends_with(".s3-object-lambda.bucketwarden.test")
}