import 'dart:convert'; import 'dart:io'; import 'dart:math'; import '../core/failure.dart'; enum AuthMethod { password, privateKey } String newId() => List.generate( 16, (_) => Random.secure().nextInt(256), ).map((e) => e.toRadixString(16).padLeft(2, '0')).join(); String normalizeHost(String input) { var host = input.trim(); if (host.startsWith('[') && host.endsWith(']')) { host = host.substring(1, host.length - 1); if (InternetAddress.tryParse(host)?.type != InternetAddressType.IPv6) { throw const Failure('invalidProfile'); } } final ip = InternetAddress.tryParse(host); final dns = RegExp( r'^(?=.{1,253}$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$', ); if (ip == null && !dns.hasMatch(host)) throw const Failure('invalidProfile'); return host; } class ServerProfile { final String id, name, host, username; final int port; final AuthMethod auth; ServerProfile({ String? id, required String name, required String host, required String username, this.port = 22, this.auth = AuthMethod.password, }) : id = id ?? newId(), name = name.trim(), host = normalizeHost(host), username = username.trim() { if (this.name.isEmpty || this.name.length > 100 || this.username.isEmpty || this.username.length > 128 || RegExp(r'[\x00-\x1f\x7f]').hasMatch(this.name + this.username) || port < 1 || port > 65535) { throw const Failure('invalidProfile'); } } String get endpoint { final ip = InternetAddress.tryParse(host); final canonical = ip == null ? host.toLowerCase() : InternetAddress.fromRawAddress(ip.rawAddress).address; return ip?.type == InternetAddressType.IPv6 ? '[$canonical]:$port' : '$canonical:$port'; } String get identity => jsonEncode([endpoint, username]); Map portable() => { 'name': name, 'host': host, 'username': username, 'port': port, }; Map toJson() => {...portable(), 'id': id, 'auth': auth.name}; factory ServerProfile.fromJson( Map json, { bool portable = false, }) { if (json['name'] is! String || json['host'] is! String || json['username'] is! String || (json.containsKey('port') && json['port'] is! int)) { throw const Failure('invalidProfile'); } return ServerProfile( id: portable ? null : json['id'] as String?, name: json['name'], host: json['host'], username: json['username'], port: json['port'] ?? 22, auth: !portable && json['auth'] == 'privateKey' ? AuthMethod.privateKey : AuthMethod.password, ); } }