feat: implementar webapp app_compartir con visor universal, auth y purga 24h para guest

This commit is contained in:
2026-10-13 22:46:02 -05:00
parent 6ea00e4ec5
commit 8519590cc7
24 changed files with 2970 additions and 71 deletions
+121
View File
@@ -0,0 +1,121 @@
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
}