@maiyunnet/kebab 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -1
- package/lib/sql.js +1 -3
- package/lib/text.js +5 -1
- package/package.json +1 -1
- package/tsconfig.json +1 -1
- package/index.ts +0 -33
- package/lib/buffer.ts +0 -152
- package/lib/captcha.ts +0 -63
- package/lib/consistent.ts +0 -219
- package/lib/core.ts +0 -880
- package/lib/crypto.ts +0 -384
- package/lib/db.ts +0 -719
- package/lib/dns.ts +0 -405
- package/lib/fs.ts +0 -527
- package/lib/jwt.ts +0 -276
- package/lib/kv.ts +0 -1489
- package/lib/lan.ts +0 -87
- package/lib/net/formdata.ts +0 -166
- package/lib/net/request.ts +0 -150
- package/lib/net/response.ts +0 -59
- package/lib/net.ts +0 -662
- package/lib/s3.ts +0 -235
- package/lib/scan.ts +0 -364
- package/lib/session.ts +0 -230
- package/lib/sql.ts +0 -1151
- package/lib/ssh/sftp.ts +0 -508
- package/lib/ssh/shell.ts +0 -123
- package/lib/ssh.ts +0 -191
- package/lib/text.ts +0 -615
- package/lib/time.ts +0 -254
- package/lib/ws.ts +0 -523
- package/lib/zip.ts +0 -447
- package/lib/zlib.ts +0 -350
- package/main.ts +0 -27
- package/sys/child.ts +0 -678
- package/sys/cmd.ts +0 -225
- package/sys/ctr.ts +0 -904
- package/sys/master.ts +0 -355
- package/sys/mod.ts +0 -1871
- package/sys/route.ts +0 -1113
- package/types/index.d.ts +0 -283
- package/www/example/ctr/main.ts +0 -9
- package/www/example/ctr/middle.ts +0 -26
- package/www/example/ctr/test.ts +0 -3218
- package/www/example/mod/test.ts +0 -47
- package/www/example/mod/testdata.ts +0 -30
- package/www/example/ws/mproxy.ts +0 -16
- package/www/example/ws/rproxy.ts +0 -14
- package/www/example/ws/test.ts +0 -36
package/lib/zip.ts
DELETED
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
import jszip from 'jszip';
|
|
2
|
-
import * as mime from '@litert/mime';
|
|
3
|
-
import * as lText from '~/lib/text';
|
|
4
|
-
import * as types from '~/types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* --- 本库主要用于读取 zip,请尽量不要用来写入 zip,尤其是大文件 zip ---
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export class Zip {
|
|
11
|
-
|
|
12
|
-
/** --- zip 对象 --- */
|
|
13
|
-
private readonly _zip!: jszip;
|
|
14
|
-
|
|
15
|
-
/** --- 当前路径,以 / 开头以 / 结尾 --- */
|
|
16
|
-
private _path: string = '/';
|
|
17
|
-
|
|
18
|
-
public constructor(zip: jszip) {
|
|
19
|
-
this._zip = zip;
|
|
20
|
-
this._refreshList();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public async getContent(path: string): Promise<string | null>;
|
|
24
|
-
public async getContent<T extends types.TZipOutputType>(
|
|
25
|
-
path: string,
|
|
26
|
-
type: T): Promise<types.IZipOutputByType[T] | null>;
|
|
27
|
-
/**
|
|
28
|
-
* --- 读取完整文件 ---
|
|
29
|
-
* @param path 文件路径
|
|
30
|
-
* @param type 返回类型
|
|
31
|
-
*/
|
|
32
|
-
public async getContent<T extends types.TZipOutputType>(path: string, type: T = 'string' as T): Promise<types.IZipOutputByType[T] | string | null> {
|
|
33
|
-
path = lText.urlResolve(this._path, path);
|
|
34
|
-
const f = this._zip.file(path.slice(1));
|
|
35
|
-
if (!f) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
if (type === 'string') {
|
|
39
|
-
return f.async('string');
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
return f.async(type);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* --- 写入文件内容 ---
|
|
48
|
-
* @param path 文件路径
|
|
49
|
-
* @param data 要写入的内容
|
|
50
|
-
* @param options 选项
|
|
51
|
-
*/
|
|
52
|
-
public putContent<T extends types.TZipInputType>(path: string, data: types.IZipInputByType[T], options: { 'base64'?: boolean; 'binary'?: boolean; 'date'?: Date; } = {}): void {
|
|
53
|
-
path = lText.urlResolve(this._path, path);
|
|
54
|
-
this._zip.file(path.slice(1), data as jszip.InputType, {
|
|
55
|
-
'base64': options.base64,
|
|
56
|
-
'binary': options.binary,
|
|
57
|
-
'date': options.date
|
|
58
|
-
});
|
|
59
|
-
this._refreshList();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* --- 删除一个文件/文件夹(深度删除) ---
|
|
64
|
-
* @param path 要删除的文件路径
|
|
65
|
-
*/
|
|
66
|
-
public unlink(path: string): void {
|
|
67
|
-
path = lText.urlResolve(this._path, path);
|
|
68
|
-
this._zip.remove(path.slice(1));
|
|
69
|
-
this._refreshList();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* --- 获取对象是否存在,存在则返回 stats 对象,否则返回 null ---
|
|
74
|
-
* @param path 对象路径
|
|
75
|
-
* @param options 选项
|
|
76
|
-
*/
|
|
77
|
-
public stats(path: string): types.IZipStats | null {
|
|
78
|
-
path = lText.urlResolve(this._path, path);
|
|
79
|
-
let dirpath = path.endsWith('/') ? path : path + '/';
|
|
80
|
-
if (!this._list[dirpath]) {
|
|
81
|
-
// --- 可能是文件 ---
|
|
82
|
-
if (path.endsWith('/')) {
|
|
83
|
-
// --- 不可能是文件 ---
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
const lio = path.lastIndexOf('/') + 1;
|
|
87
|
-
const dpath = path.slice(0, lio);
|
|
88
|
-
const fname = path.slice(lio);
|
|
89
|
-
if (!this._list[dpath]) {
|
|
90
|
-
// --- 上层目录不存在 ---
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
const file = this._list[dpath][fname];
|
|
94
|
-
if (!file) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
'compressedSize': file.compressedSize,
|
|
99
|
-
'uncompressedSize': file.uncompressedSize,
|
|
100
|
-
'date': file.date,
|
|
101
|
-
'isFile': true,
|
|
102
|
-
'isDirectory': false
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
// --- 文件夹 ---
|
|
107
|
-
if (dirpath === '/') {
|
|
108
|
-
return {
|
|
109
|
-
'compressedSize': 0,
|
|
110
|
-
'uncompressedSize': 0,
|
|
111
|
-
'date': new Date(),
|
|
112
|
-
'isFile': false,
|
|
113
|
-
'isDirectory': true
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
dirpath = dirpath.slice(0, -1);
|
|
117
|
-
const lio = dirpath.lastIndexOf('/') + 1;
|
|
118
|
-
const dpath = dirpath.slice(0, lio);
|
|
119
|
-
const fname = dirpath.slice(lio);
|
|
120
|
-
const pfolder = this._list[dpath];
|
|
121
|
-
const folder = pfolder[fname];
|
|
122
|
-
return {
|
|
123
|
-
'compressedSize': 0,
|
|
124
|
-
'uncompressedSize': 0,
|
|
125
|
-
'date': folder.date,
|
|
126
|
-
'isFile': false,
|
|
127
|
-
'isDirectory': true
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* --- 判断是否是目录或目录是否存在,是的话返回 stats ---
|
|
134
|
-
* @param path 判断路径
|
|
135
|
-
*/
|
|
136
|
-
public isDir(path: string): types.IZipStats | false {
|
|
137
|
-
const pstats = this.stats(path);
|
|
138
|
-
if (!pstats?.isDirectory) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
return pstats;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* --- 判断是否是文件或文件是否存在,是的话返回 stats ---
|
|
146
|
-
* @param path 判断路径
|
|
147
|
-
*/
|
|
148
|
-
public isFile(path: string): types.IZipStats | false {
|
|
149
|
-
const pstats = this.stats(path);
|
|
150
|
-
if (!pstats?.isFile) {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
return pstats;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** --- 读取目录,hasChildren: false, hasDir: true, pathAsKey: false --- */
|
|
157
|
-
public readDir(path?: string, opt?: { 'hasChildren'?: boolean; 'hasDir'?: boolean; 'pathAsKey'?: false; }): types.IZipItem[];
|
|
158
|
-
public readDir(path?: string, opt?: { 'hasChildren'?: boolean; 'hasDir'?: boolean; 'pathAsKey': true; }): Record<string, types.IZipItem>;
|
|
159
|
-
/**
|
|
160
|
-
* --- 获取文件夹下文件列表 ---
|
|
161
|
-
* @param path 文件夹路径
|
|
162
|
-
* @param opt 选项
|
|
163
|
-
*/
|
|
164
|
-
public readDir(path?: string, opt: { 'hasChildren'?: boolean; 'hasDir'?: boolean; 'pathAsKey'?: boolean; } = {}): Record<string, types.IZipItem> | types.IZipItem[] {
|
|
165
|
-
if (opt.hasChildren === undefined) {
|
|
166
|
-
opt.hasChildren = false;
|
|
167
|
-
}
|
|
168
|
-
if (opt.hasDir === undefined) {
|
|
169
|
-
opt.hasDir = true;
|
|
170
|
-
}
|
|
171
|
-
if (opt.pathAsKey === undefined) {
|
|
172
|
-
opt.pathAsKey = false;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (!path) {
|
|
176
|
-
path = this._path;
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
path = lText.urlResolve(this._path, path);
|
|
180
|
-
}
|
|
181
|
-
if (!path.endsWith('/')) {
|
|
182
|
-
path += '/';
|
|
183
|
-
}
|
|
184
|
-
const folder = this._zip.folder(path.slice(1));
|
|
185
|
-
if (!folder) {
|
|
186
|
-
return opt.pathAsKey ? {} : [];
|
|
187
|
-
}
|
|
188
|
-
if (!this._list[path]) {
|
|
189
|
-
return opt.pathAsKey ? {} : [];
|
|
190
|
-
}
|
|
191
|
-
if (!opt.hasChildren) {
|
|
192
|
-
if (opt.pathAsKey) {
|
|
193
|
-
return this._list[path];
|
|
194
|
-
}
|
|
195
|
-
const list: types.IZipItem[] = [];
|
|
196
|
-
for (const k in this._list[path]) {
|
|
197
|
-
list.push(this._list[path][k]);
|
|
198
|
-
}
|
|
199
|
-
return list;
|
|
200
|
-
}
|
|
201
|
-
// --- 定义 list ---
|
|
202
|
-
if (opt.pathAsKey) {
|
|
203
|
-
const list: Record<string, types.IZipItem> = {};
|
|
204
|
-
// --- 遍历子项 ---
|
|
205
|
-
for (const k in this._list[path]) {
|
|
206
|
-
const item = this._list[path][k];
|
|
207
|
-
if (item.isFile || opt.hasDir) {
|
|
208
|
-
list[item.path + item.name] = item;
|
|
209
|
-
}
|
|
210
|
-
if (item.isDirectory) {
|
|
211
|
-
Object.assign(list, this._readDir(item, {
|
|
212
|
-
'hasDir': opt.hasDir,
|
|
213
|
-
'pathAsKey': opt.pathAsKey
|
|
214
|
-
}));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return list;
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
let list: types.IZipItem[] = [];
|
|
221
|
-
// --- 遍历子项 ---
|
|
222
|
-
for (const k in this._list[path]) {
|
|
223
|
-
const item = this._list[path][k];
|
|
224
|
-
if (item.isFile || opt.hasDir) {
|
|
225
|
-
list.push(item);
|
|
226
|
-
}
|
|
227
|
-
if (item.isDirectory) {
|
|
228
|
-
list = list.concat(this._readDir(item, {
|
|
229
|
-
'hasDir': opt.hasDir,
|
|
230
|
-
'pathAsKey': opt.pathAsKey
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return list;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private _readDir(item: types.IZipItem, opt: { 'hasDir'?: boolean; 'pathAsKey'?: false; }): types.IZipItem[];
|
|
239
|
-
private _readDir(item: types.IZipItem, opt: { 'hasDir'?: boolean; 'pathAsKey': true; }): Record<string, types.IZipItem>;
|
|
240
|
-
/**
|
|
241
|
-
* --- 根据 item 文件夹读取子层及所有子层项 ---
|
|
242
|
-
* @param item 文件夹
|
|
243
|
-
*/
|
|
244
|
-
private _readDir(item: types.IZipItem, opt: { 'hasDir'?: boolean; 'pathAsKey'?: boolean; }): Record<string, types.IZipItem> | types.IZipItem[] {
|
|
245
|
-
if (opt.pathAsKey) {
|
|
246
|
-
const list: Record<string, types.IZipItem> = {};
|
|
247
|
-
if (!this._list[item.path + item.name + '/']) {
|
|
248
|
-
return {};
|
|
249
|
-
}
|
|
250
|
-
for (const k in this._list[item.path + item.name + '/']) {
|
|
251
|
-
const it = this._list[item.path + item.name + '/'][k];
|
|
252
|
-
if (it.isFile || opt.hasDir) {
|
|
253
|
-
list[it.path + it.name] = it;
|
|
254
|
-
}
|
|
255
|
-
if (it.isDirectory) {
|
|
256
|
-
Object.assign(list, this._readDir(it, {
|
|
257
|
-
'hasDir': opt.hasDir,
|
|
258
|
-
'pathAsKey': opt.pathAsKey
|
|
259
|
-
}));
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return list;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
let list: types.IZipItem[] = [];
|
|
266
|
-
if (!this._list[item.path + item.name + '/']) {
|
|
267
|
-
return [];
|
|
268
|
-
}
|
|
269
|
-
for (const k in this._list[item.path + item.name + '/']) {
|
|
270
|
-
const it = this._list[item.path + item.name + '/'][k];
|
|
271
|
-
if (it.isFile || opt.hasDir) {
|
|
272
|
-
list.push(it);
|
|
273
|
-
}
|
|
274
|
-
if (it.isDirectory) {
|
|
275
|
-
list = list.concat(this._readDir(it, {
|
|
276
|
-
'hasDir': opt.hasDir,
|
|
277
|
-
'pathAsKey': opt.pathAsKey
|
|
278
|
-
}));
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return list;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/** --- 目录列表缓存 --- */
|
|
286
|
-
private _list: Record<string, Record<string, types.IZipItem>> = {};
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* --- 重建目录列表缓存 ---
|
|
290
|
-
*/
|
|
291
|
-
private _refreshList(): void {
|
|
292
|
-
const list: Record<string, Record<string, types.IZipItem>> = {};
|
|
293
|
-
// eslint-disable-next-line @litert/rules/disable-for-each-method
|
|
294
|
-
this._zip.forEach(function(relativePath: string, item: jszip.JSZipObject) {
|
|
295
|
-
if (relativePath.startsWith('/')) {
|
|
296
|
-
relativePath = relativePath.slice(1);
|
|
297
|
-
}
|
|
298
|
-
let parentPath = '/';
|
|
299
|
-
let name = '';
|
|
300
|
-
let s: number;
|
|
301
|
-
if (item.dir) {
|
|
302
|
-
s = relativePath.slice(0, -1).lastIndexOf('/');
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
s = relativePath.lastIndexOf('/');
|
|
306
|
-
}
|
|
307
|
-
if (s !== -1) {
|
|
308
|
-
parentPath = '/' + relativePath.slice(0, s + 1);
|
|
309
|
-
name = relativePath.slice(s + 1);
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
name = relativePath;
|
|
313
|
-
}
|
|
314
|
-
if (item.dir) {
|
|
315
|
-
name = name.slice(0, -1);
|
|
316
|
-
}
|
|
317
|
-
if (!list[parentPath]) {
|
|
318
|
-
list[parentPath] = {};
|
|
319
|
-
}
|
|
320
|
-
list[parentPath][name] = {
|
|
321
|
-
'name': name,
|
|
322
|
-
'compressedSize': (item as any)._data.compressedSize ?? 0,
|
|
323
|
-
'uncompressedSize': (item as any)._data.uncompressedSize ?? 0,
|
|
324
|
-
'date': item.date,
|
|
325
|
-
'isFile': !item.dir,
|
|
326
|
-
'isDirectory': item.dir,
|
|
327
|
-
'path': parentPath
|
|
328
|
-
};
|
|
329
|
-
});
|
|
330
|
-
this._list = list;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* --- 获取当前目录,末尾不带 / ---
|
|
335
|
-
* @return string
|
|
336
|
-
*/
|
|
337
|
-
public pwd(): string {
|
|
338
|
-
return this._path.slice(0, -1);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* --- 进入一个目录(不存在也能进入,需要自行判断) ---
|
|
343
|
-
* --- 返回进入后的路径值 ---
|
|
344
|
-
* @param dir 相对路径或绝对路径
|
|
345
|
-
*/
|
|
346
|
-
public cd(dir: string): string {
|
|
347
|
-
this._path = lText.urlResolve(this._path, dir);
|
|
348
|
-
if (!this._path.endsWith('/')) {
|
|
349
|
-
this._path += '/';
|
|
350
|
-
}
|
|
351
|
-
return this._path;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* --- 打包 zip ---
|
|
356
|
-
* @param options 选项
|
|
357
|
-
*/
|
|
358
|
-
public generate<T extends types.TZipOutputType>(options: { 'type'?: T; 'level'?: number; 'onUpdate'?: (percent: number, currentFile: string | null) => void; } = {}): Promise<types.IZipOutputByType[T]> {
|
|
359
|
-
const opt: any = {};
|
|
360
|
-
if (options.type === undefined) {
|
|
361
|
-
opt.type = 'nodebuffer' as T;
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
opt.type = options.type;
|
|
365
|
-
}
|
|
366
|
-
if (options.level === undefined) {
|
|
367
|
-
options.level = 9;
|
|
368
|
-
}
|
|
369
|
-
else if (options.level > 9) {
|
|
370
|
-
options.level = 9;
|
|
371
|
-
}
|
|
372
|
-
if (options.level > 0) {
|
|
373
|
-
opt.compression = 'DEFLATE';
|
|
374
|
-
}
|
|
375
|
-
return this._zip.generateAsync(opt, function(meta: types.IZipMetadata): void {
|
|
376
|
-
options.onUpdate?.(meta.percent, meta.currentFile);
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* --- 获取 path 和 string/Buffer 对应的文件列表 ---
|
|
382
|
-
*/
|
|
383
|
-
public getList(): Promise<Record<string, Buffer | string>> {
|
|
384
|
-
return new Promise((resolve) => {
|
|
385
|
-
const files: Record<string, Buffer | string> = {};
|
|
386
|
-
const list = this.readDir('/', {
|
|
387
|
-
'hasChildren': true,
|
|
388
|
-
'hasDir': false
|
|
389
|
-
});
|
|
390
|
-
let loaded = 0;
|
|
391
|
-
for (const file of list) {
|
|
392
|
-
const mim = mime.getData(file.name);
|
|
393
|
-
if (['txt', 'json', 'js', 'css', 'xml', 'html'].includes(mim.extension)) {
|
|
394
|
-
this.getContent(file.path + file.name, 'string').then(function(fb) {
|
|
395
|
-
if (fb) {
|
|
396
|
-
files[file.path + file.name] = fb;
|
|
397
|
-
}
|
|
398
|
-
++loaded;
|
|
399
|
-
if (loaded === list.length) {
|
|
400
|
-
resolve(files);
|
|
401
|
-
}
|
|
402
|
-
}).catch(function() {
|
|
403
|
-
++loaded;
|
|
404
|
-
if (loaded === list.length) {
|
|
405
|
-
resolve(files);
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
this.getContent(file.path + file.name, 'nodebuffer').then(function(fb) {
|
|
411
|
-
if (fb) {
|
|
412
|
-
files[file.path + file.name] = fb;
|
|
413
|
-
}
|
|
414
|
-
++loaded;
|
|
415
|
-
if (loaded === list.length) {
|
|
416
|
-
resolve(files);
|
|
417
|
-
}
|
|
418
|
-
}).catch(function() {
|
|
419
|
-
++loaded;
|
|
420
|
-
if (loaded === list.length) {
|
|
421
|
-
resolve(files);
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
return files;
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* --- 获取 zip 对象 ---
|
|
434
|
-
* @param data 对象数据
|
|
435
|
-
*/
|
|
436
|
-
export async function get(data?: types.TZipInputFileFormat): Promise<Zip | null> {
|
|
437
|
-
const z = jszip();
|
|
438
|
-
try {
|
|
439
|
-
if (data) {
|
|
440
|
-
await z.loadAsync(data);
|
|
441
|
-
}
|
|
442
|
-
return new Zip(z);
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
}
|