122 lines
2.6 KiB
Go
122 lines
2.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const slugChars = "23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
|
|
|
|
var safeFilenameRegex = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
|
|
|
|
func GenerateSlug(length int) string {
|
|
b := make([]byte, length)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "x" + strings.ToLower(slugChars[:length-1])
|
|
}
|
|
charsLen := byte(len(slugChars))
|
|
for i := 0; i < length; i++ {
|
|
b[i] = slugChars[b[i]%charsLen]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func SanitizeFilename(name string) string {
|
|
base := filepath.Base(name)
|
|
sanitized := safeFilenameRegex.ReplaceAllString(base, "_")
|
|
if len(sanitized) > 100 {
|
|
ext := filepath.Ext(sanitized)
|
|
sanitized = sanitized[:100-len(ext)] + ext
|
|
}
|
|
if sanitized == "" || sanitized == "." {
|
|
sanitized = "archivo.bin"
|
|
}
|
|
return sanitized
|
|
}
|
|
|
|
func DetectMimeType(filename string, reader io.ReadSeeker) string {
|
|
ext := strings.ToLower(filepath.Ext(filename))
|
|
if m := mime.TypeByExtension(ext); m != "" {
|
|
return m
|
|
}
|
|
|
|
// Sniff first 512 bytes
|
|
if reader != nil {
|
|
buf := make([]byte, 512)
|
|
n, err := reader.Read(buf)
|
|
_, _ = reader.Seek(0, io.SeekStart)
|
|
if err == nil && n > 0 {
|
|
detected := http.DetectContentType(buf[:n])
|
|
if detected != "application/octet-stream" {
|
|
return detected
|
|
}
|
|
}
|
|
}
|
|
|
|
// Known fallback extensions
|
|
switch ext {
|
|
case ".pdf":
|
|
return "application/pdf"
|
|
case ".mp4":
|
|
return "video/mp4"
|
|
case ".webm":
|
|
return "video/webm"
|
|
case ".mp3":
|
|
return "audio/mpeg"
|
|
case ".wav":
|
|
return "audio/wav"
|
|
case ".ogg":
|
|
return "audio/ogg"
|
|
case ".png":
|
|
return "image/png"
|
|
case ".jpg", ".jpeg":
|
|
return "image/jpeg"
|
|
case ".gif":
|
|
return "image/gif"
|
|
case ".webp":
|
|
return "image/webp"
|
|
case ".svg":
|
|
return "image/svg+xml"
|
|
case ".txt", ".log", ".env":
|
|
return "text/plain; charset=utf-8"
|
|
case ".json":
|
|
return "application/json"
|
|
case ".js":
|
|
return "application/javascript"
|
|
case ".go", ".py", ".rs", ".java", ".c", ".cpp", ".sh", ".sql", ".html", ".css", ".md":
|
|
return "text/plain; charset=utf-8"
|
|
default:
|
|
return "application/octet-stream"
|
|
}
|
|
}
|
|
|
|
func SaveFile(uploadDir, slug, originalFilename string, src io.Reader) (string, int64, error) {
|
|
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
safeName := SanitizeFilename(originalFilename)
|
|
storedFilename := slug + "_" + safeName
|
|
destPath := filepath.Join(uploadDir, storedFilename)
|
|
|
|
dest, err := os.Create(destPath)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
defer dest.Close()
|
|
|
|
written, err := io.Copy(dest, src)
|
|
if err != nil {
|
|
_ = os.Remove(destPath)
|
|
return "", 0, err
|
|
}
|
|
|
|
return storedFilename, written, nil
|
|
}
|