mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 07:18:54 +00:00
Drop symlinks in os.ReadDir, os.ReadFile, os.Stat and os.FileExists
Fixes #15019
This commit is contained in:
@@ -19,7 +19,7 @@ require (
|
||||
github.com/bep/lazycache v0.8.1
|
||||
github.com/bep/logg v0.4.0
|
||||
github.com/bep/mclib v1.20401.20400
|
||||
github.com/bep/overlayfs v0.10.0
|
||||
github.com/bep/overlayfs v0.11.0
|
||||
github.com/bep/simplecobra v0.7.0
|
||||
github.com/bep/textandbinarywriter v0.1.0
|
||||
github.com/bep/tmc v0.6.0
|
||||
|
||||
@@ -174,6 +174,8 @@ github.com/bep/mclib v1.20401.20400 h1:silTOMNlNI7yHBb+HxEE0THIVFVWo/0I4SCH69Fxt
|
||||
github.com/bep/mclib v1.20401.20400/go.mod h1:v5Hh3EIinPn7epigP28uf9JCkZlYzBS2vEOPe2wrHzM=
|
||||
github.com/bep/overlayfs v0.10.0 h1:wS3eQ6bRsLX+4AAmwGjvoFSAQoeheamxofFiJ2SthSE=
|
||||
github.com/bep/overlayfs v0.10.0/go.mod h1:ouu4nu6fFJaL0sPzNICzxYsBeWwrjiTdFZdK4lI3tro=
|
||||
github.com/bep/overlayfs v0.11.0 h1:aymHDGC0CHpvn0XvTfgpK6skCp16oMi+tdUF32l6pPs=
|
||||
github.com/bep/overlayfs v0.11.0/go.mod h1:L+ggdoKm+Y7Xb4a1osd+/LOPG4qsY62snqRqJH5Mspc=
|
||||
github.com/bep/simplecobra v0.7.0 h1:kG8ZPwEc1o96hlIVGXcrrvwC8RornBqvMD3+pS0Z7y0=
|
||||
github.com/bep/simplecobra v0.7.0/go.mod h1:PDXvBWH1ZMX05DRQ25ub/C6kKUuq+jROPgjbVz8wO1g=
|
||||
github.com/bep/textandbinarywriter v0.1.0 h1:KXmXsRN2Uhwhm1G3e/snM8+5SPQBJrCEpIosdIBR3po=
|
||||
|
||||
@@ -46,6 +46,7 @@ func NewComponentFs(opts ComponentFsOptions) *componentFs {
|
||||
var (
|
||||
_ FilesystemUnwrapper = (*componentFs)(nil)
|
||||
_ ReadDirWithContextDir = (*componentFsDir)(nil)
|
||||
_ afero.Lstater = (*componentFs)(nil)
|
||||
)
|
||||
|
||||
// componentFs is a filesystem that holds one of the Hugo components, e.g. content, layouts etc.
|
||||
@@ -307,6 +308,23 @@ func (fs *componentFs) Stat(name string) (os.FileInfo, error) {
|
||||
return fim, nil
|
||||
}
|
||||
|
||||
func (fs *componentFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
|
||||
if lstater, ok := fs.Fs.(afero.Lstater); ok {
|
||||
fi, b, err := lstater.LstatIfPossible(name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
fim, ok := fs.applyMeta(fi, name)
|
||||
if !ok {
|
||||
return nil, false, os.ErrNotExist
|
||||
}
|
||||
return fim, b, nil
|
||||
}
|
||||
|
||||
fi, err := fs.Stat(name)
|
||||
return fi, false, err
|
||||
}
|
||||
|
||||
func (fs *componentFs) applyMeta(fi FileNameIsDir, name string) (FileMetaInfo, bool) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
name = norm.NFC.String(name)
|
||||
|
||||
@@ -244,6 +244,8 @@ func WrapFilesystem(container, content afero.Fs) afero.Fs {
|
||||
return filesystemsWrapper{Fs: container, content: content}
|
||||
}
|
||||
|
||||
var _ afero.Lstater = (*filesystemsWrapper)(nil)
|
||||
|
||||
type filesystemsWrapper struct {
|
||||
afero.Fs
|
||||
content afero.Fs
|
||||
@@ -253,6 +255,14 @@ func (w filesystemsWrapper) UnwrapFilesystem() afero.Fs {
|
||||
return w.content
|
||||
}
|
||||
|
||||
func (w filesystemsWrapper) LstatIfPossible(name string) (os.FileInfo, bool, error) {
|
||||
if lstater, ok := w.Fs.(afero.Lstater); ok {
|
||||
return lstater.LstatIfPossible(name)
|
||||
}
|
||||
fi, err := w.Fs.Stat(name)
|
||||
return fi, false, err
|
||||
}
|
||||
|
||||
type ReadDirWithContextDir interface {
|
||||
ReadDirWithContext(context context.Context, count int) ([]iofs.DirEntry, context.Context, error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugofs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// NewDropSymlinksFs returns an afero.Fs wrapper that treats symlinks as non-existing files.
|
||||
func NewDropSymlinksFs(base afero.Fs) *DropSymlinksFs {
|
||||
return &DropSymlinksFs{base}
|
||||
}
|
||||
|
||||
// DropSymlinksFs is an afero.Fs wrapper that treats symlinks as non-existing files.
|
||||
type DropSymlinksFs struct {
|
||||
afero.Fs
|
||||
}
|
||||
|
||||
func (fs *DropSymlinksFs) Open(name string) (afero.File, error) {
|
||||
if _, err := fs.Stat(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := fs.Fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (fs *DropSymlinksFs) Stat(name string) (os.FileInfo, error) {
|
||||
fi, err := LstatIfPossible(fs.Fs, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
ln ./rootfile.txt ./themes/mytheme/assets/modassetsymlink.txt
|
||||
ln ./rootfile.txt ./themes/mytheme/static/modstaticsymlink.txt
|
||||
ln ./README.md ./content/pagesymlink.md
|
||||
ln ./rootdir ./assets/myassets/symlinkdir
|
||||
|
||||
hugo
|
||||
|
||||
@@ -27,13 +28,30 @@ Read me.
|
||||
-- layouts/all.html --
|
||||
{{ with resources.Get "modassetok.txt"}}OK {{ .Publish }}{{ else }}FAIL{{ end }}
|
||||
{{ with resources.Get "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
||||
{{ with resources.GetMatch "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
||||
{{ with resources.GetMatch "myassets/symlinkdir/**"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
|
||||
Page: {{ .RelPermalink }}|{{ .Content }}|
|
||||
|
||||
{{/* os template package. */}}
|
||||
{{ $symFilePath := "content/pagesymlink.md" }}
|
||||
|
||||
{{ with os.ReadDir "assets/myassets/symlinkdir" }}FAIL {{ len . }}{{ else }}OK{{ end }}
|
||||
{{ with os.Stat $symFilePath }}FAIL{{ else }}OK{{ end }}
|
||||
{{ with os.ReadFile $symFilePath }}FAIL{{ else }}OK{{ end }}
|
||||
{{ with os.FileExists $symFilePath }}FAIL{{ else }}OK{{ end }}
|
||||
|
||||
-- content/pageok.md --
|
||||
-- themes/mytheme/assets/modassetok.txt --
|
||||
Content.
|
||||
-- themes/mytheme/static/modstatictok.txt --
|
||||
Content.
|
||||
-- rootfile.txt --
|
||||
Roo Content.
|
||||
Root Content.
|
||||
-- assets/myassets/myfile.txt --
|
||||
My file.
|
||||
-- rootdir/rootdirfile1.txt --
|
||||
Rootdirfile1 content.
|
||||
-- rootdir/rootdirfile2.txt --
|
||||
Rootdirfile2 content.
|
||||
|
||||
|
||||
|
||||
+13
-6
@@ -24,24 +24,25 @@ import (
|
||||
"github.com/bep/overlayfs"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// New returns a new instance of the os-namespaced template functions.
|
||||
func New(d *deps.Deps) *Namespace {
|
||||
var readFileFs, workFs afero.Fs
|
||||
var readFileFs, workFs *hugofs.DropSymlinksFs
|
||||
|
||||
// The docshelper script does not have or need all the dependencies set up.
|
||||
if d.PathSpec != nil {
|
||||
readFileFs = overlayfs.New(overlayfs.Options{
|
||||
readFileFs = hugofs.NewDropSymlinksFs(overlayfs.New(overlayfs.Options{
|
||||
Fss: []afero.Fs{
|
||||
d.PathSpec.BaseFs.Work,
|
||||
d.PathSpec.BaseFs.Content.Fs,
|
||||
},
|
||||
})
|
||||
}))
|
||||
// See #9599
|
||||
workFs = d.PathSpec.BaseFs.WorkDir
|
||||
workFs = hugofs.NewDropSymlinksFs(d.PathSpec.BaseFs.WorkDir)
|
||||
}
|
||||
|
||||
return &Namespace{
|
||||
@@ -53,8 +54,8 @@ func New(d *deps.Deps) *Namespace {
|
||||
|
||||
// Namespace provides template functions for the "os" namespace.
|
||||
type Namespace struct {
|
||||
readFileFs afero.Fs
|
||||
workFs afero.Fs
|
||||
readFileFs *hugofs.DropSymlinksFs
|
||||
workFs *hugofs.DropSymlinksFs
|
||||
deps *deps.Deps
|
||||
}
|
||||
|
||||
@@ -118,6 +119,9 @@ func (ns *Namespace) ReadDir(i any) ([]_os.FileInfo, error) {
|
||||
|
||||
list, err := afero.ReadDir(ns.workFs, path)
|
||||
if err != nil {
|
||||
if herrors.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read directory %q: %s", path, err)
|
||||
}
|
||||
|
||||
@@ -156,6 +160,9 @@ func (ns *Namespace) Stat(i any) (_os.FileInfo, error) {
|
||||
|
||||
r, err := ns.readFileFs.Stat(path)
|
||||
if err != nil {
|
||||
if herrors.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user