Do our own HTML escaping using replace(), makes the escaping less aggressive
authorNick Downing <nick@ndcode.org>
Mon, 19 Nov 2018 06:50:16 +0000 (17:50 +1100)
committerNick Downing <nick@ndcode.org>
Mon, 19 Nov 2018 06:50:16 +0000 (17:50 +1100)
README.md
jst.js
package.json
src/expression.js
visitors.js

index 6c5835a..c7bef3a 100644 (file)
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@ This generates the text:
 <html lang="en"><body>Hello, world</body></html>
 ```
 
-If the text is multi-line, backquoted (template) strings are be used, to allow
+If the text is multi-line, backquoted (template) strings are used, to allow
 embedded newlines. When the entire text is held in a variable, e.g. `myvar`, a
 template string such as ```${myvar}``` should be used, to convert it into a
 standalone statement consisting of only a string constant, and hence into HTML.
@@ -177,10 +177,14 @@ must be called `_out` and must be a JavaScript array of string fragments. As
 each output fragment (such as an opening or closing tag or an embedded string)
 is generated, it will be sent to the output buffer by an `_out.push(...)` call.
 
-If there is dynamically generated text in the template, then there must be a
-function `_html_escape()` provided in the current scope, that escapes the text.
+If there is dynamically generated text in the template, then it will be esacped
+by the sequence `.replace("&", "&amp;").replace("<", "&lt;")`, this is chosen
+so that no external dependency such as the `html-entities` package is needed,
+and so that unnecessary escaping of characters such as `'` or `>` is not done.
+Attributes are done similarly, except that `"` is replaced instead of `<`. We
+assume a `UTF-8` environment, so there is really little need for HTML entities.
 
-For example, consider a realistic template which we use on our demo server:
+For example, consider a realistic template which we use on our demo server:
 ```
 html(lang=lang) {
   head {
@@ -194,7 +198,7 @@ html(lang=lang) {
 
 This compiles to the following plain JavaScript code:
 ```
-_out.push("<html lang=\"" + _html_escape(lang.toString()) + "\">");
+_out.push("<html lang=\"" + lang.toString().replace("&", "&amp;").replace("\"", "&quot;") + "\">");
 {
   _out.push("<head>");
   _out.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/styles.css\">");
@@ -204,7 +208,7 @@ _out.push("<html lang=\"" + _html_escape(lang.toString()) + "\">");
   _out.push("<body>");
   {
     _out.push("<p>");
-    _out.push(_html_escape(`Hello, ${name}`));
+    _out.push(`Hello, ${name}`.replace("&", "&amp;").replace("<", "&lt;"));
     _out.push("</p>");
   }
   _out.push("</body>");
diff --git a/jst.js b/jst.js
index b71133e..2e26ba8 100644 (file)
--- a/jst.js
+++ b/jst.js
@@ -27,7 +27,6 @@ let assert = require('assert')
 let astring = require('astring')
 let disk_build = require('@ndcode/disk_build')
 let fs = require('fs')
-let html_escape = require('html-escape')
 let path = require('path')
 let transform = require('./transform')
 let visitors = require('./visitors')
@@ -40,7 +39,7 @@ let build_cache = new BuildCache()
 let jst = (pathname, root, args) => /*await*/ build_cache.get(
   pathname,
   async result => {
-    let arg_names = ['_require', '_html_escape', '_pathname', '_root']
+    let arg_names = ['_require', '_pathname', '_root']
     let arg_values = [
       async require_pathname => {
         let temp = path.posix.dirname(pathname)
@@ -51,7 +50,6 @@ let jst = (pathname, root, args) => /*await*/ build_cache.get(
         require_pathname = path.posix.resolve(temp, require_pathname)
         return jst(require_pathname, root, args)
       },
-      html_escape,
       pathname,
       root
     ]
index 86c7231..d3c3bdb 100644 (file)
@@ -29,7 +29,6 @@
     "@ndcode/disk_build": "^0.1.0",
     "assert": "^1.4.1",
     "astring": "^1.3.1",
-    "html-escape": "^2.0.0",
     "rollup": "^0.45.0",
     "rollup-plugin-buble": "^0.16.0",
     "uglify-es": "^3.3.9"
index 966f90a..e40808f 100644 (file)
@@ -24,7 +24,6 @@ import {functionFlags, SCOPE_ARROW, SCOPE_SUPER, SCOPE_DIRECT_SUPER, BIND_OUTSID
 
 // Nick
 import CleanCSS from "@ndcode/clean-css"
-import html_escape from "html-escape"
 let clean_css = new CleanCSS()
 
 const pp = Parser.prototype
@@ -330,7 +329,7 @@ pp.parseSubscripts = function(base, startPos, startLoc, noCalls) {
               type: 'ExpressionStatement',
               expression: {
                 type: 'Literal',
-                value: html_escape(render.styles)
+                value: render.styles // we simply assume it does not contain </style> tags, should HTML escape it??
               }
             }
           ]
index 5bce925..766daf8 100644 (file)
@@ -25,7 +25,6 @@ let assert = require('assert')
 let astring = require('astring')
 let transform = require('./transform')
 let uglify_es = require('uglify-es')
-let html_escape = require('html-escape')
 
 let expr_to_tag = (node, context, html_allowed, call_allowed) => {
   if (node.type === 'Identifier')
@@ -120,7 +119,11 @@ let html_body = (context, st, c) => {
     if (value_expr !== undefined) {
       prefix += '="'
       if (value_expr.type === 'Literal')
-        prefix += html_escape(value_expr.value)
+        prefix +=
+          value_expr.value
+            .toString()
+            .replace('&', '&amp;')
+            .replace('"', '&quot;')
       else {
         let expr1 = {
           type: 'Literal',
@@ -138,23 +141,56 @@ let html_body = (context, st, c) => {
           right: {
             type: 'CallExpression',
             callee: {
-              type: 'Identifier',
-              name: '_html_escape'
-            },
-            arguments: [
-              {
+              type: 'MemberExpression',
+              object: {
                 type: 'CallExpression',
                 callee: {
                   type: 'MemberExpression',
-                  object: value_expr,
+                  object: {
+                    type: 'CallExpression',
+                    callee: {
+                      type: 'MemberExpression',
+                      object: value_expr,
+                      property: {
+                        type: 'Identifier',
+                        name: 'toString'
+                      },
+                      computed: false
+                    },
+                    arguments: [
+                    ]
+                  },
                   property: {
                     type: 'Identifier',
-                    name: 'toString'
+                    name: 'replace'
                   },
                   computed: false
                 },
                 arguments: [
+                  {
+                    type: 'Literal',
+                    value: '&'
+                  },
+                  {
+                    type: 'Literal',
+                    value: '&amp;'
+                  }
                 ]
+              },
+              property: {
+                type: 'Identifier',
+                name: 'replace'
+              },
+              computed: false
+            },
+            arguments: [
+              {
+                type: 'Literal',
+                value: '"'
+              },
+              {
+                type: 'Literal',
+                value: '&quot;'
               }
             ]
           }
@@ -191,7 +227,7 @@ let html_body = (context, st, c) => {
     )
     if (render.error)
       throw render.error
-    prefix += render.code 
+    prefix += render.code // we simply assume it does not contain </script> tags, should HTML escape it? 
   }
   else if (body.length !== 0) {
     let expr1 = {
@@ -302,7 +338,11 @@ visitors.ExpressionStatement = (node, st, c) => {
         arguments: [
           {
             type: 'Literal',
-            value: html_escape(node.expression.value.toString())
+            value:
+              node.expression.value
+                .toString()
+                .replace('&', '&amp;')
+                .replace('<', '&lt;')
           }
         ]
       }
@@ -331,11 +371,44 @@ visitors.ExpressionStatement = (node, st, c) => {
           {
             type: 'CallExpression',
             callee: {
-              type: 'Identifier',
-              name: '_html_escape'
+              type: 'MemberExpression',
+              object: {
+                type: 'CallExpression',
+                callee: {
+                  type: 'MemberExpression',
+                  object: c(node.expression, st, 'Expression'),
+                  property: {
+                    type: 'Identifier',
+                    name: 'replace'
+                  },
+                  computed: false
+                },
+                arguments: [
+                  {
+                    type: 'Literal',
+                    value: '&'
+                  },
+                  {
+                    type: 'Literal',
+                    value: '&amp;'
+                  }
+                ]
+              },
+              property: {
+                type: 'Identifier',
+                name: 'replace'
+              },
+              computed: false
             },
             arguments: [
-              c(node.expression, st, 'Expression')
+              {
+                type: 'Literal',
+                value: '<'
+              },
+              {
+                type: 'Literal',
+                value: '&lt;'
+              }
             ]
           }
         ]