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 }