package rpm import ( "encoding/binary" "io" "io/ioutil" ) const r_MaxHeaderSize = 33554432 // A Header stores metadata about an rpm package. type Header struct { Version int Tags map[int]*Tag } // GetTag returns the tag with the given identifier. // // Nil is returned if the specified tag does not exist or the header is nil. func (c *Header) GetTag(id int) *Tag { if c == nil || len(c.Tags) == 0 { return nil } return c.Tags[id] } type rpmHeader [16]byte func (b rpmHeader) Magic() []byte { return b[:3] } func (b rpmHeader) Version() int { return int(b[3]) } func (b rpmHeader) IndexCount() int { return int(binary.BigEndian.Uint32(b[8:12])) } func (b rpmHeader) Size() int { return int(binary.BigEndian.Uint32(b[12:16])) } type rpmIndex [16]byte func (b rpmIndex) Tag() int { return int(binary.BigEndian.Uint32(b[:4])) } func (b rpmIndex) Type() TagType { return TagType(binary.BigEndian.Uint32(b[4:8])) } func (b rpmIndex) Offset() int { return int(binary.BigEndian.Uint32(b[8:12])) } func (b rpmIndex) ValueCount() int { return int(binary.BigEndian.Uint32(b[12:16])) } // readHeader reads an RPM package file header structure from r. func readHeader(r io.Reader, pad bool) (*Header, error) { // decode the header structure header var hdrBytes rpmHeader if _, err := r.Read(hdrBytes[:]); err != nil { return nil, err } if hdrBytes.Size() > r_MaxHeaderSize { return nil, errorf( "header size exceeds the maximum of %d: %d", r_MaxHeaderSize, hdrBytes.Size(), ) } if hdrBytes.IndexCount()*len(hdrBytes) > r_MaxHeaderSize { return nil, errorf( "header index size exceeds the maximum of %d: %d", r_MaxHeaderSize, hdrBytes.Size(), ) } // decode the index indexBytes := make([]rpmIndex, hdrBytes.IndexCount()) for i := 0; i < len(indexBytes); i++ { if _, err := r.Read(indexBytes[i][:]); err != nil { return nil, err } if indexBytes[i].Offset() >= hdrBytes.Size() { return nil, errorf( "offset of index %d is out of range: %s", i, indexBytes[i].Offset(), ) } } // decode the store tags := make(map[int]*Tag, len(indexBytes)) buf := make([]byte, hdrBytes.Size()) if _, err := io.ReadFull(r, buf); err != nil { return nil, err } for i, ix := range indexBytes { if ix.ValueCount() < 1 { return nil, errorf("invalid value count for index %d: %d", i, ix.ValueCount()) } o := ix.Offset() var v interface{} switch ix.Type() { case TagTypeBinary, TagTypeChar, TagTypeInt8: if o+ix.ValueCount() > len(buf) { switch ix.Type() { case TagTypeBinary: return nil, errorf("binary value for index %d is out of range", i+1) case TagTypeChar: return nil, errorf("uint8 value for index %d is out of range", i+1) case TagTypeInt8: return nil, errorf("int8 value for index %d is out of range", i+1) } return nil, errorf("value for index %d is out of range", i+1) } a := make([]byte, ix.ValueCount()) copy(a, buf[o:o+ix.ValueCount()]) v = a case TagTypeInt16: a := make([]int64, ix.ValueCount()) for v := 0; v < ix.ValueCount(); v++ { if o+2 > len(buf) { return nil, errorf("int16 value for index %d is out of range", i+1) } a[v] = int64(binary.BigEndian.Uint16(buf[o : o+2])) o += 2 } v = a case TagTypeInt32: a := make([]int64, ix.ValueCount()) for v := 0; v < ix.ValueCount(); v++ { if o+4 > len(buf) { return nil, errorf("int32 value for index %d is out of range", i+1) } a[v] = int64(binary.BigEndian.Uint32(buf[o : o+4])) o += 4 } v = a case TagTypeInt64: a := make([]int64, ix.ValueCount()) for v := 0; v < ix.ValueCount(); v++ { if o+8 > len(buf) { // TODO: better errors return nil, errorf("int64 value for index %d is out of range", i+1) } a[v] = int64(binary.BigEndian.Uint64(buf[o : o+8])) o += 8 } v = a case TagTypeString, TagTypeStringArray, TagTypeI18NString: // allow at least one byte per string if o+ix.ValueCount() > len(buf) { return nil, errorf("[]string value for index %d is out of range", i+1) } a := make([]string, ix.ValueCount()) for s := 0; s < ix.ValueCount(); s++ { // calculate string length var j int for j = 0; (o+j) < len(buf) && buf[o+j] != 0; j++ { } if j == len(buf) { return nil, errorf("string value for index %d is out of range", i+1) } a[s] = string(buf[o : o+j]) o += j + 1 } v = a case TagTypeNull: // nothing to do here default: // unknown data type return nil, errorf("unknown index data type: %0X", ix.Type()) } tags[ix.Tag()] = &Tag{ ID: ix.Tag(), Type: ix.Type(), Value: v, } } // pad to next header padding := int64(8-(hdrBytes.Size()%8)) % 8 if pad && padding != 0 { if _, err := io.CopyN(ioutil.Discard, r, padding); err != nil { return nil, err } } return &Header{ Version: hdrBytes.Version(), Tags: tags, }, nil }