diff --git a/lib/rawObject.js b/lib/rawObject.js new file mode 100644 index 0000000000..26376fc360 --- /dev/null +++ b/lib/rawObject.js @@ -0,0 +1,8 @@ +'use strict'; + +// Using a predefined object with this prototype is faster than calling `Object.create(null)` directly +// This is needed to make sure `__proto__` and similar reserved words can be used +function RawObject () {} +RawObject.prototype = Object.create(null); + +module.exports = RawObject; diff --git a/lib/utils.js b/lib/utils.js index 61e9a64f75..4093b61dcb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,7 @@ 'use strict'; +var RawObject = require('./rawObject'); + // hgetall converts its replies to an Object. If the reply is empty, null is returned. // These function are only called with internal data and have therefor always the same instanceof X function replyToObject (reply) { @@ -7,7 +9,7 @@ function replyToObject (reply) { if (reply.length === 0 || !(reply instanceof Array)) { return null; } - var obj = {}; + var obj = new RawObject(); for (var i = 0; i < reply.length; i += 2) { obj[reply[i].toString('binary')] = reply[i + 1]; } diff --git a/test/commands/hgetall.spec.js b/test/commands/hgetall.spec.js index d345b13c92..55a0e24780 100644 --- a/test/commands/hgetall.spec.js +++ b/test/commands/hgetall.spec.js @@ -22,10 +22,10 @@ describe("The 'hgetall' method", function () { }); it('handles simple keys and values', function (done) { - client.hmset(['hosts', 'mjr', '1', 'another', '23', 'home', '1234'], helper.isString('OK')); + client.hmset(['hosts', '__proto__', '1', 'another', '23', 'home', '1234'], helper.isString('OK')); client.HGETALL(['hosts'], function (err, obj) { assert.strictEqual(3, Object.keys(obj).length); - assert.strictEqual('1', obj.mjr.toString()); + assert.strictEqual('1', obj.__proto__.toString()); // eslint-disable-line no-proto assert.strictEqual('23', obj.another.toString()); assert.strictEqual('1234', obj.home.toString()); return done(err); diff --git a/test/pubsub.spec.js b/test/pubsub.spec.js index b24c1899dc..9cb388b03e 100644 --- a/test/pubsub.spec.js +++ b/test/pubsub.spec.js @@ -206,49 +206,27 @@ describe('publish/subscribe', function () { sub.subscribe(channel); }); - it('handles SUBSCRIBE_CLOSE_RESUBSCRIBE', function (done) { + it('subscribe; close; resubscribe with prototype inherited property names', function (done) { var count = 0; - /* Create two clients. c1 subscribes to two channels, c2 will publish to them. - c2 publishes the first message. - c1 gets the message and drops its connection. It must resubscribe itself. - When it resubscribes, c2 publishes the second message, on the same channel - c1 gets the message and drops its connection. It must resubscribe itself, again. - When it resubscribes, c2 publishes the third message, on the second channel - c1 gets the message and drops its connection. When it reconnects, the test ends. - */ + var channels = ['__proto__', 'channel 2']; + var msg = ['hi from channel __proto__', 'hi from channel 2']; + sub.on('message', function (channel, message) { - if (channel === 'chan1') { - assert.strictEqual(message, 'hi on channel 1'); - sub.stream.end(); - } else if (channel === 'chan2') { - assert.strictEqual(message, 'hi on channel 2'); - sub.stream.end(); - } else { - sub.quit(); - pub.quit(); - assert.fail('test failed'); - } + var n = Math.max(count - 1, 0); + assert.strictEqual(channel, channels[n]); + assert.strictEqual(message, msg[n]); + if (count === 2) return done(); + sub.stream.end(); }); - sub.subscribe('chan1', 'chan2'); + sub.subscribe(channels); sub.on('ready', function (err, results) { + pub.publish(channels[count], msg[count]); count++; - if (count === 1) { - pub.publish('chan1', 'hi on channel 1'); - return; - } else if (count === 2) { - pub.publish('chan2', 'hi on channel 2'); - } else { - sub.quit(function () { - pub.quit(function () { - return done(); - }); - }); - } }); - pub.publish('chan1', 'hi on channel 1'); + pub.publish(channels[count], msg[count]); }); });