Added `strictProtocolChecks` named parameter to `Peer` and `Server` constructors (#51)
Allows creating servers which are lenient towards misbehaving clients.
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 @@
@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 @@
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
/// If this is not provided, unhandled exceptions will be swallowed.
- Peer(StreamChannel<String> 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<String> 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 @@
///
/// 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 @@
/// 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 @@
///
/// Unhandled exceptions in callbacks will be forwarded to [onUnhandledError].
/// If this is not provided, unhandled exceptions will be swallowed.
- Server(StreamChannel<String> 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<String> 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 @@
///
/// 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 @@
'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 @@
'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 @@
}
})));
});
+
+ 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 @@
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();
}