diff --git a/Cargo.lock b/Cargo.lock index be6e844dc9..b5d77d235d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1586,6 +1586,7 @@ dependencies = [ "futures-util", "libsecp256k1", "lighthouse_network", + "mime", "procinfo", "proto_array", "psutil", diff --git a/common/eth2/Cargo.toml b/common/eth2/Cargo.toml index 674672326c..294f8ec8a3 100644 --- a/common/eth2/Cargo.toml +++ b/common/eth2/Cargo.toml @@ -26,6 +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" [target.'cfg(target_os = "linux")'.dependencies] psutil = { version = "3.2.2", optional = true } diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 8cd3a1d67f..8ef3582268 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -3,7 +3,9 @@ use crate::Error as ServerError; use lighthouse_network::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus}; +use mime::{Mime, APPLICATION, JSON, OCTET_STREAM, STAR}; use serde::{Deserialize, Serialize}; +use std::cmp::Reverse; use std::convert::TryFrom; use std::fmt; use std::str::{from_utf8, FromStr}; @@ -1008,15 +1010,37 @@ impl FromStr for Accept { type Err = String; fn from_str(s: &str) -> Result { - match s { - "application/octet-stream" => Ok(Accept::Ssz), - "application/json" => Ok(Accept::Json), - "*/*" => Ok(Accept::Any), - _ => Err("accept header cannot be parsed.".to_string()), - } + let mut mimes = parse_accept(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()) } } +fn parse_accept(accept: &str) -> Result, String> { + accept + .split(',') + .map(|part| { + part.parse() + .map_err(|e| format!("error parsing Accept header: {}", e)) + }) + .collect() +} + #[derive(Debug, Serialize, Deserialize)] pub struct LivenessRequestData { pub epoch: Epoch, @@ -1045,4 +1069,23 @@ mod tests { } ); } + + #[test] + fn parse_accept_header_content() { + assert_eq!( + Accept::from_str("application/json; charset=utf-8").unwrap(), + Accept::Json + ); + + assert_eq!( + Accept::from_str("text/plain,application/octet-stream;q=0.3,application/json;q=0.9") + .unwrap(), + Accept::Json + ); + + assert_eq!( + Accept::from_str("text/plain"), + Err("accept header is not supported".to_string()) + ) + } }