🏡 index : quine-global/quintodrome.git

package str

import (
	"cmp"
	"fmt"
	"mime"
	"path"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/deluan/sanitize"
)

const (
	maxFilenameBytes  = 255
	maxExtensionBytes = 16
	fallbackFilename  = "download"
)

// ContentDispositionAttachment builds an RFC 6266 attachment header value for a user-controlled filename.
// Non-ASCII names also get a filename* parameter, which clients prefer over the ASCII fallback.
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
	}
	// FormatMediaType emits a percent-encoded filename* for non-ASCII values
	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
}