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 }