1#![allow(clippy::new_without_default)]
24
25use bytes::BufMut;
26use http::header::{AsHeaderName, HeaderName, HeaderValue};
27use http::request::Builder as ReqBuilder;
28use http::request::Parts as ReqParts;
29use http::response::Builder as RespBuilder;
30use http::response::Parts as RespParts;
31use http::uri::Uri;
32use pingora_error::{ErrorType::*, OrErr, Result};
33use std::borrow::Cow;
34use std::ops::Deref;
35
36pub use http::method::Method;
37pub use http::status::StatusCode;
38pub use http::version::Version;
39pub use http::HeaderMap as HMap;
40
41pub mod authority;
42use authority::{raw_target_authority, RawTargetAuthority};
43
44mod case_header_name;
45use case_header_name::CaseHeaderName;
46pub use case_header_name::IntoCaseHeaderName;
47
48pub mod prelude {
49 pub use crate::RequestHeader;
50 pub use crate::ResponseHeader;
51}
52
53type CaseMap = HMap<CaseHeaderName>;
63
64pub enum HeaderNameVariant<'a> {
65 Case(&'a CaseHeaderName),
66 Titled(&'a str),
67}
68
69#[derive(Debug)]
85pub struct RequestHeader {
86 base: ReqParts,
87 header_name_map: Option<CaseMap>,
88 raw_target: RawTarget,
89 send_end_stream: bool,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
99enum RawTarget {
100 FromUri,
104 Verbatim(Box<[u8]>),
109 Lossy(Box<[u8]>),
112}
113
114impl RawTarget {
115 fn bytes(&self) -> Option<&[u8]> {
117 match self {
118 Self::FromUri => None,
119 Self::Verbatim(target) | Self::Lossy(target) => Some(target.as_ref()),
120 }
121 }
122
123 fn is_utf8(&self) -> bool {
128 match self {
129 Self::FromUri | Self::Verbatim(_) => true,
130 Self::Lossy(_) => false,
131 }
132 }
133}
134
135impl AsRef<ReqParts> for RequestHeader {
136 fn as_ref(&self) -> &ReqParts {
137 &self.base
138 }
139}
140
141impl Deref for RequestHeader {
142 type Target = ReqParts;
143
144 fn deref(&self) -> &Self::Target {
145 &self.base
146 }
147}
148
149impl RequestHeader {
150 fn new_no_case(size_hint: Option<usize>) -> Self {
151 let mut base = ReqBuilder::new().body(()).unwrap().into_parts().0;
152 base.headers.reserve(http_header_map_upper_bound(size_hint));
153 RequestHeader {
154 base,
155 header_name_map: None,
156 raw_target: RawTarget::FromUri,
157 send_end_stream: true,
158 }
159 }
160
161 pub fn build(
165 method: impl TryInto<Method>,
166 path: &[u8],
167 size_hint: Option<usize>,
168 ) -> Result<Self> {
169 let mut req = Self::build_no_case(method, path, size_hint)?;
170 req.header_name_map = Some(CaseMap::with_capacity(http_header_map_upper_bound(
171 size_hint,
172 )));
173 Ok(req)
174 }
175
176 pub fn build_no_case(
182 method: impl TryInto<Method>,
183 path: &[u8],
184 size_hint: Option<usize>,
185 ) -> Result<Self> {
186 let mut req = Self::new_no_case(size_hint);
187 req.base.method = method
188 .try_into()
189 .explain_err(InvalidHTTPHeader, |_| "invalid method")?;
190 req.set_raw_path(path)?;
191 Ok(req)
192 }
193
194 pub fn append_header(
199 &mut self,
200 name: impl IntoCaseHeaderName,
201 value: impl TryInto<HeaderValue>,
202 ) -> Result<bool> {
203 let header_value = value
204 .try_into()
205 .explain_err(InvalidHTTPHeader, |_| "invalid value while append")?;
206 append_header_value(
207 self.header_name_map.as_mut(),
208 &mut self.base.headers,
209 name,
210 header_value,
211 )
212 }
213
214 pub fn insert_header(
219 &mut self,
220 name: impl IntoCaseHeaderName,
221 value: impl TryInto<HeaderValue>,
222 ) -> Result<()> {
223 let header_value = value
224 .try_into()
225 .explain_err(InvalidHTTPHeader, |_| "invalid value while insert")?;
226 insert_header_value(
227 self.header_name_map.as_mut(),
228 &mut self.base.headers,
229 name,
230 header_value,
231 )
232 }
233
234 pub fn remove_header<'a, N: ?Sized>(&mut self, name: &'a N) -> Option<HeaderValue>
236 where
237 &'a N: 'a + AsHeaderName,
238 {
239 remove_header(self.header_name_map.as_mut(), &mut self.base.headers, name)
240 }
241
242 pub fn header_to_h1_wire(&self, buf: &mut impl BufMut) {
246 header_to_h1_wire(self.header_name_map.as_ref(), &self.base.headers, buf)
247 }
248
249 pub fn case_header_iter(&self) -> impl Iterator<Item = (&CaseHeaderName, &HeaderValue)> + '_ {
254 case_header_iter(self.header_name_map.as_ref(), &self.base.headers)
255 }
256
257 pub fn has_case(&self) -> bool {
259 self.header_name_map.is_some()
260 }
261
262 pub fn map<F: FnMut(HeaderNameVariant, &HeaderValue) -> Result<()>>(
263 &self,
264 mut f: F,
265 ) -> Result<()> {
266 let key_map = self.header_name_map.as_ref();
267 let value_map = &self.base.headers;
268
269 if let Some(key_map) = key_map {
270 let iter = key_map.iter().zip(value_map.iter());
271 for ((header, case_header), (header2, val)) in iter {
272 if header != header2 {
273 panic!("header iter mismatch {}, {}", header, header2)
275 }
276 f(HeaderNameVariant::Case(case_header), val)?;
277 }
278 } else {
279 for (header, value) in value_map {
280 let titled_header =
281 case_header_name::titled_header_name_str(header).unwrap_or(header.as_str());
282 f(HeaderNameVariant::Titled(titled_header), value)?;
283 }
284 }
285
286 Ok(())
287 }
288
289 pub fn extensions_mut(&mut self) -> &mut http::Extensions {
291 &mut self.base.extensions
292 }
293
294 pub fn set_method(&mut self, method: Method) {
296 self.base.method = method;
297 }
298
299 pub fn set_uri(&mut self, uri: http::Uri) {
301 self.base.uri = uri;
302 self.raw_target = RawTarget::FromUri;
305 }
306
307 pub fn set_raw_path(&mut self, path: &[u8]) -> Result<()> {
325 let parsed = parse_request_target(path)?;
328 self.base.uri = parsed.uri;
329 self.raw_target = parsed.raw_target;
333 Ok(())
334 }
335
336 pub fn set_send_end_stream(&mut self, send_end_stream: bool) {
338 self.send_end_stream = send_end_stream;
339 }
340
341 pub fn send_end_stream(&self) -> Option<bool> {
344 if self.base.version != Version::HTTP_2 {
345 return None;
346 }
347 Some(self.send_end_stream)
348 }
349
350 pub fn raw_path(&self) -> &[u8] {
359 self.raw_target.bytes().unwrap_or_else(|| {
360 self.base
361 .uri
362 .path_and_query()
363 .map(|path| path.as_str().as_bytes())
364 .or_else(|| {
365 self.base
366 .uri
367 .authority()
368 .map(|authority| authority.as_str().as_bytes())
369 })
370 .unwrap_or_default()
371 })
372 }
373
374 pub fn raw_path_is_utf8(&self) -> bool {
376 self.raw_target.is_utf8()
377 }
378
379 pub fn uri_file_extension(&self) -> Option<&str> {
381 let (_, ext) = self
383 .uri
384 .path_and_query()
385 .and_then(|pq| pq.path().rsplit_once('.'))?;
386 Some(ext)
387 }
388
389 pub fn set_version(&mut self, version: Version) {
391 self.base.version = version;
392 }
393
394 pub fn as_owned_parts(&self) -> ReqParts {
401 clone_req_parts(&self.base)
402 }
403}
404
405impl Clone for RequestHeader {
406 fn clone(&self) -> Self {
407 Self {
408 base: self.as_owned_parts(),
409 header_name_map: self.header_name_map.clone(),
410 raw_target: self.raw_target.clone(),
411 send_end_stream: self.send_end_stream,
412 }
413 }
414}
415
416impl From<ReqParts> for RequestHeader {
421 fn from(parts: ReqParts) -> RequestHeader {
422 Self {
423 base: parts,
424 header_name_map: None,
425 raw_target: RawTarget::FromUri,
427 send_end_stream: true,
428 }
429 }
430}
431
432impl From<RequestHeader> for ReqParts {
433 fn from(resp: RequestHeader) -> ReqParts {
434 resp.base
435 }
436}
437
438#[derive(Debug)]
452pub struct ResponseHeader {
453 base: RespParts,
454 header_name_map: Option<CaseMap>,
456 reason_phrase: Option<String>,
458}
459
460impl AsRef<RespParts> for ResponseHeader {
461 fn as_ref(&self) -> &RespParts {
462 &self.base
463 }
464}
465
466impl Deref for ResponseHeader {
467 type Target = RespParts;
468
469 fn deref(&self) -> &Self::Target {
470 &self.base
471 }
472}
473
474impl Clone for ResponseHeader {
475 fn clone(&self) -> Self {
476 Self {
477 base: self.as_owned_parts(),
478 header_name_map: self.header_name_map.clone(),
479 reason_phrase: self.reason_phrase.clone(),
480 }
481 }
482}
483
484impl From<RespParts> for ResponseHeader {
486 fn from(parts: RespParts) -> ResponseHeader {
487 Self {
488 base: parts,
489 header_name_map: None,
490 reason_phrase: None,
491 }
492 }
493}
494
495impl From<ResponseHeader> for RespParts {
496 fn from(resp: ResponseHeader) -> RespParts {
497 resp.base
498 }
499}
500
501impl From<Box<ResponseHeader>> for Box<RespParts> {
502 fn from(resp: Box<ResponseHeader>) -> Box<RespParts> {
503 Box::new(resp.base)
504 }
505}
506
507impl ResponseHeader {
508 fn new(size_hint: Option<usize>) -> Self {
509 let mut resp_header = Self::new_no_case(size_hint);
510 resp_header.header_name_map = Some(CaseMap::with_capacity(http_header_map_upper_bound(
511 size_hint,
512 )));
513 resp_header
514 }
515
516 fn new_no_case(size_hint: Option<usize>) -> Self {
517 let mut base = RespBuilder::new().body(()).unwrap().into_parts().0;
518 base.headers.reserve(http_header_map_upper_bound(size_hint));
519 ResponseHeader {
520 base,
521 header_name_map: None,
522 reason_phrase: None,
523 }
524 }
525
526 pub fn build(code: impl TryInto<StatusCode>, size_hint: Option<usize>) -> Result<Self> {
528 let mut resp = Self::new(size_hint);
529 resp.base.status = code
530 .try_into()
531 .explain_err(InvalidHTTPHeader, |_| "invalid status")?;
532 Ok(resp)
533 }
534
535 pub fn build_no_case(code: impl TryInto<StatusCode>, size_hint: Option<usize>) -> Result<Self> {
541 let mut resp = Self::new_no_case(size_hint);
542 resp.base.status = code
543 .try_into()
544 .explain_err(InvalidHTTPHeader, |_| "invalid status")?;
545 Ok(resp)
546 }
547
548 pub fn append_header(
553 &mut self,
554 name: impl IntoCaseHeaderName,
555 value: impl TryInto<HeaderValue>,
556 ) -> Result<bool> {
557 let header_value = value
558 .try_into()
559 .explain_err(InvalidHTTPHeader, |_| "invalid value while append")?;
560 append_header_value(
561 self.header_name_map.as_mut(),
562 &mut self.base.headers,
563 name,
564 header_value,
565 )
566 }
567
568 pub fn insert_header(
573 &mut self,
574 name: impl IntoCaseHeaderName,
575 value: impl TryInto<HeaderValue>,
576 ) -> Result<()> {
577 let header_value = value
578 .try_into()
579 .explain_err(InvalidHTTPHeader, |_| "invalid value while insert")?;
580 insert_header_value(
581 self.header_name_map.as_mut(),
582 &mut self.base.headers,
583 name,
584 header_value,
585 )
586 }
587
588 pub fn remove_header<'a, N: ?Sized>(&mut self, name: &'a N) -> Option<HeaderValue>
590 where
591 &'a N: 'a + AsHeaderName,
592 {
593 remove_header(self.header_name_map.as_mut(), &mut self.base.headers, name)
594 }
595
596 pub fn header_to_h1_wire(&self, buf: &mut impl BufMut) {
600 header_to_h1_wire(self.header_name_map.as_ref(), &self.base.headers, buf)
601 }
602
603 pub fn case_header_iter(&self) -> impl Iterator<Item = (&CaseHeaderName, &HeaderValue)> + '_ {
608 case_header_iter(self.header_name_map.as_ref(), &self.base.headers)
609 }
610
611 pub fn has_case(&self) -> bool {
613 self.header_name_map.is_some()
614 }
615
616 pub fn map<F: FnMut(HeaderNameVariant, &HeaderValue) -> Result<()>>(
617 &self,
618 mut f: F,
619 ) -> Result<()> {
620 let key_map = self.header_name_map.as_ref();
621 let value_map = &self.base.headers;
622
623 if let Some(key_map) = key_map {
624 let iter = key_map.iter().zip(value_map.iter());
625 for ((header, case_header), (header2, val)) in iter {
626 if header != header2 {
627 panic!("header iter mismatch {}, {}", header, header2)
629 }
630 f(HeaderNameVariant::Case(case_header), val)?;
631 }
632 } else {
633 for (header, value) in value_map {
634 let titled_header =
635 case_header_name::titled_header_name_str(header).unwrap_or(header.as_str());
636 f(HeaderNameVariant::Titled(titled_header), value)?;
637 }
638 }
639
640 Ok(())
641 }
642
643 pub fn extensions_mut(&mut self) -> &mut http::Extensions {
645 &mut self.base.extensions
646 }
647
648 pub fn set_status(&mut self, status: impl TryInto<StatusCode>) -> Result<()> {
650 self.base.status = status
651 .try_into()
652 .explain_err(InvalidHTTPHeader, |_| "invalid status")?;
653 Ok(())
654 }
655
656 pub fn set_version(&mut self, version: Version) {
658 self.base.version = version
659 }
660
661 pub fn set_reason_phrase(&mut self, reason_phrase: Option<&str>) -> Result<()> {
663 if reason_phrase == self.base.status.canonical_reason() {
665 self.reason_phrase = None;
666 return Ok(());
667 }
668
669 self.reason_phrase = reason_phrase.map(str::to_string);
671 Ok(())
672 }
673
674 pub fn get_reason_phrase(&self) -> Option<&str> {
677 self.reason_phrase
678 .as_deref()
679 .or_else(|| self.base.status.canonical_reason())
680 }
681
682 pub fn as_owned_parts(&self) -> RespParts {
684 clone_resp_parts(&self.base)
685 }
686
687 pub fn set_content_length(&mut self, len: usize) -> Result<()> {
689 self.insert_header(http::header::CONTENT_LENGTH, len)
690 }
691}
692
693fn path_and_query_uri(path_and_query: &str, target: &str) -> Result<Uri> {
696 Uri::builder()
697 .path_and_query(path_and_query)
698 .build()
699 .explain_err(InvalidHTTPHeader, |_| format!("invalid uri {target}"))
700}
701
702struct ParsedRequestTarget {
704 uri: Uri,
706 raw_target: RawTarget,
708}
709
710fn parse_request_target(target: &[u8]) -> Result<ParsedRequestTarget> {
712 let target = match target.iter().position(|&byte| byte == b'#') {
719 Some(fragment_start) => &target[..fragment_start],
720 None => target,
721 };
722
723 if target.is_empty() || matches!(target.first(), Some(b'/' | b'?')) || target == b"*" {
729 return Ok(match std::str::from_utf8(target) {
730 Ok(target) => ParsedRequestTarget {
731 uri: path_and_query_uri(target, target)?,
732 raw_target: RawTarget::FromUri,
733 },
734 Err(_) => {
737 let lossy = String::from_utf8_lossy(target);
738 ParsedRequestTarget {
739 uri: path_and_query_uri(&lossy, &lossy)?,
740 raw_target: RawTarget::Lossy(target.into()),
741 }
742 }
743 });
744 }
745
746 let lossy_target = String::from_utf8_lossy(target);
765 let uri = match raw_target_authority(target) {
766 RawTargetAuthority::Absolute { path_and_query, .. } => {
767 let path_and_query = String::from_utf8_lossy(path_and_query);
770 match path_and_query.as_ref() {
771 "" => Uri::default(),
772 pq if !pq.starts_with('/') => path_and_query_uri(&format!("/{pq}"), &lossy_target)?,
775 pq => path_and_query_uri(pq, &lossy_target)?,
776 }
777 }
778 RawTargetAuthority::None | RawTargetAuthority::AmbiguousAuthority => Uri::default(),
788 };
789 Ok(ParsedRequestTarget {
790 uri,
791 raw_target: match lossy_target {
792 Cow::Borrowed(_) => RawTarget::Verbatim(target.into()),
793 Cow::Owned(_) => RawTarget::Lossy(target.into()),
794 },
795 })
796}
797
798fn clone_req_parts(me: &ReqParts) -> ReqParts {
799 let mut parts = ReqBuilder::new()
800 .method(me.method.clone())
801 .uri(me.uri.clone())
802 .version(me.version)
803 .body(())
804 .unwrap()
805 .into_parts()
806 .0;
807 parts.headers = me.headers.clone();
808 parts.extensions = me.extensions.clone();
809 parts
810}
811
812fn clone_resp_parts(me: &RespParts) -> RespParts {
813 let mut parts = RespBuilder::new()
814 .status(me.status)
815 .version(me.version)
816 .body(())
817 .unwrap()
818 .into_parts()
819 .0;
820 parts.headers = me.headers.clone();
821 parts.extensions = me.extensions.clone();
822 parts
823}
824
825fn http_header_map_upper_bound(size_hint: Option<usize>) -> usize {
830 const PINGORA_MAX_HEADER_COUNT: usize = 4096;
838 const INIT_HEADER_SIZE: usize = 8;
839
840 std::cmp::min(
843 size_hint.unwrap_or(INIT_HEADER_SIZE),
844 PINGORA_MAX_HEADER_COUNT,
845 )
846}
847
848#[inline]
849fn append_header_value<T>(
850 name_map: Option<&mut CaseMap>,
851 value_map: &mut HMap<T>,
852 name: impl IntoCaseHeaderName,
853 value: T,
854) -> Result<bool> {
855 let case_header_name = name.into_case_header_name();
856 let header_name: HeaderName = case_header_name
857 .as_slice()
858 .try_into()
859 .or_err(InvalidHTTPHeader, "invalid header name")?;
860 if let Some(name_map) = name_map {
862 name_map
866 .try_append(header_name.clone(), case_header_name)
867 .or_err(InvalidHTTPHeader, "header name map size overflows MAX_SIZE")?;
868 }
869
870 value_map.try_append(header_name, value).or_err(
872 InvalidHTTPHeader,
873 "header value map size overflows MAX_SIZE",
874 )
875}
876
877#[inline]
878fn insert_header_value<T>(
879 name_map: Option<&mut CaseMap>,
880 value_map: &mut HMap<T>,
881 name: impl IntoCaseHeaderName,
882 value: T,
883) -> Result<()> {
884 let case_header_name = name.into_case_header_name();
885 let header_name: HeaderName = case_header_name
886 .as_slice()
887 .try_into()
888 .or_err(InvalidHTTPHeader, "invalid header name")?;
889 if let Some(name_map) = name_map {
890 name_map.insert(header_name.clone(), case_header_name);
892 }
893 value_map.insert(header_name, value);
894 Ok(())
895}
896
897#[inline]
899fn remove_header<'a, T, N: ?Sized>(
900 name_map: Option<&mut CaseMap>,
901 value_map: &mut HMap<T>,
902 name: &'a N,
903) -> Option<T>
904where
905 &'a N: 'a + AsHeaderName,
906{
907 let removed = value_map.remove(name);
908 if removed.is_some() {
909 if let Some(name_map) = name_map {
910 name_map.remove(name);
911 }
912 }
913 removed
914}
915
916pub fn header_value_from_raw(raw: impl Into<bytes::Bytes>) -> HeaderValue {
935 let normalized = normalize_field_value(raw.into());
936 unsafe { HeaderValue::from_maybe_shared_unchecked(normalized) }
943}
944
945pub fn header_value_from_slice(raw: &[u8]) -> HeaderValue {
950 header_value_from_raw(bytes::Bytes::copy_from_slice(raw))
951}
952
953fn normalize_field_value(raw: bytes::Bytes) -> bytes::Bytes {
968 if !raw.iter().any(|b| matches!(b, b'\r' | b'\n' | b'\0')) {
970 return raw;
971 }
972
973 let Some(first_nl) = raw.iter().position(|b| *b == b'\n') else {
976 let replaced: Vec<u8> = raw
977 .iter()
978 .map(|&b| if matches!(b, b'\r' | b'\0') { b' ' } else { b })
979 .collect();
980 return bytes::Bytes::from(replaced);
981 };
982
983 fn push_with_replacement(dst: &mut Vec<u8>, src: &[u8]) {
988 dst.extend(
989 src.iter()
990 .map(|&b| if matches!(b, b'\r' | b'\0') { b' ' } else { b }),
991 );
992 }
993
994 let head = raw[..first_nl].trim_ascii_end();
998 let mut unfolded = Vec::with_capacity(raw.len());
999 push_with_replacement(&mut unfolded, head);
1000 for line in raw[first_nl + 1..].split(|b| *b == b'\n') {
1001 let line = line.trim_ascii();
1002 if line.is_empty() {
1003 continue;
1004 }
1005 if !unfolded.is_empty() {
1006 unfolded.push(b' ');
1007 }
1008 push_with_replacement(&mut unfolded, line);
1009 }
1010 bytes::Bytes::from(unfolded)
1011}
1012
1013#[inline]
1014fn header_to_h1_wire(key_map: Option<&CaseMap>, value_map: &HMap, buf: &mut impl BufMut) {
1015 const CRLF: &[u8; 2] = b"\r\n";
1016 const HEADER_KV_DELIMITER: &[u8; 2] = b": ";
1017
1018 if let Some(key_map) = key_map {
1019 case_header_iter(key_map.into(), value_map).for_each(|(case_header, val)| {
1020 buf.put_slice(case_header.as_slice());
1021 buf.put_slice(HEADER_KV_DELIMITER);
1022 buf.put_slice(val.as_ref());
1023 buf.put_slice(CRLF);
1024 });
1025 } else {
1026 for (header, value) in value_map {
1027 let titled_header =
1028 case_header_name::titled_header_name_str(header).unwrap_or(header.as_str());
1029 buf.put_slice(titled_header.as_bytes());
1030 buf.put_slice(HEADER_KV_DELIMITER);
1031 buf.put_slice(value.as_ref());
1032 buf.put_slice(CRLF);
1033 }
1034 }
1035}
1036
1037#[inline]
1038fn case_header_iter<'a>(
1039 name_map: Option<&'a CaseMap>,
1040 value_map: &'a HMap,
1041) -> impl Iterator<Item = (&'a CaseHeaderName, &'a HeaderValue)> + 'a {
1042 name_map.into_iter().flat_map(|name_map| {
1043 name_map
1044 .iter()
1045 .zip(value_map.iter())
1046 .map(|((h1, name), (h2, value))| {
1047 assert_eq!(h1, h2, "header iter mismatch {}, {}", h1, h2);
1049 (name, value)
1050 })
1051 })
1052}
1053
1054#[cfg(test)]
1055mod tests {
1056 use super::*;
1057
1058 #[test]
1059 fn header_map_upper_bound() {
1060 assert_eq!(8, http_header_map_upper_bound(None));
1061 assert_eq!(16, http_header_map_upper_bound(Some(16)));
1062 assert_eq!(4096, http_header_map_upper_bound(Some(7777)));
1063 }
1064
1065 #[test]
1066 fn test_single_header() {
1067 let mut req = RequestHeader::build("GET", b"/", None).unwrap();
1068 req.insert_header("foo", "bar").unwrap();
1069 req.insert_header("FoO", "Bar").unwrap();
1070 let mut buf: Vec<u8> = vec![];
1071 req.header_to_h1_wire(&mut buf);
1072 assert_eq!(buf, b"FoO: Bar\r\n");
1073 req.case_header_iter().enumerate().for_each(|(i, (k, v))| {
1074 let name = String::from_utf8_lossy(k.as_slice()).into_owned();
1075 let value = String::from_utf8_lossy(v.as_ref()).into_owned();
1076 match i + 1 {
1077 1 => {
1078 assert_eq!(name, "FoO");
1079 assert_eq!(value, "Bar");
1080 }
1081 _ => panic!("too many headers"),
1082 }
1083 });
1084
1085 let mut resp = ResponseHeader::new(None);
1086 resp.insert_header("foo", "bar").unwrap();
1087 resp.insert_header("FoO", "Bar").unwrap();
1088 let mut buf: Vec<u8> = vec![];
1089 resp.header_to_h1_wire(&mut buf);
1090 assert_eq!(buf, b"FoO: Bar\r\n");
1091 resp.case_header_iter().enumerate().for_each(|(i, (k, v))| {
1092 let name = String::from_utf8_lossy(k.as_slice()).into_owned();
1093 let value = String::from_utf8_lossy(v.as_ref()).into_owned();
1094 match i + 1 {
1095 1 => {
1096 assert_eq!(name, "FoO");
1097 assert_eq!(value, "Bar");
1098 }
1099 _ => panic!("too many headers"),
1100 }
1101 });
1102 }
1103
1104 #[test]
1105 fn test_single_header_no_case() {
1106 let mut req = RequestHeader::new_no_case(None);
1107 req.insert_header("foo", "bar").unwrap();
1108 req.insert_header("FoO", "Bar").unwrap();
1109 let mut buf: Vec<u8> = vec![];
1110 req.header_to_h1_wire(&mut buf);
1111 assert_eq!(buf, b"foo: Bar\r\n");
1112 assert!(req.case_header_iter().next().is_none());
1113
1114 let mut resp = ResponseHeader::new_no_case(None);
1115 resp.insert_header("foo", "bar").unwrap();
1116 resp.insert_header("FoO", "Bar").unwrap();
1117 let mut buf: Vec<u8> = vec![];
1118 resp.header_to_h1_wire(&mut buf);
1119 assert_eq!(buf, b"foo: Bar\r\n");
1120 assert!(resp.case_header_iter().next().is_none());
1121 }
1122
1123 #[test]
1124 fn test_multiple_header() {
1125 let mut req = RequestHeader::build("GET", b"/", None).unwrap();
1126 req.append_header("FoO", "Bar").unwrap();
1127 req.append_header("fOO", "bar").unwrap();
1128 req.append_header("BAZ", "baR").unwrap();
1129 req.append_header(http::header::CONTENT_LENGTH, "0")
1130 .unwrap();
1131 req.append_header("a", "b").unwrap();
1132 req.remove_header("a");
1133 let mut buf: Vec<u8> = vec![];
1134 req.header_to_h1_wire(&mut buf);
1135 assert_eq!(
1136 buf,
1137 b"FoO: Bar\r\nfOO: bar\r\nBAZ: baR\r\nContent-Length: 0\r\n"
1138 );
1139 req.case_header_iter().enumerate().for_each(|(i, (k, v))| {
1140 let name = String::from_utf8_lossy(k.as_slice()).into_owned();
1141 let value = String::from_utf8_lossy(v.as_ref()).into_owned();
1142 match i + 1 {
1143 1 => {
1144 assert_eq!(name, "FoO");
1145 assert_eq!(value, "Bar");
1146 }
1147 2 => {
1148 assert_eq!(name, "fOO");
1149 assert_eq!(value, "bar");
1150 }
1151 3 => {
1152 assert_eq!(name, "BAZ");
1153 assert_eq!(value, "baR");
1154 }
1155 4 => {
1156 assert_eq!(name, "Content-Length");
1157 assert_eq!(value, "0");
1158 }
1159 _ => panic!("too many headers"),
1160 }
1161 });
1162
1163 let mut resp = ResponseHeader::new(None);
1164 resp.append_header("FoO", "Bar").unwrap();
1165 resp.append_header("fOO", "bar").unwrap();
1166 resp.append_header("BAZ", "baR").unwrap();
1167 resp.append_header(http::header::CONTENT_LENGTH, "0")
1168 .unwrap();
1169 resp.append_header("a", "b").unwrap();
1170 resp.remove_header("a");
1171 let mut buf: Vec<u8> = vec![];
1172 resp.header_to_h1_wire(&mut buf);
1173 assert_eq!(
1174 buf,
1175 b"FoO: Bar\r\nfOO: bar\r\nBAZ: baR\r\nContent-Length: 0\r\n"
1176 );
1177 resp.case_header_iter().enumerate().for_each(|(i, (k, v))| {
1178 let name = String::from_utf8_lossy(k.as_slice()).into_owned();
1179 let value = String::from_utf8_lossy(v.as_ref()).into_owned();
1180 match i + 1 {
1181 1 => {
1182 assert_eq!(name, "FoO");
1183 assert_eq!(value, "Bar");
1184 }
1185 2 => {
1186 assert_eq!(name, "fOO");
1187 assert_eq!(value, "bar");
1188 }
1189 3 => {
1190 assert_eq!(name, "BAZ");
1191 assert_eq!(value, "baR");
1192 }
1193 4 => {
1194 assert_eq!(name, "Content-Length");
1195 assert_eq!(value, "0");
1196 }
1197 _ => panic!("too many headers"),
1198 }
1199 });
1200 }
1201
1202 #[test]
1205 fn test_invalid_path() {
1206 let raw_path = b"Hello\xF0\x90\x80World";
1207 let req = RequestHeader::build("GET", &raw_path[..], None).unwrap();
1208 assert_eq!("/", req.uri.path_and_query().unwrap());
1209 assert_eq!(raw_path, req.raw_path());
1210 assert!(!req.raw_path_is_utf8());
1211 }
1212
1213 #[test]
1214 fn test_override_invalid_path() {
1215 let raw_path = b"Hello\xF0\x90\x80World";
1216 let mut req = RequestHeader::build("GET", &raw_path[..], None).unwrap();
1217 assert_eq!("/", req.uri.path_and_query().unwrap());
1218 assert_eq!(raw_path, req.raw_path());
1219
1220 let new_path = "/HelloWorld";
1221 req.set_uri(Uri::builder().path_and_query(new_path).build().unwrap());
1222 assert_eq!(new_path, req.uri.path_and_query().unwrap());
1223 assert_eq!(new_path.as_bytes(), req.raw_path());
1224 assert!(req.raw_path_is_utf8());
1225 }
1226
1227 #[test]
1228 fn test_invalid_path_with_leading_slash_reaches_the_uri() {
1229 for (raw_path, expected) in [
1235 (&b"/Hello\xF0\x90\x80World"[..], "/Hello\u{FFFD}World"),
1236 (b"/Hello\xF0\x90\x80World?q=1", "/Hello\u{FFFD}World?q=1"),
1237 (b"/\xF0\x90\x80", "/\u{FFFD}"),
1238 ] {
1239 let req = RequestHeader::build("GET", raw_path, None).unwrap();
1240 let label = String::from_utf8_lossy(raw_path);
1241 assert_eq!(expected, req.uri.path_and_query().unwrap(), "{label}");
1242 assert_eq!(raw_path, req.raw_path(), "{label}");
1243 assert!(!req.raw_path_is_utf8(), "{label}");
1244 }
1245 }
1246
1247 #[test]
1248 fn test_absolute_form_http() {
1249 let req = RequestHeader::build("GET", b"http://host/path?query=1", None).unwrap();
1252 assert_eq!("/path?query=1", req.uri.path_and_query().unwrap().as_str());
1253 assert_eq!("/path", req.uri.path());
1254 assert_eq!(b"http://host/path?query=1", req.raw_path());
1255 }
1256
1257 #[test]
1258 fn test_absolute_form_https() {
1259 let req = RequestHeader::build("GET", b"https://example.com/a/b/c?d=e", None).unwrap();
1260 assert_eq!("/a/b/c?d=e", req.uri.path_and_query().unwrap().as_str());
1261 assert_eq!("/a/b/c", req.uri.path());
1262 }
1263
1264 #[test]
1265 fn test_absolute_form_no_path() {
1266 let req = RequestHeader::build("GET", b"http://host", None).unwrap();
1268 assert_eq!("/", req.uri.path());
1269 assert_eq!(Some("/"), req.uri.path_and_query().map(|pq| pq.as_str()));
1270 assert_eq!(b"http://host", req.raw_path());
1271 }
1272
1273 #[test]
1274 fn test_absolute_form_root() {
1275 let req = RequestHeader::build("GET", b"http://host/", None).unwrap();
1276 assert_eq!("/", req.uri.path());
1277 }
1278
1279 #[test]
1280 fn test_absolute_form_no_path_with_query() {
1281 let req = RequestHeader::build("GET", b"http://host?query=1", None).unwrap();
1284 assert_eq!("/", req.uri.path());
1285 assert_eq!(Some("query=1"), req.uri.query());
1286 assert_eq!("/?query=1", req.uri.path_and_query().unwrap().as_str());
1287 assert_eq!(b"http://host?query=1", req.raw_path());
1288 }
1289
1290 #[test]
1291 fn test_absolute_form_uri_has_no_authority() {
1292 let req = RequestHeader::build("GET", b"http://host:8080/path?q=1", None).unwrap();
1296 assert_eq!(None, req.uri.scheme_str());
1297 assert_eq!(None, req.uri.authority());
1298 assert_eq!("/path", req.uri.path());
1299 assert_eq!(b"http://host:8080/path?q=1", req.raw_path());
1300 }
1301
1302 #[test]
1303 fn test_fragment_is_not_forwarded() {
1304 for (target, raw, path) in [
1308 (&b"http://host/p#frag"[..], &b"http://host/p"[..], "/p"),
1309 (b"http://host#frag", b"http://host", "/"),
1310 (b"http://host?q=1#frag", b"http://host?q=1", "/"),
1311 (b"http://host#@evil.example/", b"http://host", "/"),
1314 ] {
1315 let req = RequestHeader::build("GET", target, None).unwrap();
1316 let target = String::from_utf8_lossy(target);
1317 assert_eq!(raw, req.raw_path(), "{target}");
1318 assert_eq!(path, req.uri.path(), "{target}");
1319 }
1320
1321 let req = RequestHeader::build("GET", b"/p#frag", None).unwrap();
1324 assert_eq!(b"/p", req.raw_path());
1325
1326 let req = RequestHeader::build("CONNECT", b"host:443#x", None).unwrap();
1329 assert_eq!(b"host:443", req.raw_path());
1330
1331 let req = RequestHeader::build("GET", b"http://host/p\xff#frag", None).unwrap();
1334 assert_eq!(b"http://host/p\xff", req.raw_path());
1335 assert!(!req.raw_path_is_utf8());
1336
1337 let req = RequestHeader::build("GET", b"/a\xff#frag", None).unwrap();
1342 assert_eq!(b"/a\xff", req.raw_path());
1343 assert!(!req.raw_path_is_utf8());
1344 }
1345
1346 #[test]
1347 fn test_target_that_is_only_a_fragment_falls_back_to_root() {
1348 for target in [&b""[..], b"#", b"#frag", b"#/admin"] {
1353 let req = RequestHeader::build("GET", target, None).unwrap();
1354 let label = String::from_utf8_lossy(target);
1355 assert_eq!(b"/", req.raw_path(), "{label}");
1356 assert_eq!(RawTarget::FromUri, req.raw_target, "{label}");
1357 }
1358
1359 let req = RequestHeader::build("OPTIONS", b"*#frag", None).unwrap();
1362 assert_eq!(b"*", req.raw_path());
1363 assert_eq!(Some("*"), req.uri.path_and_query().map(|pq| pq.as_str()));
1364
1365 let req = RequestHeader::build("GET", b"?q=1#frag", None).unwrap();
1366 assert_eq!(b"?q=1", req.raw_path());
1367 assert_eq!(Some("q=1"), req.uri.query());
1368 }
1369
1370 #[test]
1371 fn test_non_utf8_target_still_yields_a_path() {
1372 let req = RequestHeader::build("GET", b"http://host/p\xff", None).unwrap();
1376 assert_eq!(b"http://host/p\xff", req.raw_path());
1377 assert!(!req.raw_path_is_utf8());
1378 assert_eq!(None, req.uri.authority());
1379 assert_eq!("/p\u{FFFD}", req.uri.path());
1380
1381 let req = RequestHeader::build("CONNECT", b"ho\xffst:443", None).unwrap();
1384 assert_eq!(b"ho\xffst:443", req.raw_path());
1385 assert_eq!("/", req.uri.path());
1386 assert!(!req.raw_path_is_utf8());
1387
1388 let req = RequestHeader::build("GET", b"/p-\xff", None).unwrap();
1390 assert_eq!(b"/p-\xff", req.raw_path());
1391 assert_eq!("/p-\u{FFFD}", req.uri.path());
1392 }
1393
1394 #[test]
1395 fn test_query_only_target_keeps_its_query() {
1396 let req = RequestHeader::build("GET", b"?q=1", None).unwrap();
1400 assert_eq!(b"?q=1", req.raw_path());
1401 assert_eq!(Some("q=1"), req.uri.query());
1402 assert_eq!("/", req.uri.path());
1403 assert_eq!(RawTarget::FromUri, req.raw_target);
1404 }
1405
1406 #[test]
1407 fn test_set_uri_clears_non_origin_form_target() {
1408 let mut req = RequestHeader::build("GET", b"http://host/abs?q=1", None).unwrap();
1411 req.set_uri("/replaced".parse().unwrap());
1412 assert_eq!(b"/replaced", req.raw_path());
1413 assert_eq!(RawTarget::FromUri, req.raw_target);
1414
1415 let mut req = RequestHeader::build("CONNECT", b"example.com:443", None).unwrap();
1416 req.set_uri("/replaced".parse().unwrap());
1417 assert_eq!(b"/replaced", req.raw_path());
1418 assert!(req.raw_path_is_utf8());
1419 }
1420
1421 #[test]
1422 fn test_unclassifiable_targets_are_anchored_to_the_root() {
1423 for target in [
1429 &b"foo:bar://evil.example/admin"[..],
1430 b"myproto:x://evil.example/admin",
1431 b"myproto:opaque",
1432 ] {
1433 let req = RequestHeader::build("GET", target, None).unwrap();
1434 let label = String::from_utf8_lossy(target);
1435 assert_eq!(
1436 RawTargetAuthority::None,
1437 raw_target_authority(req.raw_path()),
1438 "{label}"
1439 );
1440 assert_eq!("/", req.uri.path(), "{label}");
1443 assert_eq!(target, req.raw_path(), "{label}");
1445 }
1446
1447 let req = RequestHeader::build("GET", b"http:///path", None).unwrap();
1450 assert_eq!("/", req.uri.path());
1451 assert_eq!(b"http:///path", req.raw_path());
1452 }
1453
1454 #[test]
1455 fn test_relative_target_without_a_scheme_is_anchored_to_the_root() {
1456 for target in [&b"foo/bar"[..], b"host/admin", b"foo", b"foo?q=1"] {
1462 let req = RequestHeader::build("GET", target, None).unwrap();
1463 let label = String::from_utf8_lossy(target);
1464 assert_eq!(
1465 RawTargetAuthority::None,
1466 raw_target_authority(req.raw_path()),
1467 "{label}"
1468 );
1469 assert_eq!("/", req.uri.path_and_query().unwrap(), "{label}");
1470 assert_eq!(target, req.raw_path(), "{label}");
1471 }
1472 }
1473
1474 #[test]
1475 fn test_origin_form_raw_path_is_byte_identical() {
1476 for target in [
1478 &b"/"[..],
1479 b"/index.html",
1480 b"/a/b/c?d=e&f=g",
1481 b"/%2e%2e/x",
1482 b"/a+b/c%20d",
1483 b"*",
1484 ] {
1485 let req = RequestHeader::build("GET", target, None).unwrap();
1486 assert_eq!(
1487 target,
1488 req.raw_path(),
1489 "{}",
1490 String::from_utf8_lossy(target)
1491 );
1492 assert!(req.raw_path_is_utf8());
1493 }
1494 }
1495
1496 #[test]
1497 fn test_non_origin_form_survives_clone_and_parts_round_trip() {
1498 let req = RequestHeader::build("GET", b"http://host:8080/p?q=1", None).unwrap();
1499
1500 let cloned = req.clone();
1501 assert_eq!(req.raw_path(), cloned.raw_path());
1502 assert_eq!(req.uri.path(), cloned.uri.path());
1503 assert_eq!(req.raw_path_is_utf8(), cloned.raw_path_is_utf8());
1504
1505 let from_parts = RequestHeader::from(req.as_owned_parts());
1508 assert_eq!(b"/p?q=1", from_parts.raw_path());
1509 assert!(from_parts.raw_path_is_utf8());
1510 }
1511
1512 #[test]
1513 fn test_connect_authority_form() {
1514 let req = RequestHeader::build("CONNECT", b"example.com:443", None).unwrap();
1520 assert_eq!(b"example.com:443", req.raw_path());
1521 assert_eq!(None, req.uri.authority());
1522 assert_eq!("/", req.uri.path());
1523 }
1524
1525 #[test]
1526 fn test_connect_authority_form_shapes_pass_through() {
1527 for target in [
1533 &b"[v7.x]:443"[..],
1534 b"[vF.a:b~!$&'()*+,;=]:8443",
1535 b"[::1]:443",
1536 b"[2001:db8::1]:8443",
1537 b"127.0.0.1:443",
1538 b"sub.example.com:8080",
1539 b"host-with-dash:1",
1540 b"a_b:443",
1541 ] {
1542 let req = RequestHeader::build("CONNECT", target, None).unwrap();
1543 let label = String::from_utf8_lossy(target);
1544 assert_eq!(target, req.raw_path(), "{label}");
1545 assert_eq!("/", req.uri.path(), "{label}");
1548 }
1549 }
1550
1551 #[test]
1552 fn test_set_raw_path_replaces_all_target_state() {
1553 let mut req = RequestHeader::build("GET", b"/path-\xff", None).unwrap();
1561 assert!(!req.raw_path_is_utf8());
1562 assert!(matches!(req.raw_target, RawTarget::Lossy(_)));
1563
1564 req.set_raw_path(b"/plain").unwrap();
1565 assert_eq!(b"/plain", req.raw_path());
1566 assert_eq!("/plain", req.uri.path());
1567 assert!(req.raw_path_is_utf8());
1568 assert_eq!(RawTarget::FromUri, req.raw_target);
1569
1570 req.set_raw_path(b"http://host/abs?q=1").unwrap();
1571 assert_eq!(b"http://host/abs?q=1", req.raw_path());
1572 assert_eq!("/abs", req.uri.path());
1573 assert!(req.raw_path_is_utf8());
1574 }
1575
1576 #[test]
1577 fn test_raw_target_variant_per_request_target_form() {
1578 let req = RequestHeader::build("GET", b"http://host/path", None).unwrap();
1583 assert_eq!(
1584 RawTarget::Verbatim(b"http://host/path".to_vec().into()),
1585 req.raw_target
1586 );
1587 assert!(req.raw_path_is_utf8());
1588
1589 let req = RequestHeader::build("CONNECT", b"example.com:443", None).unwrap();
1590 assert_eq!(
1591 RawTarget::Verbatim(b"example.com:443".to_vec().into()),
1592 req.raw_target
1593 );
1594 assert!(req.raw_path_is_utf8());
1595
1596 let req = RequestHeader::build("GET", b"/path-\xff", None).unwrap();
1597 assert_eq!(
1598 RawTarget::Lossy(b"/path-\xff".to_vec().into()),
1599 req.raw_target
1600 );
1601 assert!(!req.raw_path_is_utf8());
1602
1603 for target in [&b"/path"[..], b"*"] {
1604 let req = RequestHeader::build("GET", target, None).unwrap();
1605 let label = String::from_utf8_lossy(target);
1606 assert_eq!(RawTarget::FromUri, req.raw_target, "{label}");
1607 assert!(req.raw_path_is_utf8(), "{label}");
1608 }
1609 }
1610
1611 #[test]
1612 fn test_set_raw_path_clears_stale_connect_fallback() {
1613 let mut req = RequestHeader::build("CONNECT", b"example.com:443", None).unwrap();
1616 assert_eq!(b"example.com:443", req.raw_path());
1617 req.set_method(Method::GET);
1618 req.set_raw_path(b"/ok").unwrap();
1619 assert_eq!(b"/ok", req.raw_path());
1620 assert_eq!("/ok", req.uri.path());
1621 }
1622
1623 #[test]
1624 fn test_absolute_form_set_raw_path_mutation() {
1625 let mut req = RequestHeader::build("GET", b"/original", None).unwrap();
1627 assert_eq!("/original", req.uri.path());
1628 req.set_raw_path(b"http://host/mutated?q=1").unwrap();
1629 assert_eq!("/mutated?q=1", req.uri.path_and_query().unwrap().as_str());
1630 assert_eq!("/mutated", req.uri.path());
1631 }
1632
1633 #[test]
1634 fn test_absolute_form_with_port() {
1635 let req = RequestHeader::build("GET", b"http://host:8080/path", None).unwrap();
1636 assert_eq!("/path", req.uri.path());
1637 }
1638
1639 #[test]
1640 fn test_absolute_form_uppercase_scheme() {
1641 let req = RequestHeader::build("GET", b"HTTP://HOST/path", None).unwrap();
1643 assert_eq!("/path", req.uri.path());
1644 }
1645
1646 #[test]
1647 fn test_absolute_form_non_http_scheme() {
1648 let req = RequestHeader::build("GET", b"ftp://host/path", None).unwrap();
1650 assert_eq!("/path", req.uri.path());
1651 }
1652
1653 #[test]
1654 fn test_origin_form_unchanged() {
1655 let req = RequestHeader::build("GET", b"/path?q=1", None).unwrap();
1656 assert_eq!("/path?q=1", req.uri.path_and_query().unwrap().as_str());
1657 }
1658
1659 #[test]
1660 fn test_origin_form_with_scheme_in_query() {
1661 let req = RequestHeader::build("GET", b"/redir?url=http://other", None).unwrap();
1664 assert_eq!(
1665 "/redir?url=http://other",
1666 req.uri.path_and_query().unwrap().as_str()
1667 );
1668 }
1669
1670 #[test]
1671 fn test_asterisk_form_unchanged() {
1672 let req = RequestHeader::build("OPTIONS", b"*", None).unwrap();
1673 assert_eq!("*", req.uri.path_and_query().unwrap().as_str());
1674 }
1675
1676 #[test]
1677 fn test_authority_form_raw_path() {
1678 let mut req = RequestHeader::new_no_case(None);
1679 req.set_method(Method::CONNECT);
1680 req.set_uri(Uri::builder().authority("pingora.org:443").build().unwrap());
1681
1682 assert!(req.uri.path_and_query().is_none());
1683 assert_eq!(b"pingora.org:443", req.raw_path());
1684 assert!(req.raw_path_is_utf8());
1685 }
1686
1687 #[test]
1688 fn test_reason_phrase() {
1689 let mut resp = ResponseHeader::new(None);
1690 let reason = resp.get_reason_phrase().unwrap();
1691 assert_eq!(reason, "OK");
1692
1693 resp.set_reason_phrase(Some("FooBar")).unwrap();
1694 let reason = resp.get_reason_phrase().unwrap();
1695 assert_eq!(reason, "FooBar");
1696
1697 resp.set_reason_phrase(Some("OK")).unwrap();
1698 let reason = resp.get_reason_phrase().unwrap();
1699 assert_eq!(reason, "OK");
1700
1701 resp.set_reason_phrase(None).unwrap();
1702 let reason = resp.get_reason_phrase().unwrap();
1703 assert_eq!(reason, "OK");
1704 }
1705
1706 #[test]
1707 fn set_test_send_end_stream() {
1708 let mut req = RequestHeader::build("GET", b"/", None).unwrap();
1709 req.set_send_end_stream(true);
1710
1711 assert!(req.send_end_stream().is_none());
1713
1714 let mut req = RequestHeader::build("GET", b"/", None).unwrap();
1715 req.set_version(Version::HTTP_2);
1716
1717 assert!(req.send_end_stream().unwrap());
1719
1720 req.set_send_end_stream(false);
1721 assert!(!req.send_end_stream().unwrap());
1723 }
1724
1725 #[test]
1726 fn set_test_set_content_length() {
1727 let mut resp = ResponseHeader::new(None);
1728 resp.set_content_length(10).unwrap();
1729
1730 assert_eq!(
1731 b"10",
1732 resp.headers
1733 .get(http::header::CONTENT_LENGTH)
1734 .map(|d| d.as_bytes())
1735 .unwrap()
1736 );
1737 }
1738
1739 #[test]
1740 fn normalize_field_value_no_fold_is_zero_copy() {
1741 let input = bytes::Bytes::from_static(b"text/html; charset=utf-8");
1747 let out = normalize_field_value(input.clone());
1748 assert_eq!(out, input);
1749 assert_eq!(out.as_ptr(), input.as_ptr());
1750 }
1751
1752 #[test]
1753 fn normalize_field_value_single_fold() {
1754 let input = bytes::Bytes::from_static(b"obs\r\n fold");
1756 assert_eq!(&normalize_field_value(input)[..], b"obs fold");
1757 }
1758
1759 #[test]
1760 fn normalize_field_value_multiple_folds_mixed_ws() {
1761 let input = bytes::Bytes::from_static(b"obs\r\n fold\r\n\t line");
1765 assert_eq!(&normalize_field_value(input)[..], b"obs fold line");
1766 }
1767
1768 #[test]
1769 fn normalize_field_value_collapses_long_indent() {
1770 let input =
1773 bytes::Bytes::from_static(b"default-src 'self';\r\n script-src 'self' blob:");
1774 assert_eq!(
1775 &normalize_field_value(input)[..],
1776 b"default-src 'self'; script-src 'self' blob:"
1777 );
1778 }
1779
1780 #[test]
1781 fn normalize_field_value_removes_all_cr_and_lf() {
1782 let input = bytes::Bytes::from_static(b"a\r\n b\r\n c\r\n d");
1785 let out = normalize_field_value(input);
1786 assert!(!out.contains(&b'\r'));
1787 assert!(!out.contains(&b'\n'));
1788 assert_eq!(&out[..], b"a b c d");
1789 }
1790
1791 #[test]
1792 fn normalize_field_value_bare_lf() {
1793 let input = bytes::Bytes::from_static(b"obs\n fold");
1796 assert_eq!(&normalize_field_value(input)[..], b"obs fold");
1797 }
1798
1799 #[test]
1800 fn normalize_field_value_empty() {
1801 let input = bytes::Bytes::new();
1802 let out = normalize_field_value(input.clone());
1803 assert_eq!(out, input);
1804 }
1805
1806 #[test]
1807 fn header_value_from_raw_round_trip() {
1808 let hv = header_value_from_raw(bytes::Bytes::from_static(
1811 b"default-src 'self';\r\n script-src 'self'",
1812 ));
1813 assert_eq!(hv.as_bytes(), b"default-src 'self'; script-src 'self'");
1814 }
1815
1816 #[test]
1817 fn header_value_from_raw_passthrough() {
1818 let hv = header_value_from_raw(bytes::Bytes::from_static(b"application/json"));
1820 assert_eq!(hv.as_bytes(), b"application/json");
1821 }
1822
1823 #[test]
1829 fn normalize_field_value_leading_newline_no_spurious_space() {
1830 let input = bytes::Bytes::from_static(b"\r\n fold");
1833 assert_eq!(&normalize_field_value(input)[..], b"fold");
1834 }
1835
1836 #[test]
1837 fn normalize_field_value_trailing_newline_no_spurious_space() {
1838 let input = bytes::Bytes::from_static(b"foo\r\n");
1841 assert_eq!(&normalize_field_value(input)[..], b"foo");
1842
1843 let input = bytes::Bytes::from_static(b"a\r\n b\r\n");
1844 assert_eq!(&normalize_field_value(input)[..], b"a b");
1845 }
1846
1847 #[test]
1848 fn normalize_field_value_only_newlines() {
1849 assert_eq!(
1851 &normalize_field_value(bytes::Bytes::from_static(b"\r\n"))[..],
1852 b""
1853 );
1854 assert_eq!(
1855 &normalize_field_value(bytes::Bytes::from_static(b"\n"))[..],
1856 b""
1857 );
1858 assert_eq!(
1859 &normalize_field_value(bytes::Bytes::from_static(b"\r\n\r\n"))[..],
1860 b""
1861 );
1862 }
1863
1864 #[test]
1865 fn normalize_field_value_replaces_bare_cr_with_sp() {
1866 let cases: &[(&[u8], &[u8])] = &[
1869 (b"foo\rbar", b"foo bar"),
1870 (b"foo\r", b"foo "),
1871 (b"\rfoo", b" foo"),
1872 (b"\r", b" "),
1873 (b"\r\r\r", b" "),
1874 ];
1875 for (input, expected) in cases {
1876 let out = normalize_field_value(bytes::Bytes::copy_from_slice(input));
1877 assert_eq!(&out[..], *expected, "input = {input:?}");
1878 assert!(!out.contains(&b'\r'));
1879 assert!(!out.contains(&b'\n'));
1880 assert!(!out.contains(&b'\0'));
1881 }
1882 }
1883
1884 #[test]
1885 fn normalize_field_value_replaces_cr_mid_segment_with_sp() {
1886 let input = bytes::Bytes::from_static(b"foo\rbar\r\n baz");
1889 assert_eq!(&normalize_field_value(input)[..], b"foo bar baz");
1890 }
1891
1892 #[test]
1893 fn normalize_field_value_replaces_nul_with_sp() {
1894 let cases: &[(&[u8], &[u8])] = &[
1898 (b"foo\0bar", b"foo bar"),
1899 (b"\0\0\0", b" "),
1900 (b"foo\0", b"foo "),
1901 (b"foo\r\0\nbar", b"foo bar"),
1903 (b"foo\r\n\0bar", b"foo bar"),
1906 ];
1907 for (input, expected) in cases {
1908 let out = normalize_field_value(bytes::Bytes::copy_from_slice(input));
1909 assert_eq!(&out[..], *expected, "input = {input:?}");
1910 assert!(!out.contains(&b'\r'));
1911 assert!(!out.contains(&b'\n'));
1912 assert!(!out.contains(&b'\0'));
1913 }
1914 }
1915
1916 #[test]
1917 fn header_value_from_raw_handles_invalid_bytes() {
1918 let hv = header_value_from_raw(bytes::Bytes::from_static(b"foo\rbar\0baz"));
1923 assert_eq!(hv.as_bytes(), b"foo bar baz");
1924 }
1925}