1
15
16 package golang
17
18 import (
19 "fmt"
20 "log"
21 "os"
22 "path"
23 "path/filepath"
24 "strings"
25 "unicode/utf8"
26
27 "golang.org/x/mod/module"
28 )
29
30
31
32 type embedResolver struct {
33
34
35 files []*embeddableNode
36 }
37
38 type embeddableNode struct {
39 path string
40 entries []*embeddableNode
41 }
42
43 func (f *embeddableNode) isDir() bool {
44 return f.entries != nil
45 }
46
47 func (f *embeddableNode) isHidden() bool {
48 base := path.Base(f.path)
49 return strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_")
50 }
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 func newEmbedResolver(dir, rel string, validBuildFileNames []string, pkgRels map[string]bool, subdirs, regFiles, genFiles []string) *embedResolver {
79 root := &embeddableNode{entries: []*embeddableNode{}}
80 index := make(map[string]*embeddableNode)
81
82 var add func(string, bool) *embeddableNode
83 add = func(rel string, isDir bool) *embeddableNode {
84 if n := index[rel]; n != nil {
85 return n
86 }
87 dir := path.Dir(rel)
88 parent := root
89 if dir != "." {
90 parent = add(dir, true)
91 }
92 f := &embeddableNode{path: rel}
93 if isDir {
94 f.entries = []*embeddableNode{}
95 }
96 parent.entries = append(parent.entries, f)
97 index[rel] = f
98 return f
99 }
100
101 for _, fs := range [...][]string{regFiles, genFiles} {
102 for _, f := range fs {
103 if !isBadEmbedName(f) {
104 add(f, false)
105 }
106 }
107 }
108
109 for _, subdir := range subdirs {
110 err := filepath.Walk(filepath.Join(dir, subdir), func(p string, info os.FileInfo, err error) error {
111 if err != nil {
112 return err
113 }
114 fileRel, _ := filepath.Rel(dir, p)
115 fileRel = filepath.ToSlash(fileRel)
116 base := filepath.Base(p)
117 if !info.IsDir() {
118 if !isBadEmbedName(base) {
119 add(fileRel, false)
120 return nil
121 }
122 return nil
123 }
124 if isBadEmbedName(base) {
125 return filepath.SkipDir
126 }
127 if pkgRels[path.Join(rel, fileRel)] {
128
129
130 return filepath.SkipDir
131 }
132 for _, name := range validBuildFileNames {
133 if bFileInfo, err := os.Stat(filepath.Join(p, name)); err == nil && !bFileInfo.IsDir() {
134
135 return filepath.SkipDir
136 }
137 }
138 add(fileRel, true)
139 return nil
140 })
141 if err != nil {
142 log.Printf("listing embeddable files in %s: %v", dir, err)
143 }
144 }
145
146 return &embedResolver{files: root.entries}
147 }
148
149
150
151
152 func (er *embedResolver) resolve(embed fileEmbed) (list []string, err error) {
153 defer func() {
154 if err != nil {
155 err = fmt.Errorf("%v: pattern %s: %w", embed.pos, embed.path, err)
156 }
157 }()
158
159 glob := embed.path
160 all := strings.HasPrefix(embed.path, "all:")
161 if all {
162 glob = strings.TrimPrefix(embed.path, "all:")
163 }
164
165
166 if _, err := path.Match(glob, ""); err != nil || !validEmbedPattern(glob) {
167 return nil, fmt.Errorf("invalid pattern syntax")
168 }
169
170
171
172
173
174
175
176
177
178 var visit func(*embeddableNode, bool)
179 visit = func(f *embeddableNode, add bool) {
180 convertedPath := filepath.ToSlash(f.path)
181 match, _ := path.Match(glob, convertedPath)
182 add = match || (add && (!f.isHidden() || all))
183 if !f.isDir() {
184 if add {
185 list = append(list, convertedPath)
186 }
187 return
188 }
189 for _, e := range f.entries {
190 visit(e, add)
191 }
192 }
193 for _, f := range er.files {
194 visit(f, false)
195 }
196 if len(list) == 0 {
197 return nil, fmt.Errorf("matched no files")
198 }
199 return list, nil
200 }
201
202
203 func validEmbedPattern(pattern string) bool {
204 return pattern != "." && fsValidPath(pattern)
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 func fsValidPath(name string) bool {
224 if !utf8.ValidString(name) {
225 return false
226 }
227
228 if name == "." {
229
230 return true
231 }
232
233
234 for {
235 i := 0
236 for i < len(name) && name[i] != '/' {
237 i++
238 }
239 elem := name[:i]
240 if elem == "" || elem == "." || elem == ".." {
241 return false
242 }
243 if i == len(name) {
244 return true
245 }
246 name = name[i+1:]
247 }
248 }
249
250
251
252
253
254
255 func isBadEmbedName(name string) bool {
256 if err := module.CheckFilePath(name); err != nil {
257 return true
258 }
259 switch name {
260
261 case "":
262 return true
263
264 case ".bzr", ".hg", ".git", ".svn":
265 return true
266 }
267 return false
268 }
269
View as plain text