You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
5.1 KiB
181 lines
5.1 KiB
3 years ago
|
"use strict";
|
||
|
|
||
|
const http = require("http");
|
||
|
const https = require("https");
|
||
|
const { ono } = require("@jsdevtools/ono");
|
||
|
const url = require("../util/url");
|
||
|
const { ResolverError } = require("../util/errors");
|
||
|
|
||
|
module.exports = {
|
||
|
/**
|
||
|
* The order that this resolver will run, in relation to other resolvers.
|
||
|
*
|
||
|
* @type {number}
|
||
|
*/
|
||
|
order: 200,
|
||
|
|
||
|
/**
|
||
|
* HTTP headers to send when downloading files.
|
||
|
*
|
||
|
* @example:
|
||
|
* {
|
||
|
* "User-Agent": "JSON Schema $Ref Parser",
|
||
|
* Accept: "application/json"
|
||
|
* }
|
||
|
*
|
||
|
* @type {object}
|
||
|
*/
|
||
|
headers: null,
|
||
|
|
||
|
/**
|
||
|
* HTTP request timeout (in milliseconds).
|
||
|
*
|
||
|
* @type {number}
|
||
|
*/
|
||
|
timeout: 5000, // 5 seconds
|
||
|
|
||
|
/**
|
||
|
* The maximum number of HTTP redirects to follow.
|
||
|
* To disable automatic following of redirects, set this to zero.
|
||
|
*
|
||
|
* @type {number}
|
||
|
*/
|
||
|
redirects: 5,
|
||
|
|
||
|
/**
|
||
|
* The `withCredentials` option of XMLHttpRequest.
|
||
|
* Set this to `true` if you're downloading files from a CORS-enabled server that requires authentication
|
||
|
*
|
||
|
* @type {boolean}
|
||
|
*/
|
||
|
withCredentials: false,
|
||
|
|
||
|
/**
|
||
|
* Determines whether this resolver can read a given file reference.
|
||
|
* Resolvers that return true will be tried in order, until one successfully resolves the file.
|
||
|
* Resolvers that return false will not be given a chance to resolve the file.
|
||
|
*
|
||
|
* @param {object} file - An object containing information about the referenced file
|
||
|
* @param {string} file.url - The full URL of the referenced file
|
||
|
* @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
canRead (file) {
|
||
|
return url.isHttp(file.url);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Reads the given URL and returns its raw contents as a Buffer.
|
||
|
*
|
||
|
* @param {object} file - An object containing information about the referenced file
|
||
|
* @param {string} file.url - The full URL of the referenced file
|
||
|
* @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
|
||
|
* @returns {Promise<Buffer>}
|
||
|
*/
|
||
|
read (file) {
|
||
|
let u = url.parse(file.url);
|
||
|
|
||
|
if (process.browser && !u.protocol) {
|
||
|
// Use the protocol of the current page
|
||
|
u.protocol = url.parse(location.href).protocol;
|
||
|
}
|
||
|
|
||
|
return download(u, this);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Downloads the given file.
|
||
|
*
|
||
|
* @param {Url|string} u - The url to download (can be a parsed {@link Url} object)
|
||
|
* @param {object} httpOptions - The `options.resolve.http` object
|
||
|
* @param {number} [redirects] - The redirect URLs that have already been followed
|
||
|
*
|
||
|
* @returns {Promise<Buffer>}
|
||
|
* The promise resolves with the raw downloaded data, or rejects if there is an HTTP error.
|
||
|
*/
|
||
|
function download (u, httpOptions, redirects) {
|
||
|
return new Promise(((resolve, reject) => {
|
||
|
u = url.parse(u);
|
||
|
redirects = redirects || [];
|
||
|
redirects.push(u.href);
|
||
|
|
||
|
get(u, httpOptions)
|
||
|
.then((res) => {
|
||
|
if (res.statusCode >= 400) {
|
||
|
throw ono({ status: res.statusCode }, `HTTP ERROR ${res.statusCode}`);
|
||
|
}
|
||
|
else if (res.statusCode >= 300) {
|
||
|
if (redirects.length > httpOptions.redirects) {
|
||
|
reject(new ResolverError(ono({ status: res.statusCode },
|
||
|
`Error downloading ${redirects[0]}. \nToo many redirects: \n ${redirects.join(" \n ")}`)));
|
||
|
}
|
||
|
else if (!res.headers.location) {
|
||
|
throw ono({ status: res.statusCode }, `HTTP ${res.statusCode} redirect with no location header`);
|
||
|
}
|
||
|
else {
|
||
|
// console.log('HTTP %d redirect %s -> %s', res.statusCode, u.href, res.headers.location);
|
||
|
let redirectTo = url.resolve(u, res.headers.location);
|
||
|
download(redirectTo, httpOptions, redirects).then(resolve, reject);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
resolve(res.body || Buffer.alloc(0));
|
||
|
}
|
||
|
})
|
||
|
.catch((err) => {
|
||
|
reject(new ResolverError(ono(err, `Error downloading ${u.href}`), u.href));
|
||
|
});
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sends an HTTP GET request.
|
||
|
*
|
||
|
* @param {Url} u - A parsed {@link Url} object
|
||
|
* @param {object} httpOptions - The `options.resolve.http` object
|
||
|
*
|
||
|
* @returns {Promise<Response>}
|
||
|
* The promise resolves with the HTTP Response object.
|
||
|
*/
|
||
|
function get (u, httpOptions) {
|
||
|
return new Promise(((resolve, reject) => {
|
||
|
// console.log('GET', u.href);
|
||
|
|
||
|
let protocol = u.protocol === "https:" ? https : http;
|
||
|
let req = protocol.get({
|
||
|
hostname: u.hostname,
|
||
|
port: u.port,
|
||
|
path: u.path,
|
||
|
auth: u.auth,
|
||
|
protocol: u.protocol,
|
||
|
headers: httpOptions.headers || {},
|
||
|
withCredentials: httpOptions.withCredentials
|
||
|
});
|
||
|
|
||
|
if (typeof req.setTimeout === "function") {
|
||
|
req.setTimeout(httpOptions.timeout);
|
||
|
}
|
||
|
|
||
|
req.on("timeout", () => {
|
||
|
req.abort();
|
||
|
});
|
||
|
|
||
|
req.on("error", reject);
|
||
|
|
||
|
req.once("response", (res) => {
|
||
|
res.body = Buffer.alloc(0);
|
||
|
|
||
|
res.on("data", (data) => {
|
||
|
res.body = Buffer.concat([res.body, Buffer.from(data)]);
|
||
|
});
|
||
|
|
||
|
res.on("error", reject);
|
||
|
|
||
|
res.on("end", () => {
|
||
|
resolve(res);
|
||
|
});
|
||
|
});
|
||
|
}));
|
||
|
}
|