1package download
2
3import (
4 "mime"
5 "net/url"
6 "path"
7 "strings"
8)
9
10var forcedDownloadExtensions = map[string]struct{}{
11 ".7z": {},
12 ".apk": {},
13 ".appimage": {},
14 ".bz2": {},
15 ".deb": {},
16 ".dmg": {},
17 ".exe": {},
18 ".gz": {},
19 ".img": {},
20 ".iso": {},
21 ".msi": {},
22 ".pdf": {},
23 ".pkg": {},
24 ".rar": {},
25 ".rpm": {},
26 ".tar": {},
27 ".tgz": {},
28 ".xz": {},
29 ".zip": {},
30}
31
32var forcedDownloadMIMETypes = map[string]struct{}{
33 "application/pdf": {},
34 "application/x-apple-diskimage": {},
35 "application/x-cd-image": {},
36 "application/x-iso9660-image": {},
37}
38
39// ShouldForceDownload returns true when the resource should be downloaded
40// instead of navigated/rendered inline.
41func ShouldForceDownload(uri, mimeType string) bool {
42 return ShouldForceDownloadForMIMEType(mimeType) || ShouldForceDownloadForURI(uri)
43}
44
45// ShouldForceDownloadForURI returns true when the URI path has a known
46// download-only extension.
47func ShouldForceDownloadForURI(uri string) bool {
48 ext := strings.ToLower(path.Ext(uriPath(uri)))
49 if ext == "" {
50 return false
51 }
52 _, ok := forcedDownloadExtensions[ext]
53 return ok
54}
55
56// ShouldForceDownloadForMIMEType returns true when the MIME type maps to a
57// known download-only artifact.
58func ShouldForceDownloadForMIMEType(mimeType string) bool {
59 if mimeType == "" {
60 return false
61 }
62
63 mediaType := strings.ToLower(strings.TrimSpace(mimeType))
64 if parsed, _, err := mime.ParseMediaType(mediaType); err == nil {
65 mediaType = parsed
66 }
67
68 _, ok := forcedDownloadMIMETypes[mediaType]
69 return ok
70}
71
72func uriPath(raw string) string {
73 if raw == "" {
74 return ""
75 }
76
77 parsed, err := url.Parse(raw)
78 if err == nil && parsed.Path != "" {
79 return parsed.Path
80 }
81
82 if cut := strings.IndexAny(raw, "?#"); cut >= 0 {
83 return raw[:cut]
84 }
85
86 return raw
87}