Skip to content

Commit a3b3d71

Browse files
0x676e67seanmonstartottotopaolobarboliniGlenDC
authored
Sync upstream (hyperium#15)
* chore: allow matching infallible (hyperium#796) * v0.4.6 * chore(ci): use tokio-util 0.7.11 in MSRV check * style: replace `split_to` and `split_off` with better alternatives This removes `let _ = ` from in front of `split_to` and `split_off` and mostly follows the suggestions from the `#[must_use]` impls. One of the uses of `split_to` is instead replaced with `take`. * improve ci/h2spec.sh (macOS compat, /tmp dir and overwrite) (hyperium#809) - detect if run on MacOS, so we download h2spec macos build in that case - support overwriting h2spec detection so we anyway download new file, useful in case you switch to new version for example - move h2spec, archive and log all to /tmp dir as to not polute the repo dir * ci: pin hashbrown for msrv job (hyperium#814) * fix: HEADERS frame with non-zero content-length and END_STREAM is malformed (hyperium#813) Before this change, content-length underflow is only checked when receiving date frames. The underflow error was never triggered if data frames are never received. This change adds similar check for headers frames. * fix: notify_recv after send_reset() in reset_on_recv_stream_err() to ensure local stream is released properly (hyperium#816) (hyperium#818) Similar to what have been done in fn send_reset<B>(), we should notify RecvStream that is parked after send_reset(). Co-authored-by: Jiahao Liang <[email protected]> --------- Co-authored-by: Sean McArthur <[email protected]> Co-authored-by: tottoto <[email protected]> Co-authored-by: Paolo Barbolini <[email protected]> Co-authored-by: Glen De Cauwsemaecker <[email protected]> Co-authored-by: Yuchen Wu <[email protected]> Co-authored-by: Jiahao Liang <[email protected]>
1 parent 0a1a923 commit a3b3d71

File tree

12 files changed

+114
-22
lines changed

12 files changed

+114
-22
lines changed

.github/workflows/CI.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ jobs:
4949
with:
5050
toolchain: ${{ steps.msrv.outputs.version }}
5151

52-
- name: Make sure tokio 1.38.1 is used for MSRV
52+
- name: Pin some dependencies for MSRV
5353
run: |
54-
cargo update
5554
cargo update --package tokio --precise 1.38.1
55+
cargo update --package tokio-util --precise 0.7.11
56+
cargo update --package hashbrown --precise 0.15.0
57+
5658
5759
- run: cargo check -p rh2

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 0.4.6 (August 19, 2024)
2+
3+
* Add `current_max_send_streams()` and `current_max_recv_streams()` to `client::SendRequest`.
4+
* Fix sending a PROTOCOL_ERROR instead of REFUSED_STREAM when receiving oversized headers.
5+
* Fix notifying a PushPromise task properly.
6+
* Fix notifying a stream task when reset.
7+
18
# 0.4.5 (May 17, 2024)
29

310
* Fix race condition that sometimes hung connections during shutdown.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "rh2"
33
# When releasing to crates.io:
44
# - Update CHANGELOG.md.
55
# - Create git tag
6-
version = "0.4.5"
6+
version = "0.4.6"
77
license = "MIT"
88
authors = ["0x676e67 <[email protected]>"]
99
description = "An HTTP/2 client and server"

ci/h2spec.sh

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
#!/bin/bash
2-
LOGFILE="h2server.log"
2+
LOGFILE="/tmp/h2server.log"
33

4-
if ! [ -e "h2spec" ] ; then
4+
override_h2spec=false
5+
6+
# Check for optional flag
7+
while getopts "F" opt; do
8+
case $opt in
9+
F) override_h2spec=true ;;
10+
*) echo "Usage: $0 [-o]"; exit 1 ;;
11+
esac
12+
done
13+
14+
if ! [ -e "/tmp/h2spec" ] || $override_h2spec ; then
515
# if we don't already have a h2spec executable, wget it from github
6-
wget https://github.com/summerwind/h2spec/releases/download/v2.1.1/h2spec_linux_amd64.tar.gz
7-
tar xf h2spec_linux_amd64.tar.gz
16+
if [[ "$OSTYPE" == "darwin"* ]]; then
17+
curl -L -o /tmp/h2spec_darwin_amd64.tar.gz https://github.com/summerwind/h2spec/releases/download/v2.1.1/h2spec_darwin_amd64.tar.gz \
18+
&& tar xf /tmp/h2spec_darwin_amd64.tar.gz -C /tmp
19+
else
20+
curl -L -o /tmp/h2spec_linux_amd64.tar.gz https://github.com/summerwind/h2spec/releases/download/v2.1.1/h2spec_linux_amd64.tar.gz \
21+
&& tar xf /tmp/h2spec_linux_amd64.tar.gz -C /tmp
22+
fi
823
fi
924

1025
cargo build --example server
@@ -16,7 +31,7 @@ SERVER_PID=$!
1631
sed '/listening on Ok(127.0.0.1:5928)/q' <&3 ; cat <&3 > "${LOGFILE}" &
1732

1833
# run h2spec against the server, printing the server log if h2spec failed
19-
./h2spec -p 5928
34+
/tmp/h2spec -p 5928
2035
H2SPEC_STATUS=$?
2136
if [ "${H2SPEC_STATUS}" -eq 0 ]; then
2237
echo "h2spec passed!"

src/codec/framed_read.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::hpack;
88

99
use futures_core::Stream;
1010

11-
use bytes::BytesMut;
11+
use bytes::{Buf, BytesMut};
1212

1313
use std::io;
1414

@@ -146,8 +146,7 @@ fn decode_frame(
146146
macro_rules! header_block {
147147
($frame:ident, $head:ident, $bytes:ident) => ({
148148
// Drop the frame header
149-
// TODO: Change to drain: carllerche/bytes#130
150-
let _ = $bytes.split_to(frame::HEADER_LEN);
149+
$bytes.advance(frame::HEADER_LEN);
151150

152151
// Parse the header frame w/o parsing the payload
153152
let (mut frame, mut payload) = match frame::$frame::load($head, $bytes) {
@@ -227,7 +226,7 @@ fn decode_frame(
227226
.into()
228227
}
229228
Kind::Data => {
230-
let _ = bytes.split_to(frame::HEADER_LEN);
229+
bytes.advance(frame::HEADER_LEN);
231230
let res = frame::Data::load(head, bytes.freeze());
232231

233232
// TODO: Should this always be connection level? Probably not...

src/frame/headers.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use crate::ext::Protocol;
33
use crate::frame::{Error, Frame, Head, Kind};
44
use crate::hpack::{self, BytesStr};
55

6-
use bytes::{BufMut, Bytes, BytesMut};
76
use http::header::{self, HeaderName, HeaderValue};
87
use http::{uri, HeaderMap, Method, Request, StatusCode, Uri};
98

9+
use bytes::{Buf, BufMut, Bytes, BytesMut};
10+
1011
use std::fmt;
1112
use std::io::Cursor;
1213

@@ -181,7 +182,7 @@ impl Headers {
181182
pad = src[0] as usize;
182183

183184
// Drop the padding
184-
let _ = src.split_to(1);
185+
src.advance(1);
185186
}
186187

187188
// Read the stream dependency
@@ -196,7 +197,7 @@ impl Headers {
196197
}
197198

198199
// Drop the next 5 bytes
199-
let _ = src.split_to(5);
200+
src.advance(5);
200201

201202
Some(stream_dep)
202203
} else {
@@ -269,6 +270,10 @@ impl Headers {
269270
&mut self.header_block.pseudo
270271
}
271272

273+
pub(crate) fn pseudo(&self) -> &Pseudo {
274+
&self.header_block.pseudo
275+
}
276+
272277
/// Whether it has status 1xx
273278
pub(crate) fn is_informational(&self) -> bool {
274279
self.header_block.pseudo.is_informational()
@@ -445,7 +450,7 @@ impl PushPromise {
445450
pad = src[0] as usize;
446451

447452
// Drop the padding
448-
let _ = src.split_to(1);
453+
src.advance(1);
449454
}
450455

451456
if src.len() < 5 {
@@ -454,7 +459,7 @@ impl PushPromise {
454459

455460
let (promised_id, _) = StreamId::parse(&src[..4]);
456461
// Drop promised_id bytes
457-
let _ = src.split_to(4);
462+
src.advance(4);
458463

459464
if pad > 0 {
460465
if pad > src.len() {
@@ -684,7 +689,7 @@ impl EncodingHeaderBlock {
684689

685690
// Now, encode the header payload
686691
let continuation = if self.hpack.len() > dst.remaining_mut() {
687-
dst.put_slice(&self.hpack.split_to(dst.remaining_mut()));
692+
dst.put((&mut self.hpack).take(dst.remaining_mut()));
688693

689694
Some(Continuation {
690695
stream_id: head.stream_id(),

src/frame/util.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt;
22

33
use super::Error;
4-
use bytes::Bytes;
4+
use bytes::{Buf, Bytes};
55

66
/// Strip padding from the given payload.
77
///
@@ -32,8 +32,8 @@ pub fn strip_padding(payload: &mut Bytes) -> Result<u8, Error> {
3232
return Err(Error::TooMuchPadding);
3333
}
3434

35-
let _ = payload.split_to(1);
36-
let _ = payload.split_off(payload_len - pad_len - 1);
35+
payload.advance(1);
36+
payload.truncate(payload_len - pad_len - 1);
3737

3838
Ok(pad_len as u8)
3939
}

src/proto/streams/recv.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@ impl Recv {
185185
};
186186

187187
stream.content_length = ContentLength::Remaining(content_length);
188+
// END_STREAM on headers frame with non-zero content-length is malformed.
189+
// https://datatracker.ietf.org/doc/html/rfc9113#section-8.1.1
190+
if frame.is_end_stream()
191+
&& content_length > 0
192+
&& frame
193+
.pseudo()
194+
.status
195+
.map_or(true, |status| status != 204 && status != 304)
196+
{
197+
proto_err!(stream: "recv_headers with END_STREAM: content-length is not zero; stream={:?};", stream.id);
198+
return Err(Error::library_reset(stream.id, Reason::PROTOCOL_ERROR).into());
199+
}
188200
}
189201
}
190202

src/proto/streams/store.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ impl Store {
137137
Ok::<_, Infallible>(())
138138
}) {
139139
Ok(()) => (),
140+
#[allow(unused)]
140141
Err(infallible) => match infallible {},
141142
}
142143
}

src/proto/streams/streams.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,9 @@ impl Actions {
15991599
// Reset the stream.
16001600
self.send
16011601
.send_reset(reason, initiator, buffer, stream, counts, &mut self.task);
1602+
self.recv.enqueue_reset_expiration(stream, counts);
1603+
// if a RecvStream is parked, ensure it's notified
1604+
stream.notify_recv();
16021605
Ok(())
16031606
} else {
16041607
tracing::warn!(

tests/h2-tests/tests/client_request.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,49 @@ async fn allow_empty_data_for_head() {
12611261
join(srv, h2).await;
12621262
}
12631263

1264+
#[tokio::test]
1265+
async fn reject_none_zero_content_length_header_with_end_stream() {
1266+
h2_support::trace_init!();
1267+
let (io, mut srv) = mock::new();
1268+
1269+
let srv = async move {
1270+
let settings = srv.assert_client_handshake().await;
1271+
assert_default_settings!(settings);
1272+
srv.recv_frame(
1273+
frames::headers(1)
1274+
.request("GET", "https://example.com/")
1275+
.eos(),
1276+
)
1277+
.await;
1278+
srv.send_frame(
1279+
frames::headers(1)
1280+
.response(200)
1281+
.field("content-length", 100)
1282+
.eos(),
1283+
)
1284+
.await;
1285+
};
1286+
1287+
let h2 = async move {
1288+
let (mut client, h2) = client::Builder::new()
1289+
.handshake::<_, Bytes>(io)
1290+
.await
1291+
.unwrap();
1292+
tokio::spawn(async {
1293+
h2.await.expect("connection failed");
1294+
});
1295+
let request = Request::builder()
1296+
.method(Method::GET)
1297+
.uri("https://example.com/")
1298+
.body(())
1299+
.unwrap();
1300+
let (response, _) = client.send_request(request, true).unwrap();
1301+
let _ = response.await.unwrap_err();
1302+
};
1303+
1304+
join(srv, h2).await;
1305+
}
1306+
12641307
#[tokio::test]
12651308
async fn early_hints() {
12661309
h2_support::trace_init!();

tests/h2-tests/tests/stream_states.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,12 @@ async fn recv_next_stream_id_updated_by_malformed_headers() {
536536
client.recv_frame(frames::go_away(1).protocol_error()).await;
537537
};
538538
let srv = async move {
539-
let mut srv = server::handshake(io).await.expect("handshake");
539+
let mut srv = server::Builder::new()
540+
// forget the bad stream immediately
541+
.max_concurrent_reset_streams(0)
542+
.handshake::<_, Bytes>(io)
543+
.await
544+
.expect("handshake");
540545
let res = srv.next().await.unwrap();
541546
let err = res.unwrap_err();
542547
assert_eq!(err.reason(), Some(h2::Reason::PROTOCOL_ERROR));

0 commit comments

Comments
 (0)