1use bytes::{Bytes, BytesMut};
2
3use std::convert::TryFrom;
4use std::error::Error;
5use std::fmt::Write;
6use std::hash::{Hash, Hasher};
7use std::str::FromStr;
8use std::{cmp, fmt, str};
9
10use crate::header::name::HeaderName;
11
12/// Represents an HTTP header field value.
13///
14/// In practice, HTTP header field values are usually valid ASCII. However, the
15/// HTTP spec allows for a header value to contain opaque bytes as well. In this
16/// case, the header field value is not able to be represented as a string.
17///
18/// To handle this, the `HeaderValue` is useable as a type and can be compared
19/// with strings and implements `Debug`. A `to_str` fn is provided that returns
20/// an `Err` if the header value contains non visible ascii characters.
21#[derive(Clone)]
22pub struct HeaderValue {
23 inner: Bytes,
24 is_sensitive: bool,
25}
26
27/// A possible error when converting a `HeaderValue` from a string or byte
28/// slice.
29pub struct InvalidHeaderValue {
30 _priv: (),
31}
32
33/// A possible error when converting a `HeaderValue` to a string representation.
34///
35/// Header field values may contain opaque bytes, in which case it is not
36/// possible to represent the value as a string.
37#[derive(Debug)]
38pub struct ToStrError {
39 _priv: (),
40}
41
42impl HeaderValue {
43 /// Convert a static string to a `HeaderValue`.
44 ///
45 /// This function will not perform any copying, however the string is
46 /// checked to ensure that no invalid characters are present. Only visible
47 /// ASCII characters (32-127) are permitted.
48 ///
49 /// # Panics
50 ///
51 /// This function panics if the argument contains invalid header value
52 /// characters.
53 ///
54 /// Until [Allow panicking in constants](https://github.com/rust-lang/rfcs/pull/2345)
55 /// makes its way into stable, the panic message at compile-time is
56 /// going to look cryptic, but should at least point at your header value:
57 ///
58 /// ```text
59 /// error: any use of this value will cause an error
60 /// --> http/src/header/value.rs:67:17
61 /// |
62 /// 67 | ([] as [u8; 0])[0]; // Invalid header value
63 /// | ^^^^^^^^^^^^^^^^^^
64 /// | |
65 /// | index out of bounds: the length is 0 but the index is 0
66 /// | inside `HeaderValue::from_static` at http/src/header/value.rs:67:17
67 /// | inside `INVALID_HEADER` at src/main.rs:73:33
68 /// |
69 /// ::: src/main.rs:73:1
70 /// |
71 /// 73 | const INVALID_HEADER: HeaderValue = HeaderValue::from_static("жsome value");
72 /// | ----------------------------------------------------------------------------
73 /// ```
74 ///
75 /// # Examples
76 ///
77 /// ```
78 /// # use http::header::HeaderValue;
79 /// let val = HeaderValue::from_static("hello");
80 /// assert_eq!(val, "hello");
81 /// ```
82 #[inline]
83 #[allow(unconditional_panic)] // required for the panic circumvention
84 pub const fn from_static(src: &'static str) -> HeaderValue {
85 let bytes = src.as_bytes();
86 let mut i = 0;
87 while i < bytes.len() {
88 if !is_visible_ascii(bytes[i]) {
89 // TODO: When msrv is bumped to larger than 1.57, this should be
90 // replaced with `panic!` macro.
91 // https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html#panic-in-const-contexts
92 //
93 // See the panics section of this method's document for details.
94 #[allow(clippy::no_effect, clippy::out_of_bounds_indexing)]
95 ([] as [u8; 0])[0]; // Invalid header value
96 }
97 i += 1;
98 }
99
100 HeaderValue {
101 inner: Bytes::from_static(bytes),
102 is_sensitive: false,
103 }
104 }
105
106 /// Attempt to convert a string to a `HeaderValue`.
107 ///
108 /// If the argument contains invalid header value characters, an error is
109 /// returned. Only visible ASCII characters (32-127) are permitted. Use
110 /// `from_bytes` to create a `HeaderValue` that includes opaque octets
111 /// (128-255).
112 ///
113 /// This function is intended to be replaced in the future by a `TryFrom`
114 /// implementation once the trait is stabilized in std.
115 ///
116 /// # Examples
117 ///
118 /// ```
119 /// # use http::header::HeaderValue;
120 /// let val = HeaderValue::from_str("hello").unwrap();
121 /// assert_eq!(val, "hello");
122 /// ```
123 ///
124 /// An invalid value
125 ///
126 /// ```
127 /// # use http::header::HeaderValue;
128 /// let val = HeaderValue::from_str("\n");
129 /// assert!(val.is_err());
130 /// ```
131 #[inline]
132 #[allow(clippy::should_implement_trait)]
133 pub fn from_str(src: &str) -> Result<HeaderValue, InvalidHeaderValue> {
134 HeaderValue::try_from_generic(src, |s| Bytes::copy_from_slice(s.as_bytes()))
135 }
136
137 /// Converts a HeaderName into a HeaderValue
138 ///
139 /// Since every valid HeaderName is a valid HeaderValue this is done infallibly.
140 ///
141 /// # Examples
142 ///
143 /// ```
144 /// # use http::header::{HeaderValue, HeaderName};
145 /// # use http::header::ACCEPT;
146 /// let val = HeaderValue::from_name(ACCEPT);
147 /// assert_eq!(val, HeaderValue::from_bytes(b"accept").unwrap());
148 /// ```
149 #[inline]
150 pub fn from_name(name: HeaderName) -> HeaderValue {
151 name.into()
152 }
153
154 /// Attempt to convert a byte slice to a `HeaderValue`.
155 ///
156 /// If the argument contains invalid header value bytes, an error is
157 /// returned. Only byte values between 32 and 255 (inclusive) are permitted,
158 /// excluding byte 127 (DEL).
159 ///
160 /// This function is intended to be replaced in the future by a `TryFrom`
161 /// implementation once the trait is stabilized in std.
162 ///
163 /// # Examples
164 ///
165 /// ```
166 /// # use http::header::HeaderValue;
167 /// let val = HeaderValue::from_bytes(b"hello\xfa").unwrap();
168 /// assert_eq!(val, &b"hello\xfa"[..]);
169 /// ```
170 ///
171 /// An invalid value
172 ///
173 /// ```
174 /// # use http::header::HeaderValue;
175 /// let val = HeaderValue::from_bytes(b"\n");
176 /// assert!(val.is_err());
177 /// ```
178 #[inline]
179 pub fn from_bytes(src: &[u8]) -> Result<HeaderValue, InvalidHeaderValue> {
180 HeaderValue::try_from_generic(src, Bytes::copy_from_slice)
181 }
182
183 /// Attempt to convert a `Bytes` buffer to a `HeaderValue`.
184 ///
185 /// This will try to prevent a copy if the type passed is the type used
186 /// internally, and will copy the data if it is not.
187 pub fn from_maybe_shared<T>(src: T) -> Result<HeaderValue, InvalidHeaderValue>
188 where
189 T: AsRef<[u8]> + 'static,
190 {
191 if_downcast_into!(T, Bytes, src, {
192 return HeaderValue::from_shared(src);
193 });
194
195 HeaderValue::from_bytes(src.as_ref())
196 }
197
198 /// Convert a `Bytes` directly into a `HeaderValue` without validating.
199 ///
200 /// This function does NOT validate that illegal bytes are not contained
201 /// within the buffer.
202 ///
203 /// ## Panics
204 /// In a debug build this will panic if `src` is not valid UTF-8.
205 ///
206 /// ## Safety
207 /// `src` must contain valid UTF-8. In a release build it is undefined
208 /// behaviour to call this with `src` that is not valid UTF-8.
209 pub unsafe fn from_maybe_shared_unchecked<T>(src: T) -> HeaderValue
210 where
211 T: AsRef<[u8]> + 'static,
212 {
213 if cfg!(debug_assertions) {
214 match HeaderValue::from_maybe_shared(src) {
215 Ok(val) => val,
216 Err(_err) => {
217 panic!("HeaderValue::from_maybe_shared_unchecked() with invalid bytes");
218 }
219 }
220 } else {
221 if_downcast_into!(T, Bytes, src, {
222 return HeaderValue {
223 inner: src,
224 is_sensitive: false,
225 };
226 });
227
228 let src = Bytes::copy_from_slice(src.as_ref());
229 HeaderValue {
230 inner: src,
231 is_sensitive: false,
232 }
233 }
234 }
235
236 fn from_shared(src: Bytes) -> Result<HeaderValue, InvalidHeaderValue> {
237 HeaderValue::try_from_generic(src, std::convert::identity)
238 }
239
240 fn try_from_generic<T: AsRef<[u8]>, F: FnOnce(T) -> Bytes>(
241 src: T,
242 into: F,
243 ) -> Result<HeaderValue, InvalidHeaderValue> {
244 for &b in src.as_ref() {
245 if !is_valid(b) {
246 return Err(InvalidHeaderValue { _priv: () });
247 }
248 }
249 Ok(HeaderValue {
250 inner: into(src),
251 is_sensitive: false,
252 })
253 }
254
255 /// Yields a `&str` slice if the `HeaderValue` only contains visible ASCII
256 /// chars.
257 ///
258 /// This function will perform a scan of the header value, checking all the
259 /// characters.
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// # use http::header::HeaderValue;
265 /// let val = HeaderValue::from_static("hello");
266 /// assert_eq!(val.to_str().unwrap(), "hello");
267 /// ```
268 pub fn to_str(&self) -> Result<&str, ToStrError> {
269 let bytes = self.as_ref();
270
271 for &b in bytes {
272 if !is_visible_ascii(b) {
273 return Err(ToStrError { _priv: () });
274 }
275 }
276
277 unsafe { Ok(str::from_utf8_unchecked(bytes)) }
278 }
279
280 /// Returns the length of `self`.
281 ///
282 /// This length is in bytes.
283 ///
284 /// # Examples
285 ///
286 /// ```
287 /// # use http::header::HeaderValue;
288 /// let val = HeaderValue::from_static("hello");
289 /// assert_eq!(val.len(), 5);
290 /// ```
291 #[inline]
292 pub fn len(&self) -> usize {
293 self.as_ref().len()
294 }
295
296 /// Returns true if the `HeaderValue` has a length of zero bytes.
297 ///
298 /// # Examples
299 ///
300 /// ```
301 /// # use http::header::HeaderValue;
302 /// let val = HeaderValue::from_static("");
303 /// assert!(val.is_empty());
304 ///
305 /// let val = HeaderValue::from_static("hello");
306 /// assert!(!val.is_empty());
307 /// ```
308 #[inline]
309 pub fn is_empty(&self) -> bool {
310 self.len() == 0
311 }
312
313 /// Converts a `HeaderValue` to a byte slice.
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// # use http::header::HeaderValue;
319 /// let val = HeaderValue::from_static("hello");
320 /// assert_eq!(val.as_bytes(), b"hello");
321 /// ```
322 #[inline]
323 pub fn as_bytes(&self) -> &[u8] {
324 self.as_ref()
325 }
326
327 /// Mark that the header value represents sensitive information.
328 ///
329 /// # Examples
330 ///
331 /// ```
332 /// # use http::header::HeaderValue;
333 /// let mut val = HeaderValue::from_static("my secret");
334 ///
335 /// val.set_sensitive(true);
336 /// assert!(val.is_sensitive());
337 ///
338 /// val.set_sensitive(false);
339 /// assert!(!val.is_sensitive());
340 /// ```
341 #[inline]
342 pub fn set_sensitive(&mut self, val: bool) {
343 self.is_sensitive = val;
344 }
345
346 /// Returns `true` if the value represents sensitive data.
347 ///
348 /// Sensitive data could represent passwords or other data that should not
349 /// be stored on disk or in memory. By marking header values as sensitive,
350 /// components using this crate can be instructed to treat them with special
351 /// care for security reasons. For example, caches can avoid storing
352 /// sensitive values, and HPACK encoders used by HTTP/2.0 implementations
353 /// can choose not to compress them.
354 ///
355 /// Additionally, sensitive values will be masked by the `Debug`
356 /// implementation of `HeaderValue`.
357 ///
358 /// Note that sensitivity is not factored into equality or ordering.
359 ///
360 /// # Examples
361 ///
362 /// ```
363 /// # use http::header::HeaderValue;
364 /// let mut val = HeaderValue::from_static("my secret");
365 ///
366 /// val.set_sensitive(true);
367 /// assert!(val.is_sensitive());
368 ///
369 /// val.set_sensitive(false);
370 /// assert!(!val.is_sensitive());
371 /// ```
372 #[inline]
373 pub fn is_sensitive(&self) -> bool {
374 self.is_sensitive
375 }
376}
377
378impl AsRef<[u8]> for HeaderValue {
379 #[inline]
380 fn as_ref(&self) -> &[u8] {
381 self.inner.as_ref()
382 }
383}
384
385impl fmt::Debug for HeaderValue {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 if self.is_sensitive {
388 f.write_str("Sensitive")
389 } else {
390 f.write_str("\"")?;
391 let mut from = 0;
392 let bytes = self.as_bytes();
393 for (i, &b) in bytes.iter().enumerate() {
394 if !is_visible_ascii(b) || b == b'"' {
395 if from != i {
396 f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..i]) })?;
397 }
398 if b == b'"' {
399 f.write_str("\\\"")?;
400 } else {
401 write!(f, "\\x{:x}", b)?;
402 }
403 from = i + 1;
404 }
405 }
406
407 f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..]) })?;
408 f.write_str("\"")
409 }
410 }
411}
412
413impl From<HeaderName> for HeaderValue {
414 #[inline]
415 fn from(h: HeaderName) -> HeaderValue {
416 HeaderValue {
417 inner: h.into_bytes(),
418 is_sensitive: false,
419 }
420 }
421}
422
423macro_rules! from_integers {
424 ($($name:ident: $t:ident => $max_len:expr),*) => {$(
425 impl From<$t> for HeaderValue {
426 fn from(num: $t) -> HeaderValue {
427 let mut buf = BytesMut::with_capacity($max_len);
428 let _ = buf.write_str(::itoa::Buffer::new().format(num));
429 HeaderValue {
430 inner: buf.freeze(),
431 is_sensitive: false,
432 }
433 }
434 }
435
436 #[test]
437 fn $name() {
438 let n: $t = 55;
439 let val = HeaderValue::from(n);
440 assert_eq!(val, &n.to_string());
441
442 let n = ::std::$t::MAX;
443 let val = HeaderValue::from(n);
444 assert_eq!(val, &n.to_string());
445 }
446 )*};
447}
448
449from_integers! {
450 // integer type => maximum decimal length
451
452 // u8 purposely left off... HeaderValue::from(b'3') could be confusing
453 from_u16: u16 => 5,
454 from_i16: i16 => 6,
455 from_u32: u32 => 10,
456 from_i32: i32 => 11,
457 from_u64: u64 => 20,
458 from_i64: i64 => 20
459}
460
461#[cfg(target_pointer_width = "16")]
462from_integers! {
463 from_usize: usize => 5,
464 from_isize: isize => 6
465}
466
467#[cfg(target_pointer_width = "32")]
468from_integers! {
469 from_usize: usize => 10,
470 from_isize: isize => 11
471}
472
473#[cfg(target_pointer_width = "64")]
474from_integers! {
475 from_usize: usize => 20,
476 from_isize: isize => 20
477}
478
479#[cfg(test)]
480mod from_header_name_tests {
481 use super::*;
482 use crate::header::map::HeaderMap;
483 use crate::header::name;
484
485 #[test]
486 fn it_can_insert_header_name_as_header_value() {
487 let mut map = HeaderMap::new();
488 map.insert(name::UPGRADE, name::SEC_WEBSOCKET_PROTOCOL.into());
489 map.insert(
490 name::ACCEPT,
491 name::HeaderName::from_bytes(b"hello-world").unwrap().into(),
492 );
493
494 assert_eq!(
495 map.get(name::UPGRADE).unwrap(),
496 HeaderValue::from_bytes(b"sec-websocket-protocol").unwrap()
497 );
498
499 assert_eq!(
500 map.get(name::ACCEPT).unwrap(),
501 HeaderValue::from_bytes(b"hello-world").unwrap()
502 );
503 }
504}
505
506impl FromStr for HeaderValue {
507 type Err = InvalidHeaderValue;
508
509 #[inline]
510 fn from_str(s: &str) -> Result<HeaderValue, Self::Err> {
511 HeaderValue::from_str(src:s)
512 }
513}
514
515impl<'a> From<&'a HeaderValue> for HeaderValue {
516 #[inline]
517 fn from(t: &'a HeaderValue) -> Self {
518 t.clone()
519 }
520}
521
522impl<'a> TryFrom<&'a str> for HeaderValue {
523 type Error = InvalidHeaderValue;
524
525 #[inline]
526 fn try_from(t: &'a str) -> Result<Self, Self::Error> {
527 t.parse()
528 }
529}
530
531impl<'a> TryFrom<&'a String> for HeaderValue {
532 type Error = InvalidHeaderValue;
533 #[inline]
534 fn try_from(s: &'a String) -> Result<Self, Self::Error> {
535 Self::from_bytes(src:s.as_bytes())
536 }
537}
538
539impl<'a> TryFrom<&'a [u8]> for HeaderValue {
540 type Error = InvalidHeaderValue;
541
542 #[inline]
543 fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
544 HeaderValue::from_bytes(src:t)
545 }
546}
547
548impl TryFrom<String> for HeaderValue {
549 type Error = InvalidHeaderValue;
550
551 #[inline]
552 fn try_from(t: String) -> Result<Self, Self::Error> {
553 HeaderValue::from_shared(src:t.into())
554 }
555}
556
557impl TryFrom<Vec<u8>> for HeaderValue {
558 type Error = InvalidHeaderValue;
559
560 #[inline]
561 fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
562 HeaderValue::from_shared(src:vec.into())
563 }
564}
565
566#[cfg(test)]
567mod try_from_header_name_tests {
568 use super::*;
569 use crate::header::name;
570
571 #[test]
572 fn it_converts_using_try_from() {
573 assert_eq!(
574 HeaderValue::try_from(name::UPGRADE).unwrap(),
575 HeaderValue::from_bytes(b"upgrade").unwrap()
576 );
577 }
578}
579
580const fn is_visible_ascii(b: u8) -> bool {
581 b >= 32 && b < 127 || b == b'\t'
582}
583
584#[inline]
585fn is_valid(b: u8) -> bool {
586 b >= 32 && b != 127 || b == b'\t'
587}
588
589impl fmt::Debug for InvalidHeaderValue {
590 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
591 fDebugStruct<'_, '_>.debug_struct(name:"InvalidHeaderValue")
592 // skip _priv noise
593 .finish()
594 }
595}
596
597impl fmt::Display for InvalidHeaderValue {
598 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599 f.write_str(data:"failed to parse header value")
600 }
601}
602
603impl Error for InvalidHeaderValue {}
604
605impl fmt::Display for ToStrError {
606 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
607 f.write_str(data:"failed to convert header to a str")
608 }
609}
610
611impl Error for ToStrError {}
612
613// ===== PartialEq / PartialOrd =====
614
615impl Hash for HeaderValue {
616 fn hash<H: Hasher>(&self, state: &mut H) {
617 self.inner.hash(state);
618 }
619}
620
621impl PartialEq for HeaderValue {
622 #[inline]
623 fn eq(&self, other: &HeaderValue) -> bool {
624 self.inner == other.inner
625 }
626}
627
628impl Eq for HeaderValue {}
629
630impl PartialOrd for HeaderValue {
631 #[inline]
632 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
633 Some(self.cmp(other))
634 }
635}
636
637impl Ord for HeaderValue {
638 #[inline]
639 fn cmp(&self, other: &Self) -> cmp::Ordering {
640 self.inner.cmp(&other.inner)
641 }
642}
643
644impl PartialEq<str> for HeaderValue {
645 #[inline]
646 fn eq(&self, other: &str) -> bool {
647 self.inner == other.as_bytes()
648 }
649}
650
651impl PartialEq<[u8]> for HeaderValue {
652 #[inline]
653 fn eq(&self, other: &[u8]) -> bool {
654 self.inner == other
655 }
656}
657
658impl PartialOrd<str> for HeaderValue {
659 #[inline]
660 fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
661 (*self.inner).partial_cmp(other.as_bytes())
662 }
663}
664
665impl PartialOrd<[u8]> for HeaderValue {
666 #[inline]
667 fn partial_cmp(&self, other: &[u8]) -> Option<cmp::Ordering> {
668 (*self.inner).partial_cmp(other)
669 }
670}
671
672impl PartialEq<HeaderValue> for str {
673 #[inline]
674 fn eq(&self, other: &HeaderValue) -> bool {
675 *other == *self
676 }
677}
678
679impl PartialEq<HeaderValue> for [u8] {
680 #[inline]
681 fn eq(&self, other: &HeaderValue) -> bool {
682 *other == *self
683 }
684}
685
686impl PartialOrd<HeaderValue> for str {
687 #[inline]
688 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
689 self.as_bytes().partial_cmp(other.as_bytes())
690 }
691}
692
693impl PartialOrd<HeaderValue> for [u8] {
694 #[inline]
695 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
696 self.partial_cmp(other.as_bytes())
697 }
698}
699
700impl PartialEq<String> for HeaderValue {
701 #[inline]
702 fn eq(&self, other: &String) -> bool {
703 *self == other[..]
704 }
705}
706
707impl PartialOrd<String> for HeaderValue {
708 #[inline]
709 fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
710 self.inner.partial_cmp(other.as_bytes())
711 }
712}
713
714impl PartialEq<HeaderValue> for String {
715 #[inline]
716 fn eq(&self, other: &HeaderValue) -> bool {
717 *other == *self
718 }
719}
720
721impl PartialOrd<HeaderValue> for String {
722 #[inline]
723 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
724 self.as_bytes().partial_cmp(other.as_bytes())
725 }
726}
727
728impl<'a> PartialEq<HeaderValue> for &'a HeaderValue {
729 #[inline]
730 fn eq(&self, other: &HeaderValue) -> bool {
731 **self == *other
732 }
733}
734
735impl<'a> PartialOrd<HeaderValue> for &'a HeaderValue {
736 #[inline]
737 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
738 (**self).partial_cmp(other)
739 }
740}
741
742impl<'a, T: ?Sized> PartialEq<&'a T> for HeaderValue
743where
744 HeaderValue: PartialEq<T>,
745{
746 #[inline]
747 fn eq(&self, other: &&'a T) -> bool {
748 *self == **other
749 }
750}
751
752impl<'a, T: ?Sized> PartialOrd<&'a T> for HeaderValue
753where
754 HeaderValue: PartialOrd<T>,
755{
756 #[inline]
757 fn partial_cmp(&self, other: &&'a T) -> Option<cmp::Ordering> {
758 self.partial_cmp(*other)
759 }
760}
761
762impl<'a> PartialEq<HeaderValue> for &'a str {
763 #[inline]
764 fn eq(&self, other: &HeaderValue) -> bool {
765 *other == *self
766 }
767}
768
769impl<'a> PartialOrd<HeaderValue> for &'a str {
770 #[inline]
771 fn partial_cmp(&self, other: &HeaderValue) -> Option<cmp::Ordering> {
772 self.as_bytes().partial_cmp(other.as_bytes())
773 }
774}
775
776#[test]
777fn test_try_from() {
778 HeaderValue::try_from(vec![127]).unwrap_err();
779}
780
781#[test]
782fn test_debug() {
783 let cases = &[
784 ("hello", "\"hello\""),
785 ("hello \"world\"", "\"hello \\\"world\\\"\""),
786 ("\u{7FFF}hello", "\"\\xe7\\xbf\\xbfhello\""),
787 ];
788
789 for &(value, expected) in cases {
790 let val = HeaderValue::from_bytes(value.as_bytes()).unwrap();
791 let actual = format!("{:?}", val);
792 assert_eq!(expected, actual);
793 }
794
795 let mut sensitive = HeaderValue::from_static("password");
796 sensitive.set_sensitive(true);
797 assert_eq!("Sensitive", format!("{:?}", sensitive));
798}
799