ref: e5f229974166402f51e4ee0695ffb4d1e09fa174
parent: 87a07282a2f01779e098cde0aaee1bae34dc32e6
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Wed Jul 24 20:12:40 EDT 2019
Block symlink dir traversal for /static This is in line with how it behaved before, but it was lifted a little for the project mount for Hugo Modules, but that could create hard-to-detect loops.
--- a/cache/filecache/filecache_test.go
+++ b/cache/filecache/filecache_test.go
@@ -292,7 +292,7 @@
cfg, err := config.FromConfigString(configStr, "toml")
assert.NoError(err)
initConfig(fs, cfg)
- p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg)
+ p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil)
assert.NoError(err)
return p
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -207,7 +207,7 @@
cfg.OutputFormats = output.DefaultFormats
}
- ps, err := helpers.NewPathSpec(fs, cfg.Language)
+ ps, err := helpers.NewPathSpec(fs, cfg.Language, logger)
if err != nil {return nil, errors.Wrap(err, "create PathSpec")
@@ -272,7 +272,7 @@
l := cfg.Language
var err error
- d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, l, d.BaseFs)
+ d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, l, d.Log, d.BaseFs)
if err != nil {return nil, err
}
--- a/helpers/path_test.go
+++ b/helpers/path_test.go
@@ -60,7 +60,7 @@
v.Set("removePathAccents", test.removeAccents)l := langs.NewDefaultLanguage(v)
- p, err := NewPathSpec(hugofs.NewMem(v), l)
+ p, err := NewPathSpec(hugofs.NewMem(v), l, nil)
require.NoError(t, err)
output := p.MakePath(test.input)
@@ -73,7 +73,7 @@
func TestMakePathSanitized(t *testing.T) {v := newTestCfg()
- p, _ := NewPathSpec(hugofs.NewMem(v), v)
+ p, _ := NewPathSpec(hugofs.NewMem(v), v, nil)
tests := []struct {input string
@@ -101,7 +101,7 @@
v.Set("disablePathToLower", true)l := langs.NewDefaultLanguage(v)
- p, _ := NewPathSpec(hugofs.NewMem(v), l)
+ p, _ := NewPathSpec(hugofs.NewMem(v), l, nil)
tests := []struct {input string
--- a/helpers/pathspec.go
+++ b/helpers/pathspec.go
@@ -16,6 +16,7 @@
import (
"strings"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib/filesystems"
@@ -37,13 +38,13 @@
}
// NewPathSpec creats a new PathSpec from the given filesystems and language.
-func NewPathSpec(fs *hugofs.Fs, cfg config.Provider) (*PathSpec, error) {- return NewPathSpecWithBaseBaseFsProvided(fs, cfg, nil)
+func NewPathSpec(fs *hugofs.Fs, cfg config.Provider, logger *loggers.Logger) (*PathSpec, error) {+ return NewPathSpecWithBaseBaseFsProvided(fs, cfg, logger, nil)
}
// NewPathSpecWithBaseBaseFsProvided creats a new PathSpec from the given filesystems and language.
// If an existing BaseFs is provided, parts of that is reused.
-func NewPathSpecWithBaseBaseFsProvided(fs *hugofs.Fs, cfg config.Provider, baseBaseFs *filesystems.BaseFs) (*PathSpec, error) {+func NewPathSpecWithBaseBaseFsProvided(fs *hugofs.Fs, cfg config.Provider, logger *loggers.Logger, baseBaseFs *filesystems.BaseFs) (*PathSpec, error) {p, err := paths.New(fs, cfg)
if err != nil {@@ -56,7 +57,7 @@
filesystems.WithBaseFs(baseBaseFs),
}
}
- bfs, err := filesystems.NewBase(p, options...)
+ bfs, err := filesystems.NewBase(p, logger, options...)
if err != nil {return nil, err
}
--- a/helpers/pathspec_test.go
+++ b/helpers/pathspec_test.go
@@ -42,7 +42,7 @@
fs := hugofs.NewMem(v)
fs.Source.MkdirAll(filepath.FromSlash("thework/thethemes/thetheme"), 0777)- p, err := NewPathSpec(fs, l)
+ p, err := NewPathSpec(fs, l, nil)
require.NoError(t, err)
require.True(t, p.CanonifyURLs)
--- a/helpers/testhelpers_test.go
+++ b/helpers/testhelpers_test.go
@@ -10,7 +10,7 @@
func newTestPathSpec(fs *hugofs.Fs, v *viper.Viper) *PathSpec {l := langs.NewDefaultLanguage(v)
- ps, _ := NewPathSpec(fs, l)
+ ps, _ := NewPathSpec(fs, l, nil)
return ps
}
--- a/helpers/url_test.go
+++ b/helpers/url_test.go
@@ -28,7 +28,7 @@
v := newTestCfg()
l := langs.NewDefaultLanguage(v)
- p, _ := NewPathSpec(hugofs.NewMem(v), l)
+ p, _ := NewPathSpec(hugofs.NewMem(v), l, nil)
tests := []struct {input string
@@ -90,7 +90,7 @@
v.Set("baseURL", test.baseURL) v.Set("contentDir", "content")l := langs.NewLanguage(lang, v)
- p, _ := NewPathSpec(hugofs.NewMem(v), l)
+ p, _ := NewPathSpec(hugofs.NewMem(v), l, nil)
output := p.AbsURL(test.input, addLanguage)
expected := test.expected
@@ -168,7 +168,7 @@
v.Set("baseURL", test.baseURL) v.Set("canonifyURLs", test.canonify)l := langs.NewLanguage(lang, v)
- p, _ := NewPathSpec(hugofs.NewMem(v), l)
+ p, _ := NewPathSpec(hugofs.NewMem(v), l, nil)
output := p.RelURL(test.input, addLanguage)
@@ -256,7 +256,7 @@
v := newTestCfg()
v.Set("uglyURLs", d.ugly)l := langs.NewDefaultLanguage(v)
- p, _ := NewPathSpec(hugofs.NewMem(v), l)
+ p, _ := NewPathSpec(hugofs.NewMem(v), l, nil)
output := p.URLPrep(d.input)
if d.output != output {--- a/hugofs/decorators.go
+++ b/hugofs/decorators.go
@@ -90,19 +90,14 @@
isSymlink := isSymlink(fi)
if isSymlink {meta[metaKeyOriginalFilename] = filename
- link, err := filepath.EvalSymlinks(filename)
+ var link string
+ var err error
+ link, fi, err = evalSymlinks(fs, filename)
if err != nil {return nil, err
}
-
- fi, err = fs.Stat(link)
- if err != nil {- return nil, err
- }
-
filename = link
meta[metaKeyIsSymlink] = true
-
}
opener := func() (afero.File, error) {@@ -115,6 +110,20 @@
ffs.decorate = decorator
return ffs
+}
+
+func evalSymlinks(fs afero.Fs, filename string) (string, os.FileInfo, error) {+ link, err := filepath.EvalSymlinks(filename)
+ if err != nil {+ return "", nil, err
+ }
+
+ fi, err := fs.Stat(link)
+ if err != nil {+ return "", nil, err
+ }
+
+ return link, fi, nil
}
type baseFileDecoratorFs struct {--- a/hugofs/fileinfo.go
+++ b/hugofs/fileinfo.go
@@ -180,9 +180,20 @@
type fileInfoMeta struct {os.FileInfo
+
m FileMeta
}
+// Name returns the file's name. Note that we follow symlinks,
+// if supported by the file system, and the Name given here will be the
+// name of the symlink, which is what Hugo needs in all situations.
+func (fi *fileInfoMeta) Name() string {+ if name := fi.m.Name(); name != "" {+ return name
+ }
+ return fi.FileInfo.Name()
+}
+
func (fi *fileInfoMeta) Meta() FileMeta {return fi.m
}
@@ -294,4 +305,12 @@
return norm.NFC.String(filename)
}
return filename
+}
+
+func fileInfosToNames(fis []os.FileInfo) []string {+ names := make([]string, len(fis))
+ for i, d := range fis {+ names[i] = d.Name()
+ }
+ return names
}
--- a/hugofs/nosymlink_fs.go
+++ b/hugofs/nosymlink_fs.go
@@ -16,7 +16,10 @@
import (
"errors"
"os"
+ "path/filepath"
+ "github.com/gohugoio/hugo/common/loggers"
+
"github.com/spf13/afero"
)
@@ -24,15 +27,48 @@
ErrPermissionSymlink = errors.New("symlinks not allowed in this filesystem"))
-func NewNoSymlinkFs(fs afero.Fs) afero.Fs {- return &noSymlinkFs{Fs: fs}+// NewNoSymlinkFs creates a new filesystem that prevents symlinks.
+func NewNoSymlinkFs(fs afero.Fs, logger *loggers.Logger, allowFiles bool) afero.Fs {+ return &noSymlinkFs{Fs: fs, logger: logger, allowFiles: allowFiles}}
// noSymlinkFs is a filesystem that prevents symlinking.
type noSymlinkFs struct {+ allowFiles bool // block dirs only
+ logger *loggers.Logger
afero.Fs
}
+type noSymlinkFile struct {+ fs *noSymlinkFs
+ afero.File
+}
+
+func (f *noSymlinkFile) Readdir(count int) ([]os.FileInfo, error) {+ fis, err := f.File.Readdir(count)
+
+ filtered := fis[:0]
+ for _, x := range fis {+ filename := filepath.Join(f.Name(), x.Name())
+ if _, err := f.fs.checkSymlinkStatus(filename, x); err != nil {+ // Log a warning and drop the file from the list
+ logUnsupportedSymlink(filename, f.fs.logger)
+ } else {+ filtered = append(filtered, x)
+ }
+ }
+
+ return filtered, err
+}
+
+func (f *noSymlinkFile) Readdirnames(count int) ([]string, error) {+ dirs, err := f.Readdir(count)
+ if err != nil {+ return nil, err
+ }
+ return fileInfosToNames(dirs), nil
+}
+
func (fs *noSymlinkFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {return fs.stat(name)
}
@@ -53,21 +89,48 @@
if lstater, ok := fs.Fs.(afero.Lstater); ok {fi, wasLstat, err = lstater.LstatIfPossible(name)
} else {-
fi, err = fs.Fs.Stat(name)
}
+ if err != nil {+ return nil, false, err
+ }
+
+ fi, err = fs.checkSymlinkStatus(name, fi)
+
+ return fi, wasLstat, err
+}
+
+func (fs *noSymlinkFs) checkSymlinkStatus(name string, fi os.FileInfo) (os.FileInfo, error) {var metaIsSymlink bool
if fim, ok := fi.(FileMetaInfo); ok {- metaIsSymlink = fim.Meta().IsSymlink()
+ meta := fim.Meta()
+ metaIsSymlink = meta.IsSymlink()
}
- if metaIsSymlink || isSymlink(fi) {- return nil, wasLstat, ErrPermissionSymlink
+ if metaIsSymlink {+ if fs.allowFiles && !fi.IsDir() {+ return fi, nil
+ }
+ return nil, ErrPermissionSymlink
}
- return fi, wasLstat, err
+ // Also support non-decorated filesystems, e.g. the Os fs.
+ if isSymlink(fi) {+ // Need to determine if this is a directory or not.
+ _, sfi, err := evalSymlinks(fs.Fs, name)
+ if err != nil {+ return nil, err
+ }
+ if fs.allowFiles && !sfi.IsDir() {+ // Return the original FileInfo to get the expected Name.
+ return fi, nil
+ }
+ return nil, ErrPermissionSymlink
+ }
+
+ return fi, nil
}
func (fs *noSymlinkFs) Open(name string) (afero.File, error) {@@ -74,7 +137,7 @@
if _, _, err := fs.stat(name); err != nil {return nil, err
}
- return fs.Fs.Open(name)
+ return fs.wrapFile(fs.Fs.Open(name))
}
func (fs *noSymlinkFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {@@ -81,5 +144,13 @@
if _, _, err := fs.stat(name); err != nil {return nil, err
}
- return fs.Fs.OpenFile(name, flag, perm)
+ return fs.wrapFile(fs.Fs.OpenFile(name, flag, perm))
+}
+
+func (fs *noSymlinkFs) wrapFile(f afero.File, err error) (afero.File, error) {+ if err != nil {+ return nil, err
+ }
+
+ return &noSymlinkFile{File: f, fs: fs}, nil}
--- a/hugofs/nosymlink_test.go
+++ b/hugofs/nosymlink_test.go
@@ -18,6 +18,8 @@
"path/filepath"
"testing"
+ "github.com/gohugoio/hugo/common/loggers"
+
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/afero"
@@ -25,73 +27,120 @@
"github.com/stretchr/testify/require"
)
-func TestNoSymlinkFs(t *testing.T) {- if skipSymlink() {- t.Skip("Skip; os.Symlink needs administrator rights on Windows")- }
+func prepareSymlinks(t *testing.T) (string, func()) {assert := require.New(t)
- workDir, clean, err := htesting.CreateTempDir(Os, "hugo-nosymlink")
+
+ workDir, clean, err := htesting.CreateTempDir(Os, "hugo-symlink-test")
assert.NoError(err)
- defer clean()
wd, _ := os.Getwd()
- defer func() {- os.Chdir(wd)
- }()
blogDir := filepath.Join(workDir, "blog")
- blogFile := filepath.Join(blogDir, "a.txt")
- assert.NoError(os.MkdirAll(blogDir, 0777))
- afero.WriteFile(Os, filepath.Join(blogFile), []byte("content"), 0777)+ blogSubDir := filepath.Join(blogDir, "sub")
+ assert.NoError(os.MkdirAll(blogSubDir, 0777))
+ blogFile1 := filepath.Join(blogDir, "a.txt")
+ blogFile2 := filepath.Join(blogSubDir, "b.txt")
+ afero.WriteFile(Os, filepath.Join(blogFile1), []byte("content1"), 0777)+ afero.WriteFile(Os, filepath.Join(blogFile2), []byte("content2"), 0777)os.Chdir(workDir)
assert.NoError(os.Symlink("blog", "symlinkdedir"))os.Chdir(blogDir)
+ assert.NoError(os.Symlink("sub", "symsub")) assert.NoError(os.Symlink("a.txt", "symlinkdedfile.txt"))- fs := NewNoSymlinkFs(Os)
- ls := fs.(afero.Lstater)
- symlinkedDir := filepath.Join(workDir, "symlinkdedir")
- symlinkedFile := filepath.Join(blogDir, "symlinkdedfile.txt")
+ return workDir, func() {+ clean()
+ os.Chdir(wd)
+ }
+}
- // Check Stat and Lstat
- for _, stat := range []func(name string) (os.FileInfo, error){- func(name string) (os.FileInfo, error) {- return fs.Stat(name)
- },
- func(name string) (os.FileInfo, error) {- fi, _, err := ls.LstatIfPossible(name)
- return fi, err
- },
- } {- _, err = stat(symlinkedDir)
- assert.Equal(ErrPermissionSymlink, err)
- _, err = stat(symlinkedFile)
- assert.Equal(ErrPermissionSymlink, err)
+func TestNoSymlinkFs(t *testing.T) {+ if skipSymlink() {+ t.Skip("Skip; os.Symlink needs administrator rights on Windows")+ }
+ assert := require.New(t)
+ workDir, clean := prepareSymlinks(t)
+ defer clean()
- fi, err := stat(filepath.Join(workDir, "blog"))
- assert.NoError(err)
- assert.NotNil(fi)
+ blogDir := filepath.Join(workDir, "blog")
+ blogFile1 := filepath.Join(blogDir, "a.txt")
- fi, err = stat(blogFile)
- assert.NoError(err)
- assert.NotNil(fi)
- }
+ logger := loggers.NewWarningLogger()
- // Check Open
- _, err = fs.Open(symlinkedDir)
- assert.Equal(ErrPermissionSymlink, err)
- _, err = fs.OpenFile(symlinkedDir, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
- assert.Equal(ErrPermissionSymlink, err)
- _, err = fs.OpenFile(symlinkedFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
- assert.Equal(ErrPermissionSymlink, err)
- _, err = fs.Open(symlinkedFile)
- assert.Equal(ErrPermissionSymlink, err)
- f, err := fs.Open(blogDir)
- assert.NoError(err)
- f.Close()
- f, err = fs.Open(blogFile)
- assert.NoError(err)
- f.Close()
+ for _, bfs := range []afero.Fs{NewBaseFileDecorator(Os), Os} {+ for _, allowFiles := range []bool{false, true} {+ logger.WarnCounter.Reset()
+ fs := NewNoSymlinkFs(bfs, logger, allowFiles)
+ ls := fs.(afero.Lstater)
+ symlinkedDir := filepath.Join(workDir, "symlinkdedir")
+ symlinkedFilename := "symlinkdedfile.txt"
+ symlinkedFile := filepath.Join(blogDir, symlinkedFilename)
- // os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
+ assertFileErr := func(err error) {+ if allowFiles {+ assert.NoError(err)
+ } else {+ assert.Equal(ErrPermissionSymlink, err)
+ }
+ }
+
+ assertFileStat := func(name string, fi os.FileInfo, err error) {+ t.Helper()
+ assertFileErr(err)
+ if err == nil {+ assert.NotNil(fi)
+ assert.Equal(name, fi.Name())
+ }
+ }
+
+ // Check Stat and Lstat
+ for _, stat := range []func(name string) (os.FileInfo, error){+ func(name string) (os.FileInfo, error) {+ return fs.Stat(name)
+ },
+ func(name string) (os.FileInfo, error) {+ fi, _, err := ls.LstatIfPossible(name)
+ return fi, err
+ },
+ } {+ fi, err := stat(symlinkedDir)
+ assert.Equal(ErrPermissionSymlink, err)
+ fi, err = stat(symlinkedFile)
+ assertFileStat(symlinkedFilename, fi, err)
+
+ fi, err = stat(filepath.Join(workDir, "blog"))
+ assert.NoError(err)
+ assert.NotNil(fi)
+
+ fi, err = stat(blogFile1)
+ assert.NoError(err)
+ assert.NotNil(fi)
+ }
+
+ // Check Open
+ _, err := fs.Open(symlinkedDir)
+ assert.Equal(ErrPermissionSymlink, err)
+ _, err = fs.OpenFile(symlinkedDir, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
+ assert.Equal(ErrPermissionSymlink, err)
+ _, err = fs.OpenFile(symlinkedFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
+ assertFileErr(err)
+ _, err = fs.Open(symlinkedFile)
+ assertFileErr(err)
+ f, err := fs.Open(blogDir)
+ assert.NoError(err)
+ f.Close()
+ f, err = fs.Open(blogFile1)
+ assert.NoError(err)
+ f.Close()
+
+ // Check readdir
+ f, err = fs.Open(workDir)
+ assert.NoError(err)
+ // There is at least one unsported symlink inside workDir
+ _, err = f.Readdir(-1)
+ f.Close()
+ assert.Equal(uint64(1), logger.WarnCounter.Count())
+
+ }
+ }
}
--- a/hugofs/rootmapping_fs.go
+++ b/hugofs/rootmapping_fs.go
@@ -459,9 +459,5 @@
if err != nil {return nil, err
}
- dirss := make([]string, len(dirs))
- for i, d := range dirs {- dirss[i] = d.Name()
- }
- return dirss, nil
+ return fileInfosToNames(dirs), nil
}
--- a/hugofs/walk.go
+++ b/hugofs/walk.go
@@ -121,8 +121,7 @@
return nil
}
- if err == ErrPermissionSymlink {- w.logger.WARN.Printf("Unsupported symlink found in %q, skipping.", w.root)+ if w.checkErr(w.root, err) {return nil
}
@@ -149,6 +148,19 @@
return fi, false, err
}
+// checkErr returns true if the error is handled.
+func (w *Walkway) checkErr(filename string, err error) bool {+ if err == ErrPermissionSymlink {+ logUnsupportedSymlink(filename, w.logger)
+ return true
+ }
+ return false
+}
+
+func logUnsupportedSymlink(filename string, logger *loggers.Logger) {+ logger.WARN.Printf("Unsupported symlink found in %q, skipping.", filename)+}
+
// walk recursively descends path, calling walkFn.
// It follow symlinks if supported by the filesystem, but only the same path once.
func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo, walkFn WalkFunc) error {@@ -168,8 +180,10 @@
if dirEntries == nil {f, err := w.fs.Open(path)
-
if err != nil {+ if w.checkErr(path, err) {+ return nil
+ }
return walkFn(path, info, errors.Wrapf(err, "walk: open %q (%q)", path, w.root))
}
@@ -176,8 +190,7 @@
fis, err := f.Readdir(-1)
f.Close()
if err != nil {- if err == ErrPermissionSymlink {- w.logger.WARN.Printf("Unsupported symlink found in %q, skipping.", filename)+ if w.checkErr(filename, err) {return nil
}
return walkFn(path, info, errors.Wrap(err, "walk: Readdir"))
--- a/hugolib/data/hugo.toml
+++ /dev/null
@@ -1,1 +1,0 @@
-slogan = "Hugo Rocks!"
\ No newline at end of file
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -23,6 +23,8 @@
"strings"
"sync"
+ "github.com/gohugoio/hugo/common/loggers"
+
"github.com/gohugoio/hugo/hugofs/files"
"github.com/pkg/errors"
@@ -295,8 +297,11 @@
}
// NewBase builds the filesystems used by Hugo given the paths and options provided.NewBase
-func NewBase(p *paths.Paths, options ...func(*BaseFs) error) (*BaseFs, error) {+func NewBase(p *paths.Paths, logger *loggers.Logger, options ...func(*BaseFs) error) (*BaseFs, error) {fs := p.Fs
+ if logger == nil {+ logger = loggers.NewWarningLogger()
+ }
publishFs := afero.NewBasePathFs(fs.Destination, p.AbsPublishDir)
@@ -314,7 +319,7 @@
return b, nil
}
- builder := newSourceFilesystemsBuilder(p, b)
+ builder := newSourceFilesystemsBuilder(p, logger, b)
sourceFilesystems, err := builder.Build()
if err != nil {return nil, errors.Wrap(err, "build filesystems")
@@ -327,6 +332,7 @@
}
type sourceFilesystemsBuilder struct {+ logger *loggers.Logger
p *paths.Paths
sourceFs afero.Fs
result *SourceFilesystems
@@ -333,9 +339,9 @@
theBigFs *filesystemsCollector
}
-func newSourceFilesystemsBuilder(p *paths.Paths, b *BaseFs) *sourceFilesystemsBuilder {+func newSourceFilesystemsBuilder(p *paths.Paths, logger *loggers.Logger, b *BaseFs) *sourceFilesystemsBuilder {sourceFs := hugofs.NewBaseFileDecorator(p.Fs.Source)
- return &sourceFilesystemsBuilder{p: p, sourceFs: sourceFs, theBigFs: b.theBigFs, result: &SourceFilesystems{}}+ return &sourceFilesystemsBuilder{p: p, logger: logger, sourceFs: sourceFs, theBigFs: b.theBigFs, result: &SourceFilesystems{}}}
func (b *sourceFilesystemsBuilder) newSourceFilesystem(fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem {@@ -415,7 +421,7 @@
ms[k] = sfs
}
} else {- bfs := afero.NewBasePathFs(b.theBigFs.overlayMounts, files.ComponentFolderStatic)
+ bfs := afero.NewBasePathFs(b.theBigFs.overlayMountsStatic, files.ComponentFolderStatic)
ms[""] = b.newSourceFilesystem(bfs, b.result.StaticDirs)
}
@@ -432,7 +438,7 @@
collector := &filesystemsCollector{sourceProject: b.sourceFs,
- sourceModules: hugofs.NewNoSymlinkFs(b.sourceFs),
+ sourceModules: hugofs.NewNoSymlinkFs(b.sourceFs, b.logger, false),
overlayDirs: make(map[string][]hugofs.FileMetaInfo),
staticPerLanguage: staticFsMap,
}
@@ -475,6 +481,10 @@
return strings.HasPrefix(mnt.Target, files.ComponentFolderContent)
}
+func (b *sourceFilesystemsBuilder) isStaticMount(mnt modules.Mount) bool {+ return strings.HasPrefix(mnt.Target, files.ComponentFolderStatic)
+}
+
func (b *sourceFilesystemsBuilder) createModFs(
collector *filesystemsCollector,
md mountsDescriptor) error {@@ -482,6 +492,7 @@
var (
fromTo []hugofs.RootMapping
fromToContent []hugofs.RootMapping
+ fromToStatic []hugofs.RootMapping
)
absPathify := func(path string) string {@@ -544,6 +555,8 @@
if isContentMount {fromToContent = append(fromToContent, rm)
+ } else if b.isStaticMount(mount) {+ fromToStatic = append(fromToStatic, rm)
} else {fromTo = append(fromTo, rm)
}
@@ -553,6 +566,7 @@
if !md.isMainProject {modBase = collector.sourceModules
}
+ sourceStatic := hugofs.NewNoSymlinkFs(modBase, b.logger, true)
rmfs, err := hugofs.NewRootMappingFs(modBase, fromTo...)
if err != nil {@@ -562,17 +576,22 @@
if err != nil {return err
}
+ rmfsStatic, err := hugofs.NewRootMappingFs(sourceStatic, fromToStatic...)
+ if err != nil {+ return err
+ }
// We need to keep the ordered list of directories for watching and
// some special merge operations (data, i18n).
collector.addDirs(rmfs)
collector.addDirs(rmfsContent)
+ collector.addDirs(rmfsStatic)
if collector.staticPerLanguage != nil { for _, l := range b.p.Languages {lang := l.Lang
- lfs := rmfs.Filter(func(rm hugofs.RootMapping) bool {+ lfs := rmfsStatic.Filter(func(rm hugofs.RootMapping) bool {rlang := rm.Meta.Lang()
return rlang == "" || rlang == lang
})
@@ -599,6 +618,7 @@
if collector.overlayMounts == nil {collector.overlayMounts = rmfs
collector.overlayMountsContent = rmfsContent
+ collector.overlayMountsStatic = rmfsStatic
collector.overlayFull = afero.NewBasePathFs(modBase, md.dir)
collector.overlayResources = afero.NewBasePathFs(modBase, getResourcesDir())
} else {@@ -605,6 +625,7 @@
collector.overlayMounts = afero.NewCopyOnWriteFs(collector.overlayMounts, rmfs)
collector.overlayMountsContent = hugofs.NewLanguageCompositeFs(collector.overlayMountsContent, rmfsContent)
+ collector.overlayMountsStatic = hugofs.NewLanguageCompositeFs(collector.overlayMountsStatic, rmfsStatic)
collector.overlayFull = afero.NewCopyOnWriteFs(collector.overlayFull, afero.NewBasePathFs(modBase, md.dir))
collector.overlayResources = afero.NewCopyOnWriteFs(collector.overlayResources, afero.NewBasePathFs(modBase, getResourcesDir()))
}
@@ -639,6 +660,7 @@
overlayMounts afero.Fs
overlayMountsContent afero.Fs
+ overlayMountsStatic afero.Fs
overlayFull afero.Fs
overlayResources afero.Fs
--- a/hugolib/filesystems/basefs_test.go
+++ b/hugolib/filesystems/basefs_test.go
@@ -124,7 +124,7 @@
p, err := paths.New(fs, v)
assert.NoError(err)
- bfs, err := NewBase(p)
+ bfs, err := NewBase(p, nil)
assert.NoError(err)
assert.NotNil(bfs)
@@ -206,7 +206,7 @@
p, err := paths.New(fs, v)
assert.NoError(err)
- bfs, err := NewBase(p)
+ bfs, err := NewBase(p, nil)
assert.NoError(err)
assert.NotNil(bfs)
assert.NotNil(bfs.Archetypes.Fs)
@@ -263,7 +263,7 @@
p, err := paths.New(fs, v)
assert.NoError(err)
- bfs, err := NewBase(p)
+ bfs, err := NewBase(p, nil)
assert.NoError(err)
assert.NotNil(bfs)
@@ -300,7 +300,7 @@
p, err := paths.New(fs, v)
assert.NoError(err)
- bfs, err := NewBase(p)
+ bfs, err := NewBase(p, nil)
assert.NoError(err)
sfs := bfs.StaticFs("en")@@ -344,7 +344,7 @@
p, err := paths.New(fs, v)
assert.NoError(err)
- bfs, err := NewBase(p)
+ bfs, err := NewBase(p, nil)
assert.NoError(err)
enFs := bfs.StaticFs("en")checkFileContent(enFs, "f1.txt", assert, "Hugo Rocks!")
--- a/hugolib/hugo_modules_test.go
+++ b/hugolib/hugo_modules_test.go
@@ -443,6 +443,7 @@
`
b := newTestSitesBuilder(t).WithNothingAdded().WithWorkingDir(workDir)
+ b.WithLogger(loggers.NewErrorLogger())
b.Fs = fs
b.WithConfigFile("toml", config)@@ -457,35 +458,46 @@
bfs := b.H.BaseFs
- for _, componentFs := range []afero.Fs{+ for i, componentFs := range []afero.Fs{+ bfs.Static[""].Fs,
bfs.Archetypes.Fs,
bfs.Content.Fs,
bfs.Data.Fs,
bfs.Assets.Fs,
- bfs.Static[""].Fs,
bfs.I18n.Fs} {- for i, id := range []string{"mod", "project"} {+ if i != 0 {+ continue
+ }
- statCheck := func(fs afero.Fs, filename string) {- shouldFail := i == 0
+ for j, id := range []string{"mod", "project"} {+
+ statCheck := func(fs afero.Fs, filename string, isDir bool) {+ shouldFail := j == 0
+ if !shouldFail && i == 0 {+ // Static dirs only supports symlinks for files
+ shouldFail = isDir
+ }
+
_, err := fs.Stat(filepath.FromSlash(filename))
+
if err != nil {- if strings.HasSuffix(filename, "toml") && strings.Contains(err.Error(), "files not supported") {+ if i > 0 && strings.HasSuffix(filename, "toml") && strings.Contains(err.Error(), "files not supported") {// OK
return
}
}
+
if shouldFail {assert.Error(err)
- assert.Equal(hugofs.ErrPermissionSymlink, err)
+ assert.Equal(hugofs.ErrPermissionSymlink, err, filename)
} else {- assert.NoError(err)
+ assert.NoError(err, filename)
}
}
- statCheck(componentFs, fmt.Sprintf("realsym%s", id))- statCheck(componentFs, fmt.Sprintf("real/datasym%s.toml", id))+ statCheck(componentFs, fmt.Sprintf("realsym%s", id), true)+ statCheck(componentFs, fmt.Sprintf("real/datasym%s.toml", id), false)}
}
--- a/hugolib/hugo_sites_build_test.go
+++ b/hugolib/hugo_sites_build_test.go
@@ -2,7 +2,6 @@
import (
"fmt"
- "os"
"strings"
"testing"
@@ -1282,7 +1281,7 @@
root = helpers.FilePathSeparator + root
}
- helpers.PrintFs(fs, root, os.Stdout)
+ //helpers.PrintFs(fs, root, os.Stdout)
t.Fatalf("Failed to read file: %s", err)}
return string(b)
--- a/hugolib/pages_capture_test.go
+++ b/hugolib/pages_capture_test.go
@@ -52,7 +52,7 @@
writeFile("pages/page2.md") writeFile("pages/page.png")- ps, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg)
+ ps, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, loggers.NewErrorLogger())
assert.NoError(err)
sourceSpec := source.NewSourceSpec(ps, fs)
--- a/resources/page/testhelpers_test.go
+++ b/resources/page/testhelpers_test.go
@@ -73,7 +73,7 @@
}
cfg.Set("allModules", modules.Modules{mod})fs := hugofs.NewMem(cfg)
- s, err := helpers.NewPathSpec(fs, cfg)
+ s, err := helpers.NewPathSpec(fs, cfg, nil)
if err != nil {panic(err)
}
--- a/resources/testhelpers_test.go
+++ b/resources/testhelpers_test.go
@@ -66,7 +66,7 @@
fs := hugofs.NewMem(cfg)
- s, err := helpers.NewPathSpec(fs, cfg)
+ s, err := helpers.NewPathSpec(fs, cfg, nil)
assert.NoError(err)
filecaches, err := filecache.NewCaches(s)
@@ -104,7 +104,7 @@
fs.Destination = &afero.MemMapFs{}fs.Source = afero.NewBasePathFs(hugofs.Os, workDir)
- s, err := helpers.NewPathSpec(fs, cfg)
+ s, err := helpers.NewPathSpec(fs, cfg, nil)
assert.NoError(err)
filecaches, err := filecache.NewCaches(s)
--- a/source/content_directory_test.go
+++ b/source/content_directory_test.go
@@ -54,7 +54,7 @@
v := newTestConfig()
v.Set("ignoreFiles", test.ignoreFilesRegexpes)fs := hugofs.NewMem(v)
- ps, err := helpers.NewPathSpec(fs, v)
+ ps, err := helpers.NewPathSpec(fs, v, nil)
assert.NoError(err)
s := NewSourceSpec(ps, fs.Source)
--- a/source/filesystem_test.go
+++ b/source/filesystem_test.go
@@ -103,7 +103,7 @@
func newTestSourceSpec() *SourceSpec {v := newTestConfig()
fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(afero.NewMemMapFs()), v)
- ps, err := helpers.NewPathSpec(fs, v)
+ ps, err := helpers.NewPathSpec(fs, v, nil)
if err != nil {panic(err)
}
--- a/tpl/data/resources_test.go
+++ b/tpl/data/resources_test.go
@@ -203,7 +203,7 @@
fs := hugofs.NewMem(cfg)
logger := loggers.NewErrorLogger()
- p, err := helpers.NewPathSpec(fs, cfg)
+ p, err := helpers.NewPathSpec(fs, cfg, nil)
if err != nil {panic(err)
}
--
⑨