base-golang/pkg/android_binary/xml.go

272 lines
7.1 KiB
Go
Raw Normal View History

2024-07-23 10:23:43 +08:00
package android_binary
import (
"bytes"
"encoding/binary"
"encoding/xml"
"fmt"
"io"
"reflect"
)
// XMLFile is an XML file expressed in binary format.
type XMLFile struct {
stringPool *ResStringPool
resourceMap []uint32
notPrecessedNS map[ResStringPoolRef]ResStringPoolRef
namespaces map[ResStringPoolRef]ResStringPoolRef
xmlBuffer bytes.Buffer
}
// ResXMLTreeNode is basic XML tree node.
type ResXMLTreeNode struct {
Header ResChunkHeader
LineNumber uint32
Comment ResStringPoolRef
}
// ResXMLTreeNamespaceExt is extended XML tree node for namespace start/end nodes.
type ResXMLTreeNamespaceExt struct {
Prefix ResStringPoolRef
URI ResStringPoolRef
}
// ResXMLTreeAttrExt is extended XML tree node for start tags -- includes attribute.
type ResXMLTreeAttrExt struct {
NS ResStringPoolRef
Name ResStringPoolRef
AttributeStart uint16
AttributeSize uint16
AttributeCount uint16
IDIndex uint16
ClassIndex uint16
StyleIndex uint16
}
// ResXMLTreeAttribute is an attribute of start tags.
type ResXMLTreeAttribute struct {
NS ResStringPoolRef
Name ResStringPoolRef
RawValue ResStringPoolRef
TypedValue ResValue
}
// ResXMLTreeEndElementExt is extended XML tree node for element start/end nodes.
type ResXMLTreeEndElementExt struct {
NS ResStringPoolRef
Name ResStringPoolRef
}
// NewXMLFile returns a new XMLFile.
func NewXMLFile(r io.ReaderAt) (*XMLFile, error) {
f := new(XMLFile)
sr := io.NewSectionReader(r, 0, 1<<63-1)
fmt.Fprintf(&f.xmlBuffer, xml.Header)
header := new(ResChunkHeader)
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
return nil, err
}
offset := int64(header.HeaderSize)
for offset < int64(header.Size) {
chunkHeader, err := f.readChunk(r, offset)
if err != nil {
return nil, err
}
offset += int64(chunkHeader.Size)
}
return f, nil
}
// Reader returns a reader of XML file expressed in text format.
func (f *XMLFile) Reader() *bytes.Reader {
return bytes.NewReader(f.xmlBuffer.Bytes())
}
// Decode decodes XML file and stores the result in the value pointed to by v.
// To resolve the resource references, Decode also stores default TableFile and ResTableConfig in the value pointed to by v.
func (f *XMLFile) Decode(v any, table *TableFile, config *ResTableConfig) error {
decoder := xml.NewDecoder(f.Reader())
if err := decoder.Decode(v); err != nil {
return err
}
inject(reflect.ValueOf(v), table, config)
return nil
}
func (f *XMLFile) readChunk(r io.ReaderAt, offset int64) (*ResChunkHeader, error) {
sr := io.NewSectionReader(r, offset, 1<<63-1-offset)
chunkHeader := &ResChunkHeader{}
if _, err := sr.Seek(0, io.SeekStart); err != nil {
return nil, err
}
if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil {
return nil, err
}
var err error
if _, err := sr.Seek(0, io.SeekStart); err != nil {
return nil, err
}
switch chunkHeader.Type {
case ResStringPoolChunkType:
f.stringPool, err = readStringPool(sr)
case ResXMLStartNamespaceType:
err = f.readStartNamespace(sr)
case ResXMLEndNamespaceType:
err = f.readEndNamespace(sr)
case ResXMLStartElementType:
err = f.readStartElement(sr)
case ResXMLEndElementType:
err = f.readEndElement(sr)
}
if err != nil {
return nil, err
}
return chunkHeader, nil
}
// GetString returns a string referenced by ref.
func (f *XMLFile) GetString(ref ResStringPoolRef) string {
return f.stringPool.GetString(ref)
}
func (f *XMLFile) readStartNamespace(sr *io.SectionReader) error {
header := new(ResXMLTreeNode)
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
return err
}
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
return err
}
namespace := new(ResXMLTreeNamespaceExt)
if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil {
return err
}
if f.notPrecessedNS == nil {
f.notPrecessedNS = make(map[ResStringPoolRef]ResStringPoolRef)
}
f.notPrecessedNS[namespace.URI] = namespace.Prefix
if f.namespaces == nil {
f.namespaces = make(map[ResStringPoolRef]ResStringPoolRef)
}
f.namespaces[namespace.URI] = namespace.Prefix
return nil
}
func (f *XMLFile) readEndNamespace(sr *io.SectionReader) error {
header := new(ResXMLTreeNode)
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
return err
}
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
return err
}
namespace := new(ResXMLTreeNamespaceExt)
if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil {
return err
}
delete(f.namespaces, namespace.URI)
return nil
}
func (f *XMLFile) addNamespacePrefix(ns, name ResStringPoolRef) string {
if ns != NilResStringPoolRef {
prefix := f.GetString(f.namespaces[ns])
return fmt.Sprintf("%s:%s", prefix, f.GetString(name))
}
return f.GetString(name)
}
func (f *XMLFile) readStartElement(sr *io.SectionReader) error {
header := new(ResXMLTreeNode)
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
return err
}
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
return err
}
ext := new(ResXMLTreeAttrExt)
if err := binary.Read(sr, binary.LittleEndian, ext); err != nil {
return nil
}
fmt.Fprintf(&f.xmlBuffer, "<%s", f.addNamespacePrefix(ext.NS, ext.Name))
// output XML namespaces
if f.notPrecessedNS != nil {
for uri, prefix := range f.notPrecessedNS {
fmt.Fprintf(&f.xmlBuffer, " xmlns:%s=\"", f.GetString(prefix))
xml.Escape(&f.xmlBuffer, []byte(f.GetString(uri)))
fmt.Fprint(&f.xmlBuffer, "\"")
}
f.notPrecessedNS = nil
}
// process attributes
offset := int64(ext.AttributeStart + header.Header.HeaderSize)
for i := 0; i < int(ext.AttributeCount); i++ {
if _, err := sr.Seek(offset, io.SeekStart); err != nil {
return err
}
attr := new(ResXMLTreeAttribute)
binary.Read(sr, binary.LittleEndian, attr)
var value string
if attr.RawValue != NilResStringPoolRef {
value = f.GetString(attr.RawValue)
} else {
data := attr.TypedValue.Data
switch attr.TypedValue.DataType {
case TypeNull:
value = ""
case TypeReference:
value = fmt.Sprintf("@0x%08X", data)
case TypeIntDec:
value = fmt.Sprintf("%d", data)
case TypeIntHex:
value = fmt.Sprintf("0x%08X", data)
case TypeIntBoolean:
if data != 0 {
value = "true"
} else {
value = "false"
}
default:
value = fmt.Sprintf("@0x%08X", data)
}
}
fmt.Fprintf(&f.xmlBuffer, " %s=\"", f.addNamespacePrefix(attr.NS, attr.Name))
xml.Escape(&f.xmlBuffer, []byte(value))
fmt.Fprint(&f.xmlBuffer, "\"")
offset += int64(ext.AttributeSize)
}
fmt.Fprint(&f.xmlBuffer, ">")
return nil
}
func (f *XMLFile) readEndElement(sr *io.SectionReader) error {
header := new(ResXMLTreeNode)
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
return err
}
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
return err
}
ext := new(ResXMLTreeEndElementExt)
if err := binary.Read(sr, binary.LittleEndian, ext); err != nil {
return err
}
fmt.Fprintf(&f.xmlBuffer, "</%s>", f.addNamespacePrefix(ext.NS, ext.Name))
return nil
}