1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
|
// 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
}
|