• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2017, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14
15// ar.go contains functions for parsing .a archive files.
16
17package main
18
19import (
20	"bytes"
21	"errors"
22	"io"
23	"strconv"
24	"strings"
25)
26
27// ParseAR parses an archive file from r and returns a map from filename to
28// contents, or else an error.
29func ParseAR(r io.Reader) (map[string][]byte, error) {
30	// See https://en.wikipedia.org/wiki/Ar_(Unix)#File_format_details
31	const expectedMagic = "!<arch>\n"
32	var magic [len(expectedMagic)]byte
33	if _, err := io.ReadFull(r, magic[:]); err != nil {
34		return nil, err
35	}
36	if string(magic[:]) != expectedMagic {
37		return nil, errors.New("ar: not an archive file")
38	}
39
40	const filenameTableName = "//"
41	const symbolTableName = "/"
42	var longFilenameTable []byte
43	ret := make(map[string][]byte)
44
45	for {
46		var header [60]byte
47		if _, err := io.ReadFull(r, header[:]); err != nil {
48			if err == io.EOF {
49				break
50			}
51			return nil, errors.New("ar: error reading file header: " + err.Error())
52		}
53
54		name := strings.TrimRight(string(header[:16]), " ")
55		sizeStr := strings.TrimRight(string(header[48:58]), "\x00 ")
56		size, err := strconv.ParseUint(sizeStr, 10, 64)
57		if err != nil {
58			return nil, errors.New("ar: failed to parse file size: " + err.Error())
59		}
60
61		// File contents are padded to a multiple of two bytes
62		storedSize := size
63		if storedSize%2 == 1 {
64			storedSize++
65		}
66
67		contents := make([]byte, storedSize)
68		if _, err := io.ReadFull(r, contents); err != nil {
69			return nil, errors.New("ar: error reading file contents: " + err.Error())
70		}
71		contents = contents[:size]
72
73		switch {
74		case name == filenameTableName:
75			if longFilenameTable != nil {
76				return nil, errors.New("ar: two filename tables found")
77			}
78			longFilenameTable = contents
79			continue
80
81		case name == symbolTableName:
82			continue
83
84		case len(name) > 1 && name[0] == '/':
85			if longFilenameTable == nil {
86				return nil, errors.New("ar: long filename reference found before filename table")
87			}
88
89			// A long filename is stored as "/" followed by a
90			// base-10 offset in the filename table.
91			offset, err := strconv.ParseUint(name[1:], 10, 64)
92			if err != nil {
93				return nil, errors.New("ar: failed to parse filename offset: " + err.Error())
94			}
95			if offset > uint64((^uint(0))>>1) {
96				return nil, errors.New("ar: filename offset overflow")
97			}
98
99			if int(offset) > len(longFilenameTable) {
100				return nil, errors.New("ar: filename offset out of bounds")
101			}
102
103			filename := longFilenameTable[offset:]
104			if i := bytes.IndexByte(filename, '/'); i < 0 {
105				return nil, errors.New("ar: unterminated filename in table")
106			} else {
107				filename = filename[:i]
108			}
109
110			name = string(filename)
111
112		default:
113			name = strings.TrimRight(name, "/")
114		}
115
116		ret[name] = contents
117	}
118
119	return ret, nil
120}
121