Skip to main content

hpke_http/
message.rs

1//! Canonical request heads and field pairs used by protocol version 3.
2//!
3//! This module implements only the small protocol subset that the engine uses.
4//! It does not depend on a general OHTTP/BHTTP package, accept indeterminate
5//! messages, informational responses, trailers, padding, or non-minimal integer
6//! encodings. Decode limits apply before any field or body allocation. The
7//! request body arrives in checked DATA records.
8
9use crate::{Error, Limits, Method};
10
11const FORBIDDEN_HEADERS: &[&[u8]] = &[
12    b"connection",
13    b"expect",
14    b"host",
15    b"keep-alive",
16    b"proxy-authenticate",
17    b"proxy-authentication-info",
18    b"proxy-authorization",
19    b"proxy-connection",
20    b"te",
21    b"trailer",
22    b"transfer-encoding",
23    b"upgrade",
24];
25
26/// One canonical end-to-end HTTP field in an ordered field section.
27#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct HeaderField {
29    /// Lower-case HTTP field name. Repeated names preserve their original order.
30    pub name: Vec<u8>,
31    /// Canonical ASCII field value without outer optional whitespace.
32    pub value: Vec<u8>,
33}
34
35/// A complete plaintext request supplied to or returned by the engine.
36#[derive(Clone, Debug, Eq, PartialEq)]
37pub struct Request {
38    /// Supported HTTP method.
39    pub method: Method,
40    /// URI authority, such as `api.example.com` or `api.example.com:8443`.
41    pub authority: Vec<u8>,
42    /// Path and optional query, beginning with `/`, or `*` for `OPTIONS`.
43    pub path: Vec<u8>,
44    /// Ordered end-to-end HTTP fields.
45    pub headers: Vec<HeaderField>,
46    /// Complete bounded request body. Empty bodies remain authenticated.
47    pub body: Vec<u8>,
48}
49
50/// Request fields without a body. Client input before protection and checked
51/// fields after server admission.
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct RequestHead {
54    /// Supported HTTP method.
55    pub method: Method,
56    /// URI authority.
57    pub authority: Vec<u8>,
58    /// Path and optional query.
59    pub path: Vec<u8>,
60    /// Ordered end-to-end HTTP fields.
61    pub headers: Vec<HeaderField>,
62}
63
64pub(crate) fn encode_stream_head(head: &RequestHead, limits: Limits) -> Result<Vec<u8>, Error> {
65    validate_target(head.method, &head.authority, &head.path, limits)?;
66    validate_payload(&head.headers, &[], limits)?;
67    stream_content_length(&head.headers)?;
68    let fields = encode_fields(&head.headers)?;
69    let mut output = Vec::with_capacity(fields.len() + head.authority.len() + head.path.len() + 64);
70    // This private stream head is not an RFC 9292 Binary HTTP message.
71    write_vector(&mut output, head.method.as_bytes())?;
72    write_vector(&mut output, &head.authority)?;
73    write_vector(&mut output, &head.path)?;
74    write_vector(&mut output, &fields)?;
75    Ok(output)
76}
77
78pub(crate) fn decode_stream_head(input: &[u8], limits: Limits) -> Result<RequestHead, Error> {
79    let mut reader = SliceReader::new(input);
80    let method = Method::from_bytes(reader.read_vector()?)?;
81    let authority = reader.read_vector()?;
82    let path = reader.read_vector()?;
83    validate_target(method, authority, path, limits).map_err(decode_validation_error)?;
84    let headers = decode_fields(reader.read_vector()?, limits).map_err(decode_validation_error)?;
85    if !reader.is_finished() {
86        return Err(Error::MalformedEnvelope);
87    }
88    stream_content_length(&headers).map_err(decode_validation_error)?;
89    Ok(RequestHead {
90        method,
91        authority: authority.to_vec(),
92        path: path.to_vec(),
93        headers,
94    })
95}
96
97pub(crate) fn stream_content_length(headers: &[HeaderField]) -> Result<Option<u64>, Error> {
98    let mut values = headers
99        .iter()
100        .filter(|field| field.name == b"content-length");
101    let Some(field) = values.next() else {
102        return Ok(None);
103    };
104    if values.next().is_some()
105        || field.value.is_empty()
106        || !field.value.iter().all(u8::is_ascii_digit)
107    {
108        return Err(Error::InvalidConfiguration);
109    }
110    let mut length = 0_u64;
111    for digit in &field.value {
112        length = length
113            .checked_mul(10)
114            .and_then(|value| value.checked_add(u64::from(digit - b'0')))
115            .ok_or(Error::LimitExceeded)?;
116    }
117    Ok(Some(length))
118}
119
120/// A complete plaintext response supplied to or returned by the engine.
121#[derive(Clone, Debug, Eq, PartialEq)]
122pub struct Response {
123    /// Final HTTP status code in the inclusive range 200 through 599.
124    pub status: u16,
125    /// Ordered end-to-end HTTP fields.
126    pub headers: Vec<HeaderField>,
127    /// Complete bounded response body. Empty bodies remain authenticated.
128    pub body: Vec<u8>,
129}
130
131pub(crate) fn validate_decoded_response_body(
132    response: &Response,
133    method: Method,
134) -> Result<(), Error> {
135    validate_response_content_length(
136        response.status,
137        &response.headers,
138        response.body.len(),
139        method,
140    )
141    .map_err(decode_validation_error)
142}
143
144const fn decode_validation_error(error: Error) -> Error {
145    match error {
146        Error::InvalidConfiguration => Error::MalformedEnvelope,
147        other => other,
148    }
149}
150
151pub(crate) fn encode_fields(headers: &[HeaderField]) -> Result<Vec<u8>, Error> {
152    let mut output = Vec::new();
153    for field in headers {
154        write_vector(&mut output, &field.name)?;
155        write_vector(&mut output, &field.value)?;
156    }
157    Ok(output)
158}
159
160pub(crate) fn decode_fields(input: &[u8], limits: Limits) -> Result<Vec<HeaderField>, Error> {
161    let mut reader = SliceReader::new(input);
162    let mut headers: Vec<HeaderField> = Vec::new();
163    let mut header_bytes = 0_usize;
164    while !reader.is_finished() {
165        if headers.len() >= limits.max_header_count {
166            return Err(Error::LimitExceeded);
167        }
168        let name = reader.read_vector()?;
169        let value = reader.read_vector()?;
170        header_bytes = header_bytes
171            .checked_add(name.len())
172            .and_then(|length| length.checked_add(value.len()))
173            .ok_or(Error::LimitExceeded)?;
174        if header_bytes > limits.max_header_bytes {
175            return Err(Error::LimitExceeded);
176        }
177        validate_header_parts(name, value)?;
178        headers.push(HeaderField {
179            name: name.to_vec(),
180            value: value.to_vec(),
181        });
182    }
183    Ok(headers)
184}
185
186pub(crate) fn validate_request(request: &Request, limits: Limits) -> Result<(), Error> {
187    validate_target(request.method, &request.authority, &request.path, limits)?;
188    validate_payload(&request.headers, &request.body, limits)?;
189    validate_content_length(
190        &request.headers,
191        request.body.len(),
192        ContentLengthRule::Exact,
193    )
194}
195
196pub(crate) fn validate_response_content_length(
197    status: u16,
198    headers: &[HeaderField],
199    body_len: usize,
200    method: Method,
201) -> Result<(), Error> {
202    validate_content_length(
203        headers,
204        body_len,
205        response_content_length_rule(method, status),
206    )
207}
208
209pub(crate) fn validate_response_head(
210    status: u16,
211    headers: &[HeaderField],
212    limits: Limits,
213) -> Result<(), Error> {
214    if !(200..=599).contains(&status) {
215        return Err(Error::InvalidConfiguration);
216    }
217    validate_payload(headers, &[], limits)
218}
219
220fn validate_target(
221    method: Method,
222    authority: &[u8],
223    path: &[u8],
224    limits: Limits,
225) -> Result<(), Error> {
226    let target_len = authority
227        .len()
228        .checked_add(path.len())
229        .ok_or(Error::LimitExceeded)?;
230    if target_len > limits.max_target_len {
231        return Err(Error::LimitExceeded);
232    }
233    if !valid_authority(authority) || !valid_request_target(method, path) {
234        return Err(Error::InvalidConfiguration);
235    }
236    Ok(())
237}
238
239fn valid_authority(authority: &[u8]) -> bool {
240    if authority.is_empty() || !authority.iter().all(u8::is_ascii_graphic) {
241        return false;
242    }
243    if authority[0] == b'[' {
244        return valid_ip_literal_authority(authority);
245    }
246
247    let (host, port) = authority
248        .iter()
249        .rposition(|byte| *byte == b':')
250        .map_or((authority, None), |separator| {
251            (&authority[..separator], Some(&authority[separator + 1..]))
252        });
253    if host.contains(&b':') || !valid_reg_name(host) || !port.is_none_or(valid_port) {
254        return false;
255    }
256    let numeric_ipv4_candidate = host.split(|byte| *byte == b'.').count() == 4
257        && host
258            .iter()
259            .all(|byte| byte.is_ascii_digit() || *byte == b'.');
260    !numeric_ipv4_candidate
261        || std::str::from_utf8(host)
262            .ok()
263            .and_then(|value| value.parse::<std::net::Ipv4Addr>().ok())
264            .is_some()
265}
266
267fn valid_ip_literal_authority(authority: &[u8]) -> bool {
268    let Some(close) = authority.iter().position(|byte| *byte == b']') else {
269        return false;
270    };
271    let literal = &authority[1..close];
272    let suffix = &authority[close + 1..];
273    let valid_suffix = suffix.is_empty() || suffix.strip_prefix(b":").is_some_and(valid_port);
274    if literal.is_empty() || !valid_suffix {
275        return false;
276    }
277    let Ok(value) = std::str::from_utf8(literal) else {
278        return false;
279    };
280    value.parse::<std::net::Ipv6Addr>().is_ok() || valid_ipv_future(literal)
281}
282
283fn valid_ipv_future(value: &[u8]) -> bool {
284    let Some(version_and_address) = value
285        .strip_prefix(b"v")
286        .or_else(|| value.strip_prefix(b"V"))
287    else {
288        return false;
289    };
290    let Some(separator) = version_and_address.iter().position(|byte| *byte == b'.') else {
291        return false;
292    };
293    let version = &version_and_address[..separator];
294    let address = &version_and_address[separator + 1..];
295    !version.is_empty()
296        && version.iter().all(u8::is_ascii_hexdigit)
297        && !address.is_empty()
298        && address
299            .iter()
300            .copied()
301            .all(|byte| is_unreserved(byte) || is_sub_delim(byte) || byte == b':')
302}
303
304fn valid_reg_name(value: &[u8]) -> bool {
305    if value.is_empty() {
306        return false;
307    }
308    let mut position = 0;
309    while position < value.len() {
310        let byte = value[position];
311        if is_unreserved(byte) || is_sub_delim(byte) {
312            position += 1;
313        } else if byte == b'%'
314            && value
315                .get(position + 1..position + 3)
316                .is_some_and(|encoded| encoded.iter().all(u8::is_ascii_hexdigit))
317        {
318            position += 3;
319        } else {
320            return false;
321        }
322    }
323    true
324}
325
326fn valid_port(value: &[u8]) -> bool {
327    if value.is_empty() || !value.iter().all(u8::is_ascii_digit) {
328        return false;
329    }
330    let significant = value
331        .iter()
332        .position(|byte| *byte != b'0')
333        .map_or(&[][..], |position| &value[position..]);
334    significant.is_empty()
335        || (significant.len() <= 5
336            && std::str::from_utf8(significant)
337                .ok()
338                .and_then(|port| port.parse::<u16>().ok())
339                .is_some())
340}
341
342const fn is_unreserved(byte: u8) -> bool {
343    byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~')
344}
345
346const fn is_sub_delim(byte: u8) -> bool {
347    matches!(
348        byte,
349        b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
350    )
351}
352
353fn validate_payload(headers: &[HeaderField], body: &[u8], limits: Limits) -> Result<(), Error> {
354    if headers.len() > limits.max_header_count || body.len() > limits.max_body_len {
355        return Err(Error::LimitExceeded);
356    }
357    let mut header_bytes = 0_usize;
358    for field in headers {
359        validate_header_parts(&field.name, &field.value)?;
360        header_bytes = header_bytes
361            .checked_add(field.name.len())
362            .and_then(|length| length.checked_add(field.value.len()))
363            .ok_or(Error::LimitExceeded)?;
364    }
365    if header_bytes > limits.max_header_bytes {
366        return Err(Error::LimitExceeded);
367    }
368    Ok(())
369}
370
371#[derive(Clone, Copy)]
372enum ContentLengthRule {
373    Exact,
374    Metadata,
375    Forbidden,
376}
377
378fn response_content_length_rule(method: Method, status: u16) -> ContentLengthRule {
379    if status == 204 {
380        ContentLengthRule::Forbidden
381    } else if method == Method::Head || status == 304 {
382        ContentLengthRule::Metadata
383    } else {
384        ContentLengthRule::Exact
385    }
386}
387
388fn validate_content_length(
389    headers: &[HeaderField],
390    body_len: usize,
391    rule: ContentLengthRule,
392) -> Result<(), Error> {
393    let mut values = headers
394        .iter()
395        .filter(|field| field.name == b"content-length")
396        .map(|field| field.value.as_slice());
397    let Some(value) = values.next() else {
398        return Ok(());
399    };
400    if values.next().is_some() || value.is_empty() || !value.iter().all(u8::is_ascii_digit) {
401        return Err(Error::InvalidConfiguration);
402    }
403    match rule {
404        ContentLengthRule::Forbidden => Err(Error::InvalidConfiguration),
405        ContentLengthRule::Metadata => Ok(()),
406        ContentLengthRule::Exact => {
407            let significant = value
408                .iter()
409                .position(|byte| *byte != b'0')
410                .map_or(&[][..], |position| &value[position..]);
411            let expected = body_len.to_string();
412            let matches = if significant.is_empty() {
413                body_len == 0
414            } else {
415                significant == expected.as_bytes()
416            };
417            if matches {
418                Ok(())
419            } else {
420                Err(Error::InvalidConfiguration)
421            }
422        }
423    }
424}
425
426fn validate_header_parts(name: &[u8], value: &[u8]) -> Result<(), Error> {
427    if name.is_empty()
428        || !name.iter().copied().all(is_lower_tchar)
429        || FORBIDDEN_HEADERS.contains(&name)
430        || !value
431            .iter()
432            .all(|byte| *byte == b'\t' || (b' '..=b'~').contains(byte))
433        || value.first().is_some_and(|byte| is_ows(*byte))
434        || value.last().is_some_and(|byte| is_ows(*byte))
435    {
436        return Err(Error::InvalidConfiguration);
437    }
438    Ok(())
439}
440
441fn valid_request_target(method: Method, path: &[u8]) -> bool {
442    if path == b"*" {
443        return method == Method::Options;
444    }
445    if !path.starts_with(b"/")
446        || path.contains(&b'#')
447        || path.contains(&b'\\')
448        || !path.iter().all(u8::is_ascii_graphic)
449        || !valid_percent_encoding(path)
450    {
451        return false;
452    }
453    let path_only = path.split(|byte| *byte == b'?').next().unwrap_or_default();
454    let Some(decoded_path) = decode_path(path_only) else {
455        return false;
456    };
457    std::str::from_utf8(&decoded_path).is_ok()
458        && !decoded_path
459            .iter()
460            .any(|byte| byte.is_ascii_control() || *byte == b'\\')
461        && !decoded_path.split(|byte| *byte == b'/').any(is_dot_segment)
462}
463
464fn decode_path(path: &[u8]) -> Option<Vec<u8>> {
465    let mut decoded = Vec::with_capacity(path.len());
466    let mut position = 0;
467    while position < path.len() {
468        if path[position] == b'%' {
469            let high = hex_value(*path.get(position + 1)?)?;
470            let low = hex_value(*path.get(position + 2)?)?;
471            let byte = high.checked_mul(16)?.checked_add(low)?;
472            if byte == b'/' {
473                return None;
474            }
475            decoded.push(byte);
476            position += 3;
477        } else {
478            decoded.push(path[position]);
479            position += 1;
480        }
481    }
482    Some(decoded)
483}
484
485const fn hex_value(byte: u8) -> Option<u8> {
486    match byte {
487        b'0'..=b'9' => Some(byte - b'0'),
488        b'a'..=b'f' => Some(byte - b'a' + 10),
489        b'A'..=b'F' => Some(byte - b'A' + 10),
490        _ => None,
491    }
492}
493
494fn valid_percent_encoding(value: &[u8]) -> bool {
495    let mut position = 0;
496    while position < value.len() {
497        if value[position] == b'%' {
498            let Some(encoded) = value.get(position + 1..position + 3) else {
499                return false;
500            };
501            if !encoded.iter().all(u8::is_ascii_hexdigit) {
502                return false;
503            }
504            position += 3;
505        } else {
506            position += 1;
507        }
508    }
509    true
510}
511
512fn is_dot_segment(segment: &[u8]) -> bool {
513    segment == b"." || segment == b".."
514}
515
516const fn is_ows(byte: u8) -> bool {
517    matches!(byte, b' ' | b'\t')
518}
519
520const fn is_lower_tchar(byte: u8) -> bool {
521    byte.is_ascii_lowercase()
522        || byte.is_ascii_digit()
523        || matches!(
524            byte,
525            b'!' | b'#'
526                | b'$'
527                | b'%'
528                | b'&'
529                | b'\''
530                | b'*'
531                | b'+'
532                | b'-'
533                | b'.'
534                | b'^'
535                | b'_'
536                | b'`'
537                | b'|'
538                | b'~'
539        )
540}
541
542fn write_vector(output: &mut Vec<u8>, value: &[u8]) -> Result<(), Error> {
543    let length = u64::try_from(value.len()).map_err(|_| Error::LimitExceeded)?;
544    write_varint(output, length)?;
545    output.extend_from_slice(value);
546    Ok(())
547}
548
549fn write_varint(output: &mut Vec<u8>, value: u64) -> Result<(), Error> {
550    match value {
551        0..=0x3f => output.push(u8::try_from(value).map_err(|_| Error::LimitExceeded)?),
552        0x40..=0x3fff => {
553            let encoded = u16::try_from(value).map_err(|_| Error::LimitExceeded)? | 0x4000;
554            output.extend_from_slice(&encoded.to_be_bytes());
555        }
556        0x4000..=0x3fff_ffff => {
557            let encoded = u32::try_from(value).map_err(|_| Error::LimitExceeded)? | 0x8000_0000;
558            output.extend_from_slice(&encoded.to_be_bytes());
559        }
560        0x4000_0000..=0x3fff_ffff_ffff_ffff => {
561            output.extend_from_slice(&(value | 0xc000_0000_0000_0000).to_be_bytes());
562        }
563        _ => return Err(Error::LimitExceeded),
564    }
565    Ok(())
566}
567
568struct SliceReader<'a> {
569    input: &'a [u8],
570    position: usize,
571}
572
573impl<'a> SliceReader<'a> {
574    const fn new(input: &'a [u8]) -> Self {
575        Self { input, position: 0 }
576    }
577
578    fn read_varint(&mut self) -> Result<u64, Error> {
579        let first = *self
580            .input
581            .get(self.position)
582            .ok_or(Error::MalformedEnvelope)?;
583        let length = 1_usize << (first >> 6);
584        let end = self
585            .position
586            .checked_add(length)
587            .ok_or(Error::MalformedEnvelope)?;
588        let encoded = self
589            .input
590            .get(self.position..end)
591            .ok_or(Error::MalformedEnvelope)?;
592        let mut value = u64::from(first & 0x3f);
593        for byte in &encoded[1..] {
594            value = (value << 8) | u64::from(*byte);
595        }
596        if varint_length(value) != length {
597            return Err(Error::MalformedEnvelope);
598        }
599        self.position = end;
600        Ok(value)
601    }
602
603    fn read_vector(&mut self) -> Result<&'a [u8], Error> {
604        let length = usize::try_from(self.read_varint()?).map_err(|_| Error::MalformedEnvelope)?;
605        let end = self
606            .position
607            .checked_add(length)
608            .ok_or(Error::MalformedEnvelope)?;
609        let value = self
610            .input
611            .get(self.position..end)
612            .ok_or(Error::MalformedEnvelope)?;
613        self.position = end;
614        Ok(value)
615    }
616
617    const fn is_finished(&self) -> bool {
618        self.position == self.input.len()
619    }
620}
621
622const fn varint_length(value: u64) -> usize {
623    match value {
624        0..=0x3f => 1,
625        0x40..=0x3fff => 2,
626        0x4000..=0x3fff_ffff => 4,
627        _ => 8,
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::{SliceReader, decode_fields, decode_stream_head, encode_stream_head, write_varint};
634    use crate::{Error, HeaderField, Limits, Method, RequestHead};
635
636    #[test]
637    fn field_decoder_stops_at_the_configured_count() {
638        let limits = Limits {
639            max_header_count: 2,
640            ..Limits::default()
641        };
642        let encoded = [1, b'a', 0, 1, b'b', 0, 1, b'c', 0];
643        assert_eq!(decode_fields(&encoded, limits), Err(Error::LimitExceeded));
644    }
645
646    #[test]
647    fn varint_round_trips_boundaries_and_rejects_non_minimal_values() -> Result<(), Error> {
648        let cases: &[(u64, &[u8])] = &[
649            (0, &[0x00]),
650            (63, &[0x3f]),
651            (64, &[0x40, 0x40]),
652            (16_383, &[0x7f, 0xff]),
653            (16_384, &[0x80, 0x00, 0x40, 0x00]),
654            (0x3fff_ffff, &[0xbf, 0xff, 0xff, 0xff]),
655            (
656                0x4000_0000,
657                &[0xc0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00],
658            ),
659        ];
660        for (value, expected) in cases {
661            let mut encoded = Vec::new();
662            write_varint(&mut encoded, *value)?;
663            assert_eq!(&encoded, expected);
664            let mut reader = SliceReader::new(expected);
665            assert_eq!(reader.read_varint()?, *value);
666            assert!(reader.is_finished());
667        }
668        let mut non_minimal = SliceReader::new(&[0x40, 0x01]);
669        assert_eq!(non_minimal.read_varint(), Err(Error::MalformedEnvelope));
670        Ok(())
671    }
672
673    #[test]
674    fn stream_head_decoder_rechecks_authenticated_content_length() -> Result<(), Error> {
675        let request = RequestHead {
676            method: Method::Post,
677            authority: b"api.example.test".to_vec(),
678            path: b"/items".to_vec(),
679            headers: vec![HeaderField {
680                name: b"content-length".to_vec(),
681                value: b"4".to_vec(),
682            }],
683        };
684        let mut encoded = encode_stream_head(&request, Limits::default())?;
685        let name_offset = encoded
686            .windows(b"content-length".len())
687            .position(|window| window == b"content-length")
688            .ok_or(Error::MalformedEnvelope)?;
689        let value_offset = name_offset + b"content-length".len() + 1;
690        encoded[value_offset] = b'x';
691        assert_eq!(
692            decode_stream_head(&encoded, Limits::default()).err(),
693            Some(Error::MalformedEnvelope)
694        );
695        Ok(())
696    }
697
698    #[test]
699    fn stream_head_decoder_rechecks_authenticated_authority_syntax() -> Result<(), Error> {
700        let request = RequestHead {
701            method: Method::Get,
702            authority: b"api.example".to_vec(),
703            path: b"/items".to_vec(),
704            headers: Vec::new(),
705        };
706        let mut encoded = encode_stream_head(&request, Limits::default())?;
707        let authority_offset = encoded
708            .windows(b"api.example".len())
709            .position(|window| window == b"api.example")
710            .ok_or(Error::MalformedEnvelope)?;
711        encoded[authority_offset] = b'[';
712        assert_eq!(
713            decode_stream_head(&encoded, Limits::default()).err(),
714            Some(Error::MalformedEnvelope)
715        );
716        Ok(())
717    }
718}