use std::path::Path;
use boko::{Book, Format};
const EPUB: &[u8] = include_bytes!("fixtures/epictetus.epub");
const AZW3: &[u8] = include_bytes!("fixtures/epictetus.azw3");
const MOBI: &[u8] = include_bytes!("fixtures/epictetus.mobi");
const KFX: &[u8] = include_bytes!("fixtures/epictetus.kfx");
fn drive(data: &[u8], format: Format) {
let Ok(book) = Book::from_bytes(data, format) else {
return;
};
let _ = book.metadata();
let spine: Vec<_> = book.spine().to_vec();
for entry in &spine {
let _ = book.load_raw(entry.id);
let _ = book.load_chapter(entry.id);
}
let _ = book.resolve_links();
let assets: Vec<_> = book.list_assets().to_vec();
for asset in assets.iter().take(8) {
let _ = book.load_asset(asset);
}
let _ = book.load_asset("does/not/exist");
}
fn mutations(seed: &[u8]) -> Vec<Vec<u8>> {
let mut out = Vec::new();
out.push(Vec::new());
for len in [
1usize, 2, 4, 8, 12, 16, 17, 20, 24, 31, 32, 48, 64, 96, 128, 192, 256, 512, 1024, 4096,
] {
if len < seed.len() {
out.push(seed[..len].to_vec());
}
}
for frac in [2usize, 3, 4, 8, 16] {
out.push(seed[..seed.len() / frac].to_vec());
}
for &off in &[
0usize, 1, 4, 8, 16, 20, 24, 32, 64, 78, 100, 128, 256, 512, 1024,
] {
if off < seed.len() {
for &xor in &[0xFFu8, 0x01, 0x80, 0x7F] {
let mut m = seed.to_vec();
m[off] ^= xor;
out.push(m);
}
}
}
for len in [4usize, 33, 200] {
out.push(vec![0xABu8; len]);
}
out
}
fn fuzz_format(seed: &[u8], format: Format) {
for input in mutations(seed) {
drive(&input, format);
}
}
#[test]
fn epub_malformed_inputs_do_not_panic() {
fuzz_format(EPUB, Format::Epub);
}
#[test]
fn mobi_malformed_inputs_do_not_panic() {
fuzz_format(MOBI, Format::Mobi);
}
#[test]
fn azw3_malformed_inputs_do_not_panic() {
fuzz_format(AZW3, Format::Azw3);
}
#[test]
fn kfx_malformed_inputs_do_not_panic() {
fuzz_format(KFX, Format::Kfx);
}
#[test]
fn cross_format_bytes_do_not_panic() {
let seeds = [EPUB, AZW3, MOBI, KFX];
let formats = [Format::Epub, Format::Azw3, Format::Mobi, Format::Kfx];
for seed in seeds {
for format in formats {
drive(seed, format);
}
}
}
#[test]
fn committed_crash_corpus_does_not_panic() {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/crashes");
let Ok(entries) = std::fs::read_dir(&dir) else {
return; };
for entry in entries.flatten() {
let path = entry.path();
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
continue;
};
let format = if name.starts_with("epub") {
Format::Epub
} else if name.starts_with("azw3") {
Format::Azw3
} else if name.starts_with("mobi") {
Format::Mobi
} else if name.starts_with("kfx") {
Format::Kfx
} else {
continue;
};
if let Ok(data) = std::fs::read(&path) {
drive(&data, format);
}
}
}