/**
* @license
* jsPDF fileloading PlugIn
* Copyright (c) 2018 Aras Abbasi (aras.abbasi@gmail.com)
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
import { jsPDF } from "../jspdf.js";
/**
* @name fileloading
* @module
*/
(function(jsPDFAPI) {
/**
* @name loadFile
* @function
* @param {string} url
* @param {boolean} sync
* @param {function} callback
* @returns {string|undefined} result
*/
jsPDFAPI.loadFile = function(url, sync, callback) {
// @if MODULE_FORMAT!='cjs'
return browserRequest(url, sync, callback);
// @endif
// @if MODULE_FORMAT='cjs'
// eslint-disable-next-line no-unreachable
return nodeReadFile.call(this, url, sync, callback);
// @endif
};
/**
* Controls which local files may be read by jsPDF when running under Node.js.
*
* Security recommendation:
* - We strongly recommend using Node's permission flags (`node --permission --allow-fs-read=...`) instead of this property,
* especially in production. The Node flags are enforced by the runtime and provide stronger guarantees.
*
* Behavior:
* - When present, jsPDF will allow reading only if the requested, resolved absolute path matches any entry in this array.
* - Each entry can be either:
* - An absolute or relative file path for an exact match, or
* - A prefix ending with a single wildcard `*` to allow all paths starting with that prefix.
* - Examples of allowed patterns:
* - `"./fonts/MyFont.ttf"` (exact match by resolved path)
* - `"/abs/path/to/file.txt"` (exact absolute path)
* - `"./assets/*"` (any file whose resolved path starts with the resolved `./assets/` directory)
*
* Notes:
* - If Node's permission API is available (`process.permission`), it is checked first. If it denies access, reading will fail regardless of `allowFsRead`.
* - If neither `process.permission` nor `allowFsRead` is set, reading from the local file system is disabled and an error is thrown.
*
* Example:
* ```js
* const doc = jsPDF();
* doc.allowFsRead = ["./fonts/*", "./images/logo.png"]; // allow everything under ./fonts and a single file
* const ttf = doc.loadFile("./fonts/MyFont.ttf", true);
* ```
*
* @property {string[]|undefined}
* @name allowFsRead
*/
jsPDFAPI.allowFsRead = undefined;
/**
* @name loadImageFile
* @function
* @param {string} path
* @param {boolean} sync
* @param {function} callback
*/
jsPDFAPI.loadImageFile = jsPDFAPI.loadFile;
function browserRequest(url, sync, callback) {
sync = sync === false ? false : true;
callback = typeof callback === "function" ? callback : function() {};
var result = undefined;
var xhr = function(url, sync, callback) {
var request = new XMLHttpRequest();
var i = 0;
var sanitizeUnicode = function(data) {
var dataLength = data.length;
var charArray = [];
var StringFromCharCode = String.fromCharCode;
//Transform Unicode to ASCII
for (i = 0; i < dataLength; i += 1) {
charArray.push(StringFromCharCode(data.charCodeAt(i) & 0xff));
}
return charArray.join("");
};
request.open("GET", url, !sync);
// XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
request.overrideMimeType("text/plain; charset=x-user-defined");
if (sync === false) {
request.onload = function() {
if (request.status === 200) {
callback(sanitizeUnicode(this.responseText));
} else {
callback(undefined);
}
};
}
request.send(null);
if (sync && request.status === 200) {
return sanitizeUnicode(request.responseText);
}
};
try {
result = xhr(url, sync, callback);
// eslint-disable-next-line no-empty
} catch (e) {}
return result;
}
function nodeReadFile(url, sync, callback) {
sync = sync === false ? false : true;
var result = undefined;
var fs = require("fs");
var path = require("path");
if (!process.permission && !this.allowFsRead) {
throw new Error(
"Trying to read a file from local file system. To enable this feature either run node with the --permission and --allow-fs-read flags or set the jsPDF.allowFsRead property."
);
}
try {
url = fs.realpathSync(path.resolve(url));
} catch (e) {
if (sync) {
return undefined;
} else {
callback(undefined);
return;
}
}
if (process.permission && !process.permission.has("fs.read", url)) {
throw new Error(`Cannot read file '${url}'. Permission denied.`);
}
if (this.allowFsRead) {
const allowRead = this.allowFsRead.some(allowedUrl => {
const starIndex = allowedUrl.indexOf("*");
if (starIndex >= 0) {
const fixedPart = allowedUrl.substring(0, starIndex);
let resolved = path.resolve(fixedPart);
if (fixedPart.endsWith(path.sep) && !resolved.endsWith(path.sep)) {
resolved += path.sep;
}
return url.startsWith(resolved);
} else {
return url === path.resolve(allowedUrl);
}
});
if (!allowRead) {
throw new Error(`Cannot read file '${url}'. Permission denied.`);
}
}
if (sync) {
try {
result = fs.readFileSync(url, {
encoding: "latin1"
});
} catch (e) {
return undefined;
}
} else {
fs.readFile(url, { encoding: "latin1" }, function(err, data) {
if (!callback) {
return;
}
if (err) {
callback(undefined);
}
callback(data);
});
}
return result;
}
})(jsPDF.API);