package dependencies import ( "archive/tar" "archive/zip" "bytes" "compress/gzip" "fmt" "io" "net/http" "os" "path/filepath" "strings" "github.com/juju/errors" log "github.com/sirupsen/logrus" ) // downloadFile will download file from a given url and store in memory the content // content will probably be untar, ungzip, .... func downloadFile(url string) (io.Reader, error) { // Get the data //nolint:gosec,noctx // we trust the url resp, err := http.Get(url) if err != nil { return nil, errors.Trace(err) } defer func() { if err := resp.Body.Close(); err != nil { log.Errorf("Error closing body: %s", err) } }() buf := new(bytes.Buffer) _, err = buf.ReadFrom(resp.Body) if err != nil { return nil, errors.Trace(err) } return buf, nil } func unGzip(reader io.Reader) (io.Reader, error) { gzipReader, err := gzip.NewReader(reader) if err != nil { return nil, errors.Trace(err) } defer func() { if err := gzipReader.Close(); err != nil { log.Errorf("Error closing gzip reader: %s", err) } }() return gzipReader, nil } func unTar(reader io.Reader, subdir string, dest string) error { tr := tar.NewReader(reader) if subdir != "" && !strings.HasSuffix(subdir, "/") { subdir = fmt.Sprintf("%s/", subdir) } for { header, err := tr.Next() switch { // no more files case errors.Is(err, io.EOF): return nil case err != nil: return errors.Trace(err) case header == nil: continue } filename := header.Name if subdir != "" && strings.HasPrefix(filename, subdir) { filename = strings.TrimPrefix(filename, subdir) if filename == "" { continue } } target, err := sanitizeArchivePath(dest, filename) if err != nil { return errors.Trace(err) } log.Debugf("Extacting %s", target) switch header.Typeflag { // create directory if doesn't exit. case tar.TypeDir: if _, err := os.Stat(target); err != nil { if err := os.MkdirAll(target, 0750); err != nil { return errors.Trace(err) } } // create file case tar.TypeReg: //nolint:gosec // we did compute the file path. f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) if err != nil { return errors.Trace(err) } defer func() { if err := f.Close(); err != nil { log.Errorf("Error closing file: %s", err) } }() // copy contents to file. //nolint:gosec // TODO: do a decompression that protect from decompression bomb. if _, err := io.Copy(f, tr); err != nil { return errors.Trace(err) } } } } func unZip(reader io.Reader, subdir string, dest string) error { if subdir != "" && !strings.HasSuffix(subdir, "/") { subdir = fmt.Sprintf("%s/", subdir) } // transform io.Reader buff := bytes.NewBuffer([]byte{}) size, err := io.Copy(buff, reader) if err != nil { return errors.Trace(err) } readerBytes := bytes.NewReader(buff.Bytes()) // Open a zip archive for reading. zipReader, err := zip.NewReader(readerBytes, size) if err != nil { return errors.Trace(err) } for _, file := range zipReader.File { filename := file.Name if subdir != "" && strings.HasPrefix(filename, subdir) { filename = strings.TrimPrefix(filename, subdir) if filename == "" { continue } } target, err := sanitizeArchivePath(dest, filename) if err != nil { return errors.Trace(err) } log.Debugf("Extacting %s", target) if file.FileInfo().IsDir() { if _, err := os.Stat(target); err != nil { if err := os.MkdirAll(target, 0750); err != nil { return errors.Trace(err) } } } else { //nolint:gosec // we did compute the file path. f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, file.Mode()) if err != nil { return errors.Trace(err) } defer func() { if err := f.Close(); err != nil { log.Errorf("Error closing file: %s", err) } }() fileInArchive, err := file.Open() if err != nil { return errors.Trace(err) } defer func() { if err := fileInArchive.Close(); err != nil { log.Errorf("Error closing file: %s", err) } }() // copy contents to file //nolint:gosec // TODO: do a decompression that protect from decompression bomb. if _, err := io.Copy(f, fileInArchive); err != nil { return errors.Trace(err) } } } return nil } // sanitizeArchivePath will sanitize archive file pathing from "G305: Zip Slip vulnerability". func sanitizeArchivePath(d string, t string) (string, error) { if v := filepath.Join(d, t); strings.HasPrefix(v, filepath.Clean(d)) { return v, nil } return "", fmt.Errorf("%s: %s", "content filepath is tainted", t) }