diff --git a/src/openwhisk/static.js b/src/openwhisk/static.js
index 2c9c0b5dc..f3f1dcf9f 100644
--- a/src/openwhisk/static.js
+++ b/src/openwhisk/static.js
@@ -97,53 +97,54 @@ function isBinary(type) {
return true;
}
-function rewriteImports(tree) {
- tree.walkAtRules('import', (rule) => {
- if (rule.name === 'import') {
- const [url, queries] = space(rule.params);
- const parsedurl = parser(url);
- if (parsedurl.nodes
- && parsedurl.nodes.length === 1
- && parsedurl.nodes[0].value === 'url'
- && parsedurl.nodes[0].nodes
- && parsedurl.nodes[0].nodes.length === 1
- && parsedurl.nodes[0].nodes[0].type === 'string'
- && typeof parsedurl.nodes[0].nodes[0].value === 'string'
- && typeof parsedurl.nodes[0].nodes[0].quote === 'string') {
- const importuri = uri.parse(parsedurl.nodes[0].nodes[0].value);
- const { quote } = parsedurl.nodes[0].nodes[0];
- if (importuri.reference === 'relative' && !importuri.query) {
- rule.replaceWith(postcss.atRule({
- name: 'import',
- params: `url(${quote}${importuri.path}${quote}) ${queries}`,
- }));
- }
- } else if (parsedurl.nodes
- && parsedurl.nodes[0].type === 'string'
- && typeof parsedurl.nodes[0].value === 'string'
- && typeof parsedurl.nodes[0].quote === 'string') {
- const importuri = uri.parse(parsedurl.nodes[0].value);
- const { quote } = parsedurl.nodes[0];
- if (importuri.reference === 'relative' && !importuri.query) {
- rule.replaceWith(postcss.atRule({
- name: 'import',
- params: `${quote}${importuri.path}${quote} ${queries}`,
- }));
+function rewriteCSS(css, base = '') {
+ function rewriteImports(tree) {
+ tree.walkAtRules('import', (rule) => {
+ if (rule.name === 'import') {
+ const [url, queries] = space(rule.params);
+ const parsedurl = parser(url);
+ if (parsedurl.nodes
+ && parsedurl.nodes.length === 1
+ && parsedurl.nodes[0].value === 'url'
+ && parsedurl.nodes[0].nodes
+ && parsedurl.nodes[0].nodes.length === 1
+ && parsedurl.nodes[0].nodes[0].type === 'string'
+ && typeof parsedurl.nodes[0].nodes[0].value === 'string'
+ && typeof parsedurl.nodes[0].nodes[0].quote === 'string') {
+ const importuri = uri.parse(parsedurl.nodes[0].nodes[0].value);
+ const { quote } = parsedurl.nodes[0].nodes[0];
+ if (importuri.reference === 'relative' && !importuri.query) {
+ rule.replaceWith(postcss.atRule({
+ name: 'import',
+ params: `url(${quote}${importuri.path}${quote}) ${queries}`,
+ }));
+ }
+ } else if (parsedurl.nodes
+ && parsedurl.nodes[0].type === 'string'
+ && typeof parsedurl.nodes[0].value === 'string'
+ && typeof parsedurl.nodes[0].quote === 'string') {
+ const importuri = uri.parse(parsedurl.nodes[0].value);
+ const { quote } = parsedurl.nodes[0];
+ if (importuri.reference === 'relative' && !importuri.query) {
+ rule.replaceWith(postcss.atRule({
+ name: 'import',
+ params: `${quote}${importuri.path}${quote} ${queries}`,
+ }));
+ }
}
}
- }
- });
- return tree;
-}
+ });
+ return tree;
+ }
+
-function rewriteCSS(css) {
const processor = postcss()
.use(rewriteImports)
.use(postcssurl({
url: (asset) => {
// TODO pass in request URL and make it absolute.
if (asset.search === '' && asset.absolutePath !== '.' && asset.relativePath !== '.') {
- return `${asset.relativePath}`;
+ return `${asset.relativePath}`;
}
return asset.url;
},
@@ -151,7 +152,7 @@ function rewriteCSS(css) {
return processor.process(css, { from: undefined }).then(result => result.css);
}
-function rewriteJavaScript(javascript) {
+function rewriteJavaScript(javascript, base = '') {
const importmap = {};
function rewriteJSImports(bab) {
@@ -169,7 +170,7 @@ function rewriteJavaScript(javascript) {
const { specifiers } = path.node;
// console.log(srcuri);
const h = ohash(srcuri.path);
- importmap[h] = `${path.node.source.value}`;
+ importmap[h] = `${path.node.source.value}`;
path.replaceWith(t.importDeclaration(specifiers, t.stringLiteral(h)));
}
}
@@ -194,7 +195,7 @@ function isJSON(type) {
return !!type.match(/json/);
}
-function getBody(type, responsebody, esi = false) {
+function getBody(type, responsebody, esi = false, entry) {
if (isBinary(type)) {
return Buffer.from(responsebody).toString('base64');
}
@@ -202,10 +203,10 @@ function getBody(type, responsebody, esi = false) {
return JSON.parse(responsebody);
}
if (esi && isCSS(type)) {
- return rewriteCSS(responsebody.toString());
+ return rewriteCSS(responsebody.toString(), entry);
}
if (esi && isJavaScript(type)) {
- return rewriteJavaScript(responsebody.toString());
+ return rewriteJavaScript(responsebody.toString(), entry);
}
return responsebody.toString();
}
@@ -229,6 +230,7 @@ function deliverPlain(owner, repo, ref, entry, root, esi = false) {
const cleanentry = (`${root}/${entry}`).replace(/^\//, '').replace(/[/]+/g, '/');
console.log('deliverPlain()', owner, repo, ref, cleanentry);
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${cleanentry}`;
+ console.log(url);
const rawopts = {
url,
headers: {
@@ -243,14 +245,14 @@ function deliverPlain(owner, repo, ref, entry, root, esi = false) {
const size = parseInt(response.headers['content-length'], 10);
console.log('size', size);
if (size < REDIRECT_LIMIT) {
- const body = await getBody(type, response.body, esi);
+ const body = await getBody(type, response.body, esi, entry);
console.log(`delivering file ${cleanentry} type ${type} binary: ${isBinary(type)}`);
return {
statusCode: 200,
headers: addHeaders({
'Content-Type': type,
'X-Static': 'Raw/Static',
- 'X-ESI': esi ? 'enabled' : undefined
+ 'X-ESI': esi ? 'enabled' : undefined,
}, ref, response.body),
body,
};
@@ -324,12 +326,14 @@ async function main({
}) {
console.log('main()', owner, repo, ref, path, entry, strain, plain, allow, deny, root);
- if (blacklisted(path, allow, deny) || blacklisted(entry, allow, deny)) {
+ const file = uri.normalize(entry);
+ console.log(file);
+ if (blacklisted(file, allow, deny) || blacklisted(file, allow, deny)) {
return forbidden();
}
if (plain) {
- return deliverPlain(owner, repo, ref, entry, root, esi);
+ return deliverPlain(owner, repo, ref, file, root, esi);
}
return forbidden();
diff --git a/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs-anywhere_508785622/recording.har b/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs-anywhere_508785622/recording.har
new file mode 100644
index 000000000..0564006e1
--- /dev/null
+++ b/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs-anywhere_508785622/recording.har
@@ -0,0 +1,161 @@
+{
+ "log": {
+ "_recordingName": "Static Delivery Action #unittest/main() normalizes URLs anywhere",
+ "creator": {
+ "comment": "persister:fs",
+ "name": "Polly.JS",
+ "version": "2.3.0"
+ },
+ "entries": [
+ {
+ "_id": "eb3115506cd378743495fae6ca116e5e",
+ "_order": 0,
+ "cache": {},
+ "request": {
+ "bodySize": 0,
+ "cookies": [],
+ "headers": [
+ {
+ "name": "user-agent",
+ "value": "Project Helix Static"
+ },
+ {
+ "name": "host",
+ "value": "raw.githubusercontent.com"
+ }
+ ],
+ "headersSize": 170,
+ "httpVersion": "HTTP/1.1",
+ "method": "GET",
+ "queryString": [],
+ "url": "https://raw.githubusercontent.com/adobe/helix-cli/master/demos/simple/htdocs/style.css"
+ },
+ "response": {
+ "bodySize": 711,
+ "content": {
+ "mimeType": "text/plain; charset=utf-8",
+ "size": 711,
+ "text": "/*\n * Copyright 2018 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nbody {\n padding: 8px;\n background-color: white;\n font-family: Arial, sans-serif;\n}"
+ },
+ "cookies": [],
+ "headers": [
+ {
+ "name": "content-security-policy",
+ "value": "default-src 'none'; style-src 'unsafe-inline'; sandbox"
+ },
+ {
+ "name": "strict-transport-security",
+ "value": "max-age=31536000"
+ },
+ {
+ "name": "x-content-type-options",
+ "value": "nosniff"
+ },
+ {
+ "name": "x-frame-options",
+ "value": "deny"
+ },
+ {
+ "name": "x-xss-protection",
+ "value": "1; mode=block"
+ },
+ {
+ "name": "etag",
+ "value": "\"ffb46f86cbf2385c8310afe1a1cc1fc2f81ddf0f\""
+ },
+ {
+ "name": "content-type",
+ "value": "text/plain; charset=utf-8"
+ },
+ {
+ "name": "cache-control",
+ "value": "max-age=300"
+ },
+ {
+ "name": "x-geo-block-list",
+ "value": ""
+ },
+ {
+ "name": "x-github-request-id",
+ "value": "92E4:692F:6D76F:81D1A:5CC2D895"
+ },
+ {
+ "name": "content-length",
+ "value": "711"
+ },
+ {
+ "name": "accept-ranges",
+ "value": "bytes"
+ },
+ {
+ "name": "date",
+ "value": "Fri, 26 Apr 2019 10:10:13 GMT"
+ },
+ {
+ "name": "via",
+ "value": "1.1 varnish"
+ },
+ {
+ "name": "connection",
+ "value": "close"
+ },
+ {
+ "name": "x-served-by",
+ "value": "cache-fra19123-FRA"
+ },
+ {
+ "name": "x-cache",
+ "value": "HIT"
+ },
+ {
+ "name": "x-cache-hits",
+ "value": "1"
+ },
+ {
+ "name": "x-timer",
+ "value": "S1556273413.375203,VS0,VE0"
+ },
+ {
+ "name": "vary",
+ "value": "Authorization,Accept-Encoding"
+ },
+ {
+ "name": "access-control-allow-origin",
+ "value": "*"
+ },
+ {
+ "name": "x-fastly-request-id",
+ "value": "9113b51dfbcbbdd78b5d8dd7c73df85b14f81b61"
+ },
+ {
+ "name": "expires",
+ "value": "Fri, 26 Apr 2019 10:15:13 GMT"
+ },
+ {
+ "name": "source-age",
+ "value": "111"
+ }
+ ],
+ "headersSize": 816,
+ "httpVersion": "HTTP/1.1",
+ "redirectURL": "",
+ "status": 200,
+ "statusText": "OK"
+ },
+ "startedDateTime": "2019-04-26T10:10:13.256Z",
+ "time": 156,
+ "timings": {
+ "blocked": -1,
+ "connect": -1,
+ "dns": -1,
+ "receive": 0,
+ "send": 0,
+ "ssl": -1,
+ "wait": 156
+ }
+ }
+ ],
+ "pages": [],
+ "version": "1.2"
+ }
+}
diff --git a/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs-in-rewritten-Javascript_420932251/recording.har b/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs-in-rewritten-Javascript_420932251/recording.har
new file mode 100644
index 000000000..04b5ee2c6
--- /dev/null
+++ b/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs-in-rewritten-Javascript_420932251/recording.har
@@ -0,0 +1,161 @@
+{
+ "log": {
+ "_recordingName": "Static Delivery Action #unittest/main() normalizes URLs in rewritten Javascript",
+ "creator": {
+ "comment": "persister:fs",
+ "name": "Polly.JS",
+ "version": "2.3.0"
+ },
+ "entries": [
+ {
+ "_id": "6cafaa07ff38b12748719008b3dcd2e3",
+ "_order": 0,
+ "cache": {},
+ "request": {
+ "bodySize": 0,
+ "cookies": [],
+ "headers": [
+ {
+ "name": "user-agent",
+ "value": "Project Helix Static"
+ },
+ {
+ "name": "host",
+ "value": "raw.githubusercontent.com"
+ }
+ ],
+ "headersSize": 160,
+ "httpVersion": "HTTP/1.1",
+ "method": "GET",
+ "queryString": [],
+ "url": "https://raw.githubusercontent.com/trieloff/helix-demo/master/htdocs/index.js"
+ },
+ "response": {
+ "bodySize": 620,
+ "content": {
+ "mimeType": "text/plain; charset=utf-8",
+ "size": 620,
+ "text": "import barba from './web_modules/@barba--core.js';\nimport prefetch from './web_modules/@barba--prefetch.js';\n\n// tells barba to use the prefetch module\nbarba.use(prefetch);\n\n// Basic default transition, with no rules and minimal hooks…\nbarba.init({\n transitions: [{\n leave({ current, next, trigger }) {\n // Do something with `current.container` for your leave transition\n // then return a promise or use `this.async()`\n },\n enter({ current, next, trigger }) {\n // Do something with `next.container` for your enter transition\n // then return a promise or use `this.async()`\n }\n }],\n});"
+ },
+ "cookies": [],
+ "headers": [
+ {
+ "name": "content-security-policy",
+ "value": "default-src 'none'; style-src 'unsafe-inline'; sandbox"
+ },
+ {
+ "name": "strict-transport-security",
+ "value": "max-age=31536000"
+ },
+ {
+ "name": "x-content-type-options",
+ "value": "nosniff"
+ },
+ {
+ "name": "x-frame-options",
+ "value": "deny"
+ },
+ {
+ "name": "x-xss-protection",
+ "value": "1; mode=block"
+ },
+ {
+ "name": "etag",
+ "value": "\"236d20fd3af45d02e50858af1fb752fe1c96cf58\""
+ },
+ {
+ "name": "content-type",
+ "value": "text/plain; charset=utf-8"
+ },
+ {
+ "name": "cache-control",
+ "value": "max-age=300"
+ },
+ {
+ "name": "x-geo-block-list",
+ "value": ""
+ },
+ {
+ "name": "x-github-request-id",
+ "value": "460C:256B:24883D:294586:5CC2D9E9"
+ },
+ {
+ "name": "content-length",
+ "value": "620"
+ },
+ {
+ "name": "accept-ranges",
+ "value": "bytes"
+ },
+ {
+ "name": "date",
+ "value": "Fri, 26 Apr 2019 10:14:01 GMT"
+ },
+ {
+ "name": "via",
+ "value": "1.1 varnish"
+ },
+ {
+ "name": "connection",
+ "value": "close"
+ },
+ {
+ "name": "x-served-by",
+ "value": "cache-hhn1538-HHN"
+ },
+ {
+ "name": "x-cache",
+ "value": "MISS"
+ },
+ {
+ "name": "x-cache-hits",
+ "value": "0"
+ },
+ {
+ "name": "x-timer",
+ "value": "S1556273642.766573,VS0,VE143"
+ },
+ {
+ "name": "vary",
+ "value": "Authorization,Accept-Encoding"
+ },
+ {
+ "name": "access-control-allow-origin",
+ "value": "*"
+ },
+ {
+ "name": "x-fastly-request-id",
+ "value": "65a3779a8589561d771bf8a86b03283dcc859580"
+ },
+ {
+ "name": "expires",
+ "value": "Fri, 26 Apr 2019 10:19:01 GMT"
+ },
+ {
+ "name": "source-age",
+ "value": "0"
+ }
+ ],
+ "headersSize": 818,
+ "httpVersion": "HTTP/1.1",
+ "redirectURL": "",
+ "status": 200,
+ "statusText": "OK"
+ },
+ "startedDateTime": "2019-04-26T10:14:01.386Z",
+ "time": 560,
+ "timings": {
+ "blocked": -1,
+ "connect": -1,
+ "dns": -1,
+ "receive": 0,
+ "send": 0,
+ "ssl": -1,
+ "wait": 560
+ }
+ }
+ ],
+ "pages": [],
+ "version": "1.2"
+ }
+}
diff --git a/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs_1747864213/recording.har b/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs_1747864213/recording.har
new file mode 100644
index 000000000..fc3956e81
--- /dev/null
+++ b/test/fixtures/recordings/Static-Delivery-Action-unittest_2612775662/main-normalizes-URLs_1747864213/recording.har
@@ -0,0 +1,161 @@
+{
+ "log": {
+ "_recordingName": "Static Delivery Action #unittest/main() normalizes URLs",
+ "creator": {
+ "comment": "persister:fs",
+ "name": "Polly.JS",
+ "version": "2.3.0"
+ },
+ "entries": [
+ {
+ "_id": "eb3115506cd378743495fae6ca116e5e",
+ "_order": 0,
+ "cache": {},
+ "request": {
+ "bodySize": 0,
+ "cookies": [],
+ "headers": [
+ {
+ "name": "user-agent",
+ "value": "Project Helix Static"
+ },
+ {
+ "name": "host",
+ "value": "raw.githubusercontent.com"
+ }
+ ],
+ "headersSize": 170,
+ "httpVersion": "HTTP/1.1",
+ "method": "GET",
+ "queryString": [],
+ "url": "https://raw.githubusercontent.com/adobe/helix-cli/master/demos/simple/htdocs/style.css"
+ },
+ "response": {
+ "bodySize": 711,
+ "content": {
+ "mimeType": "text/plain; charset=utf-8",
+ "size": 711,
+ "text": "/*\n * Copyright 2018 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nbody {\n padding: 8px;\n background-color: white;\n font-family: Arial, sans-serif;\n}"
+ },
+ "cookies": [],
+ "headers": [
+ {
+ "name": "content-security-policy",
+ "value": "default-src 'none'; style-src 'unsafe-inline'; sandbox"
+ },
+ {
+ "name": "strict-transport-security",
+ "value": "max-age=31536000"
+ },
+ {
+ "name": "x-content-type-options",
+ "value": "nosniff"
+ },
+ {
+ "name": "x-frame-options",
+ "value": "deny"
+ },
+ {
+ "name": "x-xss-protection",
+ "value": "1; mode=block"
+ },
+ {
+ "name": "etag",
+ "value": "\"ffb46f86cbf2385c8310afe1a1cc1fc2f81ddf0f\""
+ },
+ {
+ "name": "content-type",
+ "value": "text/plain; charset=utf-8"
+ },
+ {
+ "name": "cache-control",
+ "value": "max-age=300"
+ },
+ {
+ "name": "x-geo-block-list",
+ "value": ""
+ },
+ {
+ "name": "x-github-request-id",
+ "value": "92E4:692F:6D76F:81D1A:5CC2D895"
+ },
+ {
+ "name": "content-length",
+ "value": "711"
+ },
+ {
+ "name": "accept-ranges",
+ "value": "bytes"
+ },
+ {
+ "name": "date",
+ "value": "Fri, 26 Apr 2019 10:08:21 GMT"
+ },
+ {
+ "name": "via",
+ "value": "1.1 varnish"
+ },
+ {
+ "name": "connection",
+ "value": "close"
+ },
+ {
+ "name": "x-served-by",
+ "value": "cache-fra19146-FRA"
+ },
+ {
+ "name": "x-cache",
+ "value": "MISS"
+ },
+ {
+ "name": "x-cache-hits",
+ "value": "0"
+ },
+ {
+ "name": "x-timer",
+ "value": "S1556273302.767414,VS0,VE159"
+ },
+ {
+ "name": "vary",
+ "value": "Authorization,Accept-Encoding"
+ },
+ {
+ "name": "access-control-allow-origin",
+ "value": "*"
+ },
+ {
+ "name": "x-fastly-request-id",
+ "value": "57dbac3675ae149b46f44dfbb73ed01ae22562b1"
+ },
+ {
+ "name": "expires",
+ "value": "Fri, 26 Apr 2019 10:13:21 GMT"
+ },
+ {
+ "name": "source-age",
+ "value": "0"
+ }
+ ],
+ "headersSize": 817,
+ "httpVersion": "HTTP/1.1",
+ "redirectURL": "",
+ "status": 200,
+ "statusText": "OK"
+ },
+ "startedDateTime": "2019-04-26T10:08:21.694Z",
+ "time": 277,
+ "timings": {
+ "blocked": -1,
+ "connect": -1,
+ "dns": -1,
+ "receive": 0,
+ "send": 0,
+ "ssl": -1,
+ "wait": 277
+ }
+ }
+ ],
+ "pages": [],
+ "version": "1.2"
+ }
+}
diff --git a/test/testStatic.js b/test/testStatic.js
index 220279f4a..a9928ef78 100644
--- a/test/testStatic.js
+++ b/test/testStatic.js
@@ -166,7 +166,7 @@ describe('CSS and JS Rewriting', () => {
describe('Static Delivery Action #unittest', () => {
setupPolly({
recordFailedRequests: true,
- recordIfMissing: false,
+ recordIfMissing: true,
logging: false,
adapters: [NodeHttpAdapter],
persister: FSPersister,
@@ -249,6 +249,54 @@ describe('Static Delivery Action #unittest', () => {
assert.ok(res.body.indexOf('Arial') > 0, true);
});
+ it('main() normalizes URLs', async () => {
+ const res = await index.main({
+ owner: 'adobe',
+ repo: 'helix-cli',
+ entry: './demos/simple/htdocs/style.css',
+ plain: true,
+ });
+ assert.ok(res.body.indexOf('Arial') > 0, true);
+ });
+
+ it('main() normalizes URLs anywhere', async () => {
+ const res = await index.main({
+ owner: 'adobe',
+ repo: 'helix-cli',
+ entry: './demos/simple/test/../htdocs/style.css',
+ plain: true,
+ });
+ assert.ok(res.body.indexOf('Arial') > 0, true);
+ });
+
+ it('main() normalizes URLs in rewritten Javascript', async () => {
+ const res = await index.main({
+ owner: 'trieloff',
+ repo: 'helix-demo',
+ entry: '/index.js',
+ root: '/htdocs',
+ plain: true,
+ esi: true,
+ });
+ assert.equal(res.body, `import barba from "./web_modules/@barba--core.js";import
+prefetch from "./web_modules/@barba--prefetch.js";
+
+// tells barba to use the prefetch module
+barba.use(prefetch);
+
+// Basic default transition, with no rules and minimal hooks…
+barba.init({
+ transitions: [{
+ leave({ current, next, trigger }) {
+ // Do something with \`current.container\` for your leave transition
+ // then return a promise or use \`this.async()\`
+ },
+ enter({ current, next, trigger }) {
+ // Do something with \`next.container\` for your enter transition
+ // then return a promise or use \`this.async()\`
+ } }] });`);
+ });
+
it('main() returns 403 if plain is false', async () => {
const res = await index.main({
owner: 'adobe',