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