fix(gateway): 兼容 OpenAI 图像 multipart boundary 大小写 (#331)

This commit is contained in:
Entropy.Xu
2026-04-24 21:41:20 +08:00
committed by GitHub
parent db1059e73b
commit 67b092253d
2 changed files with 100 additions and 13 deletions
@@ -866,11 +866,7 @@ fn parse_multipart_fields_from_base64(
.headers
.get(http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())?;
let boundary = content_type
.split(';')
.find_map(|segment| segment.trim().strip_prefix("boundary="))?
.trim_matches('"')
.to_string();
let boundary = multipart_boundary(content_type)?;
let body_bytes = base64::engine::general_purpose::STANDARD
.decode(body_base64)
.ok()?;
@@ -905,6 +901,17 @@ fn parse_multipart_fields(body: &[u8], boundary: &str) -> Vec<MultipartField> {
parts
}
fn multipart_boundary(content_type: &str) -> Option<String> {
content_type.split(';').find_map(|segment| {
let (key, value) = segment.trim().split_once('=')?;
if !key.trim().eq_ignore_ascii_case("boundary") {
return None;
}
let boundary = value.trim().trim_matches('"').trim();
(!boundary.is_empty()).then(|| boundary.to_string())
})
}
fn parse_multipart_field(raw: &[u8]) -> Option<MultipartField> {
let header_end = find_subslice(raw, b"\r\n\r\n")?;
let headers = &raw[..header_end];
@@ -1035,6 +1042,44 @@ mod tests {
);
}
#[test]
fn normalize_edit_multipart_request_accepts_mixed_case_boundary() {
let boundary = "------------------------OYNWsMZCt0ILTwn8naP4Gb";
let body = format!(
concat!(
"--{boundary}\r\n",
"Content-Disposition: form-data; name=\"model\"\r\n\r\n",
"gpt-image-2\r\n",
"--{boundary}\r\n",
"Content-Disposition: form-data; name=\"prompt\"\r\n\r\n",
"edit this image\r\n",
"--{boundary}\r\n",
"Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r\n",
"Content-Type: image/jpeg\r\n\r\n",
"image-bytes\r\n",
"--{boundary}--\r\n"
),
boundary = boundary,
);
let body_base64 = base64::engine::general_purpose::STANDARD.encode(body.as_bytes());
let parts = request_parts(
"/v1/images/edits",
Some(&format!("multipart/form-data; boundary={boundary}")),
);
let request = normalize_openai_image_request(&parts, &json!({}), Some(&body_base64))
.expect("edit request should normalize");
assert_eq!(request.operation, OpenAiImageOperation::Edit);
assert_eq!(request.requested_model.as_deref(), Some("gpt-image-2"));
assert_eq!(request.prompt.as_deref(), Some("edit this image"));
assert_eq!(request.images.len(), 1);
assert_eq!(
request.tool.get("action").and_then(|value| value.as_str()),
Some("edit")
);
}
#[test]
fn normalize_edit_json_request_maps_mask_to_input_image_mask() {
let parts = request_parts("/v1/images/edits", Some("application/json"));