@contrast/protect 1.53.1 → 1.54.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/package.json +15 -12
- package/lib/error-handlers/common-handler.test.js +0 -52
- package/lib/error-handlers/index.test.js +0 -32
- package/lib/error-handlers/init-domain.test.js +0 -22
- package/lib/error-handlers/install/express.test.js +0 -290
- package/lib/error-handlers/install/fastify.test.js +0 -130
- package/lib/error-handlers/install/hapi.test.js +0 -102
- package/lib/error-handlers/install/koa2.test.js +0 -83
- package/lib/error-handlers/install/restify.test.js +0 -57
- package/lib/get-source-context.test.js +0 -35
- package/lib/hardening/handlers.test.js +0 -89
- package/lib/hardening/index.test.js +0 -31
- package/lib/hardening/install/node-serialize0.test.js +0 -58
- package/lib/index.test.js +0 -53
- package/lib/input-analysis/handlers.test.js +0 -1604
- package/lib/input-analysis/index.test.js +0 -45
- package/lib/input-analysis/install/body-parser1.test.js +0 -134
- package/lib/input-analysis/install/busboy1.test.js +0 -81
- package/lib/input-analysis/install/cookie-parser1.test.js +0 -144
- package/lib/input-analysis/install/express.test.js +0 -241
- package/lib/input-analysis/install/fastify.test.js +0 -96
- package/lib/input-analysis/install/formidable1.test.js +0 -114
- package/lib/input-analysis/install/hapi.test.js +0 -292
- package/lib/input-analysis/install/http.test.js +0 -270
- package/lib/input-analysis/install/koa-body5.test.js +0 -92
- package/lib/input-analysis/install/koa-bodyparser4.test.js +0 -92
- package/lib/input-analysis/install/koa2.test.js +0 -259
- package/lib/input-analysis/install/multer1.test.js +0 -209
- package/lib/input-analysis/install/qs6.test.js +0 -79
- package/lib/input-analysis/install/restify.test.js +0 -98
- package/lib/input-analysis/install/universal-cookie4.test.js +0 -70
- package/lib/input-analysis/ip-analysis.test.js +0 -71
- package/lib/input-analysis/virtual-patches.test.js +0 -106
- package/lib/input-tracing/handlers/index.test.js +0 -1236
- package/lib/input-tracing/index.test.js +0 -62
- package/lib/input-tracing/install/child-process.test.js +0 -133
- package/lib/input-tracing/install/eval.test.js +0 -78
- package/lib/input-tracing/install/fs.test.js +0 -108
- package/lib/input-tracing/install/function.test.js +0 -81
- package/lib/input-tracing/install/http.test.js +0 -85
- package/lib/input-tracing/install/http2.test.js +0 -83
- package/lib/input-tracing/install/marsdb.test.js +0 -126
- package/lib/input-tracing/install/mongodb.test.js +0 -280
- package/lib/input-tracing/install/mssql.test.js +0 -81
- package/lib/input-tracing/install/mysql.test.js +0 -108
- package/lib/input-tracing/install/postgres.test.js +0 -117
- package/lib/input-tracing/install/sequelize.test.js +0 -78
- package/lib/input-tracing/install/spdy.test.js +0 -76
- package/lib/input-tracing/install/sqlite3.test.js +0 -88
- package/lib/input-tracing/install/vm.test.js +0 -176
- package/lib/make-response-blocker.test.js +0 -99
- package/lib/make-source-context.test.js +0 -219
- package/lib/policy.test.js +0 -446
- package/lib/semantic-analysis/handlers.test.js +0 -379
- package/lib/semantic-analysis/index.test.js +0 -38
- package/lib/semantic-analysis/install/libxmljs.test.js +0 -156
- package/lib/semantic-analysis/utils/xml-analysis.test.js +0 -156
- package/lib/throw-security-exception.test.js +0 -37
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const sinon = require('sinon');
|
|
4
|
-
const { expect } = require('chai');
|
|
5
|
-
const scopes = require('@contrast/scopes');
|
|
6
|
-
const patcher = require('@contrast/patcher');
|
|
7
|
-
const mocks = require('@contrast/test/mocks');
|
|
8
|
-
const SecurityException = require('../../security-exception');
|
|
9
|
-
|
|
10
|
-
describe('protect error-handlers koa2', function () {
|
|
11
|
-
let core,
|
|
12
|
-
errorHandler,
|
|
13
|
-
Koa,
|
|
14
|
-
onerror,
|
|
15
|
-
store,
|
|
16
|
-
ctx;
|
|
17
|
-
|
|
18
|
-
beforeEach(function () {
|
|
19
|
-
core = mocks.core();
|
|
20
|
-
core.config = mocks.config();
|
|
21
|
-
core.logger = mocks.logger();
|
|
22
|
-
core.scopes = scopes(core);
|
|
23
|
-
core.protect = mocks.protect();
|
|
24
|
-
require('../../get-source-context')(core);
|
|
25
|
-
core.depHooks = mocks.depHooks();
|
|
26
|
-
core.patcher = patcher(core);
|
|
27
|
-
|
|
28
|
-
store = {
|
|
29
|
-
protect: {
|
|
30
|
-
block: sinon.stub(),
|
|
31
|
-
securityException: ['block', 'cmd-injection']
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
Koa = function () { };
|
|
36
|
-
|
|
37
|
-
Koa.prototype.handleRequest = function () { };
|
|
38
|
-
|
|
39
|
-
core.depHooks.resolve.yields(Koa);
|
|
40
|
-
|
|
41
|
-
errorHandler = require('./koa2')(core);
|
|
42
|
-
errorHandler.install();
|
|
43
|
-
|
|
44
|
-
onerror = sinon.stub();
|
|
45
|
-
ctx = {
|
|
46
|
-
onerror,
|
|
47
|
-
};
|
|
48
|
-
Koa.prototype.handleRequest(ctx);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should block the request when there is SecurityException', function () {
|
|
52
|
-
const error = SecurityException.create();
|
|
53
|
-
|
|
54
|
-
core.scopes.sources.run(store, () => {
|
|
55
|
-
ctx.onerror(error);
|
|
56
|
-
expect(core.logger.info).not.to.have.been.called;
|
|
57
|
-
expect(store.protect.block).to.have.been.calledWith('block', 'cmd-injection');
|
|
58
|
-
expect(ctx.body).to.equal('');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should not block the request when there is SecurityException but sourceContext is missing', function () {
|
|
63
|
-
const error = SecurityException.create();
|
|
64
|
-
|
|
65
|
-
core.scopes.sources.run({}, () => {
|
|
66
|
-
ctx.onerror(error);
|
|
67
|
-
expect(core.logger.info).to.have.been.calledWith(
|
|
68
|
-
{ funcKey: 'protect-error-handling:koa.ctx.onerror' },
|
|
69
|
-
'source context not found; unable to handle response'
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should skip the instrumentation when there is no SecurityException', function () {
|
|
75
|
-
const error = new Error('Error');
|
|
76
|
-
|
|
77
|
-
core.scopes.sources.run({}, () => {
|
|
78
|
-
ctx.onerror(error);
|
|
79
|
-
expect(core.logger.info).not.to.have.been.called;
|
|
80
|
-
expect(store.protect.block).not.to.have.been.called;
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { expect } = require('chai');
|
|
4
|
-
const sinon = require('sinon');
|
|
5
|
-
const { initProtectFixture } = require('@contrast/test/fixtures');
|
|
6
|
-
const SecurityException = require('../../security-exception');
|
|
7
|
-
|
|
8
|
-
describe('protect error-handlers restify v8+', function () {
|
|
9
|
-
let core, simulateRequestScope, Server, store;
|
|
10
|
-
|
|
11
|
-
beforeEach(function () {
|
|
12
|
-
({ core, simulateRequestScope } = initProtectFixture());
|
|
13
|
-
|
|
14
|
-
Server = { prototype: { _onHandlerError: sinon.stub().returnsArg(0) } };
|
|
15
|
-
store = { protect: { block: sinon.stub(), securityException: ['block'] } };
|
|
16
|
-
|
|
17
|
-
core.depHooks.resolve
|
|
18
|
-
.withArgs({ name: 'restify', file: 'lib/server.js', version: '>=8 <12' })
|
|
19
|
-
.yields(Server);
|
|
20
|
-
|
|
21
|
-
core.protect.errorHandlers.restifyErrorHandler.install();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('blocks the request when a security exception is handled', function () {
|
|
25
|
-
const err = SecurityException.create();
|
|
26
|
-
|
|
27
|
-
simulateRequestScope(() => {
|
|
28
|
-
Server.prototype._onHandlerError(err);
|
|
29
|
-
|
|
30
|
-
expect(store.protect.block).to.have.been.calledWith(...store.protect.securityException);
|
|
31
|
-
}, store);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('does not block the request when context is missing', function () {
|
|
35
|
-
const err = SecurityException.create();
|
|
36
|
-
|
|
37
|
-
simulateRequestScope(() => {
|
|
38
|
-
Server.prototype._onHandlerError(err);
|
|
39
|
-
expect(store.protect.block).not.to.have.been.called;
|
|
40
|
-
expect(core.logger.info).to.have.been.calledWith(
|
|
41
|
-
sinon.match.object,
|
|
42
|
-
'source context not found; unable to handle response',
|
|
43
|
-
);
|
|
44
|
-
}, {});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('does not handle other errors', function () {
|
|
48
|
-
const err = new Error();
|
|
49
|
-
|
|
50
|
-
simulateRequestScope(() => {
|
|
51
|
-
const result = Server.prototype._onHandlerError(err);
|
|
52
|
-
expect(store.protect.block).not.to.have.been.called;
|
|
53
|
-
expect(core.logger.info).not.to.have.been.called;
|
|
54
|
-
expect(result).to.equal(err);
|
|
55
|
-
}, store);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { expect } = require('chai');
|
|
4
|
-
const { initProtectFixture } = require('@contrast/test/fixtures');
|
|
5
|
-
|
|
6
|
-
describe('protect get-source-context', function () {
|
|
7
|
-
let core, getSourceContext;
|
|
8
|
-
|
|
9
|
-
beforeEach(function () {
|
|
10
|
-
({ core } = initProtectFixture());
|
|
11
|
-
({ getSourceContext } = core.protect);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('returns the sourceContext when there is `allowed` property in it', function () {
|
|
15
|
-
core.scopes.sources.run({ protect: { test: 'store' } }, () => {
|
|
16
|
-
const result = getSourceContext();
|
|
17
|
-
expect(result).to.deep.equal({ test: 'store' });
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('logs a debug log when the sourceContext is missing and returns null', function () {
|
|
22
|
-
core.scopes.sources.run({ protect: null }, () => {
|
|
23
|
-
const result = getSourceContext();
|
|
24
|
-
expect(result).to.null;
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('returns null when the protect store has a thruthy `allowed` property', function () {
|
|
29
|
-
core.scopes.sources.run({ protect: { allowed: true } }, () => {
|
|
30
|
-
const result = getSourceContext();
|
|
31
|
-
expect(core.logger.debug).not.to.have.been.called;
|
|
32
|
-
expect(result).to.null;
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { expect } = require('chai');
|
|
4
|
-
const mocks = require('@contrast/test/mocks');
|
|
5
|
-
const { create: createSecurityException } = require('../security-exception');
|
|
6
|
-
const handlersFactory = require('./handlers');
|
|
7
|
-
|
|
8
|
-
describe('protect hardening handlers', function () {
|
|
9
|
-
let core, handlers, map, sourceContext;
|
|
10
|
-
|
|
11
|
-
const exception = createSecurityException();
|
|
12
|
-
const sinkContext = {
|
|
13
|
-
name: 'node-serialize.unserialize',
|
|
14
|
-
value: '"foo":"_$$ND_FUNC$$_while(true)',
|
|
15
|
-
get stack() {
|
|
16
|
-
return [{
|
|
17
|
-
file: 'index.js',
|
|
18
|
-
lineNumber: 123,
|
|
19
|
-
method: '',
|
|
20
|
-
type: 'Function',
|
|
21
|
-
}];
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
function makePolicy(mode) {
|
|
26
|
-
return {
|
|
27
|
-
'untrusted-deserialization': mode,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
beforeEach(function () {
|
|
32
|
-
map = {};
|
|
33
|
-
sourceContext = {
|
|
34
|
-
resultsMap: map,
|
|
35
|
-
};
|
|
36
|
-
core = mocks.core();
|
|
37
|
-
core.protect = mocks.protect();
|
|
38
|
-
core.protect.hardening = {};
|
|
39
|
-
core.protect.throwSecurityException.throws(exception);
|
|
40
|
-
handlers = handlersFactory(core);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
['block', 'block_at_perimeter'].forEach((mode) => {
|
|
44
|
-
it(`${mode}`, function () {
|
|
45
|
-
sourceContext.policy = makePolicy(mode);
|
|
46
|
-
|
|
47
|
-
expect(function () {
|
|
48
|
-
handlers.handleUntrustedDeserialization(sourceContext, sinkContext);
|
|
49
|
-
}).to.throw(exception.constructor);
|
|
50
|
-
|
|
51
|
-
expect(map).to.have.deep.property('untrusted-deserialization', [{
|
|
52
|
-
blocked: true,
|
|
53
|
-
exploitMetadata: [{ deserializer: 'node-serialize.unserialize', command: false }],
|
|
54
|
-
sinkContext,
|
|
55
|
-
value: sinkContext.value,
|
|
56
|
-
}]);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('monitor', function () {
|
|
61
|
-
sourceContext.policy = makePolicy('monitor');
|
|
62
|
-
|
|
63
|
-
handlers.handleUntrustedDeserialization(sourceContext, sinkContext);
|
|
64
|
-
|
|
65
|
-
expect(map).to.have.deep.property('untrusted-deserialization', [{
|
|
66
|
-
blocked: false,
|
|
67
|
-
exploitMetadata: [{ deserializer: 'node-serialize.unserialize', command: false }],
|
|
68
|
-
sinkContext,
|
|
69
|
-
value: sinkContext.value,
|
|
70
|
-
}]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('off', function () {
|
|
74
|
-
sourceContext.policy = makePolicy('off');
|
|
75
|
-
|
|
76
|
-
handlers.handleUntrustedDeserialization(sourceContext, sinkContext);
|
|
77
|
-
|
|
78
|
-
expect(map).not.to.have.deep.property('untrusted-deserialization');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('returns if the result value type is incorrect', function () {
|
|
82
|
-
sourceContext.policy = makePolicy('monitor');
|
|
83
|
-
sinkContext.value = true;
|
|
84
|
-
|
|
85
|
-
handlers.handleUntrustedDeserialization(sourceContext, sinkContext);
|
|
86
|
-
|
|
87
|
-
expect(map).not.to.have.deep.property('untrusted-deserialization');
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const sinon = require('sinon');
|
|
4
|
-
const { expect } = require('chai');
|
|
5
|
-
const proxyquire = require('proxyquire');
|
|
6
|
-
const mocks = require('@contrast/test/mocks');
|
|
7
|
-
|
|
8
|
-
describe('protect hardening', function () {
|
|
9
|
-
let modulesMock, installStub, core, hardening;
|
|
10
|
-
beforeEach(function () {
|
|
11
|
-
installStub = sinon.stub();
|
|
12
|
-
modulesMock = (propertyName) => function (core) {
|
|
13
|
-
if (propertyName) {
|
|
14
|
-
core.protect.hardening[propertyName] = { install: installStub };
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
core = mocks.core();
|
|
18
|
-
core.protect = mocks.protect();
|
|
19
|
-
|
|
20
|
-
hardening = proxyquire('.', {
|
|
21
|
-
'./handlers': modulesMock,
|
|
22
|
-
'./install/node-serialize0': modulesMock('nodeSerialize0Instrumentation'),
|
|
23
|
-
// Total of 2 modules required - 1 handlers and 1 module to be installed
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('calls the install method on nodeSerialize0Instrumentation', function () {
|
|
28
|
-
hardening(core).install();
|
|
29
|
-
expect(installStub).to.have.been.calledOnce;
|
|
30
|
-
});
|
|
31
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { expect } = require('chai');
|
|
4
|
-
const sinon = require('sinon');
|
|
5
|
-
const patcher = require('@contrast/patcher');
|
|
6
|
-
const scopes = require('@contrast/scopes');
|
|
7
|
-
const mocks = require('@contrast/test/mocks');
|
|
8
|
-
const instrumentationFactory = require('./node-serialize0');
|
|
9
|
-
|
|
10
|
-
describe('protect hardening node-serialize0', function () {
|
|
11
|
-
let core, mockLib, hardening;
|
|
12
|
-
|
|
13
|
-
beforeEach(function () {
|
|
14
|
-
mockLib = {
|
|
15
|
-
unserialize() { }
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
core = mocks.core();
|
|
19
|
-
core.logger = mocks.logger();
|
|
20
|
-
core.depHooks = mocks.depHooks();
|
|
21
|
-
core.depHooks.resolve.yields(mockLib);
|
|
22
|
-
core.patcher = patcher(core);
|
|
23
|
-
core.scopes = scopes(core);
|
|
24
|
-
core.protect = mocks.protect();
|
|
25
|
-
require('../../get-source-context')(core);
|
|
26
|
-
hardening = core.protect.hardening;
|
|
27
|
-
|
|
28
|
-
instrumentationFactory(core).install();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('calls hardening handler when called in sources scope', function () {
|
|
32
|
-
const value = '"foo":"_$$ND_FUNC$$_while(true)';
|
|
33
|
-
const sourceContext = {};
|
|
34
|
-
const store = { protect: sourceContext };
|
|
35
|
-
const sinkContext = {
|
|
36
|
-
name: 'node-serialize.unserialize',
|
|
37
|
-
value,
|
|
38
|
-
stacktraceOpts: { constructorOpt: mockLib.unserialize, prependFrames: [sinon.match.func] }
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
core.scopes.sources.run(store, function () {
|
|
42
|
-
mockLib.unserialize(value);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
expect(hardening.handleUntrustedDeserialization).to.have.been.calledWith(
|
|
46
|
-
sourceContext,
|
|
47
|
-
sinkContext
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('hardening handler is not called when out of sources scope', function () {
|
|
52
|
-
const value = '"foo":"_$$ND_FUNC$$_while(true)';
|
|
53
|
-
|
|
54
|
-
mockLib.unserialize(value);
|
|
55
|
-
|
|
56
|
-
expect(hardening.handleUntrustedDeserialization).not.to.have.been.called;
|
|
57
|
-
});
|
|
58
|
-
});
|
package/lib/index.test.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { expect } = require('chai');
|
|
4
|
-
const proxyquire = require('proxyquire');
|
|
5
|
-
const mocks = require('@contrast/test/mocks');
|
|
6
|
-
|
|
7
|
-
const moduleMock = (moduleName, mock) => (deps) => {
|
|
8
|
-
deps.protect[moduleName] = mock[moduleName];
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
describe('protect', function () {
|
|
12
|
-
let core, protectMock, protectModule, modulesWithInstall;
|
|
13
|
-
|
|
14
|
-
beforeEach(function () {
|
|
15
|
-
protectMock = mocks.protect();
|
|
16
|
-
core = mocks.core();
|
|
17
|
-
core.logger = mocks.logger();
|
|
18
|
-
core.config = mocks.config();
|
|
19
|
-
core.scopes = mocks.scopes();
|
|
20
|
-
core.config.protect.rules = {
|
|
21
|
-
['nosql-injection']: { mode: 'monitor' },
|
|
22
|
-
['cmd-injection']: { mode: 'block' },
|
|
23
|
-
['path-traversal']: { mode: 'off' }
|
|
24
|
-
};
|
|
25
|
-
core.config.protect.rules.disabled_rules = ['sql-injection'];
|
|
26
|
-
core.rewriter = mocks.rewriter();
|
|
27
|
-
protectModule = proxyquire('.', {
|
|
28
|
-
'./input-analysis': moduleMock('inputAnalysis', protectMock),
|
|
29
|
-
'./input-tracing': moduleMock('inputTracing', protectMock),
|
|
30
|
-
'./semantic-analysis': moduleMock('semanticAnalysis', protectMock),
|
|
31
|
-
'./hardening': moduleMock('hardening', protectMock),
|
|
32
|
-
'./error-handlers': moduleMock('errorHandlers', protectMock),
|
|
33
|
-
});
|
|
34
|
-
modulesWithInstall = ['inputAnalysis', 'inputTracing', 'hardening', 'errorHandlers'];
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('installs components and initializes policy', function () {
|
|
38
|
-
core.config.getEffectiveValue.withArgs('protect.enable').returns(true);
|
|
39
|
-
protectModule(core).install();
|
|
40
|
-
|
|
41
|
-
modulesWithInstall.forEach((module) => {
|
|
42
|
-
expect(protectMock[module].install).to.have.been.calledOnce;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
expect(core.protect.getPolicy()).to.deep.include({
|
|
46
|
-
rulesMask: 48,
|
|
47
|
-
'cmd-injection': 'block',
|
|
48
|
-
'nosql-injection': 'monitor',
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
expect(core.rewriter.install).to.have.been.calledWith('protect');
|
|
52
|
-
});
|
|
53
|
-
});
|