parent
6dc0abc2fb
commit
b925e8ccf0
35
lib/utils.js
35
lib/utils.js
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue