provenant-cli 0.0.33

Rust-based ScanCode-compatible scanner for licenses, package metadata, SBOMs, and provenance data.
Documentation
// SPDX-FileCopyrightText: Provenant contributors
// SPDX-License-Identifier: Apache-2.0

use anyhow::{Result, anyhow};

use crate::parser_warn as warn;
use crate::parsers::utils::{MAX_ITERATION_COUNT, truncate_field};

use super::entry::IndexEntry;
use super::tags::{
    RPMTAG_ARCH, RPMTAG_BASENAMES, RPMTAG_DIRINDEXES, RPMTAG_DIRNAMES, RPMTAG_DISTRIBUTION,
    RPMTAG_EPOCH, RPMTAG_FILENAMES, RPMTAG_LICENSE, RPMTAG_NAME, RPMTAG_PLATFORM,
    RPMTAG_PROVIDENAME, RPMTAG_RELEASE, RPMTAG_REQUIRENAME, RPMTAG_SIZE, RPMTAG_SOURCERPM,
    RPMTAG_VENDOR, RPMTAG_VERSION, TagType,
};

#[derive(Debug, Default, Clone)]
pub(crate) struct InstalledRpmPackage {
    pub(crate) epoch: u32,
    pub(crate) name: String,
    pub(crate) version: String,
    pub(crate) release: String,
    pub(crate) arch: String,
    pub(crate) source_rpm: String,
    pub(crate) size: u32,
    pub(crate) license: String,
    pub(crate) vendor: String,
    pub(crate) distribution: String,
    pub(crate) platform: String,
    pub(crate) base_names: Vec<String>,
    pub(crate) dir_indexes: Vec<u32>,
    pub(crate) dir_names: Vec<String>,
    pub(crate) file_names: Vec<String>,
    pub(crate) provides: Vec<String>,
    pub(crate) requires: Vec<String>,
}

pub(crate) fn parse_installed_rpm_package(entries: Vec<IndexEntry>) -> Result<InstalledRpmPackage> {
    let mut package = InstalledRpmPackage::default();
    for (i, entry) in entries.into_iter().enumerate() {
        if i >= MAX_ITERATION_COUNT {
            warn!(
                "RPM entry iteration exceeded MAX_ITERATION_COUNT ({}), stopping",
                MAX_ITERATION_COUNT
            );
            break;
        }
        match entry.info.tag {
            RPMTAG_DIRINDEXES => {
                ensure_kind(&entry, TagType::Int32, "dir indexes")?;
                package.dir_indexes = entry.read_u32_array()?;
            }
            RPMTAG_DIRNAMES => {
                ensure_kind(&entry, TagType::StringArray, "dir names")?;
                package.dir_names = entry
                    .read_string_array()?
                    .into_iter()
                    .map(truncate_field)
                    .collect();
            }
            RPMTAG_BASENAMES => {
                ensure_kind(&entry, TagType::StringArray, "base names")?;
                package.base_names = entry
                    .read_string_array()?
                    .into_iter()
                    .map(truncate_field)
                    .collect();
            }
            RPMTAG_FILENAMES => {
                ensure_kind(&entry, TagType::StringArray, "file names")?;
                package.file_names = entry
                    .read_string_array()?
                    .into_iter()
                    .map(truncate_field)
                    .collect();
            }
            RPMTAG_NAME => {
                ensure_kind(&entry, TagType::String, "name")?;
                package.name = truncate_field(entry.read_string()?);
            }
            RPMTAG_EPOCH => {
                ensure_kind(&entry, TagType::Int32, "epoch")?;
                package.epoch = entry.read_u32()?;
            }
            RPMTAG_VERSION => {
                ensure_kind(&entry, TagType::String, "version")?;
                package.version = truncate_field(entry.read_string()?);
            }
            RPMTAG_RELEASE => {
                ensure_kind(&entry, TagType::String, "release")?;
                package.release = truncate_field(entry.read_string()?);
            }
            RPMTAG_ARCH => {
                ensure_kind(&entry, TagType::String, "arch")?;
                package.arch = truncate_field(entry.read_string()?);
            }
            RPMTAG_SOURCERPM => {
                ensure_kind(&entry, TagType::String, "source rpm")?;
                package.source_rpm = normalize_none(truncate_field(entry.read_string()?));
            }
            RPMTAG_LICENSE => {
                ensure_kind(&entry, TagType::String, "license")?;
                package.license = normalize_none(truncate_field(entry.read_string()?));
            }
            RPMTAG_VENDOR => {
                ensure_kind(&entry, TagType::String, "vendor")?;
                package.vendor = normalize_none(truncate_field(entry.read_string()?));
            }
            RPMTAG_DISTRIBUTION => {
                ensure_kind(&entry, TagType::String, "distribution")?;
                package.distribution = normalize_none(truncate_field(entry.read_string()?));
            }
            RPMTAG_PLATFORM => {
                ensure_kind(&entry, TagType::String, "platform")?;
                package.platform = normalize_none(entry.read_string()?);
            }
            RPMTAG_SIZE => {
                ensure_kind(&entry, TagType::Int32, "size")?;
                package.size = entry.read_u32()?;
            }
            RPMTAG_PROVIDENAME => {
                ensure_kind(&entry, TagType::StringArray, "provide names")?;
                package.provides = entry.read_string_array()?;
            }
            RPMTAG_REQUIRENAME => {
                ensure_kind(&entry, TagType::StringArray, "require names")?;
                package.requires = entry.read_string_array()?;
            }
            _ => {}
        }
    }

    Ok(package)
}

fn ensure_kind(entry: &IndexEntry, expected: TagType, label: &str) -> Result<()> {
    if entry.info.kind != expected {
        return Err(anyhow!(
            "invalid RPM tag type for {}: expected={:?}, actual={:?}",
            label,
            expected,
            entry.info.kind
        ));
    }
    Ok(())
}

fn normalize_none(value: String) -> String {
    if value == "(none)" {
        String::new()
    } else {
        value
    }
}