use super::*;
pub(crate) fn parse_copy_source(
source: &str,
) -> Result<(String, String, Option<String>), RuntimeError> {
let (path, query) = source
.split_once('?')
.map_or((source, ""), |(path, query)| (path, query));
let (bucket, key) = parse_s3_path(path)?;
let version_id = query
.split('&')
.filter_map(|pair| pair.split_once('='))
.find_map(|(name, value)| (name == "versionId").then(|| value.to_string()));
match (bucket, key) {
(Some(bucket), Some(key)) => Ok((bucket.to_string(), percent_decode(key), version_id)),
_ => Err(RuntimeError::InvalidObjectKey(source.to_string())),
}
}
fn meaningful_header<'a>(headers: &'a BTreeMap<String, String>, name: &str) -> Option<&'a str> {
header(headers, name).and_then(|value| {
let trimmed = value.trim();
(!trimmed.is_empty()).then_some(trimmed)
})
}
pub(crate) fn metadata_from_headers(headers: &BTreeMap<String, String>) -> ObjectMetadata {
let mut metadata = ObjectMetadata::default();
if let Some(content_type) = meaningful_header(headers, "content-type") {
metadata.content_type = content_type.to_string();
}
if let Some(content_encoding) = meaningful_header(headers, "content-encoding") {
metadata.content_encoding = Some(content_encoding.to_string());
}
for (key, value) in headers {
if key
.to_ascii_lowercase()
.strip_prefix("x-amz-meta-")
.is_some()
{
let meta_key = key
.to_ascii_lowercase()
.trim_start_matches("x-amz-meta-")
.to_string();
metadata.user_metadata.insert(meta_key, value.clone());
}
}
metadata
}
pub(crate) fn parse_max_keys(value: &str) -> Result<usize, RuntimeError> {
value
.parse::<usize>()
.map_err(|_| RuntimeError::InvalidListParameter {
name: "max-keys".to_string(),
value: value.to_string(),
})
}
pub(crate) fn parse_object_tags(body: &[u8]) -> Result<BTreeMap<String, String>, RuntimeError> {
let xml = String::from_utf8_lossy(body);
let mut tags = BTreeMap::new();
let mut remainder = xml.as_ref();
while let Some(start) = remainder.find("<Tag>") {
remainder = &remainder[start + "<Tag>".len()..];
let Some(end) = remainder.find("</Tag>") else {
return Err(RuntimeError::InvalidTagging("unclosed Tag".to_string()));
};
let tag_xml = &remainder[..end];
remainder = &remainder[end + "</Tag>".len()..];
let key = text_between(tag_xml, "<Key>", "</Key>")
.map(str::to_string)
.ok_or_else(|| RuntimeError::InvalidTagging("tag key is required".to_string()))?;
let value = text_between(tag_xml, "<Value>", "</Value>")
.map(str::to_string)
.ok_or_else(|| RuntimeError::InvalidTagging("tag value is required".to_string()))?;
tags.insert(key, value);
}
validate_tags(&tags)?;
Ok(tags)
}