| // Copyright 2017 The BoringSSL Authors | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     https://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | // ar.go contains functions for parsing .a archive files. | 
 |  | 
 | package ar | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"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:] | 
 | 			// Windows terminates filenames with NUL characters, | 
 | 			// while sysv/GNU uses /. | 
 | 			if i := bytes.IndexAny(filename, "/\x00"); i < 0 { | 
 | 				return nil, errors.New("ar: unterminated filename in table") | 
 | 			} else { | 
 | 				filename = filename[:i] | 
 | 			} | 
 |  | 
 | 			name = string(filename) | 
 |  | 
 | 		default: | 
 | 			name = strings.TrimRight(name, "/") | 
 | 		} | 
 |  | 
 | 		// Post-processing for BSD: | 
 | 		// https://en.wikipedia.org/wiki/Ar_(Unix)#BSD_variant | 
 | 		// | 
 | 		// If the name is of the form #1/XXX, XXX identifies the length of the | 
 | 		// name, and the name itself is stored as a prefix of the data, possibly | 
 | 		// null-padded. | 
 |  | 
 | 		var namelen uint | 
 | 		n, err := fmt.Sscanf(name, "#1/%d", &namelen) | 
 | 		if err == nil && n == 1 && len(contents) >= int(namelen) { | 
 | 			name = string(contents[:namelen]) | 
 | 			contents = contents[namelen:] | 
 |  | 
 | 			// Names can be null padded; find the first null (if any). Note that | 
 | 			// this also handles the case of a null followed by non-null | 
 | 			// characters. It's not clear whether those can ever show up in | 
 | 			// practice, but we might as well handle them in case they can show | 
 | 			// up. | 
 | 			var null int | 
 | 			for ; null < len(name); null++ { | 
 | 				if name[null] == 0 { | 
 | 					break | 
 | 				} | 
 | 			} | 
 | 			name = name[:null] | 
 | 		} | 
 |  | 
 | 		if name == "__.SYMDEF" || name == "__.SYMDEF SORTED" { | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		ret[name] = contents | 
 | 	} | 
 |  | 
 | 	return ret, nil | 
 | } |