diff --git a/Cargo.lock b/Cargo.lock index f53171aaf..18276b3ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2290,7 +2290,7 @@ dependencies = [ "futures-util", "libsecp256k1", "lighthouse_network", - "mime", + "mediatype", "procinfo", "proto_array", "psutil", @@ -4962,6 +4962,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mediatype" +version = "0.19.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea6e62614ab2fc0faa58bb15102a0382d368f896a9fa4776592589ab55c4de7" + [[package]] name = "memchr" version = "2.5.0" diff --git a/common/eth2/Cargo.toml b/common/eth2/Cargo.toml index 2c5e7060b..4eabd3ff8 100644 --- a/common/eth2/Cargo.toml +++ b/common/eth2/Cargo.toml @@ -10,7 +10,7 @@ edition = "2021" serde = { version = "1.0.116", features = ["derive"] } serde_json = "1.0.58" types = { path = "../../consensus/types" } -reqwest = { version = "0.11.0", features = ["json","stream"] } +reqwest = { version = "0.11.0", features = ["json", "stream"] } lighthouse_network = { path = "../../beacon_node/lighthouse_network" } proto_array = { path = "../../consensus/proto_array", optional = true } ethereum_serde_utils = "0.5.0" @@ -26,7 +26,7 @@ futures-util = "0.3.8" futures = "0.3.8" store = { path = "../../beacon_node/store", optional = true } slashing_protection = { path = "../../validator_client/slashing_protection", optional = true } -mime = "0.3.16" +mediatype = "0.19.13" [target.'cfg(target_os = "linux")'.dependencies] psutil = { version = "3.2.2", optional = true } @@ -34,4 +34,10 @@ procinfo = { version = "0.4.2", optional = true } [features] default = ["lighthouse"] -lighthouse = ["proto_array", "psutil", "procinfo", "store", "slashing_protection"] +lighthouse = [ + "proto_array", + "psutil", + "procinfo", + "store", + "slashing_protection", +] diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index d7150bff7..55759a2e1 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -3,9 +3,8 @@ use crate::Error as ServerError; use lighthouse_network::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus}; -use mime::{Mime, APPLICATION, JSON, OCTET_STREAM, STAR}; +use mediatype::{names, MediaType, MediaTypeList}; use serde::{Deserialize, Serialize}; -use std::cmp::Reverse; use std::convert::TryFrom; use std::fmt; use std::str::{from_utf8, FromStr}; @@ -1172,35 +1171,58 @@ impl FromStr for Accept { type Err = String; fn from_str(s: &str) -> Result { - let mut mimes = parse_accept(s)?; + let media_type_list = MediaTypeList::new(s); // [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2 // find the highest q-factor supported accept type - mimes.sort_by_key(|m| { - Reverse(m.get_param("q").map_or(1000_u16, |n| { - (n.as_ref().parse::().unwrap_or(0_f32) * 1000_f32) as u16 - })) - }); - mimes - .into_iter() - .find_map(|m| match (m.type_(), m.subtype()) { - (APPLICATION, OCTET_STREAM) => Some(Accept::Ssz), - (APPLICATION, JSON) => Some(Accept::Json), - (STAR, STAR) => Some(Accept::Any), - _ => None, - }) - .ok_or_else(|| "accept header is not supported".to_string()) - } -} + let mut highest_q = 0_u16; + let mut accept_type = None; -fn parse_accept(accept: &str) -> Result, String> { - accept - .split(',') - .map(|part| { - part.parse() - .map_err(|e| format!("error parsing Accept header: {}", e)) - }) - .collect() + const APPLICATION: &str = names::APPLICATION.as_str(); + const OCTET_STREAM: &str = names::OCTET_STREAM.as_str(); + const JSON: &str = names::JSON.as_str(); + const STAR: &str = names::_STAR.as_str(); + const Q: &str = names::Q.as_str(); + + media_type_list.into_iter().for_each(|item| { + if let Ok(MediaType { + ty, + subty, + suffix: _, + params, + }) = item + { + let q_accept = match (ty.as_str(), subty.as_str()) { + (APPLICATION, OCTET_STREAM) => Some(Accept::Ssz), + (APPLICATION, JSON) => Some(Accept::Json), + (STAR, STAR) => Some(Accept::Any), + _ => None, + } + .map(|item_accept_type| { + let q_val = params + .iter() + .find_map(|(n, v)| match n.as_str() { + Q => { + Some((v.as_str().parse::().unwrap_or(0_f32) * 1000_f32) as u16) + } + _ => None, + }) + .or(Some(1000_u16)); + + (q_val.unwrap(), item_accept_type) + }); + + match q_accept { + Some((q, accept)) if q > highest_q => { + highest_q = q; + accept_type = Some(accept); + } + _ => (), + } + } + }); + accept_type.ok_or_else(|| "accept header is not supported".to_string()) + } } #[derive(Debug, Serialize, Deserialize)] @@ -1268,6 +1290,11 @@ mod tests { assert_eq!( Accept::from_str("text/plain"), Err("accept header is not supported".to_string()) - ) + ); + + assert_eq!( + Accept::from_str("application/json;message=\"Hello, world!\";q=0.3,*/*;q=0.6").unwrap(), + Accept::Any + ); } }