CH-J SM Mobileserverio valdymas per SSH
Meniu
Paskelbtas šaltinis

CH-J SM Mobile

Naršyti aplankus ir failus konkrečiai programai išleisti.

Atsisiųsti šaltinį ZIP
CH-J / SM Mobile 134 failai
sm-mobile-20261016T050549Z-3678f2a05fa9 Pridėta: · Pastatytas:
Failai
sftp_service.dart Pridėta: Data neregistruota 5,9 KB · 229 eilutės
Atsiųsti failą
1import 'dart:async';
2import 'dart:io';
3import 'dart:typed_data';
4import 'package:dartssh2/dartssh2.dart';
5import '../core/failure.dart';
6import '../models/server_profile.dart';
8String 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;
19String 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'/+$'), '');
28String childPath(String parent, String name) =>
29 '${safePath(parent) == '/' ? '' : safePath(parent)}/${safeName(name)}';
30String parentPath(String path) {
31 safePath(path);
32 final index = path.lastIndexOf('/');
33 return index <= 0 ? '/' : path.substring(0, index);
36class 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 }
49class 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 = entries
76 .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.isDirectory
83 ? a.filename.toLowerCase().compareTo(b.filename.toLowerCase())
84 : a.attr.isDirectory
85 ? -1
86 : 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 file
194 .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 }
SHA-2563b1d8c99e5ee9475264d9a4c3482de0057f7a6274130e298579cfa2753e6ba19

CH-J Proprietary Software License 1.14 · Šaltinis pateiktas pagal CH-J Proprietary Software License 1.14. Jo prieinamumas nekeičia licencijos sąlygų ir nesuteikia papildomų teisių.

Archyvas SHA-256c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65