os.go (3944B)
1 // Copyright 2017 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 os provides template functions for interacting with the operating
15 // system.
16 package os
17
18 import (
19 "errors"
20 "fmt"
21 _os "os"
22 "path/filepath"
23
24 "github.com/bep/overlayfs"
25 "github.com/gohugoio/hugo/deps"
26 "github.com/spf13/afero"
27 "github.com/spf13/cast"
28 )
29
30 // New returns a new instance of the os-namespaced template functions.
31 func New(d *deps.Deps) *Namespace {
32 var readFileFs, workFs afero.Fs
33
34 // The docshelper script does not have or need all the dependencies set up.
35 if d.PathSpec != nil {
36 readFileFs = overlayfs.New(overlayfs.Options{
37 Fss: []afero.Fs{
38 d.PathSpec.BaseFs.Work,
39 d.PathSpec.BaseFs.Content.Fs,
40 },
41 })
42 // See #9599
43 workFs = d.PathSpec.BaseFs.WorkDir
44 }
45
46 return &Namespace{
47 readFileFs: readFileFs,
48 workFs: workFs,
49 deps: d,
50 }
51 }
52
53 // Namespace provides template functions for the "os" namespace.
54 type Namespace struct {
55 readFileFs afero.Fs
56 workFs afero.Fs
57 deps *deps.Deps
58 }
59
60 // Getenv retrieves the value of the environment variable named by the key.
61 // It returns the value, which will be empty if the variable is not present.
62 func (ns *Namespace) Getenv(key any) (string, error) {
63 skey, err := cast.ToStringE(key)
64 if err != nil {
65 return "", nil
66 }
67
68 if err = ns.deps.ExecHelper.Sec().CheckAllowedGetEnv(skey); err != nil {
69 return "", err
70 }
71
72 return _os.Getenv(skey), nil
73 }
74
75 // readFile reads the file named by filename in the given filesystem
76 // and returns the contents as a string.
77 func readFile(fs afero.Fs, filename string) (string, error) {
78 filename = filepath.Clean(filename)
79 if filename == "" || filename == "." || filename == string(_os.PathSeparator) {
80 return "", errors.New("invalid filename")
81 }
82
83 b, err := afero.ReadFile(fs, filename)
84 if err != nil {
85 return "", err
86 }
87
88 return string(b), nil
89 }
90
91 // ReadFile reads the file named by filename relative to the configured WorkingDir.
92 // It returns the contents as a string.
93 // There is an upper size limit set at 1 megabytes.
94 func (ns *Namespace) ReadFile(i any) (string, error) {
95 s, err := cast.ToStringE(i)
96 if err != nil {
97 return "", err
98 }
99
100 if ns.deps.PathSpec != nil {
101 s = ns.deps.PathSpec.RelPathify(s)
102 }
103
104 return readFile(ns.readFileFs, s)
105 }
106
107 // ReadDir lists the directory contents relative to the configured WorkingDir.
108 func (ns *Namespace) ReadDir(i any) ([]_os.FileInfo, error) {
109 path, err := cast.ToStringE(i)
110 if err != nil {
111 return nil, err
112 }
113
114 list, err := afero.ReadDir(ns.workFs, path)
115 if err != nil {
116 return nil, fmt.Errorf("failed to read directory %q: %s", path, err)
117 }
118
119 return list, nil
120 }
121
122 // FileExists checks whether a file exists under the given path.
123 func (ns *Namespace) FileExists(i any) (bool, error) {
124 path, err := cast.ToStringE(i)
125 if err != nil {
126 return false, err
127 }
128
129 if path == "" {
130 return false, errors.New("fileExists needs a path to a file")
131 }
132
133 status, err := afero.Exists(ns.readFileFs, path)
134 if err != nil {
135 return false, err
136 }
137
138 return status, nil
139 }
140
141 // Stat returns the os.FileInfo structure describing file.
142 func (ns *Namespace) Stat(i any) (_os.FileInfo, error) {
143 path, err := cast.ToStringE(i)
144 if err != nil {
145 return nil, err
146 }
147
148 if path == "" {
149 return nil, errors.New("fileStat needs a path to a file")
150 }
151
152 r, err := ns.readFileFs.Stat(path)
153 if err != nil {
154 return nil, err
155 }
156
157 return r, nil
158 }