Skip to content

Commit 50c25c1

Browse files
Lukasaglbrntt
andauthored
Clients can't push! (#332)
Motivation: Nowhere in the PUSH_PROMISE code do we explicitly check whether or not we're a client or a server, and we don't provide special handling for SETTINGS_ENABLE_PUSH contingent on that fact. As a result, we do technically allow clients to push. This eventually gets caught, but not until we've created an idle stream, which we shouldn't do. Modifications: - Forbid clients from pushing. Result: Clients won't be allowed to push. Co-authored-by: George Barnett <[email protected]>
1 parent 2c87ffe commit 50c25c1

File tree

4 files changed

+32
-15
lines changed

4 files changed

+32
-15
lines changed

Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingPushPromiseState.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ extension ReceivingPushPromiseState {
6868

6969
/// Whether the remote peer may push.
7070
var peerMayPush: Bool {
71-
// In the case where we don't have local settings, we have to assume the default value, in which the peer may push.
72-
return true
71+
// In the case where we don't have local settings, we have to assume the default value, in which servers may push and clients may not.
72+
return self.role == .client
7373
}
7474
}
7575

@@ -93,6 +93,6 @@ extension ReceivingPushPromiseState where Self: LocallyQuiescingState {
9393

9494
extension ReceivingPushPromiseState where Self: HasLocalSettings {
9595
var peerMayPush: Bool {
96-
return self.localSettings.enablePush == 1
96+
return self.localSettings.enablePush == 1 && self.role == .client
9797
}
9898
}

Sources/NIOHTTP2/ConnectionStateMachine/FrameSendingStates/SendingPushPromiseState.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import NIOHPACK
1818
///
1919
/// This protocol should only be conformed to by states for the HTTP/2 connection state machine.
2020
protocol SendingPushPromiseState: HasFlowControlWindows {
21+
var role: HTTP2ConnectionStateMachine.ConnectionRole { get }
22+
2123
var headerBlockValidation: HTTP2ConnectionStateMachine.ValidationState { get }
2224

2325
var streamState: ConnectionStreamState { get set }
@@ -67,8 +69,8 @@ extension SendingPushPromiseState {
6769

6870
/// Whether we may push.
6971
var mayPush: Bool {
70-
// In the case where we don't have remote settings, we have to assume the default value, in which case we may push.
71-
return true
72+
// In the case where we don't have remote settings, we have to assume the default value, in which case if we're a server we may push.
73+
return self.role == .server
7274
}
7375

7476
}
@@ -83,6 +85,6 @@ extension SendingPushPromiseState where Self: RemotelyQuiescingState {
8385

8486
extension SendingPushPromiseState where Self: HasRemoteSettings {
8587
var mayPush: Bool {
86-
return self.remoteSettings.enablePush == 1
88+
return self.remoteSettings.enablePush == 1 && self.role == .server
8789
}
8890
}

Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ extension ConnectionStateMachineTests {
8282
("testUnknownSettingsAreIgnored", testUnknownSettingsAreIgnored),
8383
("testMaxConcurrentStreamsEnforcement", testMaxConcurrentStreamsEnforcement),
8484
("testDisablingPushPreventsPush", testDisablingPushPreventsPush),
85+
("testClientsCannotPush", testClientsCannotPush),
8586
("testRatchetingGoawayEvenWhenFullyQueisced", testRatchetingGoawayEvenWhenFullyQueisced),
8687
("testRatchetingGoawayForBothPeersEvenWhenFullyQuiesced", testRatchetingGoawayForBothPeersEvenWhenFullyQuiesced),
8788
("testClientTrailersMustHaveEndStreamSet", testClientTrailersMustHaveEndStreamSet),

Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,29 +237,22 @@ class ConnectionStateMachineTests: XCTestCase {
237237

238238
assertBadStreamStateTransition(type: .halfOpenRemoteLocalIdle, self.server.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 0, isEndStreamSet: false))
239239
self.server = savedServerState
240-
241-
assertBadStreamStateTransition(type: .halfOpenRemoteLocalIdle, self.server.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: testHeaders))
242-
self.server = savedServerState
243240

244241
// Move state to fullyOpen
245242
let testSendHeaders: HPACKHeaders = [":status": "y"]
246243
assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: testSendHeaders, isEndStreamSet: false))
247244
savedServerState = self.server
248245

249-
let testPushPromiseHeaders: HPACKHeaders = ["test": "value"]
250-
assertBadStreamStateTransition(type: .fullyOpen, self.server.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: testPushPromiseHeaders))
251-
self.server = savedServerState
252-
253246
// Move state to halfClosedLocalPeerActive
254247
assertSucceeds(self.server.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 0, isEndStreamSet: true))
255248
savedServerState = self.server
256249

257250
assertBadStreamStateTransition(type: .halfClosedLocalPeerActive, self.server.sendData(streamID: streamOne, contentLength: 0, flowControlledBytes: 0, isEndStreamSet: true))
258251
self.server = savedServerState
252+
253+
let testPushPromiseHeaders: HPACKHeaders = ["test": "value"]
259254
assertBadStreamStateTransition(type: .halfClosedLocalPeerActive, self.server.sendPushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: testPushPromiseHeaders))
260255
self.server = savedServerState
261-
assertBadStreamStateTransition(type: .halfClosedLocalPeerActive, self.server.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: testPushPromiseHeaders))
262-
self.server = savedServerState
263256
let lastBadStreamStateTransition = assertBadStreamStateTransition(type: .halfClosedLocalPeerActive, self.server.sendHeaders(streamID: streamOne, headers: testPushPromiseHeaders, isEndStreamSet: false))
264257
self.server = savedServerState
265258

@@ -1778,6 +1771,27 @@ class ConnectionStateMachineTests: XCTestCase {
17781771
self.server.receiveSettings(.settings([HTTP2Setting(parameter: .enablePush, value: 2)]), frameEncoder: &self.serverEncoder, frameDecoder: &self.serverDecoder))
17791772
}
17801773

1774+
func testClientsCannotPush() {
1775+
let streamOne = HTTP2StreamID(1)
1776+
let streamTwo = HTTP2StreamID(2)
1777+
1778+
// Default settings.
1779+
assertSucceeds(self.client.sendSettings([]))
1780+
assertSucceeds(self.server.receiveSettings(.settings([]), frameEncoder: &self.serverEncoder, frameDecoder: &self.serverDecoder))
1781+
assertSucceeds(self.server.sendSettings([]))
1782+
assertSucceeds(self.client.receiveSettings(.settings([]), frameEncoder: &self.clientEncoder, frameDecoder: &self.clientDecoder))
1783+
assertSucceeds(self.client.receiveSettings(.ack, frameEncoder: &self.clientEncoder, frameDecoder: &self.clientDecoder))
1784+
assertSucceeds(self.server.receiveSettings(.ack, frameEncoder: &self.serverEncoder, frameDecoder: &self.serverDecoder))
1785+
1786+
// Client attempts to push, using a _server_ stream ID, with stream one not open. This should fail on both server and client.
1787+
assertConnectionError(type: .protocolError, self.client.sendPushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders))
1788+
assertConnectionError(type: .protocolError, self.server.receivePushPromise(originalStreamID: streamOne, childStreamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders))
1789+
1790+
// Client then sends a HEADERS frame on the stream it just pushed. This should also fail.
1791+
assertConnectionError(type: .protocolError, self.client.sendHeaders(streamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false))
1792+
assertConnectionError(type: .protocolError, self.server.receiveHeaders(streamID: streamTwo, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: false))
1793+
}
1794+
17811795
func testRatchetingGoawayEvenWhenFullyQueisced() {
17821796
let streamOne = HTTP2StreamID(1)
17831797
let streamThree = HTTP2StreamID(3)

0 commit comments

Comments
 (0)