mod content;
mod header;
use flamberge_crypto::{des, digest};
use flamberge_formats::palmdb::PalmDb;
use flamberge_formats::{pmlz, FormatError};
use crate::{DecryptedBook, KeyStore, Result, SchemeError};
const BOOK_MAGIC: &[u8; 8] = b"PNRdPPrs";
const DICT_MAGIC: &[u8; 8] = b"PDctPPrs";
pub fn detect(data: &[u8]) -> bool {
PalmDb::parse(data)
.map(|db| &db.type_creator == BOOK_MAGIC || &db.type_creator == DICT_MAGIC)
.unwrap_or(false)
}
fn invalid(msg: impl Into<String>) -> SchemeError {
SchemeError::Format(FormatError::Invalid(msg.into()))
}
pub fn decrypt(input: &[u8], keys: &KeyStore) -> Result<DecryptedBook> {
let db = PalmDb::parse(input)?;
let is_book = &db.type_creator == BOOK_MAGIC;
if !is_book && &db.type_creator != DICT_MAGIC {
return Err(SchemeError::NotThisScheme);
}
let record0 = db
.record(input, 0)
.ok_or_else(|| invalid("eReader file has no record 0"))?;
let record1 = db
.record(input, 1)
.ok_or_else(|| invalid("eReader file has no record 1"))?;
let header = header::parse(record0, record1, is_book)?;
let content_key = keys
.ereader_keys
.iter()
.find_map(|user_key| {
let key = des::ecb_decrypt(&des::fix_key(user_key), &header.encrypted_key).ok()?;
(digest::sha1(&key) == header.encrypted_key_sha).then_some(key)
})
.ok_or(SchemeError::NoKeyWorked)?;
let pml = content::clean_pml(&content::extract_pml(&header, &db, input, &content_key)?);
let images = content::extract_images(&db, input, &header);
let bookname = {
let name = content::sanitize_filename(&db.name);
if name.is_empty() {
"book".to_string()
} else {
name
}
};
let data = pmlz::write(&format!("{bookname}.pml"), &pml, &images)?;
Ok(DecryptedBook {
data,
extension: "pmlz".to_string(),
title: Some(bookname),
})
}
#[cfg(test)]
mod tests;