Skip to content

Commit

Permalink
Allows implementations to use close codes from RFC 6455
Browse files Browse the repository at this point in the history
  • Loading branch information
kpsroka committed Aug 14, 2024
1 parent 4322382 commit 3b75b67
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 15 deletions.
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
/// 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

0 comments on commit 3b75b67

Please sign in to comment.