Skip to content

Commit 589c297

Browse files
authored
Merge pull request #25 from screwdriver-cd/fixsymlink
fix: Preserve symlinks while zipping and unzipping
2 parents 76465da + 85aa69f commit 589c297

File tree

5 files changed

+229
-62
lines changed

5 files changed

+229
-62
lines changed

data/testsymlink/bar/test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
contents

data/testsymlink/symlink

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bar/test

sdstore/sdstore.go

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package sdstore
22

33
import (
4-
"archive/zip"
54
"encoding/json"
65
"fmt"
7-
"github.com/mholt/archiver"
86
"io"
97
"io/ioutil"
108
"log"
@@ -203,7 +201,7 @@ func (s *sdStore) Upload(u *url.URL, filePath string, toCompress bool) error {
203201
}
204202

205203
absPath, _ := filepath.Abs(filePath)
206-
err = archiver.Zip.Make(zipPath, []string{absPath})
204+
err = Zip(absPath, zipPath)
207205
if err != nil {
208206
log.Printf("(Try %d of %d) Unable to zip file: %v", i+1, maxRetries, err)
209207
continue
@@ -405,62 +403,3 @@ func (s *sdStore) put(url *url.URL, bodyType string, payload io.Reader, size int
405403

406404
return handleResponse(res)
407405
}
408-
409-
// Taken from https://golangcode.com/unzip-files-in-go/
410-
func Unzip(src string, dest string) ([]string, error) {
411-
var filenames []string
412-
413-
r, err := zip.OpenReader(src)
414-
if err != nil {
415-
return filenames, err
416-
}
417-
defer r.Close()
418-
419-
for _, f := range r.File {
420-
421-
rc, err := f.Open()
422-
if err != nil {
423-
return filenames, err
424-
}
425-
defer rc.Close()
426-
427-
// Store filename/path for returning and using later on
428-
fpath := filepath.Join(dest, f.Name)
429-
430-
// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
431-
if dest != "/" && !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
432-
return filenames, fmt.Errorf("%s: illegal file path", fpath)
433-
}
434-
435-
filenames = append(filenames, fpath)
436-
437-
if f.FileInfo().IsDir() {
438-
439-
// Make Folder
440-
os.MkdirAll(fpath, os.ModePerm)
441-
442-
} else {
443-
444-
// Make File
445-
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
446-
return filenames, err
447-
}
448-
449-
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
450-
if err != nil {
451-
return filenames, err
452-
}
453-
454-
_, err = io.Copy(outFile, rc)
455-
456-
// Close the file without defer to close before next iteration of loop
457-
outFile.Close()
458-
459-
if err != nil {
460-
return filenames, err
461-
}
462-
463-
}
464-
}
465-
return filenames, nil
466-
}

sdstore/sdstore_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,3 +576,29 @@ func TestRemoveRetry(t *testing.T) {
576576
t.Errorf("Expected 6 retries, got %d", callCount)
577577
}
578578
}
579+
580+
func TestZipAndUnzipWithSymlink(t *testing.T) {
581+
err := Zip("../data/testsymlink", "../data/testsymlink.zip")
582+
583+
if err != nil {
584+
t.Errorf("Unable to zip file")
585+
}
586+
587+
_, err = Unzip("../data/testsymlink.zip", "../data/test")
588+
589+
if err != nil {
590+
t.Errorf("Unable to unzip file %v", err)
591+
}
592+
593+
fi, err := os.Readlink("../data/test/testsymlink/symlink")
594+
if err != nil {
595+
t.Errorf("Could not read symbolic link: %v", err)
596+
}
597+
598+
if fi != "bar/test" {
599+
t.Errorf("Expected symlink to point to bar/test, got %s", fi)
600+
}
601+
602+
os.RemoveAll("../data/test")
603+
os.RemoveAll("../data/testsymlink.zip")
604+
}

sdstore/ziphelper.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package sdstore
2+
3+
import (
4+
"archive/zip"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path"
9+
"path/filepath"
10+
"strings"
11+
)
12+
13+
var compressedFormats = map[string]struct{}{
14+
".7z": {},
15+
".avi": {},
16+
".bz2": {},
17+
".cab": {},
18+
".gif": {},
19+
".gz": {},
20+
".jar": {},
21+
".jpeg": {},
22+
".jpg": {},
23+
".lz": {},
24+
".lzma": {},
25+
".mov": {},
26+
".mp3": {},
27+
".mp4": {},
28+
".mpeg": {},
29+
".mpg": {},
30+
".png": {},
31+
".rar": {},
32+
".tbz2": {},
33+
".tgz": {},
34+
".txz": {},
35+
".xz": {},
36+
".zip": {},
37+
".zipx": {},
38+
}
39+
40+
// Repurposed from https://github.com/mholt/archiver/pull/92/files
41+
// To include support for symbolic links
42+
func Zip(source, target string) error {
43+
zipfile, err := os.Create(target)
44+
if err != nil {
45+
return err
46+
}
47+
defer zipfile.Close()
48+
49+
w := zip.NewWriter(zipfile)
50+
defer w.Close()
51+
52+
sourceInfo, err := os.Stat(source)
53+
if err != nil {
54+
return fmt.Errorf("%s: stat: %v", source, err)
55+
}
56+
57+
var baseDir string
58+
if sourceInfo.IsDir() {
59+
baseDir = filepath.Base(source)
60+
}
61+
62+
return filepath.Walk(source, func(fpath string, info os.FileInfo, err error) error {
63+
if err != nil {
64+
return fmt.Errorf("walking to %s: %v", fpath, err)
65+
}
66+
67+
header, err := zip.FileInfoHeader(info)
68+
if err != nil {
69+
return fmt.Errorf("%s: getting header: %v", fpath, err)
70+
}
71+
72+
if baseDir != "" {
73+
name, err := filepath.Rel(source, fpath)
74+
if err != nil {
75+
return err
76+
}
77+
header.Name = path.Join(baseDir, filepath.ToSlash(name))
78+
}
79+
80+
if info.IsDir() {
81+
header.Name += "/"
82+
header.Method = zip.Store
83+
} else {
84+
ext := strings.ToLower(path.Ext(header.Name))
85+
if _, ok := compressedFormats[ext]; ok {
86+
header.Method = zip.Store
87+
} else {
88+
header.Method = zip.Deflate
89+
}
90+
}
91+
92+
writer, err := w.CreateHeader(header)
93+
if err != nil {
94+
return fmt.Errorf("%s: making header: %v", fpath, err)
95+
}
96+
97+
if info.IsDir() {
98+
return nil
99+
}
100+
101+
if (header.Mode() & os.ModeSymlink) != 0 {
102+
linkTarget, err := os.Readlink(fpath)
103+
if err != nil {
104+
return fmt.Errorf("%s: readlink: %v", fpath, err)
105+
}
106+
_, err = writer.Write([]byte(filepath.ToSlash(linkTarget)))
107+
if err != nil {
108+
return fmt.Errorf("%s: writing symlink target: %v", fpath, err)
109+
}
110+
return nil
111+
}
112+
113+
if header.Mode().IsRegular() {
114+
file, err := os.Open(fpath)
115+
if err != nil {
116+
return fmt.Errorf("%s: opening: %v", fpath, err)
117+
}
118+
defer file.Close()
119+
120+
_, err = io.CopyN(writer, file, info.Size())
121+
if err != nil && err != io.EOF {
122+
return fmt.Errorf("%s: copying contents: %v", fpath, err)
123+
}
124+
}
125+
126+
return nil
127+
})
128+
}
129+
130+
// Repurposed from https://github.com/mholt/archiver/pull/92/files
131+
// To include support for symbolic links
132+
func Unzip(src string, dest string) ([]string, error) {
133+
var filenames []string
134+
135+
r, err := zip.OpenReader(src)
136+
if err != nil {
137+
return filenames, err
138+
}
139+
defer r.Close()
140+
141+
for _, f := range r.File {
142+
143+
rc, err := f.Open()
144+
if err != nil {
145+
return filenames, err
146+
}
147+
defer rc.Close()
148+
149+
// Store filename/path for returning and using later on
150+
fpath := filepath.Join(dest, f.Name)
151+
152+
// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
153+
if dest != "/" && !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
154+
return filenames, fmt.Errorf("%s: illegal file path", fpath)
155+
}
156+
157+
filenames = append(filenames, fpath)
158+
159+
if f.FileInfo().IsDir() {
160+
161+
// Make Folder
162+
os.MkdirAll(fpath, os.ModePerm)
163+
} else if (f.FileInfo().Mode() & os.ModeSymlink) != 0 {
164+
buffer := make([]byte, f.FileInfo().Size())
165+
size, err := rc.Read(buffer)
166+
if err != nil && err != io.EOF {
167+
return filenames, err
168+
}
169+
170+
target := string(buffer[:size])
171+
172+
err = os.Symlink(target, fpath)
173+
if err != nil {
174+
return filenames, err
175+
}
176+
} else {
177+
178+
// Make File
179+
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
180+
return filenames, err
181+
}
182+
183+
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
184+
if err != nil {
185+
return filenames, err
186+
}
187+
188+
_, err = io.Copy(outFile, rc)
189+
190+
// Close the file without defer to close before next iteration of loop
191+
outFile.Close()
192+
193+
if err != nil {
194+
return filenames, err
195+
}
196+
197+
}
198+
}
199+
return filenames, nil
200+
}

0 commit comments

Comments
 (0)