diff --git a/Cakefile b/Cakefile index 89b1af6..f94512e 100644 --- a/Cakefile +++ b/Cakefile @@ -35,7 +35,7 @@ task "fixtures", "Generate .coffee fixtures from .eco fixtures", -> task "dist", "Generate dist/eco.js", -> build -> bundle -> fs = require("fs") - coffee = require("coffee-script").compile + coffee = require("coffeescript").compile uglify = require("uglify-js") read = (filename) -> @@ -58,9 +58,9 @@ task "dist", "Generate dist/eco.js", -> "./scanner": read "lib/scanner.js" "./util": read "lib/util.js" "strscan": read "node_modules/strscan/lib/strscan.js" - "coffee-script": stub "CoffeeScript" + "coffeescript": stub "CoffeeScript" - package = for name, source of modules + cake_package = for name, source of modules """ '#{name}': function(module, require, exports) { #{source} @@ -77,7 +77,20 @@ task "dist", "Generate dist/eco.js", -> */ """ - source = uglify """ + minify = (code) -> + toplevel = uglify.parse(code) + toplevel.figure_out_scope() + + compressor = uglify.Compressor() + compressed_ast = toplevel.transform(compressor) + + compressed_ast.figure_out_scope() + compressed_ast.compute_char_frequency() + compressed_ast.mangle_names() + + compressed_ast.print_to_string() + + source = minify """ this.eco = (function(modules) { return function require(name) { var fn, module = {id: name, exports: {}}; @@ -89,12 +102,12 @@ task "dist", "Generate dist/eco.js", -> } }; })({ - #{package.join ',\n'} + #{cake_package.join ',\n'} })('eco'); """ try - fs.mkdirSync "#{__dirname}/dist", 0755 + fs.mkdirSync "#{__dirname}/dist", 755 catch err fs.writeFileSync "#{__dirname}/dist/eco.js", "#{header}\n#{source}" diff --git a/README.md b/README.md index 9f09b07..ac65133 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,38 @@ +## This is a modified version of eco that depends on underscore.js for a safer `_.escape` + +**Why?** Because Eco's default `__escape` implementation doesn't escape single quotes, which makes XSS attacks like the following possible: + +```html + +``` + +with a @value of `x'onmouseover='alert(document.domain)`, an XSS occurs. + +### How to use this fork + +Use `dist/eco.js` instead of the unmaintained eco.js + +### How to use this fork with Rails and the eco gem + +Copy `dist/eco.js` into `vendor/assets/javascripts/eco-custom.js` and add the following to your application.rb file: + +```ruby +class Application < Rails::Application + ... + # Eco, the Coffee Script templating language, hasn't been updated in 3 years. We found an XSS bug in the escape + # function and fixed it by forking the library and using underscore.js's implementation. In order to avoid also + # having to fork the eco Ruby gem, we're setting an explicit override path to our updated eco.js. + ENV['ECO_SOURCE_PATH'] = Rails.root.join("vendor/assets/javascripts/eco-custom.js").to_s +end +``` + +Run `rm -rf tmp/cache/` to clear your local asset pipeline cache and restart your local Rails app. Double check the fix +by looking at application.js and searching for `__escape`. + +### How to recompile + +Compile with: `cake dist` + Eco: Embedded CoffeeScript templates ==================================== diff --git a/lib/command.js b/lib/command.js index 6fa10e8..5051158 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,6 +1,7 @@ +// Generated by CoffeeScript 1.7.1 (function() { - var compile, compileToFile, each, eachFile, eco, exec, fs, indent, mkdir, parseOptions, path, preprocessArgs, printUsage, printVersion, read, stripExtension, sys; - var __slice = Array.prototype.slice; + var compile, compileToFile, each, eachFile, eco, exec, fs, indent, mkdir, parseOptions, path, preprocessArgs, printUsage, printVersion, read, stripExtension, sys, + __slice = [].slice; fs = require("fs"); @@ -20,20 +21,20 @@ }; printVersion = function() { - var package; - package = JSON.parse(fs.readFileSync(__dirname + "/../package.json", "utf8")); - console.error("Eco version " + package.version); + var pkg; + pkg = JSON.parse(fs.readFileSync(__dirname + "/../package.json", "utf8")); + console.error("Eco version " + pkg.version); return process.exit(0); }; preprocessArgs = function(args) { - var arg, char, match, result, _i, _j, _len, _len2, _ref; + var arg, char, match, result, _i, _j, _len, _len1, _ref; result = []; for (_i = 0, _len = args.length; _i < _len; _i++) { arg = args[_i]; if (match = /^-([a-z]{2,})/.exec(arg)) { _ref = match[1].split(''); - for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) { + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { char = _ref[_j]; result.push("-" + char); } @@ -88,7 +89,9 @@ } } } - if (option) printUsage(); + if (option) { + printUsage(); + } return options; }; @@ -116,13 +119,19 @@ var traverse; traverse = function(root, dir, done) { return fs.readdir(dir, function(err, entries) { - if (err) return callback(err); + if (err) { + return callback(err); + } return each(entries, function(entry, proceed) { var file; - if (entry == null) return done(); + if (entry == null) { + return done(); + } file = path.join(dir, entry); return fs.stat(file, function(err, stat) { - if (err) return callback(err); + if (err) { + return callback(err); + } if (stat.isFile() && /\.eco$/.test(file)) { return callback(null, file, root, proceed); } else if (stat.isDirectory()) { @@ -135,10 +144,14 @@ }); }; return each(files, function(file, proceed) { - if (file == null) return; + if (file == null) { + return; + } return fs.stat(file, function(err, stat) { var root; - if (err) return callback(err); + if (err) { + return callback(err); + } if (stat.isDirectory()) { return traverse(file, file, proceed); } else { @@ -156,7 +169,9 @@ compile = function(infile, identifier, name, callback) { return fs.readFile(infile, "utf8", function(err, source) { var template; - if (err) return callback(err); + if (err) { + return callback(err); + } template = indent(eco.precompile(source), 2); return callback(null, "(function() {\n this." + identifier + " || (this." + identifier + " = {});\n this." + identifier + "[" + (JSON.stringify(name)) + "] = " + (template.slice(2)) + ";\n}).call(this);"); }); @@ -178,10 +193,14 @@ } outfile = path.join(outdir != null ? outdir : root, name + ".js"); return mkdir(path.dirname(outfile), function(err) { - if (err) return callback(err); + if (err) { + return callback(err); + } return compile(infile, identifier, name, function(err, result) { return fs.writeFile(outfile, result + "\n", "utf8", function(err) { - if (err) return callback(err); + if (err) { + return callback(err); + } return callback(null, outfile, name); }); }); @@ -190,29 +209,45 @@ exports.run = function(args) { var infile, name, options; - if (args == null) args = process.argv.slice(2); + if (args == null) { + args = process.argv.slice(2); + } options = parseOptions(args); if (options.stdio) { - if (options.files.length || options.output) printUsage(); + if (options.files.length || options.output) { + printUsage(); + } process.openStdin(); return read(process.stdin, function(source) { return sys.puts(eco.precompile(source)); }); } else if (options.print) { - if (options.files.length !== 1 || options.output) printUsage(); + if (options.files.length !== 1 || options.output) { + printUsage(); + } infile = options.files[0]; name = stripExtension(path.basename(infile)); return compile(infile, options.identifier, name, function(err, result) { - if (err) throw err; + if (err) { + throw err; + } return sys.puts(result); }); } else { - if (!options.files.length) printUsage(); + if (!options.files.length) { + printUsage(); + } return eachFile(options.files, function(err, infile, root, proceed) { - if (err) throw err; - if (infile == null) return; + if (err) { + throw err; + } + if (infile == null) { + return; + } return compileToFile(infile, options.identifier, root, options.output, function(err, outfile, name) { - if (err) throw err; + if (err) { + throw err; + } console.error("" + (JSON.stringify(name)) + ": " + infile + " -> " + outfile); return proceed(); }); diff --git a/lib/compiler.js b/lib/compiler.js index 0eff0e5..5b281c4 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -1,7 +1,8 @@ +// Generated by CoffeeScript 1.7.1 (function() { var CoffeeScript, indent, precompile, preprocess; - CoffeeScript = require("coffee-script"); + CoffeeScript = require("coffeescript"); preprocess = require("./preprocessor").preprocess; @@ -12,7 +13,7 @@ script = CoffeeScript.compile(preprocess(source), { noWrap: true }); - return "function(__obj) {\n if (!__obj) __obj = {};\n var __out = [], __capture = function(callback) {\n var out = __out, result;\n __out = [];\n callback.call(this);\n result = __out.join('');\n __out = out;\n return __safe(result);\n }, __sanitize = function(value) {\n if (value && value.ecoSafe) {\n return value;\n } else if (typeof value !== 'undefined' && value != null) {\n return __escape(value);\n } else {\n return '';\n }\n }, __safe, __objSafe = __obj.safe, __escape = __obj.escape;\n __safe = __obj.safe = function(value) {\n if (value && value.ecoSafe) {\n return value;\n } else {\n if (!(typeof value !== 'undefined' && value != null)) value = '';\n var result = new String(value);\n result.ecoSafe = true;\n return result;\n }\n };\n if (!__escape) {\n __escape = __obj.escape = function(value) {\n return ('' + value)\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\x22/g, '"');\n };\n }\n (function() {\n" + (indent(script, 4)) + "\n }).call(__obj);\n __obj.safe = __objSafe, __obj.escape = __escape;\n return __out.join('');\n}"; + return "function(__obj) {\n if (!__obj) __obj = {};\n var __out = [], __capture = function(callback) {\n var out = __out, result;\n __out = [];\n callback.call(this);\n result = __out.join('');\n __out = out;\n return __safe(result);\n }, __sanitize = function(value) {\n if (value && value.ecoSafe) {\n return value;\n } else if (typeof value !== 'undefined' && value != null) {\n return __escape(value);\n } else {\n return '';\n }\n }, __safe, __objSafe = __obj.safe, __escape = __obj.escape;\n __safe = __obj.safe = function(value) {\n if (value && value.ecoSafe) {\n return value;\n } else {\n if (!(typeof value !== 'undefined' && value != null)) value = '';\n var result = new String(value);\n result.ecoSafe = true;\n return result;\n }\n };\n if (!__escape) {\n __escape = __obj.escape = function(value) {\n return _.escape(value);\n }\n }\n (function() {\n" + (indent(script, 4)) + "\n }).call(__obj);\n __obj.safe = __objSafe, __obj.escape = __escape;\n return __out.join('');\n}"; }; exports.compile = function(source) { diff --git a/lib/index.js b/lib/index.js index bca5ac8..647cd3b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,3 +1,4 @@ +// Generated by CoffeeScript 1.7.1 (function() { var compile, eco, precompile, preprocess, _ref; @@ -6,9 +7,9 @@ preprocess = require("./preprocessor").preprocess; module.exports = eco = function(source) { - var _base, _ref2; + var _base; if (eco.cache) { - return (_ref2 = (_base = eco.cache)[source]) != null ? _ref2 : _base[source] = compile(source); + return (_base = eco.cache)[source] != null ? _base[source] : _base[source] = compile(source); } else { return compile(source); } diff --git a/lib/preprocessor.js b/lib/preprocessor.js index 5a33c64..0fbc596 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -1,3 +1,4 @@ +// Generated by CoffeeScript 1.7.1 (function() { var Preprocessor, Scanner, util; @@ -6,7 +7,6 @@ util = require("./util"); module.exports = Preprocessor = (function() { - Preprocessor.preprocess = function(source) { var preprocessor; preprocessor = new Preprocessor(source); @@ -22,11 +22,12 @@ } Preprocessor.prototype.preprocess = function() { - var _this = this; while (!this.scanner.done) { - this.scanner.scan(function(token) { - return _this[token[0]].apply(_this, token.slice(1)); - }); + this.scanner.scan((function(_this) { + return function(token) { + return _this[token[0]].apply(_this, token.slice(1)); + }; + })(this)); } return this.output; }; @@ -71,7 +72,9 @@ Preprocessor.prototype.dedent = function() { this.level--; - if (this.level < 0) this.fail("unexpected dedent"); + if (this.level < 0) { + this.fail("unexpected dedent"); + } if (this.captures[0] === this.level) { this.captures.shift(); return this.dedent(); diff --git a/lib/scanner.js b/lib/scanner.js index cd7258d..b52f580 100644 --- a/lib/scanner.js +++ b/lib/scanner.js @@ -1,3 +1,4 @@ +// Generated by CoffeeScript 1.7.1 (function() { var Scanner, StringScanner, trim; @@ -6,7 +7,6 @@ trim = require("./util").trim; module.exports = Scanner = (function() { - Scanner.modePatterns = { data: /(.*?)(<%%|<%\s*(\#)|<%(([=-])?)|\n|$)/, code: /(.*?)((((:|(->|=>))\s*))?%>|\n|$)/, @@ -100,10 +100,16 @@ } else if (this.tail) { this.mode = "data"; code = trim(this.flush()); - if (this.arrow) code += " " + this.arrow; - if (this.isDedentable(code)) callback(["dedent"]); + if (this.arrow) { + code += " " + this.arrow; + } + if (this.isDedentable(code)) { + callback(["dedent"]); + } callback(["recordCode", code]); - if (this.directive) return callback(["indent", this.arrow]); + if (this.directive) { + return callback(["indent", this.arrow]); + } } }; diff --git a/lib/util.js b/lib/util.js index 80fed3e..94a3cee 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,3 +1,4 @@ +// Generated by CoffeeScript 1.7.1 (function() { var repeat, specialCharacters; @@ -42,7 +43,9 @@ return specialCharacters[character]; } else { code = character.charCodeAt(0).toString(16); - if (code.length === 1) code = "0" + code; + if (code.length === 1) { + code = "0" + code; + } return "\\u00" + code; } }); diff --git a/package.json b/package.json index e8730af..10464f0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ } , "dependencies": { "strscan": ">=1.0.1" - , "coffee-script": ">=1.2.0" + , "coffeescript": "^1.12.7" } , "devDependencies": { "nodeunit": ">=0.6.4" diff --git a/src/command.coffee b/src/command.coffee index 7c44283..e9e9ed4 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -22,8 +22,8 @@ printUsage = -> process.exit 1 printVersion = -> - package = JSON.parse fs.readFileSync __dirname + "/../package.json", "utf8" - console.error "Eco version #{package.version}" + pkg = JSON.parse fs.readFileSync __dirname + "/../package.json", "utf8" + console.error "Eco version #{pkg.version}" process.exit 0 preprocessArgs = (args) -> diff --git a/src/compiler.coffee b/src/compiler.coffee index 9a5fc0b..24d82f3 100644 --- a/src/compiler.coffee +++ b/src/compiler.coffee @@ -1,4 +1,4 @@ -CoffeeScript = require "coffee-script" +CoffeeScript = require "coffeescript" {preprocess} = require "./preprocessor" {indent} = require "./util" @@ -36,12 +36,8 @@ exports.precompile = precompile = (source) -> }; if (!__escape) { __escape = __obj.escape = function(value) { - return ('' + value) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/\x22/g, '"'); - }; + return _.escape(value); + } } (function() { #{indent script, 4}