From dcfa75df325daf23fb839ae1bd31707a3bada431 Mon Sep 17 00:00:00 2001 From: Saurabh Daware Date: Thu, 28 May 2020 12:56:37 +0530 Subject: [PATCH] BREAKING CHANGE, allowRequire: true is necc to use require in template --- CHANGELOG.md | 11 +++++++ README.md | 5 +++- package.json | 6 ++-- src/bin.js | 5 ++-- src/index.js | 5 +++- tests/execute.spec.js | 45 ++++++++++++++++++++++++++++ tests/index.spec.js | 69 +++++++++++++++---------------------------- 7 files changed, 94 insertions(+), 52 deletions(-) create mode 100644 tests/execute.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b0934fc..073b570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v0.1.10 +- **BREAKING CHANGE**, **SECURITY UPDATE** + To use `require()` in the template, user will have to pass `allowRequire: true` in option. This option is by default set to false. + ```js + const newHTMLTemplate = abellRenderer.render( + myAbellTemplate, + mySandbox, + {allowRequire: true} + ); + ``` + ## v0.1.9 - Fix to recursively find and create nested `.abell` files diff --git a/README.md b/README.md index d1798ec..6e00b1d 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ You can use JavaScript Array methods to loop over array. Other JavaScript Array

${user.name}

Age: ${user.age} - `) + `).join('') }} @@ -113,6 +113,8 @@ Ouputs: ``` ### ⤵️ Import JS/JSON/NPM Modules +*NOTE: Starting v0.1.10 require() can only be used when `allowRequire: true` is passed from options or `--allow-require` flag is passed in CLI* + With Abell you can import your Native NodeJS Modules, NPM Modules, JS Files (should export data), and JSON Files with `require()` @@ -196,6 +198,7 @@ Outputs: `template`: Abell template in String `sandbox`: Object over which the scripts execute, Can define variables and inject them into script. `options.basePath`: basePath which is prefixed on `require()` paths in abellTemplate. +`options.allowRequire`: Passing `true` allows using `require()` in templates. Default is `false`. ## 🤗 Contributing diff --git a/package.json b/package.json index 7dbc024..fb9b46b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "abell-renderer", - "version": "0.1.9", + "version": "0.1.10", "description": "A wrapper arround Mustache that adds some additional features and some syntatic sugar required for abell", "main": "dist/index.js", "bin": { @@ -14,8 +14,8 @@ "test": "mocha --recursive \"./tests/*.js\"", "build": "node build/build.main.js", "dev": "node src/example/example.js", - "dev:cli": "node src/bin.js build --input src/example/templates/cli_example.abell --output src/example/out/cli_example.html", - "dev:cli-dir": "node src/bin.js build --input src/example/templates/loop_example --output src/example/out/loop_example", + "dev:cli": "node src/bin.js build --input src/example/templates/cli_example.abell --output src/example/out/cli_example.html --allow-require", + "dev:cli-dir": "node src/bin.js build --input src/example/templates/loop_example --output src/example/out/loop_example --allow-require", "cli": "npm run dev:cli", "eslint": "eslint .", "prepublishOnly": "npm run eslint && npm test && npm run build" diff --git a/src/bin.js b/src/bin.js index 7106e6d..36bf9de 100755 --- a/src/bin.js +++ b/src/bin.js @@ -70,6 +70,7 @@ function build() { const outputPath = (indexOfOutput > -1) ? path.join(cwd, args[indexOfOutput + 1]) : inputPath.replace('.abell', '.html'); // file name of input + const allowRequire = args.includes('--allow-require'); const basePath = path.dirname(inputPath); @@ -77,7 +78,7 @@ function build() { if (!fs.statSync(inputPath).isDirectory()) { // If input is a file - generateHTMLFromAbell(inputPath, outputPath, {basePath}); + generateHTMLFromAbell(inputPath, outputPath, {basePath, allowRequire}); } else { // If input is a directory const relativePaths = recursiveFind(inputPath, '.abell') @@ -87,7 +88,7 @@ function build() { generateHTMLFromAbell( path.join(inputPath, filepath), path.join(outputPath, filepath.replace('.abell', '.html')), - {basePath: path.dirname(path.join(inputPath, filepath))} + {basePath: path.dirname(path.join(inputPath, filepath)), allowRequire} ); } } diff --git a/src/index.js b/src/index.js index e8debc1..9b55fa8 100644 --- a/src/index.js +++ b/src/index.js @@ -42,7 +42,7 @@ const execRegexOnAll = (regex, template) => { * @param {object} options additional options e.g ({basePath}) * @return {string} htmlTemplate */ -function render(abellTemplate, sandbox, options = {basePath: ''}) { +function render(abellTemplate, sandbox, options = {basePath: '', allowRequire: false}) { // Finds all the JS expressions to be executed. const {matches, input} = execRegexOnAll(/\\?{{(.+?)}}/gs, abellTemplate); let renderedHTML = ''; @@ -54,6 +54,9 @@ function render(abellTemplate, sandbox, options = {basePath: ''}) { // Ignore the match that starts with slash '\' and return the same value without slash value = match[0].slice(1); } else if (match[1].includes('require(')) { + if (!options.allowRequire) { + throw new Error('require() is not allowed in the script'); + } // the js block is trying to require (e.g const module1 = require('module1')) const lines = match[1] .trim() diff --git a/tests/execute.spec.js b/tests/execute.spec.js new file mode 100644 index 0000000..b8e2855 --- /dev/null +++ b/tests/execute.spec.js @@ -0,0 +1,45 @@ +const path = require('path'); +const expect = require('chai').expect; + +const { + execute, + executeRequireStatement +} = require('../src/execute.js'); + + +describe('execute() - Executes JavaScript passed to it as string', () => { + it('should output added value when addition is performed on two values', () => { + expect( + execute('24 + 12', {}).value + ).to.equal(36); + }); + + it('should update value of a to new value', () => { + expect( + execute('a = 22 + 22', {a: 4}).sandbox.a + ).to.equal(44); + }); + + it('should not update value that is inside string', () => { + expect( + execute('(() => \'a = b\')()').value + ).to.equal('a = b'); + }); +}); + + +describe('executeRequireStatement() - executes the code with require() in its string', () => { + it('should add path native object when required', () => { + expect( + executeRequireStatement('const path = require(\'path\')') + .path.join('test', 'path') + ).to.equal(path.join('test', 'path')); + }); + + it('should handle the case of require(\'module\').property', () => { + expect( + executeRequireStatement('const testPath = require(\'path\').join(\'test\',\'path\')') + .testPath + ).to.equal(path.join('test', 'path')); + }); +}); diff --git a/tests/index.spec.js b/tests/index.spec.js index b029e87..17ca668 100644 --- a/tests/index.spec.js +++ b/tests/index.spec.js @@ -2,11 +2,6 @@ const fs = require('fs'); const path = require('path'); const expect = require('chai').expect; -const { - execute, - executeRequireStatement -} = require('../src/execute.js'); - const abellRenderer = require('../src/index.js'); describe('render() - renders abellTemplate into HTML Text', () => { @@ -33,7 +28,10 @@ describe('render() - renders abellTemplate into HTML Text', () => { .render( abellTemplate, sampleSandbox, - {basePath: path.join(__dirname, 'resources')} + { + basePath: path.join(__dirname, 'resources'), + allowRequire: true + } ) ).to.equal(htmlTemplate); }); @@ -79,11 +77,30 @@ describe('render() - renders abellTemplate into HTML Text', () => { expect( abellRenderer.render( abellTemplate, - {} + {}, + {allowRequire: true} ).trim() ).to.equal('
8 hi/hello hi/hello
'); }); + it('should throw an error if require() is used without allowRequire: true option', () => { + const abellTemplate = ` + {{ + const path = require('path'); + const hiHelloPath = require('path').join('hi', 'hello'); + }} +
{{ path.join('hi', 'hello') }} {{ hiHelloPath }}
+ `; + + expect( + () => + abellRenderer.render( + abellTemplate, + {}, + ) + ).to.throw('require() is not allowed in the script'); + }); + it('should not throw error and return same value if blank brackets passed', () => { expect( abellRenderer.render( @@ -102,41 +119,3 @@ describe('render() - renders abellTemplate into HTML Text', () => { ).to.equal('{{ This is ignored }}'); }); }); - - -describe('execute() - Executes JavaScript passed to it as string', () => { - it('should output added value when addition is performed on two values', () => { - expect( - execute('24 + 12', {}).value - ).to.equal(36); - }); - - it('should update value of a to new value', () => { - expect( - execute('a = 22 + 22', {a: 4}).sandbox.a - ).to.equal(44); - }); - - it('should not update value that is inside string', () => { - expect( - execute('(() => \'a = b\')()').value - ).to.equal('a = b'); - }); -}); - - -describe('executeRequireStatement() - executes the code with require() in its string', () => { - it('should add path native object when required', () => { - expect( - executeRequireStatement('const path = require(\'path\')') - .path.join('test', 'path') - ).to.equal(path.join('test', 'path')); - }); - - it('should handle the case of require(\'module\').property', () => { - expect( - executeRequireStatement('const testPath = require(\'path\').join(\'test\',\'path\')') - .testPath - ).to.equal(path.join('test', 'path')); - }); -});