1use anyhow::{bail, Result};
7use std::fs::File;
8use std::io::{Read, Seek, SeekFrom};
9use std::path::Path;
10
11pub struct BoundedFile {
15 file: File,
16 start: u64,
17 len: u64,
18 pos: u64,
19 xor_key: Option<u8>,
20}
21
22impl BoundedFile {
23 pub fn open<P: AsRef<Path>>(
24 path: P,
25 start: u64,
26 len: u64,
27 xor_key: Option<u8>,
28 ) -> Result<Self> {
29 let mut file = File::open(path)?;
30 file.seek(SeekFrom::Start(start))?;
31 Ok(Self {
32 file,
33 start,
34 len,
35 pos: 0,
36 xor_key,
37 })
38 }
39
40 pub fn remaining(&self) -> u64 {
41 self.len.saturating_sub(self.pos)
42 }
43
44 pub fn into_inner(self) -> File {
45 self.file
46 }
47
48 pub fn read_all(mut self) -> Result<Vec<u8>> {
50 let mut out = Vec::with_capacity(self.len.min(256 * 1024) as usize);
51 self.seek(SeekFrom::Start(0))?;
52 self.read_to_end(&mut out)?;
53 Ok(out)
54 }
55}
56
57impl Read for BoundedFile {
58 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
59 if self.pos >= self.len {
60 return Ok(0);
61 }
62 let max = (self.len - self.pos) as usize;
63 let to_read = buf.len().min(max);
64 let n = self.file.read(&mut buf[..to_read])?;
65 if let Some(k) = self.xor_key {
66 for b in &mut buf[..n] {
67 *b ^= k;
68 }
69 }
70 self.pos += n as u64;
71 Ok(n)
72 }
73}
74
75impl Seek for BoundedFile {
76 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
77 let new_pos: i128 = match pos {
78 SeekFrom::Start(x) => x as i128,
79 SeekFrom::Current(x) => self.pos as i128 + x as i128,
80 SeekFrom::End(x) => self.len as i128 + x as i128,
81 };
82 if new_pos < 0 {
83 return Err(std::io::Error::new(
84 std::io::ErrorKind::InvalidInput,
85 "negative seek",
86 ));
87 }
88 let new_pos_u = new_pos as u64;
89 if new_pos_u > self.len {
90 return Err(std::io::Error::new(
91 std::io::ErrorKind::InvalidInput,
92 "seek past end of bounded region",
93 ));
94 }
95 self.file.seek(SeekFrom::Start(self.start + new_pos_u))?;
96 self.pos = new_pos_u;
97 Ok(self.pos)
98 }
99}
100
101pub fn xor_file_to_vec<P: AsRef<Path>>(path: P, key: u8) -> Result<Vec<u8>> {
105 let mut f = File::open(path)?;
106 let mut v = Vec::new();
107 f.read_to_end(&mut v)?;
108 for b in &mut v {
109 *b ^= key;
110 }
111 if v.len() >= 4 && &v[..4] != b"OggS" {
113 }
117 Ok(v)
118}
119
120pub fn looks_like_ogg(buf: &[u8]) -> bool {
122 buf.len() >= 4 && &buf[..4] == b"OggS"
123}
124
125pub fn validate_subrange(file_len: u64, offset: u64, size: u64) -> Result<()> {
127 if offset > file_len {
128 bail!("subrange offset {offset} exceeds file length {file_len}");
129 }
130 if size == 0 {
131 return Ok(());
133 }
134 if offset.saturating_add(size) > file_len {
135 bail!("subrange [offset={offset}, size={size}] exceeds file length {file_len}");
136 }
137 Ok(())
138}