Skip to main content

jsonrpsee_core/
http_helpers.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27//! Shared HTTP utilities.
28
29use crate::{classify_message, BoxError};
30use bytes::{Buf, Bytes};
31use http_body::Frame;
32use http_body_util::{BodyExt, Limited};
33use std::{
34    pin::Pin,
35    task::{Context, Poll},
36};
37
38/// Upper bound for the buffer reserved from a request's declared `Content-Length`.
39///
40/// Requests up to this size are read into a single allocation. The bound keeps a client that
41/// declares a large body without sending it from pinning that much memory per connection.
42const MAX_BODY_PREALLOCATION: usize = 1024 * 1024;
43
44/// HTTP request type.
45pub type Request<T = Body> = http::Request<T>;
46/// HTTP response type.
47pub type Response<T = Body> = http::Response<T>;
48
49/// Default HTTP body used by jsonrpsee.
50#[derive(Debug, Default)]
51pub struct Body(http_body_util::combinators::UnsyncBoxBody<Bytes, BoxError>);
52
53impl Body {
54    /// Create an empty body.
55    pub fn empty() -> Self {
56        Self::default()
57    }
58
59    /// Create a new body.
60    pub fn new<B>(body: B) -> Self
61    where
62        B: http_body::Body<Data = Bytes> + Send + 'static,
63        B::Data: Send + 'static,
64        B::Error: Into<BoxError>,
65    {
66        Self(body.map_err(|e| e.into()).boxed_unsync())
67    }
68}
69
70impl From<String> for Body {
71    fn from(s: String) -> Self {
72        let body = http_body_util::Full::from(s);
73        Self::new(body)
74    }
75}
76
77impl From<&'static str> for Body {
78    fn from(s: &'static str) -> Self {
79        let body = http_body_util::Full::from(s);
80        Self::new(body)
81    }
82}
83
84impl From<Vec<u8>> for Body {
85    fn from(bytes: Vec<u8>) -> Self {
86        let body = http_body_util::Full::from(bytes);
87        Self::new(body)
88    }
89}
90
91impl http_body::Body for Body {
92    type Data = Bytes;
93    type Error = BoxError;
94
95    #[inline]
96    fn poll_frame(
97        mut self: Pin<&mut Self>,
98        cx: &mut Context<'_>,
99    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
100        Pin::new(&mut self.0).poll_frame(cx)
101    }
102
103    #[inline]
104    fn size_hint(&self) -> http_body::SizeHint {
105        self.0.size_hint()
106    }
107
108    #[inline]
109    fn is_end_stream(&self) -> bool {
110        self.0.is_end_stream()
111    }
112}
113
114/// Represents error that can when reading with a HTTP body.
115#[derive(Debug, thiserror::Error)]
116pub enum HttpError {
117    /// The HTTP message was too large.
118    #[error("The HTTP message was too big")]
119    TooLarge,
120    /// Malformed request
121    #[error("Malformed request")]
122    Malformed,
123    /// Represents error that can happen when dealing with HTTP streams.
124    #[error(transparent)]
125    Stream(#[from] BoxError),
126}
127
128/// Read data from a HTTP body and return the data if it is valid JSON and within the allowed size
129/// range.
130///
131/// Returns `Ok((bytes, single))` if the body was in valid size range; and a bool indicating whether
132/// the JSON-RPC request is a single or a batch.
133/// Returns `Err` if the body was too large or the body couldn't be read.
134pub async fn read_body<B>(
135    headers: &http::HeaderMap,
136    body: B,
137    max_body_size: u32,
138) -> Result<(Vec<u8>, bool), HttpError>
139where
140    B: http_body::Body<Data = Bytes> + Send + 'static,
141    B::Data: Send,
142    B::Error: Into<BoxError>,
143{
144    // NOTE(niklasad1): Values bigger than `u32::MAX` will be turned into zero here. This is
145    // unlikely to occur in practice and in that case we fallback to allocating in the
146    // while-loop below instead of pre-allocating.
147    let body_size = read_header_content_length(headers).unwrap_or(0);
148
149    if body_size > max_body_size {
150        return Err(HttpError::TooLarge);
151    }
152
153    futures_util::pin_mut!(body);
154
155    let mut received_data =
156        Vec::with_capacity(std::cmp::min(body_size as usize, MAX_BODY_PREALLOCATION));
157    let mut limited_body = Limited::new(body, max_body_size as usize);
158
159    let mut is_single = None;
160
161    while let Some(frame_or_err) = limited_body.frame().await {
162        let frame = frame_or_err.map_err(HttpError::Stream)?;
163        let Some(data) = frame.data_ref() else {
164            continue;
165        };
166
167        // If it's the first chunk, trim the whitespaces to determine whether it's valid JSON-RPC
168        // call.
169        if received_data.is_empty() {
170            let Some((data, single)) = classify_message(data.chunk()) else {
171                return Err(HttpError::Malformed);
172            };
173            is_single = Some(single);
174
175            // ignore whitespace as these doesn't matter just makes the JSON decoding slower.
176            received_data.extend_from_slice(data);
177        } else {
178            received_data.extend_from_slice(data.chunk());
179        }
180    }
181
182    match is_single {
183        Some(single) if !received_data.is_empty() => {
184            tracing::trace!(
185                target: "jsonrpsee-http",
186                "HTTP body: {}",
187                std::str::from_utf8(&received_data).unwrap_or("Invalid UTF-8 data")
188            );
189            Ok((received_data, single))
190        }
191        _ => Err(HttpError::Malformed),
192    }
193}
194
195/// Read the `Content-Length` HTTP Header. Must fit into a `u32`; returns `None` otherwise.
196///
197/// NOTE: There's no specific hard limit on `Content_length` in HTTP specification.
198/// Thus this method might reject valid `content_length`
199fn read_header_content_length(headers: &http::header::HeaderMap) -> Option<u32> {
200    let mut values = headers.get_all(http::header::CONTENT_LENGTH).iter();
201    let length = values.next()?;
202    // Reject ambiguous requests with more than one value.
203    if values.next().is_some() {
204        return None
205    }
206    // HTTP Content-Length indicates number of bytes in decimal.
207    length.to_str().ok()?.parse().ok()
208}
209
210#[cfg(test)]
211mod tests {
212    use super::{read_body, read_header_content_length};
213
214    type Body = http_body_util::Full<bytes::Bytes>;
215
216    #[tokio::test]
217    async fn body_to_bytes_size_limit_works() {
218        let headers = http::header::HeaderMap::new();
219        let body = Body::from(vec![0; 128]);
220        assert!(read_body(&headers, body, 127).await.is_err());
221    }
222
223    #[test]
224    fn read_content_length_works() {
225        let mut headers = http::header::HeaderMap::new();
226        headers.insert(http::header::CONTENT_LENGTH, "177".parse().unwrap());
227        assert_eq!(read_header_content_length(&headers), Some(177));
228
229        headers.append(http::header::CONTENT_LENGTH, "999".parse().unwrap());
230        assert_eq!(read_header_content_length(&headers), None);
231    }
232
233    #[test]
234    fn read_content_length_too_big_value() {
235        let mut headers = http::header::HeaderMap::new();
236        headers.insert(http::header::CONTENT_LENGTH, "18446744073709551616".parse().unwrap());
237        assert_eq!(read_header_content_length(&headers), None);
238    }
239}