use crate::{Error, ErrorKind, HttpError, ProtocolError};
use fnv::FnvHashSet;
use http::header::SEC_WEBSOCKET_PROTOCOL;
use http::{HeaderMap, HeaderValue};
use std::sync::Arc;
#[derive(Default, Debug, Clone)]
pub struct SubprotocolRegistry {
inner: Arc<Inner>,
}
#[derive(Debug, Default)]
struct Inner {
registrants: FnvHashSet<String>,
header: Option<HeaderValue>,
}
impl SubprotocolRegistry {
pub fn new<I>(i: I) -> Result<SubprotocolRegistry, Error>
where
I: IntoIterator,
I::Item: Into<String>,
{
let registrants = i.into_iter().map(Into::into).collect::<FnvHashSet<_>>();
let header_str = registrants
.clone()
.into_iter()
.collect::<Vec<_>>()
.join(", ");
let header = HeaderValue::from_str(&header_str).map_err(|_| {
Error::with_cause(ErrorKind::Http, HttpError::MalformattedHeader(header_str))
})?;
Ok(SubprotocolRegistry {
inner: Arc::new(Inner {
registrants,
header: Some(header),
}),
})
}
pub fn negotiate_client(
&self,
header_map: &HeaderMap,
) -> Result<Option<String>, ProtocolError> {
let SubprotocolRegistry { inner } = self;
for header in header_map.get_all(SEC_WEBSOCKET_PROTOCOL) {
let header_str = header.to_str().map_err(|_| ProtocolError::Encoding)?;
for protocol in header_str.split(',') {
if let Some(supported_protocol) = inner.registrants.get(protocol.trim()) {
return Ok(Some(supported_protocol.clone()));
}
}
}
Ok(None)
}
pub fn validate_accepted_subprotocol(
&self,
header_map: &HeaderMap,
) -> Result<Option<String>, ProtocolError> {
let SubprotocolRegistry { inner } = self;
let protocols: Vec<_> = header_map.get_all(SEC_WEBSOCKET_PROTOCOL).iter().collect();
if protocols.len() > 1 {
return Err(ProtocolError::InvalidSubprotocolHeader(
"Server returned too many subprotocols".to_string(),
));
}
if protocols.is_empty() {
return Ok(None);
}
let server_protocol = protocols[0].to_str().map_err(|_| ProtocolError::Encoding)?;
if inner.registrants.contains(server_protocol) {
Ok(Some(server_protocol.to_string()))
} else {
Err(ProtocolError::InvalidSubprotocolHeader(
server_protocol.to_string(),
))
}
}
pub fn apply_to(&self, target: &mut HeaderMap) {
let SubprotocolRegistry { inner } = self;
if let Some(header) = &inner.header {
target.insert(SEC_WEBSOCKET_PROTOCOL, header.clone());
}
}
}