Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbe04946ce | |||
| d069c1caf1 | |||
| 6714e07a6a | |||
| dce542c9a6 | |||
| 8a41389ba5 | |||
| 68d8df5a88 | |||
| b2a083136b | |||
| 9f98c16298 | |||
| 45443b4290 | |||
| 8841a5f6d3 | |||
| e0137c26f2 | |||
| e914d6037f | |||
| 7de4b41c34 | |||
| eab1d141cb | |||
| de4414d7fc | |||
| 08fddee033 | |||
| 4512766919 | |||
| e497a35d7f | |||
| 8c9f866655 | |||
| 520e1d5f08 | |||
| 02423780a9 | |||
| be92d2f254 | |||
| 443a613b3a | |||
| 83ee5908f2 | |||
| 8dc3d2517b | |||
| 668c4fb878 | |||
| c65c6cce3f | |||
| 3d3796c1e9 | |||
| 075b354a3b | |||
| 30dbf04781 | |||
| e3a54485db | |||
| 8e23642ea2 | |||
| 88ac06875f | |||
| c68bc08a0d | |||
| 6cfbc2653a | |||
| b65135acef |
@@ -12,10 +12,10 @@ jobs:
|
|||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-latest'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
|
|
||||||
@@ -33,16 +33,16 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
operating-system: ['ubuntu-latest', 'windows-latest']
|
operating-system: ['ubuntu-latest', 'windows-latest']
|
||||||
# https://nodejs.org/en/about/releases/
|
# https://nodejs.org/en/about/releases/
|
||||||
node-version: ['10', '12', '14', '16', '17']
|
node-version: ['16', '18', '20', '22']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Test (Integration)
|
- name: Test (Integration)
|
||||||
# https://github.com/webpack/webpack/issues/14532
|
# https://github.com/webpack/webpack/issues/14532
|
||||||
if: ${{ matrix.node-version != '17' }}
|
if: ${{ matrix.node-version == '16' }}
|
||||||
run: |
|
run: |
|
||||||
cd ./tests/integration/rollup-test && ./test.sh && cd -
|
cd ./tests/integration/rollup-test && ./test.sh && cd -
|
||||||
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
|
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
|
||||||
@@ -62,15 +62,15 @@ jobs:
|
|||||||
|
|
||||||
browser:
|
browser:
|
||||||
name: Test (Browser)
|
name: Test (Browser)
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-22.04'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-aws-s3:
|
||||||
|
name: Publish to AWS S3
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: |
|
||||||
|
git config --global user.email "release@handlebarsjs.com"
|
||||||
|
git config --global user.name "handlebars-lang"
|
||||||
|
npm run publish:aws
|
||||||
|
env:
|
||||||
|
S3_BUCKET_NAME: "builds.handlebarsjs.com"
|
||||||
|
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||||
@@ -15,5 +15,6 @@ node_modules
|
|||||||
lib/handlebars/compiler/parser.js
|
lib/handlebars/compiler/parser.js
|
||||||
/coverage/
|
/coverage/
|
||||||
/dist/
|
/dist/
|
||||||
|
/test-results/
|
||||||
/tests/integration/*/dist/
|
/tests/integration/*/dist/
|
||||||
/spec/tmp/*
|
/spec/tmp/*
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[submodule "spec/mustache"]
|
[submodule "spec/mustache"]
|
||||||
path = spec/mustache
|
path = spec/mustache
|
||||||
url = git://github.com/mustache/spec.git
|
url = https://github.com/mustache/spec.git
|
||||||
|
|||||||
+87
-16
@@ -49,7 +49,7 @@ The `grunt dev` implements watching for tests and allows for in browser testing
|
|||||||
If you notice any problems, please report them to the GitHub issue tracker at
|
If you notice any problems, please report them to the GitHub issue tracker at
|
||||||
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||||
|
|
||||||
##Running Tests
|
## Running Tests
|
||||||
|
|
||||||
To run tests locally, first install all dependencies.
|
To run tests locally, first install all dependencies.
|
||||||
|
|
||||||
@@ -86,32 +86,103 @@ You can use the following scripts to make sure that the CI job does not fail:
|
|||||||
- **npm run lint** will run `eslint` and fail on warnings
|
- **npm run lint** will run `eslint` and fail on warnings
|
||||||
- **npm run format** will run `prettier` on all files
|
- **npm run format** will run `prettier` on all files
|
||||||
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
|
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
|
||||||
- **npm run integration-test** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
||||||
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
||||||
|
|
||||||
## Releasing the latest version
|
## Releasing the latest version
|
||||||
|
|
||||||
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||||
|
|
||||||
- Push-access to `handlebars-lang/handlebars.js`
|
- Push-access to [handlebars-lang/handlebars.js](https://github.com/handlebars-lang/handlebars.js/)
|
||||||
- Publishing rights on npmjs.com for the `handlebars` package
|
- Publishing rights on npmjs.com for the [handlebars](https://www.npmjs.com/package/handlebars) package
|
||||||
- Publishing rights on gemfury for the `handlebars-source` package
|
- Publishing rights on rubygems for the [handlebars-source](https://rubygems.org/gems/handlebars-source) package
|
||||||
- Push-access to the repo for legacy package managers: `components/handlebars`
|
- Push-access to the repo for legacy package managers: [components/handlebars.js](https://github.com/components/handlebars.js)
|
||||||
- Push-access to the production-repo of the handlebars site: `handlebars-lang/handlebarsjs.com-github-pages`
|
- Push-access to the production-repo of the handlebars site: [handlebars-lang/docs](https://github.com/handlebars-lang/docs)
|
||||||
|
|
||||||
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||||
|
|
||||||
A full release may be completed with the following:
|
A full release via Docker may be completed with the following:
|
||||||
|
|
||||||
```
|
1. Create a `Dockerfile` in this folder for releasing
|
||||||
npm ci
|
```Dockerfile
|
||||||
npx grunt
|
FROM node:10-slim
|
||||||
npm publish
|
|
||||||
|
ENV EDITOR=vim
|
||||||
|
|
||||||
|
# Update stretch repositories
|
||||||
|
RUN sed -i -e 's/deb.debian.org/archive.debian.org/g' \
|
||||||
|
-e 's|security.debian.org|archive.debian.org/|g' \
|
||||||
|
-e '/stretch-updates/d' /etc/apt/sources.list
|
||||||
|
|
||||||
|
# Install release dependencies
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y git vim
|
||||||
|
|
||||||
|
# Work around deprecated npm dependency install via unauthenticated git-protocol:
|
||||||
|
# https://github.com/kpdecker/generator-release/blob/87aab9b84c9f083635c3fcc822f18acce1f48736/package.json#L31
|
||||||
|
RUN git config --system url."https://github.com/".insteadOf git://github.com/
|
||||||
|
|
||||||
|
# Configure git
|
||||||
|
RUN git config --system user.email "release@handlebarsjs.com"
|
||||||
|
RUN git config --system user.name "handlebars-lang"
|
||||||
|
|
||||||
|
RUN mkdir /home/node/.config
|
||||||
|
RUN mkdir /home/node/.ssh
|
||||||
|
RUN mkdir /home/node/tmp
|
||||||
|
|
||||||
|
# Generate config for yo generator-release:
|
||||||
|
# https://github.com/kpdecker/generator-release#example
|
||||||
|
# You have to add a valid GitHub access token! (Used for reading issues and pull requests.)
|
||||||
|
RUN echo "module.exports = {\n auth: 'oauth',\n token: 'GitHub personal access token'\n};" > /home/node/.config/generator-release
|
||||||
|
RUN chown -R node:node /home/node/.config
|
||||||
|
RUN chown -R node:node /home/node/.ssh
|
||||||
|
RUN chown -R node:node /home/node/tmp
|
||||||
|
|
||||||
|
# Add the generated key to GitHub: https://github.com/settings/keys
|
||||||
|
RUN ssh-keygen -q -t ed25519 -N '' -f /home/node/.ssh/id_ed25519 -C "release@handlebarsjs.com"
|
||||||
|
RUN chmod 0600 /home/node/.ssh/id_ed25519*
|
||||||
|
RUN chown node:node /home/node/.ssh/id_ed25519*
|
||||||
|
```
|
||||||
|
2. Build and run the Docker image
|
||||||
|
```bash
|
||||||
|
docker build --tag handlebars:release .
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
--user $(id -u):$(id -g) \
|
||||||
|
--env NPM_CONFIG_PREFIX=/home/node/.npm-global \
|
||||||
|
handlebars:release bash -c 'export PATH=$PATH:/home/node/.npm-global/bin; bash'
|
||||||
|
```
|
||||||
|
* Add SSH key to GitHub: `cat /home/node/.ssh/id_ed25519.pub` (https://github.com/settings/keys)
|
||||||
|
* Add GitHub API token: `vi /home/node/.config/generator-release`
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm install -g yo@1 grunt@1 generator-release
|
||||||
|
npm run release
|
||||||
|
# Warning! This step will collect data from GitHub, bump the version,
|
||||||
|
# create a new commit, create a new tag and push it to GitHub.
|
||||||
|
# https://github.com/kpdecker/generator-release?tab=readme-ov-file#usage
|
||||||
|
yo release
|
||||||
|
npm login
|
||||||
|
npm publish
|
||||||
|
yo release:publish components handlebars.js dist/components/
|
||||||
|
```
|
||||||
|
6. Publish Ruby `handlebars-source` gem:
|
||||||
|
```bash
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
ruby:3.2-slim bash
|
||||||
|
```
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
cd dist/components/
|
||||||
|
gem build handlebars-source.gemspec
|
||||||
|
gem push handlebars-source-*.gem
|
||||||
|
```
|
||||||
|
|
||||||
cd dist/components/
|
### After the release
|
||||||
gem build handlebars-source.gemspec
|
|
||||||
gem push handlebars-source-*.gem
|
|
||||||
```
|
|
||||||
|
|
||||||
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
|
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
|
||||||
in those places still point to the latest version
|
in those places still point to the latest version
|
||||||
|
|||||||
+5
-7
@@ -166,11 +166,9 @@ module.exports = function(grunt) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
bgShell: {
|
shell: {
|
||||||
integrationTests: {
|
integrationTests: {
|
||||||
cmd: './tests/integration/run-integration-tests.sh',
|
command: './tests/integration/run-integration-tests.sh'
|
||||||
bg: false,
|
|
||||||
fail: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -195,7 +193,7 @@ module.exports = function(grunt) {
|
|||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-babel');
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
grunt.loadNpmTasks('grunt-bg-shell');
|
grunt.loadNpmTasks('grunt-shell');
|
||||||
grunt.loadNpmTasks('grunt-webpack');
|
grunt.loadNpmTasks('grunt-webpack');
|
||||||
|
|
||||||
grunt.task.loadTasks('tasks');
|
grunt.task.loadTasks('tasks');
|
||||||
@@ -214,7 +212,7 @@ module.exports = function(grunt) {
|
|||||||
// Requires secret properties from .travis.yaml
|
// Requires secret properties from .travis.yaml
|
||||||
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
||||||
'default',
|
'default',
|
||||||
'bgShell:integrationTests',
|
'shell:integrationTests',
|
||||||
'metrics',
|
'metrics',
|
||||||
'publish-to-aws'
|
'publish-to-aws'
|
||||||
]);
|
]);
|
||||||
@@ -234,6 +232,6 @@ module.exports = function(grunt) {
|
|||||||
);
|
);
|
||||||
grunt.registerTask('integration-tests', [
|
grunt.registerTask('integration-tests', [
|
||||||
'default',
|
'default',
|
||||||
'bgShell:integrationTests'
|
'shell:integrationTests'
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-7
@@ -1,18 +1,18 @@
|
|||||||
[](https://github.com/handlebars-lang/handlebars.js/actions)
|
[](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
|
||||||
[](https://www.jsdelivr.com/package/npm/handlebars)
|
[](https://www.jsdelivr.com/package/npm/handlebars)
|
||||||
[](https://bundlephobia.com/package/handlebars)
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://bundlephobia.com/package/handlebars)
|
||||||
[](https://packagephobia.com/result?p=handlebars)
|
[](https://packagephobia.com/result?p=handlebars)
|
||||||
|
|
||||||
Handlebars.js
|
Handlebars.js
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Handlebars.js is an extension to the [Mustache templating
|
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
|
||||||
language](https://mustache.github.io/) created by Chris Wanstrath.
|
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
|
||||||
Handlebars.js and Mustache are both logicless templating languages that
|
|
||||||
keep the view and the code separated like we all know they should be.
|
|
||||||
|
|
||||||
Checkout the official Handlebars docs site at
|
Checkout the official Handlebars docs site at
|
||||||
[https://handlebarsjs.com/](https://handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.7.7",
|
"version": "4.7.9",
|
||||||
"main": "handlebars.js",
|
"main": "handlebars.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "components/handlebars.js",
|
"name": "components/handlebars.js",
|
||||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||||
"homepage": "http://handlebarsjs.com",
|
"homepage": "https://handlebarsjs.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "component",
|
"type": "component",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -11,13 +11,9 @@
|
|||||||
],
|
],
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Chris Wanstrath",
|
"name": "Chris Wanstrath"
|
||||||
"homepage": "http://chriswanstrath.com"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
|
||||||
"robloach/component-installer": "*"
|
|
||||||
},
|
|
||||||
"extra": {
|
"extra": {
|
||||||
"component": {
|
"component": {
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>handlebars.js</id>
|
<id>handlebars.js</id>
|
||||||
<version>4.7.7</version>
|
<version>4.7.9</version>
|
||||||
<authors>handlebars.js Authors</authors>
|
<authors>handlebars.js Authors</authors>
|
||||||
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||||
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "4.7.7",
|
"version": "4.7.9",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"jspm": {
|
"jspm": {
|
||||||
"main": "handlebars",
|
"main": "handlebars",
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
// Handlebars should run natively in the browser
|
// Handlebars should not use node or browser-specific APIs
|
||||||
node: false
|
'shared-node-browser': true,
|
||||||
|
node: false,
|
||||||
|
browser: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { registerDefaultDecorators } from './decorators';
|
|||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import { resetLoggedProperties } from './internal/proto-access';
|
import { resetLoggedProperties } from './internal/proto-access';
|
||||||
|
|
||||||
export const VERSION = '4.7.7';
|
export const VERSION = '4.7.9';
|
||||||
export const COMPILER_REVISION = 8;
|
export const COMPILER_REVISION = 8;
|
||||||
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global define */
|
/* global define, require */
|
||||||
import { isArray } from '../utils';
|
import { isArray } from '../utils';
|
||||||
|
|
||||||
let SourceNode;
|
let SourceNode;
|
||||||
@@ -8,7 +8,7 @@ try {
|
|||||||
if (typeof define !== 'function' || !define.amd) {
|
if (typeof define !== 'function' || !define.amd) {
|
||||||
// We don't support this in AMD environments. For these environments, we assume that
|
// We don't support this in AMD environments. For these environments, we assume that
|
||||||
// they are running on the browser and thus have no need for the source-map library.
|
// they are running on the browser and thus have no need for the source-map library.
|
||||||
let SourceMap = require('source-map'); // eslint-disable-line no-undef
|
let SourceMap = require('source-map');
|
||||||
SourceNode = SourceMap.SourceNode;
|
SourceNode = SourceMap.SourceNode;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
/* eslint-disable new-cap */
|
/* eslint-disable new-cap */
|
||||||
|
|
||||||
import Exception from '../exception';
|
import Exception from '../exception';
|
||||||
import { isArray, indexOf, extend } from '../utils';
|
import {
|
||||||
|
isArray,
|
||||||
|
indexOf,
|
||||||
|
extend,
|
||||||
|
sanitizeDepth,
|
||||||
|
sanitizeParts
|
||||||
|
} from '../utils';
|
||||||
import AST from './ast';
|
import AST from './ast';
|
||||||
|
|
||||||
const slice = [].slice;
|
const slice = [].slice;
|
||||||
@@ -243,7 +249,7 @@ Compiler.prototype = {
|
|||||||
name = path.parts[0],
|
name = path.parts[0],
|
||||||
isBlock = program != null || inverse != null;
|
isBlock = program != null || inverse != null;
|
||||||
|
|
||||||
this.opcode('getContext', path.depth);
|
this.opcode('getContext', sanitizeDepth(path.depth));
|
||||||
|
|
||||||
this.opcode('pushProgram', program);
|
this.opcode('pushProgram', program);
|
||||||
this.opcode('pushProgram', inverse);
|
this.opcode('pushProgram', inverse);
|
||||||
@@ -288,29 +294,32 @@ Compiler.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
PathExpression: function(path) {
|
PathExpression: function(path) {
|
||||||
this.addDepth(path.depth);
|
// Sanitize untrusted AST values at the compiler boundary.
|
||||||
this.opcode('getContext', path.depth);
|
// javascript-compiler.js trusts all opcode arguments to be safe.
|
||||||
|
const depth = sanitizeDepth(path.depth);
|
||||||
|
const parts = sanitizeParts(path.parts);
|
||||||
|
|
||||||
let name = path.parts[0],
|
this.addDepth(depth);
|
||||||
|
this.opcode('getContext', depth);
|
||||||
|
|
||||||
|
let name = parts[0],
|
||||||
scoped = AST.helpers.scopedId(path),
|
scoped = AST.helpers.scopedId(path),
|
||||||
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
blockParamId = !depth && !scoped && this.blockParamIndex(name);
|
||||||
|
|
||||||
if (blockParamId) {
|
if (blockParamId) {
|
||||||
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
this.opcode(
|
||||||
|
'lookupBlockParam',
|
||||||
|
[Number(blockParamId[0]), Number(blockParamId[1])],
|
||||||
|
parts
|
||||||
|
);
|
||||||
} else if (!name) {
|
} else if (!name) {
|
||||||
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
||||||
this.opcode('pushContext');
|
this.opcode('pushContext');
|
||||||
} else if (path.data) {
|
} else if (path.data) {
|
||||||
this.options.data = true;
|
this.options.data = true;
|
||||||
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
this.opcode('lookupData', depth, parts, path.strict);
|
||||||
} else {
|
} else {
|
||||||
this.opcode(
|
this.opcode('lookupOnContext', parts, path.falsy, path.strict, scoped);
|
||||||
'lookupOnContext',
|
|
||||||
path.parts,
|
|
||||||
path.falsy,
|
|
||||||
path.strict,
|
|
||||||
scoped
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -319,11 +328,11 @@ Compiler.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
NumberLiteral: function(number) {
|
NumberLiteral: function(number) {
|
||||||
this.opcode('pushLiteral', number.value);
|
this.opcode('pushLiteral', Number(number.value));
|
||||||
},
|
},
|
||||||
|
|
||||||
BooleanLiteral: function(bool) {
|
BooleanLiteral: function(bool) {
|
||||||
this.opcode('pushLiteral', bool.value);
|
this.opcode('pushLiteral', bool.value === true ? 'true' : 'false');
|
||||||
},
|
},
|
||||||
|
|
||||||
UndefinedLiteral: function() {
|
UndefinedLiteral: function() {
|
||||||
@@ -410,16 +419,16 @@ Compiler.prototype = {
|
|||||||
|
|
||||||
pushParam: function(val) {
|
pushParam: function(val) {
|
||||||
let value = val.value != null ? val.value : val.original || '';
|
let value = val.value != null ? val.value : val.original || '';
|
||||||
|
let depth = sanitizeDepth(val.depth);
|
||||||
|
|
||||||
if (this.stringParams) {
|
if (this.stringParams) {
|
||||||
if (value.replace) {
|
if (value.replace) {
|
||||||
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
||||||
}
|
}
|
||||||
|
if (depth) {
|
||||||
if (val.depth) {
|
this.addDepth(depth);
|
||||||
this.addDepth(val.depth);
|
|
||||||
}
|
}
|
||||||
this.opcode('getContext', val.depth || 0);
|
this.opcode('getContext', depth);
|
||||||
this.opcode('pushStringParam', value, val.type);
|
this.opcode('pushStringParam', value, val.type);
|
||||||
|
|
||||||
if (val.type === 'SubExpression') {
|
if (val.type === 'SubExpression') {
|
||||||
|
|||||||
@@ -165,12 +165,10 @@ JavaScriptCompiler.prototype = {
|
|||||||
|
|
||||||
let { programs, decorators } = this.context;
|
let { programs, decorators } = this.context;
|
||||||
for (i = 0, l = programs.length; i < l; i++) {
|
for (i = 0, l = programs.length; i < l; i++) {
|
||||||
if (programs[i]) {
|
ret[i] = programs[i];
|
||||||
ret[i] = programs[i];
|
if (decorators[i]) {
|
||||||
if (decorators[i]) {
|
ret[i + '_d'] = decorators[i];
|
||||||
ret[i + '_d'] = decorators[i];
|
ret.useDecorators = true;
|
||||||
ret.useDecorators = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,14 +533,22 @@ JavaScriptCompiler.prototype = {
|
|||||||
this.resolvePath('data', parts, 0, true, strict);
|
this.resolvePath('data', parts, 0, true, strict);
|
||||||
},
|
},
|
||||||
|
|
||||||
resolvePath: function(type, parts, i, falsy, strict) {
|
resolvePath: function(type, parts, startPartIndex, falsy, strict) {
|
||||||
if (this.options.strict || this.options.assumeObjects) {
|
if (this.options.strict || this.options.assumeObjects) {
|
||||||
this.push(strictLookup(this.options.strict && strict, this, parts, type));
|
this.push(
|
||||||
|
strictLookup(
|
||||||
|
this.options.strict && strict,
|
||||||
|
this,
|
||||||
|
parts,
|
||||||
|
startPartIndex,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let len = parts.length;
|
let len = parts.length;
|
||||||
for (; i < len; i++) {
|
for (let i = startPartIndex; i < len; i++) {
|
||||||
/* eslint-disable no-loop-func */
|
/* eslint-disable no-loop-func */
|
||||||
this.replaceStack(current => {
|
this.replaceStack(current => {
|
||||||
let lookup = this.nameLookup(current, parts[i], type);
|
let lookup = this.nameLookup(current, parts[i], type);
|
||||||
@@ -680,9 +686,18 @@ JavaScriptCompiler.prototype = {
|
|||||||
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
||||||
options = this.setupHelperArgs(name, paramSize);
|
options = this.setupHelperArgs(name, paramSize);
|
||||||
|
|
||||||
|
// Store the resolved decorator in a variable and verify it is a function before
|
||||||
|
// calling it. Without this, unregistered decorators can cause an unhandled TypeError
|
||||||
|
// (calling undefined), which crashes the process — enabling Denial of Service.
|
||||||
|
this.decorators.push(['var decorator = ', foundDecorator, ';']);
|
||||||
|
this.decorators.push([
|
||||||
|
'if (typeof decorator !== "function") { throw new Error(',
|
||||||
|
this.quotedString('Missing decorator: "' + name + '"'),
|
||||||
|
'); }'
|
||||||
|
]);
|
||||||
this.decorators.push([
|
this.decorators.push([
|
||||||
'fn = ',
|
'fn = ',
|
||||||
this.decorators.functionCall(foundDecorator, '', [
|
this.decorators.functionCall('decorator', '', [
|
||||||
'fn',
|
'fn',
|
||||||
'props',
|
'props',
|
||||||
'container',
|
'container',
|
||||||
@@ -906,8 +921,8 @@ JavaScriptCompiler.prototype = {
|
|||||||
let existing = this.matchExistingProgram(child);
|
let existing = this.matchExistingProgram(child);
|
||||||
|
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
// Placeholder to prevent name conflicts for nested children
|
||||||
let index = this.context.programs.length;
|
let index = this.context.programs.push('') - 1;
|
||||||
child.index = index;
|
child.index = index;
|
||||||
child.name = 'program' + index;
|
child.name = 'program' + index;
|
||||||
this.context.programs[index] = compiler.compile(
|
this.context.programs[index] = compiler.compile(
|
||||||
@@ -1261,15 +1276,14 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function strictLookup(requireTerminal, compiler, parts, type) {
|
function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
|
||||||
let stack = compiler.popStack(),
|
let stack = compiler.popStack(),
|
||||||
i = 0,
|
|
||||||
len = parts.length;
|
len = parts.length;
|
||||||
if (requireTerminal) {
|
if (requireTerminal) {
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < len; i++) {
|
for (let i = startPartIndex; i < len; i++) {
|
||||||
stack = compiler.nameLookup(stack, parts[i], type);
|
stack = compiler.nameLookup(stack, parts[i], type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1279,7 +1293,7 @@ function strictLookup(requireTerminal, compiler, parts, type) {
|
|||||||
'(',
|
'(',
|
||||||
stack,
|
stack,
|
||||||
', ',
|
', ',
|
||||||
compiler.quotedString(parts[i]),
|
compiler.quotedString(parts[len]),
|
||||||
', ',
|
', ',
|
||||||
JSON.stringify(compiler.source.currentLocation),
|
JSON.stringify(compiler.source.currentLocation),
|
||||||
' )'
|
' )'
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export function moveHelperToHooks(instance, helperName, keepHelper) {
|
|||||||
if (instance.helpers[helperName]) {
|
if (instance.helpers[helperName]) {
|
||||||
instance.hooks[helperName] = instance.helpers[helperName];
|
instance.hooks[helperName] = instance.helpers[helperName];
|
||||||
if (!keepHelper) {
|
if (!keepHelper) {
|
||||||
delete instance.helpers[helperName];
|
// Using delete is slow
|
||||||
|
instance.helpers[helperName] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { extend } from '../utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
|
|
||||||
* The resulting object can be used with "object[property]" to check if a property exists
|
|
||||||
* @param {...object} sources a varargs parameter of source objects that will be merged
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
export function createNewLookupObject(...sources) {
|
|
||||||
return extend(Object.create(null), ...sources);
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,31 @@
|
|||||||
import { createNewLookupObject } from './create-new-lookup-object';
|
import { extend } from '../utils';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
const loggedProperties = Object.create(null);
|
const loggedProperties = Object.create(null);
|
||||||
|
|
||||||
export function createProtoAccessControl(runtimeOptions) {
|
export function createProtoAccessControl(runtimeOptions) {
|
||||||
let defaultMethodWhiteList = Object.create(null);
|
// Create an object with "null"-prototype to avoid truthy results on
|
||||||
defaultMethodWhiteList['constructor'] = false;
|
// prototype properties.
|
||||||
defaultMethodWhiteList['__defineGetter__'] = false;
|
const propertyWhiteList = Object.create(null);
|
||||||
defaultMethodWhiteList['__defineSetter__'] = false;
|
|
||||||
defaultMethodWhiteList['__lookupGetter__'] = false;
|
|
||||||
|
|
||||||
let defaultPropertyWhiteList = Object.create(null);
|
|
||||||
// eslint-disable-next-line no-proto
|
// eslint-disable-next-line no-proto
|
||||||
defaultPropertyWhiteList['__proto__'] = false;
|
propertyWhiteList['__proto__'] = false;
|
||||||
|
extend(propertyWhiteList, runtimeOptions.allowedProtoProperties);
|
||||||
|
|
||||||
|
const methodWhiteList = Object.create(null);
|
||||||
|
methodWhiteList['constructor'] = false;
|
||||||
|
methodWhiteList['__defineGetter__'] = false;
|
||||||
|
methodWhiteList['__defineSetter__'] = false;
|
||||||
|
methodWhiteList['__lookupGetter__'] = false;
|
||||||
|
methodWhiteList['__lookupSetter__'] = false;
|
||||||
|
extend(methodWhiteList, runtimeOptions.allowedProtoMethods);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
properties: {
|
properties: {
|
||||||
whitelist: createNewLookupObject(
|
whitelist: propertyWhiteList,
|
||||||
defaultPropertyWhiteList,
|
|
||||||
runtimeOptions.allowedProtoProperties
|
|
||||||
),
|
|
||||||
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
whitelist: createNewLookupObject(
|
whitelist: methodWhiteList,
|
||||||
defaultMethodWhiteList,
|
|
||||||
runtimeOptions.allowedProtoMethods
|
|
||||||
),
|
|
||||||
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
|
/* global globalThis */
|
||||||
export default function(Handlebars) {
|
export default function(Handlebars) {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
let root = typeof global !== 'undefined' ? global : window, // eslint-disable-line no-undef
|
// https://mathiasbynens.be/notes/globalthis
|
||||||
$Handlebars = root.Handlebars;
|
(function() {
|
||||||
|
if (typeof globalThis === 'object') return;
|
||||||
|
Object.prototype.__defineGetter__('__magic__', function() {
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
__magic__.globalThis = __magic__; // eslint-disable-line no-undef
|
||||||
|
delete Object.prototype.__magic__;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const $Handlebars = globalThis.Handlebars;
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
Handlebars.noConflict = function() {
|
Handlebars.noConflict = function() {
|
||||||
if (root.Handlebars === Handlebars) {
|
if (globalThis.Handlebars === Handlebars) {
|
||||||
root.Handlebars = $Handlebars;
|
globalThis.Handlebars = $Handlebars;
|
||||||
}
|
}
|
||||||
return Handlebars;
|
return Handlebars;
|
||||||
};
|
};
|
||||||
|
|||||||
+24
-22
@@ -74,17 +74,10 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||||
|
|
||||||
let extendedOptions = Utils.extend({}, options, {
|
options.hooks = this.hooks;
|
||||||
hooks: this.hooks,
|
options.protoAccessControl = this.protoAccessControl;
|
||||||
protoAccessControl: this.protoAccessControl
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = env.VM.invokePartial.call(
|
let result = env.VM.invokePartial.call(this, partial, context, options);
|
||||||
this,
|
|
||||||
partial,
|
|
||||||
context,
|
|
||||||
extendedOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result == null && env.compile) {
|
if (result == null && env.compile) {
|
||||||
options.partials[options.name] = env.compile(
|
options.partials[options.name] = env.compile(
|
||||||
@@ -92,7 +85,7 @@ export function template(templateSpec, env) {
|
|||||||
templateSpec.compilerOptions,
|
templateSpec.compilerOptions,
|
||||||
env
|
env
|
||||||
);
|
);
|
||||||
result = options.partials[options.name](context, extendedOptions);
|
result = options.partials[options.name](context, options);
|
||||||
}
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (options.indent) {
|
if (options.indent) {
|
||||||
@@ -145,7 +138,7 @@ export function template(templateSpec, env) {
|
|||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
let result = depths[i] && container.lookupProperty(depths[i], name);
|
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return depths[i][name];
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -254,8 +247,9 @@ export function template(templateSpec, env) {
|
|||||||
|
|
||||||
ret._setup = function(options) {
|
ret._setup = function(options) {
|
||||||
if (!options.partial) {
|
if (!options.partial) {
|
||||||
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
|
let mergedHelpers = {};
|
||||||
wrapHelpersToPassLookupProperty(mergedHelpers, container);
|
addHelpers(mergedHelpers, env.helpers, container);
|
||||||
|
addHelpers(mergedHelpers, options.helpers, container);
|
||||||
container.helpers = mergedHelpers;
|
container.helpers = mergedHelpers;
|
||||||
|
|
||||||
if (templateSpec.usePartial) {
|
if (templateSpec.usePartial) {
|
||||||
@@ -355,21 +349,21 @@ export function wrapProgram(
|
|||||||
export function resolvePartial(partial, context, options) {
|
export function resolvePartial(partial, context, options) {
|
||||||
if (!partial) {
|
if (!partial) {
|
||||||
if (options.name === '@partial-block') {
|
if (options.name === '@partial-block') {
|
||||||
partial = options.data['partial-block'];
|
partial = lookupOwnProperty(options.data, 'partial-block');
|
||||||
} else {
|
} else {
|
||||||
partial = options.partials[options.name];
|
partial = lookupOwnProperty(options.partials, options.name);
|
||||||
}
|
}
|
||||||
} else if (!partial.call && !options.name) {
|
} else if (!partial.call && !options.name) {
|
||||||
// This is a dynamic partial that returned a string
|
// This is a dynamic partial that returned a string
|
||||||
options.name = partial;
|
options.name = partial;
|
||||||
partial = options.partials[partial];
|
partial = lookupOwnProperty(options.partials, partial);
|
||||||
}
|
}
|
||||||
return partial;
|
return partial;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invokePartial(partial, context, options) {
|
export function invokePartial(partial, context, options) {
|
||||||
// Use the current closure context to save the partial-block if this partial
|
// Use the current closure context to save the partial-block if this partial
|
||||||
const currentPartialBlock = options.data && options.data['partial-block'];
|
const currentPartialBlock = lookupOwnProperty(options.data, 'partial-block');
|
||||||
options.partial = true;
|
options.partial = true;
|
||||||
if (options.ids) {
|
if (options.ids) {
|
||||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||||
@@ -410,6 +404,12 @@ export function noop() {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lookupOwnProperty(obj, name) {
|
||||||
|
if (obj && Object.prototype.hasOwnProperty.call(obj, name)) {
|
||||||
|
return obj[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initData(context, data) {
|
function initData(context, data) {
|
||||||
if (!data || !('root' in data)) {
|
if (!data || !('root' in data)) {
|
||||||
data = data ? createFrame(data) : {};
|
data = data ? createFrame(data) : {};
|
||||||
@@ -435,9 +435,10 @@ function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
|||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
function addHelpers(mergedHelpers, helpers, container) {
|
||||||
Object.keys(mergedHelpers).forEach(helperName => {
|
if (!helpers) return;
|
||||||
let helper = mergedHelpers[helperName];
|
Object.keys(helpers).forEach(helperName => {
|
||||||
|
let helper = helpers[helperName];
|
||||||
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -445,6 +446,7 @@ function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
|||||||
function passLookupPropertyOption(helper, container) {
|
function passLookupPropertyOption(helper, container) {
|
||||||
const lookupProperty = container.lookupProperty;
|
const lookupProperty = container.lookupProperty;
|
||||||
return wrapHelper(helper, options => {
|
return wrapHelper(helper, options => {
|
||||||
return Utils.extend({ lookupProperty }, options);
|
options.lookupProperty = lookupProperty;
|
||||||
|
return options;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,3 +114,30 @@ export function blockParams(params, ids) {
|
|||||||
export function appendContextPath(contextPath, id) {
|
export function appendContextPath(contextPath, id) {
|
||||||
return (contextPath ? contextPath + '.' : '') + id;
|
return (contextPath ? contextPath + '.' : '') + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coerce an untrusted depth value to a safe non-negative integer.
|
||||||
|
* Returns `0` for any value that is not a finite, non-negative number.
|
||||||
|
*
|
||||||
|
* @param {unknown} depth - The depth value to sanitize.
|
||||||
|
* @returns {number} A non-negative integer.
|
||||||
|
*/
|
||||||
|
export function sanitizeDepth(depth) {
|
||||||
|
let number = Number(depth);
|
||||||
|
if (!Number.isFinite(number) || number < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.floor(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a sanitized copy of a PathExpression AST node's parts array.
|
||||||
|
* Coerces each element to a string, or returns an empty array if parts
|
||||||
|
* is not an array.
|
||||||
|
*
|
||||||
|
* @param {unknown} parts - The parts value to sanitize.
|
||||||
|
* @returns {string[]} A safe string array.
|
||||||
|
*/
|
||||||
|
export function sanitizeParts(parts) {
|
||||||
|
return Array.isArray(parts) ? parts.map(String) : [];
|
||||||
|
}
|
||||||
|
|||||||
+45
-8
@@ -196,16 +196,24 @@ module.exports.cli = function(opts) {
|
|||||||
|
|
||||||
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
||||||
|
|
||||||
|
if (opts.namespace && !isValidNamespace(opts.namespace)) {
|
||||||
|
throw new Handlebars.Exception('Invalid namespace format');
|
||||||
|
}
|
||||||
|
|
||||||
let output = new SourceNode();
|
let output = new SourceNode();
|
||||||
if (!opts.simple) {
|
if (!opts.simple) {
|
||||||
if (opts.amd) {
|
if (opts.amd) {
|
||||||
|
const runtimeModulePath =
|
||||||
|
(opts.handlebarPath || '') + 'handlebars.runtime';
|
||||||
output.add(
|
output.add(
|
||||||
"define(['" +
|
'define([' +
|
||||||
opts.handlebarPath +
|
quoteForJavaScript(runtimeModulePath) +
|
||||||
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||||
);
|
);
|
||||||
} else if (opts.commonjs) {
|
} else if (opts.commonjs) {
|
||||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
output.add(
|
||||||
|
'var Handlebars = require(' + quoteForJavaScript(opts.commonjs) + ');'
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
output.add('(function() {\n');
|
output.add('(function() {\n');
|
||||||
}
|
}
|
||||||
@@ -255,9 +263,9 @@ module.exports.cli = function(opts) {
|
|||||||
}
|
}
|
||||||
output.add([
|
output.add([
|
||||||
objectName,
|
objectName,
|
||||||
"['",
|
'[',
|
||||||
template.name,
|
quoteForJavaScript(template.name),
|
||||||
"'] = template(",
|
'] = template(',
|
||||||
precompiled,
|
precompiled,
|
||||||
');\n'
|
');\n'
|
||||||
]);
|
]);
|
||||||
@@ -277,7 +285,9 @@ module.exports.cli = function(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts.map) {
|
if (opts.map) {
|
||||||
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
output.add(
|
||||||
|
'\n//# sourceMappingURL=' + sanitizeSourceMapComment(opts.map) + '\n'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
output = output.toStringWithSourceMap();
|
output = output.toStringWithSourceMap();
|
||||||
@@ -307,6 +317,33 @@ function arrayCast(value) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Safely quotes a value for embedding in generated JavaScript strings
|
||||||
|
*
|
||||||
|
* Uses JSON.stringify which handles all special characters.
|
||||||
|
*/
|
||||||
|
function quoteForJavaScript(value) {
|
||||||
|
return JSON.stringify(String(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that a namespace is a legitimate dotted JavaScript identifier
|
||||||
|
* (e.g. "App.templates") to prevent arbitrary code injection
|
||||||
|
*/
|
||||||
|
function isValidNamespace(namespace) {
|
||||||
|
return /^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(
|
||||||
|
namespace
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips line terminators from source map URLs to prevent injection of new
|
||||||
|
* JavaScript lines via the sourceMappingURL comment
|
||||||
|
*/
|
||||||
|
function sanitizeSourceMapComment(value) {
|
||||||
|
return String(value).replace(/[\r\n\u2028\u2029]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
||||||
*
|
*
|
||||||
|
|||||||
Generated
+5840
-13874
File diff suppressed because it is too large
Load Diff
+8
-11
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"barename": "handlebars",
|
"barename": "handlebars",
|
||||||
"version": "4.7.7",
|
"version": "4.7.9",
|
||||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||||
"homepage": "https://www.handlebarsjs.com/",
|
"homepage": "https://handlebarsjs.com/",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"mustache",
|
"mustache",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"uglify-js": "^3.1.4"
|
"uglify-js": "^3.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.17.1",
|
"@playwright/test": "1.44.1",
|
||||||
"aws-sdk": "^2.1.49",
|
"aws-sdk": "^2.1.49",
|
||||||
"babel-loader": "^5.0.0",
|
"babel-loader": "^5.0.0",
|
||||||
"babel-runtime": "^5.1.10",
|
"babel-runtime": "^5.1.10",
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
"chai-diff": "^1.0.1",
|
"chai-diff": "^1.0.1",
|
||||||
"concurrently": "^5.0.0",
|
"concurrently": "^5.0.0",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"dtslint": "^0.5.5",
|
|
||||||
"dustjs-linkedin": "^2.0.2",
|
"dustjs-linkedin": "^2.0.2",
|
||||||
"eco": "~1.1.0-rc-3",
|
"eco": "~1.1.0-rc-3",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
@@ -47,9 +46,8 @@
|
|||||||
"eslint-plugin-compat": "^3.13.0",
|
"eslint-plugin-compat": "^3.13.0",
|
||||||
"eslint-plugin-es5": "^1.4.1",
|
"eslint-plugin-es5": "^1.4.1",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"grunt": "^1.0.4",
|
"grunt": "1.5.3",
|
||||||
"grunt-babel": "^5.0.0",
|
"grunt-babel": "^5.0.0",
|
||||||
"grunt-bg-shell": "^2.3.3",
|
|
||||||
"grunt-cli": "^1",
|
"grunt-cli": "^1",
|
||||||
"grunt-contrib-clean": "^1",
|
"grunt-contrib-clean": "^1",
|
||||||
"grunt-contrib-concat": "^1",
|
"grunt-contrib-concat": "^1",
|
||||||
@@ -58,6 +56,7 @@
|
|||||||
"grunt-contrib-requirejs": "^1",
|
"grunt-contrib-requirejs": "^1",
|
||||||
"grunt-contrib-uglify": "^1",
|
"grunt-contrib-uglify": "^1",
|
||||||
"grunt-contrib-watch": "^1.1.0",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
|
"grunt-shell": "^4.0.0",
|
||||||
"grunt-webpack": "^1.0.8",
|
"grunt-webpack": "^1.0.8",
|
||||||
"husky": "^3.1.0",
|
"husky": "^3.1.0",
|
||||||
"jison": "~0.3.0",
|
"jison": "~0.3.0",
|
||||||
@@ -76,20 +75,18 @@
|
|||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "types/index.d.ts",
|
"types": "types/index.d.ts",
|
||||||
"browser": {
|
"browser": "./dist/cjs/handlebars.js",
|
||||||
".": "./dist/cjs/handlebars.js",
|
|
||||||
"./runtime": "./dist/cjs/handlebars.runtime.js"
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"handlebars": "bin/handlebars"
|
"handlebars": "bin/handlebars"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "grunt build",
|
"build": "grunt build",
|
||||||
|
"release": "npm run build && grunt release",
|
||||||
"format": "prettier --write '**/*.js' && eslint --fix .",
|
"format": "prettier --write '**/*.js' && eslint --fix .",
|
||||||
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||||
"lint:eslint": "eslint --max-warnings 0 .",
|
"lint:eslint": "eslint --max-warnings 0 .",
|
||||||
"lint:prettier": "prettier --check '**/*.js'",
|
"lint:prettier": "prettier --check '**/*.js'",
|
||||||
"lint:types": "dtslint types",
|
"lint:types": "tsc --noEmit --project types",
|
||||||
"test": "npm run test:mocha",
|
"test": "npm run test:mocha",
|
||||||
"test:mocha": "grunt build && grunt test",
|
"test:mocha": "grunt build && grunt test",
|
||||||
"test:browser": "playwright test --config tests/browser/playwright.config.js tests/browser/spec.js",
|
"test:browser": "playwright test --config tests/browser/playwright.config.js tests/browser/spec.js",
|
||||||
|
|||||||
+22
-1
@@ -2,7 +2,28 @@
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.7...master)
|
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.9...master)
|
||||||
|
|
||||||
|
## v4.7.9 - March 26th, 2026
|
||||||
|
- fix: enable shell mode for spawn to resolve Windows EINVAL issue - e0137c2
|
||||||
|
- fix type "RuntimeOptions" also accepting string partials - eab1d14
|
||||||
|
- feat(types): set `hash` to be a `Record<string, any>` - de4414d
|
||||||
|
- fix non-contiguous program indices - 4512766
|
||||||
|
- refactor: rename i to startPartIndex - e497a35
|
||||||
|
- security: fix security issues - 68d8df5
|
||||||
|
|
||||||
|
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.8...v4.7.9)
|
||||||
|
|
||||||
|
## v4.7.8 - July 27th, 2023
|
||||||
|
|
||||||
|
- Make library compatible with workers (#1894) - 3d3796c
|
||||||
|
- Don't rely on Node.js global object (#1776) - 2954e7e
|
||||||
|
- Fix compiling of each block params in strict mode (#1855) - 30dbf04
|
||||||
|
- Fix rollup warning when importing Handlebars as ESM - 03d387b
|
||||||
|
- Fix bundler issue with webpack 5 (#1862) - c6c6bbb
|
||||||
|
- Use https instead of git for mustache submodule - 88ac068
|
||||||
|
|
||||||
|
[Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.7...v4.7.8)
|
||||||
|
|
||||||
## v4.7.7 - February 15th, 2021
|
## v4.7.7 - February 15th, 2021
|
||||||
|
|
||||||
|
|||||||
@@ -324,6 +324,15 @@ describe('builtin helpers', function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('each with block params and strict compilation', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}'
|
||||||
|
)
|
||||||
|
.withCompileOptions({ strict: true })
|
||||||
|
.withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }] })
|
||||||
|
.toCompileTo('0. goodbye!1. Goodbye!');
|
||||||
|
});
|
||||||
|
|
||||||
it('each object with @index', function() {
|
it('each object with @index', function() {
|
||||||
expectTemplate(
|
expectTemplate(
|
||||||
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
||||||
|
|||||||
@@ -128,6 +128,115 @@ describe('compiler', function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createPathExpressionAST(depth, parts) {
|
||||||
|
return {
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: { open: false, close: false },
|
||||||
|
path: {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: depth,
|
||||||
|
parts: parts,
|
||||||
|
original: 'this'
|
||||||
|
},
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should safely handle AST with non-integer PathExpression depth', function() {
|
||||||
|
// depth '0' is coerced to 0 via Number(), compiles safely
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST('0', ['this']))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with negative PathExpression depth', function() {
|
||||||
|
// Negative depth is clamped to 0
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(-1, ['this']))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with fractional PathExpression depth', function() {
|
||||||
|
// Fractional depth is floored to an integer
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(0.5, ['this']))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with non-array PathExpression parts', function() {
|
||||||
|
// Non-array parts are coerced to empty array, compiles safely
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(0, 'this'))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with non-string PathExpression part', function() {
|
||||||
|
// Non-string parts are coerced to strings via String()
|
||||||
|
var result = Handlebars.compile(createPathExpressionAST(0, [1]))();
|
||||||
|
expect(result).to.be.a('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should safely handle AST with non-boolean BooleanLiteral value type', function() {
|
||||||
|
// The compiler coerces BooleanLiteral.value via === true before
|
||||||
|
// emitting a pushLiteral opcode, so a non-boolean value like the
|
||||||
|
// string 'true' becomes the literal 'false'.
|
||||||
|
var loc = {
|
||||||
|
source: null,
|
||||||
|
start: { line: 1, column: 0 },
|
||||||
|
end: { line: 1, column: 10 }
|
||||||
|
};
|
||||||
|
var result = Handlebars.compile({
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: { open: false, close: false },
|
||||||
|
loc: loc,
|
||||||
|
path: {
|
||||||
|
type: 'BooleanLiteral',
|
||||||
|
value: 'true',
|
||||||
|
original: true,
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})();
|
||||||
|
// 'true' !== true, so the compiler emits pushLiteral('false').
|
||||||
|
// Handlebars does not render falsy values, so the output is empty.
|
||||||
|
expect(result).to.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore loc metadata in AST nodes', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.compile({
|
||||||
|
type: 'Program',
|
||||||
|
meta: null,
|
||||||
|
loc: { source: 'fake', start: { line: 1, column: 0 } },
|
||||||
|
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||||
|
})(),
|
||||||
|
'Hello'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept AST with valid NumberLiteral values', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.compile(Handlebars.parse('{{lookup this 1}}'))(['a', 'b']),
|
||||||
|
'b'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept AST with valid BooleanLiteral values', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.compile(Handlebars.parse('{{#if true}}ok{{/if}}'))({}),
|
||||||
|
'ok'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('can pass through an empty string', function() {
|
it('can pass through an empty string', function() {
|
||||||
equal(Handlebars.compile('')(), '');
|
equal(Handlebars.compile('')(), '');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['bom'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["bom"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "a";
|
return "a";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = CustomNamespace.templates = CustomNamespace.templates || {};
|
||||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
})();
|
})();
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
templates['firstTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["firstTemplate"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>1</div>";
|
return "<div>1</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
templates['secondTemplate'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["secondTemplate"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>2</div>";
|
return "<div>2</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
return templates;
|
return templates;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['artifacts/partial.template'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["artifacts/partial.template"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>Test Partial</div>";
|
return "<div>Test Partial</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['some-path/handlebars.runtime'], function(Handlebars) {
|
define(["some-path/handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = someNameSpace = someNameSpace || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = someNameSpace = someNameSpace || {};
|
||||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
templates['empty'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates["empty"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "";
|
return "";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
return templates;
|
return templates;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['non.default.extension'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return templates["non.default.extension"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>This is a test</div>";
|
return "<div>This is a test</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return templates['known.helpers'] = template({"1":function(container,depth0,helpers,partials,data) {
|
return templates["known.helpers"] = template({"0":function(container,depth0,helpers,partials,data) {
|
||||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
return parent[propertyName];
|
return parent[propertyName];
|
||||||
@@ -8,8 +8,8 @@ return parent[propertyName];
|
|||||||
return undefined
|
return undefined
|
||||||
};
|
};
|
||||||
return " <div>Some known helper</div>\n"
|
return " <div>Some known helper</div>\n"
|
||||||
+ ((stack1 = lookupProperty(helpers,"anotherHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"anotherHelper","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":3,"column":4},"end":{"line":5,"column":22}}})) != null ? stack1 : "");
|
+ ((stack1 = lookupProperty(helpers,"anotherHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"anotherHelper","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":3,"column":4},"end":{"line":5,"column":22}}})) != null ? stack1 : "");
|
||||||
},"2":function(container,depth0,helpers,partials,data) {
|
},"1":function(container,depth0,helpers,partials,data) {
|
||||||
return " <div>Another known helper</div>\n";
|
return " <div>Another known helper</div>\n";
|
||||||
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
@@ -18,7 +18,7 @@ return parent[propertyName];
|
|||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
};
|
};
|
||||||
return ((stack1 = lookupProperty(helpers,"someHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"someHelper","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":15}}})) != null ? stack1 : "");
|
return ((stack1 = lookupProperty(helpers,"someHelper").call(depth0 != null ? depth0 : (container.nullContext || {}),true,{"name":"someHelper","hash":{},"fn":container.program(0, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":15}}})) != null ? stack1 : "");
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
define(['handlebars.runtime'], function(Handlebars) {
|
define(["handlebars.runtime"], function(Handlebars) {
|
||||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
return Handlebars.partials['partial.template'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
return Handlebars.partials["partial.template"] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
return "<div>Test Partial</div>";
|
return "<div>Test Partial</div>";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
});
|
});
|
||||||
+82
-1
@@ -182,7 +182,7 @@ describe('precompiler', function() {
|
|||||||
return 'amd';
|
return 'amd';
|
||||||
};
|
};
|
||||||
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
|
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
|
||||||
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
|
equal(/return Handlebars\.partials\["empty"\]/.test(log), true);
|
||||||
equal(/template\(amd\)/.test(log), true);
|
equal(/template\(amd\)/.test(log), true);
|
||||||
});
|
});
|
||||||
it('should output multiple amd partials', function() {
|
it('should output multiple amd partials', function() {
|
||||||
@@ -405,4 +405,85 @@ describe('precompiler', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GHSA-xjpj-3mr7-gcpf: precompiler output escaping', function() {
|
||||||
|
var FullHandlebars = require('../dist/cjs/handlebars')['default'];
|
||||||
|
|
||||||
|
function runCliAndCaptureOutput(options) {
|
||||||
|
var output = '';
|
||||||
|
var oldLog = console.log;
|
||||||
|
console.log = function() {
|
||||||
|
output += Array.prototype.join.call(arguments, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
Precompiler.cli(options);
|
||||||
|
} finally {
|
||||||
|
console.log = oldLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should not inject raw template names into generated code', function() {
|
||||||
|
var output = runCliAndCaptureOutput({
|
||||||
|
templates: [
|
||||||
|
{
|
||||||
|
name: "evil'];global.__xjpjName=1;//",
|
||||||
|
source: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
amd: true
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(output).to.not.match(/\['evil'\];global\.__xjpjName=1/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not inject raw commonjs option values into generated code', function() {
|
||||||
|
var output = runCliAndCaptureOutput({
|
||||||
|
templates: [{ name: 'safe', source: '' }],
|
||||||
|
commonjs: 'handlebars");global.__xjpjCommon=1;//'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(output).to.not.match(
|
||||||
|
/require\("handlebars"\);global\.__xjpjCommon=1/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid namespace expressions', function() {
|
||||||
|
expect(function() {
|
||||||
|
runCliAndCaptureOutput({
|
||||||
|
templates: [{ name: 'safe', source: '' }],
|
||||||
|
namespace: 'App.ns;global.__xjpjNamespace=1;//'
|
||||||
|
});
|
||||||
|
}).to.throw(/Invalid namespace/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sanitize sourceMappingURL comment values', function() {
|
||||||
|
var oldPrecompile = FullHandlebars.precompile;
|
||||||
|
var oldWriteFileSync = fs.writeFileSync;
|
||||||
|
FullHandlebars.precompile = function() {
|
||||||
|
return {
|
||||||
|
code: '""',
|
||||||
|
map: '{"version":3,"sources":[],"names":[],"mappings":""}'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
fs.writeFileSync = function() {};
|
||||||
|
|
||||||
|
var output;
|
||||||
|
try {
|
||||||
|
output = runCliAndCaptureOutput({
|
||||||
|
templates: [{ name: 'safe', source: '' }],
|
||||||
|
map: 'good.js.map\n;global.__xjpjMap=1;//'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
FullHandlebars.precompile = oldPrecompile;
|
||||||
|
fs.writeFileSync = oldWriteFileSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(output).to.not.match(
|
||||||
|
/sourceMappingURL=[^\n]*\n;global\.__xjpjMap=1/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-3
@@ -54,6 +54,13 @@ describe('runtime', function() {
|
|||||||
/Template was precompiled with an older version of Handlebars than the current runtime/
|
/Template was precompiled with an older version of Handlebars than the current runtime/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should safely resolve missing partial map entries', function() {
|
||||||
|
equal(
|
||||||
|
Handlebars.VM.resolvePartial(undefined, {}, { name: 'missing' }),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#child', function() {
|
describe('#child', function() {
|
||||||
@@ -91,13 +98,13 @@ describe('runtime', function() {
|
|||||||
it('should expose child template', function() {
|
it('should expose child template', function() {
|
||||||
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
|
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
|
||||||
// Calling twice to hit the non-compiled case.
|
// Calling twice to hit the non-compiled case.
|
||||||
equal(template._child(1)(), 'bar');
|
equal(template._child(0)(), 'bar');
|
||||||
equal(template._child(1)(), 'bar');
|
equal(template._child(0)(), 'bar');
|
||||||
});
|
});
|
||||||
it('should render depthed content', function() {
|
it('should render depthed content', function() {
|
||||||
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
|
||||||
// Calling twice to hit the non-compiled case.
|
// Calling twice to hit the non-compiled case.
|
||||||
equal(template._child(1, undefined, [], [{ bar: 'baz' }])(), 'baz');
|
equal(template._child(0, undefined, [], [{ bar: 'baz' }])(), 'baz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -133,11 +133,13 @@ describe('security issues', function() {
|
|||||||
'{{__defineGetter__}}',
|
'{{__defineGetter__}}',
|
||||||
'{{__defineSetter__}}',
|
'{{__defineSetter__}}',
|
||||||
'{{__lookupGetter__}}',
|
'{{__lookupGetter__}}',
|
||||||
|
'{{__lookupSetter__}}',
|
||||||
'{{__proto__}}',
|
'{{__proto__}}',
|
||||||
'{{lookup this "constructor"}}',
|
'{{lookup this "constructor"}}',
|
||||||
'{{lookup this "__defineGetter__"}}',
|
'{{lookup this "__defineGetter__"}}',
|
||||||
'{{lookup this "__defineSetter__"}}',
|
'{{lookup this "__defineSetter__"}}',
|
||||||
'{{lookup this "__lookupGetter__"}}',
|
'{{lookup this "__lookupGetter__"}}',
|
||||||
|
'{{lookup this "__lookupSetter__"}}',
|
||||||
'{{lookup this "__proto__"}}'
|
'{{lookup this "__proto__"}}'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -422,6 +424,224 @@ describe('security issues', function() {
|
|||||||
.toCompileTo('c');
|
.toCompileTo('c');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GHSA-2qvq-rjwj-gvw9: partial resolution must not use polluted prototypes', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
delete Object.prototype.widget;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not resolve partial names from Object.prototype', function() {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Object.prototype.widget = '<img src=x onerror="alert(1)">';
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
Handlebars.compile('<div>{{> widget}}</div>')({});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-2w6w-674q-4c4q, GHSA-xhpv-hc6g-r9c6, GHSA-3mfm-83xf-c92r: untrusted AST inputs', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInjectedProgram() {
|
||||||
|
var loc = {
|
||||||
|
source: null,
|
||||||
|
start: { line: 1, column: 0 },
|
||||||
|
end: { line: 1, column: 20 }
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: {
|
||||||
|
open: false,
|
||||||
|
close: false
|
||||||
|
},
|
||||||
|
loc: loc,
|
||||||
|
path: {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 0,
|
||||||
|
parts: ['lookup'],
|
||||||
|
original: 'lookup',
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 0,
|
||||||
|
parts: [],
|
||||||
|
original: 'this',
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'NumberLiteral',
|
||||||
|
value: '{},{})) + (Function) + (({}',
|
||||||
|
original: 1,
|
||||||
|
loc: loc
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should neutralize AST NumberLiteral type confusion in compile()', function() {
|
||||||
|
// The compiler coerces NumberLiteral.value via Number() before
|
||||||
|
// emitting a pushLiteral opcode, so a type-confused string value
|
||||||
|
// becomes NaN, preventing code injection.
|
||||||
|
var template = Handlebars.compile(createInjectedProgram());
|
||||||
|
var result = template({});
|
||||||
|
expect(result).to.not.contain('Function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject AST objects passed via dynamic partial lookup', function() {
|
||||||
|
expect(function() {
|
||||||
|
var template = Handlebars.compile('{{> (lookup . "payload")}}');
|
||||||
|
template({
|
||||||
|
payload: createInjectedProgram()
|
||||||
|
});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sanitize param depth in stringParams mode', function() {
|
||||||
|
// pushParam passes val.depth directly to the getContext opcode.
|
||||||
|
// In stringParams mode, getContext stores the depth in lastContext,
|
||||||
|
// which contextName interpolates into generated code as
|
||||||
|
// 'depths[' + depth + ']'. A malicious depth string can escape the
|
||||||
|
// bracket expression and inject arbitrary code at template runtime.
|
||||||
|
//
|
||||||
|
// With sanitization the depth becomes 0, producing 'depth0' (safe).
|
||||||
|
// Without sanitization the injected expression executes and throws.
|
||||||
|
var loc = {
|
||||||
|
source: null,
|
||||||
|
start: { line: 1, column: 0 },
|
||||||
|
end: { line: 1, column: 20 }
|
||||||
|
};
|
||||||
|
var maliciousAST = {
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
escaped: true,
|
||||||
|
strip: { open: false, close: false },
|
||||||
|
loc: loc,
|
||||||
|
path: {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 0,
|
||||||
|
parts: ['lookup'],
|
||||||
|
original: 'lookup',
|
||||||
|
loc: loc
|
||||||
|
},
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 'function(){throw new Error("INJECTION")}()',
|
||||||
|
parts: [],
|
||||||
|
original: '',
|
||||||
|
loc: loc
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var template = Handlebars.compile(maliciousAST, {
|
||||||
|
stringParams: true
|
||||||
|
});
|
||||||
|
// After sanitization the depth is 0, so the template runs without
|
||||||
|
// executing the injected throw expression.
|
||||||
|
expect(function() {
|
||||||
|
template({});
|
||||||
|
}).to.not.throw();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-442j-39wm-28r2: lookup must return checked value', function() {
|
||||||
|
it('should use the validated value from lookupProperty() in compat mode', function() {
|
||||||
|
var input = { child: {} };
|
||||||
|
var readCount = 0;
|
||||||
|
Object.defineProperty(input, 'unstable', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function() {
|
||||||
|
readCount++;
|
||||||
|
return readCount === 1 ? 'first-read' : 'second-read';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectTemplate('{{#with child}}{{unstable}}{{/with}}')
|
||||||
|
.withInput(input)
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('first-read');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-9cx6-37pm-9jff: malformed decorators should fail safely', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should throw a controlled error for unknown decorators', function() {
|
||||||
|
var template = Handlebars.compile('{{*notRegistered}}');
|
||||||
|
expect(function() {
|
||||||
|
template({});
|
||||||
|
}).to.throw(/Missing decorator|not registered/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GHSA-new: @partial-block must not resolve from polluted prototype', function() {
|
||||||
|
if (!Handlebars.compile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
delete Object.prototype['partial-block'];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not resolve @partial-block from Object.prototype', function() {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Object.prototype['partial-block'] = '<img src=x onerror="alert(1)">';
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
Handlebars.compile('{{> @partial-block}}')({});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not resolve @partial-block from Object.prototype inside a partial', function() {
|
||||||
|
// eslint-disable-next-line no-extend-native
|
||||||
|
Object.prototype['partial-block'] = '<img src=x onerror="alert(1)">';
|
||||||
|
|
||||||
|
Handlebars.registerPartial('testPartial', '{{> @partial-block}}');
|
||||||
|
try {
|
||||||
|
expect(function() {
|
||||||
|
Handlebars.compile('{{> testPartial}}')({});
|
||||||
|
}).to.throw(/could not be found/);
|
||||||
|
} finally {
|
||||||
|
Handlebars.unregisterPartial('testPartial');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still render legitimate @partial-block content', function() {
|
||||||
|
Handlebars.registerPartial('wrapper', '<div>{{> @partial-block}}</div>');
|
||||||
|
try {
|
||||||
|
var result = Handlebars.compile('{{#> wrapper}}hello{{/wrapper}}')({});
|
||||||
|
expect(result).to.equal('<div>hello</div>');
|
||||||
|
} finally {
|
||||||
|
Handlebars.unregisterPartial('wrapper');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function wrapToAdjustContainer(precompiledTemplateFunction) {
|
function wrapToAdjustContainer(precompiledTemplateFunction) {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ async function execFileWithInheritedOutput(command, args) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const resolvedCommand = preferLocalDependencies(command);
|
const resolvedCommand = preferLocalDependencies(command);
|
||||||
const child = childProcess.spawn(resolvedCommand, args, {
|
const child = childProcess.spawn(resolvedCommand, args, {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit',
|
||||||
|
shell: process.platform === 'win32' // Workaround for CVE-2024-27980
|
||||||
});
|
});
|
||||||
child.on('exit', code => {
|
child.on('exit', code => {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
|
|||||||
@@ -9,6 +9,5 @@ Execute the following commands in the project root:
|
|||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npx grunt prepare
|
npx grunt prepare
|
||||||
docker pull mcr.microsoft.com/playwright:focal
|
docker run -it --rm --volume $(pwd):/srv/app --workdir /srv/app --ipc=host mcr.microsoft.com/playwright:v1.44.1-jammy npm run test:browser
|
||||||
docker run -it --rm --volume $(pwd):/srv/app --workdir /srv/app --ipc=host mcr.microsoft.com/playwright:focal npm run test:browser
|
|
||||||
```
|
```
|
||||||
@@ -2,6 +2,7 @@ const { devices } = require('@playwright/test');
|
|||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
|
testMatch: ['spec.js'],
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
|
|||||||
+10
-10
@@ -7,27 +7,27 @@ async function waitForMochaAndAssertResult(page) {
|
|||||||
expect(mochaResults.failures).toBe(0);
|
expect(mochaResults.failures).toBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
test('Spec handlebars.js', async ({ page, baseURL }) => {
|
test('Spec handlebars.js', async ({ page }) => {
|
||||||
await page.goto(`${baseURL}/spec/?headless=true`);
|
await page.goto(`/spec/?headless=true`);
|
||||||
await waitForMochaAndAssertResult(page);
|
await waitForMochaAndAssertResult(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Spec handlebars.amd.js (AMD)', async ({ page, baseURL }) => {
|
test('Spec handlebars.amd.js (AMD)', async ({ page }) => {
|
||||||
await page.goto(`${baseURL}/spec/amd.html?headless=true`);
|
await page.goto(`/spec/amd.html?headless=true`);
|
||||||
await waitForMochaAndAssertResult(page);
|
await waitForMochaAndAssertResult(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Spec handlebars.runtime.amd.js (AMD)', async ({ page, baseURL }) => {
|
test('Spec handlebars.runtime.amd.js (AMD)', async ({ page }) => {
|
||||||
await page.goto(`${baseURL}/spec/amd-runtime.html?headless=true`);
|
await page.goto(`/spec/amd-runtime.html?headless=true`);
|
||||||
await waitForMochaAndAssertResult(page);
|
await waitForMochaAndAssertResult(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Spec handlebars.js (UMD)', async ({ page, baseURL }) => {
|
test('Spec handlebars.js (UMD)', async ({ page }) => {
|
||||||
await page.goto(`${baseURL}/spec/umd.html?headless=true`);
|
await page.goto(`/spec/umd.html?headless=true`);
|
||||||
await waitForMochaAndAssertResult(page);
|
await waitForMochaAndAssertResult(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Spec handlebars.runtime.js (UMD)', async ({ page, baseURL }) => {
|
test('Spec handlebars.runtime.js (UMD)', async ({ page }) => {
|
||||||
await page.goto(`${baseURL}/spec/umd-runtime.html?headless=true`);
|
await page.goto(`/spec/umd-runtime.html?headless=true`);
|
||||||
await waitForMochaAndAssertResult(page);
|
await waitForMochaAndAssertResult(page);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ cd "$( dirname "$( readlink -f "$0" )" )" || exit 1
|
|||||||
unset npm_config_prefix
|
unset npm_config_prefix
|
||||||
|
|
||||||
echo "Handlebars should be able to run in various versions of NodeJS"
|
echo "Handlebars should be able to run in various versions of NodeJS"
|
||||||
for node_version_to_test in 0.10 0.12 4 5 6 7 8 9 10 11 12 13 14 15 16 17; do
|
for node_version_to_test in 0.10 0.12 4 5 6 7 8 9 10 11 12 13 14 15 16 18; do
|
||||||
|
|
||||||
rm target node_modules package-lock.json -rf
|
rm target node_modules package-lock.json -rf
|
||||||
mkdir target
|
mkdir target
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ set -e
|
|||||||
|
|
||||||
# Cleanup: package-lock and "npm ci" is not working with local dependencies
|
# Cleanup: package-lock and "npm ci" is not working with local dependencies
|
||||||
rm dist package-lock.json -rf
|
rm dist package-lock.json -rf
|
||||||
npm install
|
npm install --legacy-peer-deps
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
for i in dist/*-test.js ; do
|
for i in dist/*-test.js ; do
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "webpack-test",
|
"name": "webpack-test",
|
||||||
"description": "Various tests with Handlebars and Webpack",
|
"description": "Various tests with Handlebars and multiple webpack versions",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -8,14 +8,8 @@
|
|||||||
"test": "node dist/main.js"
|
"test": "node dist/main.js"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"keywords": [],
|
"dependencies": {
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
|
||||||
"handlebars": "file:../../..",
|
"handlebars": "file:../../..",
|
||||||
"handlebars-loader": "^1.7.1",
|
"handlebars-loader": "^1.7.1"
|
||||||
"webpack": "^4.39.3",
|
|
||||||
"webpack-cli": "^3.3.7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import Handlebars from 'handlebars/lib/handlebars';
|
||||||
|
import { assertEquals } from './lib/assert';
|
||||||
|
|
||||||
|
const template = Handlebars.compile('Author: {{author}}');
|
||||||
|
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import * as Handlebars from 'handlebars/runtime';
|
||||||
|
import { assertEquals } from './lib/assert';
|
||||||
|
|
||||||
|
const template = Handlebars.template({
|
||||||
|
compiler: [8, '>= 4.3.0'],
|
||||||
|
main: function(container, depth0, helpers, partials, data) {
|
||||||
|
var helper,
|
||||||
|
lookupProperty =
|
||||||
|
container.lookupProperty ||
|
||||||
|
function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
'Author: ' +
|
||||||
|
container.escapeExpression(
|
||||||
|
((helper =
|
||||||
|
(helper =
|
||||||
|
lookupProperty(helpers, 'author') ||
|
||||||
|
(depth0 != null ? lookupProperty(depth0, 'author') : depth0)) !=
|
||||||
|
null
|
||||||
|
? helper
|
||||||
|
: container.hooks.helperMissing),
|
||||||
|
typeof helper === 'function'
|
||||||
|
? helper.call(depth0 != null ? depth0 : container.nullContext || {}, {
|
||||||
|
name: 'author',
|
||||||
|
hash: {},
|
||||||
|
data: data,
|
||||||
|
loc: {
|
||||||
|
start: { line: 1, column: 8 },
|
||||||
|
end: { line: 1, column: 18 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: helper)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
useData: true
|
||||||
|
});
|
||||||
|
assertEquals(template({ author: 'Yehuda' }), 'Author: Yehuda');
|
||||||
@@ -2,15 +2,26 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
run_tests () {
|
||||||
|
for i in dist/*-test.js ; do
|
||||||
|
echo "----------------------"
|
||||||
|
echo "-- Running $i"
|
||||||
|
echo "----------------------"
|
||||||
|
node "$i"
|
||||||
|
echo "Success"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# Cleanup: package-lock and "npm ci" is not working with local dependencies
|
# Cleanup: package-lock and "npm ci" is not working with local dependencies
|
||||||
rm dist package-lock.json -rf
|
rm dist package-lock.json -rf
|
||||||
npm install
|
npm install --legacy-peer-deps
|
||||||
npm run build
|
|
||||||
|
|
||||||
for i in dist/*-test.js ; do
|
# Test with webpack 4
|
||||||
echo "----------------------"
|
npm install --legacy-peer-deps --no-save webpack@^4 webpack-cli@^3
|
||||||
echo "-- Running $i"
|
npm run build
|
||||||
echo "----------------------"
|
run_tests
|
||||||
node "$i"
|
|
||||||
echo "Success"
|
# Test with webpack 5
|
||||||
done
|
npm install --legacy-peer-deps --no-save webpack@^5 webpack-cli@^4
|
||||||
|
npm run build
|
||||||
|
run_tests
|
||||||
Vendored
+3
-3
@@ -25,7 +25,7 @@ declare namespace Handlebars {
|
|||||||
partial?: boolean;
|
partial?: boolean;
|
||||||
depths?: any[];
|
depths?: any[];
|
||||||
helpers?: { [name: string]: Function };
|
helpers?: { [name: string]: Function };
|
||||||
partials?: { [name: string]: HandlebarsTemplateDelegate };
|
partials?: { [name: string]: Template };
|
||||||
decorators?: { [name: string]: Function };
|
decorators?: { [name: string]: Function };
|
||||||
data?: any;
|
data?: any;
|
||||||
blockParams?: any[];
|
blockParams?: any[];
|
||||||
@@ -39,7 +39,7 @@ declare namespace Handlebars {
|
|||||||
export interface HelperOptions {
|
export interface HelperOptions {
|
||||||
fn: TemplateDelegate;
|
fn: TemplateDelegate;
|
||||||
inverse: TemplateDelegate;
|
inverse: TemplateDelegate;
|
||||||
hash: any;
|
hash: Record<string, any>;
|
||||||
data?: any;
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ declare namespace Handlebars {
|
|||||||
export function unregisterHelper(name: string): void;
|
export function unregisterHelper(name: string): void;
|
||||||
|
|
||||||
export function registerPartial(name: string, fn: Template): void;
|
export function registerPartial(name: string, fn: Template): void;
|
||||||
export function registerPartial(spec: { [name: string]: HandlebarsTemplateDelegate }): void;
|
export function registerPartial(spec: { [name: string]: Template }): void;
|
||||||
export function unregisterPartial(name: string): void;
|
export function unregisterPartial(name: string): void;
|
||||||
|
|
||||||
// TODO: replace Function with actual signature
|
// TODO: replace Function with actual signature
|
||||||
|
|||||||
@@ -249,6 +249,9 @@ function testProtoAccessControlControlOptions() {
|
|||||||
allowedProtoProperties: { allowedProperty: true, forbiddenProperty: false },
|
allowedProtoProperties: { allowedProperty: true, forbiddenProperty: false },
|
||||||
allowProtoMethodsByDefault: true,
|
allowProtoMethodsByDefault: true,
|
||||||
allowProtoPropertiesByDefault: false,
|
allowProtoPropertiesByDefault: false,
|
||||||
|
partials: {
|
||||||
|
link: '<a href="/people/{{id}}">{{name}}</a>'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user