forked from golang/playground
-
Notifications
You must be signed in to change notification settings - Fork 0
/
txtar.go
145 lines (132 loc) · 4.04 KB
/
txtar.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"errors"
"fmt"
"path"
"strings"
"golang.org/x/tools/txtar"
)
// fileSet is a set of files.
// The zero value for fileSet is an empty set ready to use.
type fileSet struct {
files []string // filenames in user-provided order
m map[string][]byte // filename -> source
noHeader bool // whether the prog.go entry was implicit
}
// Data returns the content of the named file.
// The fileSet retains ownership of the returned slice.
func (fs *fileSet) Data(filename string) []byte { return fs.m[filename] }
// Num returns the number of files in the set.
func (fs *fileSet) Num() int { return len(fs.m) }
// Contains reports whether fs contains the given filename.
func (fs *fileSet) Contains(filename string) bool {
_, ok := fs.m[filename]
return ok
}
// AddFile adds a file to fs. If fs already contains filename, its
// contents are replaced.
func (fs *fileSet) AddFile(filename string, src []byte) {
had := fs.Contains(filename)
if fs.m == nil {
fs.m = make(map[string][]byte)
}
fs.m[filename] = src
if !had {
fs.files = append(fs.files, filename)
}
}
func (fs *fileSet) Update(filename string, src []byte) {
if fs.Contains(filename) {
fs.m[filename] = src
}
}
func (fs *fileSet) MvFile(source, target string) {
if fs.m == nil {
return
}
data, ok := fs.m[source]
if !ok {
return
}
fs.m[target] = data
delete(fs.m, source)
for i := range fs.files {
if fs.files[i] == source {
fs.files[i] = target
break
}
}
}
// Format returns fs formatted as a txtar archive.
func (fs *fileSet) Format() []byte {
a := new(txtar.Archive)
if fs.noHeader {
a.Comment = fs.m[progName]
}
for i, f := range fs.files {
if i == 0 && f == progName && fs.noHeader {
continue
}
a.Files = append(a.Files, txtar.File{Name: f, Data: fs.m[f]})
}
return txtar.Format(a)
}
// splitFiles splits the user's input program src into 1 or more
// files, splitting it based on boundaries as specified by the "txtar"
// format. It returns an error if any filenames are bogus or
// duplicates. The implicit filename for the txtar comment (the lines
// before any txtar separator line) are named "prog.go". It is an
// error to have an explicit file named "prog.go" in addition to
// having the implicit "prog.go" file (non-empty comment section).
//
// The filenames are validated to only be relative paths, not too
// long, not too deep, not have ".." elements, not have backslashes or
// low ASCII binary characters, and to be in path.Clean canonical
// form.
//
// splitFiles takes ownership of src.
func splitFiles(src []byte) (*fileSet, error) {
fs := new(fileSet)
a := txtar.Parse(src)
if v := bytes.TrimSpace(a.Comment); len(v) > 0 {
fs.noHeader = true
fs.AddFile(progName, a.Comment)
}
const limitNumFiles = 20 // arbitrary
numFiles := len(a.Files) + fs.Num()
if numFiles > limitNumFiles {
return nil, fmt.Errorf("too many files in txtar archive (%v exceeds limit of %v)", numFiles, limitNumFiles)
}
for _, f := range a.Files {
if len(f.Name) > 200 { // arbitrary limit
return nil, errors.New("file name too long")
}
if strings.IndexFunc(f.Name, isBogusFilenameRune) != -1 {
return nil, fmt.Errorf("invalid file name %q", f.Name)
}
if f.Name != path.Clean(f.Name) || path.IsAbs(f.Name) {
return nil, fmt.Errorf("invalid file name %q", f.Name)
}
parts := strings.Split(f.Name, "/")
if len(parts) > 10 { // arbitrary limit
return nil, fmt.Errorf("file name %q too deep", f.Name)
}
for _, part := range parts {
if part == "." || part == ".." {
return nil, fmt.Errorf("invalid file name %q", f.Name)
}
}
if fs.Contains(f.Name) {
return nil, fmt.Errorf("duplicate file name %q", f.Name)
}
fs.AddFile(f.Name, f.Data)
}
return fs, nil
}
// isBogusFilenameRune reports whether r should be rejected if it
// appears in a txtar section's filename.
func isBogusFilenameRune(r rune) bool { return r == '\\' || r < ' ' }