Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
29f61abc
authored
Dec 29, 2025
by
Roy
Committed by
GitHub
Dec 29, 2025
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: santinize the upload filename (#6159)
parent
4ea50bc3
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
77 additions
and
4 deletions
+77
-4
packages/service/common/s3/utils.ts
+12
-1
packages/service/core/workflow/dispatch/ai/tool/index.ts
+0
-1
projects/app/src/pages/api/common/file/presignAvatarPostUrl.ts
+6
-1
projects/app/src/pages/api/core/chat/presignChatFilePostUrl.ts
+6
-1
test/cases/service/common/s3/utils.test.ts
+47
-0
test/cases/service/core/dataset/utils.test.ts
+6
-0
No files found.
packages/service/common/s3/utils.ts
View file @
29f61abc
...
@@ -151,7 +151,7 @@ const getFormatedFilename = (filename?: string) => {
...
@@ -151,7 +151,7 @@ const getFormatedFilename = (filename?: string) => {
// 先截断文件名,再进行格式化
// 先截断文件名,再进行格式化
const
truncatedFilename
=
truncateFilename
(
filename
);
const
truncatedFilename
=
truncateFilename
(
filename
);
const
extension
=
path
.
extname
(
truncatedFilename
);
// 带.
const
extension
=
path
.
extname
(
truncatedFilename
);
// 带.
const
name
=
path
.
basename
(
truncatedFilename
,
extension
);
const
name
=
sanitizeS3ObjectKey
(
path
.
basename
(
truncatedFilename
,
extension
)
);
return
{
return
{
formatedFilename
:
`
${
id
}
-
${
name
}
`
,
formatedFilename
:
`
${
id
}
-
${
name
}
`
,
extension
:
extension
.
replace
(
'.'
,
''
)
extension
:
extension
.
replace
(
'.'
,
''
)
...
@@ -245,3 +245,14 @@ export function isS3ObjectKey<T extends keyof typeof S3Sources>(
...
@@ -245,3 +245,14 @@ export function isS3ObjectKey<T extends keyof typeof S3Sources>(
):
key
is
`
${
T
}
/
${
string
}
`
{
):
key
is
`
${
T
}
/
${
string
}
`
{
return
typeof
key
===
'string'
&&
key
.
startsWith
(
`
${
S3Sources
[
source
]}
/`
);
return
typeof
key
===
'string'
&&
key
.
startsWith
(
`
${
S3Sources
[
source
]}
/`
);
}
}
export
function
sanitizeS3ObjectKey
(
key
:
string
)
{
// 替换掉圆括号
const
replaceParentheses
=
(
key
:
string
)
=>
{
return
key
.
replace
(
/
[
()
]
/g
,
(
match
)
=>
(
match
===
'('
?
'['
:
']'
));
};
key
=
replaceParentheses
(
key
);
return
key
;
}
packages/service/core/workflow/dispatch/ai/tool/index.ts
View file @
29f61abc
...
@@ -31,7 +31,6 @@ import { postTextCensor } from '../../../../chat/postTextCensor';
...
@@ -31,7 +31,6 @@ import { postTextCensor } from '../../../../chat/postTextCensor';
import
type
{
FlowNodeInputItemType
}
from
'@fastgpt/global/core/workflow/type/io'
;
import
type
{
FlowNodeInputItemType
}
from
'@fastgpt/global/core/workflow/type/io'
;
import
type
{
McpToolDataType
}
from
'@fastgpt/global/core/app/tool/mcpTool/type'
;
import
type
{
McpToolDataType
}
from
'@fastgpt/global/core/app/tool/mcpTool/type'
;
import
type
{
JSONSchemaInputType
}
from
'@fastgpt/global/core/app/jsonschema'
;
import
type
{
JSONSchemaInputType
}
from
'@fastgpt/global/core/app/jsonschema'
;
import
{
getFileS3Key
}
from
'../../../../../common/s3/utils'
;
type
Response
=
DispatchNodeResultType
<
{
type
Response
=
DispatchNodeResultType
<
{
[
NodeOutputKeyEnum
.
answerText
]:
string
;
[
NodeOutputKeyEnum
.
answerText
]:
string
;
...
...
projects/app/src/pages/api/common/file/presignAvatarPostUrl.ts
View file @
29f61abc
...
@@ -18,8 +18,13 @@ async function handler(
...
@@ -18,8 +18,13 @@ async function handler(
_
:
ApiResponseType
<
updateAvatarResponse
>
_
:
ApiResponseType
<
updateAvatarResponse
>
):
Promise
<
updateAvatarResponse
>
{
):
Promise
<
updateAvatarResponse
>
{
const
{
filename
,
autoExpired
}
=
req
.
body
;
const
{
filename
,
autoExpired
}
=
req
.
body
;
const
{
teamId
}
=
await
authCert
({
req
,
authToken
:
true
});
const
{
teamId
}
=
await
authCert
({
req
,
authToken
:
true
});
return
await
getS3AvatarSource
().
createUploadAvatarURL
({
teamId
,
filename
,
autoExpired
});
return
await
getS3AvatarSource
().
createUploadAvatarURL
({
teamId
,
filename
,
autoExpired
});
}
}
export
default
NextAPI
(
handler
);
export
default
NextAPI
(
handler
);
projects/app/src/pages/api/core/chat/presignChatFilePostUrl.ts
View file @
29f61abc
...
@@ -30,7 +30,12 @@ async function handler(
...
@@ -30,7 +30,12 @@ async function handler(
});
});
await
authUploadLimit
(
uid
);
await
authUploadLimit
(
uid
);
return
await
getS3ChatSource
().
createUploadChatFileURL
({
appId
,
chatId
,
filename
,
uId
:
uid
});
return
await
getS3ChatSource
().
createUploadChatFileURL
({
appId
,
chatId
,
filename
,
uId
:
uid
});
}
}
export
default
NextAPI
(
handler
);
export
default
NextAPI
(
handler
);
test/cases/service/common/s3/utils.test.ts
0 → 100644
View file @
29f61abc
import
{
describe
,
it
,
expect
}
from
'vitest'
;
import
{
sanitizeS3ObjectKey
}
from
'@fastgpt/service/common/s3/utils'
;
describe
(
'sanitizeS3ObjectKey'
,
()
=>
{
it
(
'should replace parentheses with square brackets'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
'file(1).txt'
)).
toBe
(
'file[1].txt'
);
expect
(
sanitizeS3ObjectKey
(
'photo (copy).jpg'
)).
toBe
(
'photo [copy].jpg'
);
expect
(
sanitizeS3ObjectKey
(
'document(v2)(final).pdf'
)).
toBe
(
'document[v2][final].pdf'
);
});
it
(
'should replace opening parenthesis with opening bracket'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
'test('
)).
toBe
(
'test['
);
expect
(
sanitizeS3ObjectKey
(
'((test'
)).
toBe
(
'[[test'
);
});
it
(
'should replace closing parenthesis with closing bracket'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
'test)'
)).
toBe
(
'test]'
);
expect
(
sanitizeS3ObjectKey
(
'test))'
)).
toBe
(
'test]]'
);
});
it
(
'should handle multiple parentheses'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
'a(b)c(d)e'
)).
toBe
(
'a[b]c[d]e'
);
expect
(
sanitizeS3ObjectKey
(
'((()))'
)).
toBe
(
'[[[]]]'
);
});
it
(
'should return unchanged string when no parentheses present'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
'normal-file.txt'
)).
toBe
(
'normal-file.txt'
);
expect
(
sanitizeS3ObjectKey
(
'path/to/file.jpg'
)).
toBe
(
'path/to/file.jpg'
);
expect
(
sanitizeS3ObjectKey
(
'file_name_123.pdf'
)).
toBe
(
'file_name_123.pdf'
);
});
it
(
'should handle empty string'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
''
)).
toBe
(
''
);
});
it
(
'should preserve existing square brackets'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
'file[1].txt'
)).
toBe
(
'file[1].txt'
);
expect
(
sanitizeS3ObjectKey
(
'file[1](2).txt'
)).
toBe
(
'file[1][2].txt'
);
});
it
(
'should handle S3 key paths with parentheses'
,
()
=>
{
expect
(
sanitizeS3ObjectKey
(
'dataset/uploads/file (1).pdf'
)).
toBe
(
'dataset/uploads/file [1].pdf'
);
expect
(
sanitizeS3ObjectKey
(
'chat/images/photo(copy).jpg'
)).
toBe
(
'chat/images/photo[copy].jpg'
);
});
});
test/cases/service/core/dataset/utils.test.ts
View file @
29f61abc
...
@@ -265,6 +265,12 @@ describe('replaceS3KeyToPreviewUrl', () => {
...
@@ -265,6 +265,12 @@ describe('replaceS3KeyToPreviewUrl', () => {
expect
(
result
).
toContain
(
'mock-jwt-token-dataset/team1/file{1}.png'
);
expect
(
result
).
toContain
(
'mock-jwt-token-dataset/team1/file{1}.png'
);
});
});
it
(
'文件名包含方括号应正常处理'
,
()
=>
{
const
text
=
''
;
const
result
=
replaceS3KeyToPreviewUrl
(
text
,
expiredTime
);
expect
(
result
).
toContain
(
'mock-jwt-token-dataset/team1/file[1].png'
);
});
// 引号
// 引号
it
(
'alt 文本包含单引号应正常处理'
,
()
=>
{
it
(
'alt 文本包含单引号应正常处理'
,
()
=>
{
const
text
=
""
;
const
text
=
""
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment