1use keyhog_core::{Chunk, ChunkMetadata, Source, SourceError};
4use std::io::Read;
5
6const MAX_STDIN_BYTES: usize = 10 * 1024 * 1024;
9
10pub struct StdinSource;
22
23impl Source for StdinSource {
24 fn name(&self) -> &str {
25 "stdin"
26 }
27
28 fn chunks(&self) -> Box<dyn Iterator<Item = Result<Chunk, SourceError>> + '_> {
29 let stdin_read = read_stdin_limited(MAX_STDIN_BYTES);
30
31 Box::new(std::iter::once(match stdin_read {
32 Ok(data) => Ok(Chunk {
33 data: data.into(),
34 metadata: ChunkMetadata {
35 base_offset: 0,
36 source_type: "stdin".into(),
37 path: None,
38 commit: None,
39 author: None,
40 date: None,
41 mtime_ns: None,
42 size_bytes: None,
43 },
44 }),
45 Err(e) => Err(SourceError::Io(e)),
46 }))
47 }
48
49 fn as_any(&self) -> &dyn std::any::Any {
50 self
51 }
52}
53
54fn read_stdin_limited(max_bytes: usize) -> std::io::Result<String> {
55 read_to_string_limited(&mut std::io::stdin().lock(), max_bytes)
56}
57
58fn read_to_string_limited(reader: &mut impl Read, max_bytes: usize) -> std::io::Result<String> {
59 let mut bytes = Vec::new();
60 reader.take(max_bytes as u64 + 1).read_to_end(&mut bytes)?;
63
64 if bytes.len() > max_bytes {
65 return Err(std::io::Error::other(format!(
66 "stdin exceeds {} byte limit",
67 max_bytes
68 )));
69 }
70
71 String::from_utf8(bytes).map_err(|error| {
72 std::io::Error::new(
73 std::io::ErrorKind::InvalidData,
74 format!(
75 "stdin is not valid UTF-8 near byte {}: {error}",
76 error.utf8_error().valid_up_to()
77 ),
78 )
79 })
80}