1use simoom_qpack::Field;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub struct Validated {
8 pub content_length: Option<u64>,
9 pub informational: bool,
11}
12
13pub fn pseudo<'a>(fields: &'a [Field], name: &[u8]) -> Option<&'a [u8]> {
15 fields.iter().find(|f| f.name == name).map(|f| &f.value[..])
16}
17
18fn tchar_lower(c: u8) -> bool {
20 c.is_ascii_lowercase() || c.is_ascii_digit() || b"!#$%&'*+-.^_`|~".contains(&c)
21}
22
23fn valid_name(n: &[u8]) -> bool {
24 !n.is_empty() && n.iter().all(|&c| tchar_lower(c))
25}
26
27fn valid_method(m: &[u8]) -> bool {
28 !m.is_empty() && m.iter().all(|&c| tchar_lower(c.to_ascii_lowercase()))
29}
30
31fn valid_value(v: &[u8]) -> bool {
32 if let (Some(&a), Some(&b)) = (v.first(), v.last()) {
33 if a == b' ' || a == b'\t' || b == b' ' || b == b'\t' {
34 return false;
35 }
36 }
37 v.iter().all(|&c| c != 0 && c != b'\r' && c != b'\n')
38}
39
40fn regular(fields: &[Field], start: usize) -> Result<Option<u64>, ()> {
42 let mut cl: Option<u64> = None;
43 for f in &fields[start..] {
44 if f.name.first() == Some(&b':') || !valid_name(&f.name) || !valid_value(&f.value) {
45 return Err(());
46 }
47 match &f.name[..] {
48 b"connection" | b"keep-alive" | b"proxy-connection" | b"transfer-encoding" | b"upgrade" => return Err(()),
49 b"te" if f.value != b"trailers" => return Err(()),
50 b"content-length" => {
51 if f.value.is_empty() || f.value.len() > 19 || !f.value.iter().all(u8::is_ascii_digit) {
52 return Err(());
53 }
54 let v: u64 = std::str::from_utf8(&f.value).map_err(|_| ())?.parse().map_err(|_| ())?;
55 if cl.is_some_and(|c| c != v) {
56 return Err(());
57 }
58 cl = Some(v);
59 }
60 _ => {}
61 }
62 }
63 Ok(cl)
64}
65
66pub fn validate_request(fields: &[Field]) -> Result<Validated, ()> {
70 let (mut method, mut scheme, mut path, mut authority) = (None, None, None, None);
71 let mut protocol = false;
72 let mut i = 0;
73 while i < fields.len() && fields[i].name.first() == Some(&b':') {
74 let f = &fields[i];
75 i += 1;
76 if !valid_value(&f.value) {
77 return Err(());
78 }
79 let slot = match &f.name[..] {
80 b":method" => &mut method,
81 b":scheme" => &mut scheme,
82 b":path" => &mut path,
83 b":authority" => &mut authority,
84 b":protocol" => {
85 if protocol {
86 return Err(());
87 }
88 protocol = true;
89 continue;
90 }
91 _ => return Err(()),
92 };
93 if slot.is_some() {
94 return Err(());
95 }
96 *slot = Some(&f.value[..]);
97 }
98 let method: &[u8] = method.ok_or(())?;
99 if !valid_method(method) {
100 return Err(());
101 }
102 let connect = method == b"CONNECT";
103 if connect && !protocol {
104 if scheme.is_some() || path.is_some() || authority.is_none_or(|a| a.is_empty()) {
105 return Err(());
106 }
107 } else {
108 if scheme.is_none_or(|s| s.is_empty()) || path.is_none_or(|p| p.is_empty()) {
109 return Err(());
110 }
111 if protocol && !connect {
112 return Err(());
113 }
114 }
115 let content_length = regular(fields, i)?;
116 Ok(Validated { content_length, informational: false })
117}
118
119pub fn validate_response(fields: &[Field]) -> Result<Validated, ()> {
121 let mut status = None;
122 let mut i = 0;
123 while i < fields.len() && fields[i].name.first() == Some(&b':') {
124 if fields[i].name != b":status" || status.is_some() {
125 return Err(());
126 }
127 status = Some(&fields[i].value[..]);
128 i += 1;
129 }
130 let s = status.ok_or(())?;
131 if s.len() != 3 || !s.iter().all(u8::is_ascii_digit) || s[0] == b'0' {
132 return Err(());
133 }
134 let content_length = regular(fields, i)?;
135 Ok(Validated { content_length, informational: s[0] == b'1' })
136}
137
138pub fn validate_trailers(fields: &[Field]) -> Result<(), ()> {
140 regular(fields, 0).map(|_| ())
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 fn f(list: &[(&str, &str)]) -> Vec<Field> {
148 list.iter().map(|(n, v)| Field::new(n.as_bytes(), v.as_bytes())).collect()
149 }
150
151 #[test]
152 fn request_rules() {
153 let ok = f(&[(":method", "GET"), (":scheme", "https"), (":authority", "x"), (":path", "/"), ("accept", "*/*")]);
154 assert!(validate_request(&ok).is_ok());
155 let bad = [
156 f(&[(":method", "GET"), (":path", "/")]),
157 f(&[(":method", "GET"), (":scheme", "https"), (":path", "")]),
158 f(&[(":method", "GET"), (":method", "GET"), (":scheme", "h"), (":path", "/")]),
159 f(&[(":method", "GET"), ("a", "b"), (":scheme", "h"), (":path", "/")]),
160 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), (":status", "200")]),
161 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), ("Upper", "x")]),
162 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), ("connection", "close")]),
163 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), ("te", "gzip")]),
164 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), ("x", " lead")]),
165 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), ("x", "a\r\nb")]),
166 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), ("content-length", "1x")]),
167 f(&[(":method", "GET"), (":scheme", "h"), (":path", "/"), ("content-length", "1"), ("content-length", "2")]),
168 f(&[(":method", "CONNECT"), (":authority", "x"), (":path", "/")]),
169 f(&[(":method", "GET"), (":protocol", "websocket"), (":scheme", "h"), (":path", "/")]),
170 f(&[(":method", "G T"), (":scheme", "h"), (":path", "/")]),
171 ];
172 for b in &bad {
173 assert!(validate_request(b).is_err(), "{b:?}");
174 }
175 assert!(validate_request(&f(&[(":method", "CONNECT"), (":authority", "x:443")])).is_ok());
176 let ws = f(&[(":method", "CONNECT"), (":protocol", "websocket"), (":scheme", "https"), (":path", "/c"), (":authority", "x")]);
177 assert!(validate_request(&ws).is_ok());
178 let cl = f(&[(":method", "POST"), (":scheme", "h"), (":path", "/"), ("content-length", "12"), ("te", "trailers")]);
179 assert_eq!(validate_request(&cl).unwrap().content_length, Some(12));
180 }
181
182 #[test]
183 fn response_and_trailer_rules() {
184 assert!(validate_response(&f(&[(":status", "200")])).is_ok());
185 assert!(validate_response(&f(&[(":status", "103")])).unwrap().informational);
186 assert!(validate_response(&f(&[(":status", "20")])).is_err());
187 assert!(validate_response(&f(&[(":status", "200"), (":status", "200")])).is_err());
188 assert!(validate_response(&f(&[(":path", "/")])).is_err());
189 assert!(validate_trailers(&f(&[("grpc-status", "0")])).is_ok());
190 assert!(validate_trailers(&f(&[(":status", "200")])).is_err());
191 }
192}