Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkgs/http2/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 3.0.1-wip

- Gracefully handle receiving headers on a stream that the client has canceled. (#1799)
- Treat incoming server push streams as connection protocol error when pushes are disabled (SETTINGS_ENABLE_PUSH=0).

## 3.0.0

Expand Down
9 changes: 9 additions & 0 deletions pkgs/http2/lib/src/streams/stream_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,15 @@ class StreamHandler extends Object with TerminatableMixin, ClosableMixin {
throw ProtocolException('Expected open state (was: ${stream.state}).');
}

// RFC 7540 6.5.2/8.2: An endpoint that has both sent and received
// acknowledgement of SETTINGS_ENABLE_PUSH=0 MUST treat receipt of a
// PUSH_PROMISE frame as a connection error of type PROTOCOL_ERROR.
if (!_localSettings.enablePush) {
throw ProtocolException(
'Received PUSH_PROMISE although SETTINGS_ENABLE_PUSH is 0.',
);
}

var pushedStream = newRemoteStream(frame.promisedStreamId);
_changeState(pushedStream, StreamState.ReservedRemote);

Expand Down
107 changes: 103 additions & 4 deletions pkgs/http2/test/client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,100 @@ void main() {
await Future.wait([serverFun(), clientFun()]);
});

clientTest(
'client-reports-connection-error-on-push-when-push-disabled',
(
ClientTransportConnection client,
FrameWriter serverWriter,
StreamIterator<Frame> serverReader,
Future<Frame> Function() nextFrame,
) async {
var handshakeCompleter = Completer<void>();
var gotPushPromise = Completer<void>();

Future serverFun() async {
// Server accepts the settings with SETTINGS_ENABLE_PUSH = 0.
serverWriter.writeSettingsFrame([]);
expect(await nextFrame(), isA<SettingsFrame>());
serverWriter.writeSettingsAckFrame();
expect(await nextFrame(), isA<SettingsFrame>());

handshakeCompleter.complete();

var headers = await nextFrame() as HeadersFrame;
var streamId = headers.header.streamId;

// Push stream on the open stream.
var pushStreamId = 2;
serverWriter.writePushPromiseFrame(streamId, pushStreamId, [
Header.ascii('a', 'b'),
]);

// Since the client sent SETTINGS_ENABLE_PUSH=0, receiving a
// PUSH_PROMISE **must** result in a connection error of type
// PROTOCOL_ERROR.
try {
var frame = await nextFrame().timeout(
const Duration(milliseconds: 500),
);
if (frame is GoawayFrame) {
expect(frame.errorCode, ErrorCode.PROTOCOL_ERROR);
expect(
ascii.decode(frame.debugData),
contains(
'Received PUSH_PROMISE although SETTINGS_ENABLE_PUSH is 0',
),
);
} else {
fail('Expected GoawayFrame, but got $frame');
}
} finally {
expect(await serverReader.moveNext(), false);
await serverWriter.close();
}
}

Future clientFun() async {
await handshakeCompleter.future;

var stream = client.makeRequest([Header.ascii('a', 'b')]);

// Listen for push stream, which should not happen if pushes are
// disabled.
stream.peerPushes.listen((push) {
gotPushPromise.complete();
});

// Wait for the stream to throw an error.
var done = Completer<void>();
stream.incomingMessages.listen(
(_) {},
onError: (Object e) {
expect(
e,
isA<TransportConnectionException>().having(
(p0) => p0.message,
'Forcefully terminated message',
contains('Connection is being forcefully terminated'),
),
);
if (!done.isCompleted) done.complete();
},
onDone: () {
if (!done.isCompleted) done.complete();
},
);

await Future.any([gotPushPromise.future, done.future]);

await client.terminate();
}

await Future.wait([serverFun(), clientFun()]);
},
settings: const ClientSettings(allowServerPushes: false),
);

clientTest('client-reports-flowcontrol-error-on-negative-window', (
ClientTransportConnection client,
FrameWriter serverWriter,
Expand Down Expand Up @@ -1095,10 +1189,11 @@ void clientTest(
StreamIterator<Frame> frameReader,
Future<Frame> Function() readNext,
)
func,
) {
func, {
ClientSettings? settings,
}) {
return test(name, () {
var streams = ClientStreams();
var streams = ClientStreams(settings: settings);
var serverReader = streams.serverConnectionFrameReader;

Future<Frame> readNext() async {
Expand All @@ -1116,8 +1211,12 @@ void clientTest(
}

class ClientStreams {
final ClientSettings? settings;
final StreamController<List<int>> writeA = StreamController();
final StreamController<List<int>> writeB = StreamController();

ClientStreams({this.settings});

Stream<List<int>> get readA => writeA.stream;
Stream<List<int>> get readB => writeB.stream;

Expand All @@ -1136,5 +1235,5 @@ class ClientStreams {
}

ClientTransportConnection get clientConnection =>
ClientTransportConnection.viaStreams(readB, writeA);
ClientTransportConnection.viaStreams(readB, writeA, settings: settings);
}
Loading