| //! A server's side of the ws handshake: C's `lws_process_ws_upgrade()` and
//! `handshake_0405()`.
//!
//! C's checks, in C's order, each refusal C's status: an upgrade is a GET;
//! its `Connection` names the token `upgrade`; it has a key, of less than
//! 128 bytes, and a Host; its version is `13`, a 400 without one and a 426
//! (saying `sec-websocket-version: 13`) for another; and it asks for a
//! subprotocol the server has, the first of its list that it has, or with
//! no list, the server's default.
use npro_core::base64;
use npro_core::sha1::Sha1;
use npro_h1::table::HeaderTable;
use npro_h1::token::Token;
/// The GUID RFC 6455 4.2.2 appends to a key.
const GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
/// The longest key C takes, and one: C's `MAX_WEBSOCKET_04_KEY_LEN`.
const MAX_KEY: usize = 128;
/// The length of an accept value: base64 of a SHA-1.
pub const ACCEPT_LEN: usize = 28;
/// An upgrade the server takes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Accepted {
/// The index of the subprotocol in the server's list.
pub protocol: usize,
/// Whether the request named it, so the 101 says it.
pub named: bool,
accept: [u8; ACCEPT_LEN],
}
impl Accepted {
/// The `Sec-WebSocket-Accept` value.
#[must_use]
pub const fn accept(&self) -> &[u8; ACCEPT_LEN] {
&self.accept
}
}
/// Why an upgrade is refused, as C says why.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Refusal {
/// Not a GET (400).
NotGet,
/// No `upgrade` in `Connection` (400).
NoConnectionUpgrade,
/// No key, too long a key, or no Host (400).
KeyOrHost,
/// No version (400).
NoVersion,
/// A version that is not 13 (426).
Version,
/// A protocol list that is not one (400).
ProtocolList,
/// No protocol the server has (400).
NoProtocol,
}
impl Refusal {
/// The status C answers with.
#[must_use]
pub const fn status(self) -> u16 {
match self {
Self::Version => 426,
Self::NotGet
| Self::NoConnectionUpgrade
| Self::KeyOrHost
| Self::NoVersion
| Self::ProtocolList
| Self::NoProtocol => 400,
}
}
/// The header C's 426 adds to its status page.
#[must_use]
pub const fn header(self) -> Option<(&'static [u8], &'static [u8])> {
match self {
Self::Version => Some((b"sec-websocket-version", b"13")),
Self::NotGet
| Self::NoConnectionUpgrade
| Self::KeyOrHost
| Self::NoVersion
| Self::ProtocolList
| Self::NoProtocol => None,
}
}
}
/// Whether `c` is a token's byte, RFC 9110 5.6.2's tchar.
const fn tchar(c: u8) -> bool {
c.is_ascii_alphanumeric()
|| matches!(
c,
b'!' | b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'^'
| b'_'
| b'`'
| b'|'
| b'~'
)
}
/// The elements of a comma separated list of tokens, each `Some` token, or
/// `None` for one that is not a token: what C's `lws_tokenize()` takes for
/// these lists.
fn tokens(v: &[u8]) -> impl Iterator<Item = Option<&[u8]>> {
v.split(|c| *c == b',').filter_map(|e| {
let blank = |c: &u8| *c == b' ' || *c == b'\t';
let start = e.iter().position(|c| !blank(c))?;
let end = e
.iter()
.rposition(|c| !blank(c))
.map_or(0, |n| n.saturating_add(1));
let t = e.get(start..end)?;
Some(t.iter().all(|c| tchar(*c)).then_some(t))
})
}
/// Checks an upgrade request against a server having the subprotocols
/// `protocols`, and `default` for a request naming none.
///
/// ```
/// use npro_h1::head::{Config, Head, Side};
/// use npro_ws::handshake::server;
///
/// let mut h = Head::new([0u8; 1024], Side::Server, Config::new())?;
/// h.rx(b"GET /chat HTTP/1.1\r\nHost: x\r\nUpgrade: websocket\r\n\
/// Connection: Upgrade\r\nSec-WebSocket-Version: 13\r\n\
/// Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")?;
/// let a = server(h.table(), &[b"chat"], Some(0)).unwrap();
/// assert_eq!(a.accept(), b"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
/// # Ok::<(), Box<dyn core::error::Error>>(())
/// ```
///
/// # Errors
///
/// The [`Refusal`], whose status and header C answers with.
pub fn server<S: AsRef<[u8]> + AsMut<[u8]>>(
t: &HeaderTable<S>,
protocols: &[&[u8]],
default: Option<usize>,
) -> Result<Accepted, Refusal> {
if !t.is_present(Token::GetUri) {
return Err(Refusal::NotGet);
}
let mut buf = [0u8; MAX_KEY];
let conn = t
.copy(
Token::Connection,
buf.get_mut(..MAX_KEY - 1).unwrap_or_default(),
)
.ok()
.filter(|n| *n > 0)
.and_then(|n| buf.get(..n))
.ok_or(Refusal::NoConnectionUpgrade)?;
let mut upgrade = false;
for tok in tokens(conn) {
match tok {
Some(name) if name.eq_ignore_ascii_case(b"upgrade") => {
upgrade = true;
break;
}
Some(_) => {}
None => return Err(Refusal::NoConnectionUpgrade),
}
}
if !upgrade {
return Err(Refusal::NoConnectionUpgrade);
}
let key_len = t.total_len(Token::WsKey);
if key_len == 0 || key_len >= MAX_KEY || !t.is_present(Token::Host) {
return Err(Refusal::KeyOrHost);
}
let mut key = [0u8; MAX_KEY];
let key = t
.copy(Token::WsKey, &mut key)
.ok()
.and_then(|n| key.get(..n))
.ok_or(Refusal::KeyOrHost)?;
match t.copy(Token::WsVersion, &mut buf) {
Ok(0) => return Err(Refusal::NoVersion),
Ok(2) if buf.get(..2) == Some(b"13".as_slice()) => {}
Ok(_) | Err(_) => return Err(Refusal::Version),
}
let mut list = [0u8; MAX_KEY];
let list = t
.copy(
Token::WsProtocol,
list.get_mut(..MAX_KEY - 1).unwrap_or_default(),
)
.ok()
.and_then(|n| list.get(..n))
.ok_or(Refusal::ProtocolList)?;
let (protocol, named) = if list.is_empty() {
let d = default
.filter(|d| *d < protocols.len())
.ok_or(Refusal::NoProtocol)?;
(d, false)
} else {
let mut found = None;
for tok in tokens(list) {
let name = tok.ok_or(Refusal::ProtocolList)?;
if name.len() >= 64 {
return Err(Refusal::ProtocolList);
}
if let Some(i) = protocols.iter().position(|p| *p == name) {
found = Some(i);
break;
}
}
(found.ok_or(Refusal::NoProtocol)?, true)
};
let mut h = Sha1::new();
h.update(key);
h.update(GUID);
let mut accept = [0u8; ACCEPT_LEN];
base64::encode(&h.finish(), &mut accept).map_err(|_| Refusal::KeyOrHost)?;
Ok(Accepted {
protocol,
named,
accept,
})
}
/// The most a 101 C writes may have here.
pub const MAX_101: usize = 256;
/// C's 101 for `a`, `name` being its subprotocol's: written into `out`,
/// returning how much of it.
///
/// # Errors
///
/// `None` if `out` is too small.
#[must_use]
pub fn response_101(a: &Accepted, name: &[u8], out: &mut [u8]) -> Option<usize> {
let mut at = 0usize;
let mut put = |b: &[u8]| -> Option<()> {
let end = at.checked_add(b.len())?;
out.get_mut(at..end)?.copy_from_slice(b);
at = end;
Some(())
};
put(b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ")?;
put(&a.accept)?;
// the protocol is said only if the request named one, and it has a name
if a.named && !name.is_empty() {
put(b"\r\nSec-WebSocket-Protocol: ")?;
put(name)?;
}
put(b"\r\n\r\n")?;
Some(at)
}
|