CyberChef CLI計画

昨日に引き続き、CyberChef的なCLIツールの話です。

chef

記事をPublishしてから体裁を整えたりURLのDecoderを書いたりしていました。

今日はURLの次によく使うHexを書きます。

From Hex

基本的にAutoで使っているので、自動でDelimiterを判定したい。

実装としては、全部のDelimiterでパーサを回して、パースできた長さが最大のものを返す方針。

ここで最近やってたClamAVのシグネチャをパースする時に学んだnomが活きるワケ。

と思ったけど、そんなややこしい事しなくてもDelimiterとPrefix全部消してから2文字ずつパースすればいいじゃん。

use anyhow::{anyhow, Result};

pub fn decode(input: &str) -> Result<String> {
    let delimiters = &[",", "\r", "\n", ";", ":", " ", "\t"];
    let prefixes = &["%", "0x", "x", "\\"];

    let mut cleaned_input = input.to_string();

    for delim in delimiters {
        cleaned_input = cleaned_input.replace(delim, "");
    }

    for prefix in prefixes {
        cleaned_input = cleaned_input.replace(prefix, "");
    }

    if cleaned_input.len() % 2 != 0 {
        return Err(anyhow!("Input length is not even: '{}'", cleaned_input));
    }

    let mut bytes = Vec::new();

    for (i, chunk) in cleaned_input.as_bytes().chunks(2).enumerate() {
        let hex_str = std::str::from_utf8(chunk)
            .map_err(|e| anyhow!("Failed to read chunk: {}", e))?;
        let byte = u8::from_str_radix(hex_str, 16)
            .map_err(|_| anyhow!("Failed to parse '{}' at index: {}. Successfully converted part: '{}'", hex_str, i, String::from_utf8(bytes.clone()).unwrap()))?;
        bytes.push(byte);
    }

    match String::from_utf8(bytes.clone()) {
        Ok(string) => Ok(string),
        Err(e) => {
            let valid_up_to = e.utf8_error().valid_up_to();
            let successful_string = String::from_utf8(bytes[0..valid_up_to].to_vec())
                .unwrap_or_default();
            Err(anyhow!(
                "UTF-8 conversion error: {}. Successfully converted part: '{}'",
                e,
                successful_string
            ))
        }
    }
}

こだわりポイントとしてはとにかくパースできた部分だけでも抽出できるようにした点ですね。

From Hexはこんな感じで良いとして、To Hexいるかな…

xxdとawkでなんとかなってるのであまりモチベは無いです。需要があればやります。

終わりに

この記事はn01e0 Advent Calendar 2024の22日目の記事とします。