backport #1782 to 3.x (#1785)

This commit is contained in:
Dimitris Halatsis 2019-10-28 16:42:13 +02:00 committed by Diana Lau
parent 6dc0abc2fb
commit b925e8ccf0
4 changed files with 85 additions and 2 deletions

View File

@ -26,6 +26,7 @@ exports.findIndexOf = findIndexOf;
exports.collectTargetIds = collectTargetIds; exports.collectTargetIds = collectTargetIds;
exports.idName = idName; exports.idName = idName;
exports.rankArrayElements = rankArrayElements; exports.rankArrayElements = rankArrayElements;
exports.escapeRegExp = escapeRegExp;
const g = require('strong-globalize')(); const g = require('strong-globalize')();
const traverse = require('traverse'); const traverse = require('traverse');
@ -315,6 +316,36 @@ function isProhibited(key, prohibitedKeys) {
return false; return false;
} }
/**
* Accept an operator key and return whether it is used for a regular expression query or not
* @param {string} operator
* @returns {boolean}
*/
function isRegExpOperator(operator) {
return ['like', 'nlike', 'ilike', 'nilike', 'regexp'].includes(operator);
}
/**
* Accept a RegExp string and make sure that any special characters for RegExp are escaped in case they
* create an invalid Regexp
* @param {string} str
* @returns {string}
*/
function escapeRegExp(str) {
assert.strictEqual(typeof str, 'string', 'String required for regexp escaping');
try {
new RegExp(str); // try to parse string as regexp
return str;
} catch (unused) {
console.warn(
'Auto-escaping invalid RegExp value %j supplied by the caller. ' +
'Please note this behavior may change in the future.',
str
);
return str.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&');
}
}
/** /**
* Sanitize the query object * Sanitize the query object
* @param query {object} The query object * @param query {object} The query object
@ -390,6 +421,10 @@ function sanitizeQuery(query, options) {
return x; return x;
} }
if (isRegExpOperator(this.key) && typeof x === 'string') { // we have regexp supporting operator and string to escape
return escapeRegExp(x);
}
return x; return x;
}); });

View File

@ -23,6 +23,7 @@ describe('basic-querying', function() {
birthday: {type: Date, index: true}, birthday: {type: Date, index: true},
role: {type: String, index: true}, role: {type: String, index: true},
order: {type: Number, index: true, sort: true}, order: {type: Number, index: true, sort: true},
tag: {type: String, index: true},
vip: {type: Boolean}, vip: {type: Boolean},
address: { address: {
street: String, street: String,
@ -591,6 +592,12 @@ describe('basic-querying', function() {
}); });
}); });
it('should sanitize invalid usage of like', async () => {
const users = await User.find({where: {tag: {like: '['}}});
users.should.have.length(1);
users[0].should.have.property('name', 'John Lennon');
});
it('should support "like" that is not satisfied', it('should support "like" that is not satisfied',
function(done) { function(done) {
User.find({where: {name: {like: 'Bob'}}}, User.find({where: {name: {like: 'Bob'}}},
@ -616,6 +623,11 @@ describe('basic-querying', function() {
done(); done();
}); });
}); });
it('should properly sanitize invalid ilike filter', async () => {
const users = await User.find({where: {name: {ilike: '['}}});
users.should.be.empty();
});
}); });
const itWhenNilikeSupported = connectorCapabilities.nilike !== false; const itWhenNilikeSupported = connectorCapabilities.nilike !== false;
@ -820,7 +832,7 @@ describe('basic-querying', function() {
sample({name: true}).expect(['name']); sample({name: true}).expect(['name']);
sample({name: false}).expect([ sample({name: false}).expect([
'id', 'seq', 'email', 'role', 'order', 'birthday', 'vip', 'address', 'friends', 'addressLoc', 'id', 'seq', 'email', 'role', 'order', 'birthday', 'vip', 'address', 'friends', 'addressLoc', 'tag',
]); ]);
sample({name: false, id: true}).expect(['id']); sample({name: false, id: true}).expect(['id']);
sample({id: true}).expect(['id']); sample({id: true}).expect(['id']);
@ -1363,6 +1375,7 @@ function seed(done) {
birthday: new Date('1980-12-08'), birthday: new Date('1980-12-08'),
order: 2, order: 2,
vip: true, vip: true,
tag: '[singer]',
address: { address: {
street: '123 A St', street: '123 A St',
city: 'San Jose', city: 'San Jose',

View File

@ -201,6 +201,7 @@ describe('Memory connector', function() {
birthday: {type: Date, index: true}, birthday: {type: Date, index: true},
role: {type: String, index: true}, role: {type: String, index: true},
order: {type: Number, index: true, sort: true}, order: {type: Number, index: true, sort: true},
tag: {type: String, index: true},
vip: {type: Boolean}, vip: {type: Boolean},
address: { address: {
street: String, street: String,
@ -229,6 +230,12 @@ describe('Memory connector', function() {
}); });
}); });
it('should properly sanitize like invalid query', async () => {
const users = await User.find({where: {tag: {like: '['}}});
users.should.have.length(1);
users[0].should.have.property('name', 'John Lennon');
});
it('should allow to find using like with regexp', function(done) { it('should allow to find using like with regexp', function(done) {
User.find({where: {name: {like: /.*St.*/}}}, function(err, posts) { User.find({where: {name: {like: /.*St.*/}}}, function(err, posts) {
should.not.exist(err); should.not.exist(err);
@ -253,6 +260,11 @@ describe('Memory connector', function() {
}); });
}); });
it('should sanitize nlike invalid query', async () => {
const users = await User.find({where: {name: {nlike: '['}}});
users.should.have.length(6);
});
it('should allow to find using nlike with regexp', function(done) { it('should allow to find using nlike with regexp', function(done) {
User.find({where: {name: {nlike: /.*St.*/}}}, function(err, posts) { User.find({where: {name: {nlike: /.*St.*/}}}, function(err, posts) {
should.not.exist(err); should.not.exist(err);
@ -513,7 +525,7 @@ describe('Memory connector', function() {
}); });
it('should support the regexp operator with regex strings', function(done) { it('should support the regexp operator with regex strings', function(done) {
User.find({where: {name: {regexp: '^J'}}}, function(err, users) { User.find({where: {name: {regexp: 'non$'}}}, function(err, users) {
should.not.exist(err); should.not.exist(err);
users.length.should.equal(1); users.length.should.equal(1);
users[0].name.should.equal('John Lennon'); users[0].name.should.equal('John Lennon');
@ -562,6 +574,7 @@ describe('Memory connector', function() {
role: 'lead', role: 'lead',
birthday: new Date('1980-12-08'), birthday: new Date('1980-12-08'),
vip: true, vip: true,
tag: '[singer]',
address: { address: {
street: '123 A St', street: '123 A St',
city: 'San Jose', city: 'San Jose',

View File

@ -115,6 +115,28 @@ describe('util.sanitizeQuery', function() {
sanitizeQuery(q2, {prohibitedKeys: ['secret']}); sanitizeQuery(q2, {prohibitedKeys: ['secret']});
q2.should.eql({and: [{}, {x: 1}]}); q2.should.eql({and: [{}, {x: 1}]});
}); });
it('should allow proper structured regexp string', () => {
const q1 = {where: {name: {like: '^J'}}};
sanitizeQuery(q1).should.eql({where: {name: {like: '^J'}}});
});
it('should properly sanitize regexp string operators', () => {
const q1 = {where: {name: {like: '['}}};
sanitizeQuery(q1).should.eql({where: {name: {like: '\\['}}});
const q2 = {where: {name: {nlike: '['}}};
sanitizeQuery(q2).should.eql({where: {name: {nlike: '\\['}}});
const q3 = {where: {name: {ilike: '['}}};
sanitizeQuery(q3).should.eql({where: {name: {ilike: '\\['}}});
const q4 = {where: {name: {nilike: '['}}};
sanitizeQuery(q4).should.eql({where: {name: {nilike: '\\['}}});
const q5 = {where: {name: {regexp: '['}}};
sanitizeQuery(q5).should.eql({where: {name: {regexp: '\\['}}});
});
}); });
describe('util.parseSettings', function() { describe('util.parseSettings', function() {