apk-info-cli 1.0.13

A command-line tool to inspect and extract APK files
use std::path::{Path, PathBuf};

use anyhow::Result;
use apk_info::Apk;
use apk_info_zip::{CertificateInfo, Signature};
use colored::Colorize;
use serde::Serialize;

use crate::commands::path_helpers::get_all_files;

pub(crate) fn command_show(paths: &[PathBuf], show_signatures: &bool, jsonl: &bool) -> Result<()> {
    let files = get_all_files(paths);

    for (i, path) in files.iter().enumerate() {
        show(path, show_signatures, jsonl)?;

        // Add a newline between APKs except after the last one
        if i != files.len() - 1 {
            println!();
        }
    }

    Ok(())
}

fn show(path: &Path, show_signatures: &bool, jsonl: &bool) -> Result<()> {
    let info = match collect_apk_info(path, show_signatures) {
        Ok(v) => v,
        Err(e) => {
            println!("{:?} - {}", path, e.to_string().red());
            return Ok(());
        }
    };

    if *jsonl {
        print!("{}", serde_json::to_string(&info)?);
    } else {
        pretty_print(&info);
    }

    Ok(())
}

#[derive(Serialize)]
struct ApkInfo {
    pub package_name: String,
    pub version_name: String,
    pub version_code: String,
    pub main_activity: String,
    pub min_sdk_version: String,
    pub max_sdk_version: String,
    pub target_sdk_version: String,
    pub application_label: String,
    pub abis: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signatures: Option<Vec<Signature>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub container_signatures: Option<Vec<Signature>>,
}

fn collect_apk_info(path: &Path, show_signatures: &bool) -> Result<ApkInfo> {
    let apk = Apk::new(path)?;

    let signatures = if *show_signatures {
        Some(
            apk.get_signatures()?
                .into_iter()
                .filter(|s| !matches!(s, Signature::Unknown))
                .collect::<Vec<_>>(),
        )
    } else {
        None
    };

    let container_signatures = if *show_signatures {
        let sigs = apk.get_container_signatures()?;
        let filtered = sigs
            .into_iter()
            .filter(|s| !matches!(s, Signature::Unknown))
            .collect::<Vec<_>>();
        if filtered.is_empty() {
            None
        } else {
            Some(filtered)
        }
    } else {
        None
    };

    Ok(ApkInfo {
        package_name: apk.get_package_name().unwrap_or_else(|| "-".to_string()),
        version_name: apk.get_version_name().unwrap_or_else(|| "-".to_string()),
        version_code: apk.get_version_code().unwrap_or_else(|| "-".to_string()),
        main_activity: format!(
            "{}/{}",
            apk.get_package_name().unwrap_or_default(),
            apk.get_main_activity().unwrap_or("-")
        ),
        min_sdk_version: apk.get_min_sdk_version().unwrap_or_else(|| "-".to_string()),
        max_sdk_version: apk.get_max_sdk_version().unwrap_or_else(|| "-".to_string()),
        target_sdk_version: apk.get_target_sdk_version().to_string(),
        application_label: apk
            .get_application_label()
            .unwrap_or_else(|| "-".to_string()),
        abis: apk.get_supported_abis(),
        signatures,
        container_signatures,
    })
}

fn pretty_print(info: &ApkInfo) {
    println!("Package Name: {}", info.package_name.green(),);
    println!("Main Activity: {}", info.main_activity.green(),);
    println!("Min SDK Version: {}", info.min_sdk_version.green(),);
    println!("Max SDK Version: {}", info.max_sdk_version.green(),);
    println!("Target SDK Version: {}", info.target_sdk_version.green(),);
    println!("Application Label: {}", info.application_label.green(),);
    println!("Version Name: {}", info.version_name.green(),);
    println!("Version Code: {}", info.version_code.green(),);

    if !info.abis.is_empty() {
        println!("ABIs: {}", info.abis.join(", ").green());
    }

    if let Some(signatures) = &info.signatures {
        println!("{}:", "APK Signature block".blue().bold());
        print_signatures(signatures);
    }

    if let Some(signatures) = &info.container_signatures {
        println!("{}:", "Container Signature block".blue().bold());
        print_signatures(signatures);
    }
}

fn print_signatures(signatures: &[Signature]) {
    for (i, signature) in signatures.iter().enumerate() {
        match signature {
            Signature::V1(certificates)
            | Signature::V2(certificates)
            | Signature::V3(certificates)
            | Signature::V31(certificates) => {
                println!("  Type: {}", signature.name().green());

                for (j, certificate) in certificates.iter().enumerate() {
                    print_certificate(certificate);
                    if j != certificates.len() - 1 {
                        println!();
                    }
                }
            }
            Signature::StampBlockV1(certificate) | Signature::StampBlockV2(certificate) => {
                println!("  Type: {}", signature.name().green());
                print_certificate(certificate);
            }
            Signature::ApkChannelBlock(channel) => {
                println!("  Type: {}", signature.name().green());
                println!("  Channel: {}", channel.green());
            }
            Signature::PackerNextGenV2(data) => {
                let hex_string = data
                    .iter()
                    .map(|b| format!("{:02x}", b))
                    .collect::<Vec<_>>()
                    .join("");

                println!("  Type: {}", signature.name().green());
                println!("  Value: {}", hex_string.green());
            }
            Signature::GooglePlayFrosting => {
                println!("  Type: {}", signature.name().green());
                println!("  Info: {}", "Metadata exist".green());
            }
            Signature::VasDollyV2(channel) => {
                println!("  Type: {}", signature.name().green());
                println!("  Channel: {}", channel.green());
            }
            _ => continue,
        }

        if i != signatures.len() - 1 {
            println!();
        }
    }
}

fn print_certificate(certificate: &CertificateInfo) {
    println!("  Serial Number: {}", certificate.serial_number.green());
    println!("  Subject: {}", certificate.subject.green());
    println!("  Issuer: {}", certificate.issuer.green());
    println!("  Valid from: {}", certificate.valid_from.green());
    println!("  Valid until: {}", certificate.valid_until.green());
    println!("  Signature type: {}", certificate.signature_type.green());
    println!("  MD5 fingerprint: {}", certificate.md5_fingerprint.green());
    println!(
        "  SHA1 fingerprint: {}",
        certificate.sha1_fingerprint.green()
    );
    println!(
        "  SHA256 fingerprint: {}",
        certificate.sha256_fingerprint.green()
    );
}