Permalink
Browse files

Prevent resource publishing for transformed inline resources

That is, if only `.Content` is accessed.

This means that, for a transformed resource to be published to `/public`, you need to access either `.RelPermalink` or `Permalink`.

Fixes #4944
  • Loading branch information...
bep committed Dec 21, 2018
1 parent 1021714 commit 43f9df0194d229805d80b13c9e38a7a0fec12cf4
Showing with 91 additions and 20 deletions.
  1. +27 −1 hugolib/resource_chain_test.go
  2. +64 −19 resource/transform.go
@@ -168,6 +168,8 @@ T1: {{ $r.Content }}
func TestResourceChain(t *testing.T) {
t.Parallel()

assert := require.New(t)

tests := []struct {
name string
shouldRun func() bool
@@ -199,7 +201,7 @@ T6: {{ $bundle1.Permalink }}
b.AssertFileContent("public/index.html", `T5 RelPermalink: /sass/styles3.css|`)
b.AssertFileContent("public/index.html", `T6: http://example.com/styles/bundle1.css`)

b.AssertFileContent("public/styles/templ.min.css", `.home{color:blue}`)
assert.False(b.CheckExists("public/styles/templ.min.css"))
b.AssertFileContent("public/styles/bundle1.css", `.home{color:blue}body{color:#333}`)

}},
@@ -313,6 +315,30 @@ T1: {{ $r1.Permalink }}|{{ $r1.RelPermalink }}

}},

// https://github.com/gohugoio/hugo/issues/4944
{"Prevent resource publish on .Content only", func() bool { return true }, func(b *sitesBuilder) {
b.WithTemplates("home.html", `
{{ $cssInline := "body { color: green; }" | resources.FromString "inline.css" | minify }}
{{ $cssPublish1 := "body { color: blue; }" | resources.FromString "external1.css" | minify }}
{{ $cssPublish2 := "body { color: orange; }" | resources.FromString "external2.css" | minify }}
Inline: {{ $cssInline.Content }}
Publish 1: {{ $cssPublish1.Content }} {{ $cssPublish1.RelPermalink }}
Publish 2: {{ $cssPublish2.Permalink }}
`)

}, func(b *sitesBuilder) {
b.AssertFileContent("public/index.html",
`Inline: body{color:green}`,
"Publish 1: body{color:blue} /external1.min.css",
"Publish 2: http://example.com/external2.min.css",
)
assert.True(b.CheckExists("public/external2.min.css"), "Referenced content should be copied to /public")
assert.True(b.CheckExists("public/external1.min.css"), "Referenced content should be copied to /public")

assert.False(b.CheckExists("public/inline.min.css"), "Inline content should not be copied to /public")
}},

{"template", func() bool { return true }, func(b *sitesBuilder) {}, func(b *sitesBuilder) {
}},
}
@@ -183,6 +183,11 @@ type transformedResource struct {
transformInit sync.Once
transformErr error

// We delay publishing until either .RelPermalink or .Permalink
// is invoked.
publishInit sync.Once
published bool

// The transformed values
content string
contentInit sync.Once
@@ -220,7 +225,7 @@ func (r *transformedResource) tryTransformedFileCache(key string) io.ReadCloser
}

func (r *transformedResource) Content() (interface{}, error) {
if err := r.initTransform(true); err != nil {
if err := r.initTransform(true, false); err != nil {
return nil, err
}
if err := r.initContent(); err != nil {
@@ -230,29 +235,29 @@ func (r *transformedResource) Content() (interface{}, error) {
}

func (r *transformedResource) Data() interface{} {
if err := r.initTransform(false); err != nil {
if err := r.initTransform(false, false); err != nil {
return noData
}
return r.MetaData
}

func (r *transformedResource) MediaType() media.Type {
if err := r.initTransform(false); err != nil {
if err := r.initTransform(false, false); err != nil {
return media.Type{}
}
m, _ := r.cache.rs.MediaTypes.GetByType(r.MediaTypeV)
return m
}

func (r *transformedResource) Permalink() string {
if err := r.initTransform(false); err != nil {
if err := r.initTransform(false, true); err != nil {
return ""
}
return r.linker.permalinkFor(r.Target)
}

func (r *transformedResource) RelPermalink() string {
if err := r.initTransform(false); err != nil {
if err := r.initTransform(false, true); err != nil {
return ""
}
return r.linker.relPermalinkFor(r.Target)
@@ -271,11 +276,11 @@ func (r *transformedResource) initContent() error {
return err
}

func (r *transformedResource) transform(setContent bool) (err error) {
func (r *transformedResource) openPublishFileForWriting(relTargetPath string) (io.WriteCloser, error) {
return helpers.OpenFilesForWriting(r.cache.rs.PublishFs, r.linker.relTargetPathsFor(relTargetPath)...)
}

openPublishFileForWriting := func(relTargetPath string) (io.WriteCloser, error) {
return helpers.OpenFilesForWriting(r.cache.rs.PublishFs, r.linker.relTargetPathsFor(relTargetPath)...)
}
func (r *transformedResource) transform(setContent, publish bool) (err error) {

// This can be the last resource in a chain.
// Rewind and create a processing chain.
@@ -345,7 +350,7 @@ func (r *transformedResource) transform(setContent bool) (err error) {

tctx := &ResourceTransformationCtx{
Data: r.transformedResourceMetadata.MetaData,
OpenResourcePublisher: openPublishFileForWriting,
OpenResourcePublisher: r.openPublishFileForWriting,
}

tctx.InMediaType = first.MediaType()
@@ -426,14 +431,18 @@ func (r *transformedResource) transform(setContent bool) (err error) {
r.MediaTypeV = tctx.OutMediaType.Type()
}

publicw, err := openPublishFileForWriting(r.Target)
if err != nil {
r.transformErr = err
return
}
defer publicw.Close()
var publishwriters []io.WriteCloser

if publish {
publicw, err := r.openPublishFileForWriting(r.Target)
if err != nil {
r.transformErr = err
return err
}
defer publicw.Close()

publishwriters := []io.WriteCloser{publicw}
publishwriters = append(publishwriters, publicw)
}

if transformedContentr == nil {
// Also write it to the cache
@@ -474,13 +483,49 @@ func (r *transformedResource) transform(setContent bool) (err error) {
return nil

}
func (r *transformedResource) initTransform(setContent bool) error {
func (r *transformedResource) initTransform(setContent, publish bool) error {
r.transformInit.Do(func() {
if err := r.transform(setContent); err != nil {
r.published = publish
if err := r.transform(setContent, publish); err != nil {
r.transformErr = err
r.cache.rs.Logger.ERROR.Println("error: failed to transform resource:", err)
}

})

if !publish {
return r.transformErr
}

r.publishInit.Do(func() {
if r.published {
return
}

r.published = true

// Copy the file from cache to /public
_, src, err := r.cache.fileCache.Get(r.sourceFilename)

if err == nil {
defer src.Close()

var dst io.WriteCloser
dst, err = r.openPublishFileForWriting(r.Target)
if err == nil {
defer dst.Close()
io.Copy(dst, src)
}
}

if err != nil {
r.transformErr = err
r.cache.rs.Logger.ERROR.Println("error: failed to publish resource:", err)
return
}

})

return r.transformErr
}

0 comments on commit 43f9df0

Please sign in to comment.