Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / crates / npro-test / h1 / requests / uri-escape-del.http
Author[]Andy Green <andy@warmcat.com> 2026-10-05 13:43 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-10-05 16:11 UTC
Tree5421be12d3cce41508d5c9a5bf721080b8a0a5d7   Raw Patch
 
npro-h1: a server refuses an upgrade with C's status page
npro-h1: a server refuses an upgrade with C's status page

A ws upgrade the ws role refuses is answered as C's ws_upgrade_refuse()
answers it, with _lws_return_http_status(): the request's version, the
status, C's status page, and for a 426 the header
"sec-websocket-version: 13", between its content-type and
content-length as C writes it.  Server::refuse_upgrade() is that, for
the request in hand before it is answered; the status page takes the
extra header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kg5Eemy68ZaqDBcUJQG6J
diff --git a/crates/npro-h1/src/server.rs b/crates/npro-h1/src/server.rs index 89f4ac0..1307a59 100644 --- a/crates/npro-h1/src/server.rs +++ b/crates/npro-h1/src/server.rs @@ -340,10 +340,47 @@ impl<S: AsRef<[u8]> + AsMut<[u8]>> Server<S> { self.refuse(version, code, text); } + /// Refuses the request in hand's upgrade with C's status page, and + /// `extra` as a header of it, then shuts down: C's + /// `_lws_return_http_status()` as `ws_upgrade_refuse()` uses it, a 426 + /// saying `sec-websocket-version: 13`. + /// + /// # Errors + /// + /// [`RespondError::NotNow`] if there is no request in hand, or it is + /// answered already. + pub fn refuse_upgrade( + &mut self, + code: u16, + extra: Option<(&[u8], &[u8])>, + ) -> Result<(), RespondError> { + match self.phase { + Phase::Request(Txn { + reply: Reply::Awaited, + .. + }) => {} + Phase::Request(_) | Phase::Head | Phase::Refusing | Phase::Closed(_) => { + return Err(RespondError::NotNow); + } + } + self.refuse_with(self.version, code, b"", extra); + Ok(()) + } + /// Queues C's status page, then the shutdown. fn refuse(&mut self, version: Version, code: u16, text: &[u8]) { + self.refuse_with(version, code, text, None); + } + + fn refuse_with( + &mut self, + version: Version, + code: u16, + text: &[u8], + extra: Option<(&[u8], &[u8])>, + ) { self.own = Own::new(); - if status_page(&mut self.own, version, code, text).is_err() { + if status_page(&mut self.own, version, code, text, extra).is_err() { self.own = Own::new(); } self.phase = Phase::Refusing; @@ -665,7 +702,13 @@ fn status_line(own: &mut Own, v: Version, code: u16) -> Result<(), RespondError> /// C's status page: `_lws_return_http_status()` and /// `lws_http_status_page_body()`. -fn status_page(own: &mut Own, v: Version, code: u16, text: &[u8]) -> Result<(), RespondError> { +fn status_page( + own: &mut Own, + v: Version, + code: u16, + text: &[u8], + extra: Option<(&[u8], &[u8])>, +) -> Result<(), RespondError> { const PRE: &[u8] = b"<html><head><meta charset=utf-8 http-equiv=\"Content-Language\" \ content=\"en\"/><link rel=\"stylesheet\" type=\"text/css\" href=\"/error.css\"/>\ </head><body><h1>"; @@ -678,7 +721,14 @@ content=\"en\"/><link rel=\"stylesheet\" type=\"text/css\" href=\"/error.css\"/> .try_fold(0usize, |a, n| a.checked_add(*n)) .ok_or(RespondError::TooLong)?; status_line(own, v, code)?; - own.push(b"content-type: text/html\r\ncontent-length: ")?; + own.push(b"content-type: text/html\r\n")?; + if let Some((name, value)) = extra { + own.push(name)?; + own.push(b": ")?; + own.push(value)?; + own.push(b"\r\n")?; + } + own.push(b"content-length: ")?; own.push_u64(u64::try_from(body_len).map_err(|_| RespondError::TooLong)?)?; own.push(b"\r\n\r\n")?; own.push(PRE)?;
Page fetched 0s ago, creation time: 2ms (vhost etag hits: 0%, cache hits: 0%)