Fix/fix issues #1564 (#1708)

Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
houseme
2026-02-05 13:45:14 +08:00
committed by GitHub
parent e30781654d
commit 6bba41f11f
13 changed files with 956 additions and 720 deletions
-135
View File
@@ -18,7 +18,6 @@ use crate::config::workload_profiles::{
use crate::error::ApiError;
use crate::server::cors;
use crate::storage::ecfs::ListObjectUnorderedQuery;
use axum::body::Body;
use http::{HeaderMap, HeaderValue, StatusCode};
use metrics::counter;
use rustfs_ecstore::bucket::metadata_sys;
@@ -91,140 +90,6 @@ pub(crate) fn apply_lock_retention(object_lock_config: Option<ObjectLockConfigur
}
}
/// =======================
/// Presigned POST helpers
/// =======================
///
/// AWS S3 RESTObjectPOST (HTML form upload) semantics:
/// - Default success response is 204 (No Content)
/// - If `success_action_status` is specified, it may be 200, 201, or 204
/// - If `success_action_redirect` is specified, respond with 303 and Location header.
///
/// Reference:
/// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) enum PostObjectSuccessAction {
#[default]
NoContent204,
Ok200,
Created201,
Redirect303 {
location: String,
},
}
/// Parse success action from Presigned POST form fields.
///
/// Integration point (manual):
/// - In the PostPolicy handler, after parsing form fields, call this function to determine the desired success action.
///
/// # Arguments
/// * `fields` - HashMap of form fields from the POST request
///
/// # Returns
/// * `S3Result<PostObjectSuccessAction>` - Parsed success action or error
///
/// Notes:
/// - Follows AWS S3 behavior: `success_action_redirect` takes precedence over `success_action_status`.
/// - Validates `success_action_status` values; invalid values result in MalformedPOSTRequest error.
///
#[allow(dead_code)]
pub(crate) fn parse_success_action_from_form_fields(fields: &HashMap<String, String>) -> S3Result<PostObjectSuccessAction> {
// 1) success_action_redirect wins over success_action_status (AWS compatible behavior).
if let Some(loc) = fields
.get("success_action_redirect")
.map(|s| s.trim())
.filter(|s| !s.is_empty())
{
return Ok(PostObjectSuccessAction::Redirect303 {
location: loc.to_string(),
});
}
// 2) success_action_status is optional; default is 204.
let Some(status_str) = fields
.get("success_action_status")
.map(|s| s.trim())
.filter(|s| !s.is_empty())
else {
return Ok(PostObjectSuccessAction::NoContent204);
};
// AWS allows only 200/201/204 for POST form success_action_status.
// Treat invalid values as MalformedPOSTRequest to match S3 strictness.
match status_str {
"200" => Ok(PostObjectSuccessAction::Ok200),
"201" => Ok(PostObjectSuccessAction::Created201),
"204" => Ok(PostObjectSuccessAction::NoContent204),
_ => Err(S3Error::with_message(
S3ErrorCode::MalformedPOSTRequest,
format!("Invalid success_action_status: {status_str}. Allowed values are 200, 201, 204."),
)),
}
}
/// Build the final S3Response for a successful Presigned POST upload.
///
/// Integration point (manual):
/// - After `put_object` succeeds in the PostPolicy handler, call this function
/// with parsed form fields + object info to produce the correct HTTP status.
///
/// Notes:
/// - For 204: empty body
/// - For 303: empty body + Location header
/// - For 200: empty body (some clients accept this); you may optionally return XML/HTML body if you already implement it.
/// - For 201: prefer returning PostResponse XML; if not available, empty body still satisfies most clients, but strict tests may require XML. If you have a PostResponse serializer already, plug it in here.
#[allow(dead_code)]
pub(crate) fn build_post_object_success_response(
form_fields: &HashMap<String, String>,
// These are optional; used if you want to return richer responses for 200/201.
bucket: &str,
key: &str,
etag: Option<&str>,
location: Option<&str>,
) -> S3Result<S3Response<(StatusCode, Body)>> {
let action = parse_success_action_from_form_fields(form_fields)?;
match action {
PostObjectSuccessAction::NoContent204 => Ok(S3Response::new((StatusCode::NO_CONTENT, Body::empty()))),
PostObjectSuccessAction::Redirect303 { location } => {
let mut headers = HeaderMap::new();
headers.insert(
http::header::LOCATION,
HeaderValue::from_str(&location)
.map_err(|_| S3Error::with_message(S3ErrorCode::InvalidArgument, "Invalid success_action_redirect URL"))?,
);
Ok(S3Response::with_headers((StatusCode::SEE_OTHER, Body::empty()), headers))
}
PostObjectSuccessAction::Ok200 => {
// AWS may return 200 with an HTML/redirect response for browser workflows.
// For compatibility, returning empty body is acceptable unless strict clients require content.
// Keep Content-Length implicit; Body::empty() -> 0.
Ok(S3Response::new((StatusCode::OK, Body::empty())))
}
PostObjectSuccessAction::Created201 => {
// AWS 201 response is XML:
// <PostResponse>
// <Location>...</Location>
// <Bucket>...</Bucket>
// <Key>...</Key>
// <ETag>...</ETag>
// </PostResponse>
//
// If RustFS already has a DTO for this, switch to it here.
// To keep this patch minimal and safe, return empty body with 201 by default.
//
// IMPORTANT: If you run strict s3-tests for POST Object, you may need to implement XML body.
let _ = (bucket, key, etag, location);
Ok(S3Response::new((StatusCode::CREATED, Body::empty())))
}
}
}
/// Calculate adaptive buffer size with workload profile support.
///
/// This enhanced version supports different workload profiles for optimal performance