forked from TRON-US/go-btfs-files
-
Notifications
You must be signed in to change notification settings - Fork 7
/
tarwriter.go
144 lines (123 loc) · 3.17 KB
/
tarwriter.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
package files
import (
"archive/tar"
"errors"
"fmt"
"io"
"path"
"strings"
"time"
)
const (
SmallestString = "\u0000"
)
var (
ErrUnixFSPathOutsideRoot = errors.New("relative UnixFS paths outside the root are now allowed, use CAR instead")
)
type TarWriter struct {
TarW *tar.Writer
baseDirSet bool
baseDir string
}
// NewTarWriter wraps given io.Writer into a new tar writer
func NewTarWriter(w io.Writer) (*TarWriter, error) {
return &TarWriter{
TarW: tar.NewWriter(w),
}, nil
}
func (w *TarWriter) writeDir(f Directory, fpath string) error {
if err := writeDirHeader(w.TarW, fpath); err != nil {
return err
}
it := f.Entries()
for it.Next() {
if it.Name() == SmallestString {
continue
}
if err := w.WriteFile(it.Node(), path.Join(fpath, it.Name())); err != nil {
return err
}
}
return it.Err()
}
func (w *TarWriter) writeFile(f File, fpath string) error {
size, err := f.Size()
if err != nil {
return err
}
if err := writeFileHeader(w.TarW, fpath, uint64(size)); err != nil {
return err
}
if _, err := io.Copy(w.TarW, f); err != nil {
return err
}
w.TarW.Flush()
return nil
}
func validateTarFilePath(baseDir, fpath string) bool {
// Ensure the filepath has no ".", "..", etc within the known root directory.
fpath = path.Clean(fpath)
// If we have a non-empty baseDir, check if the filepath starts with baseDir.
// If not, we can exclude it immediately. For 'ipfs get' and for the gateway,
// the baseDir would be '{cid}.tar'.
if baseDir != "" && !strings.HasPrefix(path.Clean(fpath), baseDir) {
return false
}
// Otherwise, check if the path starts with '..' which would make it fall
// outside the root path. This works since the path has already been cleaned.
if strings.HasPrefix(fpath, "..") {
return false
}
return true
}
// WriteNode adds a node to the archive.
func (w *TarWriter) WriteFile(nd Node, fpath string) error {
if !w.baseDirSet {
w.baseDirSet = true // Use a variable for this as baseDir may be an empty string.
w.baseDir = fpath
}
if !validateTarFilePath(w.baseDir, fpath) {
return ErrUnixFSPathOutsideRoot
}
switch nd := nd.(type) {
case *Symlink:
return writeSymlinkHeader(w.TarW, nd.Target, fpath)
case File:
return w.writeFile(nd, fpath)
case Directory:
return w.writeDir(nd, fpath)
default:
return fmt.Errorf("file type %T is not supported", nd)
}
}
// Close closes the tar writer.
func (w *TarWriter) Close() error {
return w.TarW.Close()
}
func writeDirHeader(w *tar.Writer, fpath string) error {
return w.WriteHeader(&tar.Header{
Name: fpath,
Typeflag: tar.TypeDir,
Mode: 0777,
ModTime: time.Now().Truncate(time.Second),
// TODO: set mode, dates, etc. when added to unixFS
})
}
func writeFileHeader(w *tar.Writer, fpath string, size uint64) error {
return w.WriteHeader(&tar.Header{
Name: fpath,
Size: int64(size),
Typeflag: tar.TypeReg,
Mode: 0644,
ModTime: time.Now().Truncate(time.Second),
// TODO: set mode, dates, etc. when added to unixFS
})
}
func writeSymlinkHeader(w *tar.Writer, target, fpath string) error {
return w.WriteHeader(&tar.Header{
Name: fpath,
Linkname: target,
Mode: 0777,
Typeflag: tar.TypeSymlink,
})
}