Commit 2f2e9fd7 authored by Fred Chasen's avatar Fred Chasen
Browse files

Add request interception handler

parent 469393cb
Pipeline #457 passed with stages
in 1 minute and 39 seconds
......@@ -26,6 +26,9 @@ program
.option("-t, --timeout [ms]", "Set a max timeout of [ms]")
.option("-x, --html", "output html file")
.option("-b, --blockLocal", "Disallow access to filesystem for local files")
.option("-r, --blockRemote", "Disallow requests to remote servers")
.option("--allowedPath [allowedPaths]", "Only allow access to given filesystem paths, repeatable.", collect, [])
.option("--allowedDomain [allowedDomains]", "Only allow access to given remote domains, repeatable", collect, [])
.option("--outline-tags [tags]", "Specifies that an outline should be " +
"generated for the resulting PDF document. [tags] specifies which " +
"HTML tags should be considered for that outline. " +
......@@ -35,13 +38,13 @@ program
"added to the HTML document before rendering. This is useful for " +
"adding custom pagedjs handlers. The option can be repeated.",
collect, [])
.option("--browserEndpoint", "Use a remote Chrome server with browserWSEndpoint")
.parse(process.argv);
function collect(value, previous) {
return previous.concat(value);
}
let input = program.inputs || program.args[0];
let dir = process.cwd();
......@@ -49,7 +52,7 @@ let dir = process.cwd();
let relativePath;
let allowLocal;
try {
input = new URL(input);
let uri = new URL(input);
allowLocal = false;
} catch (error) {
relativePath = path.resolve(dir, input);
......@@ -104,7 +107,14 @@ if (typeof input === "string") {
}
(async () => {
let printer = new Printer(headless, allowLocal, program.additionalScript);
let printer = new Printer({
headless: headless,
allowLocal: allowLocal,
allowRemote: !program.blockRemote,
allowedPaths: program.allowedPaths,
allowedDomains: program.allowedDomains,
additionalScripts: program.additionalScript,
});
printer.on("page", (page) => {
if (page.position === 0) {
......
This diff is collapsed.
{
"name": "pagedjs-cli",
"version": "0.0.10",
"version": "0.1.0",
"author": "Fred Chasen",
"license": "MIT",
"homepage": "https://pagedmedia.org",
......@@ -16,20 +16,20 @@
},
"main": "index.js",
"dependencies": {
"commander": "^3.0.2",
"commander": "^5.0.0",
"express": "^4.17.1",
"hyphenopoly": "^3.2.1",
"hyphenopoly": "^4.2.1",
"katex": "^0.11.1",
"lodash": "^4.17.15",
"mathjax": "^3.0.0",
"mathjax": "^3.0.1",
"node-fetch": "^2.6.0",
"ora": "^4.0.2",
"pagedjs": "0.1.34",
"ora": "^4.0.3",
"pagedjs": "0.1.40",
"pdf-lib": "0.6.4",
"puppeteer": "^2.0.0",
"puppeteer": "^2.1.1",
"replace-ext": "^1.0.0"
},
"devDependencies": {
"eslint": "^6.5.1"
"eslint": "^6.8.0"
}
}
......@@ -14,20 +14,37 @@ let scriptPath = paths[0] + "node_modules" + paths[paths.length-1];
const PostProcesser = require("./postprocesser");
class Printer extends EventEmitter {
constructor(headless, allowLocal, additionalScripts) {
constructor(options = {}) {
super();
this.headless = headless !== false;
this.allowLocal = allowLocal;
this.headless = options.headless !== false;
this.allowLocal = options.allowLocal;
this.allowRemote = options.allowRemote;
this.additionalScripts = options.additionalScripts;
this.allowedPaths = options.allowedPaths || [];
this.allowedDomains = options.allowedDomains || [];
this.ignoreHTTPSErrors = options.ignoreHTTPSErrors;
this.browserWSEndpoint = options.browserEndpoint;
this.pages = [];
this.additionalScripts = additionalScripts;
}
async setup() {
const browser = await puppeteer.launch({
let puppeteerOptions = {
headless: this.headless,
args: this.allowLocal ? ["--allow-file-access-from-files", "--disable-dev-shm-usage"] : ["--disable-dev-shm-usage"],
ignoreHTTPSErrors: true
});
args: ["--disable-dev-shm-usage"],
ignoreHTTPSErrors: this.ignoreHTTPSErrors
}
if (this.allowLocal) {
puppeteerOptions.args.push("--allow-file-access-from-files");
}
if (this.browserWSEndpoint) {
puppeteerOptions.browserWSEndpoint = this.browserWSEndpoint;
}
const browser = await puppeteer.launch(puppeteerOptions);
this.browser = browser;
......@@ -46,17 +63,13 @@ class Printer extends EventEmitter {
const page = await this.browser.newPage();
let uri, url, html;
let uri, url, relativePath, html;
if (typeof input === "string") {
try {
uri = new URL(input);
if (uri.protocol === "https:") {
html = await fetch(input)
.then(res => res.text());
}
url = input;
} catch (error) {
let relativePath = path.resolve(dir, input);
relativePath = path.resolve(dir, input);
url = "file://" + relativePath;
}
} else {
......@@ -64,6 +77,36 @@ class Printer extends EventEmitter {
html = input.html;
}
await page.setRequestInterception(true);
page.on('request', (request) => {
let uri = new URL(request.url());
let { host, protocol, pathname } = uri;
let local = protocol === "file:"
if (local && this.withinAllowedPath(pathname) === false) {
request.abort();
return;
}
if (local && !this.allowLocal) {
request.abort();
return;
}
if (host && this.isAllowedDomain(host) === false) {
request.abort();
return;
}
if (host && !this.allowRemote) {
request.abort();
return;
}
request.continue();
});
if (html) {
await page.setContent(html)
.catch((e) => {
......@@ -299,6 +342,28 @@ class Printer extends EventEmitter {
return this.browser.close();
}
withinAllowedPath(pathname) {
if (!this.allowedPaths || this.allowedPaths.length === 0) {
return true;
}
for (let parent of this.allowedPaths) {
const relative = path.relative(parent, pathname);
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
return true;
}
}
return false;
}
isAllowedDomain(domain) {
if (!this.allowedDomains || this.allowedDomains.length === 0) {
return true;
}
return this.allowedDomains.includes(domain);
}
}
module.exports = Printer;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment