From 3eeb7e61bd8d422a08bf36d94b48085be6553b59 Mon Sep 17 00:00:00 2001 From: Dan Dascalescu Date: Wed, 16 Apr 2014 18:23:18 -0700 Subject: [PATCH 001/189] Mark reason and error as optional in new Meteor.Error --- docs/client/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.js b/docs/client/api.js index 4bffbeb556..22a3e44450 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -404,7 +404,7 @@ Template.api.method_invocation_connection = { Template.api.error = { id: "meteor_error", - name: "new Meteor.Error(error, reason, details)", + name: "new Meteor.Error(error [, reason] [, details])", locus: "Anywhere", descr: ["This class represents a symbolic error thrown by a method."], args: [ From 64e02f2f56d1588d9daad09634d579eb61bf91ab Mon Sep 17 00:00:00 2001 From: Dan Dascalescu Date: Tue, 22 Apr 2014 19:37:15 -0700 Subject: [PATCH 002/189] Pass failure message for test.length() --- packages/tinytest/tinytest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 21498996a6..4193bba829 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -288,12 +288,12 @@ _.extend(TestCaseResults.prototype, { }, // XXX should change to lengthOf to match vowsjs - length: function (obj, expected_length) { + length: function (obj, expected_length, msg) { if (obj.length === expected_length) this.ok(); else this.fail({type: "length", expected: expected_length, - actual: obj.length}); + actual: obj.length, message: msg}); }, // EXPERIMENTAL way to compare two strings that results in From 7c813691f2b37221b46dfa72b0a15cd751998f3b Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Wed, 30 Apr 2014 11:55:45 -0700 Subject: [PATCH 003/189] Try to fix #2093 --- packages/minifiers/minifiers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/minifiers/minifiers.js b/packages/minifiers/minifiers.js index 3d4496c803..a73cfc306e 100644 --- a/packages/minifiers/minifiers.js +++ b/packages/minifiers/minifiers.js @@ -85,7 +85,7 @@ CssTools = { rewriteCssUrls: function (ast) { var isRelative = function(path) { - return path.charAt(0) !== '/'; + return path && path.charAt(0) !== '/'; }; _.each(ast.stylesheet.rules, function(rule, ruleIndex) { From e117e7025583603399ea31a3f5331c27880eb22a Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Wed, 30 Apr 2014 14:03:36 -0700 Subject: [PATCH 004/189] Test for #2093 --- packages/minifiers/urlrewriting-tests.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/minifiers/urlrewriting-tests.js b/packages/minifiers/urlrewriting-tests.js index 6314f619c7..4c65f4833d 100644 --- a/packages/minifiers/urlrewriting-tests.js +++ b/packages/minifiers/urlrewriting-tests.js @@ -29,4 +29,5 @@ Tinytest.add("minifiers - url rewriting when merging", function (test) { t('http://i.imgur.com/fBcdJIh.gif', 'http://i.imgur.com/fBcdJIh.gif', 'complete URL'); t('"http://i.imgur.com/fBcdJIh.gif"', '"http://i.imgur.com/fBcdJIh.gif"', 'complete quoted URL'); t('data:image/png;base64,iVBORw0K=', 'data:image/png;base64,iVBORw0K=', 'data URI'); + t('http://', '', 'manformed URL'); }); From 09f7cff07657c2372ad11467e290be78a4b6026a Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 30 Apr 2014 14:45:20 -0700 Subject: [PATCH 005/189] Fix test for #2093 --- packages/minifiers/urlrewriting-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/minifiers/urlrewriting-tests.js b/packages/minifiers/urlrewriting-tests.js index 4c65f4833d..b8bea8d852 100644 --- a/packages/minifiers/urlrewriting-tests.js +++ b/packages/minifiers/urlrewriting-tests.js @@ -29,5 +29,5 @@ Tinytest.add("minifiers - url rewriting when merging", function (test) { t('http://i.imgur.com/fBcdJIh.gif', 'http://i.imgur.com/fBcdJIh.gif', 'complete URL'); t('"http://i.imgur.com/fBcdJIh.gif"', '"http://i.imgur.com/fBcdJIh.gif"', 'complete quoted URL'); t('data:image/png;base64,iVBORw0K=', 'data:image/png;base64,iVBORw0K=', 'data URI'); - t('http://', '', 'manformed URL'); + t('http://', 'http://', 'manformed URL'); }); From 5137250aeed29cb1a8c56bfe143dbd344cdf9728 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 30 Apr 2014 17:35:04 -0700 Subject: [PATCH 006/189] Improve 'meteor mongo' error message Fixes #1256 --- tools/commands.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index f5b3ccbee9..10f1e73a05 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -719,10 +719,16 @@ main.registerCommand({ if (! mongoPort) { process.stdout.write( -"mongo: Meteor isn't running.\n" + +"mongo: Meteor isn't running a local MongoDB server.\n" + "\n" + "This command only works while Meteor is running your application\n" + -"locally. Start your application first.\n"); +"locally. Start your application first. (This error will also occur if\n" + +"you asked Meteor to use a different MongoDB server with $MONGO_URL when\n" + +"you ran your application.)\n" + +"\n" + +"If you're trying to connect to the database of an app you deployed\n" + +"with 'meteor deploy', specify your site's name with this command.\n" +); return 1; } mongoUrl = "mongodb://127.0.0.1:" + mongoPort + "/meteor"; From 94074c0dcdb902ef575834604eabe003ad692435 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 29 Apr 2014 16:18:47 -0700 Subject: [PATCH 007/189] new dev bundle: with npm/npm#5137 We should be able to remove our "npm install --force" workaround after this. --- meteor | 2 +- scripts/generate-dev-bundle.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/meteor b/meteor index a89420b584..99efbb0cf2 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.34 +BUNDLE_VERSION=0.3.35 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index d9b5c7690e..6816994610 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -71,12 +71,13 @@ umask 022 mkdir build cd build -git clone git://github.com/joyent/node.git +# Note: this is our fork! +git clone git://github.com/meteor/node.git cd node # When upgrading node versions, also update the values of MIN_NODE_VERSION at # the top of tools/meteor.js and tools/server/boot.js, and the text in # docs/client/concepts.html and the README in tools/bundler.js. -git checkout v0.10.26 +git checkout v0.10.26-uncorrupt-npm-cache ./configure --prefix="$DIR" make -j4 From 314c8a1a3464e368aef4ce17172af83e11a52515 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 30 Apr 2014 11:45:30 -0700 Subject: [PATCH 008/189] We no longer need to pass --force to npm install (Also, make a test assertion useful: assert.equal's default truncation is horrible.) --- History.md | 3 +++ tools/meteor-npm.js | 20 ++++++++++---------- tools/tests/old/test-bundler-npm.js | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/History.md b/History.md index 601bd33f80..5b9ca6c6ed 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,8 @@ ## v.NEXT +* Speed up updates of NPM modules by patching NPM to work around + https://github.com/npm/npm/issues/3265 instead of passing `--force`. + ## v0.8.1 #### Meteor Accounts diff --git a/tools/meteor-npm.js b/tools/meteor-npm.js index d4a9f670f7..2eab72a12e 100644 --- a/tools/meteor-npm.js +++ b/tools/meteor-npm.js @@ -459,17 +459,18 @@ var installNpmModule = function (name, version, dir) { // how to silence all output (specifically the installed tree which // is printed out with `console.log`) // - // We use --force, because the NPM cache is broken! See - // https://github.com/isaacs/npm/issues/3265 Basically, switching + // We used to use --force here, because the NPM cache is broken! See + // https://github.com/npm/npm/issues/3265 Basically, switching // back and forth between a tarball fork of version X and the real - // version X can confuse NPM. But the main reason to use tarball + // version X could confuse NPM. But the main reason to use tarball // URLs is to get a fork of the latest version with some fix, so - // it's easy to trigger this! So instead, always use --force. (Even - // with --force, we still WRITE to the cache, so we can corrupt the - // cache for other invocations of npm... ah well.) + // it was easy to trigger this! + // + // We now use a forked version of npm with our PR + // https://github.com/npm/npm/pull/5137 to work around this. var result = meteorNpm._execFileSync(path.join(files.getDevBundle(), "bin", "npm"), - ["install", "--force", installArg], + ["install", installArg], {cwd: dir}); if (! result.success) { @@ -498,11 +499,10 @@ var installFromShrinkwrap = function (dir) { ensureConnected(); - // `npm install`, which reads npm-shrinkwrap.json. See above for why - // --force. + // `npm install`, which reads npm-shrinkwrap.json. var result = meteorNpm._execFileSync(path.join(files.getDevBundle(), "bin", "npm"), - ["install", "--force"], {cwd: dir}); + ["install"], {cwd: dir}); if (! result.success) { // XXX include this in the buildmessage.error instead diff --git a/tools/tests/old/test-bundler-npm.js b/tools/tests/old/test-bundler-npm.js index bc67ad612c..7300035e15 100644 --- a/tools/tests/old/test-bundler-npm.js +++ b/tools/tests/old/test-bundler-npm.js @@ -84,7 +84,7 @@ var _assertCorrectPackageNpmDir = function (deps) { var expected = JSON.stringify({ dependencies: expectedMeteorNpmShrinkwrapDependencies}, null, /*indentation, the way npm does it*/2) + '\n'; - assert.equal(actual, expected); + assert.equal(actual, expected, actual + " == " + expected); assert.equal( fs.readFileSync(path.join(testPackageDir, ".npm", "package", ".gitignore"), 'utf8'), @@ -203,7 +203,7 @@ var runTest = function () { // just remove all of the .npm directory) var bareExecFileSync = meteorNpm._execFileSync; meteorNpm._execFileSync = function (file, args, opts) { - if (args.length > 2 && args[0] === 'install' && args[1] === '--force') + if (args.length > 1 && args[0] === 'install') assert.fail("shouldn't be installing specific npm packages: " + args[1]); return bareExecFileSync(file, args, opts); }; From 000c71ec303358ecf414ea354cbe776be3418a54 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 30 Apr 2014 13:35:37 -0700 Subject: [PATCH 009/189] A better version of our patch However, the better version involves taking some changes from a version of npm not yet released with Node. --- meteor | 2 +- scripts/generate-dev-bundle.sh | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/meteor b/meteor index 99efbb0cf2..9f1f777fc9 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.35 +BUNDLE_VERSION=0.3.36 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 6816994610..596472268c 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -71,13 +71,12 @@ umask 022 mkdir build cd build -# Note: this is our fork! -git clone git://github.com/meteor/node.git +git clone https://github.com/joyent/node.git cd node # When upgrading node versions, also update the values of MIN_NODE_VERSION at # the top of tools/meteor.js and tools/server/boot.js, and the text in # docs/client/concepts.html and the README in tools/bundler.js. -git checkout v0.10.26-uncorrupt-npm-cache +git checkout v0.10.26 ./configure --prefix="$DIR" make -j4 @@ -111,6 +110,11 @@ npm install bcrypt@0.7.7 npm install node-aes-gcm@0.1.3 npm install heapdump@0.2.5 +# Override the default npm of v0.10.26 (which is 1.4.3) with this, which is a +# fork of 1.4.7 plus some upstream commits plus +# https://github.com/npm/npm/pull/5137 +npm install https://github.com/glasser/npm/tarball/9adf943cc0837d5cedb4c4ec9dce8530507cc785 + # Fork of 1.0.2 with https://github.com/nodejitsu/node-http-proxy/pull/592 npm install https://github.com/meteor/node-http-proxy/tarball/99f757251b42aeb5d26535a7363c96804ee057f0 From e0fcd2c2ed0eec9c6f3dc9d07fb70cc0bc78655b Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 30 Apr 2014 18:02:53 -0700 Subject: [PATCH 010/189] update regexp for new version of npm also, use quotemeta where necessary --- tools/meteor-npm.js | 6 ++++-- tools/packages.js | 9 ++------- tools/utils.js | 5 +++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tools/meteor-npm.js b/tools/meteor-npm.js index 2eab72a12e..790ddd62a7 100644 --- a/tools/meteor-npm.js +++ b/tools/meteor-npm.js @@ -474,8 +474,10 @@ var installNpmModule = function (name, version, dir) { {cwd: dir}); if (! result.success) { - var pkgNotFound = "404 '" + name + "' is not in the npm registry"; - var versionNotFound = "version not found: " + version; + var pkgNotFound = "404 '" + utils.quotemeta(name) + + "' is not in the npm registry"; + var versionNotFound = "version not found: " + utils.quotemeta(name) + + '@' + utils.quotemeta(version); if (result.stderr.match(new RegExp(pkgNotFound))) { buildmessage.error("there is no npm package named '" + name + "'"); } else if (result.stderr.match(new RegExp(versionNotFound))) { diff --git a/tools/packages.js b/tools/packages.js index 8118226445..43dac0cacc 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -11,6 +11,7 @@ var meteorNpm = require('./meteor-npm.js'); var archinfo = require(path.join(__dirname, 'archinfo.js')); var linker = require(path.join(__dirname, 'linker.js')); var unipackage = require('./unipackage.js'); +var utils = require('./utils.js'); var fs = require('fs'); var sourcemap = require('source-map'); @@ -28,12 +29,6 @@ var sourcemap = require('source-map'); // update BUILT_BY, though you will need to quit and rerun "meteor run".) exports.BUILT_BY = 'meteor/10'; -// Like Perl's quotemeta: quotes all regexp metacharacters. See -// https://github.com/substack/quotemeta/blob/master/index.js -var quotemeta = function (str) { - return String(str).replace(/(\W)/g, '\\$1'); -}; - var rejectBadPath = function (p) { if (p.match(/\.\./)) throw new Error("bad path: " + p); @@ -1749,7 +1744,7 @@ _.extend(Package.prototype, { // Determine source files slice.getSourcesFunc = function () { var sourceInclude = _.map(slice.registeredExtensions(), function (ext) { - return new RegExp('\\.' + quotemeta(ext) + '$'); + return new RegExp('\\.' + utils.quotemeta(ext) + '$'); }); var sourceExclude = [/^\./].concat(ignoreFiles); diff --git a/tools/utils.js b/tools/utils.js index 198cdc8897..80ae7ab6e2 100644 --- a/tools/utils.js +++ b/tools/utils.js @@ -146,3 +146,8 @@ exports.validEmail = function (address) { return /^[^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*@([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}$/.test(address); } +// Like Perl's quotemeta: quotes all regexp metacharacters. See +// https://github.com/substack/quotemeta/blob/master/index.js +exports.quotemeta = function (str) { + return String(str).replace(/(\W)/g, '\\$1'); +}; From 5e0845a4367ca363f1d3d1367550bd9c9957abeb Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 30 Apr 2014 23:01:53 -0700 Subject: [PATCH 011/189] Follow-up to 4777e64: fix client-specified _id This was a regression in 0.8.1 which caused client-specified `_id` to always be ignored for collections with at least one allow/deny rule. Fixes #2097. Fixes #2099. --- History.md | 2 ++ packages/mongo-livedata/allow_tests.js | 29 ++++++++++++++++++++++---- packages/mongo-livedata/collection.js | 2 +- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/History.md b/History.md index 5b9ca6c6ed..654afb65cc 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,8 @@ * Speed up updates of NPM modules by patching NPM to work around https://github.com/npm/npm/issues/3265 instead of passing `--force`. +* Fix 0.8.1 regression preventing clients from specifying `_id` on insert. + ## v0.8.1 #### Meteor Accounts diff --git a/packages/mongo-livedata/allow_tests.js b/packages/mongo-livedata/allow_tests.js index e3f6c9e35e..e8a919a03e 100644 --- a/packages/mongo-livedata/allow_tests.js +++ b/packages/mongo-livedata/allow_tests.js @@ -50,7 +50,7 @@ if (Meteor.isServer) { // totally locked down collection var lockedDownCollection = defineCollection( "collection-locked-down", false /*insecure*/); - // resticted collection with same allowed modifications, both with and + // restricted collection with same allowed modifications, both with and // without the `insecure` package var restrictedCollectionDefaultSecure = defineCollection( "collection-restrictedDefaultSecure", false /*insecure*/); @@ -71,7 +71,9 @@ if (Meteor.isServer) { return doc.a; }); var restrictedCollectionForInvalidTransformTest = defineCollection( - "collection-restictedForInvalidTransform", false /*insecure*/); + "collection-restrictedForInvalidTransform", false /*insecure*/); + var restrictedCollectionForClientIdTest = defineCollection( + "collection-restrictedForClientIdTest", false /*insecure*/); if (needToConfigure) { restrictedCollectionWithTransform.allow({ @@ -98,6 +100,11 @@ if (Meteor.isServer) { transform: function (doc) { return doc._id; }, insert: function () { return true; } }); + restrictedCollectionForClientIdTest.allow({ + // This test just requires the collection to trigger the restricted + // case. + insert: function () { return true; } + }); // two calls to allow to verify that either validator is sufficient. var allows = [{ @@ -241,7 +248,7 @@ if (Meteor.isClient) { // totally locked down collection var lockedDownCollection = defineCollection("collection-locked-down"); - // resticted collection with same allowed modifications, both with and + // restricted collection with same allowed modifications, both with and // without the `insecure` package var restrictedCollectionDefaultSecure = defineCollection( "collection-restrictedDefaultSecure"); @@ -262,7 +269,9 @@ if (Meteor.isClient) { return doc.a; }); var restrictedCollectionForInvalidTransformTest = defineCollection( - "collection-restictedForInvalidTransform"); + "collection-restrictedForInvalidTransform"); + var restrictedCollectionForClientIdTest = defineCollection( + "collection-restrictedForClientIdTest"); // test that if allow is called once then the collection is // restricted, and that other mutations aren't allowed @@ -760,6 +769,18 @@ if (Meteor.isClient) { test.isTrue(err); })); }]); + testAsyncMulti( + "collection - restricted collection allows client-side id, " + idGeneration, + [function (test, expect) { + var self = this; + self.id = Random.id(); + restrictedCollectionForClientIdTest.insert({_id: self.id}, expect(function (err, res) { + test.isFalse(err); + test.equal(res, self.id); + test.equal(restrictedCollectionForClientIdTest.findOne(self.id), + {_id: self.id}); + })); + }]); }); // end idGeneration loop } // end if isClient diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index f7a0626ce3..c1f275fd92 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -700,7 +700,7 @@ Meteor.Collection.prototype._defineMutationMethods = function() { var validatedMethodName = '_validated' + method.charAt(0).toUpperCase() + method.slice(1); args.unshift(this.userId); - generatedId !== null && args.push(generatedId); + method === 'insert' && args.push(generatedId); return self[validatedMethodName].apply(self, args); } else if (self._isInsecure()) { if (generatedId !== null) From 853aa3f1d569680db5b9054e52c0c85303a07550 Mon Sep 17 00:00:00 2001 From: Andrew Wilcox Date: Tue, 22 Apr 2014 16:55:21 -0400 Subject: [PATCH 012/189] Run server tests one at a time. --- packages/email/email.js | 3 -- packages/email/email_tests.js | 2 - packages/mongo-livedata/allow_tests.js | 3 +- packages/tinytest/tinytest.js | 56 +++++++++----------------- 4 files changed, 21 insertions(+), 43 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index 4ff6155be8..1afabb7dea 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -71,9 +71,6 @@ EmailTest.restoreOutputStream = function () { var devModeSend = function (mc) { var devmode_mail_id = next_devmode_mail_id++; - // Make sure we use whatever stream was set at the time of the Email.send - // call even in the 'end' callback, in case there are multiple concurrent - // test runs. var stream = output_stream; // This approach does not prevent other writers to stdout from interleaving. diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index dbdddafeea..d3164ad99d 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -15,8 +15,6 @@ Tinytest.add("email - dev mode smoke test", function (test) { text: "This is the body\nof the message\nFrom us.", headers: {'X-Meteor-Test': 'a custom header'} }); - // Note that we use the local "stream" here rather than Email._output_stream - // in case a concurrent test run mutates Email._output_stream too. // XXX brittle if mailcomposer changes header order, etc test.equal(stream.getContentsAsString("utf8"), "====== BEGIN MAIL #0 ======\n" + diff --git a/packages/mongo-livedata/allow_tests.js b/packages/mongo-livedata/allow_tests.js index e8a919a03e..ca37a955fb 100644 --- a/packages/mongo-livedata/allow_tests.js +++ b/packages/mongo-livedata/allow_tests.js @@ -837,8 +837,7 @@ if (Meteor.isServer) { Tinytest.add("collection - global insecure", function (test) { // note: This test alters the global insecure status, by sneakily hacking - // the global Package object! This may collide with itself if run multiple - // times (but is better than the old test which had the same problem) + // the global Package object! var insecurePackage = Package.insecure; Package.insecure = {}; diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 4193bba829..2e215e192f 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -392,6 +392,7 @@ TestManager = function () { var self = this; self.tests = {}; self.ordered_tests = []; + self.testQueue = new Meteor._SynchronousQueue(); }; _.extend(TestManager.prototype, { @@ -444,8 +445,16 @@ _.extend(TestRun.prototype, { _runOne: function (test, onComplete, stop_at_offset) { var self = this; - var startTime = (+new Date); - if (self._prefixMatch(test.groupPath)) { + + if (! self._prefixMatch(test.groupPath)) { + onComplete && onComplete(); + return; + } + + self.manager.testQueue.queueTask(function () { + + var startTime = (+new Date); + test.run(function (event) { /* onEvent */ self._report(test, event); @@ -469,46 +478,21 @@ _.extend(TestRun.prototype, { onComplete && onComplete(); }, stop_at_offset); - } else { - onComplete && onComplete(); - } + }); }, run: function (onComplete) { var self = this; - // create array of arrays of tests; synchronous tests in - // different groups are run in parallel on client, async tests or - // tests in different groups are run in sequence, as are all - // tests on server - var testGroups = _.values( - _.groupBy(self.manager.ordered_tests, - function(t) { - if (Meteor.isServer) - return "SERVER"; - if (t.async) - return "ASYNC"; - return t.name.split(" - ")[0]; - })); + var tests = _.clone(self.manager.ordered_tests); - if (! testGroups.length) { - onComplete(); - } else { - var groupsDone = 0; + var runNext = function () { + if (tests.length) + self._runOne(tests.shift(), runNext); + else + onComplete && onComplete(); + }; - _.each(testGroups, function(tests) { - var runNext = function () { - if (tests.length) { - self._runOne(tests.shift(), runNext); - } else { - groupsDone++; - if (groupsDone >= testGroups.length) - onComplete(); - } - }; - - runNext(); - }); - } + runNext(); }, // An alternative to run(). Given the 'cookie' attribute of a From 482d9a78d7a0a13d0e90c84e8dcf0f51d5735455 Mon Sep 17 00:00:00 2001 From: Andrew Wilcox Date: Thu, 24 Apr 2014 18:33:43 -0400 Subject: [PATCH 013/189] Fix serializing server tests. `Tinytest.add` is now implemented in terms of `Tinytest.addAsync`, and the old `async` flag removed. --- packages/tinytest/tinytest.js | 134 ++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 2e215e192f..528a462551 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -1,3 +1,7 @@ +var Future; +if (Meteor.isServer) + Future = Npm.require('fibers/future'); + /******************************************************************************/ /* TestCaseResults */ /******************************************************************************/ @@ -317,11 +321,10 @@ _.extend(TestCaseResults.prototype, { /* TestCase */ /******************************************************************************/ -TestCase = function (name, func, async) { +TestCase = function (name, func) { var self = this; self.name = name; self.func = func; - self.async = async || false; var nameParts = _.map(name.split(" - "), function(s) { return s.replace(/^\s*|\s*$/g, ""); // trim @@ -366,16 +369,10 @@ _.extend(TestCase.prototype, { Meteor.defer(function () { try { - if (self.async) { - self.func(results, function () { - if (markComplete()) - onComplete(); - }); - } else { - self.func(results); + self.func(results, function () { if (markComplete()) onComplete(); - } + }); } catch (e) { if (markComplete()) onException(e); @@ -392,7 +389,7 @@ TestManager = function () { var self = this; self.tests = {}; self.ordered_tests = []; - self.testQueue = new Meteor._SynchronousQueue(); + self.testQueue = Meteor.isServer && new Meteor._SynchronousQueue(); }; _.extend(TestManager.prototype, { @@ -443,6 +440,49 @@ _.extend(TestRun.prototype, { return true; }, + _runTest: function (test, onComplete, stop_at_offset) { + var self = this; + + var startTime = (+new Date); + + test.run(function (event) { + /* onEvent */ + // Ignore result callbacks if the test has already been reported + // as timed out. + if (test.timedOut) + return; + self._report(test, event); + }, function () { + /* onComplete */ + if (test.timedOut) + return; + var totalTime = (+new Date) - startTime; + self._report(test, {type: "finish", timeMs: totalTime}); + onComplete(); + }, function (exception) { + /* onException */ + if (test.timedOut) + return; + + // XXX you want the "name" and "message" fields on the + // exception, to start with.. + self._report(test, { + type: "exception", + details: { + message: exception.message, // XXX empty??? + stack: exception.stack // XXX portability + } + }); + + onComplete(); + }, stop_at_offset); + }, + + // Run a single test. On the server, ensure that only one test runs + // at a time, even with multiple clients submitting tests. However, + // time out the test after three minutes to avoid locking up the + // server if a test fails to complete. + // _runOne: function (test, onComplete, stop_at_offset) { var self = this; @@ -451,34 +491,45 @@ _.extend(TestRun.prototype, { return; } - self.manager.testQueue.queueTask(function () { - - var startTime = (+new Date); - - test.run(function (event) { - /* onEvent */ - self._report(test, event); - }, function () { - /* onComplete */ - var totalTime = (+new Date) - startTime; - self._report(test, {type: "finish", timeMs: totalTime}); - onComplete && onComplete(); - }, function (exception) { - /* onException */ - - // XXX you want the "name" and "message" fields on the - // exception, to start with.. - self._report(test, { - type: "exception", - details: { - message: exception.message, // XXX empty??? - stack: exception.stack // XXX portability - } - }); - + if (Meteor.isServer) { + // On the server, ensure that only one test runs at a time, even + // with multiple clients. + self.manager.testQueue.queueTask(function () { + // The future resolves when the test completes or times out. + var future = new Future(); + Meteor.setTimeout( + function () { + if (future.isResolved()) + // If the future has resolved the test has completed. + return; + test.timedOut = true; + self._report(test, { + type: "exception", + details: { + message: "test timed out" + } + }); + future['return'](); + }, + 3 * 60 * 1000 // 3 minutes + ); + self._runTest(test, function () { + // The test can complete after it has timed out (it might + // just be slow), so only resolve the future if the test + // hasn't timed out. + if (! future.isResolved()) + future['return'](); + }, stop_at_offset); + // Wait for the test to complete or time out. + future.wait(); + onComplete & onComplete(); + }); + } else { + // client + self._runTest(test, function () { onComplete && onComplete(); }, stop_at_offset); - }); + } }, run: function (onComplete) { @@ -526,12 +577,15 @@ _.extend(TestRun.prototype, { Tinytest = {}; -Tinytest.add = function (name, func) { +Tinytest.addAsync = function (name, func) { TestManager.addCase(new TestCase(name, func)); }; -Tinytest.addAsync = function (name, func) { - TestManager.addCase(new TestCase(name, func, true)); +Tinytest.add = function (name, func) { + Tinytest.addAsync(name, function (test, onComplete) { + func(test); + onComplete(); + }); }; // Run every test, asynchronously. Runs the test in the current From af7ed1edc03739ea1d6568bff47151e8c2900644 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 30 Apr 2014 23:34:47 -0700 Subject: [PATCH 014/189] History for #2088 --- History.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/History.md b/History.md index 654afb65cc..4eadf18e9d 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,13 @@ * Fix 0.8.1 regression preventing clients from specifying `_id` on insert. +* Run server tests from multiple clients serially instead of in + parallel. This allows testing features that modify global server + state. #2088 + +Patches contributed by GitHub users awwx + + ## v0.8.1 #### Meteor Accounts From 9187c554c02a8dd01fc6a2cb7447cc51f4362adb Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 May 2014 13:57:35 -0700 Subject: [PATCH 015/189] Ban inserting EJSON custom types as documents Follow-up to 63b3119; further addresses #2095. There were a few problems here: - We didn't check that the argument to insert was a document. (EJSON custom types don't count as documents, because they don't have _ids!) - The check to see if something coming from the database was an EJSON custom type didn't match the check in ejson.js (specifically, it was missing size===2). This made it sort of look like you could use EJSON custom types as top-level documents, until a change in the MongoDB driver made made that coincidental almost-working code stop working. - The replaceNames function wasn't documented as only taking pure JSON, so it wasn't obvious that "it throws when there's a Buffer" was a bug in the caller rather than a bug in replaceNames. This should all be resolved now. Use cases like CollectionFS which were mislead by these bugs into believing that an EJSON custom type could be a document should move their custom type into a field. --- packages/minimongo/helpers.js | 2 +- packages/mongo-livedata/mongo_driver.js | 30 +++++++++++++------ .../mongo-livedata/mongo_livedata_tests.js | 24 +++++++-------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/minimongo/helpers.js b/packages/minimongo/helpers.js index ab8cc78b6f..759ce0ca62 100644 --- a/packages/minimongo/helpers.js +++ b/packages/minimongo/helpers.js @@ -8,7 +8,7 @@ isArray = function (x) { // XXX maybe this should be EJSON.isObject, though EJSON doesn't know about // RegExp // XXX note that _type(undefined) === 3!!!! -isPlainObject = function (x) { +isPlainObject = LocalCollection._isPlainObject = function (x) { return x && LocalCollection._f._type(x) === 3; }; diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index 3f8f0c690f..005b3c5e0e 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -15,12 +15,11 @@ var Future = Npm.require(path.join('fibers', 'future')); MongoInternals = {}; MongoTest = {}; +// This is used to add or remove EJSON from the beginning of everything nested +// inside an EJSON custom type. It should only be called on pure JSON! var replaceNames = function (filter, thing) { if (typeof thing === "object") { - // XXX This condition should match our `looksLikeArray` condition in - // underscore. (A Buffer might not be the only thing that should be - // treated as an array.) - if (_.isArray(thing) || thing instanceof Buffer) { + if (_.isArray(thing)) { return _.map(thing, _.bind(replaceNames, null, filter)); } var ret = {}; @@ -51,7 +50,8 @@ var replaceMongoAtomWithMeteor = function (document) { if (document instanceof MongoDB.ObjectID) { return new Meteor.Collection.ObjectID(document.toHexString()); } - if (document["EJSON$type"] && document["EJSON$value"]) { + if (document["EJSON$type"] && document["EJSON$value"] + && _.size(document) === 2) { return EJSON.fromJSONValue(replaceNames(unmakeMongoLegal, document)); } if (document instanceof MongoDB.Timestamp) { @@ -303,13 +303,25 @@ var bindEnvironmentForWrite = function (callback) { MongoConnection.prototype._insert = function (collection_name, document, callback) { var self = this; + + var sendError = function (e) { + if (callback) + return callback(e); + throw e; + }; + if (collection_name === "___meteor_failure_test_collection") { var e = new Error("Failure test"); e.expected = true; - if (callback) - return callback(e); - else - throw e; + sendError(e); + return; + } + + if (!(LocalCollection._isPlainObject(document) && + !EJSON._isCustomType(document))) { + sendError(new Error( + "Only documents (plain objects) may be inserted into MongoDB")); + return; } var write = self._maybeBeginWrite(); diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 9aafb19193..eea9dbed07 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -1448,21 +1448,28 @@ testAsyncMulti('mongo-livedata - document with a custom type, ' + idGeneration, Meteor.subscribe('c-' + this.collectionName, expect()); } }, function (test, expect) { - var coll = new Meteor.Collection(this.collectionName, collectionOptions); + var self = this; + self.coll = new Meteor.Collection(this.collectionName, collectionOptions); var docId; // Dog is implemented at the top of the file, outside of the idGeneration // loop (so that we only call EJSON.addType once). var d = new Dog("reginald", "purple"); - coll.insert({d: d}, expect(function (err, id) { + self.coll.insert({d: d}, expect(function (err, id) { test.isFalse(err); test.isTrue(id); docId = id; - var cursor = coll.find(); + var cursor = self.coll.find(); test.equal(cursor.count(), 1); - var inColl = coll.findOne(); + var inColl = self.coll.findOne(); test.isTrue(inColl); inColl && test.equal(inColl.d.speak(), "woof"); })); + }, function (test, expect) { + var self = this; + self.coll.insert(new Dog("rover", "orange"), expect(function (err, id) { + test.isTrue(err); + test.isFalse(id); + })); } ]); @@ -2954,12 +2961,3 @@ testAsyncMulti("mongo-livedata - undefined find options", [ test.equal(result, self.doc); } ]); - -// We're not sure if this should be supported, but it was broken in -// 0.8.1 and we decided to make a quick -// fix. https://github.com/meteor/meteor/issues/2095 -Meteor.isServer && Tinytest.add("mongo-livedata - insert and retrieve EJSON user-defined type as document", function (test) { - var coll = new Meteor.Collection(Random.id()); - coll.insert(new Meteor.Collection.ObjectID()); - coll.find({}).fetch(); -}); From f2e2a781ccdab271b02ceb0a7267f2e7274a8d97 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 May 2014 10:59:37 -0700 Subject: [PATCH 016/189] Update Node to 0.10.28 which includes our npm fix --- History.md | 8 ++++++-- docs/client/concepts.html | 2 +- meteor | 2 +- scripts/generate-dev-bundle.sh | 7 +------ tools/bundler.js | 2 +- tools/main.js | 2 +- tools/server/boot.js | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/History.md b/History.md index 69ef0c4f46..4db84ff7da 100644 --- a/History.md +++ b/History.md @@ -1,7 +1,8 @@ ## v.NEXT -* Speed up updates of NPM modules by patching NPM to work around - https://github.com/npm/npm/issues/3265 instead of passing `--force`. +* Speed up updates of NPM modules by upgrading Node to include our fix for + https://github.com/npm/npm/issues/3265 instead of passing `--force` to + `npm install`. * Fix 0.8.1 regression preventing clients from specifying `_id` on insert. @@ -9,6 +10,9 @@ parallel. This allows testing features that modify global server state. #2088 +* Upgraded dependencies: + - node: 0.10.28 (from 0.10.26) + Patches contributed by GitHub users awwx diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 6edef8e354..b00c4f29a7 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -741,7 +741,7 @@ To get started, run This command will generate a fully-contained Node.js application in the form of a tarball. To run this application, you need to provide Node.js 0.10 and a MongoDB server. (The current release of Meteor has been tested with Node -0.10.26; older versions contain a serious bug that can cause production servers +0.10.28; older versions contain a serious bug that can cause production servers to stall.) You can then run the application by invoking node, specifying the HTTP port for the application to listen on, and the MongoDB endpoint. If you don't already have a MongoDB server, we can recommend our friends at diff --git a/meteor b/meteor index 9f1f777fc9..0e8875133c 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.36 +BUNDLE_VERSION=0.3.37 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 596472268c..cfdaa271c8 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -76,7 +76,7 @@ cd node # When upgrading node versions, also update the values of MIN_NODE_VERSION at # the top of tools/meteor.js and tools/server/boot.js, and the text in # docs/client/concepts.html and the README in tools/bundler.js. -git checkout v0.10.26 +git checkout v0.10.28 ./configure --prefix="$DIR" make -j4 @@ -110,11 +110,6 @@ npm install bcrypt@0.7.7 npm install node-aes-gcm@0.1.3 npm install heapdump@0.2.5 -# Override the default npm of v0.10.26 (which is 1.4.3) with this, which is a -# fork of 1.4.7 plus some upstream commits plus -# https://github.com/npm/npm/pull/5137 -npm install https://github.com/glasser/npm/tarball/9adf943cc0837d5cedb4c4ec9dce8530507cc785 - # Fork of 1.0.2 with https://github.com/nodejitsu/node-http-proxy/pull/592 npm install https://github.com/meteor/node-http-proxy/tarball/99f757251b42aeb5d26535a7363c96804ee057f0 diff --git a/tools/bundler.js b/tools/bundler.js index 98b95f0767..8156750660 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -1537,7 +1537,7 @@ var writeSiteArchive = function (targets, outputPath, options) { builder.write('README', { data: new Buffer( "This is a Meteor application bundle. It has only one dependency:\n" + -"Node.js 0.10.26 or newer, plus the 'fibers' module. To run the application:\n" + +"Node.js 0.10.28 or newer, plus the 'fibers' module. To run the application:\n" + "\n" + " $ rm -r programs/server/node_modules/fibers\n" + " $ npm install fibers@1.0.1\n" + diff --git a/tools/main.js b/tools/main.js index 4521e2b9f5..a5eab6738f 100644 --- a/tools/main.js +++ b/tools/main.js @@ -330,7 +330,7 @@ Fiber(function () { // Check required Node version. // This code is duplicated in tools/server/boot.js. - var MIN_NODE_VERSION = 'v0.10.26'; + var MIN_NODE_VERSION = 'v0.10.28'; if (require('semver').lt(process.version, MIN_NODE_VERSION)) { process.stderr.write( 'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n'); diff --git a/tools/server/boot.js b/tools/server/boot.js index 6f401f4140..2076c92c2a 100644 --- a/tools/server/boot.js +++ b/tools/server/boot.js @@ -6,7 +6,7 @@ var _ = require('underscore'); var sourcemap_support = require('source-map-support'); // This code is duplicated in tools/main.js. -var MIN_NODE_VERSION = 'v0.10.26'; +var MIN_NODE_VERSION = 'v0.10.28'; if (require('semver').lt(process.version, MIN_NODE_VERSION)) { process.stderr.write( From 0c479a238bdfb1928228f0fa6e748fc49251a6c8 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 5 May 2014 20:49:38 -0700 Subject: [PATCH 017/189] Add credential secret argument to `retrieveCredential` functions. Fixes #2118. --- packages/facebook/facebook_server.js | 4 ++-- packages/github/github_server.js | 4 ++-- packages/meetup/meetup_server.js | 4 ++-- packages/meteor-developer/meteor_developer_server.js | 5 +++-- packages/twitter/twitter_server.js | 4 ++-- packages/weibo/weibo_server.js | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/facebook/facebook_server.js b/packages/facebook/facebook_server.js index 6f6b0c48a8..aca7637e1d 100644 --- a/packages/facebook/facebook_server.js +++ b/packages/facebook/facebook_server.js @@ -95,6 +95,6 @@ var getIdentity = function (accessToken) { } }; -Facebook.retrieveCredential = function(credentialToken) { - return OAuth.retrieveCredential(credentialToken); +Facebook.retrieveCredential = function(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); }; diff --git a/packages/github/github_server.js b/packages/github/github_server.js index c46fd787a5..654ed86d11 100644 --- a/packages/github/github_server.js +++ b/packages/github/github_server.js @@ -67,6 +67,6 @@ var getIdentity = function (accessToken) { }; -Github.retrieveCredential = function(credentialToken) { - return OAuth.retrieveCredential(credentialToken); +Github.retrieveCredential = function(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); }; diff --git a/packages/meetup/meetup_server.js b/packages/meetup/meetup_server.js index 38b4154df0..85c7708312 100644 --- a/packages/meetup/meetup_server.js +++ b/packages/meetup/meetup_server.js @@ -55,6 +55,6 @@ var getIdentity = function (accessToken) { }; -Meetup.retrieveCredential = function(credentialToken) { - return OAuth.retrieveCredential(credentialToken); +Meetup.retrieveCredential = function(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); }; diff --git a/packages/meteor-developer/meteor_developer_server.js b/packages/meteor-developer/meteor_developer_server.js index 47c17363c4..33d539baa1 100644 --- a/packages/meteor-developer/meteor_developer_server.js +++ b/packages/meteor-developer/meteor_developer_server.js @@ -93,6 +93,7 @@ var getIdentity = function (accessToken) { } }; -MeteorDeveloperAccounts.retrieveCredential = function (credentialToken) { - return OAuth.retrieveCredential(credentialToken); +MeteorDeveloperAccounts.retrieveCredential = function (credentialToken, + credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); }; diff --git a/packages/twitter/twitter_server.js b/packages/twitter/twitter_server.js index f7b6c0dcb0..ea0bc1cd14 100644 --- a/packages/twitter/twitter_server.js +++ b/packages/twitter/twitter_server.js @@ -36,6 +36,6 @@ OAuth.registerService('twitter', 1, urls, function(oauthBinding) { }); -Twitter.retrieveCredential = function(credentialToken) { - return OAuth.retrieveCredential(credentialToken); +Twitter.retrieveCredential = function(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); }; diff --git a/packages/weibo/weibo_server.js b/packages/weibo/weibo_server.js index 8da78bb22b..8af7b0194f 100644 --- a/packages/weibo/weibo_server.js +++ b/packages/weibo/weibo_server.js @@ -72,6 +72,6 @@ var getIdentity = function (accessToken, userId) { } }; -Weibo.retrieveCredential = function(credentialToken) { - return OAuth.retrieveCredential(credentialToken); +Weibo.retrieveCredential = function(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); }; From 74bcb916b4805e76ca1ca7bd03cb555444d8fe08 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 6 May 2014 14:11:40 -0700 Subject: [PATCH 018/189] Doc and history updates for 4777e64336 --- History.md | 6 ++++++ docs/client/api.html | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/History.md b/History.md index 4db84ff7da..67a5409d9e 100644 --- a/History.md +++ b/History.md @@ -104,6 +104,12 @@ Patches contributed by GitHub users awwx get one with `DDP.randomStream`. https://trello.com/c/moiiS2rP/57-pattern-for-creating-multiple-database-records-from-a-method +* The document passed to the `insert` callback of `allow` and `deny` now only + has a `_id` field if the client explicitly specified one; this allows you to + use `allow`/`deny` rules to prevent clients from specifying their own + `_id`. As an exception, `allow`/`deny` rules with a `transform` always have an + `_id`. + * DDP now has an implementation of bidirectional heartbeats which is consistent across SockJS and websocket transports. This enables connection keepalive and allows servers and clients to more consistently and efficiently detect diff --git a/docs/client/api.html b/docs/client/api.html index ff1f70e001..08a197dae4 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -962,6 +962,10 @@ The available callbacks are: {{#dtdd "insert(userId, doc)"}} The user `userId` wants to insert the document `doc` into the collection. Return `true` if this should be allowed. + +`doc` will contain the `_id` field if one was explicitly set by the client, or +if there is an active `transform`. You can use this to prevent users from +specifying arbitrary `_id` fields. {{/dtdd}} {{#dtdd "update(userId, doc, fieldNames, modifier)"}} From e5b5858203ab726d09086e707392c4e6aff6ab86 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Tue, 6 May 2014 20:22:51 -0700 Subject: [PATCH 019/189] Fix flakiness in "defer in rendered callback" test It was flaky before because template rendered callbacks get called after flush time, but not if the template got destroyed in the meanwhile. The way this test was written, if the client managed to respond to the server rejecting the method before the client's flush cycle, the rendered callback would never fire. Thus it would hang, since that callback was wrapped in an expect. Now we define a method on the client only, which makes it run as a stub without the server rejecting the method (ever). --- packages/spacebars-tests/template_tests.js | 34 ++++++++++++++-------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/spacebars-tests/template_tests.js b/packages/spacebars-tests/template_tests.js index 93c82cbc2c..f6eff667fd 100644 --- a/packages/spacebars-tests/template_tests.js +++ b/packages/spacebars-tests/template_tests.js @@ -767,30 +767,40 @@ Tinytest.add('spacebars - templates - textarea each', function (test) { // Ensure that one can call `Meteor.defer` within a rendered callback // triggered by a document insertion that happend in a method stub. +// +// Why do we have this test? Because you generally can't call +// `Meteor.defer` inside a method stub (see +// packages/meteor/timers.js). This test verifies that rendered +// callbacks don't fire synchronously as part of a method stub. testAsyncMulti('spacebars - template - defer in rendered callbacks', [function (test, expect) { var tmpl = Template.spacebars_template_test_defer_in_rendered; - var coll = new Meteor.Collection("test-defer-in-rendered--client-only"); + var coll = new Meteor.Collection(null); + + Meteor.methods({ + spacebarsTestInsertEmptyObject: function () { + // cause a new instance of `subtmpl` to be placed in the + // DOM. verify that it's not fired directly within a method + // stub, in which `Meteor.defer` is not allowed. + coll.insert({}); + } + }); + tmpl.items = function () { return coll.find(); }; var subtmpl = Template.spacebars_template_test_defer_in_rendered_subtemplate; + subtmpl.rendered = expect(function () { // will throw if called in a method stub - Meteor.defer(function () { - }); + Meteor.defer(function () {}); }); - + var div = renderToDiv(tmpl); - // `coll` is not defined on the server so we'll get an error. We - // can't make this a client-only collection since then we won't be - // running in a stub and the error won't fire. - Meteor._suppress_log(1); - // cause a new instance of `subtmpl` to be placed in the DOM. verify - // that it's not fired directly within a method stub, in which - // `Meteor.defer` is not allowed. - coll.insert({}); + // not defined on the server, but it's fine since the stub does + // the relevant work + Meteor.call("spacebarsTestInsertEmptyObject"); }]); testAsyncMulti('spacebars - template - rendered template is DOM in rendered callbacks', [ From 08cf1e097411df48fcb37defd772dc6bb47094ca Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 7 May 2014 17:31:29 -0700 Subject: [PATCH 020/189] shrinkwrap update from 314c8a1 Apparently not passing --force to npm install also allows us to do a better job of not bundling duplicate npm packages. --- packages/webapp/.npm/package/npm-shrinkwrap.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/webapp/.npm/package/npm-shrinkwrap.json b/packages/webapp/.npm/package/npm-shrinkwrap.json index 1cd8fc28af..dc8dc414d6 100644 --- a/packages/webapp/.npm/package/npm-shrinkwrap.json +++ b/packages/webapp/.npm/package/npm-shrinkwrap.json @@ -15,17 +15,6 @@ "cookie": { "version": "0.1.0" }, - "send": { - "version": "0.1.4", - "dependencies": { - "mime": { - "version": "1.2.11" - }, - "range-parser": { - "version": "0.0.4" - } - } - }, "bytes": { "version": "0.2.0" }, From 50b7f1292298b219779c7f03538e818b768b6eea Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 7 May 2014 17:46:08 -0700 Subject: [PATCH 021/189] Fix 0.8.1 regression in ROOT_URL with path Fixes #2109. --- packages/webapp/boilerplate.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webapp/boilerplate.html b/packages/webapp/boilerplate.html index a4fb29f8fb..e71c29c5ba 100644 --- a/packages/webapp/boilerplate.html +++ b/packages/webapp/boilerplate.html @@ -1,13 +1,13 @@ -{{#each css}} {{/each}} +{{#each css}} {{/each}} {{#if inlineScriptsAllowed}} {{else}} {{/if}} -{{#each js}} +{{#each js}} {{/each}} {{#if inlineScriptsAllowed}} From 3431c66c1648161dd1072aa50df52e61967c7d99 Mon Sep 17 00:00:00 2001 From: Felix Rabe Date: Sun, 4 May 2014 22:31:03 +0200 Subject: [PATCH 022/189] Fix occurrences of "cd `dirname $0`" They are not safe for spaces in paths. There might be other places to look for trouble. I've run the following command to produce this commit: (on OS X, copy-and-pasting the below exactly) find . -type f -name '*.sh' -print0 | # Find all .sh files xargs -0 fgrep -H -- '`' | # See all places with backticks in them fgrep 'cd `dirname $0' | # I deemed these problematic (variable assignments are safe) cut -d ':' -f 1 | # Take the from : produced by "grep -H" tr '\n' '\0' | # Also here, spaces can be problematic - always do "xargs -0"! xargs -0 -- sed -i '' 's/cd `dirname $0`/cd "`dirname "$0"`"/g' The significance of adding the two levels of "'s can be verified by running the following in your Terminal: $ node -e 'console.log(process.argv.splice(1))' -- `echo 1 2` [ '1', '2' ] $ node -e 'console.log(process.argv.splice(1))' -- "`echo 1 2`" [ '1 2' ] $ node -e 'console.log(process.argv.splice(1))' -- "`echo "1 2"`" [ '1 2' ] --- packages/test-in-console/run.sh | 2 +- scripts/admin/build-package-tarballs.sh | 2 +- scripts/admin/build-release.sh | 2 +- scripts/admin/build-tools-tarballs.sh | 2 +- scripts/admin/build-tools-tree.sh | 2 +- scripts/admin/copy-dev-bundle-from-jenkins.sh | 2 +- scripts/admin/deploy-examples.sh | 2 +- scripts/admin/publish-release.sh | 2 +- scripts/admin/upgrade-to-engine/build-fake-release.sh | 2 +- scripts/generate-dev-bundle.sh | 2 +- tools/tests/old/cli-test.sh | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/test-in-console/run.sh b/packages/test-in-console/run.sh index e6999508a8..2b31a7a065 100755 --- a/packages/test-in-console/run.sh +++ b/packages/test-in-console/run.sh @@ -1,6 +1,6 @@ #!/bin/bash -cd `dirname $0` +cd "`dirname "$0"`" cd ../.. export METEOR_HOME=`pwd` diff --git a/scripts/admin/build-package-tarballs.sh b/scripts/admin/build-package-tarballs.sh index aff7616311..7c7d18117d 100755 --- a/scripts/admin/build-package-tarballs.sh +++ b/scripts/admin/build-package-tarballs.sh @@ -14,7 +14,7 @@ set -e set -u # cd to top level dir -cd `dirname $0` +cd "`dirname "$0"`" cd ../.. export TOPDIR=$(pwd) diff --git a/scripts/admin/build-release.sh b/scripts/admin/build-release.sh index d70df75c57..63b4845648 100755 --- a/scripts/admin/build-release.sh +++ b/scripts/admin/build-release.sh @@ -4,7 +4,7 @@ set -e set -u # cd to top level dir -cd `dirname $0` +cd "`dirname "$0"`" cd ../.. TOPDIR=$(pwd) diff --git a/scripts/admin/build-tools-tarballs.sh b/scripts/admin/build-tools-tarballs.sh index 13baa9b307..62a4978f1a 100755 --- a/scripts/admin/build-tools-tarballs.sh +++ b/scripts/admin/build-tools-tarballs.sh @@ -4,7 +4,7 @@ set -e set -u # cd to top level dir -cd `dirname $0` +cd "`dirname "$0"`" cd ../.. TOPDIR=$(pwd) diff --git a/scripts/admin/build-tools-tree.sh b/scripts/admin/build-tools-tree.sh index 92e7df519c..8d949f8902 100755 --- a/scripts/admin/build-tools-tree.sh +++ b/scripts/admin/build-tools-tree.sh @@ -8,7 +8,7 @@ set -e set -u -cd `dirname $0`/../.. +cd "`dirname "$0"`"/../.. if [ "$TARGET_DIR" == "" ] ; then echo 'Must set $TARGET_DIR' diff --git a/scripts/admin/copy-dev-bundle-from-jenkins.sh b/scripts/admin/copy-dev-bundle-from-jenkins.sh index 54a7cbca9e..f7ebb5c92d 100755 --- a/scripts/admin/copy-dev-bundle-from-jenkins.sh +++ b/scripts/admin/copy-dev-bundle-from-jenkins.sh @@ -8,7 +8,7 @@ set -e set -u -cd `dirname $0` +cd "`dirname "$0"`" TARGET="s3://com.meteor.static/test/" TEST=no diff --git a/scripts/admin/deploy-examples.sh b/scripts/admin/deploy-examples.sh index a9a4a7fcdf..481aa713bf 100755 --- a/scripts/admin/deploy-examples.sh +++ b/scripts/admin/deploy-examples.sh @@ -2,7 +2,7 @@ set -e -cd `dirname $0` +cd "`dirname "$0"`" cd ../examples diff --git a/scripts/admin/publish-release.sh b/scripts/admin/publish-release.sh index 0c33ecd343..404ec741b1 100755 --- a/scripts/admin/publish-release.sh +++ b/scripts/admin/publish-release.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e set -u -cd `dirname $0` +cd "`dirname "$0"`" DIR="$(pwd)" METEOR_DIR="$(pwd)/../.." diff --git a/scripts/admin/upgrade-to-engine/build-fake-release.sh b/scripts/admin/upgrade-to-engine/build-fake-release.sh index bffff52f63..6d5553bc39 100755 --- a/scripts/admin/upgrade-to-engine/build-fake-release.sh +++ b/scripts/admin/upgrade-to-engine/build-fake-release.sh @@ -10,7 +10,7 @@ set -e set -u # cd to top level dir -cd `dirname $0` +cd "`dirname "$0"`" cd ../../.. TOPDIR=$(pwd) diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index cfdaa271c8..5d5e57255b 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -49,7 +49,7 @@ fi PLATFORM="${UNAME}_${ARCH}" # save off meteor checkout dir as final target -cd `dirname $0`/.. +cd "`dirname "$0"`"/.. TARGET_DIR=`pwd` # Read the bundle version from the meteor shell script. diff --git a/tools/tests/old/cli-test.sh b/tools/tests/old/cli-test.sh index b6f870d3a8..779bacb541 100755 --- a/tools/tests/old/cli-test.sh +++ b/tools/tests/old/cli-test.sh @@ -11,7 +11,7 @@ set -e -x -cd `dirname $0`/../../.. +cd "`dirname "$0"`"/../../.. # METEOR_TOOL_PATH is the path to the 'meteor' that we will use for # our tests. There is a vestigal capability to default to running the From d22a10cf1c87d9e96a7b62c1bcc048bcb72a059d Mon Sep 17 00:00:00 2001 From: Felix Rabe Date: Sun, 4 May 2014 23:00:07 +0200 Subject: [PATCH 023/189] Typo --- tools/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/main.js b/tools/main.js index a5eab6738f..f7cc871a4b 100644 --- a/tools/main.js +++ b/tools/main.js @@ -88,7 +88,7 @@ main.SpringboardToLatestRelease = function () {}; // - can be a basic command, like "deploy" // - can be a subcommand, like "admin grant" // (distinguished by presence of ' ') -// - can be an option that functions as a command, ilke "--arch" +// - can be an option that functions as a command, like "--arch" // (distinguished by starting with '--') // - minArgs: minimum non-option arguments that can be present (default 0) // - maxArgs: maximum non-option arguments that can be present (defaults to From 819bdb31b0bd22e04e5bffb1b5719938777340d3 Mon Sep 17 00:00:00 2001 From: Felix Rabe Date: Sun, 4 May 2014 23:11:27 +0200 Subject: [PATCH 024/189] Looks like a typo (untested minor change) --- tools/run-all.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/run-all.js b/tools/run-all.js index 0f00937549..ba7f14f22c 100644 --- a/tools/run-all.js +++ b/tools/run-all.js @@ -91,7 +91,7 @@ _.extend(Runner.prototype, { self.proxy.start(); // print the banner only once we've successfully bound the port - if (! self.quiet & ! self.stopped) { + if (! self.quiet && ! self.stopped) { runLog.log("[[[[[ " + self.banner + " ]]]]]\n"); runLog.log("=> Started proxy."); } From 27d9959326c244f37e00e85cac17f910bbf090fc Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 7 May 2014 18:04:29 -0700 Subject: [PATCH 025/189] Add missing amplify -> jquery dependency Somehow this worked in 0.7.2 but starting at 0.8.0 amplify has a ReferenceError. Fixes #2113. --- packages/amplify/package.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/amplify/package.js b/packages/amplify/package.js index 012777f8b1..8595bd8db6 100644 --- a/packages/amplify/package.js +++ b/packages/amplify/package.js @@ -3,5 +3,6 @@ Package.describe({ }); Package.on_use(function (api) { + api.use('jquery', 'client'); api.add_files('amplify.js', 'client'); }); From 4afa54ca5af80e2214e998e2a48de1dcb59c4fc6 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 5 May 2014 21:36:48 -0700 Subject: [PATCH 026/189] Set Content-Type header on js and css resources. --- packages/webapp/package.js | 5 +++++ packages/webapp/webapp_server.js | 17 +++++++++++++++-- packages/webapp/webapp_tests.js | 23 +++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 packages/webapp/webapp_tests.js diff --git a/packages/webapp/package.js b/packages/webapp/package.js index 64a05ee60b..2ad86c3809 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -30,3 +30,8 @@ Package.on_use(function (api) { api.add_files('boilerplate.html', 'server', {isAsset: true}); api.add_files('webapp_client.js', 'client'); }); + +Package.on_test(function (api) { + api.use(['tinytest', 'webapp', 'http']); + api.add_files('webapp_tests.js', 'server'); +}); diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index ab22ce2414..1c802a9d90 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -296,7 +296,8 @@ var runWebAppServer = function () { path: item.path, cacheable: item.cacheable, // Link from source to its map - sourceMapUrl: item.sourceMapUrl + sourceMapUrl: item.sourceMapUrl, + type: item.type }; if (item.sourceMap) { @@ -310,6 +311,9 @@ var runWebAppServer = function () { } }); + // Exported for tests. + WebAppInternals.staticFiles = staticFiles; + // Serve static files from the manifest. // This is inspired by the 'static' middleware. @@ -328,7 +332,9 @@ var runWebAppServer = function () { } var serveStaticJs = function (s) { - res.writeHead(200, { 'Content-type': 'application/javascript' }); + res.writeHead(200, { + 'Content-type': 'application/javascript; charset=UTF-8' + }); res.write(s); res.end(); }; @@ -400,6 +406,13 @@ var runWebAppServer = function () { // in `about:config` (it is on by default in FF 24). if (info.sourceMapUrl) res.setHeader('X-SourceMap', info.sourceMapUrl); + + if (info.type === "js") { + res.setHeader("Content-Type", "text/javascript; charset=UTF-8"); + } else if (info.type === "css") { + res.setHeader("Content-Type", "text/css; charset=UTF-8"); + } + send(req, path.join(clientDir, info.path)) .maxage(maxAge) .hidden(true) // if we specified a dotfile in the manifest, serve it diff --git a/packages/webapp/webapp_tests.js b/packages/webapp/webapp_tests.js new file mode 100644 index 0000000000..f4555898e3 --- /dev/null +++ b/packages/webapp/webapp_tests.js @@ -0,0 +1,23 @@ +var url = Npm.require("url"); + +Tinytest.add("webapp - content-type header", function (test) { + var cssResource = _.find( + _.keys(WebAppInternals.staticFiles), + function (url) { + return WebAppInternals.staticFiles[url].type === "css"; + } + ); + var jsResource = _.find( + _.keys(WebAppInternals.staticFiles), + function (url) { + return WebAppInternals.staticFiles[url].type === "js"; + } + ); + + var resp = HTTP.get(url.resolve(Meteor.absoluteUrl(), cssResource)); + test.equal(resp.headers["content-type"].toLowerCase(), + "text/css; charset=utf-8"); + resp = HTTP.get(url.resolve(Meteor.absoluteUrl(), jsResource)); + test.equal(resp.headers["content-type"].toLowerCase(), + "text/javascript; charset=utf-8"); +}); From a8673d01cd395d516539f91c2a8112bde89c8fd6 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 5 May 2014 22:22:45 -0700 Subject: [PATCH 027/189] Set X-Content-Type-Options in browser-policy-content --- docs/client/packages/browser-policy.html | 6 +++++ .../browser-policy-common.js | 24 +++++++++++++++++-- .../browser-policy-content.js | 17 ++++++++++++- .../browser-policy/browser-policy-test.js | 6 +++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/client/packages/browser-policy.html b/docs/client/packages/browser-policy.html index 8499e891c9..94c90a4616 100644 --- a/docs/client/packages/browser-policy.html +++ b/docs/client/packages/browser-policy.html @@ -169,6 +169,12 @@ sites can frame your site, while `BrowserPolicy.content.allowFrameOrigin` allows you to control which sites can be loaded inside frames on your site. +Adding `browser-policy-content` to your app also tells certain +browsers to avoid sniffing content types away from the declared type +(for example, interpreting a text file as JavaScript), using the +[X-Content-Type-Options](http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx) +header. To re-enable content sniffing, you can call +`BrowserPolicy.content.allowContentTypeSniffing()`. {{/markdown}} diff --git a/packages/browser-policy-common/browser-policy-common.js b/packages/browser-policy-common/browser-policy-common.js index 9dd18f8556..f4c2afd4ec 100644 --- a/packages/browser-policy-common/browser-policy-common.js +++ b/packages/browser-policy-common/browser-policy-common.js @@ -19,9 +19,29 @@ WebApp.connectHandlers.use(function (req, res, next) { BrowserPolicy.framing._constructXFrameOptions(); var csp = BrowserPolicy.content && BrowserPolicy.content._constructCsp(); - if (xFrameOptions) + if (xFrameOptions) { res.setHeader("X-Frame-Options", xFrameOptions); - if (csp) + } + if (csp) { res.setHeader("Content-Security-Policy", csp); + } + next(); +}); + +// We use `rawConnectHandlers` to set X-Content-Type-Options on all +// requests, including static files. +// XXX We should probably use `rawConnectHandlers` for X-Frame-Options +// and Content-Security-Policy too, but let's make sure that doesn't +// break anything first (e.g. the OAuth popup flow won't work well with +// a CSP that disallows inline scripts). +WebApp.rawConnectHandlers.use(function (req, res, next) { + if (BrowserPolicy._runningTest()) + return next(); + + var contentTypeOptions = BrowserPolicy.content && + BrowserPolicy.content._xContentTypeOptions(); + if (contentTypeOptions) { + res.setHeader("X-Content-Type-Options", contentTypeOptions); + } next(); }); diff --git a/packages/browser-policy-content/browser-policy-content.js b/packages/browser-policy-content/browser-policy-content.js index 569bf9c4d0..8550504e91 100644 --- a/packages/browser-policy-content/browser-policy-content.js +++ b/packages/browser-policy-content/browser-policy-content.js @@ -1,7 +1,8 @@ // By adding this package, you get the following default policy: // No eval or other string-to-code, and content can only be loaded from the // same origin as the app (except for XHRs and websocket connections, which can -// go to any origin). +// go to any origin). Browsers will also be told not to sniff content types +// away from declared content types (X-Content-Type-Options: nosniff). // // Apps should call BrowserPolicy.content.disallowInlineScripts() if they are // not using any inline script tags and are willing to accept an extra round @@ -32,6 +33,8 @@ // allowAllContentSameOrigin() // disallowAllContent() // +// You can allow content type sniffing by calling +// `BrowserPolicy.content.allowContentTypeSniffing()`. var cspSrcs; var cachedCsp; // Avoid constructing the header out of cspSrcs when possible. @@ -44,6 +47,9 @@ var keywords = { none: "'none'" }; +// If false, we set the X-Content-Type-Options header to 'nosniff'. +var contentSniffingAllowed = false; + BrowserPolicy.content = {}; var parseCsp = function (csp) { @@ -134,6 +140,9 @@ var setWebAppInlineScripts = function (value) { }; _.extend(BrowserPolicy.content, { + allowContentTypeSniffing: function () { + contentSniffingAllowed = true; + }, // Exported for tests and browser-policy-common. _constructCsp: function () { if (! cspSrcs || _.isEmpty(cspSrcs)) @@ -220,6 +229,12 @@ _.extend(BrowserPolicy.content, { "default-src": [] }; setWebAppInlineScripts(false); + }, + + _xContentTypeOptions: function () { + if (! contentSniffingAllowed) { + return "nosniff"; + } } }); diff --git a/packages/browser-policy/browser-policy-test.js b/packages/browser-policy/browser-policy-test.js index 48f999c9ba..2f5bcb604c 100644 --- a/packages/browser-policy/browser-policy-test.js +++ b/packages/browser-policy/browser-policy-test.js @@ -151,3 +151,9 @@ Tinytest.add("browser-policy - x-frame-options", function (test) { BrowserPolicy.framing.restrictToOrigin("bar.com"); }); }); + +Tinytest.add("browser-policy - X-Content-Type-Options", function (test) { + test.equal(BrowserPolicy.content._xContentTypeOptions(), "nosniff"); + BrowserPolicy.content.allowContentTypeSniffing(); + test.equal(BrowserPolicy.content._xContentTypeOptions(), undefined); +}); From c044786e2fd9a1cb52cb138bc2f3d64b9426fac2 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Thu, 8 May 2014 08:45:54 -0700 Subject: [PATCH 028/189] nim, glasser comments --- packages/browser-policy-content/browser-policy-content.js | 1 + packages/browser-policy/browser-policy-test.js | 1 + packages/webapp/webapp_server.js | 2 +- packages/webapp/webapp_tests.js | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/browser-policy-content/browser-policy-content.js b/packages/browser-policy-content/browser-policy-content.js index 8550504e91..157b164218 100644 --- a/packages/browser-policy-content/browser-policy-content.js +++ b/packages/browser-policy-content/browser-policy-content.js @@ -132,6 +132,7 @@ var setDefaultPolicy = function () { "connect-src *; " + "img-src data: 'self'; " + "style-src 'self' 'unsafe-inline';"); + contentSniffingAllowed = false; }; var setWebAppInlineScripts = function (value) { diff --git a/packages/browser-policy/browser-policy-test.js b/packages/browser-policy/browser-policy-test.js index 2f5bcb604c..1a2ed512bd 100644 --- a/packages/browser-policy/browser-policy-test.js +++ b/packages/browser-policy/browser-policy-test.js @@ -153,6 +153,7 @@ Tinytest.add("browser-policy - x-frame-options", function (test) { }); Tinytest.add("browser-policy - X-Content-Type-Options", function (test) { + BrowserPolicy.content._reset(); test.equal(BrowserPolicy.content._xContentTypeOptions(), "nosniff"); BrowserPolicy.content.allowContentTypeSniffing(); test.equal(BrowserPolicy.content._xContentTypeOptions(), undefined); diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 1c802a9d90..5b2429cefd 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -408,7 +408,7 @@ var runWebAppServer = function () { res.setHeader('X-SourceMap', info.sourceMapUrl); if (info.type === "js") { - res.setHeader("Content-Type", "text/javascript; charset=UTF-8"); + res.setHeader("Content-Type", "application/javascript; charset=UTF-8"); } else if (info.type === "css") { res.setHeader("Content-Type", "text/css; charset=UTF-8"); } diff --git a/packages/webapp/webapp_tests.js b/packages/webapp/webapp_tests.js index f4555898e3..1f555c362c 100644 --- a/packages/webapp/webapp_tests.js +++ b/packages/webapp/webapp_tests.js @@ -19,5 +19,5 @@ Tinytest.add("webapp - content-type header", function (test) { "text/css; charset=utf-8"); resp = HTTP.get(url.resolve(Meteor.absoluteUrl(), jsResource)); test.equal(resp.headers["content-type"].toLowerCase(), - "text/javascript; charset=utf-8"); + "application/javascript; charset=utf-8"); }); From fe52538881cb9325e6fdcbdbfdf0fc0e6299f333 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Thu, 8 May 2014 12:14:30 -0700 Subject: [PATCH 029/189] Add Content-Type to history --- History.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/History.md b/History.md index 67a5409d9e..1ab19a687b 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,13 @@ parallel. This allows testing features that modify global server state. #2088 +* Add Content-Type headers on JavaScript and CSS resources. + +* Add `X-Content-Type-Options: nosniff` header to + `browser-policy-content`'s default policy. If you are using + `browser-policy-content` and you don't want your app to send this + header, then call `BrowserPolicy.content.allowContentTypeSniffing()`. + * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) From 5358af451cf073e538aad35bf752e2845010be20 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Thu, 8 May 2014 16:21:05 -0700 Subject: [PATCH 030/189] selftest: return null when reading a file that doesn't exist --- tools/selftest.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/selftest.js b/tools/selftest.js index 494fe53ea2..0d84ac73e8 100644 --- a/tools/selftest.js +++ b/tools/selftest.js @@ -464,13 +464,16 @@ _.extend(Sandbox.prototype, { fs.writeFileSync(path.join(self.cwd, filename), contents, 'utf8'); }, - // Reads a file in the sandbox as a utf8 string. 'filename' is a path - // intepreted relative to the Sandbox's cwd. throws if the file does not - // exist. - // XXX maybe it should return null if the file does not exist? + // Reads a file in the sandbox as a utf8 string. 'filename' is a + // path intepreted relative to the Sandbox's cwd. Returns null if + // file does not exist. read: function (filename) { var self = this; - return fs.readFileSync(path.join(self.cwd, filename), 'utf8'); + var file = path.join(self.cwd, filename); + if (!fs.existsSync(file)) + return null; + else + return fs.readFileSync(path.join(self.cwd, filename), 'utf8'); }, // Delete a file in the sandbox. 'filename' is as in write(). From 1cfd91aaea9c234092605c30704fbc222247665f Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 8 May 2014 17:27:43 -0700 Subject: [PATCH 031/189] Stricter validation of connect messages Previously, you could leave out the "support" field (or claim to not support the version you were proposing) which would mean your connection would succeed iff the proposed version is the server's favorite version. This led to (eg) ObjectiveDDP accidentally writing a client that stopped working when servers started preferring pre2 over pre1. By making this a blatant error, DDP client libraries are more likely to be written in a way that works with version negotiation. Also, remove the delay in sending connect failure messages, which was intended to avoid connect storms from clients that are by now 1.5 years old. Fixes #2125. --- packages/livedata/livedata_server.js | 51 +++++++++++++++------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index fff6114866..96ea93d5ba 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -1217,37 +1217,40 @@ _.extend(Server.prototype, { _handleConnect: function (socket, msg) { var self = this; + + // The connect message must specify a version and an array of supported + // versions, and it must claim to support what it is proposing. + if (!(typeof (msg.version) === 'string' && + _.isArray(msg.support) && + _.all(msg.support, _.isString) && + _.contains(msg.support, msg.version))) { + socket.send(stringifyDDP({msg: 'failed', + version: SUPPORTED_DDP_VERSIONS[0]})); + socket.close(); + return; + } + // In the future, handle session resumption: something like: // socket._meteorSession = self.sessions[msg.session] var version = calculateVersion(msg.support, SUPPORTED_DDP_VERSIONS); - if (msg.version === version) { - // Creating a new session - socket._meteorSession = new Session(self, version, socket, self.options); - self.sessions[socket._meteorSession.id] = socket._meteorSession; - self.onConnectionHook.each(function (callback) { - if (socket._meteorSession) - callback(socket._meteorSession.connectionHandle); - return true; - }); - } else if (!msg.version) { - // connect message without a version. This means an old (pre-pre1) - // client is trying to connect. If we just disconnect the - // connection, they'll retry right away. Instead, just pause for a - // bit (randomly distributed so as to avoid synchronized swarms) - // and hold the connection open. - var timeout = 1000 * (30 + Random.fraction() * 60); - // drop all future data coming over this connection on the - // floor. We don't want to confuse things. - socket.removeAllListeners('data'); - Meteor.setTimeout(function () { - socket.send(stringifyDDP({msg: 'failed', version: version})); - socket.close(); - }, timeout); - } else { + if (msg.version !== version) { + // The best version to use (according to the client's stated preferences) + // is not the one the client is trying to use. Inform them about the best + // version to use. socket.send(stringifyDDP({msg: 'failed', version: version})); socket.close(); + return; } + + // Yay, version matches! Create a new session. + socket._meteorSession = new Session(self, version, socket, self.options); + self.sessions[socket._meteorSession.id] = socket._meteorSession; + self.onConnectionHook.each(function (callback) { + if (socket._meteorSession) + callback(socket._meteorSession.connectionHandle); + return true; + }); }, /** * Register a publish handler function. From 829325928e704f46addc6cbd0b383262b7823c0a Mon Sep 17 00:00:00 2001 From: Cangit Date: Mon, 5 May 2014 14:38:20 +0200 Subject: [PATCH 032/189] Update commandline.html Removed deprecated --password command from docs. Added info on new practices. --- docs/client/commandline.html | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/docs/client/commandline.html b/docs/client/commandline.html index fb642598e6..ad14ba599c 100644 --- a/docs/client/commandline.html +++ b/docs/client/commandline.html @@ -56,6 +56,19 @@ point at `origin.meteor.com`. +The first time you deploy an app you'll +be prompted for an email address – follow the link +in your email to finish setting up your account. + + + +Once you have your account you can log in and +log out from the command line, type `meteor whoami`, +and run `meteor authorized` to give other Meteor developers +permissions to deploy your app and access its database and logs. + + + You can deploy in debug mode by passing `--debug`. This will leave your source code readable by your favorite in-browser debugger, just like it is in local development mode. @@ -67,24 +80,6 @@ the `--delete` option along with the site. -To add an administrative password to your deployment, include -the `--password` option. Meteor will prompt -for a password. Once set, any future `meteor deploy` to -the same domain will require that you provide the password. The same -password protects access to `meteor mongo` -and `meteor logs`. You can change the password by -running `meteor deploy --password` again, -which will first prompt for the current password, then for a new -password. - - -{{#warning}} -Password protection only applies to administrative actions with the -Meteor command. It does not prevent access to your deployed -website. Also, this all is a temporary hack until we have -full-featured Meteor accounts. -{{/warning}} - {{#warning}} If you use a domain name other than `meteor.com` you must ensure that the name resolves From d6e53c0428e37e4e8571fdf2f7808e11e2ff4e71 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 8 May 2014 18:00:35 -0700 Subject: [PATCH 033/189] cleanup HTML --- docs/client/commandline.html | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/client/commandline.html b/docs/client/commandline.html index ad14ba599c..27e28ccef4 100644 --- a/docs/client/commandline.html +++ b/docs/client/commandline.html @@ -55,18 +55,14 @@ then you'll need to make sure the DNS for that domain is configured to point at `origin.meteor.com`. - -The first time you deploy an app you'll -be prompted for an email address – follow the link -in your email to finish setting up your account. +The first time you deploy an app you'll be prompted for an email address — +follow the link in your email to finish setting up your account. - -Once you have your account you can log in and -log out from the command line, type `meteor whoami`, -and run `meteor authorized` to give other Meteor developers -permissions to deploy your app and access its database and logs. - +Once you have your account you can log in and log out from the command line, +check your username with `meteor whoami`, and run `meteor authorized` to give +other Meteor developers permissions to deploy your app and access its database +and logs. You can deploy in debug mode by passing `--debug`. This From 4928ed61d1b51b25502e493b0467deb209ee47a5 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Thu, 8 May 2014 11:16:20 -0700 Subject: [PATCH 034/189] Unregister sessions from server when their heartbeats time out. --- History.md | 3 +++ packages/livedata/livedata_server.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 1ab19a687b..67801c336b 100644 --- a/History.md +++ b/History.md @@ -17,6 +17,9 @@ `browser-policy-content` and you don't want your app to send this header, then call `BrowserPolicy.content.allowContentTypeSniffing()`. +* Fix memory leak (introduced in 0.8.1) by making sure to unregister + sessions at the server when they are closed due to heartbeat timeout. + * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 96ea93d5ba..9250cabc70 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -298,7 +298,7 @@ var Session = function (server, version, socket, options) { heartbeatInterval: options.heartbeatInterval, heartbeatTimeout: options.heartbeatTimeout, onTimeout: function () { - self.destroy(); + self.close(); }, sendPing: function () { self.send({msg: 'ping'}); From 5fe931056b810359922311a4dcc218d198da5044 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Fri, 9 May 2014 16:30:05 -0700 Subject: [PATCH 035/189] Better error when releasing an unbuilt git sha --- scripts/admin/publish-release/server/publish-release.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/admin/publish-release/server/publish-release.js b/scripts/admin/publish-release/server/publish-release.js index 19b8c8abf5..75caa7c2cf 100644 --- a/scripts/admin/publish-release/server/publish-release.js +++ b/scripts/admin/publish-release/server/publish-release.js @@ -73,6 +73,9 @@ var configureS3 = function () { var getManifest = function(s3, gitSha) { var manifestMetas = list3FilesWithPrefix( s3, ["unpublished", gitSha, "release.json-"].join("/")); + if (! manifestMetas) + die("A build for " + gitSha + " can't be found on S3. Maybe you forgot to build it on Jenkins?"); + var manifests = _.map(manifestMetas, function (meta) { return s3.GetObject({ BucketName: WAREHOUSE_BUCKET, @@ -103,6 +106,9 @@ var list3FilesWithPrefix = function (s3, prefix) { Prefix: prefix }).Body.ListBucketResult.Contents; + if (! artifacts) + return null; + // We support 3 platforms. if (artifacts.length !== 3) throw new Error("Expected three artifacts with prefix " + prefix + From 758a2d260ebf4c14593243e047f62a7bcfe4b5ec Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 12 May 2014 13:53:38 -0700 Subject: [PATCH 036/189] Add an XXX about only_credential_secret_for_test --- packages/oauth/oauth_server.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index 0accb7cf4c..d0c529bb07 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -156,6 +156,10 @@ OAuth._renderOauthResults = function(res, query, credentialSecret) { // `only_credential_secret_for_test` parameter, which just returns the // credential secret without any surrounding HTML. (The test needs to // be able to easily grab the secret and use it to log in.) + // + // XXX only_credential_secret_for_test could be useful for other + // things beside tests, like command-line clients. We should give it a + // real name and serve the credential secret in JSON. if (query.only_credential_secret_for_test) { res.writeHead(200, {'Content-Type': 'text/html'}); res.end(credentialSecret, 'utf-8'); From 9f477e974d9e7d2bb4f871622c5aa17bd741abd2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 14 May 2014 17:20:50 -0700 Subject: [PATCH 037/189] Remove statement about Rails and Django Fixes #2142. Apparently Django doesn't work precisely this way. --- docs/client/concepts.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/client/concepts.html b/docs/client/concepts.html index b00c4f29a7..9ef68a965e 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -140,9 +140,8 @@ functions, available under the `Template` namespace. It's a really convenient way to ship HTML templates to the client. See the [templates](#livehtmltemplates) section for more. -Lastly, the Meteor server will serve any files under the `public` -directory, just like in a Rails or Django project. This is the place -for images, `favicon.ico`, `robots.txt`, and anything else. +Lastly, the Meteor server will serve any files under the `public` directory. +This is the place for images, `favicon.ico`, `robots.txt`, and anything else. It is best to write your application in such a way that it is insensitive to the order in which files are loaded, for example by From b46edf3c95c5a1c4ed102f99fe1e2fb3063e8040 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Thu, 24 Apr 2014 21:53:06 -0700 Subject: [PATCH 038/189] Clean up event handlers (untested) Needs manual testing and then a unit test (that exercises multiple eventTypes as well). --- History.md | 2 ++ packages/ui/domrange.js | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index fe79223e86..4f29ddd980 100644 --- a/History.md +++ b/History.md @@ -103,6 +103,8 @@ Patches contributed by GitHub users awwx * Clean up autoruns when calling `UI.toHTML`. +* Properly clean up event listeners when removing templates. + * Add support for `{{!-- block comments --}}` in Spacebars. Block comments may contain `}}`, so they are more useful than `{{! normal comments}}` for commenting out sections of Spacebars templates. diff --git a/packages/ui/domrange.js b/packages/ui/domrange.js index cc0460cd41..316eef921b 100644 --- a/packages/ui/domrange.js +++ b/packages/ui/domrange.js @@ -130,7 +130,12 @@ var rangeRemoved = function (range) { if (range._rangeDict) delete range._rangeDict[range._rangeId]; - // XXX clean up events in $_uievents + // clean up events + if (range.stopHandles) { + for (var i = 0; i < range.stopHandles.length; i++) + range.stopHandles[i].stop(); + range.stopHandles = null; + } // notify component of removal if (range.removed) @@ -183,6 +188,8 @@ var DomRange = function () { this.isParented = false; this.isRemoved = false; + + this.stopHandles = null; }; _extend(DomRange.prototype, { @@ -971,6 +978,7 @@ DomRange.prototype.on = function (events, selector, handler) { selector = null; } + var newHandlerRecs = []; for (var i = 0, N = eventTypes.length; i < N; i++) { var type = eventTypes[i]; @@ -986,6 +994,7 @@ DomRange.prototype.on = function (events, selector, handler) { var handlerList = info.handlers; var handlerRec = new HandlerRec( parentNode, type, selector, handler, this); + newHandlerRecs.push(handlerRec); handlerRec.bind(); handlerList.push(handlerRec); // move handlers of enclosing ranges to end @@ -1005,6 +1014,31 @@ DomRange.prototype.on = function (events, selector, handler) { } } } + + this.stopHandles = (this.stopHandles || []); + this.stopHandles.push({ + // closes over just `parentNode` and `newHandlerRecs` + stop: function () { + var eventDict = parentNode.$_uievents; + if (! eventDict) + return; + + for (var i = 0; i < newHandlerRecs.length; i++) { + var handlerToRemove = newHandlerRecs[i]; + var info = eventDict[handlerToRemove].type; + if (! info) + continue; + var handlerList = info.handlers; + for (var j = handlerList.length - 1; j >= 0; j--) { + if (handlerList[j] === handlerToRemove) { + handlerToRemove.unbind(); + handlerList.splice(j, 1); // remove handlerList[j] + } + } + } + newHandlerRecs.length = 0; + } + }); }; // Returns true if element a contains node b and is not node b. From 871277addee416f3773ad6a43633775d745e9f81 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Thu, 15 May 2014 13:46:40 -0700 Subject: [PATCH 039/189] Fix typo in DomRange event handler cleanup --- packages/ui/domrange.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/domrange.js b/packages/ui/domrange.js index 316eef921b..a8f88bfb82 100644 --- a/packages/ui/domrange.js +++ b/packages/ui/domrange.js @@ -1025,7 +1025,7 @@ DomRange.prototype.on = function (events, selector, handler) { for (var i = 0; i < newHandlerRecs.length; i++) { var handlerToRemove = newHandlerRecs[i]; - var info = eventDict[handlerToRemove].type; + var info = eventDict[handlerToRemove.type]; if (! info) continue; var handlerList = info.handlers; From 9ebf963be6826fd30573edd276bd6a170baae144 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Thu, 15 May 2014 13:55:55 -0700 Subject: [PATCH 040/189] Add a test for cleaning up event handlers when template is destroyed --- packages/spacebars-tests/template_tests.html | 10 +++++++ packages/spacebars-tests/template_tests.js | 28 ++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/packages/spacebars-tests/template_tests.html b/packages/spacebars-tests/template_tests.html index 0f2e733dcd..ba8f20fa27 100644 --- a/packages/spacebars-tests/template_tests.html +++ b/packages/spacebars-tests/template_tests.html @@ -726,3 +726,13 @@ Hi there! + + + + diff --git a/packages/spacebars-tests/template_tests.js b/packages/spacebars-tests/template_tests.js index f6eff667fd..a222349045 100644 --- a/packages/spacebars-tests/template_tests.js +++ b/packages/spacebars-tests/template_tests.js @@ -2021,3 +2021,31 @@ Tinytest.add( checkAttrs(" javascript:alert(1)", false); } ); + +Tinytest.add( + "spacebars - template - event handlers get cleaned up with template is removed", + function (test) { + var tmpl = Template.spacebars_test_event_handler_cleanup; + var subtmpl = Template.spacebars_test_event_handler_cleanup_sub; + + var rv = new ReactiveVar(true); + tmpl.foo = function () { + return rv.get(); + }; + + subtmpl.events({ + "click/mouseover": function () { } + }); + + var div = renderToDiv(tmpl); + + test.equal(div.$_uievents["click"].handlers.length, 1); + test.equal(div.$_uievents["mouseover"].handlers.length, 1); + + rv.set(false); + Deps.flush(); + + test.equal(div.$_uievents["click"].handlers.length, 0); + test.equal(div.$_uievents["mouseover"].handlers.length, 0); + } +); From a3d71cae7458e462daa621493b1c8bf78d4ea265 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 16 May 2014 11:09:37 -0700 Subject: [PATCH 041/189] Add credentialSecret to `Google.retrieveCredential` I missed this one in 0fa591bc5. --- packages/google/google_server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google/google_server.js b/packages/google/google_server.js index 2a4f3bc986..b4dd6f2fd4 100644 --- a/packages/google/google_server.js +++ b/packages/google/google_server.js @@ -78,6 +78,6 @@ var getIdentity = function (accessToken) { }; -Google.retrieveCredential = function(credentialToken) { - return OAuth.retrieveCredential(credentialToken); +Google.retrieveCredential = function(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); }; From 78b280ef88aef02d96691b5ecf514ecbf6123957 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 16 May 2014 12:40:01 -0700 Subject: [PATCH 042/189] Fix PollingObserveDriver error handling The getRawObjects call can throw (eg, if you can't connect to the mongo server for too long). A few pieces of state were being corrupted in that case: - self._results was being set too early, leading to 'first' not being set on future _pollMongo calls, and_multiplexer.ready() never being called. This had two effects: - The observe (and thus any subscription) would never become ready(). Due to deduping, *no observe on this query* would ever become ready either. This also implies that the observeChanges that are part of _publishCursor would never return, so the sub.onStop would never get called, so the observeHandle would never stop, leading not only to leaks, but for an inability for that query to ever stop being deduped with the corrupted PollingObserveDriver! - The onFlush calls would throw a "not ready" error instead of calling the callback, so (a) errors would be logged and (b) write fences would never be closed Fixed this by not writing to self._results at the top of the function. - writesForCycle was being lost, so those write fences would never close. Fixed this by pushing writesForCycle back onto _pendingWrites if getRawObjects throws. --- .../mongo-livedata/polling_observe_driver.js | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/mongo-livedata/polling_observe_driver.js b/packages/mongo-livedata/polling_observe_driver.js index c1c700d0c1..9bfca7ec44 100644 --- a/packages/mongo-livedata/polling_observe_driver.js +++ b/packages/mongo-livedata/polling_observe_driver.js @@ -126,10 +126,11 @@ _.extend(PollingObserveDriver.prototype, { --self._pollsScheduledButNotStarted; var first = false; - if (!self._results) { + var oldResults = self._results; + if (!oldResults) { first = true; // XXX maybe use OrderedDict instead? - self._results = self._ordered ? [] : new LocalCollection._IdMap; + oldResults = self._ordered ? [] : new LocalCollection._IdMap; } self._testOnlyPollCallback && self._testOnlyPollCallback(); @@ -138,25 +139,39 @@ _.extend(PollingObserveDriver.prototype, { var writesForCycle = self._pendingWrites; self._pendingWrites = []; - // Get the new query results. (These calls can yield.) - if (!first) - self._synchronousCursor.rewind(); - var newResults = self._synchronousCursor.getRawObjects(self._ordered); - var oldResults = self._results; + // Always rewind the cursor; it's a no-op the first time, but better safe + // than sorry (eg, if the first call to getRawObjects throws, the cursor + // needs rewinding even though 'first' is true). + self._synchronousCursor.rewind(); - // Run diffs. (This can yield too.) + // Get the new query results. (This yields.) + try { + var newResults = self._synchronousCursor.getRawObjects(self._ordered); + } catch (e) { + // getRawObjects can throw if we're having trouble talking to the + // database. That's fine --- we will repoll later anyway. But we should + // make sure not to lose track of this cycle's writes. + Array.prototype.push.apply(self._pendingWrites, writesForCycle); + throw e; + } + + // Run diffs. if (!self._stopped) { LocalCollection._diffQueryChanges( self._ordered, oldResults, newResults, self._multiplexer); } - // Replace self._results atomically. - self._results = newResults; - - // Signals the multiplexer to call all initial adds. + // Signals the multiplexer to allow all observeChanges calls that share this + // multiplexer to return. (This happens asynchronously, via the + // multiplexer's queue.) if (first) self._multiplexer.ready(); + // Replace self._results atomically. (This assignment is what makes `first` + // stay through on the next cycle, so we've waited until after we've + // committed to ready-ing the multiplexer.) + self._results = newResults; + // Once the ObserveMultiplexer has processed everything we've done in this // round, mark all the writes which existed before this call as // commmitted. (If new writes have shown up in the meantime, there'll From e86578e52b473394fb7e1acddec1584cfd095ad1 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Sat, 17 May 2014 09:50:53 -0700 Subject: [PATCH 043/189] Fix accidentally-hardcoded Twitter URL in oauth1 Fixes #2154. --- packages/oauth1/oauth1_server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index c4ba5784ce..f2f3ab008a 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -11,7 +11,8 @@ OAuth._requestHandlers['1'] = function (service, query, res) { if (query.requestTokenAndRedirect) { // step 1 - get and store a request token - var callbackUrl = Meteor.absoluteUrl("_oauth/twitter?close&state=" + + var callbackUrl = Meteor.absoluteUrl("_oauth/" + service.serviceName + + "?close&state=" + query.state); // Get a request token to start auth process From 6f8e18b5d7dee3867bdf838771bd3c3a7bb004c5 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Sun, 18 May 2014 01:39:53 -0700 Subject: [PATCH 044/189] Add short aliases for '--add' and '--remove' options in "meteor authorized" Fixes #2155 --- tools/commands.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index 10f1e73a05..8f881606d7 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -951,8 +951,8 @@ main.registerCommand({ minArgs: 1, maxArgs: 1, options: { - add: { type: String }, - remove: { type: String }, + add: { type: String, short: "a" }, + remove: { type: String, short: "r" }, list: { type: Boolean } } }, function (options) { From 9c4e305178388b39dc887716f9ccace7e774d20a Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Sun, 18 May 2014 12:42:37 -0700 Subject: [PATCH 045/189] Deduplicate the "inherits" helper in Meteor package Meteor._inherits is the way to go. --- packages/meteor/errors.js | 10 +--------- packages/meteor/helpers.js | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/meteor/errors.js b/packages/meteor/errors.js index 06e315f94b..bc76bdd8aa 100644 --- a/packages/meteor/errors.js +++ b/packages/meteor/errors.js @@ -1,11 +1,3 @@ -// http://davidshariff.com/blog/javascript-inheritance-patterns/ -var inherits = function (child, parent) { - var tmp = function () {}; - tmp.prototype = parent.prototype; - child.prototype = new tmp; - child.prototype.constructor = child; -}; - // Makes an error subclass which properly contains a stack trace in most // environments. constructor can set fields on `this` (and should probably set // `message`, which is what gets displayed at the top of a stack trace). @@ -34,7 +26,7 @@ Meteor.makeErrorType = function (name, constructor) { return self; }; - inherits(errorClass, Error); + Meteor._inherits(errorClass, Error); return errorClass; }; diff --git a/packages/meteor/helpers.js b/packages/meteor/helpers.js index 341df557b9..9ed6f64533 100644 --- a/packages/meteor/helpers.js +++ b/packages/meteor/helpers.js @@ -115,7 +115,7 @@ _.extend(Meteor, { // Sets child's prototype to a new object whose prototype is parent's // prototype. Used as: - // Meteor._inherit(ClassB, ClassA). + // Meteor._inherits(ClassB, ClassA). // _.extend(ClassB.prototype, { ... }) // Inspired by CoffeeScript's `extend` and Google Closure's `goog.inherits`. _inherits: function (Child, Parent) { From 70d98d2484f208937e70fd58f80c5fedbac68edb Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 16 May 2014 11:15:50 -0700 Subject: [PATCH 046/189] Clean up Session.destroy/Session.close. Session.destroy is now folded in to Session.close. Server._closeSession is renamed to Server._removeSession, since the `destroy()` call inside `_closeSession` was always a no-op. --- packages/livedata/livedata_server.js | 30 ++++++++++------------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 9250cabc70..faa02aad3c 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -403,13 +403,16 @@ _.extend(Session.prototype, { }); }, - // Destroy this session. Stop all processing and tear everything - // down. If a socket was attached, close it. - destroy: function () { + // Destroy this session and unregister it at the server. + close: function () { var self = this; + // Destroy this session, even if it's not registered at the + // server. Stop all processing and tear everything down. If a socket + // was attached, close it. + // Already destroyed. - if (!self.inQueue) + if (! self.inQueue) return; if (self.heartbeat) { @@ -430,7 +433,7 @@ _.extend(Session.prototype, { "livedata", "sessions", -1); Meteor.defer(function () { - // stop callbacks can yield, so we defer this on destroy. + // stop callbacks can yield, so we defer this on close. // sub._isDeactivated() detects that we set inQueue to null and // treats it as semi-deactivated (it will ignore incoming callbacks, etc). self._deactivateAllSubscriptions(); @@ -441,19 +444,9 @@ _.extend(Session.prototype, { callback(); }); }); - }, - // Destroy this session and unregister it at the server. - close: function () { - var self = this; - - // Unconditionally destroy this session, even if it's not - // registered at the server. - self.destroy(); - - // Unregister the session. This will also call `destroy`, but - // that's OK because `destroy` is idempotent. - self.server._closeSession(self); + // Unregister the session. + self.server._removeSession(self); }, // Send a message (doing nothing if no socket is connected right now.) @@ -1326,11 +1319,10 @@ _.extend(Server.prototype, { } }, - _closeSession: function (session) { + _removeSession: function (session) { var self = this; if (self.sessions[session.id]) { delete self.sessions[session.id]; - session.destroy(); } }, From d40889aff9c3ddd5f1bb87f70898337543517943 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 20 May 2014 10:06:55 -0700 Subject: [PATCH 047/189] Remove duplicate History item --- History.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/History.md b/History.md index 4f29ddd980..6ed362f345 100644 --- a/History.md +++ b/History.md @@ -4,8 +4,6 @@ https://github.com/npm/npm/issues/3265 instead of passing `--force` to `npm install`. -* Fix 0.8.1 regression preventing clients from specifying `_id` on insert. - * Run server tests from multiple clients serially instead of in parallel. This allows testing features that modify global server state. #2088 From c0491e2aa72e305d65c05a13db3f23280fffb6b9 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 20 May 2014 10:08:04 -0700 Subject: [PATCH 048/189] Add History item for e86578e5. --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index 6ed362f345..15723d69b9 100644 --- a/History.md +++ b/History.md @@ -18,6 +18,10 @@ * Fix memory leak (introduced in 0.8.1) by making sure to unregister sessions at the server when they are closed due to heartbeat timeout. +* Fix hardcoded Twitter URL in `oauth1` package. This fixes a regression + in 0.8.0.1 that broke Atmosphere packages that do OAuth1 + logins. #2154. + * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) From cad083107cc5abfe448eff5bc314466c4c07008a Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 20 May 2014 10:10:38 -0700 Subject: [PATCH 049/189] Add some History items --- History.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/History.md b/History.md index 15723d69b9..461b88cae7 100644 --- a/History.md +++ b/History.md @@ -22,6 +22,12 @@ in 0.8.0.1 that broke Atmosphere packages that do OAuth1 logins. #2154. +* Add `credentialSecret` argument to `Google.retrieveCredential`, which + was forgotten in a previous release. + +* Fix a Blaze memory leak by cleaning up event handlers when a template + instance is destroyed. #1997 + * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) From 0a4b7b55cc80ef6099b6b5e093736f773aa3e20a Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 20 May 2014 11:38:05 -0700 Subject: [PATCH 050/189] Use underscore in oauth-encryption package --- packages/oauth-encryption/package.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/oauth-encryption/package.js b/packages/oauth-encryption/package.js index 598ffccb43..8ab14c1e24 100644 --- a/packages/oauth-encryption/package.js +++ b/packages/oauth-encryption/package.js @@ -7,6 +7,7 @@ Package.describe({ Package.on_use(function (api) { api.export("OAuthEncryption", ["server"]); + api.use("underscore"); api.add_files("encrypt.js", ["server"]); }); From 04e2a74c400888983f7d8429885f6ddc53d3505b Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 20 May 2014 12:06:10 -0700 Subject: [PATCH 051/189] Use assets instead of Function.toString for multiline strings in tests --- .../assets/markdown_basic.html | 49 +++++ .../assets/markdown_each1.html | 15 ++ .../assets/markdown_each2.html | 19 ++ .../spacebars-tests/assets/markdown_if1.html | 19 ++ .../spacebars-tests/assets/markdown_if2.html | 19 ++ packages/spacebars-tests/package.js | 10 + packages/spacebars-tests/template_tests.js | 200 +++++------------- .../spacebars-tests/template_tests_server.js | 7 + 8 files changed, 193 insertions(+), 145 deletions(-) create mode 100644 packages/spacebars-tests/assets/markdown_basic.html create mode 100644 packages/spacebars-tests/assets/markdown_each1.html create mode 100644 packages/spacebars-tests/assets/markdown_each2.html create mode 100644 packages/spacebars-tests/assets/markdown_if1.html create mode 100644 packages/spacebars-tests/assets/markdown_if2.html create mode 100644 packages/spacebars-tests/template_tests_server.js diff --git a/packages/spacebars-tests/assets/markdown_basic.html b/packages/spacebars-tests/assets/markdown_basic.html new file mode 100644 index 0000000000..dbb49ca965 --- /dev/null +++ b/packages/spacebars-tests/assets/markdown_basic.html @@ -0,0 +1,49 @@ +

hi +/each}}

+ +

hi +/each}}

+ +
    +
  • hi
  • +
  • /each}}

  • +
  • hi

  • +
  • /each}}
  • +
+ +

some paragraph to fix showdown's four space parsing below.

+ +
<i>hi</i>
+/each}}
+
+<b><i>hi</i></b>
+<b>/each}}</b>
+
+ +

&gt

+ +
    +
  • &gt
  • +
+ +

&gt

+ +
&gt
+
+ +

>

+ +
    +
  • >
  • +
+ +

&gt;

+ +
&gt;
+
+ +

<i>hi</i> +/each}}

+ +

<b><i>hi</i></b> +<b>/each}}

diff --git a/packages/spacebars-tests/assets/markdown_each1.html b/packages/spacebars-tests/assets/markdown_each1.html new file mode 100644 index 0000000000..111f7e535b --- /dev/null +++ b/packages/spacebars-tests/assets/markdown_each1.html @@ -0,0 +1,15 @@ +

+ +
    +
  • +
  • +
+ +

some paragraph to fix showdown's four space parsing below.

+ +
<b></b>
+
+ +

``

+ +

<b></b>

diff --git a/packages/spacebars-tests/assets/markdown_each2.html b/packages/spacebars-tests/assets/markdown_each2.html new file mode 100644 index 0000000000..52613cfb61 --- /dev/null +++ b/packages/spacebars-tests/assets/markdown_each2.html @@ -0,0 +1,19 @@ +

item

+ +

item

+ +
    +
  • item

  • +
  • item

  • +
+ +

some paragraph to fix showdown's four space parsing below.

+ +
item
+
+<b>item</b>
+
+ +

item

+ +

<b>item</b>

diff --git a/packages/spacebars-tests/assets/markdown_if1.html b/packages/spacebars-tests/assets/markdown_if1.html new file mode 100644 index 0000000000..378522b316 --- /dev/null +++ b/packages/spacebars-tests/assets/markdown_if1.html @@ -0,0 +1,19 @@ +

false

+ +

false

+ +
    +
  • false

  • +
  • false

  • +
+ +

some paragraph to fix showdown's four space parsing below.

+ +
false
+
+<b>false</b>
+
+ +

false

+ +

<b>false</b>

diff --git a/packages/spacebars-tests/assets/markdown_if2.html b/packages/spacebars-tests/assets/markdown_if2.html new file mode 100644 index 0000000000..603c070c8a --- /dev/null +++ b/packages/spacebars-tests/assets/markdown_if2.html @@ -0,0 +1,19 @@ +

true

+ +

true

+ +
    +
  • true

  • +
  • true

  • +
+ +

some paragraph to fix showdown's four space parsing below.

+ +
true
+
+<b>true</b>
+
+ +

true

+ +

<b>true</b>

diff --git a/packages/spacebars-tests/package.js b/packages/spacebars-tests/package.js index 3a4628d47b..c65d18058a 100644 --- a/packages/spacebars-tests/package.js +++ b/packages/spacebars-tests/package.js @@ -18,4 +18,14 @@ Package.on_test(function (api) { 'template_tests.html', 'template_tests.js' ], 'client'); + + api.add_files('template_tests_server.js', 'server'); + + api.add_files([ + 'assets/markdown_basic.html', + 'assets/markdown_if1.html', + 'assets/markdown_if2.html', + 'assets/markdown_each1.html', + 'assets/markdown_each2.html' + ], 'server', { isAsset: true }); }); diff --git a/packages/spacebars-tests/template_tests.js b/packages/spacebars-tests/template_tests.js index a222349045..5a39500d44 100644 --- a/packages/spacebars-tests/template_tests.js +++ b/packages/spacebars-tests/template_tests.js @@ -975,166 +975,76 @@ var textFromFunction = function(f) { return str; }; -Tinytest.add('spacebars - templates - #markdown - basic', function (test) { +Tinytest.addAsync('spacebars - templates - #markdown - basic', function (test, onComplete) { var tmpl = Template.spacebars_template_test_markdown_basic; tmpl.obj = {snippet: "hi"}; tmpl.hi = function () { return this.snippet; }; var div = renderToDiv(tmpl); - test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(textFromFunction(function () { /* -[[[

hi -/each}}

-

hi -/each}}

- -
    -
  • hi
  • -
  • /each}}

  • -
  • hi

  • -
  • /each}}
  • -
- -

some paragraph to fix showdown's four space parsing below.

- -
<i>hi</i>
-/each}}
-
-<b><i>hi</i></b>
-<b>/each}}</b>
-
- -

&gt

- -
    -
  • &gt
  • -
- -

&gt

- -
&gt
-
- -

>

- -
    -
  • >
  • -
- -

&gt;

- -
&gt;
-
- -

<i>hi</i> -/each}}

- -

<b><i>hi</i></b> -<b>/each}}

]]] */ - }))); + Meteor.call("getAsset", "markdown_basic.html", function (err, html) { + test.isFalse(err); + test.equal(canonicalizeHtml(div.innerHTML), + canonicalizeHtml(html)); + onComplete(); + }); }); -Tinytest.add('spacebars - templates - #markdown - if', function (test) { - var tmpl = Template.spacebars_template_test_markdown_if; - var R = new ReactiveVar(false); - tmpl.cond = function () { return R.get(); }; +testAsyncMulti('spacebars - templates - #markdown - if', [ + function (test, expect) { + var self = this; + Meteor.call("getAsset", "markdown_if1.html", expect(function (err, html) { + test.isFalse(err); + self.html1 = html; + })); + Meteor.call("getAsset", "markdown_if2.html", expect(function (err, html) { + test.isFalse(err); + self.html2 = html; + })); + }, - var div = renderToDiv(tmpl); - test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(textFromFunction(function () { /* -[[[

false

+ function (test, expect) { + var self = this; + var tmpl = Template.spacebars_template_test_markdown_if; + var R = new ReactiveVar(false); + tmpl.cond = function () { return R.get(); }; -

false

+ var div = renderToDiv(tmpl); + test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(self.html1)); + R.set(true); + Deps.flush(); + test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(self.html2)); + } +]); -
    -
  • false

  • -
  • false

  • -
+testAsyncMulti('spacebars - templates - #markdown - each', [ + function (test, expect) { + var self = this; + Meteor.call("getAsset", "markdown_each1.html", expect(function (err, html) { + test.isFalse(err); + self.html1 = html; + })); + Meteor.call("getAsset", "markdown_each2.html", expect(function (err, html) { + test.isFalse(err); + self.html2 = html; + })); + }, -

some paragraph to fix showdown's four space parsing below.

+ function (test, expect) { + var self = this; + var tmpl = Template.spacebars_template_test_markdown_each; + var R = new ReactiveVar([]); + tmpl.seq = function () { return R.get(); }; -
false
+    var div = renderToDiv(tmpl);
+    test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(self.html1));
 
-<b>false</b>
-
- -

false

- -

<b>false</b>

]]] */ - }))); - R.set(true); - Deps.flush(); - test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(textFromFunction(function () { /* -[[[

true

- -

true

- -
    -
  • true

  • -
  • true

  • -
- -

some paragraph to fix showdown's four space parsing below.

- -
true
-
-<b>true</b>
-
- -

true

- -

<b>true</b>

]]] */ - }))); -}); - -Tinytest.add('spacebars - templates - #markdown - each', function (test) { - var tmpl = Template.spacebars_template_test_markdown_each; - var R = new ReactiveVar([]); - tmpl.seq = function () { return R.get(); }; - - var div = renderToDiv(tmpl); - test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(textFromFunction(function () { /* -[[[

- -
    -
  • -
  • -
- -

some paragraph to fix showdown's four space parsing below.

- -
<b></b>
-
- -

``

- -

<b></b>

]]] */ - }))); - - R.set(["item"]); - Deps.flush(); - test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(textFromFunction(function () { /* -[[[

item

- -

item

- -
    -
  • item

  • -
  • item

  • -
- -

some paragraph to fix showdown's four space parsing below.

- -
item
-
-<b>item</b>
-
- -

item

- -

<b>item</b>

]]] */ - }))); -}); + R.set(["item"]); + Deps.flush(); + test.equal(canonicalizeHtml(div.innerHTML), canonicalizeHtml(self.html2)); + } +]); Tinytest.add('spacebars - templates - #markdown - inclusion', function (test) { var tmpl = Template.spacebars_template_test_markdown_inclusion; diff --git a/packages/spacebars-tests/template_tests_server.js b/packages/spacebars-tests/template_tests_server.js new file mode 100644 index 0000000000..e120b869dd --- /dev/null +++ b/packages/spacebars-tests/template_tests_server.js @@ -0,0 +1,7 @@ +var path = Npm.require("path"); + +Meteor.methods({ + getAsset: function (filename) { + return Assets.getText(path.join("assets", filename)); + } +}); From 5a9a8a84521a3c07e6401ac6476b57eab9a92208 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 20 May 2014 12:17:13 -0700 Subject: [PATCH 052/189] Remove now-dead `textFromFunction` --- packages/spacebars-tests/template_tests.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/spacebars-tests/template_tests.js b/packages/spacebars-tests/template_tests.js index 5a39500d44..9b30d3d625 100644 --- a/packages/spacebars-tests/template_tests.js +++ b/packages/spacebars-tests/template_tests.js @@ -965,16 +965,6 @@ Tinytest.add('spacebars - templates - constant #each argument', function (test) 'foo bar 2'); }); -// extract a multi-line string from a comment within a function. -// @param f {Function} eg function () { /* [[[...content...]]] */ } -// @returns {String} eg "content" -var textFromFunction = function(f) { - var str = f.toString().match(/\[\[\[([\S\s]*)\]\]\]/m)[1]; - // remove line number comments added by linker - str = str.replace(/[ ]*\/\/ \d+$/gm, ''); - return str; -}; - Tinytest.addAsync('spacebars - templates - #markdown - basic', function (test, onComplete) { var tmpl = Template.spacebars_template_test_markdown_basic; tmpl.obj = {snippet: "hi"}; From a5e07f836611178abba37a744ed683c53571aa45 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Wed, 21 May 2014 11:42:45 -0700 Subject: [PATCH 053/189] observe-sequence depends on underscore --- packages/observe-sequence/package.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/observe-sequence/package.js b/packages/observe-sequence/package.js index 5009db78d2..a7c728704c 100644 --- a/packages/observe-sequence/package.js +++ b/packages/observe-sequence/package.js @@ -6,6 +6,7 @@ Package.describe({ Package.on_use(function (api) { api.use('deps'); api.use('minimongo'); // for idStringify + api.use('underscore'); api.export('ObserveSequence'); api.add_files(['observe_sequence.js']); }); From df9af60fe2a96b4816920d7ebaa2d30e3faf0352 Mon Sep 17 00:00:00 2001 From: Avital Oliver Date: Wed, 21 May 2014 11:58:20 -0700 Subject: [PATCH 054/189] observe-sequence depends on random --- packages/observe-sequence/package.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/observe-sequence/package.js b/packages/observe-sequence/package.js index a7c728704c..7604f3d6a7 100644 --- a/packages/observe-sequence/package.js +++ b/packages/observe-sequence/package.js @@ -7,6 +7,7 @@ Package.on_use(function (api) { api.use('deps'); api.use('minimongo'); // for idStringify api.use('underscore'); + api.use('random'); api.export('ObserveSequence'); api.add_files(['observe_sequence.js']); }); From 91aa5204ed55535ef3a386a1edba24d1ae05490a Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 21 May 2014 11:47:44 -0700 Subject: [PATCH 055/189] Allow check in non-Fiber server code Fixes #2136. --- History.md | 2 ++ packages/check/match.js | 9 ++++++++- packages/check/match_test.js | 27 +++++++++++++++++++++++++++ packages/meteor/dynamics_browser.js | 4 ++++ packages/meteor/dynamics_nodejs.js | 19 +++++++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 461b88cae7..1437500084 100644 --- a/History.md +++ b/History.md @@ -28,6 +28,8 @@ * Fix a Blaze memory leak by cleaning up event handlers when a template instance is destroyed. #1997 +* Allow `check` to work on the server outside of a Fiber. #2136 + * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) diff --git a/packages/check/match.js b/packages/check/match.js index 60783c6c97..73c7246883 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -7,7 +7,14 @@ var currentArgumentChecker = new Meteor.EnvironmentVariable; check = function (value, pattern) { // Record that check got called, if somebody cared. - var argChecker = currentArgumentChecker.get(); + // + // We use getOrNullIfOutsideFiber so that it's OK to call check() + // from non-Fiber server contexts; the downside is that if you forget to + // bindEnvironment on some random callback in your method/publisher, + // it might not find the argumentChecker and you'll get an error about + // not checking an argument that it looks like you're checking (instead + // of just getting a "Node code must run in a Fiber" error). + var argChecker = currentArgumentChecker.getOrNullIfOutsideFiber(); if (argChecker) argChecker.checking(value); try { diff --git a/packages/check/match_test.js b/packages/check/match_test.js index 2707e4aebd..c8f35d2e08 100644 --- a/packages/check/match_test.js +++ b/packages/check/match_test.js @@ -257,3 +257,30 @@ Tinytest.add("check - Match error path", function (test) { match({ "return": 0 }, { "return": String }, "[\"return\"]"); }); +// Regression test for https://github.com/meteor/meteor/issues/2136 +Meteor.isServer && Tinytest.addAsync("check - non-fiber check works", function (test, onComplete) { + var Fiber = Npm.require('fibers'); + + // We can only call test.isTrue inside normal Meteor Fibery code, so give us a + // bindEnvironment way to get back. + var report = Meteor.bindEnvironment(function (success) { + test.isTrue(success); + onComplete(); + }); + + // Get out of a fiber with process.nextTick and ensure that we can still use + // check. + process.nextTick(function () { + var success = true; + if (Fiber.current) + success = false; + if (success) { + try { + check(true, Boolean); + } catch (e) { + success = false; + } + } + report(success); + }); +}); diff --git a/packages/meteor/dynamics_browser.js b/packages/meteor/dynamics_browser.js index 06c6860612..4700f1f87d 100644 --- a/packages/meteor/dynamics_browser.js +++ b/packages/meteor/dynamics_browser.js @@ -12,6 +12,10 @@ _.extend(Meteor.EnvironmentVariable.prototype, { return currentValues[this.slot]; }, + getOrNullIfOutsideFiber: function () { + return this.get(); + }, + withValue: function (value, func) { var saved = currentValues[this.slot]; try { diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 6dc2add875..87156cb56c 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -24,6 +24,25 @@ _.extend(Meteor.EnvironmentVariable.prototype, { Fiber.current._meteor_dynamics[this.slot]; }, + // Most Meteor code ought to run inside a fiber, and the + // _nodeCodeMustBeInFiber assertion helps you remember to include appropriate + // bindEnvironment calls (which will get you the *right value* for your + // environment variables, on the server). + // + // In some very special cases, it's more important to run Meteor code on the + // server in non-Fiber contexts rather than to strongly enforce the safeguard + // against forgetting to use bindEnvironment. For example, using `check` in + // some top-level constructs like connect handlers without needing unnecessary + // Fibers on every request is more important that possibly failing to find the + // correct argumentChecker. So this function is just like get(), but it + // returns null rather than throwing when called from outside a Fiber. (On the + // client, it is identical to get().) + getOrNullIfOutsideFiber: function () { + if (!Fiber.current) + return null; + return this.get(); + }, + withValue: function (value, func) { Meteor._nodeCodeMustBeInFiber(); From 4fd4383bc0e2c66cbdc3e2eab9a3922a86a7b3b4 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 21 May 2014 14:05:22 -0700 Subject: [PATCH 056/189] EJSON custom type conversion functions can't yield Code ought to be able to parse and stringify EJSON values without having to worry about concurrency issues. Related to #2136. --- History.md | 2 ++ packages/ejson/ejson.js | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 1437500084..11ced8dadd 100644 --- a/History.md +++ b/History.md @@ -30,6 +30,8 @@ * Allow `check` to work on the server outside of a Fiber. #2136 +* EJSON custom type conversion functions should not be permitted to yield. #2136 + * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index ec846156f9..798fb38f4b 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -110,14 +110,19 @@ var builtinConverters = [ return EJSON._isCustomType(obj); }, toJSONValue: function (obj) { - return {$type: obj.typeName(), $value: obj.toJSONValue()}; + var jsonValue = Meteor._noYieldsAllowed(function () { + return obj.toJSONValue(); + }); + return {$type: obj.typeName(), $value: jsonValue}; }, fromJSONValue: function (obj) { var typeName = obj.$type; if (!_.has(customTypes, typeName)) throw new Error("Custom EJSON type " + typeName + " is not defined"); var converter = customTypes[typeName]; - return converter(obj.$value); + return Meteor._noYieldsAllowed(function () { + return converter(obj.$value); + }); } } ]; From 4ac2c4476d6f626fe4365497df38e2bc36ba7df7 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 21 May 2014 14:15:57 -0700 Subject: [PATCH 057/189] History update for 78b280ef88a --- History.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/History.md b/History.md index 11ced8dadd..b3c7f1261a 100644 --- a/History.md +++ b/History.md @@ -32,6 +32,9 @@ * EJSON custom type conversion functions should not be permitted to yield. #2136 +* The legacy polling observe driver handles errors communicating with MongoDB + better and no longer gets "stuck" in some circumstances. + * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) From 29602725ffced81a6e4b8aa919a8dd90b5dd5d38 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 21 May 2014 16:18:59 -0700 Subject: [PATCH 058/189] Update uglify-js from 2.4.7 to 2.4.13 This doesn't fix the bug I was investigating, but probably fixes other bugs. --- History.md | 1 + packages/minifiers/.npm/package/npm-shrinkwrap.json | 8 ++++---- packages/minifiers/package.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/History.md b/History.md index b3c7f1261a..b8e64a67c5 100644 --- a/History.md +++ b/History.md @@ -37,6 +37,7 @@ * Upgraded dependencies: - node: 0.10.28 (from 0.10.26) + - uglify-js: 2.4.13 (from 2.4.7) Patches contributed by GitHub users awwx diff --git a/packages/minifiers/.npm/package/npm-shrinkwrap.json b/packages/minifiers/.npm/package/npm-shrinkwrap.json index 4b424c691b..fbe29a32ed 100644 --- a/packages/minifiers/.npm/package/npm-shrinkwrap.json +++ b/packages/minifiers/.npm/package/npm-shrinkwrap.json @@ -17,13 +17,13 @@ } }, "uglify-js": { - "version": "2.4.7", + "version": "2.4.13", "dependencies": { "async": { - "version": "0.2.9" + "version": "0.2.10" }, "source-map": { - "version": "0.1.31", + "version": "0.1.33", "dependencies": { "amdefine": { "version": "0.1.0" @@ -39,7 +39,7 @@ } }, "uglify-to-browserify": { - "version": "1.0.1" + "version": "1.0.2" } } } diff --git a/packages/minifiers/package.js b/packages/minifiers/package.js index 0815ea3fa4..6f25acdadf 100644 --- a/packages/minifiers/package.js +++ b/packages/minifiers/package.js @@ -4,7 +4,7 @@ Package.describe({ }); Npm.depends({ - "uglify-js": "2.4.7", + "uglify-js": "2.4.13", "css-parse": "https://github.com/reworkcss/css-parse/tarball/aa7e23285375ca621dd20250bac0266c6d8683a5", "css-stringify": "https://github.com/reworkcss/css-stringify/tarball/a7fe6de82e055d41d1c5923ec2ccef06f2a45efa" }); From 2db7490db5a0efde738c58c920e002b28a34234c Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 21 May 2014 16:19:43 -0700 Subject: [PATCH 059/189] Fix IE8 minification bug The minifier changed the two uses of HTMLTag into two different symbols: var n = function r() { var t = this instanceof e.Tag ? this : new r(), n = 0, o = arguments.length && arguments[0]; return o && "object" == typeof o && o.constructor === Object && (t.attrs = o, n++), n < arguments.length && (t.children = Array.prototype.slice.call(arguments, n)), t; }; return n.prototype = new e.Tag(), n.prototype.constructor = n, n.prototype.tagName = t, n; Then, IE8 apparently actually creates two separate objects for 'n' and 'r'; see #3 at http://kiro.me/blog/nfe_dilemma.html So just because n.prototype is an e.Tag doesn't make r.prototype a e.Tag This means that `new r() instanceof e.Tag` is false, and so the first line of the function leads to infinite recursion. I'm not sure if this is an uglify bug as well; dealing well with multiple declarations of the same function may be out of spec. Fixes #2037. --- packages/htmljs/html.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/htmljs/html.js b/packages/htmljs/html.js index f8348d6793..1b41d8e5a9 100644 --- a/packages/htmljs/html.js +++ b/packages/htmljs/html.js @@ -31,7 +31,7 @@ HTML.ensureTag = function (tagName) { // Given "p" create the function `HTML.P`. var makeTagConstructor = function (tagName) { // HTMLTag is the per-tagName constructor of a HTML.Tag subclass - var HTMLTag = function HTMLTag(/*arguments*/) { + var HTMLTag = function (/*arguments*/) { // Work with or without `new`. If not called with `new`, // perform instantiation by recursively calling this constructor. // We can't pass varargs, so pass no args. From 1492da8ed4cc7ab1edf1c6a70f63a2ac312c9871 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Thu, 22 May 2014 12:10:26 -0700 Subject: [PATCH 060/189] Fix docs typo --- docs/client/concepts.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 9ef68a965e..0ed41a18b3 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -447,7 +447,7 @@ extension. In the file, make a `