blob: 60e1c3ca9b4ea87555d0cddaded4b10f47745f40 [file] [log] [blame]
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
)
type Archive interface {
Next() (*File, error)
Close() error
}
type File struct {
io.Reader
Name string
Size int64
}
type tarArchive struct {
tr *tar.Reader
closer io.Closer
}
func (a *tarArchive) Next() (*File, error) {
for {
hdr, err := a.tr.Next()
if err != nil {
return nil, err
}
// We only care about files
if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
continue
}
return &File{
Reader: a.tr,
Name: hdr.Name,
Size: hdr.Size,
}, nil
}
}
func (a *tarArchive) Close() error {
if a.closer != nil {
return a.closer.Close()
}
return nil
}
func detectContentType(r io.Reader) (string, io.Reader, error) {
var buf [512]byte
n, err := io.ReadFull(r, buf[:])
if err != nil && err != io.ErrUnexpectedEOF {
return "", nil, err
}
ct := http.DetectContentType(buf[:n])
// Return a new reader which merges in the read bytes
return ct, io.MultiReader(bytes.NewReader(buf[:n]), r), nil
}
// openArchive opens the tar at the URL or filepath u. Also supported is tgz
// files over http.
func openArchive(u string) (Archive, error) {
var (
r io.Reader
closer io.Closer
)
if strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://") {
resp, err := http.Get(u)
if err != nil {
return nil, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
b, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
resp.Body.Close()
if err != nil {
return nil, err
}
return nil, &url.Error{
Op: "Get",
URL: u,
Err: fmt.Errorf("%s: %s", resp.Status, string(b)),
}
}
closer = resp.Body
r = resp.Body
} else if u == "-" {
r = os.Stdin
} else {
f, err := os.Open(u)
if err != nil {
return nil, err
}
closer = f
r = f
}
ct, r, err := detectContentType(r)
if err != nil {
return nil, err
}
if ct == "application/x-gzip" {
r, err = gzip.NewReader(r)
if err != nil {
if closer != nil {
_ = closer.Close()
}
return nil, err
}
}
return &tarArchive{
tr: tar.NewReader(r),
closer: closer,
}, nil
}