Files
ddc/internal/fetch/filename.go
T
2026-10-25 20:24:50 -04:00

66 lines
1.5 KiB
Go
Executable File

package fetch
import (
"mime"
"net/http"
"path"
"path/filepath"
"strings"
"unicode/utf8"
)
const (
maxFileNameBytes = 200
maxExtensionBytes = 16
fallbackFileName = "download"
)
func FileNameFromResponse(response *http.Response) string {
if disposition := response.Header.Get("Content-Disposition"); disposition != "" {
if _, parameters, err := mime.ParseMediaType(disposition); err == nil {
if name := SanitizeFileName(parameters["filename"]); name != "" {
return name
}
}
}
if response.Request != nil && response.Request.URL != nil {
if name := SanitizeFileName(path.Base(response.Request.URL.Path)); name != "" {
return name
}
}
return fallbackFileName
}
func SanitizeFileName(raw string) string {
name := path.Base(strings.ReplaceAll(raw, "\\", "/"))
name = strings.Map(dropUnsafeRune, name)
name = strings.TrimSpace(strings.TrimLeft(name, ". "))
if name == "" {
return ""
}
return truncate(name)
}
func dropUnsafeRune(character rune) rune {
if character < 0x20 || character == 0x7f || character == '/' || character == utf8.RuneError {
return -1
}
return character
}
func truncate(name string) string {
if len(name) <= maxFileNameBytes {
return name
}
extension := filepath.Ext(name)
if len(extension) > maxExtensionBytes {
extension = ""
}
base := strings.TrimSuffix(name, extension)
for len(base)+len(extension) > maxFileNameBytes {
_, size := utf8.DecodeLastRuneInString(base)
base = base[:len(base)-size]
}
return base + extension
}