github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/go/internal/modfetch/unzip.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modfetch
     6  
     7  import (
     8  	"archive/zip"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  
    18  	"cmd/go/internal/modfetch/codehost"
    19  	"cmd/go/internal/module"
    20  	"cmd/go/internal/str"
    21  )
    22  
    23  func Unzip(dir, zipfile, prefix string, maxSize int64) error {
    24  	if maxSize == 0 {
    25  		maxSize = codehost.MaxZipFile
    26  	}
    27  
    28  	// Directory can exist, but must be empty.
    29  	// except maybe
    30  	files, _ := ioutil.ReadDir(dir)
    31  	if len(files) > 0 {
    32  		return fmt.Errorf("target directory %v exists and is not empty", dir)
    33  	}
    34  	if err := os.MkdirAll(dir, 0777); err != nil {
    35  		return err
    36  	}
    37  
    38  	f, err := os.Open(zipfile)
    39  	if err != nil {
    40  		return err
    41  	}
    42  	defer f.Close()
    43  	info, err := f.Stat()
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	z, err := zip.NewReader(f, info.Size())
    49  	if err != nil {
    50  		return fmt.Errorf("unzip %v: %s", zipfile, err)
    51  	}
    52  
    53  	foldPath := make(map[string]string)
    54  	var checkFold func(string) error
    55  	checkFold = func(name string) error {
    56  		fold := str.ToFold(name)
    57  		if foldPath[fold] == name {
    58  			return nil
    59  		}
    60  		dir := path.Dir(name)
    61  		if dir != "." {
    62  			if err := checkFold(dir); err != nil {
    63  				return err
    64  			}
    65  		}
    66  		if foldPath[fold] == "" {
    67  			foldPath[fold] = name
    68  			return nil
    69  		}
    70  		other := foldPath[fold]
    71  		return fmt.Errorf("unzip %v: case-insensitive file name collision: %q and %q", zipfile, other, name)
    72  	}
    73  
    74  	// Check total size, valid file names.
    75  	var size int64
    76  	for _, zf := range z.File {
    77  		if !str.HasPathPrefix(zf.Name, prefix) {
    78  			return fmt.Errorf("unzip %v: unexpected file name %s", zipfile, zf.Name)
    79  		}
    80  		if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
    81  			continue
    82  		}
    83  		name := zf.Name[len(prefix)+1:]
    84  		if err := module.CheckFilePath(name); err != nil {
    85  			return fmt.Errorf("unzip %v: %v", zipfile, err)
    86  		}
    87  		if err := checkFold(name); err != nil {
    88  			return err
    89  		}
    90  		if path.Clean(zf.Name) != zf.Name || strings.HasPrefix(zf.Name[len(prefix)+1:], "/") {
    91  			return fmt.Errorf("unzip %v: invalid file name %s", zipfile, zf.Name)
    92  		}
    93  		s := int64(zf.UncompressedSize64)
    94  		if s < 0 || maxSize-size < s {
    95  			return fmt.Errorf("unzip %v: content too large", zipfile)
    96  		}
    97  		size += s
    98  	}
    99  
   100  	// Unzip, enforcing sizes checked earlier.
   101  	dirs := map[string]bool{dir: true}
   102  	for _, zf := range z.File {
   103  		if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
   104  			continue
   105  		}
   106  		name := zf.Name[len(prefix):]
   107  		dst := filepath.Join(dir, name)
   108  		parent := filepath.Dir(dst)
   109  		for parent != dir {
   110  			dirs[parent] = true
   111  			parent = filepath.Dir(parent)
   112  		}
   113  		if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
   114  			return err
   115  		}
   116  		w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444)
   117  		if err != nil {
   118  			return fmt.Errorf("unzip %v: %v", zipfile, err)
   119  		}
   120  		r, err := zf.Open()
   121  		if err != nil {
   122  			w.Close()
   123  			return fmt.Errorf("unzip %v: %v", zipfile, err)
   124  		}
   125  		lr := &io.LimitedReader{R: r, N: int64(zf.UncompressedSize64) + 1}
   126  		_, err = io.Copy(w, lr)
   127  		r.Close()
   128  		if err != nil {
   129  			w.Close()
   130  			return fmt.Errorf("unzip %v: %v", zipfile, err)
   131  		}
   132  		if err := w.Close(); err != nil {
   133  			return fmt.Errorf("unzip %v: %v", zipfile, err)
   134  		}
   135  		if lr.N <= 0 {
   136  			return fmt.Errorf("unzip %v: content too large", zipfile)
   137  		}
   138  	}
   139  
   140  	// Mark directories unwritable, best effort.
   141  	var dirlist []string
   142  	for dir := range dirs {
   143  		dirlist = append(dirlist, dir)
   144  	}
   145  	sort.Strings(dirlist)
   146  
   147  	// Run over list backward to chmod children before parents.
   148  	for i := len(dirlist) - 1; i >= 0; i-- {
   149  		os.Chmod(dirlist[i], 0555)
   150  	}
   151  
   152  	return nil
   153  }