You've already forked pocketbase
mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-11-06 09:29:19 +02:00
[#4824] updated the uploaded filename normalization to take double extensions in consideration
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -172,7 +171,7 @@ var extInvalidCharsRegex = regexp.MustCompile(`[^\w\.\*\-\+\=\#]+`)
|
||||
func normalizeName(fr FileReader, name string) string {
|
||||
// extension
|
||||
// ---
|
||||
originalExt := filepath.Ext(name)
|
||||
originalExt := extractExtension(name)
|
||||
cleanExt := extInvalidCharsRegex.ReplaceAllString(originalExt, "")
|
||||
if cleanExt == "" {
|
||||
// try to detect the extension from the file content
|
||||
@@ -198,15 +197,41 @@ func normalizeName(fr FileReader, name string) string {
|
||||
)
|
||||
}
|
||||
|
||||
// extractExtension extracts the extension (with leading dot) from name.
|
||||
//
|
||||
// This differ from filepath.Ext() by supporting double extensions (eg. ".tar.gz").
|
||||
//
|
||||
// Returns an empty string if no match is found.
|
||||
//
|
||||
// Example:
|
||||
// extractExtension("test.txt") // .txt
|
||||
// extractExtension("test.tar.gz") // .tar.gz
|
||||
// extractExtension("test.a.tar.gz") // .tar.gz
|
||||
func extractExtension(name string) string {
|
||||
primaryDot := strings.LastIndex(name, ".")
|
||||
|
||||
if primaryDot == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// look for secondary extension
|
||||
secondaryDot := strings.LastIndex(name[:primaryDot], ".")
|
||||
if secondaryDot >= 0 {
|
||||
return name[secondaryDot:]
|
||||
}
|
||||
|
||||
return name[primaryDot:]
|
||||
}
|
||||
|
||||
// detectExtension tries to detect the extension from file mime type.
|
||||
func detectExtension(fr FileReader) (string, error) {
|
||||
// try to detect the extension from the mime type
|
||||
r, err := fr.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
mt, _ := mimetype.DetectReader(r)
|
||||
mt, err := mimetype.DetectReader(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
@@ -35,7 +36,7 @@ func TestNewFileFromPath(t *testing.T) {
|
||||
if f.OriginalName != originalName {
|
||||
t.Fatalf("Expected OriginalName %q, got %q", originalName, f.OriginalName)
|
||||
}
|
||||
if match, _ := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
if match, err := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
t.Fatalf("Expected Name to match %v, got %q (%v)", normalizedNamePattern, f.Name, err)
|
||||
}
|
||||
if f.Size != 73 {
|
||||
@@ -69,7 +70,7 @@ func TestNewFileFromBytes(t *testing.T) {
|
||||
if f.OriginalName != originalName {
|
||||
t.Fatalf("Expected OriginalName %q, got %q", originalName, f.OriginalName)
|
||||
}
|
||||
if match, _ := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
if match, err := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
t.Fatalf("Expected Name to match %v, got %q (%v)", normalizedNamePattern, f.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -95,12 +96,12 @@ func TestNewFileFromMultipart(t *testing.T) {
|
||||
}
|
||||
|
||||
originalNamePattern := regexp.QuoteMeta("tmpfile-") + `\w+` + regexp.QuoteMeta(".txt")
|
||||
if match, _ := regexp.Match(originalNamePattern, []byte(f.OriginalName)); !match {
|
||||
if match, err := regexp.Match(originalNamePattern, []byte(f.OriginalName)); !match {
|
||||
t.Fatalf("Expected OriginalName to match %v, got %q (%v)", originalNamePattern, f.OriginalName, err)
|
||||
}
|
||||
|
||||
normalizedNamePattern := regexp.QuoteMeta("tmpfile_") + `\w+\_\w{10}` + regexp.QuoteMeta(".txt")
|
||||
if match, _ := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
if match, err := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
t.Fatalf("Expected Name to match %v, got %q (%v)", normalizedNamePattern, f.Name, err)
|
||||
}
|
||||
|
||||
@@ -164,7 +165,7 @@ func TestNewFileFromUrlTimeout(t *testing.T) {
|
||||
if f.OriginalName != originalName {
|
||||
t.Fatalf("Expected OriginalName %q, got %q", originalName, f.OriginalName)
|
||||
}
|
||||
if match, _ := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
if match, err := regexp.Match(normalizedNamePattern, []byte(f.Name)); !match {
|
||||
t.Fatalf("Expected Name to match %v, got %q (%v)", normalizedNamePattern, f.Name, err)
|
||||
}
|
||||
if f.Size != 4 {
|
||||
@@ -175,3 +176,30 @@ func TestNewFileFromUrlTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileNameNormalizations(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
pattern string
|
||||
}{
|
||||
{"", `^\w{10}_\w{10}.txt$`},
|
||||
{".png", `^\w{10}_\w{10}.png$`},
|
||||
{".tar.gz", `^\w{10}_\w{10}.tar.gz$`},
|
||||
{"a.tar.gz", `^a\w{10}_\w{10}.tar.gz$`},
|
||||
{"a.b.c.d.tar.gz", `^a_b_c_d_\w{10}.tar.gz$`},
|
||||
{"abcd", `^abcd_\w{10}.txt$`},
|
||||
{"a b! c d . 456", `^a_b_c_d_\w{10}.456$`}, // normalize spaces
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
t.Run(strconv.Itoa(i)+"_"+s.name, func(t *testing.T) {
|
||||
f, err := filesystem.NewFileFromBytes([]byte("abc"), s.name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if match, err := regexp.Match(s.pattern, []byte(f.Name)); !match {
|
||||
t.Fatalf("Expected Name to match %v, got %q (%v)", s.pattern, f.Name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user