| 40 | * @returns {Promise<string>} fetched body |
| 41 | */ |
| 42 | const fetchUrl = (url) => |
| 43 | new Promise((resolve, reject) => { |
| 44 | https |
| 45 | .get( |
| 46 | url, |
| 47 | { headers: { "user-agent": "webpack/generate-html-entities" } }, |
| 48 | (res) => { |
| 49 | if ( |
| 50 | res.statusCode === 301 || |
| 51 | res.statusCode === 302 || |
| 52 | res.statusCode === 307 || |
| 53 | res.statusCode === 308 |
| 54 | ) { |
| 55 | // Drain the response body so the socket is released |
| 56 | // before we open the follow-up request. |
| 57 | res.resume(); |
| 58 | const location = res.headers.location; |
| 59 | if (!location) { |
| 60 | return reject( |
| 61 | new Error( |
| 62 | `Redirect from ${url} with no Location header (HTTP ${res.statusCode})` |
| 63 | ) |
| 64 | ); |
| 65 | } |
| 66 | // `Location` may be relative; resolve against the current |
| 67 | // request URL so `https.get` receives an absolute URL. |
| 68 | return fetchUrl(new URL(location, url).toString()).then( |
| 69 | resolve, |
| 70 | reject |
| 71 | ); |
| 72 | } |
| 73 | if (res.statusCode !== 200) { |
| 74 | // Drain so the response body doesn't hold the socket open. |
| 75 | res.resume(); |
| 76 | return reject( |
| 77 | new Error(`Failed to fetch ${url}: HTTP ${res.statusCode}`) |
| 78 | ); |
| 79 | } |
| 80 | /** @type {Buffer[]} */ |
| 81 | const chunks = []; |
| 82 | res.on("data", (chunk) => chunks.push(chunk)); |
| 83 | res.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); |
| 84 | res.on("error", reject); |
| 85 | } |
| 86 | ) |
| 87 | .on("error", reject); |
| 88 | }); |
| 89 | |
| 90 | /** |
| 91 | * @param {Record<string, { characters: string }>} entities raw WHATWG table |