Parse manifest

This commit is contained in:
kieran 2025-02-12 21:03:51 +00:00
parent f90187c914
commit 670dbaee7c
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941

View File

@ -1,17 +1,21 @@
use crate::manifest::Manifest; use crate::manifest::Manifest;
use crate::repo::github::GithubRepo; use crate::repo::github::GithubRepo;
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use apk::manifest::Sdk;
use apk::res::Chunk;
use apk::AndroidManifest; use apk::AndroidManifest;
use async_zip::tokio::read::seek::ZipFileReader; use async_zip::tokio::read::seek::ZipFileReader;
use async_zip::ZipFile; use async_zip::ZipFile;
use log::info; use log::{debug, info, warn};
use nostr_sdk::async_utility::futures_util::TryStreamExt; use nostr_sdk::async_utility::futures_util::TryStreamExt;
use nostr_sdk::prelude::{hex, StreamExt}; use nostr_sdk::prelude::{hex, StreamExt};
use reqwest::Url; use reqwest::Url;
use semver::Version; use semver::Version;
use serde::Deserialize; use serde::Deserialize;
use sha2::Digest; use sha2::Digest;
use std::collections::HashMap;
use std::env::temp_dir; use std::env::temp_dir;
use std::io::Cursor;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tokio::fs::File; use tokio::fs::File;
use tokio::io::{AsyncWriteExt, BufReader}; use tokio::io::{AsyncWriteExt, BufReader};
@ -144,10 +148,9 @@ async fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
}) })
.ok_or(anyhow!("missing AndroidManifest file"))?; .ok_or(anyhow!("missing AndroidManifest file"))?;
let mut manifest = zip.reader_with_entry(idx).await?; let mut manifest = zip.reader_with_entry(idx).await?;
let mut manifest_data = String::with_capacity(8192); let mut manifest_data = Vec::with_capacity(8192);
manifest.read_to_string_checked(&mut manifest_data).await?; manifest.read_to_end_checked(&mut manifest_data).await?;
info!("Successfully loaded AndroidManifest: {}", &manifest_data); let manifest: AndroidManifest = parse_android_manifest(&manifest_data)?;
let manifest: AndroidManifest = quick_xml::de::from_str(&manifest_data)?;
Ok(RepoArtifact { Ok(RepoArtifact {
name: path.file_name().unwrap().to_str().unwrap().to_string(), name: path.file_name().unwrap().to_str().unwrap().to_string(),
@ -160,3 +163,95 @@ async fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
metadata: ArtifactMetadata::APK { manifest }, metadata: ArtifactMetadata::APK { manifest },
}) })
} }
fn parse_android_manifest(data: &Vec<u8>) -> Result<AndroidManifest> {
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<String, i32>,
chunks: &Vec<Chunk>,
node: &str,
attr: &str,
) -> Option<String> {
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
}
})
}