Skip to content

Commit dd6eeab

Browse files
committed
Limit push endpoint response size
* Does not trust `Content-Length`. * Will not allocate more than 64 KiB plus one frame, even on chunked responses.
1 parent 95d8146 commit dd6eeab

File tree

4 files changed

+21
-25
lines changed

4 files changed

+21
-25
lines changed

src/clients/hyper_client.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,13 @@ impl WebPushClient for HyperWebPushClient {
6666
let response_status = response.status();
6767
trace!("Response status: {}", response_status);
6868

69-
let content_length: usize = response
70-
.headers()
71-
.get(CONTENT_LENGTH)
72-
.and_then(|s| s.to_str().ok())
73-
.and_then(|s| s.parse().ok())
74-
.unwrap_or(0);
75-
76-
let mut body: Vec<u8> = Vec::with_capacity(content_length);
7769
let mut chunks = response.into_body();
78-
70+
let mut body = Vec::new();
7971
while let Some(chunk) = chunks.data().await {
8072
body.extend(&chunk?);
73+
if body.len() > MAX_RESPONSE_SIZE {
74+
return Err(WebPushError::ResponseTooLarge);
75+
}
8176
}
8277
trace!("Body: {:?}", body);
8378

src/clients/isahc_client.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use async_trait::async_trait;
22
use futures_lite::AsyncReadExt;
3-
use http::header::{CONTENT_LENGTH, RETRY_AFTER};
3+
use http::header::RETRY_AFTER;
44
use isahc::HttpClient;
55

66
use crate::clients::request_builder;
7-
use crate::clients::WebPushClient;
7+
use crate::clients::{WebPushClient, MAX_RESPONSE_SIZE};
88
use crate::error::{RetryAfter, WebPushError};
99
use crate::message::WebPushMessage;
1010

@@ -67,21 +67,16 @@ impl WebPushClient for IsahcWebPushClient {
6767
let response_status = response.status();
6868
trace!("Response status: {}", response_status);
6969

70-
let content_length: usize = response
71-
.headers()
72-
.get(CONTENT_LENGTH)
73-
.and_then(|s| s.to_str().ok())
74-
.and_then(|s| s.parse().ok())
75-
.unwrap_or(0);
76-
77-
let mut body: Vec<u8> = Vec::with_capacity(content_length);
78-
let mut chunks = response.into_body();
79-
80-
chunks
70+
let mut body = Vec::new();
71+
if response
72+
.into_body()
73+
.take(MAX_RESPONSE_SIZE as u64 + 1)
8174
.read_to_end(&mut body)
82-
.await
83-
.map_err(|_| WebPushError::InvalidResponse)?;
84-
75+
.await?
76+
> MAX_RESPONSE_SIZE
77+
{
78+
return Err(WebPushError::ResponseTooLarge);
79+
}
8580
trace!("Body: {:?}", body);
8681

8782
trace!("Body text: {:?}", std::str::from_utf8(&body));

src/clients/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub mod hyper_client;
1414
#[cfg(feature = "isahc-client")]
1515
pub mod isahc_client;
1616

17+
const MAX_RESPONSE_SIZE: usize = 64 * 1024;
18+
1719
/// An async client for sending the notification payload.
1820
/// Other features, such as thread safety, may vary by implementation.
1921
#[async_trait]

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ pub enum WebPushError {
6464
InvalidResponse,
6565
/// A claim had invalid data
6666
InvalidClaims,
67+
/// Response from push endpoint was too large
68+
ResponseTooLarge,
6769
Other(ErrorInfo),
6870
}
6971

@@ -134,6 +136,7 @@ impl WebPushError {
134136
WebPushError::Io(_) => "io_error",
135137
WebPushError::Other(_) => "other",
136138
WebPushError::InvalidClaims => "invalidClaims",
139+
WebPushError::ResponseTooLarge => "response_too_large",
137140
}
138141
}
139142
}
@@ -162,6 +165,7 @@ impl fmt::Display for WebPushError {
162165
WebPushError::InvalidCryptoKeys => write!(f, "request has invalid cryptographic keys"),
163166
WebPushError::Other(info) => write!(f, "other: {}", info),
164167
WebPushError::InvalidClaims => write!(f, "at least one jwt claim was invalid"),
168+
WebPushError::ResponseTooLarge => write!(f, "response from push endpoint was too large"),
165169
}
166170
}
167171
}

0 commit comments

Comments
 (0)