Writing an API with Rust to parse and extract info from Apk

Writing an API with Rust to parse and extract info from Apk

Rust • Jul 24 2022

As I said in the last post, let’s write an API and extract information from Apk. I’m going to write It with Rust Programming Language and Rocket framework with some packages which are explained further in this post. the code Is uploaded to Github and everybody can fork/clone and use It or add futures to It.

This is the information we want to extract:

pub struct ApkParsedInfo {
    pub package_name: String,
    pub version_code: String,
    pub version_name: String,
    pub min_sdk_version: String,
    pub target_sdk_version: String,
    pub compile_sdk_version: String,
    pub compile_sdk_version_code_name: String,
    pub permissions: Vec<String>
}

Our API has just one endpoint to POST an Apk, so let’s write the main function and the upload endpoint:

#[derive(FromForm)]
struct Upload<'r> {
    apk: TempFile<'r>,
}

#[post("/", data = "<upload>")]
async fn upload(upload: Form<Upload<'_>>) -> status::Custom<String> {
    let path = upload.apk.path().unwrap().to_path_buf();
    if let Some(info) = parser::parse(&path).await {
        let string_json= serde_json::to_string(&info);
        if string_json.is_ok() {
            return status::Custom(Status::Accepted,string_json.unwrap());
        }
    }
    status::Custom(Status::UnprocessableEntity, "oops!".to_owned())
}

#[rocket::main]
async fn main() -> Result<(), rocket::Error> {
    let _rocket = rocket::build()
        .mount("/", routes![upload])
        .launch()
        .await
        .unwrap();

    Ok(())
}

Now after POST an Apk to the API, we call a function parser::parse(&path) and pass the Path of the uploaded file, and the base of our app going to parse It and return a Option that tells us If the operation succeed or not. let’s look at parse the function:

pub async fn parse(path: &PathBuf) -> Option<ApkParsedInfo> {
    let file = std::fs::File::open(path).unwrap();
    let mut file_content: Vec<u8> = Vec::new();
    let mut archive = zip::ZipArchive::new(file).unwrap();
    for i in 0..archive.len() {
        let mut inner_file = archive.by_index(i).unwrap();
        if inner_file.name() == "AndroidManifest.xml" {
            inner_file.read_to_end(&mut file_content).unwrap();
            break;
        }
    }

    let xml = axml::extract_xml(file_content);
    parse_to_info(xml)
}

As I earlier said, the Apk files are Archives then we can open them with libraries to work with ZIP files. we use zip crate to access archive files and extract contents of AndroidManifest.xml. after finding it, we have to decode binary XML So I use the axml crate which helps us in this purpose.

As we have the String content, let’s explore the tags of the XML and get the information we want from It. for that I’m going to use xml-rs crate. There are three Important tags to extract the values we want, Let’s look at the code:

fn parse_to_info(content: String) -> Option<ApkParsedInfo> {
    let mut apk_info = ApkParsedInfo::new();

    let reader = EventReader::from_str(content.as_str());
    for e in reader {
        match e {
            Ok(XmlEvent::StartElement { name, attributes, namespace: _ }) => {
                match name.local_name.as_str() {
                    "manifest" => {
                        for attribute in attributes {
                            let attr = attribute.name.to_string();

                            if attr.contains("versionCode") {
                                apk_info.version_code = attribute.value;
                            } else if attr.contains("versionName") {
                                apk_info.version_name = attribute.value;
                            } else if attr.contains("compileSdkVersionCodename") {
                                apk_info.compile_sdk_version_code_name = attribute.value;
                            } else if attr.contains("compileSdkVersion") {
                                apk_info.compile_sdk_version = attribute.value;
                            } else if attr.contains("package") {
                                apk_info.package_name = attribute.value;
                            }
                        }
                    }

                    "uses-sdk" => {
                        for attribute in attributes {
                            let attr = attribute.name.to_string();

                            if attr.contains("minSdkVersion") {
                                apk_info.min_sdk_version = attribute.value;
                            } else if attr.contains("targetSdkVersion") {
                                apk_info.target_sdk_version = attribute.value;
                            }
                        }
                    }

                    "uses-permission" => {
                        for attribute in attributes {
                            if attribute.name.to_string().contains("name") {
                                apk_info.permissions.push(attribute.value)
                            }
                        }
                    }

                    _ => {}
                }

            }

            Err(_) => {
                return None;
            }

            _ => {}
        }
    }

    Some(apk_info)
}

So the tags we are interested in are:

  • manifest that includes:
* PackageName
* VersionCode
* VersionName
* CompileSdkVersion
* CompileSdkVersionCodename
  • uses-sdk that includes:
* minSdkVersion
* targetSdkVersion
  • uses-permission That uses for permissions of the application.

After getting this information we return the ApkInfo struct and convert It to JSON using serde-json crate and serve It to the request as a response.

Now let’s test our API, I’m going to use curl to request:

Writing an API with Rust to parse and extract info from Apk

And the result Is:

Writing an API with Rust to parse and extract info from Apk

That’s It, we’re done.

Github - ItsBenyaamin/apk-parser

an API for parsing apk files and extract manifest information from it

Github • ItsBenyaamin
Apk Parser repository

Thanks for reading this Article, Good Luck.

Benyamin Eskandari

Curious, Experienced Android Developer Also in love with Rust, DevOps, and System Programming AND I do Back-End and Front-End too. Trying to read a lot of books and learn a lot of things, I will write posts about my path on this journey.

Comments