bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
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)
}