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