diff --git a/src/api/common/signature/payload.rs b/src/api/common/signature/payload.rs index 67e58811..ec4558e6 100644 --- a/src/api/common/signature/payload.rs +++ b/src/api/common/signature/payload.rs @@ -81,7 +81,7 @@ fn parse_x_amz_content_sha256(header: Option<&str>) -> Result { return Err(Error::bad_request( "invalid or unsupported x-amz-content-sha256", - )) + )); } }; Ok(ContentSha256Header::StreamingPayload { trailer, signed }) @@ -357,11 +357,18 @@ pub fn canonical_request( let canonical_header_string = signed_headers .iter() .map(|name| { - let value = headers - .get(name) + let all_values = headers.get_all(name); + let mut iter_values = all_values.iter(); + let base_value = iter_values + .next() .ok_or_bad_request(format!("signed header `{}` is not present", name))?; - let value = std::str::from_utf8(value.as_bytes())?; - Ok(format!("{}:{}", name.as_str(), value.trim())) + let mut built_string = std::str::from_utf8(base_value.as_bytes())?.to_string(); + for extend_value in iter_values { + let extend_string = std::str::from_utf8(extend_value.as_bytes())?; + built_string.push(','); + built_string.push_str(extend_string); + } + Ok(format!("{}:{}", name.as_str(), built_string.trim())) }) .collect::, Error>>()? .join("\n"); diff --git a/src/api/common/signature/streaming.rs b/src/api/common/signature/streaming.rs index e7af587c..5d2652fd 100644 --- a/src/api/common/signature/streaming.rs +++ b/src/api/common/signature/streaming.rs @@ -1,3 +1,4 @@ +use std::iter::FromIterator; use std::pin::Pin; use std::sync::Mutex; @@ -5,7 +6,7 @@ use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; use futures::prelude::*; use futures::task; use hmac::Mac; -use http::header::{HeaderMap, HeaderValue, CONTENT_ENCODING}; +use http::header::{Entry, HeaderMap, HeaderValue, CONTENT_ENCODING}; use hyper::body::{Bytes, Frame, Incoming as IncomingBody}; use hyper::Request; @@ -42,15 +43,52 @@ pub fn parse_streaming_body( // Remove the aws-chunked component in the content-encoding: header // Note: this header is not properly sent by minio client, so don't fail // if it is absent from the request. - if let Some(content_encoding) = req.headers_mut().remove(CONTENT_ENCODING) { - if let Some(rest) = content_encoding.as_bytes().strip_prefix(b"aws-chunked,") { - req.headers_mut() - .insert(CONTENT_ENCODING, HeaderValue::from_bytes(rest).unwrap()); - } else if content_encoding != "aws-chunked" { - return Err(Error::bad_request( - "content-encoding does not contain aws-chunked for STREAMING-*-PAYLOAD", - )); + let mut original_content_encoding = vec![]; + if let Entry::Occupied(content_encoding) = req.headers_mut().entry(CONTENT_ENCODING) { + // 1. collect headers + let (_, vals) = content_encoding.remove_entry_mult(); + original_content_encoding = Vec::from_iter(vals); + } + let mut header_initialized = false; + let mut chunked_found = false; + for enc_val in original_content_encoding.iter() { + // 2. clean each header value and reinject it. + let mut rebuilt_val = vec![]; + for part in enc_val.as_bytes().split(|c| *c == b',') { + let trimmed_part = part.trim_ascii(); + if trimmed_part == b"aws-chunked" { + chunked_found = true; + continue; + } + if !rebuilt_val.is_empty() { + rebuilt_val.push(b','); + } + rebuilt_val.extend_from_slice(trimmed_part); } + + if rebuilt_val.is_empty() { + // skip empty headers + continue; + } + + if !header_initialized { + req.headers_mut().insert( + CONTENT_ENCODING, + HeaderValue::from_bytes(&rebuilt_val).unwrap(), + ); + header_initialized = true; + } else { + req.headers_mut().append( + CONTENT_ENCODING, + HeaderValue::from_bytes(&rebuilt_val).unwrap(), + ); + } + } + + if !original_content_encoding.is_empty() && !chunked_found { + return Err(Error::bad_request( + "content-encoding does not contain aws-chunked for STREAMING-*-PAYLOAD", + )); } // If trailer header is announced, add the calculation of the requested checksum @@ -480,7 +518,7 @@ where continue; } Some(Err(e)) => { - return Poll::Ready(Some(Err(StreamingPayloadError::Stream(e)))) + return Poll::Ready(Some(Err(StreamingPayloadError::Stream(e)))); } None => { return Poll::Ready(Some(Err(StreamingPayloadError::message( @@ -490,7 +528,7 @@ where } } Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => { - return Poll::Ready(Some(Err(e))) + return Poll::Ready(Some(Err(e))); } };