1#![allow(
2 clippy::should_implement_trait,
3 clippy::no_effect,
4 clippy::missing_safety_doc
5)]
6use std::{cmp, fmt, hash, str, str::FromStr};
7
8use ntex_bytes::{ByteString, Bytes, BytesMut};
9
10#[derive(Clone, Eq)]
20pub struct HeaderValue {
21 inner: Bytes,
22 is_sensitive: bool,
23}
24
25#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
26#[error("Invalid HTTP header value")]
27pub struct InvalidHeaderValue {
30 _priv: (),
31}
32
33#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[error("failed to convert header to a str")]
40pub struct ToStrError {
41 _priv: (),
42}
43
44impl HeaderValue {
45 #[inline]
64 pub const fn from_static(src: &'static str) -> HeaderValue {
65 let bytes = src.as_bytes();
66 let mut i = 0;
67 while i < bytes.len() {
68 assert!(is_visible_ascii(bytes[i]), "invalid header value");
69 i += 1;
70 }
71
72 HeaderValue {
73 inner: Bytes::from_static(bytes),
74 is_sensitive: false,
75 }
76 }
77
78 #[inline]
103 pub fn from_str(src: &str) -> Result<HeaderValue, InvalidHeaderValue> {
104 HeaderValue::try_from_generic(src, |s| Bytes::copy_from_slice(s.as_bytes()))
105 }
106
107 #[inline]
129 pub fn from_bytes(src: &[u8]) -> Result<HeaderValue, InvalidHeaderValue> {
130 HeaderValue::try_from_generic(src, Bytes::copy_from_slice)
131 }
132
133 pub fn from_shared<T>(src: T) -> Result<HeaderValue, InvalidHeaderValue>
139 where
140 Bytes: From<T>,
141 {
142 let inner = Bytes::from(src);
143 for &b in inner.as_ref() {
144 if !is_valid(b) {
145 return Err(InvalidHeaderValue { _priv: () });
146 }
147 }
148
149 Ok(HeaderValue {
150 inner,
151 is_sensitive: false,
152 })
153 }
154
155 pub unsafe fn from_shared_unchecked(src: Bytes) -> HeaderValue {
162 HeaderValue {
163 inner: src,
164 is_sensitive: false,
165 }
166 }
167
168 fn try_from_generic<T: AsRef<[u8]>, F: FnOnce(T) -> Bytes>(
169 src: T,
170 into: F,
171 ) -> Result<HeaderValue, InvalidHeaderValue> {
172 for &b in src.as_ref() {
173 if !is_valid(b) {
174 return Err(InvalidHeaderValue { _priv: () });
175 }
176 }
177 Ok(HeaderValue {
178 inner: into(src),
179 is_sensitive: false,
180 })
181 }
182
183 pub fn to_str(&self) -> Result<&str, ToStrError> {
201 simdutf8::basic::from_utf8(self.as_ref()).map_err(|_| ToStrError { _priv: () })
202 }
203
204 #[inline]
216 pub fn len(&self) -> usize {
217 self.as_ref().len()
218 }
219
220 #[inline]
233 pub fn is_empty(&self) -> bool {
234 self.len() == 0
235 }
236
237 #[inline]
247 pub fn as_bytes(&self) -> &[u8] {
248 self.as_ref()
249 }
250
251 #[inline]
262 pub fn as_shared(&self) -> &Bytes {
263 &self.inner
264 }
265
266 #[inline]
281 pub fn set_sensitive(&mut self, val: bool) {
282 self.is_sensitive = val;
283 }
284
285 #[inline]
312 pub fn is_sensitive(&self) -> bool {
313 self.is_sensitive
314 }
315}
316
317impl AsRef<[u8]> for HeaderValue {
318 #[inline]
319 fn as_ref(&self) -> &[u8] {
320 self.inner.as_ref()
321 }
322}
323
324impl fmt::Debug for HeaderValue {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 if self.is_sensitive {
327 f.write_str("Sensitive")
328 } else {
329 f.write_str("\"")?;
330 let mut from = 0;
331 let bytes = self.as_bytes();
332 for (i, &b) in bytes.iter().enumerate() {
333 if !is_visible_ascii(b) || b == b'"' {
334 if from != i {
335 f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..i]) })?;
336 }
337 if b == b'"' {
338 f.write_str("\\\"")?;
339 } else {
340 write!(f, "\\x{b:x}")?;
341 }
342 from = i + 1;
343 }
344 }
345
346 f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..]) })?;
347 f.write_str("\"")
348 }
349 }
350}
351
352impl FromStr for HeaderValue {
353 type Err = InvalidHeaderValue;
354
355 #[inline]
356 fn from_str(s: &str) -> Result<HeaderValue, Self::Err> {
357 HeaderValue::from_str(s)
358 }
359}
360
361impl<'a> From<&'a HeaderValue> for HeaderValue {
362 #[inline]
363 fn from(t: &'a HeaderValue) -> Self {
364 t.clone()
365 }
366}
367
368impl From<http::header::HeaderValue> for HeaderValue {
369 #[inline]
370 fn from(t: http::header::HeaderValue) -> Self {
371 let inner = Bytes::copy_from_slice(t.as_ref());
372 HeaderValue {
373 inner,
374 is_sensitive: t.is_sensitive(),
375 }
376 }
377}
378
379impl<'a> From<&'a http::header::HeaderValue> for HeaderValue {
380 #[inline]
381 fn from(t: &'a http::header::HeaderValue) -> Self {
382 let inner = Bytes::copy_from_slice(t.as_ref());
383 HeaderValue {
384 inner,
385 is_sensitive: t.is_sensitive(),
386 }
387 }
388}
389
390impl<'a> TryFrom<&'a str> for HeaderValue {
391 type Error = InvalidHeaderValue;
392
393 #[inline]
394 fn try_from(t: &'a str) -> Result<Self, Self::Error> {
395 t.parse()
396 }
397}
398
399impl<'a> TryFrom<&'a String> for HeaderValue {
400 type Error = InvalidHeaderValue;
401 #[inline]
402 fn try_from(s: &'a String) -> Result<Self, Self::Error> {
403 Self::from_bytes(s.as_bytes())
404 }
405}
406
407impl<'a> TryFrom<&'a ByteString> for HeaderValue {
408 type Error = InvalidHeaderValue;
409 #[inline]
410 fn try_from(s: &'a ByteString) -> Result<Self, Self::Error> {
411 Self::from_shared(s.as_bytes().clone())
412 }
413}
414
415impl<'a> TryFrom<&'a [u8]> for HeaderValue {
416 type Error = InvalidHeaderValue;
417
418 #[inline]
419 fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
420 HeaderValue::from_bytes(t)
421 }
422}
423
424impl TryFrom<String> for HeaderValue {
425 type Error = InvalidHeaderValue;
426
427 #[inline]
428 fn try_from(s: String) -> Result<Self, Self::Error> {
429 HeaderValue::from_shared(s)
430 }
431}
432
433impl TryFrom<ByteString> for HeaderValue {
434 type Error = InvalidHeaderValue;
435 #[inline]
436 fn try_from(s: ByteString) -> Result<Self, Self::Error> {
437 Self::from_shared(s.into_bytes())
438 }
439}
440
441impl TryFrom<Vec<u8>> for HeaderValue {
442 type Error = InvalidHeaderValue;
443
444 #[inline]
445 fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
446 HeaderValue::from_shared(vec)
447 }
448}
449
450impl TryFrom<Bytes> for HeaderValue {
451 type Error = InvalidHeaderValue;
452
453 #[inline]
454 fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
455 HeaderValue::from_shared(bytes)
456 }
457}
458
459impl TryFrom<BytesMut> for HeaderValue {
460 type Error = InvalidHeaderValue;
461
462 #[inline]
463 fn try_from(bytes: BytesMut) -> Result<Self, Self::Error> {
464 HeaderValue::from_shared(bytes)
465 }
466}
467
468impl From<HeaderValue> for http::header::HeaderValue {
469 #[inline]
470 fn from(t: HeaderValue) -> Self {
471 let mut hdr = Self::from_bytes(t.as_bytes()).unwrap();
472 hdr.set_sensitive(t.is_sensitive());
473 hdr
474 }
475}
476
477impl<'a> From<&'a HeaderValue> for http::header::HeaderValue {
478 #[inline]
479 fn from(t: &'a HeaderValue) -> Self {
480 let mut hdr = Self::from_bytes(t.as_bytes()).unwrap();
481 hdr.set_sensitive(t.is_sensitive());
482 hdr
483 }
484}
485
486const fn is_visible_ascii(b: u8) -> bool {
487 b >= 32 && b < 127 || b == b'\t'
488}
489
490#[inline]
491fn is_valid(b: u8) -> bool {
492 b >= 32 && b != 127 || b == b'\t'
493}
494
495impl hash::Hash for HeaderValue {
499 #[inline]
500 fn hash<H: hash::Hasher>(&self, state: &mut H) {
501 self.inner.hash(state);
502 }
503}
504
505impl PartialEq for HeaderValue {
506 #[inline]
507 fn eq(&self, other: &HeaderValue) -> bool {
508 self.inner == other.inner
509 }
510}
511
512impl PartialOrd for HeaderValue {
513 #[inline]
514 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
515 Some(self.cmp(other))
516 }
517}
518
519impl Ord for HeaderValue {
520 #[inline]
521 fn cmp(&self, other: &Self) -> cmp::Ordering {
522 self.inner.cmp(&other.inner)
523 }
524}
525
526impl PartialEq<str> for HeaderValue {
527 #[inline]
528 fn eq(&self, other: &str) -> bool {
529 self.inner == other.as_bytes()
530 }
531}
532
533impl PartialEq<[u8]> for HeaderValue {
534 #[inline]
535 fn eq(&self, other: &[u8]) -> bool {
536 self.inner == other
537 }
538}
539
540impl PartialOrd<str> for HeaderValue {
541 #[inline]
542 fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
543 (*self.inner).partial_cmp(other.as_bytes())
544 }
545}
546
547impl PartialOrd<[u8]> for HeaderValue {
548 #[inline]
549 fn partial_cmp(&self, other: &[u8]) -> Option<cmp::Ordering> {
550 (*self.inner).partial_cmp(other)
551 }
552}
553
554impl PartialEq<HeaderValue> for str {
555 #[inline]
556 fn eq(&self, other: &HeaderValue) -> bool {
557 *other == *self
558 }
559}
560
561impl PartialEq<HeaderValue> for [u8] {
562 #[inline]
563 fn eq(&self, other: &HeaderValue) -> bool {
564 *other == *self
565 }
566}
567
568impl PartialOrd<HeaderValue> for str {
569 #[inline]
570 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
571 self.as_bytes().partial_cmp(other.as_bytes())
572 }
573}
574
575impl PartialOrd<HeaderValue> for [u8] {
576 #[inline]
577 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
578 self.partial_cmp(other.as_bytes())
579 }
580}
581
582impl PartialEq<String> for HeaderValue {
583 #[inline]
584 fn eq(&self, other: &String) -> bool {
585 *self == other[..]
586 }
587}
588
589impl PartialOrd<String> for HeaderValue {
590 #[inline]
591 fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
592 self.inner.partial_cmp(other.as_bytes())
593 }
594}
595
596impl PartialEq<HeaderValue> for String {
597 #[inline]
598 fn eq(&self, other: &HeaderValue) -> bool {
599 *other == *self
600 }
601}
602
603impl PartialOrd<HeaderValue> for String {
604 #[inline]
605 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
606 self.as_bytes().partial_cmp(other.as_bytes())
607 }
608}
609
610impl PartialEq<HeaderValue> for &HeaderValue {
611 #[inline]
612 fn eq(&self, other: &HeaderValue) -> bool {
613 **self == *other
614 }
615}
616
617impl PartialOrd<HeaderValue> for &HeaderValue {
618 #[inline]
619 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
620 (**self).partial_cmp(other)
621 }
622}
623
624impl<'a, T: ?Sized> PartialEq<&'a T> for HeaderValue
625where
626 HeaderValue: PartialEq<T>,
627{
628 #[inline]
629 fn eq(&self, other: &&'a T) -> bool {
630 *self == **other
631 }
632}
633
634impl<'a, T: ?Sized> PartialOrd<&'a T> for HeaderValue
635where
636 HeaderValue: PartialOrd<T>,
637{
638 #[inline]
639 fn partial_cmp(&self, other: &&'a T) -> Option<cmp::Ordering> {
640 self.partial_cmp(*other)
641 }
642}
643
644impl PartialEq<HeaderValue> for &str {
645 #[inline]
646 fn eq(&self, other: &HeaderValue) -> bool {
647 *other == *self
648 }
649}
650
651impl PartialOrd<HeaderValue> for &str {
652 #[inline]
653 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
654 self.as_bytes().partial_cmp(other.as_bytes())
655 }
656}
657
658macro_rules! from_integers {
659 ($($name:ident: $t:ident => $max_len:expr),*) => {$(
660 impl From<$t> for HeaderValue {
661 fn from(num: $t) -> HeaderValue {
662 let mut b = itoa::Buffer::new();
663 let inner = Bytes::copy_from_slice(b.format(num).as_ref());
664 HeaderValue {
665 inner,
666 is_sensitive: false,
667 }
668 }
669 }
670
671 #[test]
672 fn $name() {
673 let n: $t = 55;
674 let val = HeaderValue::from(n);
675 assert_eq!(val, &n.to_string());
676
677 let n = $t::MAX;
678 let val = HeaderValue::from(n);
679 assert_eq!(val, &n.to_string());
680 }
681 )*};
682}
683
684from_integers! {
685 from_u16: u16 => 5,
689 from_i16: i16 => 6,
690 from_u32: u32 => 10,
691 from_i32: i32 => 11,
692 from_u64: u64 => 20,
693 from_i64: i64 => 20
694}
695
696#[cfg(target_pointer_width = "16")]
697from_integers! {
698 from_usize: usize => 5,
699 from_isize: isize => 6
700}
701
702#[cfg(target_pointer_width = "32")]
703from_integers! {
704 from_usize: usize => 10,
705 from_isize: isize => 11
706}
707
708#[cfg(target_pointer_width = "64")]
709from_integers! {
710 from_usize: usize => 20,
711 from_isize: isize => 20
712}
713
714#[cfg(test)]
715mod tests {
716 use super::*;
717
718 #[test]
719 fn test_hash_ignores_sensitive() {
720 use std::hash::{BuildHasher, RandomState};
721
722 let state = RandomState::new();
723 let hdr = HeaderValue::from_static("secret");
724 let mut sensitive = hdr.clone();
725 sensitive.set_sensitive(true);
726 assert_eq!(hdr, sensitive);
727 assert_eq!(state.hash_one(&hdr), state.hash_one(&sensitive));
728 }
729
730 #[test]
731 #[allow(clippy::op_ref, clippy::cmp_owned)]
732 fn test_basics() {
733 assert!(HeaderValue::from_str("").unwrap().is_empty());
734
735 let hdr = HeaderValue::from_bytes(b"upgrade").unwrap();
736 let hdr2 = HeaderValue::from(&hdr);
737 assert_eq!(hdr, hdr2);
738
739 let hdr3 = HeaderValue::from_shared(Bytes::from_static(b"upgrade")).unwrap();
740 assert_eq!(hdr, hdr3);
741
742 let hdr = http::header::HeaderValue::from_bytes(b"upgrade").unwrap();
743 let hdr2 = HeaderValue::from(&hdr);
744 assert_eq!(hdr2.as_bytes(), b"upgrade");
745 assert_eq!(hdr2.as_shared(), &Bytes::from_static(b"upgrade"));
746 let hdr2 = HeaderValue::from(hdr);
747 assert_eq!(hdr2.as_bytes(), b"upgrade");
748
749 let hdr = HeaderValue::try_from("upgrade".to_string()).unwrap();
750 assert_eq!(hdr.as_bytes(), b"upgrade");
751 let hdr = HeaderValue::try_from(&("upgrade".to_string())).unwrap();
752 assert_eq!(hdr.as_bytes(), b"upgrade");
753 let hdr = HeaderValue::try_from(ByteString::from("upgrade")).unwrap();
754 assert_eq!(hdr.as_bytes(), b"upgrade");
755 let hdr = HeaderValue::try_from(&ByteString::from("upgrade")).unwrap();
756 assert_eq!(hdr.as_bytes(), b"upgrade");
757 let hdr2 = HeaderValue::try_from(&ByteString::from("upgrade2")).unwrap();
758
759 assert_eq!(hdr, hdr);
760 assert_eq!(hdr, &hdr);
761 assert_eq!(hdr, "upgrade");
762 assert_eq!(hdr, "upgrade".to_string());
763 assert_eq!(hdr, b"upgrade".as_ref());
764 assert_eq!("upgrade", hdr);
765 assert_eq!("upgrade", &hdr);
766 assert_eq!("upgrade".to_string(), hdr);
767 assert_eq!(b"upgrade".as_ref(), &hdr);
768 assert!(hdr < hdr2);
769 assert!(hdr < &hdr2);
770 assert!(&hdr < &hdr2);
771 assert!(hdr2 > hdr);
772 assert!(&hdr2 > hdr);
773 assert!(&hdr < "upgrade2");
774 assert!(hdr < "upgrade2");
775 assert!(hdr < "upgrade2".to_string());
776 assert!(hdr < &b"upgrade2"[..]);
777 assert!(hdr < b"upgrade2"[..]);
778 assert_ne!(hdr, &b"upgrade2"[..]);
779 assert_ne!(hdr, b"upgrade2"[..]);
780 assert!("upgrade2" > hdr);
781 assert!("upgrade2" > &hdr);
782 assert!("upgrade2".to_string() > hdr);
783 assert!(b"upgrade2"[..] > hdr);
784 assert_ne!("upgrade2"[..], hdr);
785 }
786
787 #[test]
788 fn test_try_from() {
789 HeaderValue::try_from(vec![127]).unwrap_err();
790
791 let hdr = HeaderValue::try_from(Bytes::from_static(b"upgrade")).unwrap();
792 assert_eq!(hdr, "upgrade");
793 HeaderValue::try_from(Bytes::from_static(b"\n")).unwrap_err();
794 let hdr = HeaderValue::try_from(BytesMut::copy_from_slice(b"upgrade")).unwrap();
795 assert_eq!(hdr, "upgrade");
796 HeaderValue::try_from(BytesMut::copy_from_slice(b"\n")).unwrap_err();
797 }
798
799 #[test]
800 fn it_converts_using_try_from() {
801 assert!(HeaderValue::from_bytes(b"upgrade").is_ok());
802 }
803
804 #[test]
805 fn into_http_value() {
806 let hdr = HeaderValue::from_bytes(b"upgrade").unwrap();
807 let _ = http::header::HeaderValue::from(&hdr);
808 let _ = http::header::HeaderValue::from(hdr);
809 }
810
811 #[test]
812 fn test_fmt() {
813 let cases = &[
814 ("hello", "\"hello\""),
815 ("hello \"world\"", "\"hello \\\"world\\\"\""),
816 ("\u{7FFF}hello", "\"\\xe7\\xbf\\xbfhello\""),
817 ];
818
819 for &(value, expected) in cases {
820 let val = HeaderValue::from_bytes(value.as_bytes()).unwrap();
821 let actual = format!("{val:?}");
822 assert_eq!(expected, actual);
823 }
824
825 let mut sensitive = HeaderValue::from_static("password");
826 sensitive.set_sensitive(true);
827 assert_eq!("Sensitive", format!("{sensitive:?}"));
828
829 let s = format!("{}", InvalidHeaderValue { _priv: {} });
830 assert_eq!(s, "Invalid HTTP header value");
831
832 let s = format!("{}", ToStrError { _priv: {} });
833 assert_eq!(s, "failed to convert header to a str");
834 }
835
836 #[test]
837 #[should_panic(expected = "invalid header value")]
838 fn from_static_invalid() {
839 let src: &'static str = std::hint::black_box("bad\nvalue");
840 let _ = HeaderValue::from_static(src);
841 }
842}