Skip to content

Commit cd6a422

Browse files
committed
feat: Extend HeaderValue to provide access to/semantics of backing Bytes
This change adds, - `HeaderValue::from_shared[_unchecked]` which directly accept a `Bytes` object as an argument. This should allow for lwss copying when constructing `HeaderValue`s from pre-existing `Bytes` buffers. - `HeaderValue::as_shared` which returns a clone of the `Bytes` object that backs the given `HeaderValue`. - `HeaderValue::into_shared` which is a self-consuming version of the above. - `HeaderValue::[try_]slice` which mirrors the `Bytes::slice` interface, and provides copy-free construction of sub-sliced `HeaderValue`s. - `HeaderName::into_shared` which is a rename of the previously `pub(crate)` `.into_bytes` method. Allows access to a `Bytes` representation of a HeaderName, though it will usually copy via `Bytes::from_static`.
1 parent 5f0c866 commit cd6a422

2 files changed

Lines changed: 218 additions & 6 deletions

File tree

src/header/name.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,24 @@ impl HeaderName {
12701270
}
12711271
}
12721272

1273-
pub(super) fn into_bytes(self) -> Bytes {
1273+
/// Creates a [`Bytes`] object from this `HeaderName`.
1274+
///
1275+
/// This function will _likely_ copy the standard string representation of
1276+
/// this `HeaderName` into a new `Bytes` object. If this `HeaderName` is a
1277+
/// non-standard header name (that just so happens to be backed by a `Bytes`
1278+
/// object), the copy may be elided in favor of claiming a reference-counted
1279+
/// borrow of the shared memory region that backs this `HeaderName`. Expect
1280+
/// the former behavior.
1281+
///
1282+
/// # Examples
1283+
///
1284+
/// ```
1285+
/// # use http::header::*;
1286+
/// # use bytes::Bytes;
1287+
/// let hdr = HeaderName::from_static("content-length");
1288+
/// assert_eq!(hdr.into_shared(), Bytes::from_static(b"content-length"));
1289+
/// ```
1290+
pub fn into_shared(self) -> Bytes {
12741291
self.inner.into()
12751292
}
12761293
}

src/header/value.rs

Lines changed: 200 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bytes::{Bytes, BytesMut};
22

3+
use core::ops::RangeBounds;
34
use std::convert::TryFrom;
45
use std::error::Error;
56
use std::fmt::Write;
@@ -152,6 +153,62 @@ impl HeaderValue {
152153
HeaderValue::try_from_generic(src, Bytes::copy_from_slice)
153154
}
154155

156+
/// Convert a [`Bytes`] into a `HeaderValue`.
157+
///
158+
/// This function will not perform any copying.
159+
///
160+
/// If the argument contains invalid header value bytes, an error is
161+
/// returned. Only byte values between 32 and 255 (inclusive) are permitted,
162+
/// excluding byte 127 (DEL).
163+
///
164+
/// # Examples
165+
///
166+
/// ```
167+
/// # use http::header::HeaderValue;
168+
/// # use bytes::Bytes;
169+
/// let bytes = Bytes::from_static(b"hello\xfa");
170+
/// let val = HeaderValue::from_shared(bytes).unwrap();
171+
/// assert_eq!(val, &b"hello\xfa"[..]);
172+
/// ```
173+
pub fn from_shared(src: Bytes) -> Result<HeaderValue, InvalidHeaderValue> {
174+
for &b in src.as_ref() {
175+
if !is_valid(b) {
176+
return Err(InvalidHeaderValue { _priv: () });
177+
}
178+
}
179+
Ok(HeaderValue {
180+
inner: src,
181+
is_sensitive: false,
182+
})
183+
}
184+
185+
/// Convert a [`Bytes`] directly into a `HeaderValue` without validating.
186+
///
187+
/// This function does NOT validate that illegal bytes are not contained
188+
/// within the buffer.
189+
///
190+
/// ## Panics
191+
/// In a debug build this will panic if `src` is not valid UTF-8.
192+
///
193+
/// ## Safety
194+
/// `src` must contain valid UTF-8. In a release build it is undefined
195+
/// behaviour to call this with `src` that is not valid UTF-8.
196+
pub unsafe fn from_shared_unchecked(src: Bytes) -> HeaderValue {
197+
if cfg!(debug_assertions) {
198+
match HeaderValue::from_shared(src) {
199+
Ok(val) => val,
200+
Err(_err) => {
201+
panic!("HeaderValue::from_shared_unchecked() with invalid bytes");
202+
}
203+
}
204+
} else {
205+
return HeaderValue {
206+
inner: src,
207+
is_sensitive: false,
208+
};
209+
}
210+
}
211+
155212
/// Attempt to convert a `Bytes` buffer to a `HeaderValue`.
156213
///
157214
/// This will try to prevent a copy if the type passed is the type used
@@ -205,10 +262,6 @@ impl HeaderValue {
205262
}
206263
}
207264

208-
fn from_shared(src: Bytes) -> Result<HeaderValue, InvalidHeaderValue> {
209-
HeaderValue::try_from_generic(src, std::convert::identity)
210-
}
211-
212265
fn try_from_generic<T: AsRef<[u8]>, F: FnOnce(T) -> Bytes>(
213266
src: T,
214267
into: F,
@@ -296,6 +349,148 @@ impl HeaderValue {
296349
self.as_ref()
297350
}
298351

352+
/// Creates a new [`Bytes`] object from this `HeaderValue`.
353+
///
354+
/// This function will not perform any copying. Rather, it allocates a new,
355+
/// owned `Bytes` object (which will be, at the time of writing, roughly 4
356+
/// pointers in size) which will claim a reference-counted borrow of the
357+
/// shared memory region that backs this `HeaderValue`. Because of the
358+
/// Arc-like semantics of `Bytes` and `HeaderValue`s, this `HeaderValue` and
359+
/// the returned `Bytes` can be dropped in any order and the shared memory
360+
/// region will remain valid for the other.
361+
///
362+
/// # Examples
363+
///
364+
/// ```
365+
/// # use http::header::HeaderValue;
366+
/// # use bytes::Bytes;
367+
/// let val = HeaderValue::from_static("hello");
368+
/// assert_eq!(val.as_shared(), Bytes::from_static(b"hello"));
369+
/// ```
370+
#[inline]
371+
pub fn as_shared(&self) -> Bytes {
372+
self.inner.clone()
373+
}
374+
375+
/// Return the inner [`Bytes`] object, consuming this `HeaderValue`.
376+
///
377+
/// Unlike [`as_shared()`], this method will not increment the reference
378+
/// count of the shared memory region that backs this `HeaderValue`. This
379+
/// method consumes `self` to do so.
380+
///
381+
/// # Examples
382+
///
383+
/// ```
384+
/// # use http::header::HeaderValue;
385+
/// # use bytes::Bytes;
386+
/// let val = HeaderValue::from_static("hello");
387+
/// assert_eq!(val.into_shared(), Bytes::from_static(b"hello"));
388+
/// ```
389+
///
390+
/// [`as_shared()`]: Self::as_shared()
391+
#[inline]
392+
pub fn into_shared(self) -> Bytes {
393+
self.inner
394+
}
395+
396+
/// Returns a slice of `self` over the provided range.
397+
///
398+
///
399+
/// This function will not perform any copying. Rather, it allocates a new,
400+
/// owned `HeaderValue` object (which will be, at the time of writing,
401+
/// roughly 5 pointers in size) which will claim a reference-counted borrow
402+
/// of the shared memory region that backs this `HeaderValue`. Because of the
403+
/// Arc-like semantics of`HeaderValue`s, this and the resulting
404+
/// `HeaderValue` can be dropped in any order and the shared memory region
405+
/// will remain valid for the other.
406+
///
407+
/// If the `range` would be out of bounds, this function will return `None`.
408+
///
409+
/// # Examples
410+
///
411+
/// ```
412+
/// # use http::header::HeaderValue;
413+
/// let val = HeaderValue::from_static(r#"W/"67ab43", "54ed21", "7892dd""#);
414+
/// let slice_1 = val.try_slice(0..10).unwrap();
415+
/// let slice_3 = val.try_slice(22..30).unwrap();
416+
/// assert_eq!(slice_1, r#"W/"67ab43""#);
417+
/// assert_eq!(slice_3, "\"7892dd\"");
418+
/// assert!(val.try_slice(0..31).is_none());
419+
/// ```
420+
#[inline]
421+
pub fn try_slice(&self, range: impl RangeBounds<usize>) -> Option<HeaderValue> {
422+
use core::ops::Bound;
423+
424+
let len = self.len();
425+
426+
let begin = match range.start_bound() {
427+
Bound::Included(&n) => n,
428+
Bound::Excluded(&n) => {
429+
if let Some(n) = n.checked_add(1) {
430+
n
431+
} else {
432+
return None;
433+
}
434+
}
435+
Bound::Unbounded => 0,
436+
};
437+
438+
let end = match range.end_bound() {
439+
Bound::Included(&n) => {
440+
if let Some(n) = n.checked_add(1) {
441+
n
442+
} else {
443+
return None;
444+
}
445+
}
446+
Bound::Excluded(&n) => n,
447+
Bound::Unbounded => len,
448+
};
449+
450+
if begin > end {
451+
return None;
452+
}
453+
if end > len {
454+
return None;
455+
}
456+
457+
Some(self.slice(range))
458+
}
459+
460+
/// Returns a slice of `self` over the provided range.
461+
///
462+
///
463+
/// This function will not perform any copying. Rather, it allocates a new,
464+
/// owned `HeaderValue` object (which will be, at the time of writing,
465+
/// roughly 5 pointers in size) which will claim a reference-counted borrow
466+
/// of the shared memory region that backs this `HeaderValue`. Because of the
467+
/// Arc-like semantics of`HeaderValue`s, this and the resulting
468+
/// `HeaderValue` can be dropped in any order and the shared memory region
469+
/// will remain valid for the other.
470+
///
471+
/// # Panics
472+
///
473+
/// Requires that `begin <= end` and `end <= self.len()`, otherwise slicing
474+
/// will panic.
475+
///
476+
/// # Examples
477+
///
478+
/// ```
479+
/// # use http::header::HeaderValue;
480+
/// let val = HeaderValue::from_static(r#"W/"67ab43", "54ed21", "7892dd""#);
481+
/// let slice_1 = val.slice(0..10);
482+
/// let slice_3 = val.slice(22..30);
483+
/// assert_eq!(slice_1, r#"W/"67ab43""#);
484+
/// assert_eq!(slice_3, "\"7892dd\"");
485+
/// ```
486+
#[inline]
487+
pub fn slice(&self, range: impl RangeBounds<usize>) -> HeaderValue {
488+
let inner_slice = self.inner.slice(range);
489+
// SAFETY: Any subslice of `self.inner` should be just as valid for use
490+
// as a HeaderValue as the full `self.inner` buffer.
491+
unsafe { HeaderValue::from_shared_unchecked(inner_slice) }
492+
}
493+
299494
/// Mark that the header value represents sensitive information.
300495
///
301496
/// # Examples
@@ -386,7 +581,7 @@ impl From<HeaderName> for HeaderValue {
386581
#[inline]
387582
fn from(h: HeaderName) -> HeaderValue {
388583
HeaderValue {
389-
inner: h.into_bytes(),
584+
inner: h.into_shared(),
390585
is_sensitive: false,
391586
}
392587
}

0 commit comments

Comments
 (0)