0

Here's the essence of some code (with associated data) for a Meteor application for counting things. Counts can be linked together so that one can increase another:

// The counting and linking code.
Meteor.methods({
    'counts.increment'(countId) {
        const count = Counts.findOne(countId);
        Counts.update(countId,{ $inc: { count: 1 } })
        Meteor.call('counts.check-links',count._id);
    },
    'counts.check-links'(countId){
        var count = Counts.findOne(countId);
        count.links.forEach(function (linkId) {
            updated = false;
            const link = Links.findOne(linkId);
            const primary = Counts.findOne(link.primary);
            const secondary = Counts.findOne(link.secondary);
            if (primary.count == link.level) {
                updated = true;
                Counts.update(secondary._id, {$inc: {count: 1}});
            }
            if (updated) {
                Meteor.call('counts.check-links',secondary._id);
            }
        })
    }
})

// Some data...
Count: {
  _id: 1,
  value: 0,
  name: A,
  links: [3]
}

Count: {
  _id: 2,
  value: 0,
  name: B,
  links: [4]
}

Link: {
  _id: 3,
  primary: 1,
  secondary: 2
}

Link: {
  _id: 4,
  primary: 2,
  secondary: 1
}

So, if Meteor.call('projects.increment',1) is called this code will crash as each count is linked to the other. Detecting such a setup could be rather difficult as there could be very complex arrangements of counts and the links can also decrease, set to zero, operate every N counts &c. &c. For the purposes of asking this question that shouldn't matter, though.

One possibility I thought of would be to add a counter within counts.check-links which would stop execution after an arbitrary number of loops, e.g. 5. Presumably, to guard against tampering the value of this counter would have to be stored in the database and acted upon via a Meteor method. It would need to be reset at the conclusion of any sequence of check-links calls.

I'm not sure if this is the best idea or if so how might be a good way to implement it, and so I would be interested to know if anyone has any suggestions.

knirirr
  • 1,860
  • 4
  • 23
  • 37
  • 1
    make sure to validate your server methods or its supereasy to fake this method calls or do some nosql injections. – Dude Apr 24 '17 at 10:07
  • The server only publishes data belonging to a logged-in user and the method calls also check that an operation is being performed on data the logged-in user owns, so that should hopefully cover it. – knirirr Apr 24 '17 at 10:10
  • I should have added that there's a check of all Meteor.method arguments in the actual application as well - I omitted that sort of thing from the examples here. – knirirr Apr 24 '17 at 20:43

1 Answers1

1

You could create a set of all objects ("Counts") already visited; so if you follow a link to such an object, you can avoid handling it again, thus running into the infinite recursion.

EDIT: Example I'm not familiar with meteor, so please forgive if it doesn't work as expected... This is a common problem for all programing languages that allow for pointers of object references, though, and the solution follows a similar pattern.

// The counting and linking code.
Meteor.methods({
  ...
'counts.check-links'(countId, alreadyVisited){

    if (!alreadyVisited) alreadyVisited = {};
    if (alreadyVisited[countId]) return;
    alreadyVisited[countId] = true;

    var count = Counts.findOne(countId);
    count.links.forEach(function (linkId) {
        updated = false;
        const link = Links.findOne(linkId);
        const primary = Counts.findOne(link.primary);
        const secondary = Counts.findOne(link.secondary);
        if (primary.count == link.level) {
            updated = true;
            Counts.update(secondary._id, {$inc: {count: 1}});
        }
        if (updated) {
            Meteor.call('counts.check-links',secondary._id, alreadyVisited);
        }
    })
}
Erich Kitzmueller
  • 36,381
  • 5
  • 80
  • 102
  • Thanks, I'll give that a go. Presumably one should empty alreadyVisited in an else block at the end, for if nothing has been updated then there's no need to check for further links. – knirirr Apr 24 '17 at 12:24
  • Looks like that did the trick. Rather than created alreadyVisited in the method I stored it in the database so that I could apply some checks (e.g. logged in user), for safety reasons. Otherwise I've used what you've put there. – knirirr Apr 24 '17 at 13:35