Skip to content

Commit 51923ca

Browse files
AlexandruCihodaruluminitavoicu
authored andcommitted
Add optional limit on request size
Signed-off-by: AlexandruCihodaru <[email protected]>
1 parent ba4e5a0 commit 51923ca

File tree

2 files changed

+121
-5
lines changed

2 files changed

+121
-5
lines changed

src/connection.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub use crate::common::{ConnectionError, HttpHeaderError, RequestError};
1111
use crate::headers::Headers;
1212
use crate::request::{find, Request, RequestLine};
1313
use crate::response::{Response, StatusCode};
14+
use crate::server::MAX_PAYLOAD_SIZE;
1415
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
1516

1617
const BUFFER_SIZE: usize = 1024;
@@ -54,6 +55,8 @@ pub struct HttpConnection<T> {
5455
/// The latest file that has been received and which must be associated
5556
/// with the pending request.
5657
file: Option<File>,
58+
/// Optional payload max size.
59+
payload_max_size: usize,
5760
}
5861

5962
impl<T: Read + Write + ScmSocket> HttpConnection<T> {
@@ -71,9 +74,16 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
7174
response_queue: VecDeque::new(),
7275
response_buffer: None,
7376
file: None,
77+
payload_max_size: MAX_PAYLOAD_SIZE,
7478
}
7579
}
7680

81+
/// This function sets the limit for PUT/PATCH requests. It overwrites the
82+
/// default limit of 0.05MiB with the one allowed by server.
83+
pub fn set_payload_max_size(&mut self, request_payload_max_size: usize) {
84+
self.payload_max_size = request_payload_max_size;
85+
}
86+
7787
/// Tries to read new bytes from the stream and automatically update the request.
7888
/// Meant to be used only with non-blocking streams and an `EPOLL` structure.
7989
/// Should be called whenever an `EPOLLIN` event is signaled.
@@ -245,6 +255,14 @@ impl<T: Read + Write + ScmSocket> HttpConnection<T> {
245255
if request.headers.content_length() == 0 {
246256
self.state = ConnectionState::RequestReady;
247257
} else {
258+
if request.headers.content_length() as usize > self.payload_max_size {
259+
return Err(ConnectionError::ParseError(
260+
RequestError::SizeLimitExceeded(
261+
self.payload_max_size,
262+
request.headers.content_length() as usize,
263+
),
264+
));
265+
}
248266
if request.headers.expect() {
249267
// Send expect.
250268
let expect_response =
@@ -504,6 +522,7 @@ mod tests {
504522

505523
use super::*;
506524
use crate::common::{Method, Version};
525+
use crate::server::MAX_PAYLOAD_SIZE;
507526

508527
#[test]
509528
fn test_try_read_expect() {
@@ -933,6 +952,24 @@ mod tests {
933952
);
934953
}
935954

955+
#[test]
956+
fn test_payload_size_limit() {
957+
let (mut sender, receiver) = UnixStream::pair().unwrap();
958+
receiver.set_nonblocking(true).expect("Can't modify socket");
959+
let mut conn = HttpConnection::new(receiver);
960+
conn.set_payload_max_size(5);
961+
sender
962+
.write_all(
963+
b"PUT http://localhost/home HTTP/1.1\r\n\
964+
Content-Length: 51200\r\n\r\naaaaaa",
965+
)
966+
.unwrap();
967+
assert_eq!(
968+
conn.try_read().unwrap_err(),
969+
ConnectionError::ParseError(RequestError::SizeLimitExceeded(5, MAX_PAYLOAD_SIZE))
970+
);
971+
}
972+
936973
#[test]
937974
fn test_read_bytes() {
938975
let (mut sender, receiver) = UnixStream::pair().unwrap();

src/server.rs

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ static SERVER_FULL_ERROR_MESSAGE: &[u8] = b"HTTP/1.1 503\r\n\
2121
Connection: close\r\n\
2222
Content-Length: 40\r\n\r\n{ \"error\": \"Too many open connections\" }";
2323
const MAX_CONNECTIONS: usize = 10;
24+
/// Payload max size
25+
pub(crate) const MAX_PAYLOAD_SIZE: usize = 51200;
2426

2527
type Result<T> = std::result::Result<T, ServerError>;
2628

@@ -259,6 +261,8 @@ pub struct HttpServer {
259261
/// We use the file descriptor of the stream as the key for mapping
260262
/// connections because the 1-to-1 relation is guaranteed by the OS.
261263
connections: HashMap<RawFd, ClientConnection<UnixStream>>,
264+
/// Payload max size
265+
payload_max_size: usize,
262266
}
263267

264268
impl HttpServer {
@@ -275,6 +279,7 @@ impl HttpServer {
275279
socket,
276280
epoll,
277281
connections: HashMap::new(),
282+
payload_max_size: MAX_PAYLOAD_SIZE,
278283
})
279284
}
280285

@@ -295,9 +300,16 @@ impl HttpServer {
295300
socket,
296301
epoll,
297302
connections: HashMap::new(),
303+
payload_max_size: MAX_PAYLOAD_SIZE,
298304
})
299305
}
300306

307+
/// This function sets the limit for PUT/PATCH requests. It overwrites the
308+
/// default limit of 0.05MiB with the one allowed by server.
309+
pub fn set_payload_max_size(&mut self, request_payload_max_size: usize) {
310+
self.payload_max_size = request_payload_max_size;
311+
}
312+
301313
/// Starts the HTTP Server.
302314
pub fn start_server(&mut self) -> Result<()> {
303315
// Add the socket on which we listen for new connections to the
@@ -573,12 +585,12 @@ impl HttpServer {
573585
})
574586
.and_then(|stream| {
575587
// Add the stream to the `epoll` structure and listen for bytes to be read.
576-
Self::epoll_add(&self.epoll, stream.as_raw_fd())?;
588+
let raw_fd = stream.as_raw_fd();
589+
Self::epoll_add(&self.epoll, raw_fd)?;
590+
let mut conn = HttpConnection::new(stream);
591+
conn.set_payload_max_size(self.payload_max_size);
577592
// Then add it to our open connections.
578-
self.connections.insert(
579-
stream.as_raw_fd(),
580-
ClientConnection::new(HttpConnection::new(stream)),
581-
);
593+
self.connections.insert(raw_fd, ClientConnection::new(conn));
582594
Ok(())
583595
})
584596
}
@@ -676,6 +688,73 @@ mod tests {
676688
assert!(socket.read(&mut buf[..]).unwrap() > 0);
677689
}
678690

691+
#[test]
692+
fn test_connection_size_limit_exceeded() {
693+
let path_to_socket = get_temp_socket_file();
694+
695+
let mut server = HttpServer::new(path_to_socket.as_path()).unwrap();
696+
server.start_server().unwrap();
697+
698+
// Test one incoming connection.
699+
let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap();
700+
assert!(server.requests().unwrap().is_empty());
701+
702+
socket
703+
.write_all(
704+
b"PATCH /machine-config HTTP/1.1\r\n\
705+
Content-Length: 51201\r\n\
706+
Content-Type: application/json\r\n\r\naaaaa",
707+
)
708+
.unwrap();
709+
assert!(server.requests().unwrap().is_empty());
710+
assert!(server.requests().unwrap().is_empty());
711+
let mut buf: [u8; 265] = [0; 265];
712+
assert!(socket.read(&mut buf[..]).unwrap() > 0);
713+
let error_message = b"HTTP/1.1 400 \r\n\
714+
Server: Firecracker API\r\n\
715+
Connection: keep-alive\r\n\
716+
Content-Type: application/json\r\n\
717+
Content-Length: 149\r\n\r\n{ \"error\": \"\
718+
Request payload with size 51201 is larger than \
719+
the limit of 51200 allowed by server.\nAll \
720+
previous unanswered requests will be dropped.";
721+
assert_eq!(&buf[..], &error_message[..]);
722+
}
723+
724+
#[test]
725+
fn test_set_payload_size() {
726+
let path_to_socket = get_temp_socket_file();
727+
728+
let mut server = HttpServer::new(path_to_socket.as_path()).unwrap();
729+
server.start_server().unwrap();
730+
server.set_payload_max_size(4);
731+
732+
// Test one incoming connection.
733+
let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap();
734+
assert!(server.requests().unwrap().is_empty());
735+
736+
socket
737+
.write_all(
738+
b"PATCH /machine-config HTTP/1.1\r\n\
739+
Content-Length: 5\r\n\
740+
Content-Type: application/json\r\n\r\naaaaa",
741+
)
742+
.unwrap();
743+
assert!(server.requests().unwrap().is_empty());
744+
assert!(server.requests().unwrap().is_empty());
745+
let mut buf: [u8; 260] = [0; 260];
746+
assert!(socket.read(&mut buf[..]).unwrap() > 0);
747+
let error_message = b"HTTP/1.1 400 \r\n\
748+
Server: Firecracker API\r\n\
749+
Connection: keep-alive\r\n\
750+
Content-Type: application/json\r\n\
751+
Content-Length: 141\r\n\r\n{ \"error\": \"\
752+
Request payload with size 5 is larger than the \
753+
limit of 4 allowed by server.\nAll previous \
754+
unanswered requests will be dropped.\" }";
755+
assert_eq!(&buf[..], &error_message[..]);
756+
}
757+
679758
#[test]
680759
fn test_wait_one_fd_connection() {
681760
use std::os::unix::io::IntoRawFd;

0 commit comments

Comments
 (0)