1use core::fmt;
4
5use crate::qpack::field::section::decode::QpackDecodedFieldSection;
6
7use super::codepoints::Http3ErrorCode;
8use super::enums::message::{Http3HeadersContext, Http3HeadersKind};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct Http3DecodedHeaderSection<'a> {
17 section: QpackDecodedFieldSection<'a>,
18 kind: Http3HeadersKind,
19 field_section_size: u64,
20}
21
22impl<'a> Http3DecodedHeaderSection<'a> {
23 pub const fn section(self) -> QpackDecodedFieldSection<'a> {
25 self.section
26 }
27
28 pub const fn kind(self) -> Http3HeadersKind {
30 self.kind
31 }
32
33 pub const fn field_section_size(self) -> u64 {
35 self.field_section_size
36 }
37}
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub struct Http3DecodedPushPromise<'a> {
45 section: QpackDecodedFieldSection<'a>,
46 field_section_size: u64,
47}
48
49impl<'a> Http3DecodedPushPromise<'a> {
50 pub const fn section(self) -> QpackDecodedFieldSection<'a> {
52 self.section
53 }
54
55 pub const fn field_section_size(self) -> u64 {
57 self.field_section_size
58 }
59}
60
61pub fn analyze_decoded_header_section<'a>(
63 section: QpackDecodedFieldSection<'a>,
64 context: Http3HeadersContext,
65) -> Result<Http3DecodedHeaderSection<'a>, Http3HeaderSectionError> {
66 let (observed, field_section_size) = scan(section, context)?;
67 let kind = match context {
68 Http3HeadersContext::Request {
69 extended_connect_enabled,
70 } => {
71 validate_request(observed, extended_connect_enabled)?;
72 Http3HeadersKind::Request
73 }
74 Http3HeadersContext::Response => validate_response(observed)?,
75 Http3HeadersContext::Trailers => Http3HeadersKind::Trailers,
76 };
77 Ok(Http3DecodedHeaderSection {
78 section,
79 kind,
80 field_section_size,
81 })
82}
83
84pub fn analyze_decoded_push_promise<'a>(
89 section: QpackDecodedFieldSection<'a>,
90 extended_connect_enabled: bool,
91) -> Result<Http3DecodedPushPromise<'a>, Http3HeaderSectionError> {
92 let context = Http3HeadersContext::Request {
93 extended_connect_enabled,
94 };
95 let (observed, field_section_size) = scan(section, context)?;
96 validate_request(observed, extended_connect_enabled)?;
97 Ok(Http3DecodedPushPromise {
98 section,
99 field_section_size,
100 })
101}
102
103#[derive(Clone, Copy)]
104struct FieldObservation<'a> {
105 value: &'a [u8],
106 field_index: usize,
107}
108
109#[derive(Clone, Copy)]
110struct Observed<'a> {
111 method: Option<FieldObservation<'a>>,
112 scheme: Option<FieldObservation<'a>>,
113 authority: Option<FieldObservation<'a>>,
114 path: Option<FieldObservation<'a>>,
115 protocol: Option<FieldObservation<'a>>,
116 status: Option<FieldObservation<'a>>,
117 host: Option<FieldObservation<'a>>,
118}
119
120fn scan<'a>(
121 section: QpackDecodedFieldSection<'a>,
122 context: Http3HeadersContext,
123) -> Result<(Observed<'a>, u64), Http3HeaderSectionError> {
124 let mut observed = Observed {
125 method: None,
126 scheme: None,
127 authority: None,
128 path: None,
129 protocol: None,
130 status: None,
131 host: None,
132 };
133 let mut size = 0_u64;
134 let mut regular_seen = false;
135
136 for (field_index, field) in section.iter().enumerate() {
137 let name = field.name();
138 let value = field.value();
139 size = add_field_size(size, name, value, field_index)?;
140 validate_field_value(value, field_index)?;
141 if name.first() == Some(&b':') {
142 if regular_seen {
143 return Err(Http3HeaderSectionError::PseudoFieldAfterRegular { field_index });
144 }
145 if context == Http3HeadersContext::Trailers {
146 return Err(Http3HeaderSectionError::PseudoFieldInTrailers { field_index });
147 }
148 observe_pseudo(&mut observed, name, value, field_index, context)?;
149 } else {
150 regular_seen = true;
151 validate_regular_name(name, field_index)?;
152 validate_regular_field(name, value, field_index, context, &mut observed)?;
153 }
154 }
155 Ok((observed, size))
156}
157
158fn add_field_size(
159 size: u64,
160 name: &[u8],
161 value: &[u8],
162 field_index: usize,
163) -> Result<u64, Http3HeaderSectionError> {
164 let name_length = u64::try_from(name.len())
165 .map_err(|_| Http3HeaderSectionError::FieldSectionSizeOverflow { field_index })?;
166 let value_length = u64::try_from(value.len())
167 .map_err(|_| Http3HeaderSectionError::FieldSectionSizeOverflow { field_index })?;
168 name_length
169 .checked_add(value_length)
170 .and_then(|length| length.checked_add(32))
171 .and_then(|length| size.checked_add(length))
172 .ok_or(Http3HeaderSectionError::FieldSectionSizeOverflow { field_index })
173}
174
175fn validate_regular_name(name: &[u8], field_index: usize) -> Result<(), Http3HeaderSectionError> {
176 if name.iter().any(|byte| byte.is_ascii_uppercase()) {
177 return Err(Http3HeaderSectionError::UppercaseFieldName { field_index });
178 }
179 if name.is_empty() || !name.iter().all(|byte| is_token(*byte)) {
180 return Err(Http3HeaderSectionError::InvalidFieldName { field_index });
181 }
182 Ok(())
183}
184
185fn validate_field_value(value: &[u8], field_index: usize) -> Result<(), Http3HeaderSectionError> {
186 if matches!(value.first(), Some(b' ' | b'\t'))
187 || matches!(value.last(), Some(b' ' | b'\t'))
188 || value.iter().any(|byte| {
189 *byte == b'\0'
190 || *byte == b'\r'
191 || *byte == b'\n'
192 || (*byte < 0x20 && *byte != b'\t')
193 || *byte == 0x7f
194 })
195 {
196 return Err(Http3HeaderSectionError::InvalidFieldValue { field_index });
197 }
198 Ok(())
199}
200
201fn observe_pseudo<'a>(
202 observed: &mut Observed<'a>,
203 name: &[u8],
204 value: &'a [u8],
205 field_index: usize,
206 context: Http3HeadersContext,
207) -> Result<(), Http3HeaderSectionError> {
208 let slot = match name {
209 b":method" if matches!(context, Http3HeadersContext::Request { .. }) => {
210 &mut observed.method
211 }
212 b":scheme" if matches!(context, Http3HeadersContext::Request { .. }) => {
213 &mut observed.scheme
214 }
215 b":authority" if matches!(context, Http3HeadersContext::Request { .. }) => {
216 &mut observed.authority
217 }
218 b":path" if matches!(context, Http3HeadersContext::Request { .. }) => &mut observed.path,
219 b":protocol" if matches!(context, Http3HeadersContext::Request { .. }) => {
220 &mut observed.protocol
221 }
222 b":status" if context == Http3HeadersContext::Response => &mut observed.status,
223 _ => return Err(Http3HeaderSectionError::InvalidPseudoField { field_index }),
224 };
225 if slot.is_some() {
226 return Err(Http3HeaderSectionError::DuplicatePseudoField { field_index });
227 }
228 *slot = Some(FieldObservation { value, field_index });
229 Ok(())
230}
231
232fn validate_regular_field<'a>(
233 name: &[u8],
234 value: &'a [u8],
235 field_index: usize,
236 context: Http3HeadersContext,
237 observed: &mut Observed<'a>,
238) -> Result<(), Http3HeaderSectionError> {
239 if matches!(
240 name,
241 b"connection" | b"keep-alive" | b"proxy-connection" | b"transfer-encoding" | b"upgrade"
242 ) {
243 return Err(Http3HeaderSectionError::ConnectionSpecificField { field_index });
244 }
245 if name == b"te"
246 && (!matches!(context, Http3HeadersContext::Request { .. }) || !valid_te(value))
247 {
248 return Err(Http3HeaderSectionError::InvalidTe { field_index });
249 }
250 if name == b"host" {
251 if observed.host.is_some() {
252 return Err(Http3HeaderSectionError::DuplicateHost { field_index });
253 }
254 observed.host = Some(FieldObservation { value, field_index });
255 }
256 Ok(())
257}
258
259fn validate_request(
260 observed: Observed<'_>,
261 extended_connect_enabled: bool,
262) -> Result<(), Http3HeaderSectionError> {
263 let method = observed
264 .method
265 .ok_or(Http3HeaderSectionError::MissingPseudoField { name: b":method" })?;
266 if method.value.is_empty() || !method.value.iter().all(|byte| is_token(*byte)) {
267 return Err(Http3HeaderSectionError::InvalidMethod {
268 field_index: method.field_index,
269 });
270 }
271 let protocol = observed.protocol;
272 let extended = protocol.is_some();
273 if let Some(protocol) = protocol {
274 if !extended_connect_enabled || method.value != b"CONNECT" {
275 return Err(Http3HeaderSectionError::InvalidProtocol {
276 field_index: protocol.field_index,
277 });
278 }
279 if protocol.value.is_empty() || !protocol.value.iter().all(|byte| is_token(*byte)) {
280 return Err(Http3HeaderSectionError::InvalidProtocol {
281 field_index: protocol.field_index,
282 });
283 }
284 }
285 if method.value == b"CONNECT" && !extended {
286 let forbidden = match (observed.scheme, observed.path) {
287 (Some(scheme), Some(path)) if scheme.field_index < path.field_index => Some(scheme),
288 (Some(_), Some(path)) => Some(path),
289 (Some(scheme), None) => Some(scheme),
290 (None, Some(path)) => Some(path),
291 (None, None) => None,
292 };
293 if let Some(field) = forbidden {
294 return Err(Http3HeaderSectionError::InvalidConnectPseudoFields {
295 field_index: field.field_index,
296 });
297 }
298 let authority = observed
299 .authority
300 .ok_or(Http3HeaderSectionError::MissingPseudoField {
301 name: b":authority",
302 })?;
303 if !valid_connect_authority(authority.value) {
304 return Err(Http3HeaderSectionError::InvalidAuthority {
305 field_index: authority.field_index,
306 });
307 }
308 validate_connect_host(authority, observed.host)?;
309 return Ok(());
310 }
311 let scheme = observed
312 .scheme
313 .ok_or(Http3HeaderSectionError::MissingPseudoField { name: b":scheme" })?;
314 let path = observed
315 .path
316 .ok_or(Http3HeaderSectionError::MissingPseudoField { name: b":path" })?;
317 if scheme.value.is_empty() || !valid_scheme(scheme.value) {
318 return Err(Http3HeaderSectionError::InvalidScheme {
319 field_index: scheme.field_index,
320 });
321 }
322 if path.value.is_empty()
323 || ((scheme.value == b"http" || scheme.value == b"https")
324 && !((method.value == b"OPTIONS" && path.value == b"*")
325 || path.value.first() == Some(&b'/')))
326 {
327 return Err(Http3HeaderSectionError::InvalidPath {
328 field_index: path.field_index,
329 });
330 }
331 validate_request_authority(
332 observed.authority,
333 observed.host,
334 extended || scheme.value == b"http" || scheme.value == b"https",
335 )
336}
337
338fn validate_request_authority(
339 authority: Option<FieldObservation<'_>>,
340 host: Option<FieldObservation<'_>>,
341 required: bool,
342) -> Result<(), Http3HeaderSectionError> {
343 if let Some(authority) = authority
344 && (authority.value.is_empty() || authority.value.contains(&b'@'))
345 {
346 return Err(Http3HeaderSectionError::InvalidAuthority {
347 field_index: authority.field_index,
348 });
349 }
350 if let Some(host) = host
351 && host.value.is_empty()
352 {
353 return Err(Http3HeaderSectionError::InvalidHost {
354 field_index: host.field_index,
355 });
356 }
357 if required {
358 match (authority, host) {
359 (Some(authority), Some(host)) if authority.value != host.value => {
360 Err(Http3HeaderSectionError::AuthorityHostMismatch {
361 authority_field_index: authority.field_index,
362 host_field_index: host.field_index,
363 })
364 }
365 (Some(_), _) | (None, Some(_)) => Ok(()),
366 (None, None) => Err(Http3HeaderSectionError::MissingAuthorityOrHost),
367 }
368 } else {
369 Ok(())
370 }
371}
372
373fn validate_connect_host(
374 authority: FieldObservation<'_>,
375 host: Option<FieldObservation<'_>>,
376) -> Result<(), Http3HeaderSectionError> {
377 let Some(host) = host else {
378 return Ok(());
379 };
380 if host.value.is_empty() {
381 return Err(Http3HeaderSectionError::InvalidHost {
382 field_index: host.field_index,
383 });
384 }
385 if authority.value != host.value {
386 return Err(Http3HeaderSectionError::AuthorityHostMismatch {
387 authority_field_index: authority.field_index,
388 host_field_index: host.field_index,
389 });
390 }
391 Ok(())
392}
393
394fn validate_response(observed: Observed<'_>) -> Result<Http3HeadersKind, Http3HeaderSectionError> {
395 let status_field = observed
396 .status
397 .ok_or(Http3HeaderSectionError::MissingPseudoField { name: b":status" })?;
398 if status_field.value.len() != 3 || !status_field.value.iter().all(|byte| byte.is_ascii_digit())
399 {
400 return Err(Http3HeaderSectionError::InvalidStatus {
401 field_index: status_field.field_index,
402 });
403 }
404 let status = u16::from(status_field.value[0] - b'0') * 100
405 + u16::from(status_field.value[1] - b'0') * 10
406 + u16::from(status_field.value[2] - b'0');
407 match status {
408 100..=199 if status != 101 => Ok(Http3HeadersKind::InformationalResponse),
409 200..=599 => Ok(Http3HeadersKind::FinalResponse),
410 _ => Err(Http3HeaderSectionError::InvalidStatus {
411 field_index: status_field.field_index,
412 }),
413 }
414}
415
416fn valid_te(value: &[u8]) -> bool {
417 let mut item_seen = false;
418 for item in value.split(|byte| *byte == b',') {
419 let item = trim_ows(item);
420 if !item.is_empty() {
421 item_seen = true;
422 if !item.eq_ignore_ascii_case(b"trailers") {
423 return false;
424 }
425 }
426 }
427 item_seen
428}
429
430fn valid_connect_authority(authority: &[u8]) -> bool {
431 if authority.is_empty() || authority.contains(&b'@') {
432 return false;
433 }
434 let (host, port, bracketed) = if authority.first() == Some(&b'[') {
435 let Some(end) = authority.iter().position(|byte| *byte == b']') else {
436 return false;
437 };
438 if end == 1 || authority.get(end + 1) != Some(&b':') {
439 return false;
440 }
441 (&authority[1..end], &authority[end + 2..], true)
442 } else {
443 let Some(port_start) = authority.iter().rposition(|byte| *byte == b':') else {
444 return false;
445 };
446 (
447 &authority[..port_start],
448 &authority[port_start + 1..],
449 false,
450 )
451 };
452 !host.is_empty() && (bracketed || !host.contains(&b':')) && valid_port(port)
453}
454
455fn valid_port(port: &[u8]) -> bool {
456 if port.is_empty() || !port.iter().all(|byte| byte.is_ascii_digit()) {
457 return false;
458 }
459 let mut value = 0_u32;
460 for byte in port {
461 value = match value
462 .checked_mul(10)
463 .and_then(|value| value.checked_add(u32::from(*byte - b'0')))
464 {
465 Some(value) if value <= 65_535 => value,
466 _ => return false,
467 };
468 }
469 true
470}
471
472fn valid_scheme(scheme: &[u8]) -> bool {
473 scheme
474 .first()
475 .is_some_and(|byte| byte.is_ascii_alphabetic())
476 && scheme[1..]
477 .iter()
478 .all(|byte| byte.is_ascii_alphanumeric() || matches!(*byte, b'+' | b'-' | b'.'))
479}
480
481fn trim_ows(mut value: &[u8]) -> &[u8] {
482 while matches!(value.first(), Some(b' ' | b'\t')) {
483 value = &value[1..];
484 }
485 while matches!(value.last(), Some(b' ' | b'\t')) {
486 value = &value[..value.len() - 1];
487 }
488 value
489}
490
491const fn is_token(byte: u8) -> bool {
492 byte.is_ascii_alphanumeric()
493 || matches!(
494 byte,
495 b'!' | b'#'
496 | b'$'
497 | b'%'
498 | b'&'
499 | b'\''
500 | b'*'
501 | b'+'
502 | b'-'
503 | b'.'
504 | b'^'
505 | b'_'
506 | b'`'
507 | b'|'
508 | b'~'
509 )
510}
511
512#[derive(Clone, Copy, Debug, Eq, PartialEq)]
517pub enum Http3HeaderSectionError {
518 FieldSectionSizeOverflow {
520 field_index: usize,
522 },
523 InvalidFieldName {
525 field_index: usize,
527 },
528 UppercaseFieldName {
530 field_index: usize,
532 },
533 InvalidFieldValue {
535 field_index: usize,
537 },
538 PseudoFieldAfterRegular {
540 field_index: usize,
542 },
543 PseudoFieldInTrailers {
545 field_index: usize,
547 },
548 InvalidPseudoField {
550 field_index: usize,
552 },
553 DuplicatePseudoField {
555 field_index: usize,
557 },
558 MissingPseudoField {
560 name: &'static [u8],
562 },
563 ConnectionSpecificField {
565 field_index: usize,
567 },
568 InvalidTe {
570 field_index: usize,
572 },
573 DuplicateHost {
575 field_index: usize,
577 },
578 MissingAuthorityOrHost,
580 InvalidAuthority {
582 field_index: usize,
584 },
585 InvalidHost {
587 field_index: usize,
589 },
590 AuthorityHostMismatch {
592 authority_field_index: usize,
594 host_field_index: usize,
596 },
597 InvalidMethod {
599 field_index: usize,
601 },
602 InvalidScheme {
604 field_index: usize,
606 },
607 InvalidPath {
609 field_index: usize,
611 },
612 InvalidProtocol {
614 field_index: usize,
616 },
617 InvalidConnectPseudoFields {
619 field_index: usize,
621 },
622 InvalidStatus {
624 field_index: usize,
626 },
627}
628
629impl Http3HeaderSectionError {
630 pub const fn error_code(self) -> Http3ErrorCode {
632 Http3ErrorCode::MESSAGE_ERROR
633 }
634}
635
636impl fmt::Display for Http3HeaderSectionError {
637 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638 match self {
639 Self::FieldSectionSizeOverflow { field_index } => write!(
640 f,
641 "HTTP/3 field {field_index} makes the field-section size overflow"
642 ),
643 Self::InvalidFieldName { field_index } => {
644 write!(f, "HTTP/3 field {field_index} has an invalid name")
645 }
646 Self::UppercaseFieldName { field_index } => {
647 write!(f, "HTTP/3 field {field_index} has an uppercase name")
648 }
649 Self::InvalidFieldValue { field_index } => {
650 write!(f, "HTTP/3 field {field_index} has an invalid value")
651 }
652 Self::PseudoFieldAfterRegular { field_index } => write!(
653 f,
654 "HTTP/3 pseudo-field {field_index} follows a regular field"
655 ),
656 Self::PseudoFieldInTrailers { field_index } => {
657 write!(f, "HTTP/3 trailer pseudo-field {field_index} is forbidden")
658 }
659 Self::InvalidPseudoField { field_index } => write!(
660 f,
661 "HTTP/3 pseudo-field {field_index} is invalid for this context"
662 ),
663 Self::DuplicatePseudoField { field_index } => write!(
664 f,
665 "HTTP/3 pseudo-field {field_index} duplicates an earlier pseudo-field"
666 ),
667 Self::MissingPseudoField { name } => write!(
668 f,
669 "HTTP/3 field section is missing required pseudo-field {name:?}"
670 ),
671 Self::ConnectionSpecificField { field_index } => {
672 write!(f, "HTTP/3 field {field_index} is connection-specific")
673 }
674 Self::InvalidTe { field_index } => {
675 write!(f, "HTTP/3 TE field {field_index} is invalid")
676 }
677 Self::DuplicateHost { field_index } => write!(
678 f,
679 "HTTP/3 Host field {field_index} duplicates an earlier Host field"
680 ),
681 Self::MissingAuthorityOrHost => {
682 f.write_str("HTTP/3 request is missing :authority and Host")
683 }
684 Self::InvalidAuthority { field_index } => {
685 write!(f, "HTTP/3 :authority field {field_index} is invalid")
686 }
687 Self::InvalidHost { field_index } => {
688 write!(f, "HTTP/3 Host field {field_index} is invalid")
689 }
690 Self::AuthorityHostMismatch {
691 authority_field_index,
692 host_field_index,
693 } => write!(
694 f,
695 "HTTP/3 :authority field {authority_field_index} and Host field {host_field_index} differ"
696 ),
697 Self::InvalidMethod { field_index } => {
698 write!(f, "HTTP/3 :method field {field_index} is invalid")
699 }
700 Self::InvalidScheme { field_index } => {
701 write!(f, "HTTP/3 :scheme field {field_index} is invalid")
702 }
703 Self::InvalidPath { field_index } => {
704 write!(f, "HTTP/3 :path field {field_index} is invalid")
705 }
706 Self::InvalidProtocol { field_index } => {
707 write!(f, "HTTP/3 :protocol field {field_index} is invalid")
708 }
709 Self::InvalidConnectPseudoFields { field_index } => write!(
710 f,
711 "HTTP/3 ordinary CONNECT pseudo-field {field_index} is forbidden"
712 ),
713 Self::InvalidStatus { field_index } => {
714 write!(f, "HTTP/3 :status field {field_index} is invalid")
715 }
716 }
717 }
718}
719
720impl core::error::Error for Http3HeaderSectionError {}