github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugofs/nosymlink_fs.go (about) 1 // Copyright 2018 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package hugofs 15 16 import ( 17 "errors" 18 "os" 19 "path/filepath" 20 21 "github.com/gohugoio/hugo/common/loggers" 22 23 "github.com/spf13/afero" 24 ) 25 26 var ErrPermissionSymlink = errors.New("symlinks not allowed in this filesystem") 27 28 // NewNoSymlinkFs creates a new filesystem that prevents symlinks. 29 func NewNoSymlinkFs(fs afero.Fs, logger loggers.Logger, allowFiles bool) afero.Fs { 30 return &noSymlinkFs{Fs: fs, logger: logger, allowFiles: allowFiles} 31 } 32 33 // noSymlinkFs is a filesystem that prevents symlinking. 34 type noSymlinkFs struct { 35 allowFiles bool // block dirs only 36 logger loggers.Logger 37 afero.Fs 38 } 39 40 type noSymlinkFile struct { 41 fs *noSymlinkFs 42 afero.File 43 } 44 45 func (f *noSymlinkFile) Readdir(count int) ([]os.FileInfo, error) { 46 fis, err := f.File.Readdir(count) 47 48 filtered := fis[:0] 49 for _, x := range fis { 50 filename := filepath.Join(f.Name(), x.Name()) 51 if _, err := f.fs.checkSymlinkStatus(filename, x); err != nil { 52 // Log a warning and drop the file from the list 53 logUnsupportedSymlink(filename, f.fs.logger) 54 } else { 55 filtered = append(filtered, x) 56 } 57 } 58 59 return filtered, err 60 } 61 62 func (f *noSymlinkFile) Readdirnames(count int) ([]string, error) { 63 dirs, err := f.Readdir(count) 64 if err != nil { 65 return nil, err 66 } 67 return fileInfosToNames(dirs), nil 68 } 69 70 func (fs *noSymlinkFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { 71 return fs.stat(name) 72 } 73 74 func (fs *noSymlinkFs) Stat(name string) (os.FileInfo, error) { 75 fi, _, err := fs.stat(name) 76 return fi, err 77 } 78 79 func (fs *noSymlinkFs) stat(name string) (os.FileInfo, bool, error) { 80 var ( 81 fi os.FileInfo 82 wasLstat bool 83 err error 84 ) 85 86 if lstater, ok := fs.Fs.(afero.Lstater); ok { 87 fi, wasLstat, err = lstater.LstatIfPossible(name) 88 } else { 89 fi, err = fs.Fs.Stat(name) 90 } 91 92 if err != nil { 93 return nil, false, err 94 } 95 96 fi, err = fs.checkSymlinkStatus(name, fi) 97 98 return fi, wasLstat, err 99 } 100 101 func (fs *noSymlinkFs) checkSymlinkStatus(name string, fi os.FileInfo) (os.FileInfo, error) { 102 var metaIsSymlink bool 103 104 if fim, ok := fi.(FileMetaInfo); ok { 105 meta := fim.Meta() 106 metaIsSymlink = meta.IsSymlink 107 } 108 109 if metaIsSymlink { 110 if fs.allowFiles && !fi.IsDir() { 111 return fi, nil 112 } 113 return nil, ErrPermissionSymlink 114 } 115 116 // Also support non-decorated filesystems, e.g. the Os fs. 117 if isSymlink(fi) { 118 // Need to determine if this is a directory or not. 119 _, sfi, err := evalSymlinks(fs.Fs, name) 120 if err != nil { 121 return nil, err 122 } 123 if fs.allowFiles && !sfi.IsDir() { 124 // Return the original FileInfo to get the expected Name. 125 return fi, nil 126 } 127 return nil, ErrPermissionSymlink 128 } 129 130 return fi, nil 131 } 132 133 func (fs *noSymlinkFs) Open(name string) (afero.File, error) { 134 if _, _, err := fs.stat(name); err != nil { 135 return nil, err 136 } 137 return fs.wrapFile(fs.Fs.Open(name)) 138 } 139 140 func (fs *noSymlinkFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { 141 if _, _, err := fs.stat(name); err != nil { 142 return nil, err 143 } 144 return fs.wrapFile(fs.Fs.OpenFile(name, flag, perm)) 145 } 146 147 func (fs *noSymlinkFs) wrapFile(f afero.File, err error) (afero.File, error) { 148 if err != nil { 149 return nil, err 150 } 151 152 return &noSymlinkFile{File: f, fs: fs}, nil 153 }