diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index ddf5c3e04a..20d9d8c8a9 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -163,6 +163,29 @@ _.extend(Meteor.Collection.prototype, { }); +// protect against dangerous selectors. falsey and {_id: falsey} are both +// likely programmer error, and not what you want, particularly for destructive +// operations. JS regexps don't serialize over DDP but can be trivially +// replaced by $regex. +Meteor.Collection._rewriteSelector = function (selector) { + // shorthand -- scalars match _id + if (LocalCollection._selectorIsId(selector)) + selector = {_id: selector}; + + if (!selector || (('_id' in selector) && !selector._id)) + // can't match anything + return {_id: Meteor.uuid()}; + + var ret = {}; + _.each(selector, function (value, key) { + if (value instanceof RegExp) + ret[key] = {$regex: value.source}; + else + ret[key] = value; + }); + return ret; +}; + // 'insert' immediately returns the inserted document's new _id. The // others return nothing. // @@ -217,6 +240,8 @@ _.each(["insert", "update", "remove"], function (name) { if ('_id' in args[0]) throw new Error("Do not pass an _id to insert. Meteor will generate the _id for you."); ret = args[0]._id = Meteor.uuid(); + } else { + args[0] = Meteor.Collection._rewriteSelector(args[0]); } if (self._manager && self._manager !== Meteor.default_server) { diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index de112880b5..07a1ecb1b2 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -42,21 +42,6 @@ _Mongo = function (url) { }); }; -// protect against dangerous selectors. falsey and {_id: falsey} -// are both likely programmer error, and not what you want, -// particularly for destructive operations. -_Mongo._rewriteSelector = function (selector) { - // shorthand -- scalars match _id - if ((typeof selector === 'string') || (typeof selector === 'number')) - selector = {_id: selector}; - - if (!selector || (('_id' in selector) && !selector._id)) - // can't match anything - return {_id: Meteor.uuid()}; - else - return selector; -}; - // callback: lambda (err, collection) called when // collection is ready to go, or on error. _Mongo.prototype._withCollection = function(collection_name, callback) { @@ -145,9 +130,6 @@ _Mongo.prototype.remove = function (collection_name, selector) { var write = self._maybeBeginWrite(); - // XXX does not allow options. matches the client. - selector = _Mongo._rewriteSelector(selector); - var future = new Future; self._withCollection(collection_name, function (err, collection) { if (err) { @@ -179,7 +161,6 @@ _Mongo.prototype.update = function (collection_name, selector, mod, options) { var write = self._maybeBeginWrite(); - selector = _Mongo._rewriteSelector(selector); if (!options) options = {}; var future = new Future; @@ -279,7 +260,7 @@ _Mongo.prototype._ensureIndex = function (collectionName, index, options) { var CursorDescription = function (collectionName, selector, options) { var self = this; self.collectionName = collectionName; - self.selector = _Mongo._rewriteSelector(selector); + self.selector = Meteor.Collection._rewriteSelector(selector); self.options = options || {}; }; diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 0189582920..d1606b077f 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -514,3 +514,51 @@ if (Meteor.isServer) { onComplete(); }); } + +if (Meteor.isServer) { + Meteor.methods({ + createInsecureCollection: function (name) { + var c = new Meteor.Collection(name); + c._insecure = true; + Meteor.publish('c-' + name, function () { + return c.find(); + }); + } + }); +} + +testAsyncMulti('mongo-livedata - rewrite selector', [ + function (test, expect) { + var collectionName = Meteor.uuid(); + if (Meteor.isClient) { + Meteor.call('createInsecureCollection', collectionName); + Meteor.subscribe('c-' + collectionName); + } + + var coll = new Meteor.Collection(collectionName); + + var docId; + + var updateCallback = expect(function (err2) { + test.isFalse(err2); + + var doc = coll.findOne(docId); + test.isTrue(doc); + test.equal(doc.name, "foobar"); + test.equal(doc.value, 43); + }); + + coll.insert({name: 'foobar', value: 42}, expect(function (err1, id) { + test.isFalse(err1); + test.isTrue(id); + docId = id; + + var doc = coll.findOne(docId); + test.isTrue(doc); + test.equal(doc.name, "foobar"); + test.equal(doc.value, 42); + + coll.update({name: /o+b/}, {$inc: {value: 1}}, updateCallback); + })); + } +]);