diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2021-12-16 15:12:13 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2021-12-17 09:50:28 +0100 |
commit | 44954497bcb2d6d589b9340a43323663061c7b42 (patch) | |
tree | 0d0d06b11e462ccff1a908c2b1c4dfd039b82787 /resources | |
parent | 22ef5da20d1685dfe6aff3bd9364c9b1f1d0d8f8 (diff) |
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
Diffstat (limited to 'resources')
-rw-r--r-- | resources/images/config.go | 15 | ||||
-rw-r--r-- | resources/images/filters.go | 3 | ||||
-rw-r--r-- | resources/postpub/fields_test.go | 1 | ||||
-rw-r--r-- | resources/resource.go | 3 | ||||
-rw-r--r-- | resources/resource_factories/create/remote.go | 30 | ||||
-rw-r--r-- | resources/resource_spec.go | 37 |
6 files changed, 58 insertions, 31 deletions
diff --git a/resources/images/config.go b/resources/images/config.go index c8990d5ca..a8b5412d6 100644 --- a/resources/images/config.go +++ b/resources/images/config.go @@ -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" diff --git a/resources/images/filters.go b/resources/images/filters.go index e166a0f9d..fd7e31457 100644 --- a/resources/images/filters.go +++ b/resources/images/filters.go @@ -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) diff --git a/resources/postpub/fields_test.go b/resources/postpub/fields_test.go index 19c3720f7..c408e7791 100644 --- a/resources/postpub/fields_test.go +++ b/resources/postpub/fields_test.go @@ -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", diff --git a/resources/resource.go b/resources/resource.go index 1f6246859..4bf35f9ac 100644 --- a/resources/resource.go +++ b/resources/resource.go @@ -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 diff --git a/resources/resource_factories/create/remote.go b/resources/resource_factories/create/remote.go index 53e77bc5e..f6d3f13dd 100644 --- a/resources/resource_factories/create/remote.go +++ b/resources/resource_factories/create/remote.go @@ -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 diff --git a/resources/resource_spec.go b/resources/resource_spec.go index 897c1bbaa..cd1e5010d 100644 --- a/resources/resource_spec.go +++ b/resources/resource_spec.go @@ -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), |