openpack 0.2.2

Safe archive-reader for ZIP-derived formats (ZIP, CRX, JAR, APK, IPA) with BOM-safe checks.
Documentation
#[cfg(feature = "apk")]
use std::str::from_utf8;

#[cfg(feature = "crx")]
use crate::types::ExtensionManifestSummary;
use crate::types::PackageJsonSummary;

#[cfg(feature = "crx")]
pub(crate) fn summarize_extension_manifest(value: serde_json::Value) -> ExtensionManifestSummary {
    let permissions = value
        .get("permissions")
        .and_then(|value| value.as_array())
        .into_iter()
        .flatten()
        .filter_map(|value| value.as_str().map(ToOwned::to_owned))
        .collect::<Vec<_>>();
    let host_permissions = value
        .get("host_permissions")
        .and_then(|value| value.as_array())
        .into_iter()
        .flatten()
        .filter_map(|value| value.as_str().map(ToOwned::to_owned))
        .collect::<Vec<_>>();
    let background_scripts = value
        .get("background")
        .map(background_scripts_from_manifest)
        .unwrap_or_default();
    let content_scripts = value
        .get("content_scripts")
        .and_then(|value| value.as_array())
        .into_iter()
        .flatten()
        .flat_map(|group| {
            group
                .get("js")
                .and_then(|value| value.as_array())
                .into_iter()
                .flatten()
                .filter_map(|value| value.as_str().map(ToOwned::to_owned))
        })
        .collect::<Vec<_>>();

    ExtensionManifestSummary {
        name: value
            .get("name")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        version: value
            .get("version")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        manifest_version: value
            .get("manifest_version")
            .and_then(|value| value.as_u64()),
        permissions,
        host_permissions,
        background_scripts,
        content_scripts,
    }
}

#[cfg(feature = "crx")]
fn background_scripts_from_manifest(value: &serde_json::Value) -> Vec<String> {
    let mut scripts = value
        .get("scripts")
        .and_then(|value| value.as_array())
        .into_iter()
        .flatten()
        .filter_map(|value| value.as_str().map(ToOwned::to_owned))
        .collect::<Vec<_>>();
    if let Some(worker) = value
        .get("service_worker")
        .and_then(|value| value.as_str())
        .map(ToOwned::to_owned)
    {
        scripts.push(worker);
    }
    scripts
}

pub(crate) fn summarize_package_json(value: serde_json::Value) -> PackageJsonSummary {
    let dependencies = value
        .get("dependencies")
        .and_then(|value| value.as_object())
        .map(|deps| deps.keys().cloned().collect::<Vec<_>>())
        .unwrap_or_default();

    PackageJsonSummary {
        name: value
            .get("name")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        version: value
            .get("version")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        description: value
            .get("description")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        main: value
            .get("main")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        module: value
            .get("module")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        browser: value
            .get("browser")
            .and_then(|value| value.as_str())
            .map(ToOwned::to_owned),
        dependencies,
    }
}

#[cfg(feature = "apk")]
pub(crate) fn parse_android_manifest(bytes: &[u8]) -> Option<crate::AndroidManifest> {
    let xml = from_utf8(bytes).ok()?;
    let package = extract_xml_attr(xml, "package")?;
    Some(crate::AndroidManifest {
        package,
        version_name: extract_xml_attr(xml, "versionName"),
        version_code: extract_xml_attr(xml, "versionCode"),
        min_sdk: extract_block_attr(xml, "uses-sdk", "android:minSdkVersion")
            .or_else(|| extract_block_attr(xml, "uses-sdk", "android:targetSdkVersion")),
    })
}

#[cfg(feature = "apk")]
fn extract_xml_attr(xml: &str, attr: &str) -> Option<String> {
    let token = format!(" {}=\"", attr);
    let start = xml.find(&token)? + token.len();
    let rest = &xml[start..];
    let end = rest.find('"')?;
    Some(rest[..end].to_string())
}

#[cfg(feature = "apk")]
fn extract_block_attr(xml: &str, block: &str, attr: &str) -> Option<String> {
    let block_start = xml.find(&format!("<{}", block))?;
    let after_block = &xml[block_start..];
    let token = format!(" {}=\"", attr);
    let start = after_block.find(&token)? + block_start + token.len();
    let value_tail = &xml[start..];
    let end = value_tail.find('"')?;
    Some(value_tail[..end].to_string())
}

#[cfg(feature = "ipa")]
pub(crate) fn parse_info_plist(xml: &str) -> Option<crate::IpaInfoPlist> {
    Some(crate::IpaInfoPlist {
        bundle_identifier: parse_plist_key(xml, "CFBundleIdentifier"),
        bundle_version: parse_plist_key(xml, "CFBundleShortVersionString"),
        executable: parse_plist_key(xml, "CFBundleExecutable"),
    })
}

#[cfg(feature = "ipa")]
fn parse_plist_key(xml: &str, key: &str) -> Option<String> {
    let marker = format!("<key>{}</key>", key);
    let key_pos = xml.find(&marker)?;
    let start =
        xml[key_pos + marker.len()..].find("<string>")? + key_pos + marker.len() + "<string>".len();
    let value_tail = &xml[start..];
    let end = value_tail.find("</string>")?;
    Some(value_tail[..end].trim().to_string())
}