rust-apk 1.0.0

Build, align, sign, verify, and inspect Android APKs in pure Rust
Documentation
//! Android binary XML (`AXML`): the compiled form of `AndroidManifest.xml`
//! and `res/xml/*` files.
//!
//! [`Document`] is a typed element tree. Build one in code, parse it from
//! text with [`Document::parse`], or decode a compiled file with
//! [`Document::decode`]; [`Document::encode`] compiles it, and its
//! [`Display`](std::fmt::Display) implementation prints readable XML.
//!
//! ```
//! use apk::android::attr;
//! use apk::xml::{Document, Element};
//!
//! let manifest = Element::new("manifest")
//!     .with_android_namespace()
//!     .with_attr("package", "org.example.app")
//!     .with_attr(attr::VERSION_CODE, 3)
//!     .with_child(Element::new("uses-sdk").with_attr(attr::MIN_SDK_VERSION, 24));
//! let document = Document::new(manifest);
//! let compiled = document.encode(&())?;
//! assert_eq!(Document::decode(&compiled)?, document);
//! # Ok::<(), apk::xml::XmlError>(())
//! ```

mod decode;
mod encode;
mod text;

use crate::android::{self, AttrDef, AttrFormat};
use crate::res::{ChunkError, ResourceId, ResourceRef, ResourceRefError, StringPoolError, Value};

pub use crate::android::NAMESPACE as ANDROID_NAMESPACE;

/// Why a binary or text XML document could not be processed.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum XmlError {
    #[error(transparent)]
    Chunk(#[from] ChunkError),
    #[error(transparent)]
    StringPool(#[from] StringPoolError),
    #[error("binary XML has no string pool before its first node")]
    MissingStringPool,
    #[error("string index {index} is outside the string pool")]
    StringOutOfRange { index: u32 },
    #[error("{kind} node at offset {offset:#x} is truncated")]
    TruncatedNode { kind: NodeKind, offset: usize },
    #[error("closing tag </{found}> does not match open element <{expected}>")]
    MismatchedEnd { expected: String, found: String },
    #[error("closing tag namespace {found:?} does not match open namespace {expected:?}")]
    MismatchedNamespace { expected: Option<String>, found: Option<String> },
    #[error("closing tag </{found}> has no open element")]
    UnexpectedEnd { found: String },
    #[error("{count} element(s) are never closed")]
    Unclosed { count: usize },
    #[error("document has no root element")]
    NoRoot,
    #[error("document has more than one root element")]
    MultipleRoots,
    #[error("elements nest deeper than {max} levels", max = MAX_DEPTH)]
    TooDeep,
    #[error("android:{name} is not a public framework attribute")]
    UnknownAttribute { name: String },
    #[error(transparent)]
    Reference(#[from] ResourceRefError),
    #[error("reference {reference} cannot be resolved")]
    UnresolvedReference { reference: ResourceRef },
    #[error("value {value:?} is not a valid {format:?} for android:{attribute}")]
    InvalidAttributeValue { attribute: &'static str, value: String, format: AttrFormat },
    #[error("android:{attribute} accepts {format:?}, not {value:?}")]
    MistypedValue { attribute: &'static str, value: Value, format: AttrFormat },
    #[error("{name} in {namespace} is an app-defined attribute, which this crate cannot compile")]
    AppAttribute { namespace: String, name: String },
    #[error("namespace prefix `{prefix}` is not declared")]
    UndeclaredPrefix { prefix: String },
    #[error("entity &{name}; is not defined (only the predefined XML entities are)")]
    UnknownEntity { name: String },
    #[error("XML syntax error: {0}")]
    Syntax(#[from] quick_xml::Error),
}

/// The binary XML node a [`XmlError::TruncatedNode`] refers to.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum NodeKind {
    Namespace,
    StartElement,
    Attribute,
    EndElement,
    Text,
}

impl std::fmt::Display for NodeKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            Self::Namespace => "namespace",
            Self::StartElement => "element start",
            Self::Attribute => "attribute",
            Self::EndElement => "element end",
            Self::Text => "text",
        })
    }
}

/// The deepest element nesting accepted when decoding, parsing, or encoding.
pub const MAX_DEPTH: usize = 512;

/// An XML document with a single root element.
#[derive(Clone, Debug, PartialEq)]
pub struct Document {
    pub root: Element,
}

impl Document {
    pub fn new(root: Element) -> Self {
        Self { root }
    }
}

/// An `xmlns:prefix="uri"` declaration.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Namespace {
    /// `None` for a default namespace (`xmlns="..."`).
    pub prefix: Option<String>,
    pub uri: String,
}

impl Namespace {
    /// `xmlns:android="http://schemas.android.com/apk/res/android"`.
    pub fn android() -> Self {
        Self { prefix: Some("android".into()), uri: ANDROID_NAMESPACE.into() }
    }
}

/// A child of an [`Element`].
#[derive(Clone, Debug, PartialEq)]
pub enum Node {
    Element(Element),
    Text(String),
}

/// An XML element.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Element {
    /// The local name, e.g. `activity`.
    pub name: String,
    /// The namespace URI of the element name, rarely used in Android XML.
    pub namespace: Option<String>,
    /// Namespaces declared on this element.
    pub namespaces: Vec<Namespace>,
    pub attributes: Vec<Attribute>,
    pub children: Vec<Node>,
}

/// An attribute's qualified name.
///
/// Framework attributes (`android:*`) carry their resource ID, which is what
/// Android actually matches on.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AttrName {
    pub namespace: Option<String>,
    pub name: String,
    pub id: Option<ResourceId>,
}

impl AttrName {
    /// An attribute in no namespace, such as `package`.
    pub fn plain(name: impl Into<String>) -> Self {
        Self { namespace: None, name: name.into(), id: None }
    }

    /// The framework attribute `def`.
    pub fn android(def: &AttrDef) -> Self {
        Self { namespace: Some(ANDROID_NAMESPACE.into()), name: def.name().into(), id: Some(def.id()) }
    }

    /// The framework attribute this name refers to, if it is public.
    pub fn android_def(&self) -> Option<&'static AttrDef> {
        if self.namespace.as_deref() != Some(ANDROID_NAMESPACE) {
            return None;
        }
        match self.id {
            Some(id) => android::attr_by_id(id),
            None => android::find_attr(&self.name),
        }
    }

    /// The resource ID Android matches this attribute by, if it has one.
    pub fn resource_id(&self) -> Option<ResourceId> {
        self.id.or_else(|| self.android_def().map(AttrDef::id))
    }

    /// The position of this attribute in an element: resource-ID attributes
    /// first in ascending ID order, then the rest by namespace and name.
    pub(crate) fn sort_key(&self) -> (bool, Option<ResourceId>, Option<&str>, &str) {
        let id = self.resource_id();
        (id.is_none(), id, self.namespace.as_deref(), &self.name)
    }

    fn matches(&self, namespace: Option<&str>, name: &str) -> bool {
        self.namespace.as_deref() == namespace && self.name == name
    }

    /// Whether `self` and `other` name the same attribute: the same resource
    /// ID (whatever the names, which obfuscators change), or the same name.
    fn same(&self, other: &Self) -> bool {
        match (self.resource_id(), other.resource_id()) {
            (Some(a), Some(b)) => a == b,
            _ => self.matches(other.namespace.as_deref(), &other.name),
        }
    }
}

impl From<&AttrDef> for AttrName {
    fn from(def: &AttrDef) -> Self {
        Self::android(def)
    }
}

impl From<&str> for AttrName {
    fn from(name: &str) -> Self {
        Self::plain(name)
    }
}

/// A name and a typed value.
#[derive(Clone, Debug, PartialEq)]
pub struct Attribute {
    pub name: AttrName,
    pub value: Value,
}

impl Element {
    pub fn new(name: impl Into<String>) -> Self {
        Self { name: name.into(), ..Self::default() }
    }

    /// Declares `xmlns:android` on this element.
    pub fn with_android_namespace(mut self) -> Self {
        self.namespaces.push(Namespace::android());
        self
    }

    /// Sets an attribute, replacing any previous value.
    pub fn with_attr(mut self, name: impl Into<AttrName>, value: impl Into<Value>) -> Self {
        self.set_attr(name, value);
        self
    }

    /// Sets an attribute when `value` is `Some`.
    pub fn with_opt_attr<V: Into<Value>>(mut self, name: impl Into<AttrName>, value: Option<V>) -> Self {
        if let Some(value) = value {
            self.set_attr(name, value);
        }
        self
    }

    pub fn with_child(mut self, child: Element) -> Self {
        self.children.push(Node::Element(child));
        self
    }

    pub fn with_children(mut self, children: impl IntoIterator<Item = Element>) -> Self {
        self.children.extend(children.into_iter().map(Node::Element));
        self
    }

    /// Sets an attribute, replacing the value of any attribute with the same
    /// resource ID or, without one, the same name.
    ///
    /// Attributes are kept in the order binary XML stores them: framework
    /// attributes by resource ID, then the rest by namespace and name.
    pub fn set_attr(&mut self, name: impl Into<AttrName>, value: impl Into<Value>) -> &mut Self {
        let name = name.into();
        let value = value.into();
        match self.attributes.iter_mut().find(|a| a.name.same(&name)) {
            Some(existing) => existing.value = value,
            None => {
                let key = name.sort_key();
                let index = self.attributes.partition_point(|a| a.name.sort_key() <= key);
                self.attributes.insert(index, Attribute { name, value });
            }
        }
        self
    }

    pub fn push_child(&mut self, child: Element) -> &mut Self {
        self.children.push(Node::Element(child));
        self
    }

    /// The value of the attribute `name` in `namespace`.
    pub fn attr(&self, namespace: Option<&str>, name: &str) -> Option<&Value> {
        self.attributes.iter().find(|a| a.name.matches(namespace, name)).map(|a| &a.value)
    }

    /// The value of the framework attribute `def`.
    pub fn android_attr(&self, def: &AttrDef) -> Option<&Value> {
        self.attributes
            .iter()
            .find(|a| a.name.id == Some(def.id()) || a.name.matches(Some(ANDROID_NAMESPACE), def.name()))
            .map(|a| &a.value)
    }

    /// Child elements.
    pub fn elements(&self) -> impl Iterator<Item = &Element> {
        self.children.iter().filter_map(|node| match node {
            Node::Element(element) => Some(element),
            Node::Text(_) => None,
        })
    }

    /// Child elements named `name`.
    pub fn elements_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Element> + 'a {
        self.elements().filter(move |e| e.name == name)
    }

    /// The first child element named `name`.
    pub fn element(&self, name: &str) -> Option<&Element> {
        self.elements().find(|e| e.name == name)
    }

    /// Mutable child elements.
    pub fn elements_mut(&mut self) -> impl Iterator<Item = &mut Element> {
        self.children.iter_mut().filter_map(|node| match node {
            Node::Element(element) => Some(element),
            Node::Text(_) => None,
        })
    }

    /// The first child element named `name`, for editing.
    pub fn element_mut(&mut self, name: &str) -> Option<&mut Element> {
        self.elements_mut().find(|e| e.name == name)
    }
}

/// A step of a depth-first walk over an element tree.
#[derive(Clone, Copy, Debug)]
pub(crate) enum Step<'a> {
    Open(&'a Element),
    Text(&'a str),
    Close(&'a Element),
}

/// Walks the tree under `root` depth-first without recursion, so trees of
/// any depth are safe to traverse. Each step comes with the depth of its
/// element, or of the parent of a text node; the root is at depth 0.
pub(crate) fn walk<'a>(root: &'a Element) -> impl Iterator<Item = (usize, Step<'a>)> {
    // Open elements, innermost last, each with the index of its next child.
    let mut open: Vec<(&'a Element, usize)> = Vec::new();
    let mut start = Some(root);
    std::iter::from_fn(move || {
        if let Some(root) = start.take() {
            open.push((root, 0));
            return Some((0, Step::Open(root)));
        }
        let depth = open.len().checked_sub(1)?;
        let (element, next) = open.last_mut()?;
        // Copy the tree reference out, so `open` is free to change below.
        let element = *element;
        let child = element.children.get(*next);
        *next += 1;
        Some(match child {
            Some(Node::Element(child)) => {
                open.push((child, 0));
                (depth + 1, Step::Open(child))
            }
            Some(Node::Text(text)) => (depth, Step::Text(text)),
            None => {
                open.pop();
                (depth, Step::Close(element))
            }
        })
    })
}