aboutsummaryrefslogtreecommitdiff
path: root/src/api/s3_put.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/s3_put.rs')
-rw-r--r--src/api/s3_put.rs59
1 files changed, 52 insertions, 7 deletions
diff --git a/src/api/s3_put.rs b/src/api/s3_put.rs
index 5b5da3d2..4a2af919 100644
--- a/src/api/s3_put.rs
+++ b/src/api/s3_put.rs
@@ -11,14 +11,15 @@ use garage_table::*;
use garage_util::data::*;
use garage_util::error::Error as GarageError;
-use crate::error::*;
use garage_model::block::INLINE_THRESHOLD;
use garage_model::block_ref_table::*;
use garage_model::garage::Garage;
use garage_model::object_table::*;
use garage_model::version_table::*;
+use crate::error::*;
use crate::encoding::*;
+use crate::signature::verify_signed_content;
pub async fn handle_put(
garage: Arc<Garage>,
@@ -416,11 +417,19 @@ pub async fn handle_put_part(
pub async fn handle_complete_multipart_upload(
garage: Arc<Garage>,
- _req: Request<Body>,
+ req: Request<Body>,
bucket: &str,
key: &str,
upload_id: &str,
+ content_sha256: Option<Hash>,
) -> Result<Response<Body>, Error> {
+ let body = hyper::body::to_bytes(req.into_body()).await?;
+ verify_signed_content(content_sha256, &body[..])?;
+
+ let body_xml = roxmltree::Document::parse(&std::str::from_utf8(&body)?)?;
+ let body_list_of_parts = parse_complete_multpart_upload_body(&body_xml).ok_or_bad_request("Invalid CompleteMultipartUpload XML")?;
+ debug!("CompleteMultipartUpload list of parts: {:?}", body_list_of_parts);
+
let version_uuid = decode_upload_id(upload_id)?;
let bucket = bucket.to_string();
@@ -450,6 +459,16 @@ pub async fn handle_complete_multipart_upload(
_ => unreachable!(),
};
+ // Check that the list of parts they gave us corresponds to the parts we have here
+ // TODO: check MD5 sum of all uploaded parts? but that would mean we have to store them somewhere...
+ let mut parts = version.blocks().iter().map(|x| x.part_number)
+ .collect::<Vec<_>>();
+ parts.dedup();
+ let same_parts = body_list_of_parts.iter().map(|x| &x.part_number).eq(parts.iter());
+ if !same_parts {
+ return Err(Error::BadRequest(format!("We don't have the same parts")));
+ }
+
// ETag calculation: we produce ETags that have the same form as
// those of S3 multipart uploads, but we don't use their actual
// calculation for the first part (we use random bytes). This
@@ -465,11 +484,6 @@ pub async fn handle_complete_multipart_upload(
num_parts
);
- // TODO: check that all the parts that they pretend they gave us are indeed there
- // TODO: when we read the XML from _req, remember to check the sha256 sum of the payload
- // against the signed x-amz-content-sha256
- // TODO: check MD5 sum of all uploaded parts? but that would mean we have to store them somewhere...
-
let total_size = version
.blocks()
.iter()
@@ -583,3 +597,34 @@ fn decode_upload_id(id: &str) -> Result<UUID, Error> {
uuid.copy_from_slice(&id_bin[..]);
Ok(UUID::from(uuid))
}
+
+#[derive(Debug)]
+struct CompleteMultipartUploadPart {
+ etag: String,
+ part_number: u64,
+}
+
+fn parse_complete_multpart_upload_body(xml: &roxmltree::Document) -> Option<Vec<CompleteMultipartUploadPart>> {
+ let mut parts = vec![];
+
+ let root = xml.root();
+ let cmu = root.first_child()?;
+ if !cmu.has_tag_name("CompleteMultipartUpload") {
+ return None;
+ }
+
+ for item in cmu.children() {
+ if item.has_tag_name("Part") {
+ let etag = item.children().find(|e| e.has_tag_name("ETag"))?.text()?;
+ let part_number = item.children().find(|e| e.has_tag_name("PartNumber"))?.text()?;
+ parts.push(CompleteMultipartUploadPart{
+ etag: etag.trim_matches('"').to_string(),
+ part_number: part_number.parse().ok()?,
+ });
+ } else {
+ return None;
+ }
+ }
+
+ Some(parts)
+}