Skip to main content

apk/xml/
decode.rs

1//! Decoding binary XML into a [`Document`].
2
3use super::encode::{ATTRIBUTE_SIZE, AttributeRecord, ElementStart, NONE};
4use super::{AttrName, Attribute, Document, Element, MAX_DEPTH, Namespace, Node, NodeKind, XmlError};
5use crate::res::chunk::{Chunk, ChunkType, read_padded};
6use crate::res::{ResourceId, StringPool, TextBudget, Value, data_type};
7
8impl Document {
9    /// Decodes a compiled binary XML file such as `AndroidManifest.xml`.
10    ///
11    /// Attribute values keep their compiled types; references hold resource
12    /// IDs. Comments and line numbers are discarded.
13    pub fn decode(bytes: &[u8]) -> Result<Self, XmlError> {
14        let root = Chunk::parse_root(bytes, ChunkType::XML)?;
15        let mut decoder = Decoder {
16            strings: None,
17            resource_map: &[],
18            budget: TextBudget::for_input(bytes.len()),
19            pending_namespaces: Vec::new(),
20            open: Vec::new(),
21            root: None,
22            offset: 0,
23        };
24        for chunk in root.children() {
25            decoder.chunk(&chunk?)?;
26        }
27        if !decoder.open.is_empty() {
28            return Err(XmlError::Unclosed { count: decoder.open.len() });
29        }
30        decoder.root.map(Document::new).ok_or(XmlError::NoRoot)
31    }
32}
33
34struct Decoder<'a> {
35    strings: Option<StringPool<'a>>,
36    /// The resource ID of each attribute name, by string index.
37    resource_map: &'a [[u8; 4]],
38    budget: TextBudget,
39    pending_namespaces: Vec<Namespace>,
40    open: Vec<Element>,
41    root: Option<Element>,
42    offset: usize,
43}
44
45/// The first `N` little-endian words of `body`.
46fn words<const N: usize>(body: &[u8]) -> Option<[u32; N]> {
47    let words: &[[u8; 4]; N] = body.as_chunks::<4>().0.get(..N)?.try_into().ok()?;
48    Some(words.map(u32::from_le_bytes))
49}
50
51impl<'a> Decoder<'a> {
52    fn string(&mut self, index: u32) -> Result<String, XmlError> {
53        let strings = self.strings.as_ref().ok_or(XmlError::MissingStringPool)?;
54        let text = strings.get(index).ok_or(XmlError::StringOutOfRange { index })?;
55        Ok(self.budget.own(text)?)
56    }
57
58    fn optional_string(&mut self, index: u32) -> Result<Option<String>, XmlError> {
59        (index != NONE).then(|| self.string(index)).transpose()
60    }
61
62    fn chunk(&mut self, chunk: &Chunk<'a>) -> Result<(), XmlError> {
63        let offset = self.offset;
64        self.offset += chunk.bytes.len();
65        let truncated = |kind| XmlError::TruncatedNode { kind, offset };
66        match chunk.ty {
67            ChunkType::STRING_POOL if self.strings.is_none() => self.strings = Some(StringPool::parse(chunk)?),
68            ChunkType::XML_RESOURCE_MAP => self.resource_map = chunk.body.as_chunks::<4>().0,
69            ChunkType::XML_START_NAMESPACE => {
70                let [prefix, uri] = words(chunk.body).ok_or(truncated(NodeKind::Namespace))?;
71                let namespace = Namespace { prefix: self.optional_string(prefix)?, uri: self.string(uri)? };
72                self.pending_namespaces.push(namespace);
73            }
74            ChunkType::XML_START_ELEMENT => {
75                if self.open.len() >= MAX_DEPTH {
76                    return Err(XmlError::TooDeep);
77                }
78                let element = self.start_element(chunk.body).ok_or(truncated(NodeKind::StartElement))??;
79                self.open.push(element);
80            }
81            ChunkType::XML_END_ELEMENT => {
82                let [namespace, name] = words(chunk.body).ok_or(truncated(NodeKind::EndElement))?;
83                let found = self.string(name)?;
84                let namespace = self.optional_string(namespace)?;
85                let element = self.open.pop().ok_or_else(|| XmlError::UnexpectedEnd { found: found.clone() })?;
86                if element.name != found {
87                    return Err(XmlError::MismatchedEnd { expected: element.name, found });
88                }
89                if element.namespace != namespace {
90                    return Err(XmlError::MismatchedNamespace { expected: element.namespace, found: namespace });
91                }
92                match self.open.last_mut() {
93                    Some(parent) => parent.children.push(Node::Element(element)),
94                    None if self.root.is_none() => self.root = Some(element),
95                    None => return Err(XmlError::MultipleRoots),
96                }
97            }
98            ChunkType::XML_CDATA => {
99                let [data] = words(chunk.body).ok_or(truncated(NodeKind::Text))?;
100                let text = self.string(data)?;
101                if let Some(parent) = self.open.last_mut() {
102                    parent.children.push(Node::Text(text));
103                }
104            }
105            _ => {}
106        }
107        Ok(())
108    }
109
110    /// Returns `None` if the element chunk is truncated.
111    fn start_element(&mut self, body: &[u8]) -> Option<Result<Element, XmlError>> {
112        let start: ElementStart = read_padded(body.get(..20)?);
113        let count = usize::from(start.attribute_count);
114        if count != 0 && (start.attribute_size < ATTRIBUTE_SIZE || start.attribute_start < 20) {
115            return None;
116        }
117        let stride = usize::from(start.attribute_size.max(ATTRIBUTE_SIZE));
118        let records = body.get(usize::from(start.attribute_start)..)?;
119        let records = records.get(..stride.checked_mul(count)?)?.chunks(stride);
120        Some(self.element(&start, records.map(read_padded)))
121    }
122
123    fn element(
124        &mut self,
125        start: &ElementStart,
126        records: impl ExactSizeIterator<Item = AttributeRecord>,
127    ) -> Result<Element, XmlError> {
128        let mut attributes = Vec::with_capacity(records.len());
129        for record in records {
130            let id = self.resource_map.get(record.name as usize).map(|&id| ResourceId(u32::from_le_bytes(id)));
131            let name = AttrName {
132                namespace: self.optional_string(record.namespace)?,
133                name: self.string(record.name)?,
134                id: id.filter(|id| id.0 != 0),
135            };
136            let raw = record.typed_value;
137            let value = match raw.data_type == data_type::NULL && record.raw_value != NONE {
138                // An attribute aapt did not type, such as `package`, is its raw string.
139                true => Value::String(self.string(record.raw_value)?),
140                false => Value::from_raw(raw, |index| self.string(index))?,
141            };
142            attributes.push(Attribute { name, value });
143        }
144        Ok(Element {
145            name: self.string(start.name)?,
146            namespace: self.optional_string(start.namespace)?,
147            namespaces: std::mem::take(&mut self.pending_namespaces),
148            attributes,
149            children: Vec::new(),
150        })
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::android::attr;
158    use crate::res::{Color, Dimension, DimensionUnit, ResourceRef};
159
160    fn sample() -> Document {
161        Document::new(
162            Element::new("manifest")
163                .with_android_namespace()
164                .with_attr("package", "org.example")
165                .with_attr(attr::VERSION_CODE, 7)
166                .with_child(
167                    Element::new("application")
168                        .with_attr(attr::LABEL, "Hëllo 🦀")
169                        .with_attr(attr::DEBUGGABLE, false)
170                        .with_attr(attr::THEME, ResourceRef::Id(ResourceId(0x0103_0005)))
171                        .with_child(Element::new("meta-data").with_attr(attr::VALUE, Color::rgb(0x336699)))
172                        .with_child(
173                            Element::new("layout")
174                                .with_attr(attr::MIN_WIDTH, Dimension::new(4.0, DimensionUnit::Dp).unwrap()),
175                        ),
176                ),
177        )
178    }
179
180    #[test]
181    fn decodes_what_it_encodes() {
182        let document = sample();
183        assert_eq!(Document::decode(&document.encode(&()).unwrap()).unwrap(), document);
184    }
185
186    #[test]
187    fn truncation_before_the_root_closes_always_fails() {
188        let bytes = sample().encode(&()).unwrap();
189        // The trailing 24-byte namespace-end chunk carries no content.
190        const NAMESPACE_END: usize = 24;
191        for len in 0..bytes.len() - NAMESPACE_END {
192            let mut truncated = bytes[..len].to_vec();
193            if len >= 8 {
194                truncated[4..8].copy_from_slice(&(len as u32).to_le_bytes());
195            }
196            assert!(Document::decode(&truncated).is_err(), "truncation at {len} decoded");
197        }
198    }
199
200    #[test]
201    fn mismatched_closing_tags_are_rejected() {
202        let mut bytes = Document::new(Element::new("a").with_child(Element::new("b"))).encode(&()).unwrap();
203        // The document ends with `</b></a>`: two 24-byte chunks whose last field is the name.
204        const END_CHUNK: usize = 24;
205        let outer_name = bytes.len() - 4;
206        let inner_name = outer_name - END_CHUNK;
207        let a = bytes[outer_name..].to_vec();
208        bytes[inner_name..inner_name + 4].copy_from_slice(&a);
209        assert!(matches!(
210            Document::decode(&bytes),
211            Err(XmlError::MismatchedEnd { expected, found }) if expected == "b" && found == "a"
212        ));
213    }
214
215    #[test]
216    fn attributes_cannot_overlap_the_element_header_or_each_other() {
217        let original = sample().encode(&()).unwrap();
218        let root = Chunk::parse_root(&original, ChunkType::XML).unwrap();
219        let element = root.children().map(Result::unwrap).find(|c| c.ty == ChunkType::XML_START_ELEMENT).unwrap();
220        let body = element.body.as_ptr() as usize - original.as_ptr() as usize;
221        for (field, value) in [(8, 0u16), (8, 19), (10, 0), (10, 19)] {
222            let mut bytes = original.clone();
223            bytes[body + field..body + field + 2].copy_from_slice(&value.to_le_bytes());
224            assert!(matches!(Document::decode(&bytes), Err(XmlError::TruncatedNode { .. })), "{field}: {value}");
225        }
226    }
227
228    #[test]
229    fn closing_namespaces_must_match_even_when_local_names_match() {
230        let original = sample().encode(&()).unwrap();
231        let root = Chunk::parse_root(&original, ChunkType::XML).unwrap();
232        let chunks: Vec<_> = root.children().map(Result::unwrap).collect();
233        let namespace = chunks.iter().find(|c| c.ty == ChunkType::XML_START_NAMESPACE).unwrap();
234        let uri = &namespace.body[4..8];
235        let closing = chunks.iter().rev().find(|c| c.ty == ChunkType::XML_END_ELEMENT).unwrap();
236        let at = closing.body.as_ptr() as usize - original.as_ptr() as usize;
237        let mut bytes = original.clone();
238        bytes[at..at + 4].copy_from_slice(uri);
239        assert!(matches!(
240            Document::decode(&bytes),
241            Err(XmlError::MismatchedNamespace { expected: None, found: Some(found) })
242                if found == crate::android::NAMESPACE
243        ));
244    }
245
246    #[test]
247    fn repeated_strings_cannot_amplify_a_document() {
248        // 2,000 elements that all carry the same 8 KiB attribute value.
249        let text = "x".repeat(8 << 10);
250        let children = (0..2_000).map(|_| Element::new("e").with_attr("v", text.as_str()));
251        let bytes = Document::new(Element::new("root").with_children(children)).encode(&()).unwrap();
252        assert!(bytes.len() < 256 << 10, "the input stays small: {} bytes", bytes.len());
253        assert!(matches!(
254            Document::decode(&bytes),
255            Err(XmlError::StringPool(crate::res::StringPoolError::TextBudgetExceeded { .. }))
256        ));
257    }
258}