Commit 655a85fa by Michael Mifsud Committed by GitHub

Prevent options from leaking (#1595)

The options object passed into `render` and `renderSync` is only
shallow copied. This is an issue when nested objects and arrays like
custom importer and functions. In the case of custom function we
wrap the provided function. This wrapped version replaces the provided
one which can then leak back into the calling code. This is an issue
if `render*` is called in a loop like in gulp-sass.

Fixes #1168
parent ea6e470d
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
var path = require('path'), var path = require('path'),
util = require('util'), util = require('util'),
clonedeep = require('lodash.clonedeep'),
errors = require('./errors'), errors = require('./errors'),
sass = require('./extensions'); sass = require('./extensions');
...@@ -170,8 +171,8 @@ function getLinefeed(options) { ...@@ -170,8 +171,8 @@ function getLinefeed(options) {
* @api private * @api private
*/ */
function getOptions(options, cb) { function getOptions(opts, cb) {
options = options || {}; var options = clonedeep(opts || {});
options.sourceComments = options.sourceComments || false; options.sourceComments = options.sourceComments || false;
if (options.hasOwnProperty('file')) { if (options.hasOwnProperty('file')) {
options.file = getInputFile(options); options.file = getInputFile(options);
...@@ -263,8 +264,8 @@ function normalizeFunctionSignature(signature, callback) { ...@@ -263,8 +264,8 @@ function normalizeFunctionSignature(signature, callback) {
* @api public * @api public
*/ */
module.exports.render = function(options, cb) { module.exports.render = function(opts, cb) {
options = getOptions(options, cb); var options = getOptions(opts, cb);
// options.error and options.success are for libsass binding // options.error and options.success are for libsass binding
options.error = function(err) { options.error = function(err) {
...@@ -293,6 +294,7 @@ module.exports.render = function(options, cb) { ...@@ -293,6 +294,7 @@ module.exports.render = function(options, cb) {
if (importer) { if (importer) {
if (Array.isArray(importer)) { if (Array.isArray(importer)) {
options.importer = [];
importer.forEach(function(subject, index) { importer.forEach(function(subject, index) {
options.importer[index] = function(file, prev, bridge) { options.importer[index] = function(file, prev, bridge) {
function done(result) { function done(result) {
...@@ -321,7 +323,7 @@ module.exports.render = function(options, cb) { ...@@ -321,7 +323,7 @@ module.exports.render = function(options, cb) {
} }
} }
var functions = options.functions; var functions = clonedeep(options.functions);
if (functions) { if (functions) {
options.functions = {}; options.functions = {};
...@@ -362,13 +364,13 @@ module.exports.render = function(options, cb) { ...@@ -362,13 +364,13 @@ module.exports.render = function(options, cb) {
* @api public * @api public
*/ */
module.exports.renderSync = function(options) { module.exports.renderSync = function(opts) {
options = getOptions(options); var options = getOptions(opts);
var importer = options.importer; var importer = options.importer;
if (importer) { if (importer) {
if (Array.isArray(importer)) { if (Array.isArray(importer)) {
options.importer = [];
importer.forEach(function(subject, index) { importer.forEach(function(subject, index) {
options.importer[index] = function(file, prev) { options.importer[index] = function(file, prev) {
var result = subject.call(options.context, file, prev); var result = subject.call(options.context, file, prev);
...@@ -385,7 +387,7 @@ module.exports.renderSync = function(options) { ...@@ -385,7 +387,7 @@ module.exports.renderSync = function(options) {
} }
} }
var functions = options.functions; var functions = clonedeep(options.functions);
if (options.functions) { if (options.functions) {
options.functions = {}; options.functions = {};
......
...@@ -58,8 +58,9 @@ ...@@ -58,8 +58,9 @@
"gaze": "^1.0.0", "gaze": "^1.0.0",
"get-stdin": "^4.0.1", "get-stdin": "^4.0.1",
"glob": "^7.0.3", "glob": "^7.0.3",
"meow": "^3.7.0",
"in-publish": "^2.0.0", "in-publish": "^2.0.0",
"lodash.clonedeep": "^4.3.2",
"meow": "^3.7.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"nan": "^2.3.2", "nan": "^2.3.2",
"node-gyp": "^3.3.1", "node-gyp": "^3.3.1",
......
...@@ -535,19 +535,18 @@ describe('api', function() { ...@@ -535,19 +535,18 @@ describe('api', function() {
}); });
}); });
it('should copy all options properties', function(done) { it('should wrap importer options', function(done) {
var options; var options;
options = { options = {
data: src, data: src,
importer: function() { importer: function() {
assert.strictEqual(this.options.importer, options.importer); assert.notStrictEqual(this.options.importer, options.importer);
return { return {
contents: 'div {color: yellow;}' contents: 'div {color: yellow;}'
}; };
} }
}; };
sass.render(options, function() { sass.render(options, function() {
assert.strictEqual(this.options, options);
done(); done();
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment