├── .gitmodules ├── .jshintignore ├── examples ├── http │ ├── .gitignore │ ├── config.json │ ├── package.json │ ├── respond.js │ └── server.js ├── basic │ ├── hello.js │ ├── index.js │ └── hello-world.js ├── foo-bar.json ├── express │ ├── .gitignore │ ├── public │ │ └── stylesheets │ │ │ └── style.styl │ ├── routes │ │ ├── user.js │ │ └── index.js │ ├── package.json │ ├── views │ │ └── index.ejs │ └── app.js ├── comment-at-end │ └── index.js ├── resolve │ ├── app.js │ └── package.json ├── json-everywhere │ ├── config │ │ ├── development.json │ │ └── production.json │ ├── data │ │ ├── 01.json │ │ ├── 02.json │ │ ├── 04.json │ │ └── 03.json │ └── app.js ├── non-included-files │ ├── lib │ │ └── foo.js │ ├── config │ │ └── bar.js │ └── app.js ├── multiline-strings │ └── index.js └── escaped-strings │ └── index.js ├── .travis.yml ├── .gitignore ├── .npmignore ├── index.js ├── .jshintrc ├── .editorconfig ├── tasks └── obfuscator.js ├── test ├── .jshintrc ├── acceptance │ ├── resolve.js │ ├── comment-at-end.js │ ├── multiline-strings.js │ ├── non-included-files.js │ ├── escaped-strings.js │ ├── basic.js │ ├── express.js │ ├── http.js │ └── json-everywhere.js └── obfuscator.js ├── package.json ├── Makefile ├── bin └── obfuscator ├── docs.md ├── lib ├── require.js ├── utils.js └── obfuscator.js ├── History.md └── Readme.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | 2 | lib/require.js -------------------------------------------------------------------------------- /examples/http/.gitignore: -------------------------------------------------------------------------------- 1 | obfuscated.js -------------------------------------------------------------------------------- /examples/basic/hello.js: -------------------------------------------------------------------------------- 1 | exports.hello = 'hello'; 2 | -------------------------------------------------------------------------------- /examples/basic/index.js: -------------------------------------------------------------------------------- 1 | exports.world = 'world'; 2 | -------------------------------------------------------------------------------- /examples/foo-bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar" 3 | } 4 | -------------------------------------------------------------------------------- /examples/express/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.css 3 | obfuscated.js -------------------------------------------------------------------------------- /examples/comment-at-end/index.js: -------------------------------------------------------------------------------- 1 | exports.hello = 'hello' 2 | //#im a comment -------------------------------------------------------------------------------- /examples/resolve/app.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require.resolve('mocha'); 3 | -------------------------------------------------------------------------------- /examples/json-everywhere/config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "dev" 3 | } 4 | -------------------------------------------------------------------------------- /examples/json-everywhere/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": "prod" 3 | } 4 | -------------------------------------------------------------------------------- /examples/json-everywhere/data/01.json: -------------------------------------------------------------------------------- 1 | { 2 | "fragment": "The lead character" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "iojs" 6 | -------------------------------------------------------------------------------- /examples/json-everywhere/data/02.json: -------------------------------------------------------------------------------- 1 | { 2 | "fragment": "called \"The Bride\"" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib-cov 3 | coverage.html 4 | npm-debug.log 5 | obfuscated.js 6 | -------------------------------------------------------------------------------- /examples/json-everywhere/data/04.json: -------------------------------------------------------------------------------- 1 | { 2 | "fragment": "lead by her lover \"Bill\"." 3 | } 4 | -------------------------------------------------------------------------------- /examples/non-included-files/lib/foo.js: -------------------------------------------------------------------------------- 1 | module.exports = function(){ 2 | return 'foo' 3 | } 4 | -------------------------------------------------------------------------------- /examples/http/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "foo? bar.", 3 | "bar": "bar? foo.", 4 | "port": 4300 5 | } -------------------------------------------------------------------------------- /examples/non-included-files/config/bar.js: -------------------------------------------------------------------------------- 1 | module.exports = function(){ 2 | return 'bar' 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/ 2 | lib-cov/ 3 | test/ 4 | .editorconfig 5 | .jshintrc 6 | .travis.yml 7 | coverage.html 8 | Makefile -------------------------------------------------------------------------------- /examples/json-everywhere/data/03.json: -------------------------------------------------------------------------------- 1 | { 2 | "fragment": "was a member of the Deadly Viper Assassination Squad" 3 | } 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = process.env.OBF_COV 3 | ? require('./lib-cov/obfuscator') 4 | : require('./lib/obfuscator'); 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "laxbreak": true, 4 | "indent": 2, 5 | "maxlen": 80, 6 | "expr": true, 7 | "supernew": true 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.js] 8 | indent_style = space 9 | indent_size = 2 -------------------------------------------------------------------------------- /examples/express/public/stylesheets/style.styl: -------------------------------------------------------------------------------- 1 | body 2 | padding: 50px 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif 4 | a 5 | color: #00B7FF -------------------------------------------------------------------------------- /examples/express/routes/user.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET users listing. 4 | */ 5 | 6 | exports.list = function(req, res){ 7 | res.send("respond with a resource"); 8 | }; -------------------------------------------------------------------------------- /examples/express/routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET home page. 4 | */ 5 | 6 | exports.index = function(req, res){ 7 | res.render('index', { title: 'Express' }); 8 | }; -------------------------------------------------------------------------------- /examples/http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obfuscator-http-example", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": {}, 6 | "dependencies": {} 7 | } 8 | -------------------------------------------------------------------------------- /examples/multiline-strings/index.js: -------------------------------------------------------------------------------- 1 | 2 | var str = 3 | 'hello\ 4 | \'world\'\ 5 | \"and\"\ 6 | stuff'; 7 | 8 | exports.fn = function () { 9 | return str; 10 | } 11 | -------------------------------------------------------------------------------- /examples/resolve/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obfuscator-example-resolve", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "mocha": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/basic/hello-world.js: -------------------------------------------------------------------------------- 1 | exports.helloWorld = function () { 2 | var hello = require('./hello').hello; 3 | var world = require('./').world; 4 | 5 | return hello + ' ' + world; 6 | }; 7 | -------------------------------------------------------------------------------- /examples/non-included-files/app.js: -------------------------------------------------------------------------------- 1 | 2 | var bar = require('./config/bar') 3 | var foo = require('./lib/foo') 4 | 5 | module.exports = function(){ 6 | return [foo(), bar()].join(' ') 7 | } 8 | -------------------------------------------------------------------------------- /examples/escaped-strings/index.js: -------------------------------------------------------------------------------- 1 | 2 | exports.a = '\n\t\ba\r\n'; 3 | exports.b = 'b'; 4 | exports.c = '\nc'; 5 | 6 | exports.string = function () { 7 | return this.a + this.b + this.c; 8 | }; 9 | -------------------------------------------------------------------------------- /examples/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obfuscator-example-express", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": {}, 6 | "dependencies": { 7 | "express": "3.0.0rc4", 8 | "ejs": "*", 9 | "stylus": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tasks/obfuscator.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function () { 3 | var err = 4 | 'Grunt support has been removed from obfusactor.\n' 5 | + 'Please use\n\n' 6 | + ' https://github.com/stephenmathieson/grunt-obfuscator\n'; 7 | 8 | console.error(err); 9 | }; 10 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "describe": false, 4 | "before": false, 5 | "beforeEach": false, 6 | "after": false, 7 | "afterEach": false, 8 | "it": false 9 | }, 10 | "node": true, 11 | "expr": true, 12 | "laxbreak": true 13 | } 14 | -------------------------------------------------------------------------------- /examples/express/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

Welcome to <%= title %>

10 | 11 | -------------------------------------------------------------------------------- /examples/http/respond.js: -------------------------------------------------------------------------------- 1 | 2 | var url = require('url'); 3 | 4 | var config = require('./config.json'); 5 | 6 | module.exports = function (req, res) { 7 | var parsed = url.parse(req.url, true); 8 | 9 | res.writeHead(200, { 'content-type': 'text/plain' }); 10 | if ('bar' === parsed.query.foo) { 11 | res.write(config.bar); 12 | } else { 13 | res.write(config.foo); 14 | } 15 | res.end(); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/json-everywhere/app.js: -------------------------------------------------------------------------------- 1 | 2 | exports = module.exports = app; 3 | 4 | function app(env) { 5 | env = env || 'development'; 6 | return require('./config/' + env).env; 7 | } 8 | 9 | exports.killbill = 10 | [ 11 | require('./data/01'), 12 | require('./data/02'), 13 | require('./data/03'), 14 | require('./data/04') 15 | ] 16 | .map(function (o) { 17 | return o.fragment; 18 | }) 19 | .join(', '); 20 | -------------------------------------------------------------------------------- /test/acceptance/resolve.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var assert = require('better-assert'); 4 | var fs = require('fs'); 5 | var obfuscator = require('../..'); 6 | 7 | var dir = path.join(__dirname, '..', '..', 'examples', 'resolve'); 8 | var app = path.join(dir, 'app.js'); 9 | 10 | var opts = obfuscator.options([ app ], dir, app, true); 11 | 12 | obfuscator.obfuscator(opts, function (err, code) { 13 | if (err) { 14 | throw err; 15 | } 16 | 17 | var file = path.join(dir, 'obfuscated.js'); 18 | fs.writeFileSync(file, code); 19 | assert(path.join(dir, 'node_modules', 'mocha', 'index.js') == require(file)); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/http/server.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | var config = require('./config.json'); 4 | var respond = require('./respond'); 5 | 6 | exports.start = function (cb) { 7 | cb = typeof cb === 'function' 8 | ? cb 9 | : function () {}; 10 | 11 | exports._http = http.createServer(function (req, res) { 12 | return respond(req, res); 13 | }); 14 | 15 | exports._http.listen(config.port, cb); 16 | }; 17 | 18 | exports.port = config.port; 19 | 20 | exports._http = null; 21 | 22 | exports.close = function (cb) { 23 | cb = typeof cb === 'function' 24 | ? cb 25 | : function () {}; 26 | exports._http.close(cb); 27 | }; 28 | -------------------------------------------------------------------------------- /test/acceptance/comment-at-end.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var assert = require('better-assert'); 4 | var fs = require('fs'); 5 | var obfuscator = require('../..'); 6 | 7 | var EXAMPLES = path.join(__dirname, '..', '..', 'examples'); 8 | var FILE = path.join(EXAMPLES, 'comment-at-end', 'index.js'); 9 | var opts = obfuscator.options([ FILE ], EXAMPLES, FILE, true); 10 | 11 | obfuscator.obfuscator(opts, function (err, code) { 12 | if (err) { 13 | throw err; 14 | } 15 | 16 | var file = path.join(EXAMPLES, 'comment-at-end', 'obfuscated.js'); 17 | fs.writeFileSync(file, code); 18 | assert('hello' === require(file).hello); 19 | }); 20 | -------------------------------------------------------------------------------- /test/acceptance/multiline-strings.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var path = require('path'); 4 | var assert = require('better-assert'); 5 | var fs = require('fs'); 6 | var obfuscator = require('../..'); 7 | 8 | var root = path.join(__dirname, '..', '..', 'examples', 'multiline-strings'); 9 | var entry = path.join(root, 'index.js'); 10 | var files = [entry]; 11 | 12 | var opts = new obfuscator.Options(files, root, entry, true); 13 | 14 | obfuscator(opts, function (err, code) { 15 | if (err) throw err; 16 | var file = path.join(root, 'obfuscated.js'); 17 | fs.writeFileSync(file, code); 18 | 19 | var module = require(file); 20 | assert('hello \'world\' "and" stuff' == module.fn()); 21 | }); 22 | -------------------------------------------------------------------------------- /test/acceptance/non-included-files.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var assert = require('better-assert'); 4 | var fs = require('fs'); 5 | var obfuscator = require('../..'); 6 | 7 | var root = path.join(__dirname, '..', '..', 'examples', 'non-included-files'); 8 | 9 | var opts = obfuscator.options([ 10 | path.join(root, 'app.js'), 11 | path.join(root, 'lib/foo.js'), 12 | ], 13 | root, 14 | path.join(root, 'app.js'), 15 | true); 16 | 17 | obfuscator.obfuscator(opts, function (err, code) { 18 | if (err) { 19 | throw err; 20 | } 21 | 22 | var file = path.join(root, 'obfuscated.js'); 23 | fs.writeFileSync(file, code); 24 | assert('foo bar' === require(file)()); 25 | }); 26 | -------------------------------------------------------------------------------- /test/acceptance/escaped-strings.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var path = require('path'); 4 | var assert = require('better-assert'); 5 | var fs = require('fs'); 6 | var obfuscator = require('../..'); 7 | 8 | var root = path.join(__dirname, '..', '..', 'examples', 'escaped-strings'); 9 | var entry = path.join(root, 'index.js'); 10 | var files = [entry]; 11 | 12 | var opts = new obfuscator.Options(files, root, entry, true); 13 | 14 | obfuscator(opts, function (err, code) { 15 | if (err) throw err; 16 | var file = path.join(root, 'obfuscated.js'); 17 | fs.writeFileSync(file, code); 18 | 19 | var module = require(file); 20 | 21 | assert('\n\t\ba\r\n' == module.a); 22 | assert('b' == module.b); 23 | assert('\nc' == module.c); 24 | assert('\n\t\ba\r\nb\nc' == module.string()); 25 | }); 26 | -------------------------------------------------------------------------------- /test/acceptance/basic.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var assert = require('better-assert'); 4 | var fs = require('fs'); 5 | var obfuscator = require('../..'); 6 | 7 | var EXAMPLES = path.join(__dirname, '..', '..', 'examples'); 8 | 9 | var opts = obfuscator.options([ 10 | path.join(EXAMPLES, 'basic', 'hello.js'), 11 | path.join(EXAMPLES, 'basic', 'index.js'), 12 | path.join(EXAMPLES, 'basic', 'hello-world.js') 13 | ], 14 | EXAMPLES, 15 | path.join(EXAMPLES, 'basic', 'hello-world.js'), 16 | true); 17 | 18 | obfuscator.obfuscator(opts, function (err, code) { 19 | if (err) { 20 | throw err; 21 | } 22 | 23 | var file = path.join(EXAMPLES, 'basic', 'obfuscated.js'); 24 | fs.writeFileSync(file, code); 25 | assert('hello world' === require(file).helloWorld()); 26 | }); 27 | -------------------------------------------------------------------------------- /test/acceptance/express.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | var supertest = require('supertest'); 6 | var obfuscator = require('../..'); 7 | 8 | var EXAMPLES = path.join(__dirname, '..', '..', 'examples'); 9 | 10 | var opts = obfuscator.options([ 11 | path.join(EXAMPLES, 'express', 'app.js'), 12 | path.join(EXAMPLES, 'express', 'routes', 'index.js'), 13 | path.join(EXAMPLES, 'express', 'routes', 'user.js') 14 | ], 15 | EXAMPLES, 16 | path.join(EXAMPLES, 'express', 'app.js'), 17 | true); 18 | 19 | obfuscator.obfuscator(opts, function (err, code) { 20 | if (err) { 21 | throw err; 22 | } 23 | 24 | var file = path.join(EXAMPLES, 'express', 'obfuscated.js'); 25 | 26 | fs.writeFileSync(file, code); 27 | 28 | var app = require(file).app; 29 | app.listen(3344, function () { 30 | var request = supertest(app); 31 | request.get('/').expect(200, function () { 32 | request.get('/users').expect(200, process.exit); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/express/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express') 7 | , routes = require('./routes') 8 | , user = require('./routes/user') 9 | , http = require('http') 10 | , path = require('path'); 11 | 12 | var app = express(); 13 | 14 | app.configure(function(){ 15 | app.set('port', process.env.PORT || 3000); 16 | app.set('views', __dirname + '/views'); 17 | app.set('view engine', 'ejs'); 18 | app.use(express.favicon()); 19 | app.use(express.logger('dev')); 20 | app.use(express.bodyParser()); 21 | app.use(express.methodOverride()); 22 | app.use(express.cookieParser('your secret here')); 23 | app.use(express.session()); 24 | app.use(app.router); 25 | app.use(require('stylus').middleware(__dirname + '/public')); 26 | app.use(express.static(path.join(__dirname, 'public'))); 27 | }); 28 | 29 | app.configure('development', function(){ 30 | app.use(express.errorHandler()); 31 | }); 32 | 33 | app.get('/', routes.index); 34 | app.get('/users', user.list); 35 | 36 | exports.app = app; 37 | -------------------------------------------------------------------------------- /test/acceptance/http.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | var supertest = require('supertest'); 6 | var obfuscator = require('../..'); 7 | 8 | var EXAMPLES = path.join(__dirname, '..', '..', 'examples'); 9 | 10 | var opts = obfuscator.options([ 11 | path.join(EXAMPLES, 'http', 'server.js'), 12 | path.join(EXAMPLES, 'http', 'respond.js') 13 | ], 14 | EXAMPLES, 15 | path.join(EXAMPLES, 'http', 'server.js'), 16 | false); 17 | 18 | obfuscator.obfuscator(opts, function (err, code) { 19 | if (err) { 20 | throw err; 21 | } 22 | 23 | var file = path.join(EXAMPLES, 'http', 'obfuscated.js'); 24 | 25 | fs.writeFileSync(file, code); 26 | 27 | var app = require(file); 28 | app.start(function () { 29 | var request = supertest('http://localhost:' + app.port); 30 | request.get('/').expect(200, function () { 31 | request.get('/?foo=bar').expect('foo? bar.', function () { 32 | request.get('/?bar=foo').expect('bar? foo.', app.close); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/acceptance/json-everywhere.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var assert = require('better-assert'); 4 | var fs = require('fs'); 5 | var glob = require('glob'); 6 | var obfusactor = require('../..'); 7 | 8 | var dir = path.join(__dirname, '../../examples/json-everywhere'); 9 | 10 | try { 11 | fs.unlinkSync(path.join(dir, 'obfuscated.js')); 12 | } catch (e) {} 13 | 14 | var opts = { 15 | files: glob.sync(path.join(dir, '/**/*.js*')), 16 | root: path.join(__dirname, '../../examples'), 17 | entry: path.join(dir, 'app.js'), 18 | strings: true 19 | }; 20 | 21 | obfusactor(opts, function (err, js) { 22 | if (err) throw err; 23 | var file = path.join(dir, 'obfuscated.js'); 24 | fs.writeFile(file, js, function (err) { 25 | if (err) throw err; 26 | var app = require(file); 27 | 28 | assert( 29 | 'The lead character, called "The Bride", ' 30 | + 'was a member of the Deadly Viper ' 31 | + 'Assassination Squad, lead by her ' 32 | + 'lover "Bill".' === app.killbill); 33 | 34 | assert('dev' === app('development')); 35 | assert('prod' === app('production')); 36 | 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obfuscator", 3 | "version": "0.5.4", 4 | "scripts": { 5 | "test": "make all" 6 | }, 7 | "description": "Code protection / obfuscation for node", 8 | "keywords": [ 9 | "code protection", 10 | "obfuscator", 11 | "uglify" 12 | ], 13 | "author": "Stephen Mathieson ", 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/stephenmathieson/node-obfuscator.git" 17 | }, 18 | "dependencies": { 19 | "uglify-js": "~2.4.0", 20 | "commander": "~2.0.0" 21 | }, 22 | "devDependencies": { 23 | "better-assert": "~1.0.0", 24 | "glob": "~3.2.7", 25 | "jshint": "~2.0.1", 26 | "mocha": "^2.3.0", 27 | "should": "~1.2.2", 28 | "supertest": "~0.6.0" 29 | }, 30 | "bin": { 31 | "obfuscator": "bin/obfuscator" 32 | }, 33 | "main": "index.js", 34 | "bugs": { 35 | "url": "https://github.com/stephenmathieson/node-obfuscator/issues" 36 | }, 37 | "homepage": "https://github.com/stephenmathieson/node-obfuscator", 38 | "directories": { 39 | "example": "examples", 40 | "test": "test" 41 | }, 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | BINS = node_modules/.bin 3 | SRC = index.js $(wildcard lib/*.js) 4 | TESTS = $(wildcard test/*.js) 5 | EXAMPLES = $(wildcard examples/*/*.js) 6 | ACCEPTANCE = $(wildcard test/acceptance/*.js) 7 | 8 | MOCHA_REPORTER ?= spec 9 | JSCOVERAGE ?= jscoverage 10 | 11 | all: lint test test-acceptance 12 | 13 | test: node_modules 14 | @$(BINS)/mocha \ 15 | -R $(MOCHA_REPORTER) \ 16 | -r should 17 | 18 | test-acceptance: examples/resolve/node_modules \ 19 | examples/express/node_modules \ 20 | $(ACCEPTANCE) 21 | @echo 'everything looks good' 22 | 23 | examples/resolve/node_modules: examples/resolve/package.json 24 | @cd examples/resolve; npm install 25 | 26 | examples/express/node_modules: examples/express/package.json 27 | @cd examples/express; npm install 28 | 29 | $(ACCEPTANCE): 30 | node $@ 31 | 32 | test-cov: coverage.html 33 | @open coverage.html 34 | 35 | coverage.html: node_modules lib-cov $(TESTS) 36 | @OBF_COV=1 \ 37 | MOCHA_REPORTER=html-cov \ 38 | $(MAKE) test > coverage.html 39 | 40 | lib-cov: $(SRC) 41 | @rm -rf $@ 42 | @$(JSCOVERAGE) lib $@ 43 | 44 | lint: node_modules 45 | @$(BINS)/jshint \ 46 | --verbose \ 47 | $(SRC) \ 48 | $(TESTS) \ 49 | $(ACCEPTANCE) 50 | 51 | node_modules: package.json 52 | @npm install 53 | 54 | clean: 55 | rm -rf examples/*/obfuscated.js 56 | rm -rf examples/*/node_modules 57 | rm -rf lib-cov coverage.html 58 | 59 | .PHONY: test $(ACCEPTANCE) clean 60 | -------------------------------------------------------------------------------- /bin/obfuscator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /*jslint node:true*/ 4 | 5 | 'use strict'; 6 | 7 | var program = require('commander'), 8 | fs = require('fs'), 9 | Options = require('..').Options, 10 | obfuscator = require('..').obfuscator; 11 | 12 | program 13 | .version(require('../package.json').version) 14 | .usage('[options] ') 15 | .option('--entry ', 'The application\'s entry point', String) 16 | .option('--root ', 'The application\'s root directory', 17 | String, process.cwd()) 18 | .option('--out ', 'The output file', String) 19 | .option('--no-color', 'Disable colored output', Boolean, false) 20 | .option('--strings', 'Obfuscate strings contained in the project') 21 | .parse(process.argv); 22 | 23 | var options = new Options(program.args, 24 | program.root, 25 | program.entry, 26 | program.strings); 27 | 28 | obfuscator(options, function (err, obfuscated) { 29 | if (err) { 30 | throw err; 31 | } 32 | 33 | if (program.out) { 34 | fs.writeFile(program.out, obfuscated, function (err) { 35 | if (err) { 36 | throw err; 37 | } 38 | 39 | var message = 'wrote '; 40 | 41 | if (program['no-color']) { 42 | message += program.out; 43 | } else { 44 | message += '\x1B[36m' + program.out + '\x1B[39m'; 45 | } 46 | 47 | console.log(message); 48 | }); 49 | } else { 50 | console.log(obfuscated); 51 | } 52 | }); 53 | 54 | -------------------------------------------------------------------------------- /docs.md: -------------------------------------------------------------------------------- 1 | # Obfuscator Documentation 2 | 3 | ### `obfuscator(options, cb)` 4 | 5 | #### Parameters 6 | 7 | * options `Object` The options 8 | * cb `Function` Callback: `function (err, obfuscated)` 9 | 10 | Obfuscate and concatenate a NodeJS _"package"_ 11 | because corporate says so. 12 | 13 | ### `obfuscator.options(files, root, entry, [strings])` 14 | 15 | #### Parameters 16 | 17 | * files `Array` The files contained in the package 18 | * root `String` The root of the package 19 | * entry `String` The entry point 20 | * [strings] `Boolean` Shall strings be obfuscated 21 | 22 | #### Returns 23 | 24 | `Object` 25 | 26 | Create an `options` object for the `obfuscator` 27 | 28 | Aliases (for back-compat): 29 | 30 | - `Options` 31 | - `ObfuscatorOptions` 32 | 33 | 34 | Examples: 35 | 36 | ```js 37 | var opts = new obfuscator.Options( 38 | // files 39 | [ './myfile.js', './mydir/thing.js'], 40 | // root 41 | './', 42 | // entry 43 | 'myfile.js', 44 | // strings 45 | true) 46 | 47 | var opts = obfuscator.options({...}) 48 | ``` 49 | 50 | ### `obfuscator.utils.hex(str)` 51 | 52 | #### Parameters 53 | 54 | * str `String` 55 | 56 | #### Returns 57 | 58 | `String` 59 | 60 | Convert (or _obfuscate_) a string to its escaped 61 | hexidecimal representation. For example, 62 | `hex('a')` will return `'\x63'`. 63 | 64 | ### `obfuscator.utils.strings(js)` 65 | 66 | #### Parameters 67 | 68 | * js `String` 69 | 70 | #### Returns 71 | 72 | `String` 73 | 74 | Mangle simple strings contained in some `js` 75 | 76 | Strings will be _mangled_ by replacing each 77 | contained character with its escaped hexidecimal 78 | representation. For example, "a" will render 79 | to "\x63". 80 | 81 | Strings which contain non-alphanumeric characters 82 | other than `.-_/` will be ignored. 83 | 84 | Strings not wrapped in double quotes will be ignored. 85 | 86 | Example: 87 | 88 | ```js 89 | utils.strings('var foo = "foo"';); 90 | //=> 'var foo = "\x66\x6f\x6f";' 91 | ``` 92 | 93 | -------------------------------------------------------------------------------- /lib/require.js: -------------------------------------------------------------------------------- 1 | 2 | // based on TJ Holowaychuk's commonjs require binding 3 | 4 | function require(p, root) { 5 | // third-party module? use native require 6 | if ('.' != p[0] && '/' != p[0]) 7 | return native_require(p); 8 | 9 | root = root || 'root'; 10 | 11 | var path = require.resolve(p); 12 | 13 | // if it's a non-registered json file, it 14 | // must be at the root of the project 15 | if (!path && /\.json$/i.test(p)) 16 | return native_require('./' + require.basename(p)); 17 | 18 | var module = require.cache[path]; 19 | 20 | if (!module) { 21 | try { 22 | return native_require(p); 23 | } catch (err) { 24 | throw new Error('failed to require "' + p + '" from ' + root +'\n' + err.message + '\n' + err.stack); 25 | } 26 | } 27 | 28 | if (!module.exports) { 29 | module.exports = {}; 30 | module.call(module.exports, module, module.exports, 31 | require.relative(path)); 32 | } 33 | 34 | return module.exports; 35 | } 36 | 37 | // same as node's `require` 38 | require.cache = {}; 39 | 40 | // node's native `path.basename` 41 | require.basename = native_require('path').basename; 42 | 43 | require.resolve = function (path) { 44 | // GH-12 45 | if ('.' != path[0]) return native_require.resolve(path); 46 | 47 | var pathWithSlash = path.slice(-1) === '/' ? path : path + '/'; 48 | var paths = [ 49 | path, 50 | path + '.js', 51 | pathWithSlash + 'index.js', 52 | path + '.json', 53 | pathWithSlash + 'index.json' 54 | ]; 55 | 56 | for (var i = 0, p; p = paths[i]; i++) { 57 | if (require.cache[p]) return p; 58 | } 59 | }; 60 | 61 | require.register = function (path, fn) { 62 | require.cache[path] = fn; 63 | }; 64 | 65 | require.relative = function (parent) { 66 | function relative(p) { 67 | if ('.' != p[0]) return require(p); 68 | 69 | var path = parent.split('/'); 70 | var segs = p.split('/'); 71 | path.pop(); 72 | 73 | for (var i = 0, len = segs.length; i < len; i += 1) { 74 | var seg = segs[i]; 75 | if ('..' == seg) { 76 | path.pop(); 77 | } else if ('.' != seg) { 78 | path.push(seg); 79 | } 80 | } 81 | 82 | return require(path.join('/'), parent); 83 | } 84 | 85 | relative.resolve = require.resolve; 86 | relative.cache = require.cache; 87 | return relative; 88 | }; 89 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.5.4 / 2015-09-14 3 | ================== 4 | 5 | * require: support `require('.\')` (@benweet, #28) 6 | 7 | 0.5.3 / 2015-09-01 8 | ================== 9 | 10 | * register: ensure a trailing newline after each file's contents (#26) 11 | * package: update "mocha" to 2.3.0 12 | * test: add assertions for files with trailing commas 13 | 14 | 0.5.2 / 2015-04-03 15 | ================== 16 | 17 | * travis: Remove 0.8, add 0.12 and iojs 18 | 19 | 0.5.1 / 2015-04-03 20 | ================== 21 | 22 | * require: Also throw the error native_require returns (@JamyDev, #20) 23 | 24 | 0.5.0 / 2014-09-30 25 | ================== 26 | 27 | * require: Fallback to the native `require()` 28 | * .jshintrc: Relax options 29 | * examples: Add a "non-included file" example 30 | * Readme: Add note about stupid usage of `require()` 31 | * docs: Use escaped string 32 | 33 | 0.4.2 / 2014-04-07 34 | ================== 35 | 36 | * pkg: npm init 37 | * pkg: Remove 'preferGlobal' 38 | * Use Uglify's TreeTransformer to mangle strings rather than regular expressions. 39 | 40 | 0.4.1 / 2014-03-31 41 | ================== 42 | 43 | * Fix obfuscation of escaped characters 44 | * license: update copyright year 45 | 46 | 0.4.0 / 2014-02-26 47 | ================== 48 | 49 | * Add `resolve.resolve` support ([GH-12](https://github.com/stephenmathieson/node-obfuscator/issues/12)) 50 | * Fix mocked require tests 51 | * Cleanup makefile 52 | * Refactor JSON file support 53 | * Obfuscate all strings 54 | * Remove component-builder example 55 | 56 | 0.3.0 / 2013-12-20 57 | ================== 58 | 59 | - removed grunt support; use [grunt-obfuscator](https://github.com/stephenmathieson/grunt-obfuscator) instead. 60 | 61 | 0.2.2 / 2013-10-22 62 | ================== 63 | 64 | - update uglify-js to ~2.4.0 65 | 66 | 0.2.1 / 2013-09-12 67 | ================== 68 | 69 | - fixed grunt task ([#9](https://github.com/stephenmathieson/node-obfuscator/pull/9)) 70 | 71 | 0.2.0 / 2013-08-13 72 | ================== 73 | 74 | - fixed windows pathing bug ([#8](https://github.com/stephenmathieson/node-obfuscator/pull/8)) 75 | - `obfuscator` is now a function ([#1](https://github.com/stephenmathieson/node-obfuscator/issues/1)) 76 | - added support for custom compression options ([#2](https://github.com/stephenmathieson/node-obfuscator/issues/2)) 77 | - updated UglifyJS 78 | 79 | 0.1.0 / 2013-05-17 80 | ================== 81 | 82 | - added `string` option, optionally obfuscating strings contained in your package 83 | - added ability to export from an _"obfuscated"_ package 84 | 85 | 0.0.2 / 2013-02-10 86 | ================== 87 | 88 | - initial release 89 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # maintainer wanted 3 | 4 | if you want this package, open an issue or email me 5 | 6 |









7 | 8 | # Obfuscator 9 | 10 | [![Build Status](https://travis-ci.org/stephenmathieson/node-obfuscator.png?branch=master)](https://travis-ci.org/stephenmathieson/node-obfuscator) [![Dependency Status](https://gemnasium.com/stephenmathieson/node-obfuscator.png)](https://gemnasium.com/stephenmathieson/node-obfuscator) 11 | 12 | Obfuscate your node packages because your boss says so! 13 | 14 | ## Why? 15 | 16 | Because I had this conversation: 17 | 18 | > **me**: hi boss. this application should be written in node, not java. node is good and stuff. 19 | 20 | > **boss**: oh, okay. node sounds great. what about code protection so people don't steal our software? 21 | 22 | > **me**: ... 23 | 24 | > **boss**: you can't use node. 25 | 26 | ... but now: 27 | 28 | > **me**: hi boss. first off, code protection is stupid. secondly, java can be decompiled. 29 | 30 | > **boss**: but decompiling java is a lot of work. 31 | 32 | > **me**: so is [un-obfuscating javascript](http://github.com/stephenmathieson/node-obfuscator)! 33 | 34 | ## Usage 35 | 36 | ### Command Line (installed globally with the `-g` flag) 37 | 38 | ``` 39 | $ obfuscator --entry app.js ./app.js ./routes/index.js ./routes/user.js 40 | ``` 41 | 42 | ### JavaScript API 43 | 44 | ```javascript 45 | var Options = require('obfuscator').Options; 46 | var obfuscator = require('obfuscator').obfuscator; 47 | var fs = require('fs'); 48 | var options = new Options([ '/path/to/file1.js', '/path/to/file2.js' ], '/path/to', 'file1.js', true); 49 | 50 | // custom compression options 51 | // see https://github.com/mishoo/UglifyJS2/#compressor-options 52 | options.compressor = { 53 | conditionals: true, 54 | evaluate: true, 55 | booleans: true, 56 | loops: true, 57 | unused: false, 58 | hoist_funs: false 59 | }; 60 | 61 | obfuscator(options, function (err, obfuscated) { 62 | if (err) { 63 | throw err; 64 | } 65 | fs.writeFile('./cool.js', obfuscated, function (err) { 66 | if (err) { 67 | throw err; 68 | } 69 | 70 | console.log('cool.'); 71 | }); 72 | }); 73 | ``` 74 | 75 | Also see [acceptance tests](https://github.com/stephenmathieson/node-obfuscator/tree/master/test/acceptance) or the [docs](https://github.com/stephenmathieson/node-obfuscator/tree/master/docs.md). 76 | 77 | ## How it Works 78 | 79 | Think [browserify](https://github.com/substack/node-browserify) only for node, plus [UglifyJs](https://github.com/mishoo/UglifyJS2). Your entire project will be concatenated into a single file. This file will contain a stubbed `require` function, which will handle everything for you. This single file will be run through [UglifyJs](https://github.com/mishoo/UglifyJS2), making it more difficult to read. 80 | 81 | Undoing this process is hopefully as painful as decompiling java bytecode. 82 | 83 | ## Known bugs and limitations 84 | 85 | - everything (including json, subdirectories, etc.) must be in the `root` of your project. 86 | - you're not able to use many of the native module's `require` features; only `require.cache` and `require.resolve` have been exposed. 87 | - you're not able to do silly things with `module.` 88 | - dynamically built `require()`s are not supported; chances are, there's a significantly cleaner way of handling loading your depenencies anyway. 89 | 90 | ## License 91 | 92 | (The MIT License) 93 | 94 | Copyright (c) 2013-2014 Stephen Mathieson <me@stephenmathieson.com> 95 | 96 | Permission is hereby granted, free of charge, to any person obtaining 97 | a copy of this software and associated documentation files (the 98 | 'Software'), to deal in the Software without restriction, including 99 | without limitation the rights to use, copy, modify, merge, publish, 100 | distribute, sublicense, and/or sell copies of the Software, and to 101 | permit persons to whom the Software is furnished to do so, subject to 102 | the following conditions: 103 | 104 | The above copyright notice and this permission notice shall be 105 | included in all copies or substantial portions of the Software. 106 | 107 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 108 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 109 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 110 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 111 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 112 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 113 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 114 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var uglifyjs = require('uglify-js'); 5 | var merge = require('util')._extend; 6 | 7 | /** 8 | * Create an `AST` from the given `js`, invoking `cb(err, ast)` 9 | * 10 | * @api private 11 | * @param {String} js 12 | * @param {Function} cb 13 | */ 14 | 15 | exports.ast = function (js, cb) { 16 | try { 17 | var ast = uglifyjs.parse(js); 18 | cb(null, ast); 19 | } catch (err) { 20 | var e = new Error(err.message); 21 | // cheap hack to use actual errors 22 | // rather than the indecipherable JS_Parse_Error garbage 23 | merge(e, err); 24 | // expose the bad js 25 | e.source = js; 26 | cb(e); 27 | } 28 | }; 29 | 30 | /** 31 | * Compress the given `ast`, conditionally using `opts` 32 | * 33 | * @api private 34 | * @param {Object} [opts] 35 | * @return {AST} 36 | */ 37 | 38 | exports.compress = function (ast, opts) { 39 | opts = opts || exports.compress.defaults; 40 | var compressor = uglifyjs.Compressor(opts); 41 | // for some stupid reason, this is the 42 | // only non-modifier method... 43 | return ast.transform(compressor); 44 | }; 45 | 46 | /** 47 | * Default compression options 48 | * 49 | * @api private 50 | * @type {Object} 51 | */ 52 | 53 | exports.compress.defaults = { 54 | sequences: true, 55 | properties: true, 56 | dead_code: true, 57 | drop_debugger: true, 58 | unsafe: true, 59 | conditionals: true, 60 | comparisons: true, 61 | evaluate: true, 62 | booleans: true, 63 | loops: true, 64 | unused: true, 65 | hoist_funs: true, 66 | hoist_vars: true, 67 | if_return: true, 68 | join_vars: true, 69 | cascade: true, 70 | warnings: false 71 | }; 72 | 73 | /** 74 | * Uglify the given `js` with `opts` 75 | * 76 | * @api private 77 | * @param {String} js 78 | * @param {Object} [opts] 79 | * @param {Function} cb 80 | */ 81 | 82 | exports.uglify = function (js, opts, cb) { 83 | 84 | /** 85 | * Handle mangling and compression of the generated `AST` 86 | * 87 | * @api private 88 | * @param {Error} err 89 | * @param {AST} ast 90 | */ 91 | 92 | function handleAST(err, ast) { 93 | if (err) return cb(err); 94 | 95 | var stream = new uglifyjs.OutputStream; 96 | 97 | ast.figure_out_scope(); 98 | ast.mangle_names(); 99 | ast = exports.compress(ast, opts.compressor); 100 | 101 | if (opts.strings) { 102 | ast = mangleStrings(ast); 103 | // disable uglify's string escaping to prevent 104 | // double escaping our hex 105 | stream.print_string = function (str) { 106 | return this.print('"' + str + '"'); 107 | }; 108 | } 109 | 110 | ast.print(stream); 111 | return cb(null, stream.toString()); 112 | } 113 | 114 | if (typeof opts === 'function') { 115 | cb = opts; 116 | opts = {}; 117 | } 118 | 119 | // build the AST 120 | exports.ast(js, handleAST); 121 | }; 122 | 123 | 124 | /** 125 | * Escape map. 126 | */ 127 | 128 | var map = { 129 | '\b': '\\b', 130 | '\f': '\\f', 131 | '\n': '\\n', 132 | '\r': '\\r', 133 | '\t': '\\t', 134 | '\\': '\\\\' 135 | }; 136 | 137 | /** 138 | * Convert (or _obfuscate_) a string to its escaped 139 | * hexidecimal representation. For example, 140 | * `hex('a')` will return `'\x63'`. 141 | * 142 | * @api public 143 | * @name obfuscator.utils.hex 144 | * @param {String} str 145 | * @return {String} 146 | */ 147 | 148 | exports.hex = function (str) { 149 | var result = ''; 150 | 151 | for (var i = 0, l = str.length; i < l; i++) { 152 | var char = str[i]; 153 | 154 | if (map[char]) { 155 | result += map[char]; 156 | } else if ('\\' == char) { 157 | result += '\\' + str[++i]; 158 | } else { 159 | result += '\\x' + str.charCodeAt(i).toString(16); 160 | } 161 | } 162 | return result; 163 | }; 164 | 165 | 166 | /** 167 | * Mangle strings contained in the given `ast`. 168 | * 169 | * @api private 170 | * @param {AST} ast 171 | * @return {AST} mangled ast 172 | */ 173 | 174 | function mangleStrings(ast) { 175 | var transformer = new uglifyjs.TreeTransformer(null, mangleString); 176 | return ast.transform(transformer); 177 | } 178 | 179 | /** 180 | * Mangle the given `node`, assuming it's an `AST_String`. 181 | * 182 | * @api private 183 | * @param {AST_Node} node 184 | * @return {AST_Node} 185 | */ 186 | 187 | function mangleString(node) { 188 | if (!(node instanceof uglifyjs.AST_String)) { 189 | return; 190 | } 191 | 192 | var str = node.getValue(); 193 | var hex = exports.hex(str); 194 | var obj = merge({}, node); 195 | obj.value = hex; 196 | return new uglifyjs.AST_String(obj); 197 | } 198 | -------------------------------------------------------------------------------- /lib/obfuscator.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var uglify = require('uglify-js'); 7 | var utils = require('./utils'); 8 | 9 | var rJSON = /\.json$/; 10 | var __require = fs.readFileSync(path.join(__dirname, './require.js'), 'utf-8'); 11 | 12 | module.exports = obfuscator; 13 | 14 | 15 | /** 16 | * Obfuscate and concatenate a NodeJS _"package"_ 17 | * because corporate says so. 18 | * 19 | * @api public 20 | * @param {Object} options The options 21 | * @param {Function} cb Callback: `function (err, obfuscated)` 22 | */ 23 | 24 | function obfuscator(options, cb) { 25 | if (!options.files || !options.root || !options.entry) { 26 | return cb(new TypeError('Invalid options')); 27 | } 28 | 29 | obfuscator.concatenate(options, function (err, code) { 30 | if (err) { 31 | return cb(err); 32 | } 33 | 34 | utils.uglify(code, options, cb); 35 | }); 36 | } 37 | 38 | // back-compat alias 39 | obfuscator.obfuscator = obfuscator; 40 | 41 | /** 42 | * Expose the current version 43 | * 44 | * @api private 45 | * @type {String} 46 | */ 47 | 48 | obfuscator.version = require('../package').version; 49 | 50 | /** 51 | * Expose utils 52 | * 53 | * @api private 54 | * @type {Object} 55 | */ 56 | 57 | obfuscator.utils = utils; 58 | 59 | /** 60 | * Create an `options` object for the `obfuscator` 61 | * 62 | * Aliases (for back-compat): 63 | * 64 | * - `Options` 65 | * - `ObfuscatorOptions` 66 | * 67 | * 68 | * Examples: 69 | * 70 | * ```js 71 | * var opts = new obfuscator.Options( 72 | * // files 73 | * [ './myfile.js', './mydir/thing.js'], 74 | * // root 75 | * './', 76 | * // entry 77 | * 'myfile.js', 78 | * // strings 79 | * true) 80 | * 81 | * var opts = obfuscator.options({...}) 82 | * ``` 83 | * 84 | * @api public 85 | * @param {Array} files The files contained in the package 86 | * @param {String} root The root of the package 87 | * @param {String} entry The entry point 88 | * @param {Boolean} [strings] Shall strings be obfuscated 89 | * @return {Object} 90 | */ 91 | 92 | obfuscator.options = function (files, root, entry, strings) { 93 | // TODO support globbling 94 | if (!Array.isArray(files) || !files.length) { 95 | throw new TypeError('Invalid files array'); 96 | } 97 | 98 | if (typeof root !== 'string' || !root) { 99 | throw new TypeError('Invalid root directory'); 100 | } 101 | 102 | if (typeof entry !== 'string' || !entry) { 103 | throw new TypeError('Invalid entry point'); 104 | } 105 | 106 | return { 107 | files: files, 108 | root: root, 109 | entry: dotslash(entry), 110 | strings: strings 111 | }; 112 | }; 113 | 114 | // alias 115 | obfuscator.Options = 116 | obfuscator.ObfuscatorOptions = obfuscator.options; 117 | 118 | /** 119 | * Register a `file` in location to `root` 120 | * 121 | * @api private 122 | * @param {String} root 123 | * @param {String} file 124 | * @param {Function} cb 125 | */ 126 | 127 | obfuscator.register = function (root, file, cb) { 128 | var filename = dotslash(path.relative(root, file)); 129 | 130 | fs.readFile(file, 'utf-8', function (err, data) { 131 | if (err) { 132 | return cb(err); 133 | } 134 | 135 | var code = 136 | 'require.register("' + filename + '",' 137 | + 'function (module, exports, require) {' 138 | + (rJSON.test(file) 139 | // just export json 140 | ? 'module.exports = ' + data + ';' 141 | // add the file as is 142 | : data) + '\n' 143 | + '});'; 144 | return cb(null, code); 145 | }); 146 | }; 147 | 148 | /** 149 | * Concatenate a list of files for pre-obfuscation 150 | * 151 | * @api private 152 | * @param {Object} options 153 | * @param {Function} [cb] 154 | */ 155 | 156 | obfuscator.concatenate = function (options, cb) { 157 | var entry = dotslash(path.relative(options.root, options.entry)); 158 | var index = -1; 159 | var built = []; 160 | 161 | function complete() { 162 | // export the exported stuff from entry 163 | built.push('this_module.exports = require("' + entry + '");'); 164 | // end iffe 165 | built.push('}(require, module));'); 166 | // done :) 167 | cb(null, built.join('\n')); 168 | } 169 | 170 | // begin iffe with access to node's native 171 | // require and the current module 172 | built.push('(function (native_require, this_module) {'); 173 | // add the `require` shim 174 | built.push(__require); 175 | 176 | (function next() { 177 | index++; 178 | var file = options.files[index]; 179 | 180 | // no remaining files? 181 | if (!file) { 182 | return complete(); 183 | } 184 | 185 | // register the file 186 | obfuscator.register(options.root, file, function (err, data) { 187 | if (err) { 188 | return cb(err); 189 | } 190 | 191 | built.push(data); 192 | next(); 193 | }); 194 | }()); 195 | }; 196 | 197 | /** 198 | * Force a filepath to start with _./_ 199 | * 200 | * @api private 201 | * @param {String} filepath 202 | * @return {String} 203 | */ 204 | 205 | function dotslash(filepath) { 206 | filepath = filepath.replace(/\\/g, '/'); 207 | switch (filepath[0]) { 208 | case '.': 209 | case '/': 210 | return filepath; 211 | default: 212 | return './' + filepath; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /test/obfuscator.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var read = require('fs').readFileSync, 5 | path = require('path'), 6 | obfuscator = require('..'), 7 | utils = obfuscator.utils, 8 | uglifyjs = require('uglify-js'), 9 | assert = require('better-assert'); 10 | 11 | var FIXTURES = path.join(__dirname, '..', 'examples'); 12 | 13 | function fixture(file) { 14 | return path.join(FIXTURES, file); 15 | } 16 | 17 | fixture.read = function (file) { 18 | return read(fixture(file)); 19 | }; 20 | 21 | describe('obfuscator', function () { 22 | it('should be a function', function () { 23 | obfuscator.should.be.a.function; 24 | }); 25 | 26 | describe('.obfuscator()', function () { 27 | it('should be a function', function () { 28 | obfuscator.obfuscator.should.be.a.function; 29 | }); 30 | 31 | it('should mirror obfuscator', function () { 32 | obfuscator.obfuscator.should.be.equal(obfuscator); 33 | }); 34 | 35 | it('should error when given invalid options', function (done) { 36 | obfuscator.obfuscator({}, function (err) { 37 | if (!err) { 38 | throw new Error('should have errored'); 39 | } 40 | done(); 41 | }); 42 | }); 43 | 44 | it('should obfuscate a package', function (done) { 45 | var opts = obfuscator.options([ 46 | fixture('basic/hello.js'), 47 | fixture('basic/index.js'), 48 | fixture('basic/hello-world.js') 49 | ], 50 | FIXTURES, 51 | fixture('basic/hello-world')); 52 | 53 | obfuscator.obfuscator(opts, function (err, code) { 54 | if (err) { 55 | return done(err); 56 | } 57 | 58 | code.should.be.a.string; 59 | done(); 60 | }); 61 | }); 62 | 63 | it('should not obfuscate strings by default', function (done) { 64 | var opts = obfuscator.options([ 65 | fixture('basic/hello.js'), 66 | fixture('basic/index.js'), 67 | fixture('basic/hello-world.js') 68 | ], 69 | FIXTURES, 70 | fixture('basic/hello-world')); 71 | 72 | obfuscator.obfuscator(opts, function (err, code) { 73 | if (err) { 74 | return done(err); 75 | } 76 | 77 | code.should.include('basic/hello'); 78 | code.should.include('basic/index'); 79 | code.should.include('basic/hello-world'); 80 | done(); 81 | }); 82 | }); 83 | 84 | it('should obfuscate strings when strings=true', function (done) { 85 | var opts = obfuscator.options([ 86 | fixture('basic/hello.js'), 87 | fixture('basic/index.js'), 88 | fixture('basic/hello-world.js') 89 | ], 90 | FIXTURES, 91 | fixture('basic/hello-world'), 92 | true); 93 | 94 | obfuscator.obfuscator(opts, function (err, code) { 95 | if (err) { 96 | return done(err); 97 | } 98 | 99 | code.should.not.include('basic/hello'); 100 | code.should.not.include('basic/index'); 101 | code.should.not.include('basic/hello-world'); 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should support files with trailing comments', function(done){ 107 | var file = fixture('comment-at-end/index.js'); 108 | var opts = obfuscator.options([ file ], FIXTURES, file); 109 | obfuscator.obfuscator(opts, done); 110 | }); 111 | }); 112 | 113 | describe('.options()', function () { 114 | it('should be a function', function () { 115 | obfuscator.Options.should.be.a.function; 116 | }); 117 | 118 | it('should be aliased as Options', function () { 119 | obfuscator.options.should.be.eql(obfuscator.Options); 120 | }); 121 | 122 | it('should be aliased as ObfuscatorOptions', function () { 123 | obfuscator.options.should.be.eql(obfuscator.ObfuscatorOptions); 124 | }); 125 | 126 | it('should throw without files', function () { 127 | (function () { 128 | obfuscator.options(null, 'foo', 'bar'); 129 | }).should.throw(/files/); 130 | }); 131 | 132 | it('should throw with a bad root', function () { 133 | (function () { 134 | obfuscator.options([ 'file.js' ], null, 'bar'); 135 | }).should.throw(/root/); 136 | }); 137 | 138 | it('should throw with a bad entry', function () { 139 | (function () { 140 | obfuscator.options([ 'file.js' ], 'foo', null); 141 | }).should.throw(/entry/); 142 | }); 143 | 144 | it('should force ./ on the entrypoint', function () { 145 | var opts = obfuscator.options(['file.js'], 'foo', 'bar'); 146 | opts.entry.should.be.equal('./bar'); 147 | }); 148 | 149 | it('should work when invoked with "new"', function () { 150 | var opts = new obfuscator.Options(['file.js'], 'foo', 'bar'); 151 | opts.files.should.be.eql(['file.js']); 152 | opts.root.should.be.equal('foo'); 153 | opts.entry.should.be.equal('./bar'); 154 | }); 155 | 156 | it('should work when invoked without "new"', function () { 157 | var opts = obfuscator.options(['file.js'], 'foo', 'bar'); 158 | opts.files.should.be.eql(['file.js']); 159 | opts.root.should.be.equal('foo'); 160 | opts.entry.should.be.equal('./bar'); 161 | }); 162 | }); 163 | 164 | describe('.register()', function () { 165 | it('should be a function', function () { 166 | obfuscator.register.should.be.a.function; 167 | }); 168 | 169 | it('should handle ENOENT errors', function (done) { 170 | obfuscator.register('fakeroot', 'fakefile', function (err) { 171 | err.code.should.be.equal('ENOENT'); 172 | done(); 173 | }); 174 | }); 175 | 176 | it('should wrap the file contents with require stuff', function (done) { 177 | obfuscator.register(FIXTURES, fixture('basic/hello.js'), function (err, js) { 178 | if (err) { 179 | done(err); 180 | } 181 | 182 | js.should.include(fixture.read('basic/hello.js')); 183 | js.should.match(/^require\.register/); 184 | js.should.match(/\}\)\;$/); 185 | done(); 186 | }); 187 | }); 188 | 189 | it('should register the file relative to the root', function (done) { 190 | obfuscator.register(FIXTURES, fixture('basic/hello.js'), function (err, js) { 191 | if (err) { 192 | done(err); 193 | } 194 | 195 | js.should.include('require.register("./basic/hello.js",'); 196 | done(); 197 | }); 198 | }); 199 | 200 | it('should handle JSON exporting', function (done) { 201 | obfuscator.register(FIXTURES, fixture('foo-bar.json'), function (err, js) { 202 | if (err) { 203 | done(err); 204 | } 205 | 206 | js.should.include('module.exports = ' + fixture.read('foo-bar.json')); 207 | done(); 208 | }); 209 | }); 210 | }); 211 | 212 | describe('.concatenate()', function () { 213 | it('should be a function', function () { 214 | obfuscator.concatenate.should.be.a.function; 215 | }); 216 | 217 | describe('given no files', function () { 218 | var code; 219 | 220 | beforeEach(function (done) { 221 | obfuscator.concatenate({ 222 | files: [], 223 | root: path.join(FIXTURES, 'basic'), 224 | entry: fixture('basic/hello-world.js') 225 | }, function (err, _code) { 226 | if (err) { 227 | done(err); 228 | } 229 | code = _code; 230 | done(); 231 | }); 232 | }); 233 | 234 | it('should start the iffe', function () { 235 | code.should.match(/^\(function \(native_require, this_module\) \{/); 236 | }); 237 | 238 | it('should contain the require mock', function () { 239 | code.should.include('function require('); 240 | }); 241 | 242 | it('should export the entry point\'s exports', function () { 243 | code.should.include('this_module.exports = require("./hello-world.js");'); 244 | }); 245 | 246 | it('should end the iffe', function () { 247 | code.should.match(/\}\(require, module\)\)\;$/); 248 | }); 249 | }); 250 | 251 | describe('given files', function () { 252 | var code; 253 | 254 | beforeEach(function (done) { 255 | obfuscator.concatenate({ 256 | files: [ 257 | fixture('basic/hello.js'), 258 | fixture('basic/index.js'), 259 | fixture('basic/hello-world.js') 260 | ], 261 | root: path.join(FIXTURES, 'basic'), 262 | entry: fixture('basic/hello-world.js') 263 | }, function (err, _code) { 264 | if (err) { 265 | done(err); 266 | } 267 | code = _code; 268 | done(); 269 | }); 270 | }); 271 | 272 | it('should wrap each file', function () { 273 | code.should.include(fixture.read('basic/hello.js')); 274 | code.should.include(fixture.read('basic/index.js')); 275 | code.should.include(fixture.read('basic/hello-world.js')); 276 | }); 277 | 278 | it('should start the iffe', function () { 279 | code.should.match(/^\(function \(native_require, this_module\) \{/); 280 | }); 281 | 282 | it('should contain the require mock', function () { 283 | code.should.include('function require('); 284 | }); 285 | 286 | it('should export the entry point\'s exports', function () { 287 | code.should.include('this_module.exports = require("./hello-world.js");'); 288 | }); 289 | 290 | it('should end the iffe', function () { 291 | code.should.match(/\}\(require, module\)\)\;$/); 292 | }); 293 | }); 294 | 295 | describe('given a missing file', function () { 296 | it('should pass the error', function (done) { 297 | obfuscator.concatenate({ 298 | files: [ 'hello' ], 299 | root: '/dev/null', 300 | entry: 'yeah' 301 | }, function (err) { 302 | err.code.should.be.equal('ENOENT'); 303 | done(); 304 | }); 305 | }); 306 | }); 307 | 308 | describe('given missing files', function () { 309 | it('should pass the error', function (done) { 310 | obfuscator.concatenate({ 311 | files: [ 'hello', 'world', 'yeah', 'things', '1', '2', '3', '4' ], 312 | root: '/dev/null', 313 | entry: 'stuff' 314 | }, function (err) { 315 | err.code.should.be.equal('ENOENT'); 316 | done(); 317 | }); 318 | }); 319 | }); 320 | 321 | }); 322 | 323 | describe('.utils', function () { 324 | describe('.uglify', function () { 325 | it('should pass an error given bad js', function (done) { 326 | utils.uglify('a b c d e', function (err) { 327 | assert(err instanceof Error); 328 | done(); 329 | }); 330 | }); 331 | 332 | it('should allow for custom compression options', function (done) { 333 | var opts = { 334 | compressor: { 335 | join_vars: false 336 | } 337 | }, 338 | code = [ 339 | '(function () {', 340 | ' var a = "a"', 341 | ' var b = "b"', 342 | ' alert(a + b)', 343 | '}())' 344 | ].join('\n'); 345 | 346 | utils.uglify(code, opts, function (err, js) { 347 | if (err) { 348 | return done(err); 349 | } 350 | 351 | js.match(/var/g).length.should.be.equal(2); 352 | done(); 353 | }); 354 | }); 355 | 356 | it('should have default options', function () { 357 | utils.compress.defaults.should.be.an.object; 358 | utils.compress.defaults.join_vars.should.be.true; 359 | }); 360 | }); 361 | 362 | describe('.ast()', function () { 363 | var bad = 'blah blah blah', 364 | good = 'var hello = "hello"'; 365 | 366 | it('should not throw given unparseable js', function (done) { 367 | utils.ast(bad, function () { 368 | done(); 369 | }); 370 | }); 371 | 372 | it('should pass an Error given unparseable js', function (done) { 373 | utils.ast(bad, function (err) { 374 | assert(err instanceof Error); 375 | done(); 376 | }); 377 | }); 378 | 379 | it('should keep JS_Parse_Error properties', function (done) { 380 | utils.ast(bad, function (err) { 381 | assert(err.line); 382 | assert(err.col); 383 | assert(err.message); 384 | done(); 385 | }); 386 | }); 387 | 388 | it('should provide a walkable AST given parseable js', function (done) { 389 | utils.ast(good, function (err, ast) { 390 | assert(ast.walk); 391 | 392 | function walker() { 393 | if (walker.done) { 394 | return; 395 | } 396 | walker.done = true; 397 | done(); 398 | } 399 | 400 | ast.walk(new uglifyjs.TreeWalker(walker)); 401 | }); 402 | }); 403 | }); 404 | 405 | describe('.hex()', function () { 406 | it('should encode strings to their hex representations', function () { 407 | utils.hex('foo').should.be.equal('\\x66\\x6f\\x6f'); 408 | utils.hex('bar').should.be.equal('\\x62\\x61\\x72'); 409 | utils.hex('baz').should.be.equal('\\x62\\x61\\x7a'); 410 | utils.hex('qaz').should.be.equal('\\x71\\x61\\x7a'); 411 | utils.hex('dat').should.be.equal('\\x64\\x61\\x74'); 412 | utils.hex('\b').should.be.equal('\\b'); 413 | utils.hex('\f').should.be.equal('\\f'); 414 | utils.hex('\n').should.be.equal('\\n'); 415 | utils.hex('\r').should.be.equal('\\r'); 416 | utils.hex('\t').should.be.equal('\\t'); 417 | utils.hex('\\').should.be.equal('\\\\'); 418 | utils.hex('\\\t').should.be.equal('\\\\\\t'); 419 | }); 420 | }); 421 | }); 422 | }); 423 | --------------------------------------------------------------------------------