mdict-rs 0.1.4

Library-first Rust parser for MDict .mdx and .mdd dictionaries
Documentation
use std::fs;
use std::panic;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

use mdict_rs::{MdxFile, checksum::adler32, compression::decode_block};

#[test]
fn mutated_compression_blocks_do_not_panic() {
    let block = zlib_block(b"hello mdict");
    for index in 0..block.len() {
        let mut mutated = block.clone();
        mutated[index] ^= 0x5a;
        let result = panic::catch_unwind(|| {
            let _ = decode_block("mutated zlib block", &mutated, 11);
        });
        assert!(result.is_ok(), "decode_block panicked at byte {index}");
    }
}

fn zlib_block(data: &[u8]) -> Vec<u8> {
    let mut out = Vec::new();
    out.extend_from_slice(&[2, 0, 0, 0]);
    out.extend_from_slice(&adler32(data).to_be_bytes());
    out.extend_from_slice(&miniz_oxide::deflate::compress_to_vec_zlib(data, 6));
    out
}

#[test]
fn mutated_header_files_do_not_panic() {
    let bytes = minimal_header_only_mdx();
    for index in 0..bytes.len() {
        let mut mutated = bytes.clone();
        mutated[index] ^= 0xa5;
        let path = write_temp_file("mutated-header", &mutated);
        let result = panic::catch_unwind(|| {
            let _ = MdxFile::open(&path);
        });
        fs::remove_file(path).ok();
        assert!(result.is_ok(), "MdxFile::open panicked at byte {index}");
    }
}

fn minimal_header_only_mdx() -> Vec<u8> {
    let xml = "<Dictionary GeneratedByEngineVersion=\"2.0\" RequiredEngineVersion=\"2.0\" Encoding=\"UTF-8\"/>";
    let utf16 = utf16le(xml);
    let mut bytes = Vec::new();
    bytes.extend_from_slice(&(utf16.len() as u32).to_be_bytes());
    bytes.extend_from_slice(&utf16);
    bytes.extend_from_slice(&adler32(&utf16).to_le_bytes());
    bytes
}

fn utf16le(text: &str) -> Vec<u8> {
    let mut out = Vec::new();
    for unit in text.encode_utf16() {
        out.extend_from_slice(&unit.to_le_bytes());
    }
    out
}

fn write_temp_file(prefix: &str, bytes: &[u8]) -> PathBuf {
    let unique = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let path = std::env::temp_dir().join(format!("{prefix}-{unique}.mdx"));
    fs::write(&path, bytes).unwrap();
    path
}