abxml 0.8.2

Parser for resources.arsc contained on APKs and binary XML decompresser.
Documentation
use std::{cell::RefCell, collections::HashMap, rc::Rc};

use failure::{format_err, Error, ResultExt};
use log::error;

use crate::{
    chunks::{
        PackageWrapper, StringTableCache, StringTableWrapper, TableTypeWrapper, TypeSpecWrapper,
    },
    model::{
        owned::Entry, Entries, Identifier, Library as LibraryTrait, LibraryBuilder,
        Resources as ResourcesTrait, StringTable as StringTableTrait, TypeSpec as TypeSpecTrait,
    },
};

use super::{ChunkVisitor, Origin};

#[derive(Default, Debug)]
pub struct ModelVisitor<'a> {
    package_mask: u32,
    resources: Resources<'a>,
    current_spec: Option<TypeSpecWrapper<'a>>,
    tables: HashMap<Origin, StringTableCache<StringTableWrapper<'a>>>,
}

impl<'a> ModelVisitor<'a> {
    pub fn get_resources(&self) -> &'a Resources {
        &self.resources
    }

    pub fn get_mut_resources(&mut self) -> &'a mut Resources {
        &mut self.resources
    }
}

impl<'a> ChunkVisitor<'a> for ModelVisitor<'a> {
    fn visit_string_table(&mut self, string_table: StringTableWrapper<'a>, origin: Origin) {
        if let Origin::Global = origin {
            self.tables
                .insert(origin, StringTableCache::new(string_table));
        } else {
            let package_id = self.package_mask.get_package();

            let st_res = self
                .resources
                .get_mut_package(package_id)
                .and_then(|package| {
                    package.set_string_table(StringTableCache::new(string_table), origin);
                    Some(())
                });

            if st_res.is_none() {
                error!("Could not retrieve target package");
            }
        }
    }

    fn visit_package(&mut self, package: PackageWrapper<'a>) {
        if let Ok(package_id) = package.get_id() {
            self.package_mask = package_id << 24;

            let package_id = self.package_mask.get_package();
            let rp = Library::new(package);

            self.resources.push_package(package_id, rp);

            if self.tables.contains_key(&Origin::Global) {
                if let Some(st) = self.tables.remove(&Origin::Global) {
                    let set_result =
                        self.resources
                            .get_mut_package(package_id)
                            .and_then(|package| {
                                package.set_string_table(st, Origin::Global);

                                Some(())
                            });

                    if set_result.is_none() {
                        error!(
                            "could not set the string table because it refers to a non-existing \
                             package"
                        );
                    }
                }
            }
        }
    }

    fn visit_table_type(&mut self, table_type: TableTypeWrapper<'a>) {
        let mut entries = Entries::new();

        if let Some(ts) = &self.current_spec {
            let mask = ts
                .get_id()
                .and_then(|id| Ok(self.package_mask | (u32::from(id) << 16)))
                .unwrap_or(0);

            let entries_result = table_type.get_entries();

            match entries_result {
                Ok(ventries) => {
                    for e in &ventries {
                        let id = mask | e.get_id();

                        if !e.is_empty() {
                            entries.insert(id, e.clone());
                        }
                    }
                }
                Err(err) => error!("Error visiting table_type: {}", err),
            }
        }

        let package_id = self.package_mask.get_package();

        self.resources
            .get_mut_package(package_id)
            .and_then(|package| {
                package.add_entries(entries);
                Some(())
            });
    }

    fn visit_type_spec(&mut self, type_spec: TypeSpecWrapper<'a>) {
        self.current_spec = Some(type_spec.clone());
        let package_id = (self.package_mask >> 24) as u8;
        if let Some(package) = self.resources.get_mut_package(package_id) {
            let _ = package
                .add_type_spec(type_spec)
                .map_err(|e| error!("Could not add type spec: {}", e));
        } else {
            error!("Type spec refers to a non existing package");
        }
    }
}

pub type RefPackage<'a> = Rc<RefCell<Library<'a>>>;

#[derive(Default, Debug)]
pub struct Resources<'a> {
    packages: HashMap<u8, Library<'a>>,
    main_package: Option<u8>,
}

impl<'a> Resources<'a> {
    pub fn push_package(&mut self, package_id: u8, package: Library<'a>) {
        if self.packages.is_empty() {
            self.main_package = Some(package_id);
        }

        self.packages.insert(package_id, package);
    }
}

impl<'a> ResourcesTrait<'a> for Resources<'a> {
    type Library = Library<'a>;

    fn get_package(&self, package_id: u8) -> Option<&Self::Library> {
        self.packages.get(&package_id)
    }

    fn get_mut_package(&mut self, package_id: u8) -> Option<&mut Self::Library> {
        self.packages.get_mut(&package_id)
    }

    fn get_main_package(&self) -> Option<&Self::Library> {
        match self.main_package {
            Some(package_id) => match self.packages.get(&package_id) {
                Some(package) => Some(package),
                None => None,
            },
            _ => None,
        }
    }

    fn is_main_package(&self, package_id: u8) -> bool {
        match self.main_package {
            Some(pid) => pid == package_id,
            None => false,
        }
    }
}

#[derive(Debug)]
pub struct Library<'a> {
    package: PackageWrapper<'a>,
    specs: HashMap<u32, TypeSpecWrapper<'a>>,
    string_table: Option<StringTableCache<StringTableWrapper<'a>>>,
    spec_string_table: Option<StringTableCache<StringTableWrapper<'a>>>,
    entries_string_table: Option<StringTableCache<StringTableWrapper<'a>>>,
    entries: Entries,
}

impl<'a> Library<'a> {
    pub fn new(package: PackageWrapper<'a>) -> Self {
        Self {
            package,
            specs: HashMap::new(),
            string_table: None,
            spec_string_table: None,
            entries_string_table: None,
            entries: Entries::default(),
        }
    }

    fn get_spec_as_str(&self, spec_id: u32) -> Result<String, Error> {
        if self.specs.get(&(spec_id)).is_some() {
            if let Some(spec_string_table) = &self.spec_string_table {
                if let Ok(spec_str) = spec_string_table.get_string(spec_id - 1) {
                    return Ok((*spec_str).clone());
                }
            }
        }

        Err(format_err!("could not retrieve spec as string"))
    }
}

impl<'a> LibraryTrait for Library<'a> {
    fn get_name(&self) -> Option<String> {
        self.package.get_name().ok()
    }

    fn format_reference(
        &self,
        id: u32,
        key: u32,
        namespace: Option<String>,
        prefix: &str,
    ) -> Result<String, Error> {
        let spec_id = u32::from(id.get_spec());
        let spec_str = self
            .get_spec_as_str(spec_id)
            .context(format_err!("could not find spec: {}", spec_id))?;
        let string = self.get_entries_string(key).context(format_err!(
            "could not find key {} on entries string table",
            key
        ))?;

        let ending = if spec_str == "attr" {
            string
        } else {
            Rc::new(format!("{}/{}", spec_str, string))
        };

        match namespace {
            Some(ns) => Ok(format!("{}{}:{}", prefix, ns, ending)),
            None => Ok(format!("{}{}", prefix, ending)),
        }
    }

    fn get_entry(&self, id: u32) -> Result<&Entry, Error> {
        self.entries
            .get(&id)
            .ok_or_else(|| format_err!("could not find entry"))
    }

    fn get_entries_string(&self, str_id: u32) -> Result<Rc<String>, Error> {
        if let Some(string_table) = &self.entries_string_table {
            let out_string = string_table.get_string(str_id).context(format_err!(
                "could not find string {} on entries string table",
                str_id
            ))?;

            return Ok(out_string);
        }

        Err(format_err!("string not found on entries string table"))
    }

    fn get_spec_string(&self, str_id: u32) -> Result<Rc<String>, Error> {
        if let Some(string_table) = &self.spec_string_table {
            let out_string = string_table.get_string(str_id).context(format_err!(
                "could not find string {} on spec string table",
                str_id
            ))?;

            return Ok(out_string);
        }

        Err(format_err!("string not found on spec string table"))
    }
}

impl<'a> LibraryBuilder<'a> for Library<'a> {
    type StringTable = StringTableCache<StringTableWrapper<'a>>;
    type TypeSpec = TypeSpecWrapper<'a>;

    fn set_string_table(&mut self, string_table: Self::StringTable, origin: Origin) {
        match origin {
            Origin::Global => self.string_table = Some(string_table),
            Origin::Spec => self.spec_string_table = Some(string_table),
            Origin::Entries => self.entries_string_table = Some(string_table),
        }
    }

    fn add_entries(&mut self, entries: Entries) {
        self.entries.extend(entries);
    }

    fn add_type_spec(&mut self, type_spec: Self::TypeSpec) -> Result<(), Error> {
        let id = u32::from(type_spec.get_id()?);
        self.specs.insert(id, type_spec);

        Ok(())
    }
}