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("e_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("e_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())
}