diff options
Diffstat (limited to 'src/util/fipstools/fipscommon/ar.go')
-rw-r--r-- | src/util/fipstools/fipscommon/ar.go | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/src/util/fipstools/fipscommon/ar.go b/src/util/fipstools/fipscommon/ar.go new file mode 100644 index 00000000..85b378d6 --- /dev/null +++ b/src/util/fipstools/fipscommon/ar.go @@ -0,0 +1,120 @@ +// Copyright (c) 2017, Google Inc. +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +// ar.go contains functions for parsing .a archive files. + +package fipscommon + +import ( + "bytes" + "errors" + "io" + "strconv" + "strings" +) + +// ParseAR parses an archive file from r and returns a map from filename to +// contents, or else an error. +func ParseAR(r io.Reader) (map[string][]byte, error) { + // See https://en.wikipedia.org/wiki/Ar_(Unix)#File_format_details + const expectedMagic = "!<arch>\n" + var magic [len(expectedMagic)]byte + if _, err := io.ReadFull(r, magic[:]); err != nil { + return nil, err + } + if string(magic[:]) != expectedMagic { + return nil, errors.New("ar: not an archive file") + } + + const filenameTableName = "//" + const symbolTableName = "/" + var longFilenameTable []byte + ret := make(map[string][]byte) + + for { + var header [60]byte + if _, err := io.ReadFull(r, header[:]); err != nil { + if err == io.EOF { + break + } + return nil, errors.New("ar: error reading file header: " + err.Error()) + } + + name := strings.TrimRight(string(header[:16]), " ") + sizeStr := strings.TrimRight(string(header[48:58]), "\x00 ") + size, err := strconv.ParseUint(sizeStr, 10, 64) + if err != nil { + return nil, errors.New("ar: failed to parse file size: " + err.Error()) + } + + // File contents are padded to a multiple of two bytes + storedSize := size + if storedSize%2 == 1 { + storedSize++ + } + + contents := make([]byte, storedSize) + if _, err := io.ReadFull(r, contents); err != nil { + return nil, errors.New("ar: error reading file contents: " + err.Error()) + } + contents = contents[:size] + + switch { + case name == filenameTableName: + if longFilenameTable != nil { + return nil, errors.New("ar: two filename tables found") + } + longFilenameTable = contents + continue + + case name == symbolTableName: + continue + + case len(name) > 1 && name[0] == '/': + if longFilenameTable == nil { + return nil, errors.New("ar: long filename reference found before filename table") + } + + // A long filename is stored as "/" followed by a + // base-10 offset in the filename table. + offset, err := strconv.ParseUint(name[1:], 10, 64) + if err != nil { + return nil, errors.New("ar: failed to parse filename offset: " + err.Error()) + } + if offset > uint64((^uint(0))>>1) { + return nil, errors.New("ar: filename offset overflow") + } + + if int(offset) > len(longFilenameTable) { + return nil, errors.New("ar: filename offset out of bounds") + } + + filename := longFilenameTable[offset:] + if i := bytes.IndexByte(filename, '/'); i < 0 { + return nil, errors.New("ar: unterminated filename in table") + } else { + filename = filename[:i] + } + + name = string(filename) + + default: + name = strings.TrimRight(name, "/") + } + + ret[name] = contents + } + + return ret, nil +} |