Always use content to resolve content type in resources.GetRemote

This is a security hardening measure; don't trust the URL extension or any `Content-Type`/`Content-Disposition` header on its own, always look at the file content using Go's `http.DetectContentType`.

This commit also adds ttf and otf media type definitions to Hugo.

Fixes #9302
Fixes #9301
This commit is contained in:
Bjørn Erik Pedersen
2021-12-16 15:12:13 +01:00
parent 22ef5da20d
commit 44954497bc
26 changed files with 378 additions and 49 deletions

View File

@@ -17,6 +17,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strings"
@@ -60,6 +61,42 @@ type SuffixInfo struct {
FullSuffix string `json:"fullSuffix"`
}
// FromContent resolve the Type primarily using http.DetectContentType.
// If http.DetectContentType resolves to application/octet-stream, a zero Type is returned.
// If http.DetectContentType resolves to text/plain or application/xml, we try to get more specific using types and ext.
func FromContent(types Types, ext string, content []byte) Type {
ext = strings.TrimPrefix(ext, ".")
t := strings.Split(http.DetectContentType(content), ";")[0]
var m Type
if t == "application/octet-stream" {
return m
}
var found bool
m, found = types.GetByType(t)
if !found {
if t == "text/xml" {
// This is how it's configured in Hugo by default.
m, found = types.GetByType("application/xml")
}
}
if !found || ext == "" {
return m
}
if m.Type() == "text/plain" || m.Type() == "application/xml" {
// http.DetectContentType isn't brilliant when it comes to common text formats, so we need to do better.
// For now we say that if it's detected to be a text format and the extension/content type in header reports
// it to be a text format, then we use that.
mm, _, found := types.GetFirstBySuffix(ext)
if found && mm.IsText() {
return mm
}
}
return m
}
// FromStringAndExt creates a Type from a MIME string and a given extension.
func FromStringAndExt(t, ext string) (Type, error) {
tp, err := fromString(t)
@@ -122,6 +159,21 @@ func (m Type) Suffixes() []string {
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
func (m Type) IsText() bool {
if m.MainType == "text" {
return true
}
switch m.SubType {
case "javascript", "json", "rss", "xml", "svg", TOMLType.SubType, YAMLType.SubType:
return true
}
return false
}
func (m *Type) init() {
m.FirstSuffix.FullSuffix = ""
m.FirstSuffix.Suffix = ""
@@ -183,6 +235,10 @@ var (
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 video types
AVIType = newMediaType("video", "x-msvideo", []string{"avi"})
MPEGType = newMediaType("video", "mpeg", []string{"mpg", "mpeg"})
@@ -224,6 +280,8 @@ var DefaultTypes = Types{
OGGType,
WEBMType,
GPPType,
OpenTypeFontType,
TrueTypeFontType,
}
func init() {