use super::{wrap_internal_error, ReqContext, S3Handler};
use crate::dto::{HeadObjectError, HeadObjectOutput, HeadObjectRequest};
use crate::errors::{S3Error, S3ErrorCode, S3Result};
use crate::headers::{
ACCEPT_RANGES, CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE,
CONTENT_LENGTH, CONTENT_TYPE, ETAG, EXPIRES, IF_MATCH, IF_MODIFIED_SINCE, IF_NONE_MATCH,
IF_UNMODIFIED_SINCE, LAST_MODIFIED, RANGE, X_AMZ_DELETE_MARKER, X_AMZ_EXPIRATION,
X_AMZ_MISSING_META, X_AMZ_MP_PARTS_COUNT, X_AMZ_OBJECT_LOCK_LEGAL_HOLD, X_AMZ_OBJECT_LOCK_MODE,
X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE, X_AMZ_REPLICATION_STATUS, X_AMZ_REQUEST_CHARGED,
X_AMZ_REQUEST_PAYER, X_AMZ_RESTORE, X_AMZ_SERVER_SIDE_ENCRYPTION,
X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM,
X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5,
X_AMZ_STORAGE_CLASS, X_AMZ_VERSION_ID, X_AMZ_WEBSITE_REDIRECT_LOCATION,
};
use crate::output::S3Output;
use crate::storage::S3Storage;
use crate::utils::{time, ResponseExt};
use crate::{async_trait, Method, Response};
pub struct Handler;
#[async_trait]
impl S3Handler for Handler {
fn is_match(&self, ctx: &'_ ReqContext<'_>) -> bool {
bool_try!(ctx.req.method() == Method::HEAD);
ctx.path.is_object()
}
async fn handle(
&self,
ctx: &mut ReqContext<'_>,
storage: &(dyn S3Storage + Send + Sync),
) -> S3Result<Response> {
let input = extract(ctx)?;
let output = storage.head_object(input).await;
output.try_into_response()
}
}
fn extract(ctx: &mut ReqContext<'_>) -> S3Result<HeadObjectRequest> {
let (bucket, key) = ctx.unwrap_object_path();
let mut input = HeadObjectRequest {
bucket: bucket.into(),
key: key.into(),
..HeadObjectRequest::default()
};
let h = &ctx.headers;
h.assign_str(IF_MATCH, &mut input.if_match);
h.assign_str(IF_MODIFIED_SINCE, &mut input.if_modified_since);
h.assign_str(IF_NONE_MATCH, &mut input.if_none_match);
h.assign_str(IF_UNMODIFIED_SINCE, &mut input.if_unmodified_since);
h.assign_str(RANGE, &mut input.range);
h.assign_str(
&*X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM,
&mut input.sse_customer_algorithm,
);
h.assign_str(
&*X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY,
&mut input.sse_customer_key,
);
h.assign_str(
&*X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5,
&mut input.sse_customer_key_md5,
);
h.assign_str(&*X_AMZ_REQUEST_PAYER, &mut input.request_payer);
Ok(input)
}
impl S3Output for HeadObjectOutput {
fn try_into_response(self) -> S3Result<Response> {
wrap_internal_error(|res| {
res.set_optional_header(
&*X_AMZ_DELETE_MARKER,
self.delete_marker.map(|b| b.to_string()),
)?;
res.set_optional_header(ACCEPT_RANGES, self.accept_ranges)?;
res.set_optional_header(&*X_AMZ_EXPIRATION, self.expiration)?;
res.set_optional_header(&*X_AMZ_RESTORE, self.restore)?;
res.set_optional_header(
LAST_MODIFIED,
time::map_opt_rfc3339_to_last_modified(self.last_modified.as_deref())?,
)?;
res.set_optional_header(CONTENT_LENGTH, self.content_length.map(|l| l.to_string()))?;
res.set_optional_header(ETAG, self.e_tag)?;
res.set_optional_header(
&*X_AMZ_MISSING_META,
self.missing_meta.map(|m| m.to_string()),
)?;
res.set_optional_header(&*X_AMZ_VERSION_ID, self.version_id)?;
res.set_optional_header(CACHE_CONTROL, self.cache_control)?;
res.set_optional_header(CONTENT_DISPOSITION, self.content_disposition)?;
res.set_optional_header(CONTENT_ENCODING, self.content_encoding)?;
res.set_optional_header(CONTENT_LANGUAGE, self.content_language)?;
res.set_optional_header(CONTENT_TYPE, self.content_type)?;
res.set_optional_header(EXPIRES, self.expires)?;
res.set_optional_header(
&*X_AMZ_WEBSITE_REDIRECT_LOCATION,
self.website_redirect_location,
)?;
res.set_optional_header(&*X_AMZ_SERVER_SIDE_ENCRYPTION, self.server_side_encryption)?;
res.set_optional_header(
&*X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM,
self.sse_customer_algorithm,
)?;
res.set_optional_header(
&*X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5,
self.sse_customer_key_md5,
)?;
res.set_optional_header(
&*X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID,
self.ssekms_key_id,
)?;
res.set_optional_header(&*X_AMZ_STORAGE_CLASS, self.storage_class)?;
res.set_optional_header(&*X_AMZ_REQUEST_CHARGED, self.request_charged)?;
res.set_optional_header(&*X_AMZ_REPLICATION_STATUS, self.replication_status)?;
res.set_optional_header(
&*X_AMZ_MP_PARTS_COUNT,
self.parts_count.map(|c| c.to_string()),
)?;
res.set_optional_header(&*X_AMZ_OBJECT_LOCK_MODE, self.object_lock_mode)?;
res.set_optional_header(
&*X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE,
self.object_lock_retain_until_date,
)?;
res.set_optional_header(
&*X_AMZ_OBJECT_LOCK_LEGAL_HOLD,
self.object_lock_legal_hold_status,
)?;
if let Some(ref metadata) = self.metadata {
res.set_metadata_headers(metadata)?;
}
Ok(())
})
}
}
impl From<HeadObjectError> for S3Error {
fn from(e: HeadObjectError) -> Self {
match e {
HeadObjectError::NoSuchKey(msg) => Self::new(S3ErrorCode::NoSuchKey, msg),
}
}
}