use neqo_common::{Datagram, event::Provider as _, qdebug, qwarn}; use neqo_http3::Error; use neqo_transport::{
ConnectionEvent, ConnectionIdGenerator, OutputBatch, State, StreamId,
server::{ConnectionRef, Server},
}; use nss::{AllowZeroRtt, AntiReplay}; use rustc_hash::FxHashMap as HashMap;
fn save_partial(&mutself, stream_id: StreamId, partial: Vec<u8>, conn: &ConnectionRef) { if partial.len() < 4096 {
qdebug!("Saving partial URL: {}", String::from_utf8_lossy(&partial)); self.read_state.insert(stream_id, partial);
} else {
qdebug!( "Giving up on partial URL {}",
String::from_utf8_lossy(&partial)
);
_ = conn.borrow_mut().stream_stop_sending(stream_id, 0); // Stream may be closed; ignore errors.
}
}
/// Parse a complete HQ request buffer and return the path component. /// /// Returns `None` on non-UTF-8 input, missing `GET /` prefix, or a path /// that doesn't pass the filter for the current mode (QNS vs. non-QNS). fn parse_path(buf: &[u8], is_qns_test: bool) -> Option<&str> { let msg = str::from_utf8(buf).ok()?;
msg.strip_prefix("GET /")
.and_then(|s| s.lines().next())
.filter(|p| { if is_qns_test {
!p.chars().any(char::is_whitespace)
} else {
p.chars().all(|c| c.is_ascii_digit())
}
})
}
fn stream_readable(&mutself, stream_id: StreamId, conn: &ConnectionRef) { if !stream_id.is_client_initiated() || !stream_id.is_bidi() {
qdebug!("Stream {stream_id} not client-initiated bidi, ignoring"); return;
} let (sz, fin) = conn
.borrow_mut()
.stream_recv(stream_id, &mutself.read_buffer)
.expect("Read should succeed");
// A zero-length read with no FIN is unexpected but harmless; leave any // buffered partial data untouched and wait for more. if sz == 0 && !fin {
qdebug!("size 0 but !fin"); return;
}
// HQ requests are terminated by stream FIN. Never process a request // before FIN: partial data could look like a valid truncated path, // causing the server to serve the wrong (or non-existent) file. if !fin { self.save_partial(stream_id, buf, conn); return;
}
// FIN is set: the request is complete. If no data was received (either // now or buffered from a prior read), there is nothing to serve. if buf.is_empty() { self.write_state.remove(&stream_id); return;
}
// Non-UTF-8 or unrecognised format cannot be recovered by waiting for // more data; reset the stream so the client gets a clean signal. let Some(path) = Self::parse_path(&buf, self.is_qns_test) else {
_ = conn.borrow_mut().stream_reset_send(stream_id, 0); self.write_state.remove(&stream_id); return;
};
qdebug!("Path = '{path}'"); let resp = super::response_for_path(path, self.is_qns_test)
.unwrap_or_else(|()| b"404".as_slice().into());
let stream_state = self.write_state.entry(stream_id).or_default(); if stream_state.data_to_send.is_none() {
stream_state.data_to_send = Some(resp);
} else {
qdebug!("Data already set, doing nothing");
} let writable = stream_state.writable; if writable { self.stream_writable(stream_id, conn);
}
}
impl Display for HttpServer { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Http 0.9 server ")
}
}
#[cfg(test)] mod tests { usesuper::HttpServer;
// Issue 1 (FIN-only frame after buffered partial data) is exercised by // the QNS zerortt interop test end-to-end; unit testing it would require // a real neqo_transport::server::ConnectionRef.
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.