Support for PostObject #222
2 changed files with 84 additions and 61 deletions
|
@ -39,7 +39,8 @@ pub async fn handle_post_object(
|
||||||
.for_field("file", 5 * 1024 * 1024 * 1024),
|
.for_field("file", 5 * 1024 * 1024 * 1024),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut multipart = Multipart::with_constraints(req.into_body(), boundary, constraints);
|
let (_head, body) = req.into_parts();
|
||||||
|
let mut multipart = Multipart::with_constraints(body, boundary, constraints);
|
||||||
|
|
||||||
let mut params = HeaderMap::new();
|
let mut params = HeaderMap::new();
|
||||||
while let Some(field) = multipart.next_field().await? {
|
while let Some(field) = multipart.next_field().await? {
|
||||||
|
@ -56,6 +57,7 @@ pub async fn handle_post_object(
|
||||||
params.append("x-amz-acl", content);
|
params.append("x-amz-acl", content);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
// TODO actually that's illegal to have the same param multiple times
|
||||||
params.append(name, content);
|
params.append(name, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,57 +124,65 @@ pub async fn handle_post_object(
|
||||||
let conditions = decoded_policy.into_conditions()?;
|
let conditions = decoded_policy.into_conditions()?;
|
||||||
|
|
||||||
for (param_key, value) in params.iter() {
|
for (param_key, value) in params.iter() {
|
||||||
let param_key = param_key.as_str();
|
let mut param_key = param_key.to_string();
|
||||||
if param_key.eq_ignore_ascii_case("content-type") {
|
param_key.make_ascii_lowercase();
|
||||||
for cond in &conditions.content_type {
|
match param_key.as_str() {
|
||||||
let ok = match cond {
|
"policy" | "x-amz-signature" => (), // this is always accepted, as it's required to validate other fields
|
||||||
Operation::Equal(s) => value == s,
|
"content-type" => {
|
||||||
Operation::StartsWith(s) => {
|
for cond in &conditions.content_type {
|
||||||
value.to_str()?.split(',').all(|v| v.starts_with(s))
|
let ok = match cond {
|
||||||
|
Operation::Equal(s) => value == s,
|
||||||
|
Operation::StartsWith(s) => {
|
||||||
|
value.to_str()?.split(',').all(|v| v.starts_with(s))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !ok {
|
||||||
|
return Err(Error::BadRequest(format!(
|
||||||
|
"Key '{}' has value not allowed in policy",
|
||||||
|
param_key
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if !ok {
|
|
||||||
return Err(Error::BadRequest(format!(
|
|
||||||
"Key '{}' has value not allowed in policy",
|
|
||||||
param_key
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if param_key == "key" {
|
"key" => {
|
||||||
let conds = conditions.params.get("key").ok_or_else(|| {
|
let conds = conditions.params.get("key").ok_or_else(|| {
|
||||||
Error::BadRequest(format!("Key '{}' is not allowed in policy", param_key))
|
Error::BadRequest(format!("Key '{}' is not allowed in policy", param_key))
|
||||||
})?;
|
})?;
|
||||||
for cond in conds {
|
for cond in conds {
|
||||||
let ok = match cond {
|
let ok = match cond {
|
||||||
Operation::Equal(s) => s == &key,
|
Operation::Equal(s) => s == &key,
|
||||||
Operation::StartsWith(s) => key.starts_with(s),
|
Operation::StartsWith(s) => key.starts_with(s),
|
||||||
};
|
};
|
||||||
if !ok {
|
if !ok {
|
||||||
return Err(Error::BadRequest(format!(
|
return Err(Error::BadRequest(format!(
|
||||||
"Key '{}' has value not allowed in policy",
|
"Key '{}' has value not allowed in policy",
|
||||||
param_key
|
param_key
|
||||||
)));
|
)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
_ => {
|
||||||
let conds = conditions.params.get(param_key).ok_or_else(|| {
|
let conds = conditions.params.get(¶m_key).ok_or_else(|| {
|
||||||
Error::BadRequest(format!("Key '{}' is not allowed in policy", param_key))
|
Error::BadRequest(format!("Key '{}' is not allowed in policy", param_key))
|
||||||
})?;
|
})?;
|
||||||
for cond in conds {
|
for cond in conds {
|
||||||
let ok = match cond {
|
let ok = match cond {
|
||||||
Operation::Equal(s) => s == value,
|
Operation::Equal(s) => s == value,
|
||||||
Operation::StartsWith(s) => value.to_str()?.starts_with(s),
|
Operation::StartsWith(s) => value.to_str()?.starts_with(s),
|
||||||
};
|
};
|
||||||
if !ok {
|
if !ok {
|
||||||
return Err(Error::BadRequest(format!(
|
return Err(Error::BadRequest(format!(
|
||||||
"Key '{}' has value not allowed in policy",
|
"Key '{}' has value not allowed in policy",
|
||||||
param_key
|
param_key
|
||||||
)));
|
)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO check that each policy item is used
|
||||||
|
|
||||||
let content_type = field
|
let content_type = field
|
||||||
.content_type()
|
.content_type()
|
||||||
.map(AsRef::as_ref)
|
.map(AsRef::as_ref)
|
||||||
|
@ -185,7 +195,7 @@ pub async fn handle_post_object(
|
||||||
let headers = get_headers(¶ms)?;
|
let headers = get_headers(¶ms)?;
|
||||||
|
|
||||||
let stream = field.map(|r| r.map_err(Into::into));
|
let stream = field.map(|r| r.map_err(Into::into));
|
||||||
let res = save_stream(
|
let (_, md5) = save_stream(
|
||||||
garage,
|
garage,
|
||||||
headers,
|
headers,
|
||||||
StreamLimiter::new(stream, conditions.content_length),
|
StreamLimiter::new(stream, conditions.content_length),
|
||||||
|
@ -196,35 +206,45 @@ pub async fn handle_post_object(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let resp = if let Some(target) = params
|
let etag = format!("\"{}\"", md5);
|
||||||
|
// TODO get uri
|
||||||
|
// get Host
|
||||||
|
// append www-form-urlencoded key
|
||||||
|
let location = "todo";
|
||||||
|
|
||||||
|
let resp = if let Some(mut target) = params
|
||||||
.get("success_action_redirect")
|
.get("success_action_redirect")
|
||||||
.and_then(|h| h.to_str().ok())
|
.and_then(|h| h.to_str().ok())
|
||||||
.and_then(|u| url::Url::parse(u).ok())
|
.and_then(|u| url::Url::parse(u).ok())
|
||||||
.filter(|u| u.scheme() == "https" || u.scheme() == "http")
|
.filter(|u| u.scheme() == "https" || u.scheme() == "http")
|
||||||
{
|
{
|
||||||
|
target
|
||||||
|
.query_pairs_mut()
|
||||||
|
.append_pair("bucket", &bucket)
|
||||||
|
.append_pair("key", &key)
|
||||||
|
.append_pair("etag", &etag);
|
||||||
let target = target.to_string();
|
let target = target.to_string();
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::SEE_OTHER)
|
.status(StatusCode::SEE_OTHER)
|
||||||
.header(header::LOCATION, target.clone())
|
.header(header::LOCATION, target.clone())
|
||||||
|
.header(header::ETAG, etag)
|
||||||
.body(target.into())?
|
.body(target.into())?
|
||||||
} else {
|
} else {
|
||||||
let action = params
|
let action = params
|
||||||
.get("success_action_status")
|
.get("success_action_status")
|
||||||
.and_then(|h| h.to_str().ok())
|
.and_then(|h| h.to_str().ok())
|
||||||
.unwrap_or("204");
|
.unwrap_or("204");
|
||||||
|
let builder = Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header(header::LOCATION, location)
|
||||||
|
.header(header::ETAG, etag);
|
||||||
match action {
|
match action {
|
||||||
"200" => Response::builder()
|
"200" => builder.status(StatusCode::OK).body(Body::empty())?,
|
||||||
.status(StatusCode::OK)
|
|
||||||
.body(Body::empty())?,
|
|
||||||
"201" => {
|
"201" => {
|
||||||
// TODO body should be an XML document, not sure which yet
|
// TODO body should be an XML document, not sure which yet
|
||||||
Response::builder()
|
builder.status(StatusCode::CREATED).body(Body::from(""))?
|
||||||
.status(StatusCode::CREATED)
|
|
||||||
.body(res.into_body())?
|
|
||||||
}
|
}
|
||||||
_ => Response::builder()
|
_ => builder.status(StatusCode::NO_CONTENT).body(Body::empty())?,
|
||||||
.status(StatusCode::NO_CONTENT)
|
|
||||||
.body(Body::empty())?,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -254,8 +274,9 @@ impl Policy {
|
||||||
if map.len() != 1 {
|
if map.len() != 1 {
|
||||||
return Err(Error::BadRequest("Invalid policy item".to_owned()));
|
return Err(Error::BadRequest("Invalid policy item".to_owned()));
|
||||||
}
|
}
|
||||||
let (k, v) = map.into_iter().next().expect("size was verified");
|
let (mut k, v) = map.into_iter().next().expect("size was verified");
|
||||||
if k.eq_ignore_ascii_case("content-type") {
|
k.make_ascii_lowercase();
|
||||||
|
if k == "content-type" {
|
||||||
content_type.push(Operation::Equal(v));
|
content_type.push(Operation::Equal(v));
|
||||||
} else {
|
} else {
|
||||||
params.entry(k).or_default().push(Operation::Equal(v));
|
params.entry(k).or_default().push(Operation::Equal(v));
|
||||||
|
@ -265,16 +286,17 @@ impl Policy {
|
||||||
if key.remove(0) != '$' {
|
if key.remove(0) != '$' {
|
||||||
return Err(Error::BadRequest("Invalid policy item".to_owned()));
|
return Err(Error::BadRequest("Invalid policy item".to_owned()));
|
||||||
}
|
}
|
||||||
|
key.make_ascii_lowercase();
|
||||||
match cond.as_str() {
|
match cond.as_str() {
|
||||||
"eq" => {
|
"eq" => {
|
||||||
if key.eq_ignore_ascii_case("content-type") {
|
if key == "content-type" {
|
||||||
content_type.push(Operation::Equal(value));
|
content_type.push(Operation::Equal(value));
|
||||||
} else {
|
} else {
|
||||||
params.entry(key).or_default().push(Operation::Equal(value));
|
params.entry(key).or_default().push(Operation::Equal(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"starts-with" => {
|
"starts-with" => {
|
||||||
if key.eq_ignore_ascii_case("content-type") {
|
if key == "content-type" {
|
||||||
content_type.push(Operation::StartsWith(value));
|
content_type.push(Operation::StartsWith(value));
|
||||||
} else {
|
} else {
|
||||||
params
|
params
|
||||||
|
|
|
@ -99,6 +99,7 @@ pub async fn handle_put(
|
||||||
content_sha256,
|
content_sha256,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.map(|(uuid, md5)| put_response(uuid, md5))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>(
|
pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>(
|
||||||
|
@ -109,7 +110,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>(
|
||||||
key: &str,
|
key: &str,
|
||||||
content_md5: Option<String>,
|
content_md5: Option<String>,
|
||||||
content_sha256: Option<FixedBytes32>,
|
content_sha256: Option<FixedBytes32>,
|
||||||
) -> Result<Response<Body>, Error> {
|
) -> Result<(Uuid, String), Error> {
|
||||||
// Generate identity of new version
|
// Generate identity of new version
|
||||||
let version_uuid = gen_uuid();
|
let version_uuid = gen_uuid();
|
||||||
let version_timestamp = now_msec();
|
let version_timestamp = now_msec();
|
||||||
|
@ -150,7 +151,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>(
|
||||||
let object = Object::new(bucket_id, key.into(), vec![object_version]);
|
let object = Object::new(bucket_id, key.into(), vec![object_version]);
|
||||||
garage.object_table.insert(&object).await?;
|
garage.object_table.insert(&object).await?;
|
||||||
|
|
||||||
return Ok(put_response(version_uuid, data_md5sum_hex));
|
return Ok((version_uuid, data_md5sum_hex));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write version identifier in object table so that we have a trace
|
// Write version identifier in object table so that we have a trace
|
||||||
|
@ -216,7 +217,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>(
|
||||||
let object = Object::new(bucket_id, key.into(), vec![object_version]);
|
let object = Object::new(bucket_id, key.into(), vec![object_version]);
|
||||||
garage.object_table.insert(&object).await?;
|
garage.object_table.insert(&object).await?;
|
||||||
|
|
||||||
Ok(put_response(version_uuid, md5sum_hex))
|
Ok((version_uuid, md5sum_hex))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate MD5 sum against content-md5 header
|
/// Validate MD5 sum against content-md5 header
|
||||||
|
@ -512,7 +513,7 @@ pub async fn handle_put_part(
|
||||||
|
|
||||||
let response = Response::builder()
|
let response = Response::builder()
|
||||||
.header("ETag", format!("\"{}\"", data_md5sum_hex))
|
.header("ETag", format!("\"{}\"", data_md5sum_hex))
|
||||||
.body(Body::from(vec![]))
|
.body(Body::empty())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue