Skip to content

Commit

Permalink
fix(static): normalize URLs in rewritten CSS and JS
Browse files Browse the repository at this point in the history
  • Loading branch information
trieloff committed Apr 26, 2019
1 parent 8b2436b commit 25964ba
Show file tree
Hide file tree
Showing 5 changed files with 583 additions and 48 deletions.
98 changes: 51 additions & 47 deletions src/openwhisk/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,61 +97,62 @@ 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}<esi:include src="${importuri.path}.esi"/><esi:remove>${importuri.path}</esi:remove>${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}<esi:include src="${importuri.path}.esi"/><esi:remove>${importuri.path}</esi:remove>${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}<esi:include src="${importuri.path}.esi"/><esi:remove>${importuri.path}</esi:remove>${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}<esi:include src="${uri.resolve(base, importuri.path)}.esi"/><esi:remove>${importuri.path}</esi:remove>${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 `<esi:include src="${asset.relativePath}.esi"/><esi:remove>${asset.relativePath}</esi:remove>`;
return `<esi:include src="${uri.resolve(base, asset.relativePath)}.esi"/><esi:remove>${asset.relativePath}</esi:remove>`;
}
return asset.url;
},
}));
return processor.process(css, { from: undefined }).then(result => result.css);
}

function rewriteJavaScript(javascript) {
function rewriteJavaScript(javascript, base = '') {
const importmap = {};

function rewriteJSImports(bab) {
Expand All @@ -169,7 +170,7 @@ function rewriteJavaScript(javascript) {
const { specifiers } = path.node;
// console.log(srcuri);
const h = ohash(srcuri.path);
importmap[h] = `<esi:include src="${srcuri.path}.esi"/><esi:remove>${path.node.source.value}</esi:remove>`;
importmap[h] = `<esi:include src="${uri.resolve(base, srcuri.path)}.esi"/><esi:remove>${path.node.source.value}</esi:remove>`;
path.replaceWith(t.importDeclaration(specifiers, t.stringLiteral(h)));
}
}
Expand All @@ -194,18 +195,18 @@ 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');
}
if (isJSON(type)) {
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();
}
Expand All @@ -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: {
Expand All @@ -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,
};
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading

0 comments on commit 25964ba

Please sign in to comment.