|
@@ -104,11 +104,11 @@ module.exports.getProtectedHttpAgent = (protocol, blockPrivateIPs) => {
|
|
* @param {boolean} blockLocalIPs
|
|
* @param {boolean} blockLocalIPs
|
|
* @returns {Promise<{type: string, size: number}>}
|
|
* @returns {Promise<{type: string, size: number}>}
|
|
*/
|
|
*/
|
|
-exports.getURLMeta = (url, blockLocalIPs = false) => {
|
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
|
|
|
+exports.getURLMeta = async (url, blockLocalIPs = false) => {
|
|
|
|
+ const requestWithMethod = async (method) => new Promise((resolve, reject) => {
|
|
const opts = {
|
|
const opts = {
|
|
uri: url,
|
|
uri: url,
|
|
- method: 'GET',
|
|
|
|
|
|
+ method,
|
|
followRedirect: exports.getRedirectEvaluator(url, blockLocalIPs),
|
|
followRedirect: exports.getRedirectEvaluator(url, blockLocalIPs),
|
|
agentClass: exports.getProtectedHttpAgent((new URL(url)).protocol, blockLocalIPs),
|
|
agentClass: exports.getProtectedHttpAgent((new URL(url)).protocol, blockLocalIPs),
|
|
}
|
|
}
|
|
@@ -117,20 +117,39 @@ exports.getURLMeta = (url, blockLocalIPs = false) => {
|
|
if (err) reject(err)
|
|
if (err) reject(err)
|
|
})
|
|
})
|
|
req.on('response', (response) => {
|
|
req.on('response', (response) => {
|
|
- if (response.statusCode >= 300) {
|
|
|
|
- // @todo possibly set a status code in the error object to get a more helpful
|
|
|
|
- // hint at what the cause of error is.
|
|
|
|
- reject(new Error(`URL server responded with status: ${response.statusCode}`))
|
|
|
|
- } else {
|
|
|
|
- req.abort() // No need to get the rest of the response, as we only want header
|
|
|
|
-
|
|
|
|
- // Can be undefined for unknown length URLs, e.g. transfer-encoding: chunked
|
|
|
|
- const contentLength = parseInt(response.headers['content-length'], 10)
|
|
|
|
- resolve({
|
|
|
|
- type: response.headers['content-type'],
|
|
|
|
- size: Number.isNaN(contentLength) ? null : contentLength,
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
|
|
+ // Can be undefined for unknown length URLs, e.g. transfer-encoding: chunked
|
|
|
|
+ const contentLength = parseInt(response.headers['content-length'], 10)
|
|
|
|
+
|
|
|
|
+ // No need to get the rest of the response, as we only want header (not really relevant for HEAD, but why not)
|
|
|
|
+ req.abort()
|
|
|
|
+
|
|
|
|
+ resolve({
|
|
|
|
+ type: response.headers['content-type'],
|
|
|
|
+ size: Number.isNaN(contentLength) ? null : contentLength,
|
|
|
|
+ statusCode: response.statusCode,
|
|
|
|
+ })
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
+
|
|
|
|
+ // We prefer to use a HEAD request, as it doesn't download the content. If the URL doesn't
|
|
|
|
+ // support HEAD, or doesn't follow the spec and provide the correct Content-Length, we
|
|
|
|
+ // fallback to GET.
|
|
|
|
+ let urlMeta = await requestWithMethod('HEAD')
|
|
|
|
+
|
|
|
|
+ // If HTTP error response, we retry with GET, which may work on non-compliant servers
|
|
|
|
+ // (e.g. HEAD doesn't work on signed S3 URLs)
|
|
|
|
+ // We look for status codes in the 400 and 500 ranges here, as 3xx errors are
|
|
|
|
+ // unlikely to have to do with our choice of method
|
|
|
|
+ if (urlMeta.statusCode >= 400 || urlMeta.size === 0 || urlMeta.size == null) {
|
|
|
|
+ urlMeta = await requestWithMethod('GET')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (urlMeta.statusCode >= 300) {
|
|
|
|
+ // @todo possibly set a status code in the error object to get a more helpful
|
|
|
|
+ // hint at what the cause of error is.
|
|
|
|
+ throw new Error(`URL server responded with status: ${urlMeta.statusCode}`)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const { size, type } = urlMeta
|
|
|
|
+ return { size, type }
|
|
}
|
|
}
|