Skip to content

Commit d9ee8e0

Browse files
authored
indexeddb: prevent collisions in find() (#8807)
1 parent 14a566f commit d9ee8e0

File tree

2 files changed

+160
-3
lines changed

2 files changed

+160
-3
lines changed

packages/node_modules/pouchdb-adapter-indexeddb/src/rewrite.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ var IDB_TRUE = Number.MIN_SAFE_INTEGER + 2;
77
// These are the same as below but without the global flag
88
// we want to use RegExp.test because it's really fast, but the global flag
99
// makes the regex const stateful (seriously) as it walked through all instances
10-
var TEST_KEY_INVALID = /^[^a-zA-Z_$]|[^a-zA-Z0-9_$]+/;
11-
var TEST_PATH_INVALID = /\\.|(^|\.)[^a-zA-Z_$]|[^a-zA-Z0-9_$.]+/;
10+
var TEST_KEY_INVALID = /^[^a-zA-Z$]|[^a-zA-Z0-9$]+/;
11+
var TEST_PATH_INVALID = /\\.|(^|\.)[^a-zA-Z$]|[^a-zA-Z0-9$.]+/;
1212
function needsSanitise(name, isPath) {
1313
if (isPath) {
1414
return TEST_PATH_INVALID.test(name);
@@ -27,9 +27,12 @@ function needsSanitise(name, isPath) {
2727
//
2828
// Very high level rules for valid JS names are:
2929
// - First character cannot start with a number
30-
// - Otherwise all characters must be be a-z, A-Z, 0-9, $ or _.
30+
// - Otherwise all characters must be be a-z, A-Z, 0-9, or $.
31+
// - Underscores (_) are encoded even though legal, to avoid collsions with
32+
// encoded illegal characters
3133
// - We allow . unless the name represents a single field, as that represents
3234
// a deep index path.
35+
// See: https://www.w3.org/TR/IndexedDB/#key-path-construct
3336
//
3437
// This is more aggressive than it needs to be, but also simpler.
3538
//

tests/find/test-suite-1/test.escaping.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,158 @@ describe('test.escaping.js', function () {
237237
res.docs.should.deep.equal([{ "_id": "doc", "foo_c46_bar": "a" }]);
238238
});
239239
});
240+
241+
it('#8808 handles escape patterns without collisions (with indexes)', function () {
242+
var db = context.db;
243+
var index1 = {
244+
"index": {
245+
"fields": [
246+
"foo/bar"
247+
]
248+
},
249+
"name": "foo-index-1",
250+
"type": "json"
251+
};
252+
var index2 = {
253+
"index": {
254+
"fields": [
255+
"foo_c47_bar"
256+
]
257+
},
258+
"name": "foo-index-2",
259+
"type": "json"
260+
};
261+
return db.bulkDocs([
262+
{_id: 'doc1', 'foo/bar': 'a'},
263+
{_id: 'doc2', 'foo_c47_bar': 'a'},
264+
]).then(function () {
265+
return db.createIndex(index1);
266+
}).then(function () {
267+
return db.createIndex(index2);
268+
}).then(function () {
269+
return db.find({
270+
selector: {'foo/bar': 'a'},
271+
fields: ['_id', 'foo/bar', 'foo_c47_bar']
272+
});
273+
}).then(function (res) {
274+
res.docs.should.deep.equal([{ _id: 'doc1', 'foo/bar': 'a' }]);
275+
}).then(function () {
276+
return db.find({
277+
selector: {'foo_c47_bar': 'a'},
278+
fields: ['_id', 'foo/bar', 'foo_c47_bar']
279+
});
280+
}).then(function (res) {
281+
res.docs.should.deep.equal([{ _id: 'doc2', foo_c47_bar: 'a' }]);
282+
});
283+
});
284+
285+
it('#8808 handles escape patterns without collisions (no indexes)', function () {
286+
var db = context.db;
287+
return db.bulkDocs([
288+
{_id: 'doc1', 'foo/bar': 'a'},
289+
{_id: 'doc2', 'foo_c47_bar': 'a'},
290+
]).then(function () {
291+
return db.find({
292+
selector: {'foo/bar': 'a'},
293+
fields: ['_id', 'foo/bar', 'foo_c47_bar']
294+
});
295+
}).then(function (res) {
296+
res.docs.should.deep.equal([{ _id: 'doc1', 'foo/bar': 'a' }]);
297+
}).then(function () {
298+
return db.find({
299+
selector: {'foo_c47_bar': 'a'},
300+
fields: ['_id', 'foo/bar', 'foo_c47_bar']
301+
});
302+
}).then(function (res) {
303+
res.docs.should.deep.equal([{ _id: 'doc2', foo_c47_bar: 'a' }]);
304+
});
305+
});
306+
307+
it('#8808 bulk docs id escaping collisions in same doc (with indexes)', function () {
308+
var db = context.db;
309+
var docs = [ { _id: 'doc', 'foo/bar': -1, foo_c47_bar: 2 } ];
310+
var index1 = {
311+
"index": {
312+
"fields": [
313+
"foo/bar"
314+
]
315+
},
316+
"name": "foo-index-1",
317+
"type": "json"
318+
};
319+
var index2 = {
320+
"index": {
321+
"fields": [
322+
"foo_c47_bar"
323+
]
324+
},
325+
"name": "foo-index-2",
326+
"type": "json"
327+
};
328+
return db.bulkDocs(docs).then(function (results) {
329+
results.should.have.length(1, 'results length did not match');
330+
results[0].ok.should.equal(true);
331+
}).then(function () {
332+
return db.allDocs({ include_docs: true });
333+
}).then(function (results) {
334+
results.rows.should.have.length(1, 'results length did not match');
335+
336+
results.rows[0].doc._id.should.equal('doc');
337+
results.rows[0].doc['foo/bar'].should.equal(-1);
338+
results.rows[0].doc['foo_c47_bar'].should.equal(2);
339+
}).then(function () {
340+
return db.createIndex(index1);
341+
}).then(function () {
342+
return db.createIndex(index2);
343+
}).then(function () {
344+
return db.find({ selector: {'foo/bar': {$gt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
345+
}).then(function (res) {
346+
res.docs.length.should.equal(0, 'foo/bar should not be greater than 0');
347+
}).then(function () {
348+
return db.find({ selector: {'foo/bar': {$lt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
349+
}).then(function (res) {
350+
res.docs.should.deep.equal([{ _id: 'doc', 'foo/bar': -1, foo_c47_bar: 2 }]);
351+
}).then(function () {
352+
return db.find({ selector: {'foo_c47_bar': {$lt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
353+
}).then(function (res) {
354+
res.docs.length.should.equal(0, 'foo_c47_bar should not be less than 0');
355+
}).then(function () {
356+
return db.find({ selector: {'foo_c47_bar': {$gt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
357+
}).then(function (res) {
358+
res.docs.should.deep.equal([{ _id: 'doc', 'foo/bar': -1, foo_c47_bar: 2 } ]);
359+
});
360+
});
361+
362+
it('#8808 bulk docs id escaping collisions in same doc (no indexes)', function () {
363+
var db = context.db;
364+
var docs = [ { _id: 'doc', 'foo/bar': -1, foo_c47_bar: 2 } ];
365+
return db.bulkDocs(docs).then(function (results) {
366+
results.should.have.length(1, 'results length did not match');
367+
results[0].ok.should.equal(true);
368+
}).then(function () {
369+
return db.allDocs({ include_docs: true });
370+
}).then(function (results) {
371+
results.rows.should.have.length(1, 'results length did not match');
372+
373+
results.rows[0].doc._id.should.equal('doc');
374+
results.rows[0].doc['foo/bar'].should.equal(-1);
375+
results.rows[0].doc['foo_c47_bar'].should.equal(2);
376+
}).then(function () {
377+
return db.find({ selector: {'foo/bar': {$gt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
378+
}).then(function (res) {
379+
res.docs.length.should.equal(0, 'foo/bar should not be greater than 0');
380+
}).then(function () {
381+
return db.find({ selector: {'foo/bar': {$lt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
382+
}).then(function (res) {
383+
res.docs.should.deep.equal([ { _id: 'doc', 'foo/bar': -1, foo_c47_bar: 2 } ]);
384+
}).then(function () {
385+
return db.find({ selector: {'foo_c47_bar': {$lt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
386+
}).then(function (res) {
387+
res.docs.length.should.equal(0, 'foo_c47_bar should not be less than 0');
388+
}).then(function () {
389+
return db.find({ selector: {'foo_c47_bar': {$gt: 0}}, fields: ['_id', 'foo/bar', 'foo_c47_bar'] });
390+
}).then(function (res) {
391+
res.docs.should.deep.equal([ { _id: 'doc', 'foo/bar': -1, foo_c47_bar: 2 } ]);
392+
});
393+
});
240394
});

0 commit comments

Comments
 (0)