mirror of https://github.com/usememos/memos.git
fix: normalize attachment MIME types before validation
This commit is contained in:
parent
aafcc21ae6
commit
c3e7e2c316
|
|
@ -82,24 +82,25 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
|
|||
if !validateFilename(request.Attachment.Filename) {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "filename contains invalid characters or format")
|
||||
}
|
||||
if request.Attachment.Type == "" {
|
||||
normalizedMimeType := request.Attachment.Type
|
||||
if normalizedMimeType == "" {
|
||||
ext := filepath.Ext(request.Attachment.Filename)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
mimeType = http.DetectContentType(request.Attachment.Content)
|
||||
}
|
||||
// ParseMediaType to strip parameters
|
||||
mediaType, _, err := mime.ParseMediaType(mimeType)
|
||||
if err == nil {
|
||||
request.Attachment.Type = mediaType
|
||||
if normalizedType, ok := normalizeMimeType(mimeType); ok {
|
||||
normalizedMimeType = normalizedType
|
||||
}
|
||||
}
|
||||
if request.Attachment.Type == "" {
|
||||
request.Attachment.Type = "application/octet-stream"
|
||||
if normalizedMimeType == "" {
|
||||
normalizedMimeType = "application/octet-stream"
|
||||
}
|
||||
if !isValidMimeType(request.Attachment.Type) {
|
||||
normalizedType, ok := normalizeMimeType(normalizedMimeType)
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid MIME type format")
|
||||
}
|
||||
request.Attachment.Type = normalizedType
|
||||
|
||||
attachmentUID, err := ValidateAndGenerateUID(request.AttachmentId)
|
||||
if err != nil {
|
||||
|
|
@ -617,16 +618,18 @@ func validateFilename(filename string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func isValidMimeType(mimeType string) bool {
|
||||
// Reject empty or excessively long MIME types
|
||||
func normalizeMimeType(mimeType string) (string, bool) {
|
||||
mimeType = strings.TrimSpace(mimeType)
|
||||
if mimeType == "" || len(mimeType) > 255 {
|
||||
return false
|
||||
return "", false
|
||||
}
|
||||
|
||||
// MIME type must match the pattern: type/subtype
|
||||
// Allow common characters in MIME types per RFC 2045
|
||||
matched, _ := regexp.MatchString(`^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]{0,126}/[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]{0,126}$`, mimeType)
|
||||
return matched
|
||||
mediaType, _, err := mime.ParseMediaType(mimeType)
|
||||
if err != nil || mediaType == "" || len(mediaType) > 255 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return mediaType, true
|
||||
}
|
||||
|
||||
func (s *APIV1Service) validateAttachmentFilter(ctx context.Context, filterStr string) error {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,30 @@ func TestCreateAttachment(t *testing.T) {
|
|||
require.Equal(t, "application/octet-stream", attachment.Type)
|
||||
})
|
||||
|
||||
t.Run("Type_WithParameters_NormalizedBeforeValidation", func(t *testing.T) {
|
||||
attachment, err := ts.Service.CreateAttachment(userCtx, &v1pb.CreateAttachmentRequest{
|
||||
Attachment: &v1pb.Attachment{
|
||||
Filename: "voice-note.webm",
|
||||
Type: "audio/webm;codecs=opus",
|
||||
Content: []byte("fake webm content"),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "audio/webm", attachment.Type)
|
||||
})
|
||||
|
||||
t.Run("Type_InvalidFormat_Rejected", func(t *testing.T) {
|
||||
_, err := ts.Service.CreateAttachment(userCtx, &v1pb.CreateAttachmentRequest{
|
||||
Attachment: &v1pb.Attachment{
|
||||
Filename: "broken.webm",
|
||||
Type: `audio/webm;codecs="unterminated`,
|
||||
Content: []byte("fake webm content"),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid MIME type format")
|
||||
})
|
||||
|
||||
t.Run("LocalStorage_PathCollisionUsesUniqueReference", func(t *testing.T) {
|
||||
_, err := ts.Store.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
||||
Key: storepb.InstanceSettingKey_STORAGE,
|
||||
|
|
|
|||
Loading…
Reference in New Issue