Skip to content

Commit bb75355

Browse files
committed
feat: Extend HeaderValue to provide access to/semantics of backing Bytes
This change adds (or makes public), - `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`. - `Uri::from_shared` which was previously `pub(crate)` and is now `pub` with an expanded doc comment.
1 parent 4d18d3e commit bb75355

3 files changed

Lines changed: 232 additions & 16 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: 202 additions & 6 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;
@@ -157,6 +158,62 @@ impl HeaderValue {
157158
HeaderValue::try_from_generic(src, Bytes::copy_from_slice, is_valid_ascii_or_opaque_byte)
158159
}
159160

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

213-
fn from_shared(src: Bytes) -> Result<HeaderValue, InvalidHeaderValue> {
214-
HeaderValue::try_from_generic(src, std::convert::identity, is_valid_ascii_or_opaque_byte)
215-
}
216-
217270
fn try_from_generic<T: AsRef<[u8]>, F: FnOnce(T) -> Bytes, V: Fn(u8) -> bool>(
218271
src: T,
219272
into: F,
@@ -308,6 +361,148 @@ impl HeaderValue {
308361
self.as_ref()
309362
}
310363

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

759+
#[inline]
564760
const fn is_valid_ascii(b: u8) -> bool {
565761
b >= 32 && b < 127 || b == b'\t'
566762
}
@@ -569,7 +765,7 @@ const fn is_valid_ascii(b: u8) -> bool {
569765
// may contain opaque bytes, even though those bytes cannot be exposed by
570766
// `HeaderValue::to_str`.
571767
#[inline]
572-
fn is_valid_ascii_or_opaque_byte(b: u8) -> bool {
768+
const fn is_valid_ascii_or_opaque_byte(b: u8) -> bool {
573769
b >= 32 && b != 127 || b == b'\t'
574770
}
575771

src/uri/mod.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -289,19 +289,22 @@ impl Uri {
289289
Uri::try_from(src.as_ref())
290290
}
291291

292-
// Not public while `bytes` is unstable.
293-
fn from_shared(s: Bytes) -> Result<Uri, InvalidUri> {
292+
/// Convert a [`Bytes`] into a `Uri`.
293+
///
294+
/// This function will not perform any copying, however the value will be
295+
/// checked to ensure that it is valid.
296+
pub fn from_shared(src: Bytes) -> Result<Uri, InvalidUri> {
294297
use self::ErrorKind::*;
295298

296-
if s.len() > MAX_LEN {
299+
if src.len() > MAX_LEN {
297300
return Err(TooLong.into());
298301
}
299302

300-
match s.len() {
303+
match src.len() {
301304
0 => {
302305
return Err(Empty.into());
303306
}
304-
1 => match s[0] {
307+
1 => match src[0] {
305308
b'/' => {
306309
return Ok(Uri {
307310
scheme: Scheme::empty(),
@@ -317,7 +320,7 @@ impl Uri {
317320
});
318321
}
319322
_ => {
320-
let authority = Authority::from_shared(s)?;
323+
let authority = Authority::from_shared(src)?;
321324

322325
return Ok(Uri {
323326
scheme: Scheme::empty(),
@@ -329,15 +332,15 @@ impl Uri {
329332
_ => {}
330333
}
331334

332-
if s[0] == b'/' {
335+
if src[0] == b'/' {
333336
return Ok(Uri {
334337
scheme: Scheme::empty(),
335338
authority: Authority::empty(),
336-
path_and_query: PathAndQuery::from_shared(s)?,
339+
path_and_query: PathAndQuery::from_shared(src)?,
337340
});
338341
}
339342

340-
parse_full(s)
343+
parse_full(src)
341344
}
342345

343346
/// Convert a `Uri` from a static string.

0 commit comments

Comments
 (0)