Home | History | Annotate | Download | only in fipstools
      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 
     17 package main
     18 
     19 import (
     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.
     29 func 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