Veröffentlichte Quellen
Quellen als ZIP herunterladen
CH-J SM Mobile
Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.
CH-J / SM Mobile
134 Dateien
sm-mobile-20261016T050549Z-3678f2a05fa9
Hinzugefügt: · Erstellt:
1
import 'dart:async';2
import 'dart:io';3
import 'dart:typed_data';4
import 'package:dartssh2/dartssh2.dart';5
import '../core/failure.dart';6
import '../models/server_profile.dart';8
String safeName(String name) {9
if (name.isEmpty ||10
name == '.' ||11
name == '..' ||12
name.length > 255 ||13
RegExp(r'[/\\\x00-\x1f\x7f]').hasMatch(name)) {14
throw const Failure('unsafePath');15
}16
return name;17
}19
String safePath(String path) {20
if (!path.startsWith('/') ||21
RegExp(r'[\\\x00-\x1f\x7f]').hasMatch(path) ||22
path.split('/').any((s) => s == '.' || s == '..')) {23
throw const Failure('unsafePath');24
}25
return path == '/' ? '/' : path.replaceAll(RegExp(r'/+$'), '');26
}28
String childPath(String parent, String name) =>29
'${safePath(parent) == '/' ? '' : safePath(parent)}/${safeName(name)}';30
String parentPath(String path) {31
safePath(path);32
final index = path.lastIndexOf('/');33
return index <= 0 ? '/' : path.substring(0, index);34
}36
class TransferToken {37
bool cancelled = false;38
void Function()? abort;39
void cancel() {40
cancelled = true;41
abort?.call();42
}44
void check() {45
if (cancelled) throw const Failure('cancelled');46
}47
}49
class SftpService {50
final SftpClient client;51
SftpService(this.client);52
Future<String> home() async => safePath(await client.absolute('.'));53
Future<void> guard(String path, {bool missingLeaf = false}) async {54
safePath(path);55
var current = '';56
final parts = path.split('/').where((p) => p.isNotEmpty).toList();57
for (var i = 0; i < parts.length; i++) {58
current += '/${safeName(parts[i])}';59
try {60
final attrs = await client.stat(current, followLink: false);61
if (attrs.isSymbolicLink ||62
(i < parts.length - 1 && !attrs.isDirectory)) {63
throw const Failure('unsafePath');64
}65
} on SftpStatusError catch (e) {66
if (missingLeaf && i == parts.length - 1 && e.code == 2) return;67
rethrow;68
}69
}70
}72
Future<List<SftpName>> list(String path) async {73
await guard(path);74
final entries = await client.listdir(path);75
final filtered = entries76
.where((e) => e.filename != '.' && e.filename != '..')77
.toList();78
for (final entry in filtered) {79
safeName(entry.filename);80
}81
filtered.sort(82
(a, b) => a.attr.isDirectory == b.attr.isDirectory83
? a.filename.toLowerCase().compareTo(b.filename.toLowerCase())84
: a.attr.isDirectory85
? -186
: 1,87
);88
return filtered;89
}91
Future<void> mkdir(String path) async {92
await guard(path, missingLeaf: true);93
await client.mkdir(path);94
}96
Future<void> rename(String oldPath, String newPath) async {97
await guard(oldPath);98
await guard(newPath, missingLeaf: true);99
await _absent(newPath);100
await client.rename(oldPath, newPath);101
}103
Future<void> remove(String path) async {104
if (path == '/') throw const Failure('unsafePath');105
await guard(path);106
final attrs = await client.stat(path, followLink: false);107
if (attrs.isDirectory) {108
await client.rmdir(path);109
} else {110
await client.remove(path);111
}112
}114
Future<void> _absent(String path) async {115
try {116
await client.stat(path, followLink: false);117
} on SftpStatusError catch (e) {118
if (e.code == 2) return;119
rethrow;120
}121
throw const Failure('fileExists');122
}124
Future<void> upload(125
Stream<Uint8List> stream,126
int size,127
String remote,128
TransferToken token,129
void Function(int, int) progress,130
) async {131
await guard(remote, missingLeaf: true);132
await _absent(remote);133
token.check();134
final temp = childPath(parentPath(remote), '.sm-${newId()}.part');135
SftpFile? file;136
var created = false;137
try {138
file = await client.open(139
temp,140
mode:141
SftpFileOpenMode.write |142
SftpFileOpenMode.create |143
SftpFileOpenMode.exclusive,144
);145
created = true;146
final writer = file.write(147
stream,148
onProgress: (n) {149
progress(n, size);150
},151
);152
token.abort = () {153
unawaited(writer.abort().catchError((Object _) {}));154
};155
if (token.cancelled) token.abort!();156
await writer.done;157
token.check();158
await file.close();159
file = null;160
await guard(remote, missingLeaf: true);161
await _absent(remote);162
await client.rename(temp, remote);163
} catch (_) {164
try {165
await file?.close();166
if (created) await client.remove(temp);167
} catch (_) {168
/* Session may already be closed. */169
}170
rethrow;171
} finally {172
token.abort = null;173
}174
}176
Future<void> download(177
String remote,178
File local,179
TransferToken token,180
void Function(int, int) progress,181
) async {182
await guard(remote);183
token.check();184
final attrs = await client.stat(remote, followLink: false);185
if (!attrs.isFile) throw const Failure('unsafePath');186
final file = await client.open(remote);187
final sink = local.openWrite();188
token.abort = () {189
unawaited(file.close().catchError((Object _) {}));190
};191
try {192
await sink.addStream(193
file194
.read(195
onProgress: (n) {196
progress(n, attrs.size ?? 0);197
},198
)199
.map((c) {200
token.check();201
return c;202
}),203
);204
token.check();205
await sink.flush();206
await sink.close();207
} catch (_) {208
try {209
await sink.close();210
} catch (_) {211
/* Preserve the original transfer failure. */212
}213
if (await local.exists()) await local.delete();214
rethrow;215
} finally {216
token.abort = null;217
try {218
await sink.close();219
} catch (_) {220
/* Already handled above. */221
}222
try {223
await file.close();224
} catch (_) {225
/* Cancellation closes the handle. */226
}227
}228
}229
}SHA-256
3b1d8c99e5ee9475264d9a4c3482de0057f7a6274130e298579cfa2753e6ba19CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.
SHA-256 des Archivs
c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65