diff options
Diffstat (limited to 'media/mediaType.go')
-rw-r--r-- | media/mediaType.go | 305 |
1 files changed, 67 insertions, 238 deletions
diff --git a/media/mediaType.go b/media/mediaType.go index 084f1fb5b..8204fc435 100644 --- a/media/mediaType.go +++ b/media/mediaType.go @@ -16,38 +16,36 @@ package media import ( "encoding/json" - "errors" "fmt" "net/http" - "sort" "strings" - - "github.com/spf13/cast" - - "github.com/gohugoio/hugo/common/maps" - - "github.com/mitchellh/mapstructure" ) var zero Type const ( - defaultDelimiter = "." + DefaultDelimiter = "." ) -// Type (also known as MIME type and content type) is a two-part identifier for +// MediaType (also known as MIME type and content type) is a two-part identifier for // file formats and format contents transmitted on the Internet. // For Hugo's use case, we use the top-level type name / subtype name + suffix. // One example would be application/svg+xml // If suffix is not provided, the sub type will be used. -// See // https://en.wikipedia.org/wiki/Media_type +// <docsmeta>{ "name": "MediaType" }</docsmeta> type Type struct { - MainType string `json:"mainType"` // i.e. text - SubType string `json:"subType"` // i.e. html - Delimiter string `json:"delimiter"` // e.g. "." + // The full MIME type string, e.g. "application/rss+xml". + Type string `json:"-"` - // FirstSuffix holds the first suffix defined for this Type. - FirstSuffix SuffixInfo `json:"firstSuffix"` + // The top-level type name, e.g. "application". + MainType string `json:"mainType"` + // The subtype name, e.g. "rss". + SubType string `json:"subType"` + // The delimiter before the suffix, e.g. ".". + Delimiter string `json:"delimiter"` + + // FirstSuffix holds the first suffix defined for this MediaType. + FirstSuffix SuffixInfo `json:"-"` // This is the optional suffix after the "+" in the MIME type, // e.g. "xml" in "application/rss+xml". @@ -55,12 +53,16 @@ type Type struct { // E.g. "jpg,jpeg" // Stored as a string to make Type comparable. - suffixesCSV string + // For internal use only. + SuffixesCSV string `json:"-"` } -// SuffixInfo holds information about a Type's suffix. +// SuffixInfo holds information about a Media Type's suffix. type SuffixInfo struct { - Suffix string `json:"suffix"` + // Suffix is the suffix without the delimiter, e.g. "xml". + Suffix string `json:"suffix"` + + // FullSuffix is the suffix with the delimiter, e.g. ".xml". FullSuffix string `json:"fullSuffix"` } @@ -121,12 +123,21 @@ func FromStringAndExt(t, ext string) (Type, error) { if err != nil { return tp, err } - tp.suffixesCSV = strings.TrimPrefix(ext, ".") - tp.Delimiter = defaultDelimiter + tp.SuffixesCSV = strings.TrimPrefix(ext, ".") + tp.Delimiter = DefaultDelimiter tp.init() return tp, nil } +// MustFromString is like FromString but panics on error. +func MustFromString(t string) Type { + tp, err := FromString(t) + if err != nil { + panic(err) + } + return tp +} + // FromString creates a new Type given a type string on the form MainType/SubType and // an optional suffix, e.g. "text/html" or "text/html+html". func FromString(t string) (Type, error) { @@ -146,52 +157,49 @@ func FromString(t string) (Type, error) { suffix = subParts[1] } - return Type{MainType: mainType, SubType: subType, mimeSuffix: suffix}, nil -} - -// Type returns a string representing the main- and sub-type of a media type, e.g. "text/css". -// A suffix identifier will be appended after a "+" if set, e.g. "image/svg+xml". -// Hugo will register a set of default media types. -// These can be overridden by the user in the configuration, -// by defining a media type with the same Type. -func (m Type) Type() string { - // Examples are - // image/svg+xml - // text/css - if m.mimeSuffix != "" { - return m.MainType + "/" + m.SubType + "+" + m.mimeSuffix + var typ string + if suffix != "" { + typ = mainType + "/" + subType + "+" + suffix + } else { + typ = mainType + "/" + subType } - return m.MainType + "/" + m.SubType + + return Type{Type: typ, MainType: mainType, SubType: subType, mimeSuffix: suffix}, nil } // For internal use. func (m Type) String() string { - return m.Type() + return m.Type } // Suffixes returns all valid file suffixes for this type. func (m Type) Suffixes() []string { - if m.suffixesCSV == "" { + if m.SuffixesCSV == "" { return nil } - return strings.Split(m.suffixesCSV, ",") + return strings.Split(m.SuffixesCSV, ",") } // IsText returns whether this Type is a text format. // Note that this may currently return false negatives. // TODO(bep) improve +// For internal use. func (m Type) IsText() bool { if m.MainType == "text" { return true } switch m.SubType { - case "javascript", "json", "rss", "xml", "svg", TOMLType.SubType, YAMLType.SubType: + case "javascript", "json", "rss", "xml", "svg", "toml", "yml", "yaml": return true } return false } +func InitMediaType(m *Type) { + m.init() +} + func (m *Type) init() { m.FirstSuffix.FullSuffix = "" m.FirstSuffix.Suffix = "" @@ -204,13 +212,13 @@ func (m *Type) init() { // WithDelimiterAndSuffixes is used in tests. func WithDelimiterAndSuffixes(t Type, delimiter, suffixesCSV string) Type { t.Delimiter = delimiter - t.suffixesCSV = suffixesCSV + t.SuffixesCSV = suffixesCSV t.init() return t } func newMediaType(main, sub string, suffixes []string) Type { - t := Type{MainType: main, SubType: sub, suffixesCSV: strings.Join(suffixes, ","), Delimiter: defaultDelimiter} + t := Type{MainType: main, SubType: sub, SuffixesCSV: strings.Join(suffixes, ","), Delimiter: DefaultDelimiter} t.init() return t } @@ -222,118 +230,18 @@ func newMediaTypeWithMimeSuffix(main, sub, mimeSuffix string, suffixes []string) return mt } -// Definitions from https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types etc. -// Note that from Hugo 0.44 we only set Suffix if it is part of the MIME type. -var ( - CalendarType = newMediaType("text", "calendar", []string{"ics"}) - CSSType = newMediaType("text", "css", []string{"css"}) - SCSSType = newMediaType("text", "x-scss", []string{"scss"}) - SASSType = newMediaType("text", "x-sass", []string{"sass"}) - CSVType = newMediaType("text", "csv", []string{"csv"}) - HTMLType = newMediaType("text", "html", []string{"html"}) - JavascriptType = newMediaType("text", "javascript", []string{"js", "jsm", "mjs"}) - TypeScriptType = newMediaType("text", "typescript", []string{"ts"}) - TSXType = newMediaType("text", "tsx", []string{"tsx"}) - JSXType = newMediaType("text", "jsx", []string{"jsx"}) - - JSONType = newMediaType("application", "json", []string{"json"}) - WebAppManifestType = newMediaTypeWithMimeSuffix("application", "manifest", "json", []string{"webmanifest"}) - RSSType = newMediaTypeWithMimeSuffix("application", "rss", "xml", []string{"xml", "rss"}) - XMLType = newMediaType("application", "xml", []string{"xml"}) - SVGType = newMediaTypeWithMimeSuffix("image", "svg", "xml", []string{"svg"}) - TextType = newMediaType("text", "plain", []string{"txt"}) - TOMLType = newMediaType("application", "toml", []string{"toml"}) - YAMLType = newMediaType("application", "yaml", []string{"yaml", "yml"}) - - // Common image types - PNGType = newMediaType("image", "png", []string{"png"}) - JPEGType = newMediaType("image", "jpeg", []string{"jpg", "jpeg", "jpe", "jif", "jfif"}) - GIFType = newMediaType("image", "gif", []string{"gif"}) - TIFFType = newMediaType("image", "tiff", []string{"tif", "tiff"}) - BMPType = newMediaType("image", "bmp", []string{"bmp"}) - WEBPType = newMediaType("image", "webp", []string{"webp"}) - - // Common font types - TrueTypeFontType = newMediaType("font", "ttf", []string{"ttf"}) - OpenTypeFontType = newMediaType("font", "otf", []string{"otf"}) - - // Common document types - PDFType = newMediaType("application", "pdf", []string{"pdf"}) - MarkdownType = newMediaType("text", "markdown", []string{"md", "markdown"}) - - // Common video types - AVIType = newMediaType("video", "x-msvideo", []string{"avi"}) - MPEGType = newMediaType("video", "mpeg", []string{"mpg", "mpeg"}) - MP4Type = newMediaType("video", "mp4", []string{"mp4"}) - OGGType = newMediaType("video", "ogg", []string{"ogv"}) - WEBMType = newMediaType("video", "webm", []string{"webm"}) - GPPType = newMediaType("video", "3gpp", []string{"3gpp", "3gp"}) - - OctetType = newMediaType("application", "octet-stream", nil) -) - -// DefaultTypes is the default media types supported by Hugo. -var DefaultTypes = Types{ - CalendarType, - CSSType, - CSVType, - SCSSType, - SASSType, - HTMLType, - MarkdownType, - JavascriptType, - TypeScriptType, - TSXType, - JSXType, - JSONType, - WebAppManifestType, - RSSType, - XMLType, - SVGType, - TextType, - OctetType, - YAMLType, - TOMLType, - PNGType, - GIFType, - BMPType, - JPEGType, - WEBPType, - AVIType, - MPEGType, - MP4Type, - OGGType, - WEBMType, - GPPType, - OpenTypeFontType, - TrueTypeFontType, - PDFType, -} - -func init() { - sort.Sort(DefaultTypes) - - // Sanity check. - seen := make(map[Type]bool) - for _, t := range DefaultTypes { - if seen[t] { - panic(fmt.Sprintf("MediaType %s duplicated in list", t)) - } - seen[t] = true - } -} - // Types is a slice of media types. +// <docsmeta>{ "name": "MediaTypes" }</docsmeta> type Types []Type func (t Types) Len() int { return len(t) } func (t Types) Swap(i, j int) { t[i], t[j] = t[j], t[i] } -func (t Types) Less(i, j int) bool { return t[i].Type() < t[j].Type() } +func (t Types) Less(i, j int) bool { return t[i].Type < t[j].Type } // GetByType returns a media type for tp. func (t Types) GetByType(tp string) (Type, bool) { for _, tt := range t { - if strings.EqualFold(tt.Type(), tp) { + if strings.EqualFold(tt.Type, tp) { return tt, true } } @@ -399,8 +307,19 @@ func (t Types) GetBySuffix(suffix string) (tp Type, si SuffixInfo, found bool) { return } +func (t Types) IsTextSuffix(suffix string) bool { + suffix = strings.ToLower(suffix) + for _, tt := range t { + if tt.hasSuffix(suffix) { + return tt.IsText() + } + } + return false + +} + func (m Type) hasSuffix(suffix string) bool { - return strings.Contains(","+m.suffixesCSV+",", ","+suffix+",") + return strings.Contains(","+m.SuffixesCSV+",", ","+suffix+",") } // GetByMainSubType gets a media type given a main and a sub type e.g. "text" and "plain". @@ -423,96 +342,6 @@ func (t Types) GetByMainSubType(mainType, subType string) (tp Type, found bool) return } -func suffixIsRemoved() error { - return errors.New(`MediaType.Suffix is removed. Before Hugo 0.44 this was used both to set a custom file suffix and as way -to augment the mediatype definition (what you see after the "+", e.g. "image/svg+xml"). - -This had its limitations. For one, it was only possible with one file extension per MIME type. - -Now you can specify multiple file suffixes using "suffixes", but you need to specify the full MIME type -identifier: - -[mediaTypes] -[mediaTypes."image/svg+xml"] -suffixes = ["svg", "abc" ] - -In most cases, it will be enough to just change: - -[mediaTypes] -[mediaTypes."my/custom-mediatype"] -suffix = "txt" - -To: - -[mediaTypes] -[mediaTypes."my/custom-mediatype"] -suffixes = ["txt"] - -Note that you can still get the Media Type's suffix from a template: {{ $mediaType.Suffix }}. But this will now map to the MIME type filename. -`) -} - -// DecodeTypes takes a list of media type configurations and merges those, -// in the order given, with the Hugo defaults as the last resort. -func DecodeTypes(mms ...map[string]any) (Types, error) { - var m Types - - // Maps type string to Type. Type string is the full application/svg+xml. - mmm := make(map[string]Type) - for _, dt := range DefaultTypes { - mmm[dt.Type()] = dt - } - - for _, mm := range mms { - for k, v := range mm { - var mediaType Type - - mediaType, found := mmm[k] - if !found { - var err error - mediaType, err = FromString(k) - if err != nil { - return m, err - } - } - - if err := mapstructure.WeakDecode(v, &mediaType); err != nil { - return m, err - } - - vm := maps.ToStringMap(v) - maps.PrepareParams(vm) - _, delimiterSet := vm["delimiter"] - _, suffixSet := vm["suffix"] - - if suffixSet { - return Types{}, suffixIsRemoved() - } - - if suffixes, found := vm["suffixes"]; found { - mediaType.suffixesCSV = strings.TrimSpace(strings.ToLower(strings.Join(cast.ToStringSlice(suffixes), ","))) - } - - // The user may set the delimiter as an empty string. - if !delimiterSet && mediaType.suffixesCSV != "" { - mediaType.Delimiter = defaultDelimiter - } - - mediaType.init() - - mmm[k] = mediaType - - } - } - - for _, v := range mmm { - m = append(m, v) - } - sort.Sort(m) - - return m, nil -} - // IsZero reports whether this Type represents a zero value. // For internal use. func (m Type) IsZero() bool { @@ -530,8 +359,8 @@ func (m Type) MarshalJSON() ([]byte, error) { Suffixes []string `json:"suffixes"` }{ Alias: (Alias)(m), - Type: m.Type(), + Type: m.Type, String: m.String(), - Suffixes: strings.Split(m.suffixesCSV, ","), + Suffixes: strings.Split(m.SuffixesCSV, ","), }) } |