Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Allows native WebSocket implementations to use close codes from RFC 6455 #1295

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
8 changes: 4 additions & 4 deletions pkgs/cupertino_http/lib/src/cupertino_web_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:cupertino_http/src/utils.dart';
import 'package:web_socket/web_socket.dart';

import 'cupertino_api.dart';
Expand Down Expand Up @@ -206,11 +207,10 @@ class CupertinoWebSocket implements WebSocket {
if (_events.isClosed) {
throw WebSocketConnectionClosed();
}

checkCloseCodeRfc(code);
checkCloseReason(reason);

if (code != null && code != 1000 && !(code >= 3000 && code <= 4999)) {
throw ArgumentError('Invalid argument: $code, close code must be 1000 or '
'in the range 3000-4999');
}
if (reason != null && utf8.encode(reason).length > 123) {
throw ArgumentError.value(reason, 'reason',
'reason must be <= 123 bytes long when encoded as UTF-8');
Expand Down
25 changes: 25 additions & 0 deletions pkgs/cupertino_http/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:ffi';
import 'dart:io';

Expand Down Expand Up @@ -90,3 +91,27 @@ ncb.NSArray stringIterableToNSArray(Iterable<String> strings) {

ncb.NSURL uriToNSURL(Uri uri) => ncb.NSURL
.URLWithString_(linkedLibs, uri.toString().toNSString(linkedLibs))!;


/// Throw if the given close code is not valid according to RFC 6455.
/// See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4
void checkCloseCodeRfc(int? code) {
const reservedCloseCodes = [1004, 1005, 1006];
if (code != null &&
!(code >= 1000 && code <= 1011 && !reservedCloseCodes.contains(code)) &&
!(code >= 3000 && code <= 4999)) {
throw ArgumentError(
'Invalid argument: $code, close code must be in the range 1000-1011 or '
'in the range 3000-4999, and cannot be one of reserved codes '
'(${reservedCloseCodes.join(', ')})',
);
}
}

/// Throw if the given close reason is not valid.
void checkCloseReason(String? reason) {
if (reason != null && utf8.encode(reason).length > 123) {
throw ArgumentError.value(reason, 'reason',
'reason must be <= 123 bytes long when encoded as UTF-8');
}
}
2 changes: 1 addition & 1 deletion pkgs/web_socket/lib/src/browser_web_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class BrowserWebSocket implements WebSocket {
throw WebSocketConnectionClosed();
}

checkCloseCode(code);
checkCloseCodeWeb(code);
checkCloseReason(reason);

unawaited(_events.close());
Expand Down
2 changes: 1 addition & 1 deletion pkgs/web_socket/lib/src/fake_web_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class FakeWebSocket implements WebSocket {
throw WebSocketConnectionClosed();
}

checkCloseCode(code);
checkCloseCodeWeb(code);
checkCloseReason(reason);

unawaited(_events.close());
Expand Down
2 changes: 1 addition & 1 deletion pkgs/web_socket/lib/src/io_web_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class IOWebSocket implements WebSocket {
throw WebSocketConnectionClosed();
}

checkCloseCode(code);
checkCloseCodeRfc(code);
checkCloseReason(reason);

unawaited(_events.close());
Expand Down
27 changes: 25 additions & 2 deletions pkgs/web_socket/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,37 @@

import 'dart:convert';

/// Throw if the given close code is not valid.
void checkCloseCode(int? code) {
/// Throw if the given close code is not valid according to WHATWG spec.
///
/// This is more suitable for clients running in web browsers.
///
/// See https://websockets.spec.whatwg.org/#dom-websocket-close.
void checkCloseCodeWeb(int? code) {
if (code != null && code != 1000 && !(code >= 3000 && code <= 4999)) {
throw ArgumentError('Invalid argument: $code, close code must be 1000 or '
'in the range 3000-4999');
}
}

/// Throw if the given close code is not valid according to RFC 6455.
///
/// This is more suitable for clients running in native environments, possibly
/// as a server endpoint.
///
/// See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4
void checkCloseCodeRfc(int? code) {
const reservedCloseCodes = [1004, 1005, 1006];
if (code != null &&
!(code >= 1000 && code <= 1011 && !reservedCloseCodes.contains(code)) &&
!(code >= 3000 && code <= 4999)) {
throw ArgumentError(
'Invalid argument: $code, close code must be in the range 1000-1011 or '
'in the range 3000-4999, and cannot be one of reserved codes '
'(${reservedCloseCodes.join(', ')})',
);
}
}

/// Throw if the given close reason is not valid.
void checkCloseReason(String? reason) {
if (reason != null && utf8.encode(reason).length > 123) {
Expand Down
5 changes: 4 additions & 1 deletion pkgs/web_socket/lib/src/web_socket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,10 @@ abstract interface class WebSocket {
/// [code] is set then the peer will see a 1005 status code. If no [reason]
/// is set then the peer will not receive a reason string.
///
/// Throws an [ArgumentError] if [code] is not 1000 or in the range 3000-4999.
/// Throws an [ArgumentError] if [code] is not accepted by the implementation.
/// All implementations must accept [code] values of 1000, and in the range
/// 3000-4999. Some implementations may accept additional values, but must not
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change must => will because this is meant to be read by a WebSocket user not implementer ;-)

/// accept reserved codes 1004, 1005, 1006, and 1015.
///
/// Throws an [ArgumentError] if [reason] is longer than 123 bytes when
/// encoded as UTF-8
Expand Down
15 changes: 10 additions & 5 deletions pkgs/web_socket_conformance_tests/lib/src/close_local_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,16 @@ void testCloseLocal(
httpServerChannel.sink.add(null);
});

test('reserved close code: 1004', () async {
final channel = await channelFactory(uri);
await expectLater(
() => channel.close(1004), throwsA(isA<ArgumentError>()));
});
/// Codes 1004-1006 and 1015 cannot be used in close() calls.
for (final reservedCode in [1004, 1005, 1006, 1015]) {
test('reserved close code: $reservedCode', () async {
final channel = await channelFactory(uri);
await expectLater(
() => channel.close(reservedCode),
throwsA(isA<ArgumentError>()),
);
});
}

test('reserved close code: 2999', () async {
final channel = await channelFactory(uri);
Expand Down
Loading