Skip to main content

iterm2_client/
validate.rs

1use crate::error::{Error, Result};
2
3const MAX_ID_LEN: usize = 256;
4const MAX_VEC_LEN: usize = 10_000;
5const MAX_TEXT_LEN: usize = 10 * 1024 * 1024; // 10MB
6
7/// Validate an iTerm2 object identifier (session, tab, window ID).
8/// Rejects null bytes and excessively long values.
9pub fn identifier(id: &str, kind: &str) -> Result<()> {
10    if id.len() > MAX_ID_LEN {
11        return Err(Error::Api(format!(
12            "{kind} ID too long ({} bytes, max {MAX_ID_LEN})",
13            id.len()
14        )));
15    }
16    if id.contains('\0') {
17        return Err(Error::Api(format!(
18            "{kind} ID contains null byte"
19        )));
20    }
21    Ok(())
22}
23
24/// Validate a vector parameter is within bounds.
25pub fn vec_len<T>(v: &[T], param: &str) -> Result<()> {
26    if v.len() > MAX_VEC_LEN {
27        return Err(Error::Api(format!(
28            "{param} too many elements ({}, max {MAX_VEC_LEN})",
29            v.len()
30        )));
31    }
32    Ok(())
33}
34
35/// Validate text length for send operations.
36pub fn text_len(text: &str) -> Result<()> {
37    if text.len() > MAX_TEXT_LEN {
38        return Err(Error::Api(format!(
39            "Text too long ({} bytes, max {MAX_TEXT_LEN})",
40            text.len()
41        )));
42    }
43    Ok(())
44}
45
46/// Validate that a string is syntactically valid JSON.
47pub fn json_value(value: &str) -> Result<()> {
48    serde_json::from_str::<serde_json::Value>(value).map_err(|e| {
49        Error::Api(format!("Invalid JSON value: {e}"))
50    })?;
51    Ok(())
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn valid_identifier() {
60        identifier("session-abc-123", "session").unwrap();
61        identifier("active", "session").unwrap();
62        identifier("all", "session").unwrap();
63    }
64
65    #[test]
66    fn identifier_with_null_byte() {
67        let err = identifier("session\0id", "session").unwrap_err();
68        assert!(err.to_string().contains("null byte"));
69    }
70
71    #[test]
72    fn identifier_too_long() {
73        let long_id = "x".repeat(MAX_ID_LEN + 1);
74        let err = identifier(&long_id, "session").unwrap_err();
75        assert!(err.to_string().contains("too long"));
76    }
77
78    #[test]
79    fn vec_within_bounds() {
80        vec_len(&vec![1, 2, 3], "ids").unwrap();
81    }
82
83    #[test]
84    fn vec_exceeds_bounds() {
85        let big: Vec<i32> = vec![0; MAX_VEC_LEN + 1];
86        let err = vec_len(&big, "ids").unwrap_err();
87        assert!(err.to_string().contains("too many"));
88    }
89
90    #[test]
91    fn text_within_bounds() {
92        text_len("hello world").unwrap();
93    }
94
95    #[test]
96    fn text_exceeds_bounds() {
97        let big = "x".repeat(MAX_TEXT_LEN + 1);
98        let err = text_len(&big).unwrap_err();
99        assert!(err.to_string().contains("too long"));
100    }
101
102    #[test]
103    fn valid_json_values() {
104        json_value(r#""hello""#).unwrap();
105        json_value("42").unwrap();
106        json_value("true").unwrap();
107        json_value("null").unwrap();
108        json_value(r#"{"key": "value"}"#).unwrap();
109        json_value(r#"[1, 2, 3]"#).unwrap();
110    }
111
112    #[test]
113    fn invalid_json_value() {
114        let err = json_value("not valid json").unwrap_err();
115        assert!(err.to_string().contains("Invalid JSON"));
116    }
117
118    #[test]
119    fn empty_string_is_invalid_json() {
120        let err = json_value("").unwrap_err();
121        assert!(err.to_string().contains("Invalid JSON"));
122    }
123}