pub mod file_operations {
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::Path;
pub fn read_file<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
fs::read(path)
}
pub fn write_file<P: AsRef<Path>>(path: P, data: &[u8]) -> io::Result<()> {
File::create(path)?.write_all(data)
}
}
pub mod pdf_parser {
pub fn find_eof_end(pdf: &[u8]) -> Option<usize> {
let eof_marker = b"%%EOF";
pdf.windows(eof_marker.len())
.rposition(|window| window == eof_marker)
.map(|pos| pos + eof_marker.len())
}
}
pub mod payload_operations {
use base64::{Engine as _, engine::general_purpose};
use std::io;
pub fn encode_payload(pdf_data: &[u8], eof_end: usize, payload: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(pdf_data.len() + payload.len() + 64);
out.extend_from_slice(&pdf_data[..eof_end]);
out.extend_from_slice(general_purpose::STANDARD.encode(payload).as_bytes());
out
}
pub fn extract_payload(data: &[u8]) -> io::Result<Vec<u8>> {
let eof_marker = b"%%EOF";
let eof_pos = data
.windows(eof_marker.len())
.rposition(|w| w == eof_marker)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"%%EOF marker not found – this doesn't appear to be a valid PDF file.",
)
})?;
let payload_start = eof_pos + eof_marker.len();
let payload = &data[payload_start..];
let payload = payload
.iter()
.skip_while(|&&b| b == b'\n' || b == b'\r' || b == b' ' || b == b'\t')
.copied()
.collect::<Vec<u8>>();
if payload.is_empty() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"No payload found after %%EOF marker – this PDF doesn't contain hidden data.",
));
}
general_purpose::STANDARD.decode(&payload).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"Failed to decode base64 payload. The data might be corrupted.",
)
})
}
}
pub mod operations {
use crate::file_operations::{read_file, write_file};
use crate::payload_operations::{encode_payload, extract_payload};
use crate::pdf_parser::find_eof_end;
use std::io::{self, Write};
use std::path::Path;
pub fn embed(src_pdf: &Path, payload: &Path, out_pdf: &Path) -> io::Result<()> {
let pdf_bytes = read_file(src_pdf)?;
let eof_end = find_eof_end(&pdf_bytes).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"Could not locate a '%%EOF' marker in the source PDF. Are you sure this is a valid PDF file?",
)
})?;
let payload_bytes = read_file(payload)?;
let output_data = encode_payload(&pdf_bytes, eof_end, &payload_bytes);
write_file(out_pdf, &output_data)?;
println!(
"✅ Embedded {} bytes into '{}', saved as '{}'.",
payload_bytes.len(),
src_pdf.display(),
out_pdf.display()
);
Ok(())
}
pub fn extract(src_pdf: &Path, out_bin: &Path) -> io::Result<()> {
let data = read_file(src_pdf)?;
let decoded_payload = extract_payload(&data)?;
if out_bin.as_os_str() == "-" {
let stdout = io::stdout();
let mut handle = stdout.lock();
handle.write_all(&decoded_payload)?;
} else {
write_file(out_bin, &decoded_payload)?;
println!(
"✅ Extracted {} bytes from '{}', saved as '{}'.",
decoded_payload.len(),
src_pdf.display(),
out_bin.display()
);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::payload_operations::{encode_payload, extract_payload};
use crate::pdf_parser::find_eof_end;
#[test]
fn test_find_eof_end() {
let pdf_data = b"Some PDF content\n%%EOF\nExtra data";
let result = find_eof_end(pdf_data);
assert_eq!(result, Some(22)); }
#[test]
fn test_find_eof_end_multiple() {
let pdf_data = b"%%EOF\nSome content\n%%EOF\n";
let result = find_eof_end(pdf_data);
assert_eq!(result, Some(24)); }
#[test]
fn test_find_eof_end_not_found() {
let pdf_data = b"Some PDF content without EOF marker";
let result = find_eof_end(pdf_data);
assert_eq!(result, None); }
#[test]
fn test_encode_and_extract_payload() {
let pdf_data = b"PDF content\n%%EOF";
let eof_end = 17; let payload = b"Hello, World!";
let encoded = encode_payload(pdf_data, eof_end, payload);
let extracted = extract_payload(&encoded).unwrap();
assert_eq!(extracted, payload);
}
#[test]
fn test_extract_payload_missing_eof() {
let data = b"PDF content without EOF marker";
let result = extract_payload(data);
assert!(result.is_err()); }
#[test]
fn test_extract_payload_invalid_base64() {
let data = b"PDF content\n%%EOFInvalidBase64!!!";
let result = extract_payload(data);
assert!(result.is_err()); }
}