summaryrefslogtreecommitdiffstats
path: root/third_party/rust/hyper/src/ext
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/hyper/src/ext
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/hyper/src/ext')
-rw-r--r--third_party/rust/hyper/src/ext/h1_reason_phrase.rs221
1 files changed, 221 insertions, 0 deletions
diff --git a/third_party/rust/hyper/src/ext/h1_reason_phrase.rs b/third_party/rust/hyper/src/ext/h1_reason_phrase.rs
new file mode 100644
index 0000000000..021b632b6d
--- /dev/null
+++ b/third_party/rust/hyper/src/ext/h1_reason_phrase.rs
@@ -0,0 +1,221 @@
+use std::convert::TryFrom;
+
+use bytes::Bytes;
+
+/// A reason phrase in an HTTP/1 response.
+///
+/// # Clients
+///
+/// For clients, a `ReasonPhrase` will be present in the extensions of the `http::Response` returned
+/// for a request if the reason phrase is different from the canonical reason phrase for the
+/// response's status code. For example, if a server returns `HTTP/1.1 200 Awesome`, the
+/// `ReasonPhrase` will be present and contain `Awesome`, but if a server returns `HTTP/1.1 200 OK`,
+/// the response will not contain a `ReasonPhrase`.
+///
+/// ```no_run
+/// # #[cfg(all(feature = "tcp", feature = "client", feature = "http1"))]
+/// # async fn fake_fetch() -> hyper::Result<()> {
+/// use hyper::{Client, Uri};
+/// use hyper::ext::ReasonPhrase;
+///
+/// let res = Client::new().get(Uri::from_static("http://example.com/non_canonical_reason")).await?;
+///
+/// // Print out the non-canonical reason phrase, if it has one...
+/// if let Some(reason) = res.extensions().get::<ReasonPhrase>() {
+/// println!("non-canonical reason: {}", std::str::from_utf8(reason.as_bytes()).unwrap());
+/// }
+/// # Ok(())
+/// # }
+/// ```
+///
+/// # Servers
+///
+/// When a `ReasonPhrase` is present in the extensions of the `http::Response` written by a server,
+/// its contents will be written in place of the canonical reason phrase when responding via HTTP/1.
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct ReasonPhrase(Bytes);
+
+impl ReasonPhrase {
+ /// Gets the reason phrase as bytes.
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.0
+ }
+
+ /// Converts a static byte slice to a reason phrase.
+ pub fn from_static(reason: &'static [u8]) -> Self {
+ // TODO: this can be made const once MSRV is >= 1.57.0
+ if find_invalid_byte(reason).is_some() {
+ panic!("invalid byte in static reason phrase");
+ }
+ Self(Bytes::from_static(reason))
+ }
+
+ /// Converts a `Bytes` directly into a `ReasonPhrase` without validating.
+ ///
+ /// Use with care; invalid bytes in a reason phrase can cause serious security problems if
+ /// emitted in a response.
+ pub unsafe fn from_bytes_unchecked(reason: Bytes) -> Self {
+ Self(reason)
+ }
+}
+
+impl TryFrom<&[u8]> for ReasonPhrase {
+ type Error = InvalidReasonPhrase;
+
+ fn try_from(reason: &[u8]) -> Result<Self, Self::Error> {
+ if let Some(bad_byte) = find_invalid_byte(reason) {
+ Err(InvalidReasonPhrase { bad_byte })
+ } else {
+ Ok(Self(Bytes::copy_from_slice(reason)))
+ }
+ }
+}
+
+impl TryFrom<Vec<u8>> for ReasonPhrase {
+ type Error = InvalidReasonPhrase;
+
+ fn try_from(reason: Vec<u8>) -> Result<Self, Self::Error> {
+ if let Some(bad_byte) = find_invalid_byte(&reason) {
+ Err(InvalidReasonPhrase { bad_byte })
+ } else {
+ Ok(Self(Bytes::from(reason)))
+ }
+ }
+}
+
+impl TryFrom<String> for ReasonPhrase {
+ type Error = InvalidReasonPhrase;
+
+ fn try_from(reason: String) -> Result<Self, Self::Error> {
+ if let Some(bad_byte) = find_invalid_byte(reason.as_bytes()) {
+ Err(InvalidReasonPhrase { bad_byte })
+ } else {
+ Ok(Self(Bytes::from(reason)))
+ }
+ }
+}
+
+impl TryFrom<Bytes> for ReasonPhrase {
+ type Error = InvalidReasonPhrase;
+
+ fn try_from(reason: Bytes) -> Result<Self, Self::Error> {
+ if let Some(bad_byte) = find_invalid_byte(&reason) {
+ Err(InvalidReasonPhrase { bad_byte })
+ } else {
+ Ok(Self(reason))
+ }
+ }
+}
+
+impl Into<Bytes> for ReasonPhrase {
+ fn into(self) -> Bytes {
+ self.0
+ }
+}
+
+impl AsRef<[u8]> for ReasonPhrase {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+/// Error indicating an invalid byte when constructing a `ReasonPhrase`.
+///
+/// See [the spec][spec] for details on allowed bytes.
+///
+/// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7
+#[derive(Debug)]
+pub struct InvalidReasonPhrase {
+ bad_byte: u8,
+}
+
+impl std::fmt::Display for InvalidReasonPhrase {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Invalid byte in reason phrase: {}", self.bad_byte)
+ }
+}
+
+impl std::error::Error for InvalidReasonPhrase {}
+
+const fn is_valid_byte(b: u8) -> bool {
+ // See https://www.rfc-editor.org/rfc/rfc5234.html#appendix-B.1
+ const fn is_vchar(b: u8) -> bool {
+ 0x21 <= b && b <= 0x7E
+ }
+
+ // See https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#fields.values
+ //
+ // The 0xFF comparison is technically redundant, but it matches the text of the spec more
+ // clearly and will be optimized away.
+ #[allow(unused_comparisons)]
+ const fn is_obs_text(b: u8) -> bool {
+ 0x80 <= b && b <= 0xFF
+ }
+
+ // See https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7
+ b == b'\t' || b == b' ' || is_vchar(b) || is_obs_text(b)
+}
+
+const fn find_invalid_byte(bytes: &[u8]) -> Option<u8> {
+ let mut i = 0;
+ while i < bytes.len() {
+ let b = bytes[i];
+ if !is_valid_byte(b) {
+ return Some(b);
+ }
+ i += 1;
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn basic_valid() {
+ const PHRASE: &'static [u8] = b"OK";
+ assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
+ assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
+ }
+
+ #[test]
+ fn empty_valid() {
+ const PHRASE: &'static [u8] = b"";
+ assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
+ assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
+ }
+
+ #[test]
+ fn obs_text_valid() {
+ const PHRASE: &'static [u8] = b"hyp\xe9r";
+ assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
+ assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
+ }
+
+ const NEWLINE_PHRASE: &'static [u8] = b"hyp\ner";
+
+ #[test]
+ #[should_panic]
+ fn newline_invalid_panic() {
+ ReasonPhrase::from_static(NEWLINE_PHRASE);
+ }
+
+ #[test]
+ fn newline_invalid_err() {
+ assert!(ReasonPhrase::try_from(NEWLINE_PHRASE).is_err());
+ }
+
+ const CR_PHRASE: &'static [u8] = b"hyp\rer";
+
+ #[test]
+ #[should_panic]
+ fn cr_invalid_panic() {
+ ReasonPhrase::from_static(CR_PHRASE);
+ }
+
+ #[test]
+ fn cr_invalid_err() {
+ assert!(ReasonPhrase::try_from(CR_PHRASE).is_err());
+ }
+}