From 48c5638893ed03234f597e20a5f83e9cb2a9123d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 11 Sep 2026 17:28:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=A9=E5=A4=A7=E5=8E=9F=E5=A7=8B=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=96=87=E6=9C=AC=E5=AD=97=E6=AE=B5=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit multipart 文本字段改为 chunk 流式读取并限制 16KB。 统一 UTF-8 校验和超限错误,避免先完整分配超长 prompt。 --- server-rs/crates/api-server/src/raw_image.rs | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/server-rs/crates/api-server/src/raw_image.rs b/server-rs/crates/api-server/src/raw_image.rs index 1b071e05e..7bde75405 100644 --- a/server-rs/crates/api-server/src/raw_image.rs +++ b/server-rs/crates/api-server/src/raw_image.rs @@ -58,7 +58,7 @@ pub(crate) struct RawImageEditResponse { pub(crate) data: Vec, } -const RAW_IMAGE_MAX_PROMPT_BYTES: usize = 4 * 1024; +const RAW_IMAGE_MAX_TEXT_FIELD_BYTES: usize = 16 * 1024; pub(crate) async fn edit_raw_image( State(state): State, @@ -244,15 +244,32 @@ async fn read_multipart_image( async fn set_text_field( target: &mut Option, - field: axum::extract::multipart::Field<'_>, + mut field: axum::extract::multipart::Field<'_>, name: &str, ) -> Result<(), AppError> { if target.is_some() { return Err(bad_request(format!("{name} 字段不能重复"))); } - *target = Some(field.text().await.map_err(|error| { + let mut bytes = Vec::new(); + while let Some(chunk) = field.chunk().await.map_err(|error| { tracing::warn!(field = name, error = %error, "raw image multipart 文本读取失败"); bad_request(format!("{name} 字段读取失败")) + })? { + if bytes.len().saturating_add(chunk.len()) > RAW_IMAGE_MAX_TEXT_FIELD_BYTES { + tracing::warn!( + field = name, + limit_bytes = RAW_IMAGE_MAX_TEXT_FIELD_BYTES, + "raw image multipart 文本字段超过大小限制" + ); + return Err(bad_request(format!( + "{name} 字段不能超过 {RAW_IMAGE_MAX_TEXT_FIELD_BYTES} 字节" + ))); + } + bytes.extend_from_slice(&chunk); + } + *target = Some(String::from_utf8(bytes).map_err(|error| { + tracing::warn!(field = name, error = %error, "raw image multipart 文本字段不是有效 UTF-8"); + bad_request(format!("{name} 字段必须为有效 UTF-8 文本")) })?); Ok(()) } @@ -269,9 +286,9 @@ fn prepare_request(payload: RawImageEditRequest) -> Result RAW_IMAGE_MAX_PROMPT_BYTES { + if payload.prompt.len() > RAW_IMAGE_MAX_TEXT_FIELD_BYTES { return Err(bad_request(format!( - "prompt 不能超过 {RAW_IMAGE_MAX_PROMPT_BYTES} 字节" + "prompt 不能超过 {RAW_IMAGE_MAX_TEXT_FIELD_BYTES} 字节" ))); } validate_raw_image_edit_dimensions(payload.width, payload.height) @@ -567,12 +584,12 @@ mod tests { #[test] fn prompt_uses_raw_utf8_byte_limit() { let mut parsed = request(png_bytes(1, 1), None); - parsed.prompt = "a".repeat(RAW_IMAGE_MAX_PROMPT_BYTES + 1); + parsed.prompt = "a".repeat(RAW_IMAGE_MAX_TEXT_FIELD_BYTES + 1); let error = match prepare_request(parsed) { Ok(_) => panic!("oversized prompt should fail before image decode and billing"), Err(error) => error, }; - assert!(format!("{error:?}").contains("prompt 不能超过 4096 字节")); + assert!(format!("{error:?}").contains("prompt 不能超过 16384 字节")); } }