nosymlink_fs.go (3905B)
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 var ( 34 _ FilesystemUnwrapper = (*noSymlinkFs)(nil) 35 ) 36 37 // noSymlinkFs is a filesystem that prevents symlinking. 38 type noSymlinkFs struct { 39 allowFiles bool // block dirs only 40 logger loggers.Logger 41 afero.Fs 42 } 43 44 type noSymlinkFile struct { 45 fs *noSymlinkFs 46 afero.File 47 } 48 49 func (f *noSymlinkFile) Readdir(count int) ([]os.FileInfo, error) { 50 fis, err := f.File.Readdir(count) 51 52 filtered := fis[:0] 53 for _, x := range fis { 54 filename := filepath.Join(f.Name(), x.Name()) 55 if _, err := f.fs.checkSymlinkStatus(filename, x); err != nil { 56 // Log a warning and drop the file from the list 57 logUnsupportedSymlink(filename, f.fs.logger) 58 } else { 59 filtered = append(filtered, x) 60 } 61 } 62 63 return filtered, err 64 } 65 66 func (f *noSymlinkFile) Readdirnames(count int) ([]string, error) { 67 dirs, err := f.Readdir(count) 68 if err != nil { 69 return nil, err 70 } 71 return fileInfosToNames(dirs), nil 72 } 73 74 func (fs *noSymlinkFs) UnwrapFilesystem() afero.Fs { 75 return fs.Fs 76 } 77 78 func (fs *noSymlinkFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { 79 return fs.stat(name) 80 } 81 82 func (fs *noSymlinkFs) Stat(name string) (os.FileInfo, error) { 83 fi, _, err := fs.stat(name) 84 return fi, err 85 } 86 87 func (fs *noSymlinkFs) stat(name string) (os.FileInfo, bool, error) { 88 var ( 89 fi os.FileInfo 90 wasLstat bool 91 err error 92 ) 93 94 if lstater, ok := fs.Fs.(afero.Lstater); ok { 95 fi, wasLstat, err = lstater.LstatIfPossible(name) 96 } else { 97 fi, err = fs.Fs.Stat(name) 98 } 99 100 if err != nil { 101 return nil, false, err 102 } 103 104 fi, err = fs.checkSymlinkStatus(name, fi) 105 106 return fi, wasLstat, err 107 } 108 109 func (fs *noSymlinkFs) checkSymlinkStatus(name string, fi os.FileInfo) (os.FileInfo, error) { 110 var metaIsSymlink bool 111 112 if fim, ok := fi.(FileMetaInfo); ok { 113 meta := fim.Meta() 114 metaIsSymlink = meta.IsSymlink 115 } 116 117 if metaIsSymlink { 118 if fs.allowFiles && !fi.IsDir() { 119 return fi, nil 120 } 121 return nil, ErrPermissionSymlink 122 } 123 124 // Also support non-decorated filesystems, e.g. the Os fs. 125 if isSymlink(fi) { 126 // Need to determine if this is a directory or not. 127 _, sfi, err := evalSymlinks(fs.Fs, name) 128 if err != nil { 129 return nil, err 130 } 131 if fs.allowFiles && !sfi.IsDir() { 132 // Return the original FileInfo to get the expected Name. 133 return fi, nil 134 } 135 return nil, ErrPermissionSymlink 136 } 137 138 return fi, nil 139 } 140 141 func (fs *noSymlinkFs) Open(name string) (afero.File, error) { 142 if _, _, err := fs.stat(name); err != nil { 143 return nil, err 144 } 145 return fs.wrapFile(fs.Fs.Open(name)) 146 } 147 148 func (fs *noSymlinkFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { 149 if _, _, err := fs.stat(name); err != nil { 150 return nil, err 151 } 152 return fs.wrapFile(fs.Fs.OpenFile(name, flag, perm)) 153 } 154 155 func (fs *noSymlinkFs) wrapFile(f afero.File, err error) (afero.File, error) { 156 if err != nil { 157 return nil, err 158 } 159 160 return &noSymlinkFile{File: f, fs: fs}, nil 161 }