rusty_axml/lib.rs
1pub mod parser;
2pub mod chunks;
3pub mod errors;
4
5use std::{
6 fs,
7 collections::HashMap,
8 path::Path,
9};
10use std::io::{
11 Read,
12 Cursor,
13};
14
15use crate::errors::AxmlError;
16use crate::chunks::{
17 resource_map::ResourceMap,
18 res_table::ResTable,
19 string_pool::StringPool,
20};
21use crate::parser::{
22 Axml,
23 XmlNode
24};
25
26/// Component state
27///
28/// A component can be exported or enabled. Each of these feature have default values
29/// but these default values can be overriden by the developer. This means they have
30/// essentially four states:
31/// * default to `true`,
32/// * default to `false`,
33/// * explicitely set to `true`,
34/// * explicitely set to `false`
35#[derive(Debug, PartialEq)]
36pub enum ComponentState {
37 Unknown,
38 DefaultTrue,
39 DefaultFalse,
40 ExplicitTrue,
41 ExplicitFalse,
42}
43
44/// Read and parse the manifest of an APK
45///
46/// This function will extract the binary AXML from an APK and parse it, returning
47/// an `Axml` object.
48///
49/// # Example
50///
51/// ```
52/// let axml = rusty_axml::parse_from_apk("tests/assets/app.apk").unwrap();
53/// assert!(rusty_axml::get_requested_permissions(&axml)
54/// .contains(&"android.permission.ACCESS_FINE_LOCATION".to_string()));
55/// ```
56pub fn parse_from_apk<P: AsRef<Path>>(file_path: P) -> Result<Axml, AxmlError> {
57 let cursor = create_cursor_from_apk(file_path)?;
58 parse_from_cursor(cursor)
59}
60
61/// Read and parse an AXML file
62///
63/// This function will read a binary AXML and parse it, returning an `Axml` object.
64///
65/// # Example
66///
67/// ```
68/// let axml = rusty_axml::parse_from_axml("tests/assets/AndroidManifest.xml").unwrap();
69/// assert!(rusty_axml::get_requested_permissions(&axml)
70/// .contains(&"android.permission.ACCESS_FINE_LOCATION".to_string()));
71/// ```
72pub fn parse_from_axml<P: AsRef<Path>>(file_path: P) -> Result<Axml, AxmlError> {
73 let cursor = create_cursor_from_axml(file_path)?;
74 parse_from_cursor(cursor)
75}
76
77/// Create cursor of bytes from an APK
78///
79/// Open an APK, read the contents, and create a `Cursor` of the raw data
80/// for easier handling when parsing the XML data.
81/// This function expects `file_path` to point to an APK (or really, any valid
82/// zip file that contains a file named `AndroidManifest.xml`).
83/// To read an AXML file directly use [`create_cursor_from_axml`] instead.
84///
85/// # Example
86///
87/// ```
88/// let cursor = rusty_axml::create_cursor_from_apk("tests/assets/app.apk").unwrap();
89/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
90///
91/// assert!(rusty_axml::get_requested_permissions(&axml)
92/// .contains(&"android.permission.ACCESS_FINE_LOCATION".to_string()))
93/// ```
94///
95/// [`create_cursor_from_axml`]: fn.create_cursor_from_axml.html
96pub fn create_cursor_from_apk<P: AsRef<Path>>(file_path: P) -> Result<Cursor<Vec<u8>>, AxmlError> {
97 let mut axml_cursor = Vec::new();
98
99 let zipfile = std::fs::File::open(file_path.as_ref())?;
100 let mut archive = zip::ZipArchive::new(zipfile)?;
101 let mut raw_file = match archive.by_name("AndroidManifest.xml") {
102 Ok(file) => file,
103 Err(..) => {
104 panic!("Error: no AndroidManifest.xml in APK");
105 }
106 };
107 raw_file.read_to_end(&mut axml_cursor)?;
108
109 Ok(Cursor::new(axml_cursor))
110}
111
112/// Create cursor of bytes from an AXML file
113///
114/// Open an AXML file, read the contents, and create a `Cursor` of the raw data
115/// for easier handling when parsing the XML data.
116/// This function expects `file_path` to point to an AXML file.
117/// To read the manifest from an APK file use [`create_cursor_from_apk`] instead.
118///
119/// # Example
120///
121/// ```
122/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
123/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
124///
125/// assert!(rusty_axml::get_requested_permissions(&axml)
126/// .contains(&"android.permission.ACCESS_FINE_LOCATION".to_string()))
127/// ```
128///
129/// [`create_cursor_from_apk`]: fn.create_cursor_from_apk.html
130pub fn create_cursor_from_axml<P: AsRef<Path>>(file_path: P) -> Result<Cursor<Vec<u8>>, AxmlError> {
131 let mut axml_cursor = Vec::new();
132
133 let mut raw_file = fs::File::open(file_path.as_ref())?;
134 raw_file.read_to_end(&mut axml_cursor)?;
135
136 Ok(Cursor::new(axml_cursor))
137}
138
139/// Parses the AXML file from a cursor.
140///
141/// This function will return an `XmlElement` which represents the root of the
142/// parsed XML document. Note that the Cursor must first be created using either
143/// [`create_cursor_from_axml`] or [`create_cursor_from_apk`].
144///
145/// # Example
146///
147/// ```
148/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
149/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
150///
151/// assert!(rusty_axml::get_requested_permissions(&axml)
152/// .contains(&"android.permission.ACCESS_FINE_LOCATION".to_string()))
153/// ```
154///
155/// [`create_cursor_from_axml`]: fn.create_cursor_from_axml.html
156/// [`create_cursor_from_apk`]: fn.create_cursor_from_apk.html
157pub fn parse_from_cursor(axml_cursor: Cursor<Vec<u8>>) -> Result<Axml, AxmlError> {
158 parser::parse_xml(axml_cursor)
159}
160
161/// Parses the AXML file from a string.
162///
163/// Given a string that represents an AXML document, parses the string into XML.
164/// This function will return an `XmlElement` which represents the root of the
165/// parsed XML document.
166pub fn parse_from_string(axml_str: &str) -> Result<Axml, AxmlError> {
167 let axml_cursor = Cursor::new(Vec::from(axml_str.as_bytes()));
168 parser::parse_xml(axml_cursor)
169}
170
171/// Parses the AXML from a generic reader
172///
173/// This is a more generic version of [`parse_from_string`]. This function will
174/// read and attempt to parse AXML from any type that implements the [`Read`] trait.
175///
176/// [`parse_from_string`]: fn.parse_from_string.html
177/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
178pub fn parse_from_reader<R>(mut reader: R) -> Result<Axml, AxmlError>
179where
180 R: Read,
181{
182 let mut axml_vec = Vec::<u8>::new();
183 reader.read_to_end(&mut axml_vec)?;
184
185 let axml_cursor = Cursor::new(axml_vec);
186 parser::parse_xml(axml_cursor)
187}
188
189/// Return all elements of the given type
190///
191/// # Example
192///
193/// ```
194/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
195/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
196///
197/// assert_eq!(rusty_axml::find_nodes_by_type(&axml, "activity").len(), 1)
198/// ```
199pub fn find_nodes_by_type(axml: &Axml, element_type: &str) -> Vec<XmlNode> {
200 axml.iter()
201 .filter(|element| element.borrow().element_type() == element_type)
202 .collect()
203}
204
205/// Returns an `XmlNode` if it exists
206///
207/// # Example
208///
209/// ```
210/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
211/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
212///
213/// let service = rusty_axml::find_node_by_name(&axml, "androidx.profileinstaller.ProfileInstallReceiver").unwrap();
214/// assert_eq!(service.borrow().get_attr("android:permission"), Some("android.permission.DUMP"));
215/// ```
216pub fn find_node_by_name(axml: &Axml, node_name: &str) -> Option<XmlNode> {
217 let name = node_name.to_string();
218
219 // Component names are unique so after filter there will either be zero or one element
220 // Calling next() will either yield the component or `None`
221 axml.iter()
222 .find(|element| element.borrow().get_name() == Some(&name))
223}
224
225/// Check if a component is exposed
226///
227/// A component is considered exposed if it is both enabled and exported. Both of these properties
228/// can either be explicitely set (as parameters in the component declaration in the manifest) or
229/// left to their default state. The default state depends on the presence or not of intent
230/// filters: if there is an intent filter, the assumption is that the compoennt is meants to be
231/// available to other apps, and so it is exported by default, otherwise not.
232///
233/// # Example
234///
235/// ```
236/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
237/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
238///
239/// let service = rusty_axml::find_node_by_name(&axml, "eu.jgamba.myapplication.MyFirstService").unwrap();
240/// assert!(rusty_axml::is_component_exposed(&service));
241/// ```
242pub fn is_component_exposed(component: &XmlNode) -> bool {
243 let mut _enabled_state = ComponentState::DefaultTrue;
244 let mut exported_state = ComponentState::Unknown;
245
246 if let Some(enabled) = component.borrow().get_attr("android:enabled") {
247 if enabled == "false" {
248 return false;
249 } else {
250 _enabled_state = ComponentState::ExplicitTrue;
251 }
252 }
253
254 if let Some(exported) = component.borrow().get_attr("android:exported") {
255 if exported == "false" {
256 return false;
257 } else {
258 exported_state = ComponentState::ExplicitTrue;
259 }
260 }
261
262 // If the component has intent filters then the default exported value is `true`, otherwise
263 // `false`. This is not the case for content providers though, which usually have explicit
264 // values anyway.
265 if exported_state == ComponentState::Unknown {
266 for item in component.borrow().children().iter() {
267 if item.borrow().element_type() == "intent-filter" {
268 exported_state = ComponentState::DefaultTrue;
269 break;
270 }
271 }
272 if exported_state == ComponentState::Unknown {
273 exported_state = ComponentState::DefaultFalse;
274 }
275 }
276
277 // At this point we know the component is enabled so we just need to check if it is also
278 // exported. Also, if the component is explicitly not exported then we return early so here we
279 // do not have to check all the cases
280 match exported_state {
281 ComponentState::DefaultFalse => false,
282 ComponentState::DefaultTrue => true,
283 ComponentState::ExplicitTrue => true,
284 _ => panic!("never going to happen")
285 }
286}
287
288/// Get the list of activities names
289///
290/// This is only valid for APK manifest files and will return an empty vector otherwise
291///
292/// # Example
293///
294/// ```
295/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
296/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
297///
298/// assert_eq!(rusty_axml::get_activities_names(&axml).len(), 1)
299/// ```
300pub fn get_activities_names(parsed_xml: &Axml) -> Vec<String> {
301 find_nodes_by_type(parsed_xml, "activity")
302 .into_iter()
303 .filter(|element| element.borrow().get_name().is_some())
304 .map(|element| element.borrow().get_name().unwrap().to_string())
305 .collect()
306}
307
308/// Get the list of services names
309///
310/// This is only valid for APK manifest files and will return an empty vector otherwise
311///
312/// # Example
313///
314/// ```
315/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
316/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
317///
318/// assert_eq!(rusty_axml::get_services_names(&axml).len(), 2)
319/// ```
320pub fn get_services_names(parsed_xml: &Axml) -> Vec<String> {
321 find_nodes_by_type(parsed_xml, "service")
322 .into_iter()
323 .filter(|element| element.borrow().get_name().is_some())
324 .map(|element| element.borrow().get_name().unwrap().to_string())
325 .collect()
326}
327
328/// Get the list of providers names
329///
330/// This is only valid for APK manifest files and will return an empty vector otherwise
331///
332/// # Example
333///
334/// ```
335/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
336/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
337///
338/// assert_eq!(rusty_axml::get_providers_names(&axml).len(), 2)
339/// ```
340pub fn get_providers_names(parsed_xml: &Axml) -> Vec<String> {
341 find_nodes_by_type(parsed_xml, "provider")
342 .into_iter()
343 .filter(|element| element.borrow().get_name().is_some())
344 .map(|element| element.borrow().get_name().unwrap().to_string())
345 .collect()
346}
347
348/// Get the list of receivers names
349///
350/// This is only valid for APK manifest files and will return an empty vector otherwise
351///
352/// # Example
353///
354/// ```
355/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
356/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
357///
358/// assert_eq!(rusty_axml::get_receivers_names(&axml).len(), 2)
359/// ```
360pub fn get_receivers_names(parsed_xml: &Axml) -> Vec<String> {
361 find_nodes_by_type(parsed_xml, "receiver")
362 .into_iter()
363 .filter(|element| element.borrow().get_name().is_some())
364 .map(|element| element.borrow().get_name().unwrap().to_string())
365 .collect()
366}
367
368/// Get the list of declared permissions
369///
370/// This is only valid for APK manifest files and will return an empty vector otherwise.
371///
372/// # Example
373///
374/// ```
375/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
376/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
377///
378/// assert_eq!(rusty_axml::get_declared_permissions(&axml).len(), 2)
379/// ```
380pub fn get_declared_permissions(parsed_xml: &Axml) -> Vec<String> {
381 find_nodes_by_type(parsed_xml, "permission")
382 .into_iter()
383 .filter(|element| element.borrow().get_name().is_some())
384 .map(|element| element.borrow().get_name().unwrap().to_string())
385 .collect()
386}
387
388/// Get the list of requested permissions
389///
390/// This is only valid for APK manifest files and will return an empty vector otherwise. This also
391/// does not include permissions requested from within components.
392///
393/// # Example
394///
395/// ```
396/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
397/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
398///
399/// assert_eq!(rusty_axml::get_requested_permissions(&axml).len(), 3)
400/// ```
401pub fn get_requested_permissions(parsed_xml: &Axml) -> Vec<String> {
402 find_nodes_by_type(parsed_xml, "uses-permission")
403 .into_iter()
404 .filter(|element| element.borrow().get_name().is_some())
405 .map(|element| element.borrow().get_name().unwrap().to_string())
406 .collect()
407}
408
409/// Parse an app's manifest and get the list of exposed components
410///
411/// We first check if the app has the `android:enabled` component set, which would influence the
412/// state of all the components in the app
413///
414/// # Example
415///
416/// ```
417/// let cursor = rusty_axml::create_cursor_from_axml("tests/assets/AndroidManifest.xml").unwrap();
418/// let axml = rusty_axml::parse_from_cursor(cursor).unwrap();
419///
420/// let exposed_components = rusty_axml::get_exposed_components(&axml).unwrap();
421/// assert_eq!(exposed_components.get("activity").unwrap().len(), 1);
422/// assert_eq!(exposed_components.get("service").unwrap().len(), 1);
423/// assert_eq!(exposed_components.get("receiver").unwrap().len(), 2);
424/// assert_eq!(exposed_components.get("provider").unwrap().len(), 0);
425/// ```
426pub fn get_exposed_components(parsed_xml: &Axml) -> Option<HashMap<String, Vec<XmlNode>>> {
427 // Checking if the `<application>` tag has the `enabled` attribute set to `false`
428 let application = find_nodes_by_type(parsed_xml, "application").pop()?;
429 if let Some(enabled) = application.borrow().get_attr("android:enabled") {
430 if enabled == "false" {
431 return None;
432 }
433 }
434
435 let mut components = HashMap::new();
436
437 components.insert(
438 String::from("activity"),
439 find_nodes_by_type(parsed_xml, "activity")
440 .into_iter()
441 .filter(is_component_exposed)
442 .collect()
443 );
444 components.insert(
445 String::from("service"),
446 find_nodes_by_type(parsed_xml, "service")
447 .into_iter()
448 .filter(is_component_exposed)
449 .collect()
450 );
451 components.insert(
452 String::from("provider"),
453 find_nodes_by_type(parsed_xml, "provider")
454 .into_iter()
455 .filter(is_component_exposed)
456 .collect()
457 );
458 components.insert(
459 String::from("receiver"),
460 find_nodes_by_type(parsed_xml, "receiver")
461 .into_iter()
462 .filter(is_component_exposed)
463 .collect()
464 );
465
466 Some(components)
467}
468