限制附件元数据格式与长度
校验 inline 附件名称和媒体类型的控制字符、格式与长度。 共享附件清洗上限常量并补充异常元数据拒绝测试。
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
//! 有项目路径或导入状态时输出路径映射;否则保持首页元数据文案。不灌正文。
|
||||
|
||||
pub(crate) const MAX_DIRECT_CODEX_ATTACHMENTS: usize = 8;
|
||||
const MAX_DIRECT_CODEX_ATTACHMENT_NAME_CHARS: usize = 160;
|
||||
const MAX_DIRECT_CODEX_ATTACHMENT_MEDIA_TYPE_CHARS: usize = 96;
|
||||
const MAX_DIRECT_CODEX_ATTACHMENT_LOCAL_PATH_CHARS: usize = 512;
|
||||
pub(crate) const MAX_DIRECT_CODEX_ATTACHMENT_NAME_CHARS: usize = 160;
|
||||
pub(crate) const MAX_DIRECT_CODEX_ATTACHMENT_MEDIA_TYPE_CHARS: usize = 96;
|
||||
pub(crate) const MAX_DIRECT_CODEX_ATTACHMENT_LOCAL_PATH_CHARS: usize = 512;
|
||||
|
||||
const HOME_ATTACHMENT_HEADER: &str =
|
||||
"[首页附件说明:当前尚未打开项目,以下仅为附件元数据,附件内容尚不可读取]";
|
||||
|
||||
+66
-1
@@ -4,7 +4,8 @@ use super::model::{
|
||||
};
|
||||
use crate::agent::{
|
||||
read_manifest_for_project, sanitize_attachment_local_path, GameCreationAppManifest,
|
||||
MAX_DIRECT_CODEX_ATTACHMENTS,
|
||||
MAX_DIRECT_CODEX_ATTACHMENTS, MAX_DIRECT_CODEX_ATTACHMENT_MEDIA_TYPE_CHARS,
|
||||
MAX_DIRECT_CODEX_ATTACHMENT_NAME_CHARS,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -50,6 +51,22 @@ pub(crate) fn validate_direct_codex_user_item(
|
||||
if reference.name.trim().is_empty() {
|
||||
return Err("附件缺少文件名".to_string());
|
||||
}
|
||||
let name = reference.name.trim();
|
||||
if name.chars().count() > MAX_DIRECT_CODEX_ATTACHMENT_NAME_CHARS
|
||||
|| name.chars().any(char::is_control)
|
||||
{
|
||||
return Err("附件文件名无效或过长".to_string());
|
||||
}
|
||||
let media_type = reference.media_type.trim();
|
||||
if media_type.is_empty()
|
||||
|| media_type.chars().count() > MAX_DIRECT_CODEX_ATTACHMENT_MEDIA_TYPE_CHARS
|
||||
|| media_type.chars().any(|character| {
|
||||
!(character.is_ascii_alphanumeric()
|
||||
|| matches!(character, '/' | '+' | '-' | '.' | '_'))
|
||||
})
|
||||
{
|
||||
return Err("附件媒体类型无效或过长".to_string());
|
||||
}
|
||||
let status = reference.status.trim();
|
||||
if status == "imported" && reference.local_path.trim().is_empty() {
|
||||
return Err("已导入附件缺少项目路径".to_string());
|
||||
@@ -214,4 +231,52 @@ mod tests {
|
||||
.expect_err("imported attachment without a project path must fail");
|
||||
assert!(error.contains("缺少项目路径"), "{error}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attachment_name_and_media_type_are_bounded_and_well_formed() {
|
||||
let root = tempfile::tempdir().expect("temp project");
|
||||
crate::init_local_game_project_at(root.path(), "validation-test", "校验测试")
|
||||
.expect("init project");
|
||||
let long_name = "a".repeat(crate::agent::MAX_DIRECT_CODEX_ATTACHMENT_NAME_CHARS + 1);
|
||||
let cases = [
|
||||
(
|
||||
json!({
|
||||
"name": "bad\nname.txt",
|
||||
"mediaType": "text/plain"
|
||||
}),
|
||||
"文件名",
|
||||
),
|
||||
(
|
||||
json!({
|
||||
"name": "ok.txt",
|
||||
"mediaType": "text/plain\nsecret"
|
||||
}),
|
||||
"媒体类型",
|
||||
),
|
||||
(
|
||||
json!({
|
||||
"name": long_name,
|
||||
"mediaType": "text/plain"
|
||||
}),
|
||||
"文件名",
|
||||
),
|
||||
];
|
||||
for (metadata, expected) in cases {
|
||||
let mut value = metadata;
|
||||
value["type"] = json!("agc_attachment_reference");
|
||||
value["size"] = json!(1);
|
||||
value["localPath"] = json!("");
|
||||
value["status"] = json!("failed");
|
||||
let item = serde_json::from_value(json!({
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [value],
|
||||
"id": "turn-1:user"
|
||||
}))
|
||||
.expect("deserialize user item");
|
||||
let error = validate_direct_codex_user_item(root.path(), &item)
|
||||
.expect_err("invalid attachment metadata must fail");
|
||||
assert!(error.contains(expected), "{error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,8 +227,8 @@ mod tests {
|
||||
"id": "turn-1:user",
|
||||
"content": [{
|
||||
"type": "agc_attachment_reference",
|
||||
"name": "C:\\tmp\\notes\nsecret.md",
|
||||
"mediaType": "text/plain\nBearer secret",
|
||||
"name": "C:\\tmp\\notes.md",
|
||||
"mediaType": "text/plain",
|
||||
"size": 4,
|
||||
"localPath": "assets\\.\\notes.txt",
|
||||
"status": "imported"
|
||||
@@ -240,10 +240,9 @@ mod tests {
|
||||
)
|
||||
.expect("attachment metadata should project");
|
||||
let text = wire[0]["text"].as_str().expect("wire text");
|
||||
assert!(text.contains("名称=notessecret.md"), "{text}");
|
||||
assert!(text.contains("类型=application/octet-stream"), "{text}");
|
||||
assert!(text.contains("名称=notes.md"), "{text}");
|
||||
assert!(text.contains("类型=text/plain"), "{text}");
|
||||
assert!(text.contains("项目路径=assets/notes.txt"), "{text}");
|
||||
assert!(!text.contains("Bearer secret"), "{text}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user