99 lines
5.5 KiB
Dart
99 lines
5.5 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/app/settings.dart';
|
|
import 'package:jj_mkvmaker/core/lan_scan.dart';
|
|
import 'package:jj_mkvmaker/core/vfs.dart';
|
|
import 'package:jj_mkvmaker/core/wifi_share.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// 새 네트워크 기능 보안 검토 (관리자 지시 10/18) 의 고침:
|
|
/// 1 원격 이름으로 로컬 경로 벗어나기 · 3 Wi-Fi 공유는 Wi-Fi · 유선 주소에만 · 4 SMB 찾기는 Wi-Fi · 유선만 · 11 mDNS 는 사설망 주소만
|
|
void main() {
|
|
test('1: 원격 목록은 그 폴더 바로 아래의 평범한 이름만 ("..", "\\", NUL, 더 깊은 것 · 다른 폴더는 버림)', () {
|
|
expect(remoteChildOk('/', '/a.txt'), isTrue);
|
|
expect(remoteChildOk('/video', '/video/한글 이름.mkv'), isTrue);
|
|
expect(remoteChildOk('/video/', '/video/x'), isTrue);
|
|
for (final bad in ['/video/..', '/video/.', '/video/', '/video/..\\..\\x', '/video/a\u0000b', '/video/a/b', '/other/x', 'x']) {
|
|
expect(remoteChildOk('/video', bad), isFalse, reason: bad);
|
|
}
|
|
});
|
|
|
|
test('1: 원격 경로의 ".." 마디는 받지 않는다 (\\ 로 쓴 것도)', () {
|
|
expect(DavPath.parse('sftp://nas/a/b').rel, '/a/b');
|
|
expect(DavPath.parse('dav://nas/a..b/c').rel, '/a..b/c', reason: '이름 안의 점 두 개는 괜찮다');
|
|
for (final bad in ['sftp://nas/a/../b', r'ftp://nas/a\..\..\x', 'dav://nas/..', 'smb://nas/a/..']) {
|
|
expect(() => DavPath.parse(bad), throwsA(isA<FileSystemException>()), reason: bad);
|
|
}
|
|
});
|
|
|
|
test('1: 원격 파일을 받는 임시 사본은 늘 임시 폴더 안 (서버 id 가 ".." 여도)', () async {
|
|
final tmp = Directory.systemTemp.createTempSync('jj_netsec_');
|
|
addTearDown(() => tmp.deleteSync(recursive: true));
|
|
await expectLater(vLocalCopy('sftp://../x.txt', tmp.path), throwsA(anything));
|
|
expect(File(p.join(tmp.parent.path, 'x.txt')).existsSync(), isFalse);
|
|
});
|
|
|
|
test('3 · 4: Wi-Fi · 유선 연결만 (모바일 데이터 · 가상 어댑터 · VPN 은 뺀다), Wi-Fi 공유는 Tailscale 주소도', () {
|
|
for (final n in ['Wi-Fi', '이더넷', 'Ethernet 2', 'wlan0', 'eth0', 'en0', 'swlan0']) {
|
|
expect(isLanInterfaceName(n), isTrue, reason: n);
|
|
}
|
|
for (final n in [
|
|
'rmnet_data0', 'v4-rmnet_data1', 'ccmni1', 'pdp0', 'vEthernet (WSL)', 'vEthernet (Default Switch)',
|
|
'VirtualBox Host-Only Network', 'VMware Network Adapter VMnet8', 'docker0', 'br-1a2b', 'tun0', 'ppp0', 'wg0', 'Tailscale',
|
|
]) {
|
|
expect(isLanInterfaceName(n), isFalse, reason: n);
|
|
}
|
|
expect(WifiShare.shareInterface('Wi-Fi', '192.168.0.5'), isTrue);
|
|
expect(WifiShare.shareInterface('Tailscale', '100.69.29.106'), isTrue);
|
|
expect(WifiShare.shareInterface('tun0', '100.80.1.2'), isTrue, reason: 'Android 의 Tailscale');
|
|
expect(WifiShare.shareInterface('rmnet_data0', '100.70.1.2'), isFalse, reason: '통신사 CGNAT');
|
|
expect(WifiShare.shareInterface('rmnet_data0', '10.20.30.40'), isFalse);
|
|
expect(WifiShare.shareInterface('vEthernet (WSL)', '172.24.240.1'), isFalse);
|
|
expect(WifiShare.shareInterface('VirtualBox Host-Only Network', '192.168.56.1'), isFalse);
|
|
expect(WifiShare.shareInterface('Wi-Fi', '169.254.10.1'), isFalse, reason: '주소를 못 받은 연결');
|
|
// 설정 "Tailscale 에서도 열기" 를 끄면 Tailscale 주소는 열지 않는다 (처음 값은 켬)
|
|
expect(WifiShare.shareInterface('Tailscale', '100.69.29.106', tailscale: false), isFalse);
|
|
expect(WifiShare.shareInterface('Wi-Fi', '192.168.0.5', tailscale: false), isTrue);
|
|
expect(AppSettings().wifiShareTailscale, isTrue);
|
|
expect(AppSettings.fromJson(const {'wifiShareTailscale': false}).wifiShareTailscale, isFalse);
|
|
expect(AppSettings.fromJson(const {'wifiShareTailscale': 'x'}).wifiShareTailscale, isTrue, reason: '틀린 값은 처음 값');
|
|
});
|
|
|
|
test('11: mDNS 로 찾은 주소는 사설망만', () {
|
|
for (final ip in ['192.168.0.15', '10.0.0.2', '172.16.5.5']) {
|
|
expect(isPrivateLanAddress(ip), isTrue, reason: ip);
|
|
}
|
|
for (final ip in ['8.8.8.8', '100.69.29.106', '172.32.0.1', '127.0.0.1', '169.254.1.1', '1.2.3', 'x', '300.1.1.1']) {
|
|
expect(isPrivateLanAddress(ip), isFalse, reason: ip);
|
|
}
|
|
});
|
|
|
|
test('3: Wi-Fi · 유선 주소가 없으면 공유를 켜지 않는다 (모든 연결 0.0.0.0 에 열지 않음)', () async {
|
|
final tmp = Directory.systemTemp.createTempSync('jj_netsec_');
|
|
addTearDown(() => tmp.deleteSync(recursive: true));
|
|
WifiShare.debugAddresses = const [];
|
|
addTearDown(() => WifiShare.debugAddresses = null);
|
|
await expectLater(WifiShare.start(tmp.path), throwsA(isA<SocketException>()));
|
|
expect(WifiShare.instance, isNull);
|
|
});
|
|
|
|
test('3: 공유는 고른 주소에만 열린다 (127.0.0.1 로 열면 다른 주소로는 닿지 않음)', () async {
|
|
final tmp = Directory.systemTemp.createTempSync('jj_netsec_');
|
|
addTearDown(() => tmp.deleteSync(recursive: true));
|
|
WifiShare.debugAddresses = const ['127.0.0.1'];
|
|
addTearDown(() => WifiShare.debugAddresses = null);
|
|
final s = await WifiShare.start(tmp.path, port: 0);
|
|
addTearDown(s.stop);
|
|
expect(s.addresses, ['127.0.0.1']);
|
|
final ok = await Socket.connect('127.0.0.1', s.port);
|
|
ok.destroy();
|
|
// 이 PC 의 다른 주소 (있으면) 로는 연결되지 않는다
|
|
for (final ni in await NetworkInterface.list(type: InternetAddressType.IPv4)) {
|
|
for (final a in ni.addresses) {
|
|
await expectLater(Socket.connect(a, s.port, timeout: const Duration(seconds: 1)), throwsA(isA<SocketException>()), reason: a.address);
|
|
}
|
|
}
|
|
});
|
|
}
|