Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 246x 49x 46x 46x 21x 21x 25x 25x 10x 246x 246x 246x 246x 246x 246x 25x 25x 25x 25x 8x 17x 4x 4x 4x 4x 4x 4x 4x 13x 1x 1x 12x 13x 13x 13x 13x 10x | /**
* Retry-After in seconds, relative to receipt of the response, or null when invalid.
* RFC 9110: https://www.rfc-editor.org/rfc/rfc9110.html#section-10.2.3
* and section 5.6.7 require delay-seconds and all three HTTP-date formats.
*/
export function parseRetryAfterSeconds(value: string | null, receivedAt: number): number | null {
if (value === null) return null;
const text = value.trim();
if (/^\d+$/.test(text)) {
const seconds = Number(text);
return Number.isSafeInteger(seconds) ? seconds : null;
}
const timestamp = parseHttpDate(text, receivedAt);
if (timestamp === null) return null;
return Math.max(0, (timestamp - receivedAt) / 1000);
}
const DAY = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)';
const MONTH = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)';
const TIME = String.raw`(\d{2}:\d{2}:\d{2})`;
const IMF_DATE = new RegExp(String.raw`^${DAY}, (\d{2}) ${MONTH} (\d{4}) ${TIME} GMT$`);
const RFC850_DATE = new RegExp(
String.raw`^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d{2})-${MONTH}-(\d{2}) ${TIME} GMT$`,
);
const ASCTIME_DATE = new RegExp(String.raw`^${DAY} ${MONTH} ( \d|\d{2}) ${TIME} (\d{4})$`);
function parseHttpDate(text: string, receivedAt: number): number | null {
let canonical: string;
const imf = IMF_DATE.exec(text);
const rfc850 = RFC850_DATE.exec(text);
const asctime = ASCTIME_DATE.exec(text);
if (imf) {
canonical = text;
} else if (rfc850) {
const [, weekday, day, month, shortYear, time] = rfc850;
const currentYear = new Date(receivedAt).getUTCFullYear();
let year = Math.floor((currentYear + 50) / 100) * 100 + Number(shortYear);
const threshold = new Date(receivedAt);
threshold.setUTCFullYear(currentYear + 50);
// RFC 9110 section 5.6.7 requires a rolling 50-year interpretation, not Date.parse's pivot.
if (Date.parse(`${day} ${month} ${year} ${time} GMT`) > threshold.getTime()) year -= 100;
canonical = `${weekday.slice(0, 3)}, ${day} ${month} ${year} ${time} GMT`;
} else if (asctime) {
const [, weekday, month, day, time, year] = asctime;
canonical = `${weekday}, ${day.trim().padStart(2, '0')} ${month} ${year} ${time} GMT`;
} else {
return null;
}
// Date.parse normalizes impossible calendar dates; round-trip before accepting them.
// HTTP-date also permits a leap second, represented by the following instant.
const leapSecond = canonical.endsWith(':60 GMT');
const normalized = leapSecond ? `${canonical.slice(0, -7)}:59 GMT` : canonical;
const timestamp = Date.parse(normalized);
if (!Number.isFinite(timestamp) || new Date(timestamp).toUTCString() !== normalized) return null;
return timestamp + (leapSecond ? 1000 : 0);
}
|