fsd-oss 0.14.0 → 0.14.1
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/lib/index.js +37 -36
- package/lib/simple-oss-client.js +19 -18
- package/package.json +5 -6
- package/LICENSE +0 -21
package/lib/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
5
|
+
const qs_1 = tslib_1.__importDefault(require("qs"));
|
|
6
|
+
const slash_1 = tslib_1.__importDefault(require("slash"));
|
|
7
|
+
const minimatch_1 = require("minimatch");
|
|
8
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
9
|
+
const eachLimit_1 = tslib_1.__importDefault(require("async/eachLimit"));
|
|
10
|
+
const pop_core_1 = tslib_1.__importDefault(require("@alicloud/pop-core"));
|
|
11
|
+
const simple_oss_client_1 = tslib_1.__importDefault(require("./simple-oss-client"));
|
|
11
12
|
const stream_1 = require("stream");
|
|
12
|
-
const debug =
|
|
13
|
+
const debug = (0, debug_1.default)('fsd-oss');
|
|
13
14
|
const CALLBACK_BODY = 'bucket=${bucket}&path=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}&format=${imageInfo.format}';
|
|
14
15
|
class OSSAdapter {
|
|
15
16
|
constructor(options) {
|
|
@@ -41,7 +42,7 @@ class OSSAdapter {
|
|
|
41
42
|
if (options.region) {
|
|
42
43
|
stsEndpoint = `https://sts.${options.region.replace('oss-', '')}.aliyuncs.com`;
|
|
43
44
|
}
|
|
44
|
-
this._rpc = new
|
|
45
|
+
this._rpc = new pop_core_1.default({
|
|
45
46
|
accessKeyId: options.accessKeyId,
|
|
46
47
|
accessKeySecret: options.accessKeySecret,
|
|
47
48
|
endpoint: stsEndpoint,
|
|
@@ -51,7 +52,7 @@ class OSSAdapter {
|
|
|
51
52
|
this.createUploadToken = async (path, meta, durationSeconds) => {
|
|
52
53
|
if (!options.accountId || !options.roleName)
|
|
53
54
|
throw new Error('Can not create sts token, missing options: accountId and roleName!');
|
|
54
|
-
path =
|
|
55
|
+
path = (0, slash_1.default)(path_1.default.join(options.root, path)).substring(1);
|
|
55
56
|
let params = {
|
|
56
57
|
RoleArn: `acs:ram::${options.accountId}:role/${options.roleName}`,
|
|
57
58
|
RoleSessionName: 'fsd',
|
|
@@ -109,7 +110,7 @@ class OSSAdapter {
|
|
|
109
110
|
async append(path, data) {
|
|
110
111
|
debug('append %s', path);
|
|
111
112
|
const { root } = this._options;
|
|
112
|
-
let p =
|
|
113
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
113
114
|
if (typeof data === 'string') {
|
|
114
115
|
data = Buffer.from(data);
|
|
115
116
|
}
|
|
@@ -123,7 +124,7 @@ class OSSAdapter {
|
|
|
123
124
|
async createReadStream(path, options) {
|
|
124
125
|
debug('createReadStream %s options: %o', path, options);
|
|
125
126
|
const { root } = this._options;
|
|
126
|
-
let p =
|
|
127
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
127
128
|
let opts = {};
|
|
128
129
|
if (options) {
|
|
129
130
|
let start = options.start || 0;
|
|
@@ -146,7 +147,7 @@ class OSSAdapter {
|
|
|
146
147
|
if (options?.start)
|
|
147
148
|
throw new Error('fsd-oss read stream does not support start options');
|
|
148
149
|
const { root } = this._options;
|
|
149
|
-
let p =
|
|
150
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
150
151
|
let stream = new stream_1.PassThrough();
|
|
151
152
|
stream.promise = this._oss.put(p, stream);
|
|
152
153
|
return stream;
|
|
@@ -154,7 +155,7 @@ class OSSAdapter {
|
|
|
154
155
|
async unlink(path) {
|
|
155
156
|
debug('unlink %s', path);
|
|
156
157
|
const { root } = this._options;
|
|
157
|
-
let p =
|
|
158
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
158
159
|
if (path.endsWith('/')) {
|
|
159
160
|
let continuationToken = '';
|
|
160
161
|
do {
|
|
@@ -178,7 +179,7 @@ class OSSAdapter {
|
|
|
178
179
|
}
|
|
179
180
|
async mkdir(path, recursive) {
|
|
180
181
|
debug('mkdir %s', path);
|
|
181
|
-
let parent =
|
|
182
|
+
let parent = path_1.default.dirname(path);
|
|
182
183
|
if (recursive && parent !== '/') {
|
|
183
184
|
parent += '/';
|
|
184
185
|
if (!(await this.exists(parent))) {
|
|
@@ -187,7 +188,7 @@ class OSSAdapter {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
const { root } = this._options;
|
|
190
|
-
let p =
|
|
191
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
191
192
|
let res = await this._oss.put(p, Buffer.from(''));
|
|
192
193
|
debug('mkdir result: %O', res);
|
|
193
194
|
}
|
|
@@ -202,7 +203,7 @@ class OSSAdapter {
|
|
|
202
203
|
pattern = recursion;
|
|
203
204
|
}
|
|
204
205
|
const { root } = this._options;
|
|
205
|
-
let p =
|
|
206
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
206
207
|
let results = Object.create(null);
|
|
207
208
|
let continuationToken = '';
|
|
208
209
|
let hasContents = false;
|
|
@@ -219,12 +220,12 @@ class OSSAdapter {
|
|
|
219
220
|
if (list.Contents) {
|
|
220
221
|
hasContents = true;
|
|
221
222
|
list.Contents.forEach((object) => {
|
|
222
|
-
let relative =
|
|
223
|
+
let relative = (0, slash_1.default)(path_1.default.relative(p, object.Key));
|
|
223
224
|
if (!relative)
|
|
224
225
|
return;
|
|
225
226
|
if (object.Key.endsWith('/'))
|
|
226
227
|
relative += '/';
|
|
227
|
-
if (pattern && pattern !== '**/*' && !minimatch(relative, pattern))
|
|
228
|
+
if (pattern && pattern !== '**/*' && !(0, minimatch_1.minimatch)(relative, pattern))
|
|
228
229
|
return;
|
|
229
230
|
results[relative] = {
|
|
230
231
|
name: relative,
|
|
@@ -238,7 +239,7 @@ class OSSAdapter {
|
|
|
238
239
|
if (list.CommonPrefixes) {
|
|
239
240
|
hasCommonPrefixes = true;
|
|
240
241
|
list.CommonPrefixes.forEach((prefix) => {
|
|
241
|
-
let relative =
|
|
242
|
+
let relative = (0, slash_1.default)(path_1.default.relative(p, prefix.Prefix));
|
|
242
243
|
if (!relative)
|
|
243
244
|
return;
|
|
244
245
|
relative += '/';
|
|
@@ -259,7 +260,7 @@ class OSSAdapter {
|
|
|
259
260
|
debug('createUrl %s', path);
|
|
260
261
|
options = Object.assign({}, options);
|
|
261
262
|
const { root, urlPrefix, publicRead } = this._options;
|
|
262
|
-
let p =
|
|
263
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path));
|
|
263
264
|
let suffix = '';
|
|
264
265
|
if (options.thumb) {
|
|
265
266
|
if (options.thumb in this._options.thumbs) {
|
|
@@ -275,7 +276,7 @@ class OSSAdapter {
|
|
|
275
276
|
if (suffix) {
|
|
276
277
|
if (suffix[0] === '?')
|
|
277
278
|
suffix = suffix.substring(1);
|
|
278
|
-
options.query =
|
|
279
|
+
options.query = qs_1.default.parse(suffix);
|
|
279
280
|
}
|
|
280
281
|
let url = this._oss.signatureUrl(p.substring(1), options);
|
|
281
282
|
if (urlPrefix) {
|
|
@@ -288,8 +289,8 @@ class OSSAdapter {
|
|
|
288
289
|
if (!(await this.exists(path)))
|
|
289
290
|
throw new Error('The source path is not exists!');
|
|
290
291
|
const { root } = this._options;
|
|
291
|
-
let from =
|
|
292
|
-
let to =
|
|
292
|
+
let from = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
293
|
+
let to = (0, slash_1.default)(path_1.default.join(root, dest)).substring(1);
|
|
293
294
|
if (path.endsWith('/')) {
|
|
294
295
|
debug('copy directory %s -> %s', from, to);
|
|
295
296
|
let continuationToken = '';
|
|
@@ -302,10 +303,10 @@ class OSSAdapter {
|
|
|
302
303
|
debug('list result: %O', list);
|
|
303
304
|
continuationToken = list.NextContinuationToken;
|
|
304
305
|
if (list.Contents?.length) {
|
|
305
|
-
await
|
|
306
|
+
await (0, eachLimit_1.default)(list.Contents, 10, async (object) => {
|
|
306
307
|
debug(' -> copy %s', object.Key);
|
|
307
|
-
let relative =
|
|
308
|
-
let target =
|
|
308
|
+
let relative = (0, slash_1.default)(path_1.default.relative(from, object.Key));
|
|
309
|
+
let target = (0, slash_1.default)(path_1.default.join(to, relative));
|
|
309
310
|
await this._oss.copy(target, object.Key);
|
|
310
311
|
});
|
|
311
312
|
}
|
|
@@ -328,7 +329,7 @@ class OSSAdapter {
|
|
|
328
329
|
async exists(path) {
|
|
329
330
|
debug('check exists %s', path);
|
|
330
331
|
const { root } = this._options;
|
|
331
|
-
let p =
|
|
332
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
332
333
|
if (path.endsWith('/')) {
|
|
333
334
|
let list = await this._oss.list({
|
|
334
335
|
prefix: p,
|
|
@@ -347,7 +348,7 @@ class OSSAdapter {
|
|
|
347
348
|
async isFile(path) {
|
|
348
349
|
debug('check is file %s', path);
|
|
349
350
|
const { root } = this._options;
|
|
350
|
-
let p =
|
|
351
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
351
352
|
try {
|
|
352
353
|
await this._oss.head(p);
|
|
353
354
|
return true;
|
|
@@ -358,7 +359,7 @@ class OSSAdapter {
|
|
|
358
359
|
}
|
|
359
360
|
async isDirectory(path) {
|
|
360
361
|
debug('check is directory %s', path);
|
|
361
|
-
let p =
|
|
362
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
362
363
|
try {
|
|
363
364
|
await this._oss.head(p);
|
|
364
365
|
return true;
|
|
@@ -369,20 +370,20 @@ class OSSAdapter {
|
|
|
369
370
|
}
|
|
370
371
|
async size(path) {
|
|
371
372
|
debug('get file size %s', path);
|
|
372
|
-
let p =
|
|
373
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
373
374
|
let res = await this._oss.head(p);
|
|
374
375
|
return parseInt(res.headers.get('Content-Length')) || 0;
|
|
375
376
|
}
|
|
376
377
|
async lastModified(path) {
|
|
377
378
|
debug('get file lastModified %s', path);
|
|
378
|
-
let p =
|
|
379
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
379
380
|
let res = await this._oss.head(p);
|
|
380
381
|
let headers = res.headers;
|
|
381
382
|
return new Date(headers.get('Last-Modified'));
|
|
382
383
|
}
|
|
383
384
|
async initMultipartUpload(path, partCount) {
|
|
384
385
|
debug('initMultipartUpload %s, partCount: %d', path, partCount);
|
|
385
|
-
let p =
|
|
386
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
386
387
|
let { UploadId } = await this._oss.initMultipartUpload(p);
|
|
387
388
|
let files = [];
|
|
388
389
|
for (let i = 1; i <= partCount; i += 1) {
|
|
@@ -392,7 +393,7 @@ class OSSAdapter {
|
|
|
392
393
|
}
|
|
393
394
|
async writePart(path, partTask, data, size) {
|
|
394
395
|
debug('writePart %s, task: %s', path, partTask);
|
|
395
|
-
let p =
|
|
396
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
396
397
|
if (!partTask.startsWith('task://'))
|
|
397
398
|
throw new Error('Invalid part task id');
|
|
398
399
|
let [uploadId, no] = partTask.replace('task://', '').split('?');
|
|
@@ -407,7 +408,7 @@ class OSSAdapter {
|
|
|
407
408
|
async completeMultipartUpload(path, parts) {
|
|
408
409
|
debug('completeMultipartUpload %s', path);
|
|
409
410
|
let uploadId = parts[0].replace('part://', '').split('?')[0];
|
|
410
|
-
let p =
|
|
411
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
411
412
|
debug('update id: %s, target: %s', uploadId, p);
|
|
412
413
|
let datas = parts.map((item, key) => ({
|
|
413
414
|
etag: item.split('#')[1],
|
package/lib/simple-oss-client.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const qs_1 = tslib_1.__importDefault(require("qs"));
|
|
5
|
+
const xml2js_1 = tslib_1.__importDefault(require("xml2js"));
|
|
6
|
+
const hmac_sha1_1 = tslib_1.__importDefault(require("crypto-js/hmac-sha1"));
|
|
7
|
+
const md5_1 = tslib_1.__importDefault(require("crypto-js/md5"));
|
|
8
|
+
const enc_base64_1 = tslib_1.__importDefault(require("crypto-js/enc-base64"));
|
|
9
|
+
const mime_types_1 = tslib_1.__importDefault(require("mime-types"));
|
|
10
|
+
const akita_1 = tslib_1.__importDefault(require("akita"));
|
|
10
11
|
const client = akita_1.default.create({});
|
|
11
12
|
class SimpleOSSClient {
|
|
12
13
|
constructor(config) {
|
|
@@ -23,7 +24,7 @@ class SimpleOSSClient {
|
|
|
23
24
|
options = options || {};
|
|
24
25
|
let position = options.position || 0;
|
|
25
26
|
if (!options.mime)
|
|
26
|
-
options.mime =
|
|
27
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
27
28
|
return this.requestData('POST', `${name}?append&position=${position}`, body, options);
|
|
28
29
|
}
|
|
29
30
|
get(name, options) {
|
|
@@ -32,7 +33,7 @@ class SimpleOSSClient {
|
|
|
32
33
|
put(name, body, options) {
|
|
33
34
|
options = options || {};
|
|
34
35
|
if (!options.mime)
|
|
35
|
-
options.mime =
|
|
36
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
36
37
|
return this.requestData('PUT', name, body, options);
|
|
37
38
|
}
|
|
38
39
|
async copy(to, from, options) {
|
|
@@ -95,7 +96,7 @@ class SimpleOSSClient {
|
|
|
95
96
|
del(name, options) {
|
|
96
97
|
options = options || {};
|
|
97
98
|
if (!options.mime)
|
|
98
|
-
options.mime =
|
|
99
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
99
100
|
return this.requestData('DELETE', name, null, options);
|
|
100
101
|
}
|
|
101
102
|
async deleteMulti(names, options) {
|
|
@@ -105,7 +106,7 @@ class SimpleOSSClient {
|
|
|
105
106
|
options.mime = 'application/xml';
|
|
106
107
|
if (!options.headers)
|
|
107
108
|
options.headers = {};
|
|
108
|
-
options.headers['Content-MD5'] =
|
|
109
|
+
options.headers['Content-MD5'] = (0, md5_1.default)(body).toString(enc_base64_1.default);
|
|
109
110
|
let res = await this.requestData('POST', '?delete', body, options);
|
|
110
111
|
if (res.Deleted) {
|
|
111
112
|
if (!Array.isArray(res.Deleted))
|
|
@@ -120,13 +121,13 @@ class SimpleOSSClient {
|
|
|
120
121
|
async initMultipartUpload(name, options) {
|
|
121
122
|
options = options || {};
|
|
122
123
|
if (!options.mime)
|
|
123
|
-
options.mime =
|
|
124
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
124
125
|
return await this.requestData('POST', `${name}?uploads`, '', options);
|
|
125
126
|
}
|
|
126
127
|
uploadPart(name, uploadId, partNumber, body, options) {
|
|
127
128
|
options = options || {};
|
|
128
129
|
if (!options.mime)
|
|
129
|
-
options.mime =
|
|
130
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
130
131
|
return this.requestData('PUT', `${name}?partNumber=${partNumber}&uploadId=${uploadId}`, body, options);
|
|
131
132
|
}
|
|
132
133
|
async completeMultipartUpload(name, uploadId, parts, options) {
|
|
@@ -173,7 +174,7 @@ class SimpleOSSClient {
|
|
|
173
174
|
headers: response.headers
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
|
-
let data = await
|
|
177
|
+
let data = await xml2js_1.default.parseStringPromise(xml, {
|
|
177
178
|
trim: true,
|
|
178
179
|
explicitArray: false,
|
|
179
180
|
explicitRoot: false
|
|
@@ -202,12 +203,12 @@ class SimpleOSSClient {
|
|
|
202
203
|
query['security-token'] = this.config.stsToken;
|
|
203
204
|
}
|
|
204
205
|
let parts = [options.method || 'GET', '', '', String(expires)];
|
|
205
|
-
let string =
|
|
206
|
+
let string = qs_1.default.stringify(query);
|
|
206
207
|
parts.push(`/${this.config.bucket}/${name}${string ? `?${string}` : ''}`);
|
|
207
|
-
query.Signature =
|
|
208
|
+
query.Signature = (0, hmac_sha1_1.default)(parts.join('\n'), this.config.accessKeySecret).toString(enc_base64_1.default);
|
|
208
209
|
query.OSSAccessKeyId = this.config.accessKeyId;
|
|
209
210
|
query.Expires = expires;
|
|
210
|
-
return `${url}?${
|
|
211
|
+
return `${url}?${qs_1.default.stringify(query)}`;
|
|
211
212
|
}
|
|
212
213
|
createCanonicalizedResource(resource, subres) {
|
|
213
214
|
if (!subres)
|
|
@@ -235,7 +236,7 @@ class SimpleOSSClient {
|
|
|
235
236
|
parts.push(`${key}:${headers[key]}`);
|
|
236
237
|
});
|
|
237
238
|
parts.push(canonicalizedResource);
|
|
238
|
-
let sign =
|
|
239
|
+
let sign = (0, hmac_sha1_1.default)(parts.join('\n'), this.config.accessKeySecret).toString(enc_base64_1.default);
|
|
239
240
|
return `OSS ${this.config.accessKeyId}:${sign}`;
|
|
240
241
|
}
|
|
241
242
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fsd-oss",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Aliyun OSS adapter for fsd",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -16,11 +16,10 @@
|
|
|
16
16
|
"akita": "^1.1.0",
|
|
17
17
|
"async": "*",
|
|
18
18
|
"crypto-js": "^4.2.0",
|
|
19
|
-
"debug": "^4.4.
|
|
20
|
-
"mime-types": "^
|
|
21
|
-
"minimatch": "^
|
|
19
|
+
"debug": "^4.4.3",
|
|
20
|
+
"mime-types": "^3.0.2",
|
|
21
|
+
"minimatch": "^9.0.5",
|
|
22
22
|
"slash": "^3.0.0",
|
|
23
23
|
"xml2js": "^0.6.2"
|
|
24
|
-
}
|
|
25
|
-
"gitHead": "74e32bf47242909f040eb6012dda56e5c5a668a0"
|
|
24
|
+
}
|
|
26
25
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2023 郑州渺漠信息科技有限公司
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, destribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|