Skip to content
Merged
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
4 changes: 2 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2135,10 +2135,10 @@ packages:
dependency: "direct main"
description:
name: socks5_proxy
sha256: e0cba6917cd374de6f6cb0ce081e50e6efc24c61644b8e9f20c8bf8b91bb0b75
sha256: "80fa31a9ebfc0dc8de7b0e568c8d8927b65558ef2c7591cbee5afac814fb8f74"
url: "https://pub.dev"
source: hosted
version: "1.0.3+dev.3"
version: "2.1.1"
socks_socket:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion scripts/app_config/templates/pubspec.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ dependencies:
git:
url: https://github.com/cypherstack/tezart.git
ref: 84c563104f1a19c26e49bafccb7da404b210b666
socks5_proxy: 1.0.3+dev.3
socks5_proxy: ^2.1.1
convert: ^3.1.1
flutter_hooks: ^0.20.3
meta: ^1.9.1
Expand Down
82 changes: 82 additions & 0 deletions test/networking/http_socks_proxy_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter_test/flutter_test.dart';
import 'package:stackwallet/networking/http.dart';

// Minimal SOCKS5 server that records the CONNECT target and answers any
// request with a canned HTTP 200.
class _FakeSocksServer {
late final ServerSocket _server;
int? addressType;
String? target;
int? port;

int get listeningPort => _server.port;

Future<void> start() async {
_server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
_server.listen((socket) {
var stage = 0;
socket.listen((Uint8List bytes) {
switch (stage) {
case 0:
socket.add([0x05, 0x00]);
stage = 1;
case 1:
addressType = bytes[3];
if (addressType == 0x03) {
target = String.fromCharCodes(bytes.sublist(5, 5 + bytes[4]));
} else {
target = bytes.sublist(4, bytes.length - 2).join('.');
}
port = (bytes[bytes.length - 2] << 8) | bytes[bytes.length - 1];
socket.add([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]);
stage = 2;
default:
socket.write(
'HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok',
);
socket.close();
}
});
});
}

Future<void> stop() => _server.close();
}

void main() {
late _FakeSocksServer socks;

setUp(() async {
socks = _FakeSocksServer();
await socks.start();
});

tearDown(() => socks.stop());

Future<Response> get(String host) => const HTTP().get(
url: Uri.http(host, '/'),
proxyInfo: (host: InternetAddress.loopbackIPv4, port: socks.listeningPort),
);

test('proxied request sends the hostname to the SOCKS5 proxy', () async {
final response = await get('example.invalid');

expect(response.code, 200);
expect(socks.addressType, 0x03);
expect(socks.target, 'example.invalid');
expect(socks.port, 80);
});

test('proxied request can target an onion address', () async {
const onion =
'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
final response = await get(onion);

expect(response.code, 200);
expect(socks.addressType, 0x03);
expect(socks.target, onion);
});
}
Loading