1use 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 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 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
45fn 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 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 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 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 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 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}