Skip to content

Commit 9e95c7e

Browse files
committed
Upload: Delete invalid files, improve type checks and import logs photoprism#4895
Signed-off-by: Michael Mayer <michael@photoprism.app>
1 parent 1f36d35 commit 9e95c7e

10 files changed

+78
-30
lines changed

internal/api/users_upload.go

+36-16
Original file line numberDiff line numberDiff line change
@@ -86,36 +86,55 @@ func UploadUserFiles(router *gin.RouterGroup) {
8686
return
8787
}
8888

89-
// List of allowed file extensions (all types allowed if empty).
90-
allowExt := conf.UploadAllow()
89+
// If the file extension list is empty, all file types may
90+
// be uploaded except raw files if raw support is disabled.
91+
allowedExt := conf.UploadAllow()
92+
rejectRaw := conf.DisableRaw()
9193

92-
// Save uploaded files if their extension is allowed.
94+
// Save uploaded files and append their names
95+
// to "uploads" if they pass all checks.
9396
for _, file := range files {
94-
fileName := filepath.Base(file.Filename)
95-
filePath := path.Join(uploadDir, fileName)
96-
fileType := fs.FileType(fileName)
97+
baseName := filepath.Base(file.Filename)
98+
destName := path.Join(uploadDir, baseName)
99+
fileType := fs.FileType(baseName)
97100

101+
// Reject unsupported files and files with extensions that aren't allowed.
98102
if fileType == fs.TypeUnknown {
99-
log.Warnf("upload: rejected %s due to unknown or unsupported extension", clean.Log(fileName))
103+
log.Warnf("upload: rejected %s because it has an unsupported file extension", clean.Log(baseName))
100104
continue
101-
} else if allowExt.Excludes(fileType.DefaultExt()) {
102-
log.Warnf("upload: rejected %s because the file type is not allowed", clean.Log(fileName))
105+
} else if allowedExt.Excludes(fileType.DefaultExt()) {
106+
log.Warnf("upload: rejected %s because its extension is not allowed", clean.Log(baseName))
103107
continue
104108
}
105109

106-
if err = c.SaveUploadedFile(file, filePath); err != nil {
107-
log.Errorf("upload: failed saving file %s", clean.Log(fileName))
110+
// Save uploaded file in the user upload path.
111+
if err = c.SaveUploadedFile(file, destName); err != nil {
112+
log.Errorf("upload: failed to save %s", clean.Log(baseName))
113+
log.Debugf("upload: %s in %s", clean.Error(err), clean.Log(baseName))
108114
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
109115
return
110116
} else {
111-
log.Debugf("upload: saved file %s", clean.Log(fileName))
112-
event.Publish("upload.saved", event.Data{"uid": s.UserUID, "file": fileName})
117+
log.Debugf("upload: saved %s in user upload path", clean.Log(baseName))
118+
event.Publish("upload.saved", event.Data{"uid": s.UserUID, "file": baseName})
113119
}
114120

115-
uploads = append(uploads, filePath)
121+
// Make sure the file is supported and has the correct extension before importing it.
122+
if mediaFile, mediaErr := photoprism.NewMediaFile(destName); mediaErr != nil {
123+
log.Errorf("upload: rejected %s, %s", clean.Error(err), clean.Log(baseName))
124+
logErr("upload", os.Remove(destName))
125+
} else if typeErr := mediaFile.CheckType(); typeErr != nil {
126+
log.Warnf("upload: rejected %s %s", clean.Log(baseName), typeErr)
127+
logErr("upload", os.Remove(destName))
128+
} else if rejectRaw && mediaFile.IsRaw() {
129+
log.Warnf("upload: rejected %s because raw support is disabled", clean.Log(baseName))
130+
logErr("upload", os.Remove(destName))
131+
} else {
132+
// Successfully validated upload.
133+
uploads = append(uploads, destName)
134+
}
116135
}
117136

118-
// Check if uploaded file is safe.
137+
// Check if the uploaded file may contain inappropriate content.
119138
if len(uploads) > 0 && !conf.UploadNSFW() {
120139
nd := get.NsfwDetector()
121140

@@ -152,6 +171,7 @@ func UploadUserFiles(router *gin.RouterGroup) {
152171

153172
elapsed := int(time.Since(start).Seconds())
154173

174+
// Log number of successfully uploaded files.
155175
msg := i18n.Msg(i18n.MsgFilesUploadedIn, len(uploads), elapsed)
156176

157177
log.Info(msg)
@@ -240,7 +260,7 @@ func ProcessUserUpload(router *gin.RouterGroup) {
240260
}
241261

242262
// Update moments if files have been imported.
243-
if n := len(imported); n == 0 {
263+
if n := imported.Processed(); n == 0 {
244264
log.Infof("upload: found no new files to import from %s", clean.Log(uploadPath))
245265
} else {
246266
log.Infof("upload: imported %s", english.Plural(n, "file", "files"))

internal/photoprism/import.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,15 @@ func (imp *Import) Start(opt ImportOptions) fs.Done {
183183
// Report files that have a missing or invalid filename extension,
184184
// see https://github.com/photoprism/photoprism/issues/3518.
185185
if typeErr := mf.CheckType(); typeErr != nil {
186-
log.Warnf("import: %s has %s", clean.Log(mf.RootRelName()), typeErr)
186+
if !opt.RemoveInvalidFiles {
187+
log.Warnf("import: %s %s and will not be indexed", clean.Log(mf.RootRelName()), typeErr)
188+
} else if removeErr := mf.Remove(); removeErr != nil {
189+
log.Errorf("import: %s %s and %s", clean.Log(mf.RootRelName()), typeErr, removeErr)
190+
return nil
191+
} else {
192+
log.Warnf("import: %s %s and was deleted", clean.Log(mf.RootRelName()), typeErr)
193+
return nil
194+
}
187195
}
188196

189197
// Create JSON sidecar file, if needed.

internal/photoprism/import_options.go

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type ImportOptions struct {
1212
NonBlocking bool
1313
DestFolder string
1414
RemoveDotFiles bool
15+
RemoveInvalidFiles bool
1516
RemoveExistingFiles bool
1617
RemoveEmptyDirectories bool
1718
}
@@ -35,6 +36,7 @@ func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
3536
NonBlocking: false,
3637
DestFolder: destFolder,
3738
RemoveDotFiles: false,
39+
RemoveInvalidFiles: false,
3840
RemoveExistingFiles: false,
3941
RemoveEmptyDirectories: false,
4042
}
@@ -52,6 +54,7 @@ func ImportOptionsMove(importPath, destFolder string) ImportOptions {
5254
NonBlocking: false,
5355
DestFolder: destFolder,
5456
RemoveDotFiles: true,
57+
RemoveInvalidFiles: false,
5558
RemoveExistingFiles: true,
5659
RemoveEmptyDirectories: true,
5760
}
@@ -69,6 +72,7 @@ func ImportOptionsUpload(uploadPath, destFolder string) ImportOptions {
6972
NonBlocking: true,
7073
DestFolder: destFolder,
7174
RemoveDotFiles: true,
75+
RemoveInvalidFiles: true,
7276
RemoveExistingFiles: true,
7377
RemoveEmptyDirectories: true,
7478
}

internal/photoprism/index.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) {
222222
// Skip files if the filename extension does not match their mime type,
223223
// see https://github.com/photoprism/photoprism/issues/3518 for details.
224224
if typeErr := mf.CheckType(); typeErr != nil {
225-
log.Warnf("index: skipped %s due to %s", clean.Log(mf.RootRelName()), typeErr)
225+
log.Warnf("index: skipped %s because it %s", clean.Log(mf.RootRelName()), typeErr)
226226
return nil
227227
}
228228

internal/photoprism/index_main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func IndexMain(related *RelatedFiles, ind *Index, o IndexOptions) (result IndexR
2222
if typeErr := f.CheckType(); typeErr != nil {
2323
// Skip files if the filename extension does not match their mime type,
2424
// see https://github.com/photoprism/photoprism/issues/3518 for details.
25-
result.Err = fmt.Errorf("index: skipped %s due to %w", clean.Log(f.RootRelName()), typeErr)
25+
result.Err = fmt.Errorf("index: skipped %s because it %w", clean.Log(f.RootRelName()), typeErr)
2626
result.Status = IndexFailed
2727
return result
2828
} else if limitErr, _ := f.ExceedsBytes(o.ByteLimit); limitErr != nil {

internal/photoprism/index_related.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func IndexRelated(related RelatedFiles, ind *Index, o IndexOptions) (result Inde
5252
// Skip files if the filename extension does not match their mime type,
5353
// see https://github.com/photoprism/photoprism/issues/3518 for details.
5454
if typeErr := f.CheckType(); typeErr != nil {
55-
result.Err = fmt.Errorf("index: skipped %s due to %w", clean.Log(f.RootRelName()), typeErr)
55+
result.Err = fmt.Errorf("index: skipped %s because it %w", clean.Log(f.RootRelName()), typeErr)
5656
result.Status = IndexFailed
5757
continue
5858
}

internal/photoprism/mediafile.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -875,14 +875,14 @@ func (m *MediaFile) CheckType() error {
875875
extension := m.Extension()
876876

877877
if extension == "" {
878-
return fmt.Errorf("missing extension")
878+
return fmt.Errorf("has no file extension")
879879
}
880880

881881
// Detect file type and return error if unknown.
882882
fileType := fs.FileType(m.fileName)
883883

884884
if fileType == fs.TypeUnknown {
885-
return fmt.Errorf("unknown file type")
885+
return fmt.Errorf("is an unknown file type")
886886
}
887887

888888
// Detect media type (formerly known as a MIME type),
@@ -915,10 +915,10 @@ func (m *MediaFile) CheckType() error {
915915

916916
// Exclude mime type from the error message if it could not be detected.
917917
if mimeType == fs.MimeTypeUnknown {
918-
return fmt.Errorf("invalid extension (unknown media type)")
918+
return fmt.Errorf("has an invalid extension (unknown media type)")
919919
}
920920

921-
return fmt.Errorf("invalid extension for media type %s", clean.LogQuote(mimeType))
921+
return fmt.Errorf("has an invalid extension for media type %s", clean.LogQuote(mimeType))
922922
}
923923

924924
// Media returns the media content type (video, image, raw, sidecar,...).

pkg/fs/done.go

+13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ const (
99

1010
type Done map[string]Status
1111

12+
// Processed counts the number of processed files.
13+
func (d Done) Processed() int {
14+
count := 0
15+
16+
for _, s := range d {
17+
if s.Processed() {
18+
count++
19+
}
20+
}
21+
22+
return count
23+
}
24+
1225
func (s Status) Exists() bool {
1326
return s > 0
1427
}

pkg/fs/ext_list.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,9 @@ func (b ExtList) Accept() string {
111111

112112
list := make([]string, 0, len(b))
113113

114-
for typeExt := range b {
115-
allExt := FileTypesLower[FileType("."+typeExt)]
116-
for _, s := range allExt {
117-
list = append(list, s)
114+
for s := range b {
115+
if s = TrimExt(s); s != "" {
116+
list = append(list, "."+s)
118117
}
119118
}
120119

pkg/fs/ext_list_test.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,15 @@ func TestExtList_String(t *testing.T) {
130130
func TestExtList_Accept(t *testing.T) {
131131
t.Run("One", func(t *testing.T) {
132132
list := NewExtList("jpg")
133-
assert.Equal(t, ".jfi,.jfif,.jif,.jpe,.jpeg,.jpg", list.Accept())
133+
assert.Equal(t, ".jpg", list.Accept())
134134
})
135135
t.Run("Two", func(t *testing.T) {
136136
list := NewExtList("mp4, avi")
137-
assert.Equal(t, ".avi,.mp,.mp4", list.Accept())
137+
assert.Equal(t, ".avi,.mp4", list.Accept())
138+
})
139+
t.Run("Jpeg", func(t *testing.T) {
140+
list := NewExtList("jpg,jpeg,jxl")
141+
assert.Equal(t, ".jpeg,.jpg,.jxl", list.Accept())
138142
})
139143
t.Run("Empty", func(t *testing.T) {
140144
list := NewExtList("")

0 commit comments

Comments
 (0)