summaryrefslogtreecommitdiff
path: root/libgo/go/cmd/go/internal/modfetch/unzip.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/cmd/go/internal/modfetch/unzip.go')
-rw-r--r--libgo/go/cmd/go/internal/modfetch/unzip.go153
1 files changed, 153 insertions, 0 deletions
diff --git a/libgo/go/cmd/go/internal/modfetch/unzip.go b/libgo/go/cmd/go/internal/modfetch/unzip.go
new file mode 100644
index 00000000000..a50431fd862
--- /dev/null
+++ b/libgo/go/cmd/go/internal/modfetch/unzip.go
@@ -0,0 +1,153 @@
+// Copyright 2018 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 modfetch
+
+import (
+ "archive/zip"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "cmd/go/internal/modfetch/codehost"
+ "cmd/go/internal/module"
+ "cmd/go/internal/str"
+)
+
+func Unzip(dir, zipfile, prefix string, maxSize int64) error {
+ if maxSize == 0 {
+ maxSize = codehost.MaxZipFile
+ }
+
+ // Directory can exist, but must be empty.
+ // except maybe
+ files, _ := ioutil.ReadDir(dir)
+ if len(files) > 0 {
+ return fmt.Errorf("target directory %v exists and is not empty", dir)
+ }
+ if err := os.MkdirAll(dir, 0777); err != nil {
+ return err
+ }
+
+ f, err := os.Open(zipfile)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ info, err := f.Stat()
+ if err != nil {
+ return err
+ }
+
+ z, err := zip.NewReader(f, info.Size())
+ if err != nil {
+ return fmt.Errorf("unzip %v: %s", zipfile, err)
+ }
+
+ foldPath := make(map[string]string)
+ var checkFold func(string) error
+ checkFold = func(name string) error {
+ fold := str.ToFold(name)
+ if foldPath[fold] == name {
+ return nil
+ }
+ dir := path.Dir(name)
+ if dir != "." {
+ if err := checkFold(dir); err != nil {
+ return err
+ }
+ }
+ if foldPath[fold] == "" {
+ foldPath[fold] = name
+ return nil
+ }
+ other := foldPath[fold]
+ return fmt.Errorf("unzip %v: case-insensitive file name collision: %q and %q", zipfile, other, name)
+ }
+
+ // Check total size, valid file names.
+ var size int64
+ for _, zf := range z.File {
+ if !str.HasPathPrefix(zf.Name, prefix) {
+ return fmt.Errorf("unzip %v: unexpected file name %s", zipfile, zf.Name)
+ }
+ if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
+ continue
+ }
+ name := zf.Name[len(prefix)+1:]
+ if err := module.CheckFilePath(name); err != nil {
+ return fmt.Errorf("unzip %v: %v", zipfile, err)
+ }
+ if err := checkFold(name); err != nil {
+ return err
+ }
+ if path.Clean(zf.Name) != zf.Name || strings.HasPrefix(zf.Name[len(prefix)+1:], "/") {
+ return fmt.Errorf("unzip %v: invalid file name %s", zipfile, zf.Name)
+ }
+ s := int64(zf.UncompressedSize64)
+ if s < 0 || maxSize-size < s {
+ return fmt.Errorf("unzip %v: content too large", zipfile)
+ }
+ size += s
+ }
+
+ // Unzip, enforcing sizes checked earlier.
+ dirs := map[string]bool{dir: true}
+ for _, zf := range z.File {
+ if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
+ continue
+ }
+ name := zf.Name[len(prefix):]
+ dst := filepath.Join(dir, name)
+ parent := filepath.Dir(dst)
+ for parent != dir {
+ dirs[parent] = true
+ parent = filepath.Dir(parent)
+ }
+ if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
+ return err
+ }
+ w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444)
+ if err != nil {
+ return fmt.Errorf("unzip %v: %v", zipfile, err)
+ }
+ r, err := zf.Open()
+ if err != nil {
+ w.Close()
+ return fmt.Errorf("unzip %v: %v", zipfile, err)
+ }
+ lr := &io.LimitedReader{R: r, N: int64(zf.UncompressedSize64) + 1}
+ _, err = io.Copy(w, lr)
+ r.Close()
+ if err != nil {
+ w.Close()
+ return fmt.Errorf("unzip %v: %v", zipfile, err)
+ }
+ if err := w.Close(); err != nil {
+ return fmt.Errorf("unzip %v: %v", zipfile, err)
+ }
+ if lr.N <= 0 {
+ return fmt.Errorf("unzip %v: content too large", zipfile)
+ }
+ }
+
+ // Mark directories unwritable, best effort.
+ var dirlist []string
+ for dir := range dirs {
+ dirlist = append(dirlist, dir)
+ }
+ sort.Strings(dirlist)
+
+ // Run over list backward to chmod children before parents.
+ for i := len(dirlist) - 1; i >= 0; i-- {
+ os.Chmod(dirlist[i], 0555)
+ }
+
+ return nil
+}