1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-01-10 00:43:36 +02:00
pocketbase/tools/rest/uploaded_file.go

101 lines
2.6 KiB
Go
Raw Normal View History

2022-07-06 23:19:05 +02:00
package rest
import (
"fmt"
"mime/multipart"
"net/http"
"path/filepath"
"regexp"
"strings"
2022-07-06 23:19:05 +02:00
2022-10-30 10:28:14 +02:00
"github.com/gabriel-vasile/mimetype"
"github.com/pocketbase/pocketbase/tools/inflector"
2022-07-06 23:19:05 +02:00
"github.com/pocketbase/pocketbase/tools/security"
)
// DefaultMaxMemory defines the default max memory bytes that
// will be used when parsing a form request body.
const DefaultMaxMemory = 32 << 20 // 32mb
var extensionInvalidCharsRegex = regexp.MustCompile(`[^\w\.\*\-\+\=\#]+`)
2022-07-06 23:19:05 +02:00
// UploadedFile defines a single multipart uploaded file instance.
type UploadedFile struct {
name string
header *multipart.FileHeader
}
// Name returns an assigned unique name to the uploaded file.
func (f *UploadedFile) Name() string {
return f.name
}
// Header returns the file header that comes with the multipart request.
func (f *UploadedFile) Header() *multipart.FileHeader {
return f.header
}
// FindUploadedFiles extracts all form files of `key` from a http request
// and returns a slice with `UploadedFile` instances (if any).
func FindUploadedFiles(r *http.Request, key string) ([]*UploadedFile, error) {
if r.MultipartForm == nil {
err := r.ParseMultipartForm(DefaultMaxMemory)
if err != nil {
return nil, err
}
}
if r.MultipartForm == nil || r.MultipartForm.File == nil || len(r.MultipartForm.File[key]) == 0 {
return nil, http.ErrMissingFile
}
2022-10-30 10:28:14 +02:00
result := make([]*UploadedFile, 0, len(r.MultipartForm.File[key]))
2022-07-06 23:19:05 +02:00
2022-10-30 10:28:14 +02:00
for _, fh := range r.MultipartForm.File[key] {
2022-07-06 23:19:05 +02:00
file, err := fh.Open()
if err != nil {
return nil, err
}
defer file.Close()
2022-10-30 10:28:14 +02:00
// extension
// ---
originalExt := filepath.Ext(fh.Filename)
sanitizedExt := extensionInvalidCharsRegex.ReplaceAllString(originalExt, "")
2022-10-30 10:28:14 +02:00
if sanitizedExt == "" {
// try to detect the extension from the mime type
mt, err := mimetype.DetectReader(file)
if err != nil {
return nil, err
}
sanitizedExt = mt.Extension()
}
2022-10-30 10:28:14 +02:00
// name
// ---
originalName := strings.TrimSuffix(fh.Filename, originalExt)
sanitizedName := inflector.Snakecase(originalName)
if length := len(sanitizedName); length < 3 {
// the name is too short so we concatenate an additional random part
sanitizedName += security.RandomString(10)
} else if length > 100 {
// keep only the first 100 characters (it is multibyte safe after Snakecase)
sanitizedName = sanitizedName[:100]
}
uploadedFilename := fmt.Sprintf(
"%s_%s%s",
sanitizedName,
security.RandomString(10), // ensure that there is always a random part
sanitizedExt,
)
2022-10-30 10:28:14 +02:00
result = append(result, &UploadedFile{
name: uploadedFilename,
2022-07-06 23:19:05 +02:00
header: fh,
2022-10-30 10:28:14 +02:00
})
2022-07-06 23:19:05 +02:00
}
return result, nil
}