Skip to content

Commit 73f1be3

Browse files
committed
Merge branch 'master' into headers_encoding
2 parents af3b26b + ad331d0 commit 73f1be3

File tree

8 files changed

+114
-19
lines changed

8 files changed

+114
-19
lines changed

Cargo.toml

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ authors = ["Sean McArthur <[email protected]>", "Jose Quintana <joseluisq.net
1010
keywords = ["http", "headers", "hyper", "hyperium"]
1111
categories = ["web-programming"]
1212
rust-version = "1.56"
13+
rust-version = "1.56"
1314

1415
[workspace]
1516
members = [
@@ -18,9 +19,9 @@ members = [
1819
]
1920

2021
[dependencies]
21-
http = "0.2.0"
22-
headers-core = { version = "0.2" }
23-
base64 = "0.21.3"
22+
http = "1.0.0"
23+
headers-core = { version = "0.3" }
24+
base64 = "0.21.5"
2425
bytes = "1"
2526
mime = "0.3.14"
2627
sha1 = "0.10"

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2014-2019 Sean McArthur
1+
Copyright (c) 2014-2023 Sean McArthur
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

headers-core/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "headers-core"
3-
version = "0.2.0" # don't forget to update html_root_url
3+
version = "0.3.0" # don't forget to update html_root_url
44
description = "typed HTTP headers core trait"
55
license = "MIT"
66
readme = "README.md"
@@ -10,4 +10,4 @@ authors = ["Sean McArthur <[email protected]>"]
1010
keywords = ["http", "headers", "hyper", "hyperium"]
1111

1212
[dependencies]
13-
http = "0.2.0"
13+
http = "1.0.0"

headers-core/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![deny(missing_docs)]
22
#![deny(missing_debug_implementations)]
33
#![cfg_attr(test, deny(warnings))]
4-
#![doc(html_root_url = "https://docs.rs/headers-core/0.2.0")]
4+
#![doc(html_root_url = "https://docs.rs/headers-core/0.3.0")]
55

66
//! # headers-core
77
//!

src/common/authorization.rs

+33-5
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ impl<C: Credentials> ::Header for Authorization<C> {
8282
.next()
8383
.and_then(|val| {
8484
let slice = val.as_bytes();
85-
if slice.starts_with(C::SCHEME.as_bytes())
86-
&& slice.len() > C::SCHEME.len()
85+
if slice.len() > C::SCHEME.len()
8786
&& slice[C::SCHEME.len()] == b' '
87+
&& slice[..C::SCHEME.len()].eq_ignore_ascii_case(C::SCHEME.as_bytes())
8888
{
8989
C::decode(val).map(Authorization)
9090
} else {
@@ -151,7 +151,7 @@ impl Credentials for Basic {
151151

152152
fn decode(value: &HeaderValue) -> Option<Self> {
153153
debug_assert!(
154-
value.as_bytes().starts_with(b"Basic "),
154+
value.as_bytes()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()),
155155
"HeaderValue to decode should start with \"Basic ..\", received = {:?}",
156156
value,
157157
);
@@ -186,7 +186,7 @@ pub struct Bearer(HeaderValueString);
186186
impl Bearer {
187187
/// View the token part as a `&str`.
188188
pub fn token(&self) -> &str {
189-
&self.0.as_str()["Bearer ".len()..]
189+
self.0.as_str()["Bearer ".len()..].trim_start()
190190
}
191191
}
192192

@@ -195,7 +195,7 @@ impl Credentials for Bearer {
195195

196196
fn decode(value: &HeaderValue) -> Option<Self> {
197197
debug_assert!(
198-
value.as_bytes().starts_with(b"Bearer "),
198+
value.as_bytes()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()),
199199
"HeaderValue to decode should start with \"Bearer ..\", received = {:?}",
200200
value,
201201
);
@@ -252,6 +252,22 @@ mod tests {
252252
assert_eq!(auth.0.password(), "open sesame");
253253
}
254254

255+
#[test]
256+
fn basic_decode_case_insensitive() {
257+
let auth: Authorization<Basic> =
258+
test_decode(&["basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
259+
assert_eq!(auth.0.username(), "Aladdin");
260+
assert_eq!(auth.0.password(), "open sesame");
261+
}
262+
263+
#[test]
264+
fn basic_decode_extra_whitespaces() {
265+
let auth: Authorization<Basic> =
266+
test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap();
267+
assert_eq!(auth.0.username(), "Aladdin");
268+
assert_eq!(auth.0.password(), "open sesame");
269+
}
270+
255271
#[test]
256272
fn basic_decode_no_password() {
257273
let auth: Authorization<Basic> = test_decode(&["Basic QWxhZGRpbjo="]).unwrap();
@@ -273,6 +289,18 @@ mod tests {
273289
let auth: Authorization<Bearer> = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
274290
assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
275291
}
292+
293+
#[test]
294+
fn bearer_decode_case_insensitive() {
295+
let auth: Authorization<Bearer> = test_decode(&["bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
296+
assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
297+
}
298+
299+
#[test]
300+
fn bearer_decode_extra_whitespaces() {
301+
let auth: Authorization<Bearer> = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
302+
assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
303+
}
276304
}
277305

278306
//bench_header!(raw, Authorization<String>, { vec![b"foo bar baz".to_vec()] });

src/common/cache_control.rs

+29
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ impl Flags {
5959
const PRIVATE: Self = Self { bits: 0b001000000 };
6060
const PROXY_REVALIDATE: Self = Self { bits: 0b010000000 };
6161
const IMMUTABLE: Self = Self { bits: 0b100000000 };
62+
const MUST_UNDERSTAND: Self = Self { bits: 0b1000000000 };
6263

6364
fn empty() -> Self {
6465
Self { bits: 0 }
@@ -121,6 +122,10 @@ impl CacheControl {
121122
pub fn immutable(&self) -> bool {
122123
self.flags.contains(Flags::IMMUTABLE)
123124
}
125+
/// Check if the `must_understand` directive is set.
126+
pub fn must_understand(&self) -> bool {
127+
self.flags.contains(Flags::MUST_UNDERSTAND)
128+
}
124129

125130
/// Get the value of the `max-age` directive if set.
126131
pub fn max_age(&self) -> Option<Duration> {
@@ -186,6 +191,11 @@ impl CacheControl {
186191
self
187192
}
188193

194+
/// Set the `must_understand` directive.
195+
pub fn with_must_understand(mut self) -> Self {
196+
self.flags.insert(Flags::MUST_UNDERSTAND);
197+
self
198+
}
189199
/// Set the `max-age` directive.
190200
pub fn with_max_age(mut self, duration: Duration) -> Self {
191201
self.max_age = Some(duration.into());
@@ -258,6 +268,9 @@ impl FromIterator<KnownDirective> for FromIter {
258268
Directive::MustRevalidate => {
259269
cc.flags.insert(Flags::MUST_REVALIDATE);
260270
}
271+
Directive::MustUnderstand => {
272+
cc.flags.insert(Flags::MUST_UNDERSTAND);
273+
}
261274
Directive::Public => {
262275
cc.flags.insert(Flags::PUBLIC);
263276
}
@@ -310,6 +323,7 @@ impl<'a> fmt::Display for Fmt<'a> {
310323
if_flag(Flags::PUBLIC, Directive::Public),
311324
if_flag(Flags::PRIVATE, Directive::Private),
312325
if_flag(Flags::IMMUTABLE, Directive::Immutable),
326+
if_flag(Flags::MUST_UNDERSTAND, Directive::MustUnderstand),
313327
if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate),
314328
self.0
315329
.max_age
@@ -355,6 +369,7 @@ enum Directive {
355369

356370
// response directives
357371
MustRevalidate,
372+
MustUnderstand,
358373
Public,
359374
Private,
360375
Immutable,
@@ -376,6 +391,7 @@ impl fmt::Display for Directive {
376391
Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs),
377392

378393
Directive::MustRevalidate => "must-revalidate",
394+
Directive::MustUnderstand => "must-understand",
379395
Directive::Public => "public",
380396
Directive::Private => "private",
381397
Directive::Immutable => "immutable",
@@ -399,6 +415,7 @@ impl FromStr for KnownDirective {
399415
"public" => Directive::Public,
400416
"private" => Directive::Private,
401417
"immutable" => Directive::Immutable,
418+
"must-understand" => Directive::MustUnderstand,
402419
"proxy-revalidate" => Directive::ProxyRevalidate,
403420
"" => return Err(()),
404421
_ => match s.find('=') {
@@ -472,6 +489,18 @@ mod tests {
472489
assert!(cc.immutable());
473490
}
474491

492+
#[test]
493+
fn test_must_understand() {
494+
let cc = CacheControl::new().with_must_understand();
495+
let headers = test_encode(cc.clone());
496+
assert_eq!(headers["cache-control"], "must-understand");
497+
assert_eq!(
498+
test_decode::<CacheControl>(&["must-understand"]).unwrap(),
499+
cc
500+
);
501+
assert!(cc.must_understand());
502+
}
503+
475504
#[test]
476505
fn test_parse_bad_syntax() {
477506
assert_eq!(test_decode::<CacheControl>(&["max-age=lolz"]), None);

src/common/range.rs

+43-6
Original file line numberDiff line numberDiff line change
@@ -48,29 +48,52 @@ impl Range {
4848
/// Creates a `Range` header from bounds.
4949
pub fn bytes(bounds: impl RangeBounds<u64>) -> Result<Self, InvalidRange> {
5050
let v = match (bounds.start_bound(), bounds.end_bound()) {
51-
(Bound::Unbounded, Bound::Included(end)) => format!("bytes=-{}", end),
52-
(Bound::Unbounded, Bound::Excluded(&end)) => format!("bytes=-{}", end - 1),
5351
(Bound::Included(start), Bound::Included(end)) => format!("bytes={}-{}", start, end),
5452
(Bound::Included(start), Bound::Excluded(&end)) => {
5553
format!("bytes={}-{}", start, end - 1)
5654
}
5755
(Bound::Included(start), Bound::Unbounded) => format!("bytes={}-", start),
56+
// These do not directly translate.
57+
//(Bound::Unbounded, Bound::Included(end)) => format!("bytes=-{}", end),
58+
//(Bound::Unbounded, Bound::Excluded(&end)) => format!("bytes=-{}", end - 1),
5859
_ => return Err(InvalidRange { _inner: () }),
5960
};
6061

6162
Ok(Range(::HeaderValue::from_str(&v).unwrap()))
6263
}
6364

64-
/// Iterate the range sets as a tuple of bounds.
65-
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (Bound<u64>, Bound<u64>)> + 'a {
65+
/// Iterate the range sets as a tuple of bounds, if valid with length.
66+
///
67+
/// The length of the content is passed as an argument, and all ranges
68+
/// that can be satisfied will be iterated.
69+
pub fn satisfiable_ranges<'a>(
70+
&'a self,
71+
len: u64,
72+
) -> impl Iterator<Item = (Bound<u64>, Bound<u64>)> + 'a {
6673
let s = self
6774
.0
6875
.to_str()
6976
.expect("valid string checked in Header::decode()");
7077

71-
s["bytes=".len()..].split(',').filter_map(|spec| {
78+
s["bytes=".len()..].split(',').filter_map(move |spec| {
7279
let mut iter = spec.trim().splitn(2, '-');
73-
Some((parse_bound(iter.next()?)?, parse_bound(iter.next()?)?))
80+
let start = parse_bound(iter.next()?)?;
81+
let end = parse_bound(iter.next()?)?;
82+
83+
// Unbounded ranges in HTTP are actually a suffix
84+
// For example, `-100` means the last 100 bytes.
85+
if let Bound::Unbounded = start {
86+
if let Bound::Included(end) = end {
87+
if len < end {
88+
// Last N bytes is larger than available!
89+
return None;
90+
}
91+
return Some((Bound::Included(len - end), Bound::Unbounded));
92+
}
93+
// else fall through
94+
}
95+
96+
Some((start, end))
7497
})
7598
}
7699
}
@@ -416,3 +439,17 @@ fn test_byte_range_spec_to_satisfiable_range() {
416439
bench_header!(bytes_multi, Range, { vec![b"bytes=1-1001,2001-3001,10001-".to_vec()]});
417440
bench_header!(custom_unit, Range, { vec![b"other=0-100000".to_vec()]});
418441
*/
442+
443+
#[test]
444+
fn test_to_satisfiable_range_suffix() {
445+
let range = super::test_decode::<Range>(&["bytes=-100"]).unwrap();
446+
let bounds = range.satisfiable_ranges(350).next().unwrap();
447+
assert_eq!(bounds, (Bound::Included(250), Bound::Unbounded));
448+
}
449+
450+
#[test]
451+
fn test_to_unsatisfiable_range_suffix() {
452+
let range = super::test_decode::<Range>(&["bytes=-350"]).unwrap();
453+
let bounds = range.satisfiable_ranges(100).next();
454+
assert_eq!(bounds, None);
455+
}

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#![deny(missing_debug_implementations)]
33
#![cfg_attr(test, deny(warnings))]
44
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
5-
#![doc(html_root_url = "https://docs.rs/headers/0.3.9")]
5+
#![doc(html_root_url = "https://docs.rs/headers/0.4.0")]
66

77
//! # Typed HTTP Headers
88
//!

0 commit comments

Comments
 (0)