From d589e635d8ccb7cda6a804bd571f88abbabab146 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 18 May 2020 14:01:38 -0700 Subject: [PATCH] Added `strictProtocolChecks` named parameter to `Peer` and `Server` constructors (#51) Allows creating servers which are lenient towards misbehaving clients. --- CHANGELOG.md | 6 ++++ lib/src/peer.dart | 25 +++++++++++++--- lib/src/server.dart | 43 ++++++++++++++++++++------- pubspec.yaml | 2 +- test/server/invalid_request_test.dart | 17 +++++++++++ test/server/utils.dart | 7 +++-- 6 files changed, 82 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c95f7f1..c3eec9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.2.0 + +* Added `strictProtocolChecks` named parameter to `Server` and `Peer` + constructors. Setting this parameter to false will result in the server not + rejecting requests missing the `jsonrpc` parameter. + ## 2.1.1 * Fixed issue where throwing `RpcException.methodNotFound` in an asynchronous diff --git a/lib/src/peer.dart b/lib/src/peer.dart index cd51a7c..eeb7cd9 100644 --- a/lib/src/peer.dart +++ b/lib/src/peer.dart @@ -44,6 +44,9 @@ class Peer implements Client, Server { @override ErrorCallback get onUnhandledError => _server?.onUnhandledError; + @override + bool get strictProtocolChecks => _server.strictProtocolChecks; + /// Creates a [Peer] that communicates over [channel]. /// /// Note that the peer won't begin listening to [channel] until [Peer.listen] @@ -51,10 +54,17 @@ class Peer implements Client, Server { /// /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. /// If this is not provided, unhandled exceptions will be swallowed. - Peer(StreamChannel channel, {ErrorCallback onUnhandledError}) + /// + /// If [strictProtocolChecks] is false, the underlying [Server] will accept + /// some requests which are not conformant with the JSON-RPC 2.0 + /// specification. In particular, requests missing the `jsonrpc` parameter + /// will be accepted. + Peer(StreamChannel channel, + {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) : this.withoutJson( jsonDocument.bind(channel).transform(respondToFormatExceptions), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); /// Creates a [Peer] that communicates using decoded messages over [channel]. /// @@ -66,11 +76,18 @@ class Peer implements Client, Server { /// /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. /// If this is not provided, unhandled exceptions will be swallowed. - Peer.withoutJson(StreamChannel channel, {ErrorCallback onUnhandledError}) + /// + /// If [strictProtocolChecks] is false, the underlying [Server] will accept + /// some requests which are not conformant with the JSON-RPC 2.0 + /// specification. In particular, requests missing the `jsonrpc` parameter + /// will be accepted. + Peer.withoutJson(StreamChannel channel, + {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) : _manager = ChannelManager('Peer', channel) { _server = Server.withoutJson( StreamChannel(_serverIncomingForwarder.stream, channel.sink), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); _client = Client.withoutJson( StreamChannel(_clientIncomingForwarder.stream, channel.sink)); } diff --git a/lib/src/server.dart b/lib/src/server.dart index 127a6d5..83129ea 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -61,6 +61,13 @@ class Server { /// invoked. If it is not set, the exception will be swallowed. final ErrorCallback onUnhandledError; + /// Whether to strictly enforce the JSON-RPC 2.0 specification for received messages. + /// + /// If `false`, this [Server] will accept some requests which are not conformant + /// with the JSON-RPC 2.0 specification. In particular, requests missing the + /// `jsonrpc` parameter will be accepted. + final bool strictProtocolChecks; + /// Creates a [Server] that communicates over [channel]. /// /// Note that the server won't begin listening to [requests] until @@ -68,10 +75,16 @@ class Server { /// /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. /// If this is not provided, unhandled exceptions will be swallowed. - Server(StreamChannel channel, {ErrorCallback onUnhandledError}) + /// + /// If [strictProtocolChecks] is false, this [Server] will accept some + /// requests which are not conformant with the JSON-RPC 2.0 specification. In + /// particular, requests missing the `jsonrpc` parameter will be accepted. + Server(StreamChannel channel, + {ErrorCallback onUnhandledError, bool strictProtocolChecks = true}) : this.withoutJson( jsonDocument.bind(channel).transform(respondToFormatExceptions), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); /// Creates a [Server] that communicates using decoded messages over /// [channel]. @@ -84,7 +97,12 @@ class Server { /// /// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError]. /// If this is not provided, unhandled exceptions will be swallowed. - Server.withoutJson(StreamChannel channel, {this.onUnhandledError}) + /// + /// If [strictProtocolChecks] is false, this [Server] will accept some + /// requests which are not conformant with the JSON-RPC 2.0 specification. In + /// particular, requests missing the `jsonrpc` parameter will be accepted. + Server.withoutJson(StreamChannel channel, + {this.onUnhandledError, this.strictProtocolChecks = true}) : _manager = ChannelManager('Server', channel); /// Starts listening to the underlying stream. @@ -217,14 +235,15 @@ class Server { 'an Array or an Object.'); } - if (!request.containsKey('jsonrpc')) { + if (strictProtocolChecks && !request.containsKey('jsonrpc')) { throw RpcException( error_code.INVALID_REQUEST, 'Request must ' 'contain a "jsonrpc" key.'); } - if (request['jsonrpc'] != '2.0') { + if ((strictProtocolChecks || request.containsKey('jsonrpc')) && + request['jsonrpc'] != '2.0') { throw RpcException( error_code.INVALID_REQUEST, 'Invalid JSON-RPC ' @@ -246,12 +265,14 @@ class Server { 'be a string, but was ${jsonEncode(method)}.'); } - var params = request['params']; - if (request.containsKey('params') && params is! List && params is! Map) { - throw RpcException( - error_code.INVALID_REQUEST, - 'Request params must ' - 'be an Array or an Object, but was ${jsonEncode(params)}.'); + if (request.containsKey('params')) { + var params = request['params']; + if (params is! List && params is! Map) { + throw RpcException( + error_code.INVALID_REQUEST, + 'Request params must ' + 'be an Array or an Object, but was ${jsonEncode(params)}.'); + } } var id = request['id']; diff --git a/pubspec.yaml b/pubspec.yaml index 0f55fb7..abd2520 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 2.1.1 +version: 2.2.0 description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. homepage: https://github.com/dart-lang/json_rpc_2 diff --git a/test/server/invalid_request_test.dart b/test/server/invalid_request_test.dart index 4dbca0c..a67afec 100644 --- a/test/server/invalid_request_test.dart +++ b/test/server/invalid_request_test.dart @@ -74,4 +74,21 @@ void main() { } }))); }); + + group('strict protocol checks disabled', () { + setUp(() => controller = ServerController(strictProtocolChecks: false)); + + test('and no jsonrpc param', () { + expectErrorResponse(controller, {'method': 'foo', 'id': 1234}, + error_code.METHOD_NOT_FOUND, 'Unknown method "foo".'); + }); + + test('the jsonrpc version must be 2.0', () { + expectErrorResponse( + controller, + {'jsonrpc': '1.0', 'method': 'foo', 'id': 1234}, + error_code.INVALID_REQUEST, + 'Invalid JSON-RPC version "1.0", expected "2.0".'); + }); + }); } diff --git a/test/server/utils.dart b/test/server/utils.dart index bf1db6f..7ff491c 100644 --- a/test/server/utils.dart +++ b/test/server/utils.dart @@ -23,10 +23,13 @@ class ServerController { json_rpc.Server get server => _server; json_rpc.Server _server; - ServerController({json_rpc.ErrorCallback onUnhandledError}) { + ServerController( + {json_rpc.ErrorCallback onUnhandledError, + bool strictProtocolChecks = true}) { _server = json_rpc.Server( StreamChannel(_requestController.stream, _responseController.sink), - onUnhandledError: onUnhandledError); + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks); _server.listen(); }