























@@ -987,6 +987,15 @@ function responseHeader(response, name) {
987987return response.headers?.get?.(name) ?? null;
988988}
989989990+function createPackageDownloadTimeoutError(parsed, timeoutMs) {
991+return Object.assign(
992+new Error(`package_url download timed out after ${timeoutMs}ms: ${parsed.toString()}`),
993+{
994+code: "ETIMEDOUT",
995+},
996+);
997+}
998+990999async function closeResponseBody(body) {
9911000if (!body) {
9921001return;
@@ -1002,8 +1011,16 @@ async function closeResponseBody(body) {
1002101110031012async function openFetchPackageDownloadResponse(parsed, options) {
10041013const controller = new AbortController();
1005-const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
1006-timeout.unref?.();
1014+const timeoutError = createPackageDownloadTimeoutError(parsed, options.timeoutMs);
1015+let timeout;
1016+const timeoutPromise = new Promise((_, reject) => {
1017+timeout = setTimeout(() => {
1018+controller.abort(timeoutError);
1019+reject(timeoutError);
1020+}, options.timeoutMs);
1021+timeout.unref?.();
1022+});
1023+timeoutPromise.catch(() => {});
10071024const response = await options
10081025.fetchImpl(parsed, {
10091026headers: options.headers,
@@ -1013,27 +1030,31 @@ async function openFetchPackageDownloadResponse(parsed, options) {
10131030.catch((error) => {
10141031clearTimeout(timeout);
10151032if (error?.name === "AbortError") {
1016-throw new Error(
1017-`package_url download timed out after ${options.timeoutMs}ms: ${parsed.toString()}`,
1018-{
1019-cause: error,
1020-},
1021-);
1033+throw Object.assign(timeoutError, { cause: error });
10221034}
10231035throw error;
10241036});
10251037return {
10261038close: async () => closeResponseBody(response.body),
10271039 response,
10281040 timeout,
1041+ timeoutPromise,
10291042timeoutMs: options.timeoutMs,
10301043};
10311044}
1032104510331046async function openHttpsPackageDownloadResponse(parsed, options) {
10341047const controller = new AbortController();
1035-const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
1036-timeout.unref?.();
1048+const timeoutError = createPackageDownloadTimeoutError(parsed, options.timeoutMs);
1049+let timeout;
1050+const timeoutPromise = new Promise((_, reject) => {
1051+timeout = setTimeout(() => {
1052+controller.abort(timeoutError);
1053+reject(timeoutError);
1054+}, options.timeoutMs);
1055+timeout.unref?.();
1056+});
1057+timeoutPromise.catch(() => {});
10371058const lookup = createPinnedLookup(parsed.hostname, options.addresses);
10381059const response = await new Promise((resolve, reject) => {
10391060const request = httpsRequest(
@@ -1065,12 +1086,7 @@ async function openHttpsPackageDownloadResponse(parsed, options) {
10651086/** @param {unknown} error */ (error) => {
10661087clearTimeout(timeout);
10671088if (error?.name === "AbortError" || error?.code === "ABORT_ERR") {
1068-throw new Error(
1069-`package_url download timed out after ${options.timeoutMs}ms: ${parsed.toString()}`,
1070-{
1071-cause: error,
1072-},
1073-);
1089+throw Object.assign(timeoutError, { cause: error });
10741090}
10751091throw error;
10761092},
@@ -1079,6 +1095,7 @@ async function openHttpsPackageDownloadResponse(parsed, options) {
10791095close: async () => closeResponseBody(response.body),
10801096 response,
10811097 timeout,
1098+ timeoutPromise,
10821099timeoutMs: options.timeoutMs,
10831100};
10841101}
@@ -1124,7 +1141,46 @@ async function openPackageDownloadResponse(url, options) {
11241141throw new Error(`package_url exceeded ${maxRedirects} redirects: ${url}`);
11251142}
112611431127-async function* limitResponseBody(body, maxBytes) {
1144+async function* limitWebResponseBody(body, maxBytes, timeoutPromise) {
1145+let downloaded = 0;
1146+const reader = body.getReader();
1147+let timedOut = false;
1148+let timeoutFailure;
1149+const timeoutRead = timeoutPromise?.catch((error) => {
1150+timedOut = true;
1151+timeoutFailure = error;
1152+void reader.cancel().catch(() => {});
1153+throw error;
1154+});
1155+try {
1156+for (;;) {
1157+const next = reader.read();
1158+const { done, value } = timeoutRead ? await Promise.race([next, timeoutRead]) : await next;
1159+if (timedOut) {
1160+throw toLintErrorObject(timeoutFailure, "package_url download timed out");
1161+}
1162+if (done) {
1163+return;
1164+}
1165+const size = typeof value === "string" ? Buffer.byteLength(value) : value.byteLength;
1166+downloaded += size;
1167+if (downloaded > maxBytes) {
1168+throw new Error(`package_url exceeds maximum download size of ${maxBytes} bytes`);
1169+}
1170+yield value;
1171+}
1172+} finally {
1173+if (!timedOut) {
1174+reader.releaseLock();
1175+}
1176+}
1177+}
1178+1179+async function* limitResponseBody(body, maxBytes, timeoutPromise) {
1180+if (typeof body.getReader === "function") {
1181+yield* limitWebResponseBody(body, maxBytes, timeoutPromise);
1182+return;
1183+}
11281184let downloaded = 0;
11291185for await (const chunk of body) {
11301186const size = typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
@@ -1138,7 +1194,10 @@ async function* limitResponseBody(body, maxBytes) {
1138119411391195export async function downloadUrl(url, target, options = {}) {
11401196const maxBytes = options.maxBytes ?? PACKAGE_URL_MAX_BYTES;
1141-const { close, response, timeout, timeoutMs } = await openPackageDownloadResponse(url, options);
1197+const { close, response, timeout, timeoutMs, timeoutPromise } = await openPackageDownloadResponse(
1198+url,
1199+options,
1200+);
11421201const tempTarget = `${target}.tmp`;
11431202try {
11441203if (!responseOk(response) || !response.body) {
@@ -1151,9 +1210,15 @@ export async function downloadUrl(url, target, options = {}) {
11511210throw new Error(`package_url exceeds maximum download size of ${maxBytes} bytes`);
11521211}
11531212await fs.rm(tempTarget, { force: true });
1154-await pipeline(limitResponseBody(response.body, maxBytes), createWriteStream(tempTarget));
1213+await pipeline(
1214+limitResponseBody(response.body, maxBytes, timeoutPromise),
1215+createWriteStream(tempTarget),
1216+);
11551217await fs.rename(tempTarget, target);
11561218} catch (error) {
1219+if (error?.code === "ETIMEDOUT") {
1220+throw error;
1221+}
11571222if (error?.name === "AbortError") {
11581223throw new Error(`package_url download timed out after ${timeoutMs}ms: ${url}`, {
11591224cause: error,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。