blob: 9d9e29889e46dc56b7b9daf4db363cd57f025461 [file] [log] [blame]
// 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/filepath"
"strings"
"cmd/go/internal/modfetch/codehost"
)
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)
}
// Check total size.
var size int64
for _, zf := range z.File {
if !strings.HasPrefix(zf.Name, prefix) {
return fmt.Errorf("unzip %v: unexpected file name %s", zipfile, zf.Name)
}
if strings.HasSuffix(zf.Name, "/") {
continue
}
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.
for _, zf := range z.File {
if strings.HasSuffix(zf.Name, "/") {
continue
}
dst := filepath.Join(dir, zf.Name[len(prefix):])
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 {
r.Close()
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)
}
}
return nil
}