package str
import (
"cmp"
"fmt"
"mime"
"path"
"strings"
"unicode"
"unicode/utf8"
"github.com/deluan/sanitize"
)
const (
maxFilenameBytes = 255
maxExtensionBytes = 16
fallbackFilename = "download"
)
func ContentDispositionAttachment(filename string) string {
stem, ext := splitFilename(filename)
header := fmt.Sprintf("attachment; filename=%q", joinFilename(toASCII(stem), toASCII(ext)))
name := joinFilename(stem, ext)
if isASCII(name) {
return header
}
extended := mime.FormatMediaType("attachment", map[string]string{"filename": name})
return header + strings.TrimPrefix(extended, "attachment")
}
func splitFilename(filename string) (stem, ext string) {
name := strings.Map(func(r rune) rune {
if unicode.IsControl(r) || unicode.Is(unicode.Bidi_Control, r) {
return -1
}
return r
}, SanitizeFilename(strings.ToValidUTF8(filename, "_")))
ext = path.Ext(name)
if len(ext) > maxExtensionBytes {
ext = ""
}
stem = strings.TrimSuffix(name, ext)
if limit := maxFilenameBytes - len(ext); len(stem) > limit {
for limit > 0 && !utf8.RuneStart(stem[limit]) {
limit--
}
stem = stem[:limit]
}
return stem, ext
}
func joinFilename(stem, ext string) string {
stem = cmp.Or(strings.TrimSpace(stem), fallbackFilename)
return cmp.Or(strings.TrimRight(stem+ext, " ."), fallbackFilename)
}
func toASCII(s string) string {
return strings.Map(func(r rune) rune {
switch {
case r == ',':
return '_'
case r >= ' ' && r <= '~':
return r
}
return -1
}, sanitize.Accents(SanitizeFilename(Clear(s))))
}
func isASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
return false
}
}
return true
}