ueberdb2 3.0.1 → 4.0.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/CHANGELOG.md +54 -3
- package/README.md +2 -10
- package/databases/redis_db.js +37 -69
- package/lib/CacheAndBufferLayer.js +12 -18
- package/package.json +2 -2
- package/test/test.js +0 -1
- package/test/test_getSub.js +31 -0
- package/test/test_setSub.js +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,49 @@
|
|
|
1
1
|
# Notable Changes
|
|
2
2
|
|
|
3
|
+
## v4.0.1
|
|
4
|
+
|
|
5
|
+
Security fix:
|
|
6
|
+
|
|
7
|
+
* `getSub()` now returns `null` when it encounters a non-"own" property
|
|
8
|
+
(including `__proto__`) or any non-object while walking the given property
|
|
9
|
+
path. This should make it easier to avoid accidental prototype pollution
|
|
10
|
+
vulnerabilities.
|
|
11
|
+
|
|
12
|
+
## v4.0.0
|
|
13
|
+
|
|
14
|
+
Compatibility changes:
|
|
15
|
+
|
|
16
|
+
* `redis`: The `socket` and `client_options` settings, deprecated since
|
|
17
|
+
v1.3.1, have been removed.
|
|
18
|
+
* `redis`: The client configuration object has changed with the new version of
|
|
19
|
+
the `redis` client library. See the [`redis` client library
|
|
20
|
+
documentation](https://github.com/redis/node-redis/blob/redis%404.1.0/docs/client-configuration.md)
|
|
21
|
+
for details.
|
|
22
|
+
|
|
23
|
+
Bug fixes:
|
|
24
|
+
|
|
25
|
+
* `redis`: Several `findKeys()` fixes.
|
|
26
|
+
|
|
27
|
+
Updated database dependencies:
|
|
28
|
+
|
|
29
|
+
* `redis`: Updated `redis` from 3.1.2 to 4.1.0.
|
|
30
|
+
|
|
31
|
+
## v3.0.2
|
|
32
|
+
|
|
33
|
+
Security fix:
|
|
34
|
+
|
|
35
|
+
* `getSub()` now returns `null` when it encounters a non-"own" property
|
|
36
|
+
(including `__proto__`) or any non-object while walking the given property
|
|
37
|
+
path. This should make it easier to avoid accidental prototype pollution
|
|
38
|
+
vulnerabilities.
|
|
39
|
+
|
|
3
40
|
## v3.0.1
|
|
4
41
|
|
|
5
42
|
Bug fixes:
|
|
6
43
|
|
|
7
44
|
* Fixed `findKeys()` calls containing special regular expression characters
|
|
8
|
-
|
|
45
|
+
(applicable to the database drivers that use the glob-to-regex helper
|
|
46
|
+
function).
|
|
9
47
|
|
|
10
48
|
## v3.0.0
|
|
11
49
|
|
|
@@ -17,8 +55,12 @@ Compatibility changes:
|
|
|
17
55
|
the `migrate_to_newer_schema` option to `true`.
|
|
18
56
|
* As mentioned in the v2.2.0 changes, passing callbacks to the database
|
|
19
57
|
methods is deprecated. Use the returned Promises instead.
|
|
20
|
-
* As mentioned in the v1.4.15 changes, `postgrespool` is
|
|
21
|
-
`postgres` instead.
|
|
58
|
+
* `postgrespool`: As mentioned in the v1.4.15 changes, `postgrespool` is
|
|
59
|
+
deprecated. Use `postgres` instead.
|
|
60
|
+
* `redis`: As mentioned in the v1.3.1 changes, the `socket` and
|
|
61
|
+
`client_options` settings are deprecated. Pass the [client options
|
|
62
|
+
object](https://www.npmjs.com/package/redis/v/3.1.2#options-object-properties)
|
|
63
|
+
directly.
|
|
22
64
|
|
|
23
65
|
Bug fixes:
|
|
24
66
|
|
|
@@ -33,6 +75,15 @@ Updated database dependencies:
|
|
|
33
75
|
* `postgres`: Updated `pg` to 8.7.3.
|
|
34
76
|
* `sqlite`: Updated `sqlite3` to 5.0.6.
|
|
35
77
|
|
|
78
|
+
## v2.2.4
|
|
79
|
+
|
|
80
|
+
Security fix:
|
|
81
|
+
|
|
82
|
+
* `getSub()` now returns `null` when it encounters a non-"own" property
|
|
83
|
+
(including `__proto__`) or any non-object while walking the given property
|
|
84
|
+
path. This should make it easier to avoid accidental prototype pollution
|
|
85
|
+
vulnerabilities.
|
|
86
|
+
|
|
36
87
|
## v2.2.0
|
|
37
88
|
|
|
38
89
|
Compatibility changes:
|
package/README.md
CHANGED
|
@@ -272,16 +272,8 @@ You should create your database as utf8mb4_bin.
|
|
|
272
272
|
If you enabled TLS on your Redis database (available since Redis 6.0) you will
|
|
273
273
|
need to change your connections parameters, here is an example:
|
|
274
274
|
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
{
|
|
278
|
-
host:
|
|
279
|
-
port: rediss://<redis_database_address>:<redis_database_port>
|
|
280
|
-
socket:
|
|
281
|
-
database:
|
|
282
|
-
password:
|
|
283
|
-
client_options
|
|
284
|
-
}
|
|
275
|
+
```javascript
|
|
276
|
+
const db = new ueberdb.Database('redis', {url: 'rediss://localhost'});
|
|
285
277
|
```
|
|
286
278
|
|
|
287
279
|
Do not provide a `host` value.
|
package/databases/redis_db.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
/* eslint new-cap: ["error", {"capIsNewExceptions": ["KEYS", "SMEMBERS"]}] */
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* 2011 Peter 'Pita' Martischka
|
|
@@ -18,111 +17,80 @@
|
|
|
18
17
|
*/
|
|
19
18
|
|
|
20
19
|
const AbstractDatabase = require('../lib/AbstractDatabase');
|
|
21
|
-
const async = require('async');
|
|
22
20
|
const redis = require('redis');
|
|
23
21
|
|
|
24
22
|
exports.Database = class extends AbstractDatabase {
|
|
25
23
|
constructor(settings) {
|
|
26
24
|
super();
|
|
27
|
-
this.
|
|
25
|
+
this._client = null;
|
|
28
26
|
this.settings = settings || {};
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
if (!this.settings.password) return callback();
|
|
33
|
-
this.client.auth(this.settings.password, callback);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
select(callback) {
|
|
37
|
-
if (!this.settings.database) return callback();
|
|
38
|
-
this.client.select(this.settings.database, callback);
|
|
39
|
-
}
|
|
29
|
+
get isAsync() { return true; }
|
|
40
30
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.settings.client_options);
|
|
46
|
-
} else {
|
|
47
|
-
// Deprecated, but kept for backwards compatibility.
|
|
48
|
-
this.client = redis.createClient(this.settings.port,
|
|
49
|
-
this.settings.host, this.settings.client_options);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
this.client.database = this.settings.database;
|
|
53
|
-
async.waterfall([this.auth.bind(this), this.select.bind(this)], callback);
|
|
31
|
+
async init() {
|
|
32
|
+
this._client = redis.createClient(this.settings);
|
|
33
|
+
await this._client.connect();
|
|
34
|
+
await this._client.ping();
|
|
54
35
|
}
|
|
55
36
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.client = redis.createClient(this.settings);
|
|
59
|
-
callback();
|
|
37
|
+
async get(key) {
|
|
38
|
+
return await this._client.get(key);
|
|
60
39
|
}
|
|
61
40
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} else if (notKey === '*:*:*') {
|
|
73
|
-
// restrict key to format "text:*"
|
|
74
|
-
const matches = /^([^:]+):\*$/.exec(key);
|
|
75
|
-
if (matches) {
|
|
76
|
-
this.client.SMEMBERS(`ueberDB:keys:${matches[1]}`, callback);
|
|
77
|
-
} else {
|
|
78
|
-
const msg = 'redis db only supports key patterns like pad:* when notKey is set to *:*:*';
|
|
79
|
-
callback(new Error(msg), null);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
callback(new Error('redis db currently only supports *:*:* as notKey'), null);
|
|
41
|
+
async findKeys(key, notKey) {
|
|
42
|
+
const [type] = /^([^:*]+):\*$/.exec(key) || [];
|
|
43
|
+
if (type != null && ['*:*:*', `${key}:*`].includes(notKey)) {
|
|
44
|
+
// Performance optimization for a common Etherpad case.
|
|
45
|
+
return await this._client.sMembers(`ueberDB:keys:${type}`);
|
|
46
|
+
}
|
|
47
|
+
let keys = await this._client.keys(key.replace(/[?[\]\\]/g, '\\$&'));
|
|
48
|
+
if (notKey != null) {
|
|
49
|
+
const regex = this.createFindRegex(key, notKey);
|
|
50
|
+
keys = keys.filter((k) => regex.test(k));
|
|
83
51
|
}
|
|
52
|
+
return keys;
|
|
84
53
|
}
|
|
85
54
|
|
|
86
|
-
set(key, value
|
|
55
|
+
async set(key, value) {
|
|
87
56
|
const matches = /^([^:]+):([^:]+)$/.exec(key);
|
|
88
|
-
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
57
|
+
await Promise.all([
|
|
58
|
+
matches && this._client.sAdd(`ueberDB:keys:${matches[1]}`, matches[0]),
|
|
59
|
+
this._client.set(key, value),
|
|
60
|
+
]);
|
|
92
61
|
}
|
|
93
62
|
|
|
94
|
-
remove(key
|
|
63
|
+
async remove(key) {
|
|
95
64
|
const matches = /^([^:]+):([^:]+)$/.exec(key);
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
65
|
+
await Promise.all([
|
|
66
|
+
matches && this._client.sRem(`ueberDB:keys:${matches[1]}`, matches[0]),
|
|
67
|
+
this._client.del(key),
|
|
68
|
+
]);
|
|
100
69
|
}
|
|
101
70
|
|
|
102
|
-
doBulk(bulk
|
|
103
|
-
const multi = this.
|
|
71
|
+
async doBulk(bulk) {
|
|
72
|
+
const multi = this._client.multi();
|
|
104
73
|
|
|
105
74
|
for (const {key, type, value} of bulk) {
|
|
106
75
|
const matches = /^([^:]+):([^:]+)$/.exec(key);
|
|
107
76
|
if (type === 'set') {
|
|
108
77
|
if (matches) {
|
|
109
|
-
multi.
|
|
78
|
+
multi.sAdd(`ueberDB:keys:${matches[1]}`, matches[0]);
|
|
110
79
|
}
|
|
111
80
|
multi.set(key, value);
|
|
112
81
|
} else if (type === 'remove') {
|
|
113
82
|
if (matches) {
|
|
114
|
-
multi.
|
|
83
|
+
multi.sRem(`ueberDB:keys:${matches[1]}`, matches[0]);
|
|
115
84
|
}
|
|
116
85
|
multi.del(key);
|
|
117
86
|
}
|
|
118
87
|
}
|
|
119
88
|
|
|
120
|
-
multi.exec(
|
|
89
|
+
await multi.exec();
|
|
121
90
|
}
|
|
122
91
|
|
|
123
|
-
close(
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
});
|
|
92
|
+
async close() {
|
|
93
|
+
await this._client.quit();
|
|
94
|
+
this._client = null;
|
|
127
95
|
}
|
|
128
96
|
};
|
|
@@ -508,33 +508,27 @@ exports.Database = class {
|
|
|
508
508
|
* "bla"]
|
|
509
509
|
*/
|
|
510
510
|
async getSub(key, sub) {
|
|
511
|
-
let subvalue;
|
|
512
511
|
await this._lock(key);
|
|
513
512
|
try {
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (subvalue != null && subvalue[sub[i]] !== undefined) {
|
|
523
|
-
subvalue = subvalue[sub[i]];
|
|
524
|
-
} else {
|
|
525
|
-
// the subvalue doesn't exist, break the loop and return null
|
|
526
|
-
subvalue = null;
|
|
527
|
-
break;
|
|
513
|
+
let v = await this._getLocked(key);
|
|
514
|
+
for (const k of sub) {
|
|
515
|
+
if (typeof v !== 'object' || (v != null && !Object.prototype.hasOwnProperty.call(v, k)) ||
|
|
516
|
+
// __proto__ is not an "own" property but we check for it explicitly for added safety,
|
|
517
|
+
// to improve readability, and to help static code analysis tools rule out prototype
|
|
518
|
+
// pollution vulnerabilities.
|
|
519
|
+
k === '__proto__') {
|
|
520
|
+
v = null;
|
|
528
521
|
}
|
|
522
|
+
if (v == null) break;
|
|
523
|
+
v = v[k];
|
|
529
524
|
}
|
|
530
|
-
|
|
531
525
|
if (this.logger.isDebugEnabled()) {
|
|
532
|
-
this.logger.debug(`GETSUB - ${key}${JSON.stringify(sub)} - ${JSON.stringify(
|
|
526
|
+
this.logger.debug(`GETSUB - ${key}${JSON.stringify(sub)} - ${JSON.stringify(v)}`);
|
|
533
527
|
}
|
|
528
|
+
return clone(v);
|
|
534
529
|
} finally {
|
|
535
530
|
this._unlock(key);
|
|
536
531
|
}
|
|
537
|
-
return clone(subvalue);
|
|
538
532
|
}
|
|
539
533
|
|
|
540
534
|
/**
|
package/package.json
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"mysql": "2.18.1",
|
|
31
31
|
"nano": "^10.0.0",
|
|
32
32
|
"pg": "^8.7.3",
|
|
33
|
-
"redis": "^
|
|
33
|
+
"redis": "^4.1.0",
|
|
34
34
|
"rethinkdb": "^2.4.2",
|
|
35
35
|
"simple-git": "^3.7.1"
|
|
36
36
|
},
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"url": "https://github.com/ether/ueberDB.git"
|
|
52
52
|
},
|
|
53
53
|
"main": "./index",
|
|
54
|
-
"version": "
|
|
54
|
+
"version": "4.0.1",
|
|
55
55
|
"bugs": {
|
|
56
56
|
"url": "https://github.com/ether/ueberDB/issues"
|
|
57
57
|
},
|
package/test/test.js
CHANGED
|
@@ -142,7 +142,6 @@ describe(__filename, function () {
|
|
|
142
142
|
|
|
143
143
|
it('findKeys with exclusion works', async function () {
|
|
144
144
|
if (database === 'mongodb') this.skip(); // TODO: Fix mongodb.
|
|
145
|
-
if (database === 'redis') this.skip(); // TODO: Fix redis.
|
|
146
145
|
const key = new Randexp(/([a-z]\w{0,20})foo\1/).gen();
|
|
147
146
|
await Promise.all([
|
|
148
147
|
db.set(key, true),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert').strict;
|
|
4
|
+
const ueberdb = require('../index');
|
|
5
|
+
|
|
6
|
+
describe(__filename, function () {
|
|
7
|
+
let db;
|
|
8
|
+
|
|
9
|
+
beforeEach(async function () {
|
|
10
|
+
db = new ueberdb.Database('memory', {}, {});
|
|
11
|
+
await db.init();
|
|
12
|
+
await db.set('k', {s: 'v'});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async function () {
|
|
16
|
+
if (db != null) await db.close();
|
|
17
|
+
db = null;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('getSub stops at non-objects', async function () {
|
|
21
|
+
assert(await db.getSub('k', ['s', 'length']) == null);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('getSub ignores non-own properties', async function () {
|
|
25
|
+
assert(await db.getSub('k', ['toString']) == null);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('getSub ignores __proto__', async function () {
|
|
29
|
+
assert(await db.getSub('k', ['__proto__']) == null);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/test/test_setSub.js
CHANGED
|
@@ -4,9 +4,19 @@ const assert = require('assert').strict;
|
|
|
4
4
|
const ueberdb = require('../index');
|
|
5
5
|
|
|
6
6
|
describe(__filename, function () {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
let db;
|
|
8
|
+
|
|
9
|
+
beforeEach(async function () {
|
|
10
|
+
db = new ueberdb.Database('memory', {}, {});
|
|
9
11
|
await db.init();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(async function () {
|
|
15
|
+
if (db != null) await db.close();
|
|
16
|
+
db = null;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('setSub rejects __proto__', async function () {
|
|
10
20
|
await db.set('k', {});
|
|
11
21
|
await assert.rejects(db.setSub('k', ['__proto__'], 'v'));
|
|
12
22
|
});
|