mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
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:
@@ -20,6 +20,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -45,6 +46,15 @@ var (
|
||||
".webp": WEBP,
|
||||
}
|
||||
|
||||
imageFormatsBySubType = map[string]Format{
|
||||
media.JPEGType.SubType: JPEG,
|
||||
media.PNGType.SubType: PNG,
|
||||
media.TIFFType.SubType: TIFF,
|
||||
media.BMPType.SubType: BMP,
|
||||
media.GIFType.SubType: GIF,
|
||||
media.WEBPType.SubType: WEBP,
|
||||
}
|
||||
|
||||
// Add or increment if changes to an image format's processing requires
|
||||
// re-generation.
|
||||
imageFormatsVersions = map[Format]int{
|
||||
@@ -102,6 +112,11 @@ func ImageFormatFromExt(ext string) (Format, bool) {
|
||||
return f, found
|
||||
}
|
||||
|
||||
func ImageFormatFromMediaSubType(sub string) (Format, bool) {
|
||||
f, found := imageFormatsBySubType[sub]
|
||||
return f, found
|
||||
}
|
||||
|
||||
const (
|
||||
defaultJPEGQuality = 75
|
||||
defaultResampleFilter = "box"
|
||||
|
@@ -66,6 +66,9 @@ func (*Filters) Text(text string, options ...interface{}) gift.Filter {
|
||||
case "linespacing":
|
||||
tf.linespacing = cast.ToInt(v)
|
||||
case "font":
|
||||
if err, ok := v.(error); ok {
|
||||
panic(fmt.Sprintf("invalid font source: %s", err))
|
||||
}
|
||||
fontSource, ok1 := v.(hugio.ReadSeekCloserProvider)
|
||||
identifier, ok2 := v.(resource.Identifier)
|
||||
|
||||
|
@@ -36,6 +36,7 @@ func TestCreatePlaceholders(t *testing.T) {
|
||||
"Suffixes": "pre_foo.Suffixes_post",
|
||||
"Delimiter": "pre_foo.Delimiter_post",
|
||||
"FirstSuffix": "pre_foo.FirstSuffix_post",
|
||||
"IsText": "pre_foo.IsText_post",
|
||||
"String": "pre_foo.String_post",
|
||||
"Type": "pre_foo.Type_post",
|
||||
"MainType": "pre_foo.MainType_post",
|
||||
|
@@ -69,6 +69,9 @@ type ResourceSourceDescriptor struct {
|
||||
|
||||
Fs afero.Fs
|
||||
|
||||
// Set when its known up front, else it's resolved from the target filename.
|
||||
MediaType media.Type
|
||||
|
||||
// The relative target filename without any language code.
|
||||
RelTargetFilename string
|
||||
|
||||
|
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -99,7 +100,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]interface{}) (resour
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read remote resource %s", uri)
|
||||
return nil, errors.Wrapf(err, "failed to read remote resource %q", uri)
|
||||
}
|
||||
|
||||
filename := path.Base(rURL.Path)
|
||||
@@ -109,33 +110,30 @@ func (c *Client) FromRemote(uri string, optionsm map[string]interface{}) (resour
|
||||
}
|
||||
}
|
||||
|
||||
var extension string
|
||||
var extensionHint string
|
||||
|
||||
if arr, _ := mime.ExtensionsByType(res.Header.Get("Content-Type")); len(arr) == 1 {
|
||||
extension = arr[0]
|
||||
extensionHint = arr[0]
|
||||
}
|
||||
|
||||
// If extension was not determined by header, look for a file extention
|
||||
if extension == "" {
|
||||
// Look for a file extention
|
||||
if extensionHint == "" {
|
||||
if ext := path.Ext(filename); ext != "" {
|
||||
extension = ext
|
||||
extensionHint = ext
|
||||
}
|
||||
}
|
||||
|
||||
// If extension was not determined by header or file extention, try using content itself
|
||||
if extension == "" {
|
||||
if ct := http.DetectContentType(body); ct != "application/octet-stream" {
|
||||
if ct == "image/jpeg" {
|
||||
extension = ".jpg"
|
||||
} else if arr, _ := mime.ExtensionsByType(ct); arr != nil {
|
||||
extension = arr[0]
|
||||
}
|
||||
}
|
||||
// Now resolve the media type primarily using the content.
|
||||
mediaType := media.FromContent(c.rs.MediaTypes, extensionHint, body)
|
||||
if mediaType.IsZero() {
|
||||
return nil, errors.Errorf("failed to resolve media type for remote resource %q", uri)
|
||||
}
|
||||
|
||||
resourceID = filename[:len(filename)-len(path.Ext(filename))] + "_" + resourceID + extension
|
||||
resourceID = filename[:len(filename)-len(path.Ext(filename))] + "_" + resourceID + mediaType.FirstSuffix.FullSuffix
|
||||
|
||||
return c.rs.New(
|
||||
resources.ResourceSourceDescriptor{
|
||||
MediaType: mediaType,
|
||||
LazyPublish: true,
|
||||
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
|
||||
return hugio.NewReadSeekerNoOpCloser(bytes.NewReader(body)), nil
|
||||
|
@@ -272,21 +272,28 @@ func (r *Spec) newResource(sourceFs afero.Fs, fd ResourceSourceDescriptor) (reso
|
||||
fd.RelTargetFilename = sourceFilename
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(fd.RelTargetFilename))
|
||||
mimeType, suffixInfo, found := r.MediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, "."))
|
||||
// TODO(bep) we need to handle these ambiguous types better, but in this context
|
||||
// we most likely want the application/xml type.
|
||||
if suffixInfo.Suffix == "xml" && mimeType.SubType == "rss" {
|
||||
mimeType, found = r.MediaTypes.GetByType("application/xml")
|
||||
}
|
||||
mimeType := fd.MediaType
|
||||
if mimeType.IsZero() {
|
||||
ext := strings.ToLower(filepath.Ext(fd.RelTargetFilename))
|
||||
var (
|
||||
found bool
|
||||
suffixInfo media.SuffixInfo
|
||||
)
|
||||
mimeType, suffixInfo, found = r.MediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, "."))
|
||||
// TODO(bep) we need to handle these ambiguous types better, but in this context
|
||||
// we most likely want the application/xml type.
|
||||
if suffixInfo.Suffix == "xml" && mimeType.SubType == "rss" {
|
||||
mimeType, found = r.MediaTypes.GetByType("application/xml")
|
||||
}
|
||||
|
||||
if !found {
|
||||
// A fallback. Note that mime.TypeByExtension is slow by Hugo standards,
|
||||
// so we should configure media types to avoid this lookup for most
|
||||
// situations.
|
||||
mimeStr := mime.TypeByExtension(ext)
|
||||
if mimeStr != "" {
|
||||
mimeType, _ = media.FromStringAndExt(mimeStr, ext)
|
||||
if !found {
|
||||
// A fallback. Note that mime.TypeByExtension is slow by Hugo standards,
|
||||
// so we should configure media types to avoid this lookup for most
|
||||
// situations.
|
||||
mimeStr := mime.TypeByExtension(ext)
|
||||
if mimeStr != "" {
|
||||
mimeType, _ = media.FromStringAndExt(mimeStr, ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +308,7 @@ func (r *Spec) newResource(sourceFs afero.Fs, fd ResourceSourceDescriptor) (reso
|
||||
mimeType)
|
||||
|
||||
if mimeType.MainType == "image" {
|
||||
imgFormat, ok := images.ImageFormatFromExt(ext)
|
||||
imgFormat, ok := images.ImageFormatFromMediaSubType(mimeType.SubType)
|
||||
if ok {
|
||||
ir := &imageResource{
|
||||
Image: images.NewImage(imgFormat, r.imaging, nil, gr),
|
||||
|
Reference in New Issue
Block a user