Skip to content

Commit f18821e

Browse files
authored
Merge pull request #301 from http-rs/transfer
Add `Transfer-Encoding` and `TE` headers
2 parents 0946e81 + 3eda9df commit f18821e

File tree

6 files changed

+805
-3
lines changed

6 files changed

+805
-3
lines changed

src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ pub mod mime;
127127
pub mod other;
128128
pub mod proxies;
129129
pub mod server;
130+
pub mod trace;
131+
pub mod transfer;
132+
pub mod upgrade;
130133

131134
mod body;
132135
mod error;
@@ -140,9 +143,6 @@ mod status;
140143
mod status_code;
141144
mod version;
142145

143-
pub mod trace;
144-
pub mod upgrade;
145-
146146
pub use body::Body;
147147
pub use error::{Error, Result};
148148
pub use method::Method;

src/transfer/encoding.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use crate::headers::HeaderValue;
2+
use std::fmt::{self, Display};
3+
4+
/// Available compression algorithms.
5+
///
6+
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding#Directives)
7+
#[non_exhaustive]
8+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
9+
pub enum Encoding {
10+
/// Send a series of chunks.
11+
Chunked,
12+
/// The Gzip encoding.
13+
Gzip,
14+
/// The Deflate encoding.
15+
Deflate,
16+
/// The Brotli encoding.
17+
Brotli,
18+
/// The Zstd encoding.
19+
Zstd,
20+
/// No encoding.
21+
Identity,
22+
}
23+
24+
impl Encoding {
25+
/// Parses a given string into its corresponding encoding.
26+
pub(crate) fn from_str(s: &str) -> Option<Encoding> {
27+
let s = s.trim();
28+
29+
// We're dealing with an empty string.
30+
if s.is_empty() {
31+
return None;
32+
}
33+
34+
match s {
35+
"chunked" => Some(Encoding::Chunked),
36+
"gzip" => Some(Encoding::Gzip),
37+
"deflate" => Some(Encoding::Deflate),
38+
"br" => Some(Encoding::Brotli),
39+
"zstd" => Some(Encoding::Zstd),
40+
"identity" => Some(Encoding::Identity),
41+
_ => None,
42+
}
43+
}
44+
}
45+
46+
impl Display for Encoding {
47+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48+
match self {
49+
Encoding::Gzip => write!(f, "gzip"),
50+
Encoding::Deflate => write!(f, "deflate"),
51+
Encoding::Brotli => write!(f, "br"),
52+
Encoding::Zstd => write!(f, "zstd"),
53+
Encoding::Identity => write!(f, "identity"),
54+
Encoding::Chunked => write!(f, "chunked"),
55+
}
56+
}
57+
}
58+
59+
impl From<Encoding> for HeaderValue {
60+
fn from(directive: Encoding) -> Self {
61+
let s = directive.to_string();
62+
unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
63+
}
64+
}

src/transfer/encoding_proposal.rs

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use crate::ensure;
2+
use crate::headers::HeaderValue;
3+
use crate::transfer::Encoding;
4+
use crate::utils::parse_weight;
5+
6+
use std::cmp::{Ordering, PartialEq};
7+
use std::ops::{Deref, DerefMut};
8+
9+
/// A proposed `Encoding` in `AcceptEncoding`.
10+
///
11+
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/TE#Directives)
12+
#[derive(Debug, Clone, Copy, PartialEq)]
13+
pub struct EncodingProposal {
14+
/// The proposed encoding.
15+
pub(crate) encoding: Encoding,
16+
17+
/// The weight of the proposal.
18+
///
19+
/// This is a number between 0.0 and 1.0, and is max 3 decimal points.
20+
weight: Option<f32>,
21+
}
22+
23+
impl EncodingProposal {
24+
/// Create a new instance of `EncodingProposal`.
25+
pub fn new(encoding: impl Into<Encoding>, weight: Option<f32>) -> crate::Result<Self> {
26+
if let Some(weight) = weight {
27+
ensure!(
28+
weight.is_sign_positive() && weight <= 1.0,
29+
"EncodingProposal should have a weight between 0.0 and 1.0"
30+
)
31+
}
32+
33+
Ok(Self {
34+
encoding: encoding.into(),
35+
weight,
36+
})
37+
}
38+
39+
/// Get the proposed encoding.
40+
pub fn encoding(&self) -> &Encoding {
41+
&self.encoding
42+
}
43+
44+
/// Get the weight of the proposal.
45+
pub fn weight(&self) -> Option<f32> {
46+
self.weight
47+
}
48+
49+
pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
50+
let mut parts = s.split(';');
51+
let encoding = match Encoding::from_str(parts.next().unwrap()) {
52+
Some(encoding) => encoding,
53+
None => return Ok(None),
54+
};
55+
let weight = parts.next().map(parse_weight).transpose()?;
56+
57+
Ok(Some(Self::new(encoding, weight)?))
58+
}
59+
}
60+
61+
impl From<Encoding> for EncodingProposal {
62+
fn from(encoding: Encoding) -> Self {
63+
Self {
64+
encoding,
65+
weight: None,
66+
}
67+
}
68+
}
69+
70+
impl PartialEq<Encoding> for EncodingProposal {
71+
fn eq(&self, other: &Encoding) -> bool {
72+
self.encoding == *other
73+
}
74+
}
75+
76+
impl PartialEq<Encoding> for &EncodingProposal {
77+
fn eq(&self, other: &Encoding) -> bool {
78+
self.encoding == *other
79+
}
80+
}
81+
82+
impl Deref for EncodingProposal {
83+
type Target = Encoding;
84+
fn deref(&self) -> &Self::Target {
85+
&self.encoding
86+
}
87+
}
88+
89+
impl DerefMut for EncodingProposal {
90+
fn deref_mut(&mut self) -> &mut Self::Target {
91+
&mut self.encoding
92+
}
93+
}
94+
95+
// NOTE: Firefox populates Accept-Encoding as `gzip, deflate, br`. This means
96+
// when parsing encodings we should choose the last value in the list under
97+
// equal weights. This impl doesn't know which value was passed later, so that
98+
// behavior needs to be handled separately.
99+
//
100+
// NOTE: This comparison does not include a notion of `*` (any value is valid).
101+
// that needs to be handled separately.
102+
impl PartialOrd for EncodingProposal {
103+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
104+
match (self.weight, other.weight) {
105+
(Some(left), Some(right)) => left.partial_cmp(&right),
106+
(Some(_), None) => Some(Ordering::Greater),
107+
(None, Some(_)) => Some(Ordering::Less),
108+
(None, None) => None,
109+
}
110+
}
111+
}
112+
113+
impl From<EncodingProposal> for HeaderValue {
114+
fn from(entry: EncodingProposal) -> HeaderValue {
115+
let s = match entry.weight {
116+
Some(weight) => format!("{};q={:.3}", entry.encoding, weight),
117+
None => entry.encoding.to_string(),
118+
};
119+
unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
120+
}
121+
}
122+
123+
#[cfg(test)]
124+
mod test {
125+
use super::*;
126+
127+
#[test]
128+
fn smoke() -> crate::Result<()> {
129+
let _ = EncodingProposal::new(Encoding::Gzip, Some(0.0)).unwrap();
130+
let _ = EncodingProposal::new(Encoding::Gzip, Some(0.5)).unwrap();
131+
let _ = EncodingProposal::new(Encoding::Gzip, Some(1.0)).unwrap();
132+
Ok(())
133+
}
134+
135+
#[test]
136+
fn error_code_500() -> crate::Result<()> {
137+
let err = EncodingProposal::new(Encoding::Gzip, Some(1.1)).unwrap_err();
138+
assert_eq!(err.status(), 500);
139+
140+
let err = EncodingProposal::new(Encoding::Gzip, Some(-0.1)).unwrap_err();
141+
assert_eq!(err.status(), 500);
142+
143+
let err = EncodingProposal::new(Encoding::Gzip, Some(-0.0)).unwrap_err();
144+
assert_eq!(err.status(), 500);
145+
Ok(())
146+
}
147+
}

src/transfer/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! HTTP transfer headers.
2+
//!
3+
//! [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#Transfer_coding)
4+
5+
mod encoding;
6+
mod encoding_proposal;
7+
mod te;
8+
mod transfer_encoding;
9+
10+
pub use encoding::Encoding;
11+
pub use encoding_proposal::EncodingProposal;
12+
pub use te::TE;
13+
pub use transfer_encoding::TransferEncoding;

0 commit comments

Comments
 (0)