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 ar
18
19import (
20	"bytes"
21	"errors"
22	"fmt"
23	"io"
24	"strconv"
25	"strings"
26)
27
28// ParseAR parses an archive file from r and returns a map from filename to
29// contents, or else an error.
30func ParseAR(r io.Reader) (map[string][]byte, error) {
31	// See https://en.wikipedia.org/wiki/Ar_(Unix)#File_format_details
32	const expectedMagic = "!<arch>\n"
33	var magic [len(expectedMagic)]byte
34	if _, err := io.ReadFull(r, magic[:]); err != nil {
35		return nil, err
36	}
37	if string(magic[:]) != expectedMagic {
38		return nil, errors.New("ar: not an archive file")
39	}
40
41	const filenameTableName = "//"
42	const symbolTableName = "/"
43	var longFilenameTable []byte
44	ret := make(map[string][]byte)
45
46	for {
47		var header [60]byte
48		if _, err := io.ReadFull(r, header[:]); err != nil {
49			if err == io.EOF {
50				break
51			}
52			return nil, errors.New("ar: error reading file header: " + err.Error())
53		}
54
55		name := strings.TrimRight(string(header[:16]), " ")
56		sizeStr := strings.TrimRight(string(header[48:58]), "\x00 ")
57		size, err := strconv.ParseUint(sizeStr, 10, 64)
58		if err != nil {
59			return nil, errors.New("ar: failed to parse file size: " + err.Error())
60		}
61
62		// File contents are padded to a multiple of two bytes
63		storedSize := size
64		if storedSize%2 == 1 {
65			storedSize++
66		}
67
68		contents := make([]byte, storedSize)
69		if _, err := io.ReadFull(r, contents); err != nil {
70			return nil, errors.New("ar: error reading file contents: " + err.Error())
71		}
72		contents = contents[:size]
73
74		switch {
75		case name == filenameTableName:
76			if longFilenameTable != nil {
77				return nil, errors.New("ar: two filename tables found")
78			}
79			longFilenameTable = contents
80			continue
81
82		case name == symbolTableName:
83			continue
84
85		case len(name) > 1 && name[0] == '/':
86			if longFilenameTable == nil {
87				return nil, errors.New("ar: long filename reference found before filename table")
88			}
89
90			// A long filename is stored as "/" followed by a
91			// base-10 offset in the filename table.
92			offset, err := strconv.ParseUint(name[1:], 10, 64)
93			if err != nil {
94				return nil, errors.New("ar: failed to parse filename offset: " + err.Error())
95			}
96			if offset > uint64((^uint(0))>>1) {
97				return nil, errors.New("ar: filename offset overflow")
98			}
99
100			if int(offset) > len(longFilenameTable) {
101				return nil, errors.New("ar: filename offset out of bounds")
102			}
103
104			filename := longFilenameTable[offset:]
105			// Windows terminates filenames with NUL characters,
106			// while sysv/GNU uses /.
107			if i := bytes.IndexAny(filename, "/\x00"); i < 0 {
108				return nil, errors.New("ar: unterminated filename in table")
109			} else {
110				filename = filename[:i]
111			}
112
113			name = string(filename)
114
115		default:
116			name = strings.TrimRight(name, "/")
117		}
118
119		// Post-processing for BSD:
120		// https://en.wikipedia.org/wiki/Ar_(Unix)#BSD_variant
121		//
122		// If the name is of the form #1/XXX, XXX identifies the length of the
123		// name, and the name itself is stored as a prefix of the data, possibly
124		// null-padded.
125
126		var namelen uint
127		n, err := fmt.Sscanf(name, "#1/%d", &namelen)
128		if err == nil && n == 1 && len(contents) >= int(namelen) {
129			name = string(contents[:namelen])
130			contents = contents[namelen:]
131
132			// Names can be null padded; find the first null (if any). Note that
133			// this also handles the case of a null followed by non-null
134			// characters. It's not clear whether those can ever show up in
135			// practice, but we might as well handle them in case they can show
136			// up.
137			var null int
138			for ; null < len(name); null++ {
139				if name[null] == 0 {
140					break
141				}
142			}
143			name = name[:null]
144		}
145
146		if name == "__.SYMDEF" || name == "__.SYMDEF SORTED" {
147			continue
148		}
149
150		ret[name] = contents
151	}
152
153	return ret, nil
154}
155