This repository has been archived by the owner on Mar 29, 2023. It is now read-only.
/
tarwriter.go
138 lines (118 loc) · 3.1 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
package files
import (
"archive/tar"
"errors"
"fmt"
"io"
"path"
"strings"
"time"
)
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 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
}
// If there is no defined baseDir, i.e., if there's multiple files in the
// root, check if paths contain elements that would otherwise make them fall
// outside the root.
if strings.Contains(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,
})
}