getdeps/untar.go (111 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"errors"
"github.com/ulikunitz/xz"
"io"
"log"
"os"
"path/filepath"
"strings"
)
// Untar represents a tarball
type Untar struct {
Label string `json:"label"`
URL string `json:"url"`
Hash string `json:"hash,omitempty"`
Subdir string `json:"subdir,omitempty"`
}
// CompressionType is the type that defines compression types.
type CompressionType int
// compression types.
const (
CompressionTypeUnsupported = iota
CompressionTypeGzip
CompressionTypeXz
)
var (
magicBytesGzip = []byte{0x1f, 0x8b}
magicBytesXz = []byte{0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00}
)
func detectCompressionType(data []byte) CompressionType {
switch {
case len(data) >= len(magicBytesGzip) && bytes.Equal(data[:len(magicBytesGzip)], magicBytesGzip):
return CompressionTypeGzip
case len(data) >= len(magicBytesXz) && bytes.Equal(data[:len(magicBytesXz)], magicBytesXz):
return CompressionTypeXz
default:
return CompressionTypeUnsupported
}
}
// Get downloads a tar.gz file and uncompresses it
func (pkg *Untar) Get(projectDir string, urlOverrides *URLOverrides, hashMode HashMode) error {
// ignore file info, will use permissions from the tar metadata
data, _, err := fetchAndVerify(pkg.Label, projectDir, pkg.URL, hashMode, &pkg.Hash, urlOverrides)
if err != nil {
return err
}
dir, _ := os.Getwd()
log.Printf("%s: Uncompressing into %s...", pkg.Label, dir)
// uncompress. We support gzip and xz.
reader := bytes.NewReader(data)
var archive io.Reader
// gzip can be detected with http.DetectContentType, but xz is not
// supported. So we match the magic bytes in the xz header, as specified in
// the XZ file format Section 2.1.1.1, see
// https://tukaani.org/xz/xz-file-format.txt .
//
// For gzip, the magic bytes are 1F 8B (starting at 0)
// for xz the magic bytes are FD 37 7A 58 5A 00 (starting at 0)
compressionType := detectCompressionType(data)
switch compressionType {
case CompressionTypeGzip:
archive, err = gzip.NewReader(reader)
// Close only required for gzip package
defer archive.(io.ReadCloser).Close()
case CompressionTypeXz:
archive, err = xz.NewReader(reader)
case CompressionTypeUnsupported:
fallthrough
default:
return errors.New("unsupported compression type")
}
if err != nil {
return err
}
// untar
tarReader := tar.NewReader(archive)
subdirParts := strings.Split(pkg.Subdir, "/")
entry:
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
var name string
if len(pkg.Subdir) > 0 {
nameParts := strings.Split(header.Name, "/")
if len(nameParts) <= len(subdirParts) {
continue entry
}
for i, p := range subdirParts {
if nameParts[i] != p {
continue entry
}
}
name = filepath.Join(nameParts[len(subdirParts):]...)
if len(name) == 0 {
continue entry
}
} else {
name = header.Name
}
info := header.FileInfo()
if info.IsDir() {
if err = os.MkdirAll(name, info.Mode()); err != nil {
return err
}
continue
}
file, err := os.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
if err != nil {
return err
}
if _, err = io.Copy(file, tarReader); err != nil {
return err
}
if err = file.Close(); err != nil {
return err
}
}
return err
}