"use strict"; const fs = require("fs"); const { parseURL } = require("whatwg-url"); const dataURLFromRecord = require("data-urls").fromURLRecord; const request = require("request-promise-native"); const wrapCookieJarForRequest = require("../../living/helpers/wrap-cookie-jar-for-request"); const packageVersion = require("../../../../package.json").version; const IS_BROWSER = Object.prototype.toString.call(process) !== "[object process]"; module.exports = class ResourceLoader { constructor({ strictSSL = true, proxy = undefined, userAgent = `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 ` + `(KHTML, like Gecko) jsdom/${packageVersion}` } = {}) { this._strictSSL = strictSSL; this._proxy = proxy; this._userAgent = userAgent; } _readFile(filePath) { let readableStream; let abort; // Native Promises doesn't have an "abort" method. /* * Creating a promise for two reason: * 1. fetch always return a promise. * 2. We need to add an abort handler. */ const promise = new Promise((resolve, reject) => { readableStream = fs.createReadStream(filePath); let data = Buffer.alloc(0); abort = reject; readableStream.on("error", reject); readableStream.on("data", chunk => { data = Buffer.concat([data, chunk]); }); readableStream.on("end", () => { resolve(data); }); }); promise.abort = () => { readableStream.destroy(); const error = new Error("request canceled by user"); error.isAbortError = true; abort(error); }; return promise; } _getRequestOptions({ cookieJar, referrer, accept = "*/*" }) { const requestOptions = { encoding: null, gzip: true, jar: wrapCookieJarForRequest(cookieJar), strictSSL: this._strictSSL, proxy: this._proxy, forever: true, headers: { "User-Agent": this._userAgent, "Accept-Language": "en", Accept: accept } }; if (referrer && !IS_BROWSER) { requestOptions.headers.referer = referrer; } return requestOptions; } fetch(urlString, options = {}) { const url = parseURL(urlString); if (!url) { return Promise.reject(new Error(`Tried to fetch invalid URL ${urlString}`)); } switch (url.scheme) { case "data": { return Promise.resolve(dataURLFromRecord(url).body); } case "http": case "https": { const requestOptions = this._getRequestOptions(options); return request(urlString, requestOptions); } case "file": { // TODO: Improve the URL => file algorithm. See https://github.com/jsdom/jsdom/pull/2279#discussion_r199977987 const filePath = urlString .replace(/^file:\/\//, "") .replace(/^\/([a-z]):\//i, "$1:/") .replace(/%20/g, " "); return this._readFile(filePath); } default: { return Promise.reject(new Error(`Tried to fetch URL ${urlString} with invalid scheme ${url.scheme}`)); } } } };