From 670dbaee7ca9f517235d09252f7bf516cecf5ca2 Mon Sep 17 00:00:00 2001 From: kieran Date: Wed, 12 Feb 2025 21:03:51 +0000 Subject: [PATCH] Parse manifest --- src/repo/mod.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 2bb240d..d013a8e 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,17 +1,21 @@ use crate::manifest::Manifest; use crate::repo::github::GithubRepo; use anyhow::{anyhow, bail, Context, Result}; +use apk::manifest::Sdk; +use apk::res::Chunk; use apk::AndroidManifest; use async_zip::tokio::read::seek::ZipFileReader; use async_zip::ZipFile; -use log::info; +use log::{debug, info, warn}; use nostr_sdk::async_utility::futures_util::TryStreamExt; use nostr_sdk::prelude::{hex, StreamExt}; use reqwest::Url; use semver::Version; use serde::Deserialize; use sha2::Digest; +use std::collections::HashMap; use std::env::temp_dir; +use std::io::Cursor; use std::path::{Path, PathBuf}; use tokio::fs::File; use tokio::io::{AsyncWriteExt, BufReader}; @@ -144,10 +148,9 @@ async fn load_apk_artifact(path: &Path) -> Result { }) .ok_or(anyhow!("missing AndroidManifest file"))?; let mut manifest = zip.reader_with_entry(idx).await?; - let mut manifest_data = String::with_capacity(8192); - manifest.read_to_string_checked(&mut manifest_data).await?; - info!("Successfully loaded AndroidManifest: {}", &manifest_data); - let manifest: AndroidManifest = quick_xml::de::from_str(&manifest_data)?; + let mut manifest_data = Vec::with_capacity(8192); + manifest.read_to_end_checked(&mut manifest_data).await?; + let manifest: AndroidManifest = parse_android_manifest(&manifest_data)?; Ok(RepoArtifact { name: path.file_name().unwrap().to_str().unwrap().to_string(), @@ -160,3 +163,95 @@ async fn load_apk_artifact(path: &Path) -> Result { metadata: ArtifactMetadata::APK { manifest }, }) } + +fn parse_android_manifest(data: &Vec) -> Result { + let chunks = if let Chunk::Xml(chunks) = Chunk::parse(&mut Cursor::new(data))? { + chunks + } else { + bail!("Invalid AndroidManifest file"); + }; + + let strings = if let Chunk::StringPool(strings, _) = &chunks[0] { + HashMap::from_iter( + strings + .iter() + .enumerate() + .map(|(i, s)| (s.to_string(), i as i32)), + ) + } else { + bail!("invalid manifest 1"); + }; + + let mut res = AndroidManifest::default(); + res.package = find_value_in(&strings, &chunks, "manifest", "package"); + res.version_code = + find_value_in(&strings, &chunks, "manifest", "versionCode").and_then(|v| v.parse().ok()); + res.version_name = find_value_in(&strings, &chunks, "manifest", "versionName"); + res.compile_sdk_version = find_value_in(&strings, &chunks, "manifest", "compileSdkVersion") + .and_then(|v| v.parse().ok()); + res.compile_sdk_version_codename = + find_value_in(&strings, &chunks, "manifest", "compileSdkVersionCodename") + .and_then(|v| v.parse().ok()); + res.platform_build_version_code = + find_value_in(&strings, &chunks, "manifest", "platformBuildVersionCode") + .and_then(|v| v.parse().ok()); + res.platform_build_version_name = + find_value_in(&strings, &chunks, "manifest", "platformBuildVersionName") + .and_then(|v| v.parse().ok()); + + res.sdk.min_sdk_version = + find_value_in(&strings, &chunks, "uses-sdk", "minSdkVersion").and_then(|v| v.parse().ok()); + res.sdk.target_sdk_version = find_value_in(&strings, &chunks, "uses-sdk", "targetSdkVersion") + .and_then(|v| v.parse().ok()); + res.sdk.max_sdk_version = + find_value_in(&strings, &chunks, "uses-sdk", "maxSdkVersion").and_then(|v| v.parse().ok()); + + res.application.theme = find_value_in(&strings, &chunks, "application", "theme"); + res.application.label = find_value_in(&strings, &chunks, "application", "label"); + res.application.icon = find_value_in(&strings, &chunks, "application", "icon"); + + Ok(res) +} + +fn find_value_in( + strings: &HashMap, + chunks: &Vec, + node: &str, + attr: &str, +) -> Option { + let idx_node = if let Some(i) = strings.get(node) { + *i + } else { + return None; + }; + + let idx_attr = if let Some(i) = strings.get(attr) { + *i + } else { + return None; + }; + + chunks.iter().find_map(|chunk| { + if let Chunk::XmlStartElement(_, el, attrs) = chunk { + match el.name { + x if x == idx_node => attrs.iter().find(|e| e.name == idx_attr).and_then(|e| { + debug!("{}, {}, {:?}", node, attr, e); + match e.typed_value.data_type { + 3 => strings + .iter() + .find(|(_, v)| **v == e.raw_value) + .map(|(k, _)| k.clone()), + 16 => Some(e.typed_value.data.to_string()), + _ => { + warn!("unknown data type {},{},{:?}", node, attr, e); + None + } + } + }), + _ => None, + } + } else { + None + } + }) +}