Avaldatud allikas
Laadi alla allikas ZIP
CH-J SM Mobile
Vaata katalooge ja faile konkreetse rakenduse vabastamiseks.
CH-J / SM Mobile
134 failid
sm-mobile-20261016T050549Z-3678f2a05fa9
Lisatud: · Ehitatud:
1
import 'dart:async';2
import 'dart:convert';3
import 'dart:io';4
import 'package:dartssh2/dartssh2.dart';5
import 'package:flutter/foundation.dart';6
import 'package:xterm/xterm.dart';7
import '../core/failure.dart';8
import '../models/server_profile.dart';9
import 'host_verifier.dart';10
import 'vault.dart';12
String failureCode(Object error) {13
if (error is Failure) return error.code;14
if (error is TimeoutException) return 'timeout';15
if ((error is SSHHandshakeError && error.message == 'Handshake timed out') ||16
(error is SSHAuthAbortError &&17
error.message == 'Authentication timed out')) {18
return 'timeout';19
}20
if (error is SSHAuthError) return 'authFailed';21
if (error is SSHHostkeyError) return 'hostRejected';22
if (error is SocketException) return 'networkError';23
if (error is SftpStatusError) {24
return error.code == 3 ? 'permissionDenied' : 'fileError';25
}26
if (error is SSHKeyDecodeError) return 'invalidKey';27
return 'operationFailed';28
}30
class SshService extends ChangeNotifier {31
final Vault vault;32
final Duration connectTimeout, handshakeTimeout, authTimeout;33
SSHClient? _client;34
SSHSession? _shell;35
SSHSocket? _pendingSocket;36
ServerProfile? profile;37
Terminal terminal = Terminal(maxLines: 3000);38
bool connecting = false;39
bool get connected => _client != null && _shell != null;40
String? error;41
int _epoch = 0;42
int? handshakeMs;43
bool ctrlNext = false, altNext = false;44
SshService(45
this.vault, {46
this.connectTimeout = const Duration(seconds: 15),47
this.handshakeTimeout = const Duration(seconds: 90),48
this.authTimeout = const Duration(seconds: 30),49
});50
SSHClient get client {51
if (!connected || !vault.unlocked) throw const Failure('disconnected');52
return _client!;53
}55
Future<void> connect(56
ServerProfile target, {57
required String credential,58
String? passphrase,59
required TrustPrompt prompt,60
}) async {61
disconnect();62
final epoch = _epoch;63
final vaultEpoch = vault.generation;64
connecting = true;65
profile = target;66
error = null;67
notifyListeners();68
SSHClient? candidate;69
Object? hostFailure;70
void check() {71
if (_epoch != epoch ||72
!vault.unlocked ||73
vaultEpoch != vault.generation) {74
throw const Failure('locked');75
}76
}78
try {79
final identities = target.auth == AuthMethod.privateKey80
? SSHKeyPair.fromPem(credential, passphrase)81
: null;82
check();83
final watch = Stopwatch()..start();84
final socket = await SSHSocket.connect(85
target.host,86
target.port,87
timeout: connectTimeout,88
);89
if (_epoch != epoch || !vault.unlocked) {90
socket.close();91
throw const Failure('locked');92
}93
_pendingSocket = socket;94
candidate = SSHClient(95
socket,96
username: target.username,97
identities: identities,98
onPasswordRequest: target.auth == AuthMethod.password99
? () {100
check();101
return credential;102
}103
: null,104
handshakeTimeout: handshakeTimeout,105
authTimeout: authTimeout,106
onVerifyHostKey: (type, fingerprint) async {107
watch.stop();108
try {109
check();110
final accepted = await HostVerifier(111
vault,112
).verify(target.endpoint, type, utf8.decode(fingerprint), prompt);113
check();114
return accepted;115
} catch (e) {116
hostFailure = e;117
return false;118
} finally {119
watch.start();120
}121
},122
);123
unawaited(124
candidate.done.then(125
(_) {126
if (_epoch == epoch) _lost('disconnected');127
},128
onError: (Object e) {129
if (_epoch == epoch) _lost(failureCode(hostFailure ?? e));130
},131
),132
);133
await candidate.authenticated;134
check();135
handshakeMs = watch.elapsedMilliseconds;136
final shell = await candidate137
.shell(138
pty: const SSHPtyConfig(139
type: 'xterm-256color',140
width: 80,141
height: 24,142
),143
)144
.timeout(const Duration(seconds: 15));145
check();146
_client = candidate;147
_shell = shell;148
_pendingSocket = null;149
terminal = Terminal(maxLines: 3000);150
terminal.onOutput = (output) {151
final hadModifier = ctrlNext || altNext;152
if (ctrlNext && output.length == 1) {153
final n = output.codeUnitAt(0);154
if (n >= 0x40 && n <= 0x7f) output = String.fromCharCode(n & 0x1f);155
}156
if (altNext) output = '\x1b$output';157
ctrlNext = false;158
altNext = false;159
if (connected && vault.unlocked) {160
_shell!.write(Uint8List.fromList(utf8.encode(output)));161
}162
if (hadModifier) notifyListeners();163
};164
terminal.onResize = (w, h, pw, ph) {165
if (connected) _shell!.resizeTerminal(w, h, pw, ph);166
};167
void consume(Stream<Uint8List> stream) {168
stream169
.cast<List<int>>()170
.transform(const Utf8Decoder(allowMalformed: true))171
.listen(172
(s) {173
if (_epoch == epoch && vault.unlocked) terminal.write(s);174
},175
onError: (Object e) {176
if (_epoch == epoch) _lost(failureCode(e));177
},178
);179
}181
consume(shell.stdout);182
consume(shell.stderr);183
unawaited(184
shell.done.then(185
(_) {186
if (_epoch == epoch) _lost('disconnected');187
},188
onError: (Object e) {189
if (_epoch == epoch) _lost(failureCode(e));190
},191
),192
);193
} catch (e) {194
if (candidate != null) unawaited(candidate.close());195
if (_epoch == epoch) {196
_pendingSocket?.close();197
_pendingSocket = null;198
error = failureCode(hostFailure ?? e);199
}200
} finally {201
if (_epoch == epoch) {202
connecting = false;203
notifyListeners();204
}205
}206
}208
void _lost(String code) {209
disconnect();210
error = code;211
notifyListeners();212
}214
void disconnect({bool clear = false}) {215
ctrlNext = false;216
altNext = false;217
_epoch++;218
connecting = false;219
final c = _client;220
_client = null;221
_shell?.close();222
_shell = null;223
_pendingSocket?.close();224
_pendingSocket = null;225
if (c != null) unawaited(c.close());226
if (clear) {227
profile = null;228
terminal = Terminal(maxLines: 3000);229
error = null;230
handshakeMs = null;231
}232
notifyListeners();233
}235
@override236
void dispose() {237
disconnect(clear: true);238
super.dispose();239
}240
}SHA-256
281e845e0759b099582c37c6930844bffcefd220fc49f3b4cabc56fae584617cCH-J Proprietary Software License 1.14 · Allikas on esitatud CH-J Proprietary Software License 1.14 all. Selle kättesaadavus ei muuda litsentsitingimusi ega anna täiendavaid õigusi.
Arhiiv SHA-256
c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65