bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
use super::*;

pub(crate) fn create_multipart_upload_xml(result: &CreateMultipartUploadResult) -> String {
    format!(
        "<InitiateMultipartUploadResult><Bucket>{}</Bucket><Key>{}</Key><UploadId>{}</UploadId></InitiateMultipartUploadResult>",
        xml_escape(&result.bucket),
        xml_escape(&result.key),
        xml_escape(&result.upload_id)
    )
}

pub(crate) fn upload_part_copy_xml(result: &CopyPartResult) -> String {
    format!(
        "<CopyPartResult><ETag>{}</ETag></CopyPartResult>",
        xml_escape(&quote_etag(&result.etag))
    )
}

pub(crate) fn list_multipart_uploads_xml(result: &ListMultipartUploadsResult) -> String {
    let uploads = result
        .uploads
        .iter()
        .map(|upload| {
            format!(
                "<Upload><Key>{}</Key><UploadId>{}</UploadId><Initiated>{}</Initiated></Upload>",
                xml_escape(&upload.key),
                xml_escape(&upload.upload_id),
                upload.initiated_epoch_seconds
            )
        })
        .collect::<String>();
    let prefix = result
        .prefix
        .as_ref()
        .map(|prefix| format!("<Prefix>{}</Prefix>", xml_escape(prefix)))
        .unwrap_or_default();
    let key_marker = result
        .key_marker
        .as_ref()
        .map(|marker| format!("<KeyMarker>{}</KeyMarker>", xml_escape(marker)))
        .unwrap_or_default();
    let upload_id_marker = result
        .upload_id_marker
        .as_ref()
        .map(|marker| format!("<UploadIdMarker>{}</UploadIdMarker>", xml_escape(marker)))
        .unwrap_or_default();
    let next_key_marker = result
        .next_key_marker
        .as_ref()
        .map(|marker| format!("<NextKeyMarker>{}</NextKeyMarker>", xml_escape(marker)))
        .unwrap_or_default();
    let next_upload_id_marker = result
        .next_upload_id_marker
        .as_ref()
        .map(|marker| format!("<NextUploadIdMarker>{}</NextUploadIdMarker>", xml_escape(marker)))
        .unwrap_or_default();
    format!(
        "<ListMultipartUploadsResult><Bucket>{}</Bucket>{prefix}{key_marker}{upload_id_marker}<MaxUploads>{}</MaxUploads><IsTruncated>{}</IsTruncated>{next_key_marker}{next_upload_id_marker}{uploads}</ListMultipartUploadsResult>",
        xml_escape(&result.bucket),
        result.max_uploads,
        result.is_truncated
    )
}

pub(crate) fn list_parts_xml(result: &ListPartsResult) -> String {
    let parts = result
        .parts
        .iter()
        .map(|part| {
            format!(
                "<Part><PartNumber>{}</PartNumber><ETag>{}</ETag><Size>{}</Size></Part>",
                part.part_number,
                xml_escape(&quote_etag(&part.etag)),
                part.size
            )
        })
        .collect::<String>();
    let part_number_marker = result
        .part_number_marker
        .map(|part| format!("<PartNumberMarker>{part}</PartNumberMarker>"))
        .unwrap_or_default();
    let next_marker = result
        .next_part_number_marker
        .map(|part| format!("<NextPartNumberMarker>{part}</NextPartNumberMarker>"))
        .unwrap_or_default();
    format!(
        "<ListPartsResult><Bucket>{}</Bucket><Key>{}</Key><UploadId>{}</UploadId>{part_number_marker}<MaxParts>{}</MaxParts><IsTruncated>{}</IsTruncated>{next_marker}{parts}</ListPartsResult>",
        xml_escape(&result.bucket),
        xml_escape(&result.key),
        xml_escape(&result.upload_id),
        result.max_parts,
        result.is_truncated
    )
}

pub(crate) fn complete_multipart_upload_xml(result: &CompleteMultipartUploadResult) -> String {
    format!(
        "<CompleteMultipartUploadResult><Bucket>{}</Bucket><Key>{}</Key><VersionId>{}</VersionId><ETag>{}</ETag></CompleteMultipartUploadResult>",
        xml_escape(&result.bucket),
        xml_escape(&result.key),
        xml_escape(&result.version_id),
        xml_escape(&result.etag)
    )
}

pub(crate) fn parse_completed_multipart_upload(body: &[u8]) -> CompletedMultipartUpload {
    let xml = String::from_utf8_lossy(body);
    let mut parts = Vec::new();
    let mut remainder = xml.as_ref();
    while let Some(start) = remainder.find("<Part>") {
        remainder = &remainder[start + "<Part>".len()..];
        let Some(end) = remainder.find("</Part>") else {
            break;
        };
        let part_xml = &remainder[..end];
        remainder = &remainder[end + "</Part>".len()..];
        let Some(part_number) = text_between(part_xml, "<PartNumber>", "</PartNumber>")
            .and_then(|value| value.parse::<u16>().ok())
        else {
            continue;
        };
        let Some(etag) = text_between(part_xml, "<ETag>", "</ETag>") else {
            continue;
        };
        parts.push(CompletedPart {
            part_number,
            etag: etag.trim_matches('"').to_string(),
        });
    }
    CompletedMultipartUpload { parts }
}

pub(crate) fn parse_copy_source_range(
    value: &str,
    total: usize,
) -> Result<ByteRange, RuntimeError> {
    parse_byte_range(value, total).map_err(|_| RuntimeError::InvalidRange(value.to_string()))
}

pub(crate) fn validate_part_number(part_number: u16) -> Result<(), RuntimeError> {
    if (1..=10_000).contains(&part_number) {
        Ok(())
    } else {
        Err(RuntimeError::InvalidPartNumber(part_number.to_string()))
    }
}

pub(crate) fn multipart_etag(body: &[u8]) -> String {
    sha256_hex(body)
}

pub(crate) fn multipart_complete_etag(parts: &[CompletedPart]) -> String {
    let seed = parts
        .iter()
        .map(|part| format!("{}:{}", part.part_number, part.etag))
        .collect::<Vec<_>>()
        .join("|");
    format!("{}-{}", sha256_hex(seed.as_bytes()), parts.len())
}