192 lines
5.7 KiB
Go
192 lines
5.7 KiB
Go
package entity
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
PurposeAvatar = "avatar"
|
|
PurposeGroupAvatar = "group_avatar"
|
|
PurposeChatImage = "chat_image"
|
|
PurposeChatVoice = "chat_voice"
|
|
PurposeChatVideo = "chat_video"
|
|
PurposeChatGIF = "chat_gif"
|
|
PurposeChatFile = "chat_file"
|
|
PurposeMomentImage = "moment_image"
|
|
PurposeMomentVideo = "moment_video"
|
|
PurposeMomentCover = "moment_cover"
|
|
|
|
PrefixPerm = "perm/"
|
|
PrefixChat = "chat/"
|
|
PrefixAvatar = "perm/avatar/"
|
|
PrefixGroup = "perm/group/"
|
|
PrefixFav = "perm/fav/"
|
|
PrefixChatImage = "chat/image/"
|
|
PrefixChatVoice = "chat/voice/"
|
|
PrefixChatVideo = "chat/video/"
|
|
PrefixChatGIF = "chat/gif/"
|
|
PrefixChatFile = "chat/file/"
|
|
PrefixMoment = "perm/moment/"
|
|
PrefixMomentImage = "perm/moment/image/"
|
|
PrefixMomentVideo = "perm/moment/video/"
|
|
PrefixMomentCover = "perm/moment/cover/"
|
|
|
|
MethodPUT = "PUT"
|
|
MethodGET = "GET"
|
|
)
|
|
|
|
const (
|
|
maxAvatar = 2 << 20
|
|
maxImage = 32 << 20
|
|
maxMoment = 10 << 20
|
|
maxGIF = 10 << 20
|
|
maxVoice = 16 << 20
|
|
maxVideo = 100 << 20
|
|
maxFile = 50 << 20
|
|
maxKeyLen = 512
|
|
extDefault = "bin"
|
|
)
|
|
|
|
// Spec is the server-owned policy for one upload purpose.
|
|
type Spec struct {
|
|
Purpose string
|
|
MaxSize int64
|
|
MIMEs map[string]struct{}
|
|
}
|
|
|
|
func LookupSpec(purpose string) (Spec, bool) {
|
|
s, ok := specs[purpose]
|
|
return s, ok
|
|
}
|
|
|
|
func NormalizeMIME(raw string) string {
|
|
s := strings.ToLower(strings.TrimSpace(raw))
|
|
s, _, _ = strings.Cut(s, ";")
|
|
return strings.TrimSpace(s)
|
|
}
|
|
|
|
func ExtForMIME(mime string) string {
|
|
if ext, ok := mimeExt[NormalizeMIME(mime)]; ok {
|
|
return ext
|
|
}
|
|
return extDefault
|
|
}
|
|
|
|
func ExtOfKey(key string) string {
|
|
base := path.Base(strings.TrimSpace(key))
|
|
ext := strings.ToLower(strings.TrimPrefix(path.Ext(base), "."))
|
|
if ext == "" || len(ext) > 8 {
|
|
return extDefault
|
|
}
|
|
for _, r := range ext {
|
|
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
|
|
return extDefault
|
|
}
|
|
}
|
|
return ext
|
|
}
|
|
|
|
func BuildObjectKey(purpose string, ownerID int64, name, day, ext string) string {
|
|
if ext == "" {
|
|
ext = extDefault
|
|
}
|
|
if name == "" {
|
|
name = "0"
|
|
}
|
|
switch purpose {
|
|
case PurposeAvatar:
|
|
return fmt.Sprintf("%s%s/%s.%s", PrefixAvatar, day, name, ext)
|
|
case PurposeGroupAvatar:
|
|
return fmt.Sprintf("%s%d/%s.%s", PrefixGroup, ownerID, name, ext)
|
|
case PurposeMomentImage:
|
|
return fmt.Sprintf("%s%s/%s.%s", PrefixMomentImage, day, name, ext)
|
|
case PurposeMomentVideo:
|
|
return fmt.Sprintf("%s%s/%s.%s", PrefixMomentVideo, day, name, ext)
|
|
case PurposeMomentCover:
|
|
return fmt.Sprintf("%s%s/%s.%s", PrefixMomentCover, day, name, ext)
|
|
default:
|
|
return fmt.Sprintf("%s%s/%s/%s.%s", PrefixChat, chatKind(purpose), day, name, ext)
|
|
}
|
|
}
|
|
|
|
func BuildFavKey(name, day, ext string) string {
|
|
if ext == "" {
|
|
ext = extDefault
|
|
}
|
|
if name == "" {
|
|
name = "0"
|
|
}
|
|
return fmt.Sprintf("%s%s/%s.%s", PrefixFav, day, name, ext)
|
|
}
|
|
|
|
func chatKind(purpose string) string {
|
|
switch purpose {
|
|
case PurposeChatImage:
|
|
return "image"
|
|
case PurposeChatVoice:
|
|
return "voice"
|
|
case PurposeChatVideo:
|
|
return "video"
|
|
case PurposeChatGIF:
|
|
return "gif"
|
|
default:
|
|
return "file"
|
|
}
|
|
}
|
|
|
|
func ValidObjectKey(key string) bool {
|
|
if key == "" || len(key) > maxKeyLen {
|
|
return false
|
|
}
|
|
if strings.Contains(key, "..") || strings.ContainsAny(key, " \t\n\\") || strings.HasPrefix(key, "/") {
|
|
return false
|
|
}
|
|
return strings.HasPrefix(key, PrefixPerm) || strings.HasPrefix(key, PrefixChat)
|
|
}
|
|
|
|
var specs = map[string]Spec{
|
|
PurposeAvatar: {Purpose: PurposeAvatar, MaxSize: maxAvatar, MIMEs: imageMIMEs},
|
|
PurposeGroupAvatar: {Purpose: PurposeGroupAvatar, MaxSize: maxAvatar, MIMEs: imageMIMEs},
|
|
PurposeMomentCover: {Purpose: PurposeMomentCover, MaxSize: maxAvatar, MIMEs: imageMIMEs},
|
|
PurposeMomentImage: {Purpose: PurposeMomentImage, MaxSize: maxMoment, MIMEs: momentImageMIMEs},
|
|
PurposeMomentVideo: {Purpose: PurposeMomentVideo, MaxSize: maxVideo, MIMEs: videoMIMEs},
|
|
PurposeChatImage: {Purpose: PurposeChatImage, MaxSize: maxImage, MIMEs: imageMIMEs},
|
|
PurposeChatGIF: {Purpose: PurposeChatGIF, MaxSize: maxGIF, MIMEs: gifMIMEs},
|
|
PurposeChatVoice: {Purpose: PurposeChatVoice, MaxSize: maxVoice, MIMEs: voiceMIMEs},
|
|
PurposeChatVideo: {Purpose: PurposeChatVideo, MaxSize: maxVideo, MIMEs: videoMIMEs},
|
|
PurposeChatFile: {Purpose: PurposeChatFile, MaxSize: maxFile, MIMEs: fileMIMEs},
|
|
}
|
|
|
|
var imageMIMEs = set("image/jpeg", "image/jpg", "image/png")
|
|
var momentImageMIMEs = set("image/jpeg", "image/jpg", "image/png", "image/gif")
|
|
var gifMIMEs = set("image/gif", "image/webp")
|
|
var voiceMIMEs = set("audio/mpeg", "audio/mp4", "audio/aac", "audio/amr", "audio/ogg", "audio/wav", "audio/webm", "audio/x-m4a")
|
|
var videoMIMEs = set("video/mp4", "video/quicktime", "video/webm")
|
|
var fileMIMEs = set(
|
|
"application/pdf", "application/zip", "application/x-zip-compressed",
|
|
"application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"application/octet-stream", "text/plain",
|
|
)
|
|
|
|
var mimeExt = map[string]string{
|
|
"image/jpeg": "jpg", "image/jpg": "jpg", "image/png": "png", "image/webp": "webp",
|
|
"image/gif": "gif",
|
|
"audio/mpeg": "mp3", "audio/mp4": "m4a", "audio/aac": "aac", "audio/amr": "amr",
|
|
"audio/ogg": "ogg", "audio/wav": "wav", "audio/webm": "weba", "audio/x-m4a": "m4a",
|
|
"video/mp4": "mp4", "video/quicktime": "mov", "video/webm": "webm",
|
|
"application/pdf": "pdf", "application/zip": "zip", "application/x-zip-compressed": "zip",
|
|
"application/octet-stream": extDefault, "text/plain": "txt",
|
|
}
|
|
|
|
func set(values ...string) map[string]struct{} {
|
|
m := make(map[string]struct{}, len(values))
|
|
for _, v := range values {
|
|
m[v] = struct{}{}
|
|
}
|
|
return m
|
|
}
|