mirror of
https://github.com/ntex-rs/ntex.git
synced 2025-04-04 21:37:58 +03:00
Various http fixes (#132)
* Fix parsing ambiguity in Transfer-Encoding and Content-Length headers for HTTP/1.0 requests * Fix http2 content-length handling * fix h2 content-length support
This commit is contained in:
parent
767f022a8e
commit
ee4b23465b
29 changed files with 235 additions and 73 deletions
1
.github/workflows/windows.yml
vendored
1
.github/workflows/windows.yml
vendored
|
@ -80,3 +80,4 @@ jobs:
|
|||
--skip test_no_decompress
|
||||
--skip test_connection_reuse_h2
|
||||
--skip test_h2_tcp
|
||||
--skip test_timer
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
[](https://github.com/ntex-rs/ntex/actions?query=workflow%3A"CI+(Linux)")
|
||||
[](https://crates.io/crates/ntex)
|
||||
[](https://docs.rs/ntex)
|
||||
[](https://blog.rust-lang.org/2021/09/09/Rust-1.55.0.html)
|
||||
[](https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html)
|
||||

|
||||
[](https://codecov.io/gh/ntex-rs/ntex)
|
||||
[](https://discord.gg/zBNyhVRz)
|
||||
|
@ -35,7 +35,7 @@ ntex = { version = "0.5", features = ["glommio"] }
|
|||
## Documentation & community resources
|
||||
|
||||
* [Documentation](https://docs.rs/ntex)
|
||||
* Minimum supported Rust version: 1.55 or later
|
||||
* Minimum supported Rust version: 1.57 or later
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ impl Future for ReadTask {
|
|||
}
|
||||
Poll::Ready(Err(err)) => {
|
||||
log::trace!("read task failed on io {:?}", err);
|
||||
let _ = this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.close(Some(err));
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
@ -444,7 +444,7 @@ mod unixstream {
|
|||
}
|
||||
Poll::Ready(Err(err)) => {
|
||||
log::trace!("read task failed on io {:?}", err);
|
||||
let _ = this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.close(Some(err));
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ thread_local! {
|
|||
}
|
||||
|
||||
/// Different types of process signals
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Signal {
|
||||
/// SIGHUP
|
||||
Hup,
|
||||
|
|
|
@ -17,7 +17,7 @@ impl ByteString {
|
|||
/// Get a str slice.
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&*self
|
||||
self
|
||||
}
|
||||
|
||||
/// Get a reference to the underlying bytes.
|
||||
|
@ -185,7 +185,7 @@ impl<T: AsRef<str>> PartialEq<T> for ByteString {
|
|||
impl AsRef<str> for ByteString {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str {
|
||||
&*self
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ impl ops::Deref for ByteString {
|
|||
impl borrow::Borrow<str> for ByteString {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &str {
|
||||
&*self
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ struct TcpConnectorResponse<T> {
|
|||
req: Option<T>,
|
||||
port: u16,
|
||||
addrs: Option<VecDeque<SocketAddr>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
stream: Option<Pin<Box<dyn Future<Output = Result<Io, io::Error>>>>>,
|
||||
pool: PoolRef,
|
||||
}
|
||||
|
|
|
@ -416,7 +416,7 @@ impl Future for ReadTask {
|
|||
Poll::Ready(Ok(n)) => {
|
||||
if n == 0 {
|
||||
log::trace!("io stream is disconnected");
|
||||
let _ = this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.close(None);
|
||||
return Poll::Ready(());
|
||||
} else {
|
||||
|
@ -428,14 +428,14 @@ impl Future for ReadTask {
|
|||
}
|
||||
Poll::Ready(Err(err)) => {
|
||||
log::trace!("read task failed on io {:?}", err);
|
||||
let _ = this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.close(Some(err));
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.release_read_buf(buf, new_bytes);
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
|
|
|
@ -80,6 +80,6 @@ pub(crate) fn register(timeout: Duration, io: &IoRef) -> Instant {
|
|||
|
||||
pub(crate) fn unregister(expire: Instant, io: &IoRef) {
|
||||
TIMER.with(|timer| {
|
||||
let _ = timer.borrow_mut().unregister(expire, io);
|
||||
timer.borrow_mut().unregister(expire, io);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
|||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{AttributeArgs, Ident, NestedMeta, Path};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum MethodType {
|
||||
Get,
|
||||
Post,
|
||||
|
|
|
@ -44,7 +44,7 @@ impl<'a> ResourcePath for &'a str {
|
|||
|
||||
impl ResourcePath for ntex_bytes::ByteString {
|
||||
fn path(&self) -> &str {
|
||||
&*self
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -302,7 +302,7 @@ impl ResourceDef {
|
|||
),
|
||||
};
|
||||
|
||||
re.push_str(&format!(r"(?P<{}>{})", &escape(name), pat));
|
||||
re = format!(r"{}(?P<{}>{})", re, &escape(name), pat);
|
||||
|
||||
elems.push(PathElement::Var(name.to_string()));
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ impl Future for ReadTask {
|
|||
Poll::Ready(Err(err)) => {
|
||||
log::trace!("read task failed on io {:?}", err);
|
||||
drop(io);
|
||||
let _ = this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.close(Some(err));
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
@ -540,7 +540,7 @@ mod unixstream {
|
|||
Poll::Ready(Err(err)) => {
|
||||
log::trace!("read task failed on io {:?}", err);
|
||||
drop(io);
|
||||
let _ = this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.release_read_buf(buf, new_bytes);
|
||||
this.state.close(Some(err));
|
||||
return Poll::Ready(());
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ thread_local! {
|
|||
}
|
||||
|
||||
/// Different types of process signals
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Signal {
|
||||
/// SIGHUP
|
||||
Hup,
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Changes
|
||||
|
||||
## [0.5.25] - 2022-08-22
|
||||
|
||||
* http: Fix http2 content-length handling
|
||||
|
||||
* http: Fix parsing ambiguity in Transfer-Encoding and Content-Length headers for HTTP/1.0 requests
|
||||
|
||||
## [0.5.24] - 2022-07-14
|
||||
|
||||
* ws: Do not encode pong into binary message (#130)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ntex"
|
||||
version = "0.5.24"
|
||||
version = "0.5.25"
|
||||
authors = ["ntex contributors <team@ntex.rs>"]
|
||||
description = "Framework for composable network services"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::{
|
|||
|
||||
use crate::util::{Bytes, BytesMut, Stream};
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
/// Body size hint
|
||||
pub enum BodySize {
|
||||
None,
|
||||
|
|
|
@ -58,7 +58,7 @@ where
|
|||
}
|
||||
|
||||
// Content length
|
||||
let _ = match length {
|
||||
match length {
|
||||
BodySize::None | BodySize::Stream => (),
|
||||
BodySize::Empty => {
|
||||
hdrs.insert(header::CONTENT_LENGTH, HeaderValue::from_static("0"))
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::http::{Request, Response};
|
|||
use crate::time::{sleep, Millis, Seconds};
|
||||
use crate::{io::IoRef, service::boxed::BoxService, util::BytesMut};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
/// Server keep-alive setting
|
||||
pub enum KeepAlive {
|
||||
/// Keep alive in seconds
|
||||
|
|
|
@ -228,7 +228,7 @@ pub enum H2Error {
|
|||
}
|
||||
|
||||
/// A set of error that can occure during parsing content type
|
||||
#[derive(thiserror::Error, PartialEq, Debug)]
|
||||
#[derive(thiserror::Error, PartialEq, Eq, Debug)]
|
||||
pub enum ContentTypeError {
|
||||
/// Cannot parse content type
|
||||
#[error("Cannot parse content type")]
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use std::{
|
||||
cell::Cell, convert::TryFrom, marker::PhantomData, mem::MaybeUninit, task::Poll,
|
||||
};
|
||||
use std::{cell::Cell, convert::TryFrom, marker::PhantomData, mem, task::Poll};
|
||||
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::{header, Method, StatusCode, Uri, Version};
|
||||
|
@ -19,7 +17,7 @@ const MAX_HEADERS: usize = 96;
|
|||
/// Incoming messagd decoder
|
||||
pub(super) struct MessageDecoder<T: MessageType>(PhantomData<T>);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
/// Incoming request type
|
||||
pub enum PayloadType {
|
||||
None,
|
||||
|
@ -48,12 +46,30 @@ impl<T: MessageType> Decoder for MessageDecoder<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(super) enum PayloadLength {
|
||||
Payload(PayloadType),
|
||||
Upgrade,
|
||||
None,
|
||||
}
|
||||
|
||||
#[allow(clippy::declare_interior_mutable_const)]
|
||||
const ZERO: PayloadLength = PayloadLength::Payload(PayloadType::Payload(PayloadDecoder {
|
||||
kind: Cell::new(Kind::Length(0)),
|
||||
}));
|
||||
|
||||
impl PayloadLength {
|
||||
/// Returns true if variant is `None`.
|
||||
fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
/// Returns true if variant is represents zero-length (not none) payload.
|
||||
fn is_zero(&self) -> bool {
|
||||
self == &ZERO
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait MessageType: Sized {
|
||||
fn set_connection_type(&mut self, ctype: Option<ConnectionType>);
|
||||
|
||||
|
@ -66,6 +82,7 @@ pub(super) trait MessageType: Sized {
|
|||
fn set_headers(
|
||||
&mut self,
|
||||
slice: &Bytes,
|
||||
version: Version,
|
||||
raw_headers: &[HeaderIndex],
|
||||
) -> Result<PayloadLength, ParseError> {
|
||||
let mut ka = None;
|
||||
|
@ -99,9 +116,9 @@ pub(super) trait MessageType: Sized {
|
|||
}
|
||||
Ok(s) => {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
if len != 0 {
|
||||
content_length = Some(len);
|
||||
}
|
||||
// accept 0 lengths here and remove them in `decode` after all
|
||||
// headers have been processed to prevent request smuggling issues
|
||||
content_length = Some(len);
|
||||
} else {
|
||||
log::debug!("illegal Content-Length: {:?}", s);
|
||||
return Err(ParseError::Header);
|
||||
|
@ -117,7 +134,7 @@ pub(super) trait MessageType: Sized {
|
|||
log::debug!("Transfer-Encoding header usage is not allowed");
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
header::TRANSFER_ENCODING => {
|
||||
header::TRANSFER_ENCODING if version == Version::HTTP_11 => {
|
||||
seen_te = true;
|
||||
if let Ok(s) = value.to_str().map(str::trim) {
|
||||
if s.eq_ignore_ascii_case("chunked") && content_length.is_none()
|
||||
|
@ -211,10 +228,10 @@ impl MessageType for Request {
|
|||
}
|
||||
|
||||
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
|
||||
let mut headers: [MaybeUninit<HeaderIndex>; MAX_HEADERS] = uninit_array();
|
||||
let mut headers: [mem::MaybeUninit<HeaderIndex>; MAX_HEADERS] = uninit_array();
|
||||
|
||||
let (len, method, uri, ver, headers) = {
|
||||
let mut parsed: [MaybeUninit<httparse::Header<'_>>; MAX_HEADERS] =
|
||||
let mut parsed: [mem::MaybeUninit<httparse::Header<'_>>; MAX_HEADERS] =
|
||||
uninit_array();
|
||||
|
||||
let mut req = httparse::Request::new(&mut []);
|
||||
|
@ -251,7 +268,21 @@ impl MessageType for Request {
|
|||
let mut msg = Request::new();
|
||||
|
||||
// convert headers
|
||||
let length = msg.set_headers(&src.split_to(len).freeze(), headers)?;
|
||||
let mut length = msg.set_headers(&src.split_to(len).freeze(), ver, headers)?;
|
||||
|
||||
// disallow HTTP/1.0 POST requests that do not contain a Content-Length headers
|
||||
// see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2
|
||||
if ver == Version::HTTP_10 && method == Method::POST && length.is_none() {
|
||||
debug!("no Content-Length specified for HTTP/1.0 POST request");
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
|
||||
// Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed.
|
||||
// Protects against some request smuggling attacks.
|
||||
// See https://github.com/actix/actix-web/issues/2767.
|
||||
if length.is_zero() {
|
||||
length = PayloadLength::None;
|
||||
}
|
||||
|
||||
// payload decoder
|
||||
let decoder = match length {
|
||||
|
@ -294,10 +325,10 @@ impl MessageType for ResponseHead {
|
|||
}
|
||||
|
||||
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
|
||||
let mut headers: [MaybeUninit<HeaderIndex>; MAX_HEADERS] = uninit_array();
|
||||
let mut headers: [mem::MaybeUninit<HeaderIndex>; MAX_HEADERS] = uninit_array();
|
||||
|
||||
let (len, ver, status, headers) = {
|
||||
let mut parsed: [MaybeUninit<httparse::Header<'_>>; MAX_HEADERS] =
|
||||
let mut parsed: [mem::MaybeUninit<httparse::Header<'_>>; MAX_HEADERS] =
|
||||
uninit_array();
|
||||
|
||||
let mut res = httparse::Response::new(&mut []);
|
||||
|
@ -337,7 +368,14 @@ impl MessageType for ResponseHead {
|
|||
msg.version = ver;
|
||||
|
||||
// convert headers
|
||||
let length = msg.set_headers(&src.split_to(len).freeze(), headers)?;
|
||||
let mut length = msg.set_headers(&src.split_to(len).freeze(), ver, headers)?;
|
||||
|
||||
// Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed.
|
||||
// Protects against some request smuggling attacks.
|
||||
// See https://github.com/actix/actix-web/issues/2767.
|
||||
if length.is_zero() {
|
||||
length = PayloadLength::None;
|
||||
}
|
||||
|
||||
// message payload
|
||||
let decoder = if let PayloadLength::Payload(pl) = length {
|
||||
|
@ -369,7 +407,7 @@ impl HeaderIndex {
|
|||
pub(super) fn record<'a>(
|
||||
bytes: &[u8],
|
||||
headers: &[httparse::Header<'_>],
|
||||
indices: &'a mut [MaybeUninit<HeaderIndex>],
|
||||
indices: &'a mut [mem::MaybeUninit<HeaderIndex>],
|
||||
) -> &'a [HeaderIndex] {
|
||||
let bytes_ptr = bytes.as_ptr() as usize;
|
||||
|
||||
|
@ -393,13 +431,13 @@ impl HeaderIndex {
|
|||
//
|
||||
// The total initialized items are counted by iterator.
|
||||
unsafe {
|
||||
&*(&indices[..init_len] as *const [MaybeUninit<HeaderIndex>]
|
||||
&*(&indices[..init_len] as *const [mem::MaybeUninit<HeaderIndex>]
|
||||
as *const [HeaderIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Http payload item
|
||||
pub enum PayloadItem {
|
||||
Chunk(Bytes),
|
||||
|
@ -410,7 +448,7 @@ pub enum PayloadItem {
|
|||
///
|
||||
/// If a message body does not include a Transfer-Encoding, it *should*
|
||||
/// include a Content-Length header.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PayloadDecoder {
|
||||
kind: Cell<Kind>,
|
||||
}
|
||||
|
@ -435,7 +473,7 @@ impl PayloadDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Kind {
|
||||
/// A Reader used when a Content-Length header is passed with a positive
|
||||
/// integer.
|
||||
|
@ -459,7 +497,7 @@ enum Kind {
|
|||
Eof,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum ChunkedState {
|
||||
Size,
|
||||
SizeLws,
|
||||
|
@ -693,9 +731,9 @@ impl ChunkedState {
|
|||
}
|
||||
}
|
||||
|
||||
fn uninit_array<T, const LEN: usize>() -> [MaybeUninit<T>; LEN] {
|
||||
// SAFETY: An uninitialized `[MaybeUninit<_>; LEN]` is valid.
|
||||
unsafe { MaybeUninit::uninit().assume_init() }
|
||||
fn uninit_array<T, const LEN: usize>() -> [mem::MaybeUninit<T>; LEN] {
|
||||
// SAFETY: An uninitialized `[mem::MaybeUninit<_>; LEN]` is valid.
|
||||
unsafe { mem::MaybeUninit::uninit().assume_init() }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -785,14 +823,79 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_post() {
|
||||
let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n");
|
||||
fn parse_h10_get() {
|
||||
let mut buf = BytesMut::from(
|
||||
"GET /test1 HTTP/1.0\r\n\
|
||||
\r\n\
|
||||
abc",
|
||||
);
|
||||
|
||||
let reader = MessageDecoder::<Request>::default();
|
||||
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
assert_eq!(req.version(), Version::HTTP_10);
|
||||
assert_eq!(*req.method(), Method::GET);
|
||||
assert_eq!(req.path(), "/test1");
|
||||
|
||||
let mut buf = BytesMut::from(
|
||||
"GET /test2 HTTP/1.0\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
\r\n",
|
||||
);
|
||||
|
||||
let reader = MessageDecoder::<Request>::default();
|
||||
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
assert_eq!(req.version(), Version::HTTP_10);
|
||||
assert_eq!(*req.method(), Method::GET);
|
||||
assert_eq!(req.path(), "/test2");
|
||||
|
||||
let mut buf = BytesMut::from(
|
||||
"GET /test3 HTTP/1.0\r\n\
|
||||
Content-Length: 3\r\n\
|
||||
\r\n
|
||||
abc",
|
||||
);
|
||||
|
||||
let reader = MessageDecoder::<Request>::default();
|
||||
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
assert_eq!(req.version(), Version::HTTP_10);
|
||||
assert_eq!(*req.method(), Method::GET);
|
||||
assert_eq!(req.path(), "/test3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_h10_post() {
|
||||
let mut buf = BytesMut::from(
|
||||
"POST /test1 HTTP/1.0\r\n\
|
||||
Content-Length: 3\r\n\
|
||||
\r\n\
|
||||
abc",
|
||||
);
|
||||
|
||||
let reader = MessageDecoder::<Request>::default();
|
||||
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
assert_eq!(req.version(), Version::HTTP_10);
|
||||
assert_eq!(*req.method(), Method::POST);
|
||||
assert_eq!(req.path(), "/test1");
|
||||
|
||||
let mut buf = BytesMut::from(
|
||||
"POST /test2 HTTP/1.0\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
\r\n",
|
||||
);
|
||||
|
||||
let reader = MessageDecoder::<Request>::default();
|
||||
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
assert_eq!(req.version(), Version::HTTP_10);
|
||||
assert_eq!(*req.method(), Method::POST);
|
||||
assert_eq!(req.path(), "/test2");
|
||||
|
||||
let mut buf = BytesMut::from(
|
||||
"POST /test3 HTTP/1.0\r\n\
|
||||
\r\n",
|
||||
);
|
||||
let reader = MessageDecoder::<Request>::default();
|
||||
let err = reader.decode(&mut buf).unwrap_err();
|
||||
assert!(err.to_string().contains("Header"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1281,6 +1384,50 @@ mod tests {
|
|||
abcd",
|
||||
);
|
||||
expect_parse_err!(&mut buf);
|
||||
|
||||
let mut buf = BytesMut::from(
|
||||
"GET / HTTP/1.1\r\n\
|
||||
Host: example.com\r\n\
|
||||
Content-Length: 0\r\n\
|
||||
Content-Length: 2\r\n\
|
||||
\r\n\
|
||||
ab",
|
||||
);
|
||||
expect_parse_err!(&mut buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_encoding_http10() {
|
||||
// in HTTP/1.0 transfer encoding is ignored and must therefore contain a CL header
|
||||
|
||||
let mut buf = BytesMut::from(
|
||||
"POST / HTTP/1.0\r\n\
|
||||
Host: example.com\r\n\
|
||||
Transfer-Encoding: chunked\r\n\
|
||||
\r\n\
|
||||
3\r\n\
|
||||
aaa\r\n\
|
||||
0\r\n\
|
||||
",
|
||||
);
|
||||
|
||||
expect_parse_err!(&mut buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_length_and_te_http10() {
|
||||
// in HTTP/1.0 transfer encoding is simply ignored so it's fine to have both
|
||||
|
||||
let mut buf = BytesMut::from(
|
||||
"GET / HTTP/1.0\r\n\
|
||||
Host: example.com\r\n\
|
||||
Content-Length: 3\r\n\
|
||||
Transfer-Encoding: chunked\r\n\
|
||||
\r\n\
|
||||
000",
|
||||
);
|
||||
|
||||
parse_ready!(&mut buf);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -6,7 +6,7 @@ use ntex_h2::{self as h2, frame::StreamId, server};
|
|||
use crate::http::body::{BodySize, MessageBody};
|
||||
use crate::http::config::{DispatcherConfig, ServiceConfig};
|
||||
use crate::http::error::{DispatchError, H2Error, ResponseError};
|
||||
use crate::http::header::{self, HeaderMap, HeaderValue};
|
||||
use crate::http::header::{self, HeaderMap, HeaderName, HeaderValue};
|
||||
use crate::http::message::{CurrentIo, ResponseHead};
|
||||
use crate::http::{DateService, Method, Request, Response, StatusCode, Uri, Version};
|
||||
use crate::io::{types, Filter, Io, IoBoxed, IoRef};
|
||||
|
@ -440,39 +440,46 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn prepare_response(timer: &DateService, head: &mut ResponseHead, size: &mut BodySize) {
|
||||
let mut skip_len = size == &BodySize::Stream;
|
||||
#[allow(clippy::declare_interior_mutable_const)]
|
||||
const ZERO_CONTENT_LENGTH: HeaderValue = HeaderValue::from_static("0");
|
||||
#[allow(clippy::declare_interior_mutable_const)]
|
||||
const KEEP_ALIVE: HeaderName = HeaderName::from_static("keep-alive");
|
||||
#[allow(clippy::declare_interior_mutable_const)]
|
||||
const PROXY_CONNECTION: HeaderName = HeaderName::from_static("proxy-connection");
|
||||
|
||||
fn prepare_response(timer: &DateService, head: &mut ResponseHead, size: &mut BodySize) {
|
||||
// Content length
|
||||
match head.status {
|
||||
StatusCode::NO_CONTENT | StatusCode::CONTINUE | StatusCode::PROCESSING => {
|
||||
*size = BodySize::None
|
||||
}
|
||||
StatusCode::SWITCHING_PROTOCOLS => {
|
||||
skip_len = true;
|
||||
*size = BodySize::Stream;
|
||||
head.headers.remove(header::CONTENT_LENGTH);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let _ = match size {
|
||||
BodySize::None | BodySize::Stream => (),
|
||||
match size {
|
||||
BodySize::None | BodySize::Stream => head.headers.remove(header::CONTENT_LENGTH),
|
||||
BodySize::Empty => head
|
||||
.headers
|
||||
.insert(header::CONTENT_LENGTH, HeaderValue::from_static("0")),
|
||||
.insert(header::CONTENT_LENGTH, ZERO_CONTENT_LENGTH),
|
||||
BodySize::Sized(len) => {
|
||||
if !skip_len {
|
||||
head.headers.insert(
|
||||
header::CONTENT_LENGTH,
|
||||
HeaderValue::try_from(format!("{}", len)).unwrap(),
|
||||
);
|
||||
}
|
||||
head.headers.insert(
|
||||
header::CONTENT_LENGTH,
|
||||
HeaderValue::try_from(format!("{}", len)).unwrap(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// http2 specific1
|
||||
head.headers.remove(header::CONNECTION);
|
||||
head.headers.remove(header::TRANSFER_ENCODING);
|
||||
head.headers.remove(header::UPGRADE);
|
||||
|
||||
// omit HTTP/1.x only headers according to:
|
||||
// https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
|
||||
head.headers.remove(KEEP_ALIVE);
|
||||
head.headers.remove(PROXY_CONNECTION);
|
||||
|
||||
// set date header
|
||||
if !head.headers.contains_key(header::DATE) {
|
||||
|
|
|
@ -8,7 +8,7 @@ pub use ntex_http::header::{AsName, GetAll, Value};
|
|||
pub use ntex_http::HeaderMap;
|
||||
|
||||
/// Represents supported types of content encodings
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ContentEncoding {
|
||||
/// Automatically select encoding based on encoding negotiation
|
||||
Auto,
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::io::{types, IoBoxed, IoRef};
|
|||
use crate::util::Extensions;
|
||||
|
||||
/// Represents various types of connection
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ConnectionType {
|
||||
/// Close connection after response
|
||||
Close,
|
||||
|
|
|
@ -78,14 +78,14 @@ where
|
|||
}
|
||||
|
||||
/// Errors which can occur when attempting to work with `Data` extractor
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
#[derive(Error, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DataExtractorError {
|
||||
#[error("App data is not configured, to configure use App::data()")]
|
||||
NotConfigured,
|
||||
}
|
||||
|
||||
/// Errors which can occur when attempting to generate resource uri.
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
#[derive(Error, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum UrlGenerationError {
|
||||
/// Resource not found
|
||||
#[error("Resource not found")]
|
||||
|
|
|
@ -207,7 +207,7 @@ impl HttpRequest {
|
|||
/// borrowed.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
||||
ConnectionInfo::get(self.head(), &*self.app_config())
|
||||
ConnectionInfo::get(self.head(), self.app_config())
|
||||
}
|
||||
|
||||
/// App config
|
||||
|
|
|
@ -169,7 +169,7 @@ impl<Err> WebRequest<Err> {
|
|||
/// Get *ConnectionInfo* for the current request.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
|
||||
ConnectionInfo::get(self.head(), &*self.app_config())
|
||||
ConnectionInfo::get(self.head(), self.app_config())
|
||||
}
|
||||
|
||||
/// Get a reference to the Path parameters.
|
||||
|
|
|
@ -8,7 +8,7 @@ use super::frame::Parser;
|
|||
use super::proto::{CloseReason, OpCode};
|
||||
|
||||
/// WebSocket message
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Message {
|
||||
/// Text message
|
||||
Text(ByteString),
|
||||
|
@ -25,7 +25,7 @@ pub enum Message {
|
|||
}
|
||||
|
||||
/// WebSocket frame
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Frame {
|
||||
/// Text frame, codec does not verify utf8 encoding
|
||||
Text(Bytes),
|
||||
|
@ -42,7 +42,7 @@ pub enum Frame {
|
|||
}
|
||||
|
||||
/// WebSocket continuation item
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Item {
|
||||
FirstText(Bytes),
|
||||
FirstBinary(Bytes),
|
||||
|
|
|
@ -124,7 +124,7 @@ impl From<Either<io::Error, io::Error>> for WsClientError {
|
|||
}
|
||||
|
||||
/// Websocket handshake errors
|
||||
#[derive(Error, PartialEq, Debug)]
|
||||
#[derive(Error, PartialEq, Eq, Debug)]
|
||||
pub enum HandshakeError {
|
||||
/// Only get method is allowed
|
||||
#[error("Method not allowed")]
|
||||
|
|
|
@ -116,7 +116,7 @@ async fn test_chunked_payload() {
|
|||
let returned_size = {
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ =
|
||||
stream.write_all(b"POST /test HTTP/1.0\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
stream.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
|
||||
for chunk_size in chunk_sizes.iter() {
|
||||
let mut bytes = Vec::new();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue