1
15
16 package content
17
18 import (
19 "archive/tar"
20 "compress/gzip"
21 "fmt"
22 "io"
23 "os"
24 "path/filepath"
25 "strings"
26 "time"
27
28 digest "github.com/opencontainers/go-digest"
29 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
30 "github.com/pkg/errors"
31 )
32
33
34 func ResolveName(desc ocispec.Descriptor) (string, bool) {
35 name, ok := desc.Annotations[ocispec.AnnotationTitle]
36 return name, ok
37 }
38
39
40
41 func tarDirectory(root, prefix string, w io.Writer, stripTimes bool) error {
42 tw := tar.NewWriter(w)
43 defer tw.Close()
44 if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
45 if err != nil {
46 return err
47 }
48
49
50 name, err := filepath.Rel(root, path)
51 if err != nil {
52 return err
53 }
54 name = filepath.Join(prefix, name)
55 name = filepath.ToSlash(name)
56
57
58 var link string
59 mode := info.Mode()
60 if mode&os.ModeSymlink != 0 {
61 if link, err = os.Readlink(path); err != nil {
62 return err
63 }
64 }
65 header, err := tar.FileInfoHeader(info, link)
66 if err != nil {
67 return errors.Wrap(err, path)
68 }
69 header.Name = name
70 header.Uid = 0
71 header.Gid = 0
72 header.Uname = ""
73 header.Gname = ""
74
75 if stripTimes {
76 header.ModTime = time.Time{}
77 header.AccessTime = time.Time{}
78 header.ChangeTime = time.Time{}
79 }
80
81
82 if err := tw.WriteHeader(header); err != nil {
83 return errors.Wrap(err, "tar")
84 }
85 if mode.IsRegular() {
86 file, err := os.Open(path)
87 if err != nil {
88 return err
89 }
90 defer file.Close()
91 if _, err := io.Copy(tw, file); err != nil {
92 return errors.Wrap(err, path)
93 }
94 }
95
96 return nil
97 }); err != nil {
98 return err
99 }
100 return nil
101 }
102
103
104
105
106 func extractTarDirectory(root, prefix string, r io.Reader) error {
107 tr := tar.NewReader(r)
108 for {
109 header, err := tr.Next()
110 if err != nil {
111 if err == io.EOF {
112 return nil
113 }
114 return err
115 }
116
117
118 name := header.Name
119 path, err := ensureBasePath(root, prefix, name)
120 if err != nil {
121 return err
122 }
123 path = filepath.Join(root, path)
124
125
126 switch header.Typeflag {
127 case tar.TypeLink, tar.TypeSymlink:
128 link := header.Linkname
129 if !filepath.IsAbs(link) {
130 link = filepath.Join(filepath.Dir(name), link)
131 }
132 if _, err := ensureBasePath(root, prefix, link); err != nil {
133 return err
134 }
135 }
136
137
138 switch header.Typeflag {
139 case tar.TypeReg:
140 err = writeFile(path, tr, header.FileInfo().Mode())
141 case tar.TypeDir:
142 err = os.MkdirAll(path, header.FileInfo().Mode())
143 case tar.TypeLink:
144 err = os.Link(header.Linkname, path)
145 case tar.TypeSymlink:
146 err = os.Symlink(header.Linkname, path)
147 default:
148 continue
149 }
150 if err != nil {
151 return err
152 }
153
154
155 os.Chtimes(path, header.AccessTime, header.ModTime)
156 }
157 }
158
159
160
161 func ensureBasePath(root, base, target string) (string, error) {
162 path, err := filepath.Rel(base, target)
163 if err != nil {
164 return "", err
165 }
166 cleanPath := filepath.ToSlash(filepath.Clean(path))
167 if cleanPath == ".." || strings.HasPrefix(cleanPath, "../") {
168 return "", fmt.Errorf("%q is outside of %q", target, base)
169 }
170
171
172 dir := filepath.Dir(path)
173 for dir != "." {
174 if info, err := os.Lstat(filepath.Join(root, dir)); err != nil {
175 if !os.IsNotExist(err) {
176 return "", err
177 }
178 } else if info.Mode()&os.ModeSymlink != 0 {
179 return "", fmt.Errorf("no symbolic link allowed between %q and %q", base, target)
180 }
181 dir = filepath.Dir(dir)
182 }
183
184 return path, nil
185 }
186
187 func writeFile(path string, r io.Reader, perm os.FileMode) error {
188 file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
189 if err != nil {
190 return err
191 }
192 defer file.Close()
193 _, err = io.Copy(file, r)
194 return err
195 }
196
197 func extractTarGzip(root, prefix, filename, checksum string) error {
198 file, err := os.Open(filename)
199 if err != nil {
200 return err
201 }
202 defer file.Close()
203 zr, err := gzip.NewReader(file)
204 if err != nil {
205 return err
206 }
207 defer zr.Close()
208 var r io.Reader = zr
209 var verifier digest.Verifier
210 if checksum != "" {
211 if digest, err := digest.Parse(checksum); err == nil {
212 verifier = digest.Verifier()
213 r = io.TeeReader(r, verifier)
214 }
215 }
216 if err := extractTarDirectory(root, prefix, r); err != nil {
217 return err
218 }
219 if verifier != nil && !verifier.Verified() {
220 return errors.New("content digest mismatch")
221 }
222 return nil
223 }
224
View as plain text