...

Source file src/pkg/cmd/go/internal/modfetch/unzip.go

     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		"strings"
    16	
    17		"cmd/go/internal/modfetch/codehost"
    18		"cmd/go/internal/module"
    19		"cmd/go/internal/str"
    20	)
    21	
    22	func Unzip(dir, zipfile, prefix string, maxSize int64) error {
    23		// TODO(bcmills): The maxSize parameter is invariantly 0. Remove it.
    24		if maxSize == 0 {
    25			maxSize = codehost.MaxZipFile
    26		}
    27	
    28		// Directory can exist, but must be empty.
    29		files, _ := ioutil.ReadDir(dir)
    30		if len(files) > 0 {
    31			return fmt.Errorf("target directory %v exists and is not empty", dir)
    32		}
    33		if err := os.MkdirAll(dir, 0777); err != nil {
    34			return err
    35		}
    36	
    37		f, err := os.Open(zipfile)
    38		if err != nil {
    39			return err
    40		}
    41		defer f.Close()
    42		info, err := f.Stat()
    43		if err != nil {
    44			return err
    45		}
    46	
    47		z, err := zip.NewReader(f, info.Size())
    48		if err != nil {
    49			return fmt.Errorf("unzip %v: %s", zipfile, err)
    50		}
    51	
    52		foldPath := make(map[string]string)
    53		var checkFold func(string) error
    54		checkFold = func(name string) error {
    55			fold := str.ToFold(name)
    56			if foldPath[fold] == name {
    57				return nil
    58			}
    59			dir := path.Dir(name)
    60			if dir != "." {
    61				if err := checkFold(dir); err != nil {
    62					return err
    63				}
    64			}
    65			if foldPath[fold] == "" {
    66				foldPath[fold] = name
    67				return nil
    68			}
    69			other := foldPath[fold]
    70			return fmt.Errorf("unzip %v: case-insensitive file name collision: %q and %q", zipfile, other, name)
    71		}
    72	
    73		// Check total size, valid file names.
    74		var size int64
    75		for _, zf := range z.File {
    76			if !str.HasPathPrefix(zf.Name, prefix) {
    77				return fmt.Errorf("unzip %v: unexpected file name %s", zipfile, zf.Name)
    78			}
    79			if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
    80				continue
    81			}
    82			name := zf.Name[len(prefix)+1:]
    83			if err := module.CheckFilePath(name); err != nil {
    84				return fmt.Errorf("unzip %v: %v", zipfile, err)
    85			}
    86			if err := checkFold(name); err != nil {
    87				return err
    88			}
    89			if path.Clean(zf.Name) != zf.Name || strings.HasPrefix(zf.Name[len(prefix)+1:], "/") {
    90				return fmt.Errorf("unzip %v: invalid file name %s", zipfile, zf.Name)
    91			}
    92			s := int64(zf.UncompressedSize64)
    93			if s < 0 || maxSize-size < s {
    94				return fmt.Errorf("unzip %v: content too large", zipfile)
    95			}
    96			size += s
    97		}
    98	
    99		// Unzip, enforcing sizes checked earlier.
   100		for _, zf := range z.File {
   101			if zf.Name == prefix || strings.HasSuffix(zf.Name, "/") {
   102				continue
   103			}
   104			name := zf.Name[len(prefix):]
   105			dst := filepath.Join(dir, name)
   106			if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
   107				return err
   108			}
   109			w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0444)
   110			if err != nil {
   111				return fmt.Errorf("unzip %v: %v", zipfile, err)
   112			}
   113			r, err := zf.Open()
   114			if err != nil {
   115				w.Close()
   116				return fmt.Errorf("unzip %v: %v", zipfile, err)
   117			}
   118			lr := &io.LimitedReader{R: r, N: int64(zf.UncompressedSize64) + 1}
   119			_, err = io.Copy(w, lr)
   120			r.Close()
   121			if err != nil {
   122				w.Close()
   123				return fmt.Errorf("unzip %v: %v", zipfile, err)
   124			}
   125			if err := w.Close(); err != nil {
   126				return fmt.Errorf("unzip %v: %v", zipfile, err)
   127			}
   128			if lr.N <= 0 {
   129				return fmt.Errorf("unzip %v: content too large", zipfile)
   130			}
   131		}
   132	
   133		return nil
   134	}
   135	
   136	// makeDirsReadOnly makes a best-effort attempt to remove write permissions for dir
   137	// and its transitive contents.
   138	func makeDirsReadOnly(dir string) {
   139		type pathMode struct {
   140			path string
   141			mode os.FileMode
   142		}
   143		var dirs []pathMode // in lexical order
   144		filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   145			if err == nil && info.Mode()&0222 != 0 {
   146				if info.IsDir() {
   147					dirs = append(dirs, pathMode{path, info.Mode()})
   148				}
   149			}
   150			return nil
   151		})
   152	
   153		// Run over list backward to chmod children before parents.
   154		for i := len(dirs) - 1; i >= 0; i-- {
   155			os.Chmod(dirs[i].path, dirs[i].mode&^0222)
   156		}
   157	}
   158	
   159	// RemoveAll removes a directory written by Download or Unzip, first applying
   160	// any permission changes needed to do so.
   161	func RemoveAll(dir string) error {
   162		// Module cache has 0555 directories; make them writable in order to remove content.
   163		filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   164			if err != nil {
   165				return nil // ignore errors walking in file system
   166			}
   167			if info.IsDir() {
   168				os.Chmod(path, 0777)
   169			}
   170			return nil
   171		})
   172		return os.RemoveAll(dir)
   173	}
   174	

View as plain text