Files
aptly/vendor/github.com/mkrautz/goar/reader.go
T
2017-03-22 19:24:06 +03:00

262 lines
6.6 KiB
Go

// Copyright (c) 2011 Mikkel Krautz
// The use of this source code is goverened by a BSD-style
// license that can be found in the LICENSE-file.
package ar
import (
"errors"
"io"
"strconv"
"strings"
)
// A Reader provides sequential access to the contents of a BSD or GNU-style ar archive.
// An archive file consists of a sequence of files.
// The Next method advances to the next file in the archive (including the first).
// After Next has returned a header, the Reader can be treated as an io.Reader to
// access the data of the file described by the header received from Next.
//
// Example:
// tr := ar.NewReader(r)
// for {
// hdr, err := tr.Next()
// if err == io.EOF {
// // end of archive
// break
// }
// if err != nil {
// // handle error
// }
// io.Copy(data, tr)
// }
type Reader struct {
r io.Reader
offset int64
dataRemain int64
gnuLongFn []byte
}
// NewReader creates a new Reader reading from r.
func NewReader(r io.Reader) *Reader {
return &Reader{r, 0, 0, nil}
}
// Next advances to the next entry in the archive.
func (ar *Reader) Next() (hdr *Header, err error) {
// If this is our first read, we should check whether a global
// ar header is present.
if ar.offset == 0 {
ghdr := make([]byte, len(globalHeader))
nread, err := io.ReadFull(ar.r, ghdr)
if err != nil {
return nil, err
}
ar.offset += int64(nread)
if globalHeader != string(ghdr) {
return nil, ErrMissingGlobalHeader
}
}
// If an entry wasn't fully read, skip the remaining bytes
if ar.dataRemain > 0 {
sw := skippingWriter{}
ncopied, err := io.CopyN(sw, ar.r, ar.dataRemain)
if err == io.EOF || err == nil {
ar.offset += ncopied
ar.dataRemain -= ncopied
if ar.dataRemain > 0 {
return nil, errors.New("ar: skip failed")
}
} else if err != nil {
return nil, err
}
}
// Read a file header from the archive.
hdr, err = ar.consumeHeader()
if err != nil {
return nil, err
}
// If the consumed header is a GNU long file name section,
// read its filename table and update the Reader struct with it.
if hdr.Name == "//" {
// Return an error if we've already read a GNU long filename
// section.
if ar.gnuLongFn != nil {
return nil, errors.New("ar: malformed archive, duplicate gnu long filename sections")
}
ar.dataRemain = hdr.Size
buf := make([]byte, int(hdr.Size))
_, err = io.ReadFull(ar, buf)
// We expect the GNU long filename section
// to be as long as noted in the header.
if err != nil {
return nil, err
}
ar.gnuLongFn = buf
// The special header has been consumed.
// Read the next file header in the file so we can return
// that to the user.
hdr, err = ar.consumeHeader()
if err != nil {
return nil, err
}
}
ar.dataRemain = hdr.Size
return hdr, nil
}
// Read reads from the current entry in the archive.
// It returns 0, io.EOF when it reaches the end of that entry,
// until Next is called to advance to the next entry.
func (ar *Reader) Read(b []byte) (n int, err error) {
if ar.dataRemain == 0 {
return 0, io.EOF
}
if int64(len(b)) > ar.dataRemain {
b = b[:ar.dataRemain]
}
n, err = ar.r.Read(b)
ar.offset += int64(n)
ar.dataRemain -= int64(n)
if ar.dataRemain == 0 {
err = io.EOF
}
return
}
func (ar *Reader) consumeHeader() (*Header, error) {
// Data sections are required to always end on a 2-byte boundary.
// Simply check if we're at a 2-byte offset before consuming a new
// file header. If not, consume the padding byte and check that it
// is a '/n' like we expect.
if ar.offset%2 != 0 {
lineFeed := make([]byte, 1)
_, err := ar.r.Read(lineFeed)
if err != nil {
if lineFeed[0] != '\n' {
return nil, errors.New("ar: alignment byte read, not '\n'")
}
}
ar.offset += 1
}
fhdr := make([]byte, 60)
nread, err := io.ReadFull(ar.r, fhdr)
if err != nil {
return nil, err
}
ar.offset += int64(nread)
hdr := &Header{}
fileName := arString(string(fhdr[0:16]))
mtime := arString(string(fhdr[16:28]))
uid := arString(string(fhdr[28:34]))
gid := arString(string(fhdr[34:40]))
mode := arString(string(fhdr[40:48]))
size := arString(string(fhdr[48:58]))
magic := arString(string(fhdr[58:60]))
if magic != fileHeaderMagic {
return nil, ErrFileHeader
}
if mtime != "" {
hdr.Mtime, err = strconv.ParseInt(mtime, 10, 64)
if err != nil {
return nil, err
}
}
if uid != "" {
hdr.Uid, err = strconv.Atoi(uid)
if err != nil {
return nil, err
}
}
if gid != "" {
hdr.Gid, err = strconv.Atoi(gid)
if err != nil {
return nil, err
}
}
hdr.Size, err = strconv.ParseInt(size, 10, 64)
if err != nil {
return nil, err
}
if mode != "" {
hdr.Mode, err = strconv.ParseInt(mode, 8, 64)
if err != nil {
return nil, err
}
}
// GNU-style ar archives use '/' as a filename terminator for everything
// but special sections (sections that start with a '/'), so we strip trailing
// slashes from all filenames that do not start with a slash themselves.
if len(fileName) > 0 && fileName[0] != '/' && fileName[len(fileName)-1] == '/' {
fileName = fileName[:len(fileName)-1]
}
// The file name is stored as a BSD long filename
// That is, the filename is stored directly after the file header, as
// part of the data section.
if strings.HasPrefix(fileName, bsdLongFileNamePrefix) {
fnLengthStr := arString(fileName[len(bsdLongFileNamePrefix):])
fnLength, err := strconv.Atoi(fnLengthStr)
if err != nil {
return nil, err
}
if int64(fnLength) > hdr.Size {
return nil, errors.New("ar: invalid bsd long filename in file")
}
longFn := make([]byte, fnLength)
nread, err = io.ReadFull(ar.r, longFn)
if err != nil {
return nil, err
}
ar.offset += int64(nread)
hdr.Size -= int64(nread)
hdr.Name = nulTerminated(longFn)
// The file name is stored as a GNU long filename
} else if fhdr[0] == '/' && fhdr[1] >= '0' && fhdr[1] <= '9' {
// We must have read a GNU-style long filename section for this lookup
// to succeed.
if ar.gnuLongFn == nil {
return nil, errors.New("ar: encountered gnu-style long fn without corresponding long fn section")
}
gnuOffset, err := strconv.ParseInt(fileName[1:], 10, 64)
if err != nil {
return nil, err
}
if gnuOffset <= int64(len(ar.gnuLongFn)) {
fnStr, err := gnuArString(ar.gnuLongFn[gnuOffset:])
if err != nil {
return nil, err
}
if fnStr[len(fnStr)-1] != '/' {
return nil, errors.New("ar: gnu long filename is not terminated")
}
hdr.Name = fnStr[:len(fnStr)-1]
} else {
// The offset overflows our long filename section
return nil, errors.New("ar: gnu long filename lookup out of bounds")
}
// Regular short file name
} else {
hdr.Name = fileName
}
return hdr, nil
}