diff --git a/package-lock.json b/package-lock.json index 4423ba0fb..d45ab7eac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@adobe/helix-pipeline", - "version": "1.5.1", + "version": "1.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -12649,10 +12649,9 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", - "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" }, "quick-lru": { "version": "1.1.0", @@ -12980,6 +12979,15 @@ "xtend": "^4.0.1" } }, + "rehype-stringify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-5.0.0.tgz", + "integrity": "sha512-uQVzP/eLMM6kdM5Myo0mt5rrb5ntCAxFGNobtzMTTdL1+hqc6WNVxB9uwZyckm54qz/0SnDkzgvCV0/AZy4wCw==", + "requires": { + "hast-util-to-html": "^5.0.0", + "xtend": "^4.0.1" + } + }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -13114,8 +13122,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "resolve": { "version": "1.8.1", @@ -15311,10 +15318,9 @@ "dev": true }, "url-parse": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", - "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", - "dev": true, + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.6.tgz", + "integrity": "sha512-/B8AD9iQ01seoXmXf9z/MjLZQIdOoYl/+gvsQF6+mpnxaTfG9P7srYaiqaDMyKkR36XMXfhqSHss5MyFAO8lew==", "requires": { "querystringify": "^2.0.0", "requires-port": "^1.0.0" diff --git a/package.json b/package.json index 313432d02..ff53b8e8e 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "micromatch": "^4.0.0", "object-hash": "^1.3.1", "rehype-parse": "^6.0.0", + "rehype-stringify": "^5.0.0", "remark-parse": "^6.0.0", "remark-rehype": "^4.0.0", "request": "^2.87.0", @@ -77,6 +78,7 @@ "unist-util-map": "^1.0.4", "unist-util-select": "^2.0.0", "uri-js": "^4.2.2", + "url-parse": "^1.4.6", "winston": "^3.0.0", "xml2js": "^0.4.19", "xmlbuilder": "^12.0.0" diff --git a/src/html/static-asset-links.js b/src/html/static-asset-links.js new file mode 100644 index 000000000..382ccdb0b --- /dev/null +++ b/src/html/static-asset-links.js @@ -0,0 +1,79 @@ +/* + * Copyright 2019 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +const unified = require('unified'); +const parse = require('rehype-parse'); +const stringify = require('rehype-stringify'); +const map = require('unist-util-map'); +const Url = require('url-parse'); + +function scripts() { + return function transformer(tree) { + map(tree, (node) => { + if (node.tagName === 'script' + && node.properties + && node.properties.src) { + const src = new Url(node.properties.src); + if (src.host === '' && src.query === '' && src.pathname) { + // eslint-disable-next-line no-param-reassign + node.properties.src = `${node.properties.src}`; + } + } + }); + }; +} + +function links() { + return function transformer(tree) { + map(tree, (node) => { + if (node.tagName === 'link' + && node.properties + && node.properties.rel + && Array.isArray(node.properties.rel) + && node.properties.rel.indexOf('stylesheet') !== -1 + && node.properties.href) { + const href = new Url(node.properties.href); + if (href.host === '' && href.query === '' && href.pathname) { + // eslint-disable-next-line no-param-reassign + node.properties.href = `${node.properties.href}`; + } + } + }); + }; +} + +function rewrite({ response: { body, headers } }) { + if (headers && headers['Content-Type'] && headers['Content-Type'].match(/html/)) { + const doc = unified() + .use(parse, { + fragment: false, + }) + .use(scripts) + .use(links) + .use(stringify, { + allowParseErrors: true, + allowDangerousHTML: true, + allowDangerousCharacters: true, + quoteSmart: true, + + }) + .processSync(body); + return { + response: { + body: doc.contents, + }, + }; + } + return {}; +} + +module.exports = rewrite; diff --git a/test/testRewriteStatic.js b/test/testRewriteStatic.js new file mode 100644 index 000000000..974821281 --- /dev/null +++ b/test/testRewriteStatic.js @@ -0,0 +1,86 @@ +/* + * Copyright 2019 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +/* eslint-env mocha */ +const assert = require('assert'); +const rewrite = require('../src/html/static-asset-links'); + +function rw(content) { + return rewrite({ + response: { + body: content, + headers: { + 'Content-Type': 'text/html', + }, + }, + }).response.body; +} + +describe('Test Static Asset Rewriting', () => { + it('Ignores non-HTML', () => { + assert.deepEqual(rewrite({ + response: { + body: '{}', + headers: { + 'Content-Type': 'application/json', + }, + }, + }), {}); + }); + + it('Load simple HTML', async () => { + assert.equal(rw(` + Normal + + Just normal things +`), ` + Normal + + Just normal things +`); + }); + + it('ESI include script tags HTML', async () => { + assert.equal(rw(` + Normal + + + + + Just normal things +`), ` + Normal + + + + + Just normal things +`); + }); + + it('ESI include link tags HTML', async () => { + assert.equal(rw(` + Normal + + + + + Just normal things +`), ` + Normal + + + + + Just normal things +`); + }); +});