2022-06-15 03:32:29 -05:00
|
|
|
package store
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2022-07-25 02:30:20 -05:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2022-06-15 03:32:29 -05:00
|
|
|
"path/filepath"
|
2022-07-25 02:30:20 -05:00
|
|
|
"strings"
|
2022-06-15 03:32:29 -05:00
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/infra/filestorage"
|
2022-08-10 04:56:48 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2023-12-06 16:00:53 -06:00
|
|
|
"github.com/grafana/grafana/pkg/util"
|
2022-06-15 03:32:29 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
allowedImageExtensions = map[string]bool{
|
|
|
|
".jpg": true,
|
|
|
|
".jpeg": true,
|
2022-07-07 06:32:18 -05:00
|
|
|
".svg": true,
|
2022-06-15 03:32:29 -05:00
|
|
|
".gif": true,
|
|
|
|
".png": true,
|
|
|
|
".webp": true,
|
|
|
|
}
|
|
|
|
imageExtensionsToMatchingMimeTypes = map[string]map[string]bool{
|
|
|
|
".jpg": {"image/jpg": true, "image/jpeg": true},
|
|
|
|
".jpeg": {"image/jpg": true, "image/jpeg": true},
|
|
|
|
".gif": {"image/gif": true},
|
|
|
|
".png": {"image/png": true},
|
|
|
|
".webp": {"image/webp": true},
|
2022-07-25 02:30:20 -05:00
|
|
|
".svg": {"image/svg+xml": true},
|
2022-06-15 03:32:29 -05:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
type validationResult struct {
|
|
|
|
ok bool
|
|
|
|
reason string
|
|
|
|
}
|
|
|
|
|
|
|
|
func success() validationResult {
|
|
|
|
return validationResult{
|
|
|
|
ok: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func fail(reason string) validationResult {
|
|
|
|
return validationResult{
|
|
|
|
ok: false,
|
|
|
|
reason: reason,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-10 04:56:48 -05:00
|
|
|
func (s *standardStorageService) detectMimeType(ctx context.Context, user *user.SignedInUser, uploadRequest *UploadRequest) string {
|
2022-07-25 02:30:20 -05:00
|
|
|
if strings.HasSuffix(uploadRequest.Path, ".svg") {
|
2023-12-06 16:00:53 -06:00
|
|
|
if util.IsSVG(uploadRequest.Contents) {
|
2022-07-25 02:30:20 -05:00
|
|
|
return "image/svg+xml"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return http.DetectContentType(uploadRequest.Contents)
|
2022-06-15 03:32:29 -05:00
|
|
|
}
|
|
|
|
|
2022-08-10 04:56:48 -05:00
|
|
|
func (s *standardStorageService) validateImage(ctx context.Context, user *user.SignedInUser, uploadRequest *UploadRequest) validationResult {
|
2022-06-15 03:32:29 -05:00
|
|
|
ext := filepath.Ext(uploadRequest.Path)
|
|
|
|
if !allowedImageExtensions[ext] {
|
2022-07-25 02:30:20 -05:00
|
|
|
return fail(fmt.Sprintf("unsupported extension: %s", ext))
|
2022-06-15 03:32:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
mimeType := s.detectMimeType(ctx, user, uploadRequest)
|
|
|
|
if !imageExtensionsToMatchingMimeTypes[ext][mimeType] {
|
2022-07-25 02:30:20 -05:00
|
|
|
return fail(fmt.Sprintf("extension '%s' does not match the detected MimeType: %s", ext, mimeType))
|
2022-06-15 03:32:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return success()
|
|
|
|
}
|
|
|
|
|
2022-08-10 04:56:48 -05:00
|
|
|
func (s *standardStorageService) validateUploadRequest(ctx context.Context, user *user.SignedInUser, req *UploadRequest, storagePath string) validationResult {
|
2022-06-15 03:32:29 -05:00
|
|
|
// TODO: validateSize
|
|
|
|
// TODO: validateProperties
|
|
|
|
|
|
|
|
if err := filestorage.ValidatePath(storagePath); err != nil {
|
2022-07-25 02:30:20 -05:00
|
|
|
return fail(fmt.Sprintf("path validation failed. error: %s. path: %s", err.Error(), storagePath))
|
2022-06-15 03:32:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
switch req.EntityType {
|
2022-07-13 12:15:25 -05:00
|
|
|
case EntityTypeJSON:
|
|
|
|
fallthrough
|
2022-06-15 03:32:29 -05:00
|
|
|
case EntityTypeFolder:
|
|
|
|
fallthrough
|
|
|
|
case EntityTypeDashboard:
|
|
|
|
// TODO: add proper validation
|
2022-07-13 12:15:25 -05:00
|
|
|
if !json.Valid(req.Contents) {
|
|
|
|
return fail("invalid json")
|
2022-06-15 03:32:29 -05:00
|
|
|
}
|
|
|
|
return success()
|
|
|
|
case EntityTypeImage:
|
|
|
|
return s.validateImage(ctx, user, req)
|
|
|
|
default:
|
|
|
|
return fail("unknown entity")
|
|
|
|
}
|
|
|
|
}
|