undici 7.15.0 → 7.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +48 -2
  2. package/docs/docs/api/Agent.md +1 -0
  3. package/docs/docs/api/Client.md +1 -0
  4. package/docs/docs/api/DiagnosticsChannel.md +57 -0
  5. package/docs/docs/api/Dispatcher.md +86 -0
  6. package/docs/docs/api/Errors.md +0 -1
  7. package/docs/docs/api/RoundRobinPool.md +145 -0
  8. package/docs/docs/api/WebSocket.md +21 -0
  9. package/docs/docs/best-practices/crawling.md +58 -0
  10. package/index-fetch.js +2 -2
  11. package/index.js +8 -9
  12. package/lib/api/api-request.js +22 -8
  13. package/lib/api/api-upgrade.js +2 -1
  14. package/lib/api/readable.js +7 -5
  15. package/lib/core/connect.js +4 -1
  16. package/lib/core/diagnostics.js +28 -1
  17. package/lib/core/errors.js +217 -13
  18. package/lib/core/request.js +5 -1
  19. package/lib/core/symbols.js +3 -0
  20. package/lib/core/util.js +61 -41
  21. package/lib/dispatcher/agent.js +19 -7
  22. package/lib/dispatcher/balanced-pool.js +10 -0
  23. package/lib/dispatcher/client-h1.js +18 -23
  24. package/lib/dispatcher/client-h2.js +166 -26
  25. package/lib/dispatcher/client.js +64 -59
  26. package/lib/dispatcher/dispatcher-base.js +20 -16
  27. package/lib/dispatcher/env-http-proxy-agent.js +12 -16
  28. package/lib/dispatcher/fixed-queue.js +15 -39
  29. package/lib/dispatcher/h2c-client.js +7 -78
  30. package/lib/dispatcher/pool-base.js +60 -43
  31. package/lib/dispatcher/pool.js +2 -2
  32. package/lib/dispatcher/proxy-agent.js +27 -11
  33. package/lib/dispatcher/round-robin-pool.js +137 -0
  34. package/lib/encoding/index.js +33 -0
  35. package/lib/global.js +19 -1
  36. package/lib/handler/cache-handler.js +84 -27
  37. package/lib/handler/deduplication-handler.js +216 -0
  38. package/lib/handler/retry-handler.js +0 -2
  39. package/lib/interceptor/cache.js +94 -15
  40. package/lib/interceptor/decompress.js +2 -1
  41. package/lib/interceptor/deduplicate.js +109 -0
  42. package/lib/interceptor/dns.js +55 -13
  43. package/lib/mock/mock-agent.js +4 -4
  44. package/lib/mock/mock-errors.js +10 -0
  45. package/lib/mock/mock-utils.js +13 -12
  46. package/lib/mock/snapshot-agent.js +11 -5
  47. package/lib/mock/snapshot-recorder.js +12 -4
  48. package/lib/mock/snapshot-utils.js +4 -4
  49. package/lib/util/cache.js +29 -1
  50. package/lib/util/date.js +534 -140
  51. package/lib/util/runtime-features.js +124 -0
  52. package/lib/web/cookies/index.js +1 -1
  53. package/lib/web/cookies/parse.js +1 -1
  54. package/lib/web/eventsource/eventsource-stream.js +2 -2
  55. package/lib/web/eventsource/eventsource.js +34 -29
  56. package/lib/web/eventsource/util.js +1 -9
  57. package/lib/web/fetch/body.js +45 -61
  58. package/lib/web/fetch/data-url.js +12 -160
  59. package/lib/web/fetch/formdata-parser.js +204 -127
  60. package/lib/web/fetch/index.js +21 -19
  61. package/lib/web/fetch/request.js +6 -0
  62. package/lib/web/fetch/response.js +4 -7
  63. package/lib/web/fetch/util.js +10 -79
  64. package/lib/web/infra/index.js +229 -0
  65. package/lib/web/subresource-integrity/subresource-integrity.js +6 -5
  66. package/lib/web/webidl/index.js +207 -44
  67. package/lib/web/websocket/connection.js +33 -22
  68. package/lib/web/websocket/events.js +1 -1
  69. package/lib/web/websocket/frame.js +9 -15
  70. package/lib/web/websocket/stream/websocketerror.js +22 -1
  71. package/lib/web/websocket/stream/websocketstream.js +17 -8
  72. package/lib/web/websocket/util.js +2 -1
  73. package/lib/web/websocket/websocket.js +32 -42
  74. package/package.json +9 -7
  75. package/types/agent.d.ts +2 -1
  76. package/types/api.d.ts +2 -2
  77. package/types/balanced-pool.d.ts +2 -1
  78. package/types/cache-interceptor.d.ts +1 -0
  79. package/types/client.d.ts +1 -1
  80. package/types/connector.d.ts +2 -2
  81. package/types/diagnostics-channel.d.ts +2 -2
  82. package/types/dispatcher.d.ts +12 -12
  83. package/types/errors.d.ts +5 -15
  84. package/types/fetch.d.ts +4 -4
  85. package/types/formdata.d.ts +1 -1
  86. package/types/h2c-client.d.ts +1 -1
  87. package/types/index.d.ts +9 -1
  88. package/types/interceptors.d.ts +36 -2
  89. package/types/pool.d.ts +1 -1
  90. package/types/readable.d.ts +2 -2
  91. package/types/round-robin-pool.d.ts +41 -0
  92. package/types/webidl.d.ts +82 -21
  93. package/types/websocket.d.ts +9 -9
package/lib/util/date.js CHANGED
@@ -1,32 +1,20 @@
1
1
  'use strict'
2
2
 
3
- const IMF_DAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
4
- const IMF_SPACES = [4, 7, 11, 16, 25]
5
- const IMF_MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
6
- const IMF_COLONS = [19, 22]
7
-
8
- const ASCTIME_SPACES = [3, 7, 10, 19]
9
-
10
- const RFC850_DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
11
-
12
3
  /**
13
4
  * @see https://www.rfc-editor.org/rfc/rfc9110.html#name-date-time-formats
14
5
  *
15
6
  * @param {string} date
16
- * @param {Date} [now]
17
7
  * @returns {Date | undefined}
18
8
  */
19
- function parseHttpDate (date, now) {
9
+ function parseHttpDate (date) {
20
10
  // Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
21
11
  // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
22
12
  // Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
23
13
 
24
- date = date.toLowerCase()
25
-
26
14
  switch (date[3]) {
27
15
  case ',': return parseImfDate(date)
28
16
  case ' ': return parseAscTimeDate(date)
29
- default: return parseRfc850Date(date, now)
17
+ default: return parseRfc850Date(date)
30
18
  }
31
19
  }
32
20
 
@@ -37,69 +25,207 @@ function parseHttpDate (date, now) {
37
25
  * @returns {Date | undefined}
38
26
  */
39
27
  function parseImfDate (date) {
40
- if (date.length !== 29) {
28
+ if (
29
+ date.length !== 29 ||
30
+ date[4] !== ' ' ||
31
+ date[7] !== ' ' ||
32
+ date[11] !== ' ' ||
33
+ date[16] !== ' ' ||
34
+ date[19] !== ':' ||
35
+ date[22] !== ':' ||
36
+ date[25] !== ' ' ||
37
+ date[26] !== 'G' ||
38
+ date[27] !== 'M' ||
39
+ date[28] !== 'T'
40
+ ) {
41
41
  return undefined
42
42
  }
43
43
 
44
- if (!date.endsWith('gmt')) {
45
- // Unsupported timezone
46
- return undefined
44
+ let weekday = -1
45
+ if (date[0] === 'S' && date[1] === 'u' && date[2] === 'n') { // Sunday
46
+ weekday = 0
47
+ } else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n') { // Monday
48
+ weekday = 1
49
+ } else if (date[0] === 'T' && date[1] === 'u' && date[2] === 'e') { // Tuesday
50
+ weekday = 2
51
+ } else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd') { // Wednesday
52
+ weekday = 3
53
+ } else if (date[0] === 'T' && date[1] === 'h' && date[2] === 'u') { // Thursday
54
+ weekday = 4
55
+ } else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i') { // Friday
56
+ weekday = 5
57
+ } else if (date[0] === 'S' && date[1] === 'a' && date[2] === 't') { // Saturday
58
+ weekday = 6
59
+ } else {
60
+ return undefined // Not a valid day of the week
47
61
  }
48
62
 
49
- for (const spaceInx of IMF_SPACES) {
50
- if (date[spaceInx] !== ' ') {
51
- return undefined
63
+ let day = 0
64
+ if (date[5] === '0') {
65
+ // Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
66
+ const code = date.charCodeAt(6)
67
+ if (code < 49 || code > 57) {
68
+ return undefined // Not a digit
52
69
  }
53
- }
54
-
55
- for (const colonIdx of IMF_COLONS) {
56
- if (date[colonIdx] !== ':') {
57
- return undefined
70
+ day = code - 48 // Convert ASCII code to number
71
+ } else {
72
+ const code1 = date.charCodeAt(5)
73
+ if (code1 < 49 || code1 > 51) {
74
+ return undefined // Not a digit between 1 and 3
75
+ }
76
+ const code2 = date.charCodeAt(6)
77
+ if (code2 < 48 || code2 > 57) {
78
+ return undefined // Not a digit
58
79
  }
80
+ day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
59
81
  }
60
82
 
61
- const dayName = date.substring(0, 3)
62
- if (!IMF_DAYS.includes(dayName)) {
83
+ let monthIdx = -1
84
+ if (
85
+ (date[8] === 'J' && date[9] === 'a' && date[10] === 'n')
86
+ ) {
87
+ monthIdx = 0 // Jan
88
+ } else if (
89
+ (date[8] === 'F' && date[9] === 'e' && date[10] === 'b')
90
+ ) {
91
+ monthIdx = 1 // Feb
92
+ } else if (
93
+ (date[8] === 'M' && date[9] === 'a')
94
+ ) {
95
+ if (date[10] === 'r') {
96
+ monthIdx = 2 // Mar
97
+ } else if (date[10] === 'y') {
98
+ monthIdx = 4 // May
99
+ } else {
100
+ return undefined // Invalid month
101
+ }
102
+ } else if (
103
+ (date[8] === 'J')
104
+ ) {
105
+ if (date[9] === 'a' && date[10] === 'n') {
106
+ monthIdx = 0 // Jan
107
+ } else if (date[9] === 'u') {
108
+ if (date[10] === 'n') {
109
+ monthIdx = 5 // Jun
110
+ } else if (date[10] === 'l') {
111
+ monthIdx = 6 // Jul
112
+ } else {
113
+ return undefined // Invalid month
114
+ }
115
+ } else {
116
+ return undefined // Invalid month
117
+ }
118
+ } else if (
119
+ (date[8] === 'A')
120
+ ) {
121
+ if (date[9] === 'p' && date[10] === 'r') {
122
+ monthIdx = 3 // Apr
123
+ } else if (date[9] === 'u' && date[10] === 'g') {
124
+ monthIdx = 7 // Aug
125
+ } else {
126
+ return undefined // Invalid month
127
+ }
128
+ } else if (
129
+ (date[8] === 'S' && date[9] === 'e' && date[10] === 'p')
130
+ ) {
131
+ monthIdx = 8 // Sep
132
+ } else if (
133
+ (date[8] === 'O' && date[9] === 'c' && date[10] === 't')
134
+ ) {
135
+ monthIdx = 9 // Oct
136
+ } else if (
137
+ (date[8] === 'N' && date[9] === 'o' && date[10] === 'v')
138
+ ) {
139
+ monthIdx = 10 // Nov
140
+ } else if (
141
+ (date[8] === 'D' && date[9] === 'e' && date[10] === 'c')
142
+ ) {
143
+ monthIdx = 11 // Dec
144
+ } else {
145
+ // Not a valid month
63
146
  return undefined
64
147
  }
65
148
 
66
- const dayString = date.substring(5, 7)
67
- const day = Number.parseInt(dayString)
68
- if (isNaN(day) || (day < 10 && dayString[0] !== '0')) {
69
- // Not a number, 0, or it's less than 10 and didn't start with a 0
70
- return undefined
149
+ const yearDigit1 = date.charCodeAt(12)
150
+ if (yearDigit1 < 48 || yearDigit1 > 57) {
151
+ return undefined // Not a digit
71
152
  }
72
-
73
- const month = date.substring(8, 11)
74
- const monthIdx = IMF_MONTHS.indexOf(month)
75
- if (monthIdx === -1) {
76
- return undefined
153
+ const yearDigit2 = date.charCodeAt(13)
154
+ if (yearDigit2 < 48 || yearDigit2 > 57) {
155
+ return undefined // Not a digit
77
156
  }
78
-
79
- const year = Number.parseInt(date.substring(12, 16))
80
- if (isNaN(year)) {
81
- return undefined
157
+ const yearDigit3 = date.charCodeAt(14)
158
+ if (yearDigit3 < 48 || yearDigit3 > 57) {
159
+ return undefined // Not a digit
160
+ }
161
+ const yearDigit4 = date.charCodeAt(15)
162
+ if (yearDigit4 < 48 || yearDigit4 > 57) {
163
+ return undefined // Not a digit
82
164
  }
165
+ const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48)
83
166
 
84
- const hourString = date.substring(17, 19)
85
- const hour = Number.parseInt(hourString)
86
- if (isNaN(hour) || (hour < 10 && hourString[0] !== '0')) {
87
- return undefined
167
+ let hour = 0
168
+ if (date[17] === '0') {
169
+ const code = date.charCodeAt(18)
170
+ if (code < 48 || code > 57) {
171
+ return undefined // Not a digit
172
+ }
173
+ hour = code - 48 // Convert ASCII code to number
174
+ } else {
175
+ const code1 = date.charCodeAt(17)
176
+ if (code1 < 48 || code1 > 50) {
177
+ return undefined // Not a digit between 0 and 2
178
+ }
179
+ const code2 = date.charCodeAt(18)
180
+ if (code2 < 48 || code2 > 57) {
181
+ return undefined // Not a digit
182
+ }
183
+ if (code1 === 50 && code2 > 51) {
184
+ return undefined // Hour cannot be greater than 23
185
+ }
186
+ hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
88
187
  }
89
188
 
90
- const minuteString = date.substring(20, 22)
91
- const minute = Number.parseInt(minuteString)
92
- if (isNaN(minute) || (minute < 10 && minuteString[0] !== '0')) {
93
- return undefined
189
+ let minute = 0
190
+ if (date[20] === '0') {
191
+ const code = date.charCodeAt(21)
192
+ if (code < 48 || code > 57) {
193
+ return undefined // Not a digit
194
+ }
195
+ minute = code - 48 // Convert ASCII code to number
196
+ } else {
197
+ const code1 = date.charCodeAt(20)
198
+ if (code1 < 48 || code1 > 53) {
199
+ return undefined // Not a digit between 0 and 5
200
+ }
201
+ const code2 = date.charCodeAt(21)
202
+ if (code2 < 48 || code2 > 57) {
203
+ return undefined // Not a digit
204
+ }
205
+ minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
94
206
  }
95
207
 
96
- const secondString = date.substring(23, 25)
97
- const second = Number.parseInt(secondString)
98
- if (isNaN(second) || (second < 10 && secondString[0] !== '0')) {
99
- return undefined
208
+ let second = 0
209
+ if (date[23] === '0') {
210
+ const code = date.charCodeAt(24)
211
+ if (code < 48 || code > 57) {
212
+ return undefined // Not a digit
213
+ }
214
+ second = code - 48 // Convert ASCII code to number
215
+ } else {
216
+ const code1 = date.charCodeAt(23)
217
+ if (code1 < 48 || code1 > 53) {
218
+ return undefined // Not a digit between 0 and 5
219
+ }
220
+ const code2 = date.charCodeAt(24)
221
+ if (code2 < 48 || code2 > 57) {
222
+ return undefined // Not a digit
223
+ }
224
+ second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
100
225
  }
101
226
 
102
- return new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
227
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
228
+ return result.getUTCDay() === weekday ? result : undefined
103
229
  }
104
230
 
105
231
  /**
@@ -111,147 +237,415 @@ function parseImfDate (date) {
111
237
  function parseAscTimeDate (date) {
112
238
  // This is assumed to be in UTC
113
239
 
114
- if (date.length !== 24) {
240
+ if (
241
+ date.length !== 24 ||
242
+ date[7] !== ' ' ||
243
+ date[10] !== ' ' ||
244
+ date[19] !== ' '
245
+ ) {
115
246
  return undefined
116
247
  }
117
248
 
118
- for (const spaceIdx of ASCTIME_SPACES) {
119
- if (date[spaceIdx] !== ' ') {
120
- return undefined
121
- }
249
+ let weekday = -1
250
+ if (date[0] === 'S' && date[1] === 'u' && date[2] === 'n') { // Sunday
251
+ weekday = 0
252
+ } else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n') { // Monday
253
+ weekday = 1
254
+ } else if (date[0] === 'T' && date[1] === 'u' && date[2] === 'e') { // Tuesday
255
+ weekday = 2
256
+ } else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd') { // Wednesday
257
+ weekday = 3
258
+ } else if (date[0] === 'T' && date[1] === 'h' && date[2] === 'u') { // Thursday
259
+ weekday = 4
260
+ } else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i') { // Friday
261
+ weekday = 5
262
+ } else if (date[0] === 'S' && date[1] === 'a' && date[2] === 't') { // Saturday
263
+ weekday = 6
264
+ } else {
265
+ return undefined // Not a valid day of the week
122
266
  }
123
267
 
124
- const dayName = date.substring(0, 3)
125
- if (!IMF_DAYS.includes(dayName)) {
268
+ let monthIdx = -1
269
+ if (
270
+ (date[4] === 'J' && date[5] === 'a' && date[6] === 'n')
271
+ ) {
272
+ monthIdx = 0 // Jan
273
+ } else if (
274
+ (date[4] === 'F' && date[5] === 'e' && date[6] === 'b')
275
+ ) {
276
+ monthIdx = 1 // Feb
277
+ } else if (
278
+ (date[4] === 'M' && date[5] === 'a')
279
+ ) {
280
+ if (date[6] === 'r') {
281
+ monthIdx = 2 // Mar
282
+ } else if (date[6] === 'y') {
283
+ monthIdx = 4 // May
284
+ } else {
285
+ return undefined // Invalid month
286
+ }
287
+ } else if (
288
+ (date[4] === 'J')
289
+ ) {
290
+ if (date[5] === 'a' && date[6] === 'n') {
291
+ monthIdx = 0 // Jan
292
+ } else if (date[5] === 'u') {
293
+ if (date[6] === 'n') {
294
+ monthIdx = 5 // Jun
295
+ } else if (date[6] === 'l') {
296
+ monthIdx = 6 // Jul
297
+ } else {
298
+ return undefined // Invalid month
299
+ }
300
+ } else {
301
+ return undefined // Invalid month
302
+ }
303
+ } else if (
304
+ (date[4] === 'A')
305
+ ) {
306
+ if (date[5] === 'p' && date[6] === 'r') {
307
+ monthIdx = 3 // Apr
308
+ } else if (date[5] === 'u' && date[6] === 'g') {
309
+ monthIdx = 7 // Aug
310
+ } else {
311
+ return undefined // Invalid month
312
+ }
313
+ } else if (
314
+ (date[4] === 'S' && date[5] === 'e' && date[6] === 'p')
315
+ ) {
316
+ monthIdx = 8 // Sep
317
+ } else if (
318
+ (date[4] === 'O' && date[5] === 'c' && date[6] === 't')
319
+ ) {
320
+ monthIdx = 9 // Oct
321
+ } else if (
322
+ (date[4] === 'N' && date[5] === 'o' && date[6] === 'v')
323
+ ) {
324
+ monthIdx = 10 // Nov
325
+ } else if (
326
+ (date[4] === 'D' && date[5] === 'e' && date[6] === 'c')
327
+ ) {
328
+ monthIdx = 11 // Dec
329
+ } else {
330
+ // Not a valid month
126
331
  return undefined
127
332
  }
128
333
 
129
- const month = date.substring(4, 7)
130
- const monthIdx = IMF_MONTHS.indexOf(month)
131
- if (monthIdx === -1) {
132
- return undefined
334
+ let day = 0
335
+ if (date[8] === ' ') {
336
+ // Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
337
+ const code = date.charCodeAt(9)
338
+ if (code < 49 || code > 57) {
339
+ return undefined // Not a digit
340
+ }
341
+ day = code - 48 // Convert ASCII code to number
342
+ } else {
343
+ const code1 = date.charCodeAt(8)
344
+ if (code1 < 49 || code1 > 51) {
345
+ return undefined // Not a digit between 1 and 3
346
+ }
347
+ const code2 = date.charCodeAt(9)
348
+ if (code2 < 48 || code2 > 57) {
349
+ return undefined // Not a digit
350
+ }
351
+ day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
133
352
  }
134
353
 
135
- const dayString = date.substring(8, 10)
136
- const day = Number.parseInt(dayString)
137
- if (isNaN(day) || (day < 10 && dayString[0] !== ' ')) {
138
- return undefined
354
+ let hour = 0
355
+ if (date[11] === '0') {
356
+ const code = date.charCodeAt(12)
357
+ if (code < 48 || code > 57) {
358
+ return undefined // Not a digit
359
+ }
360
+ hour = code - 48 // Convert ASCII code to number
361
+ } else {
362
+ const code1 = date.charCodeAt(11)
363
+ if (code1 < 48 || code1 > 50) {
364
+ return undefined // Not a digit between 0 and 2
365
+ }
366
+ const code2 = date.charCodeAt(12)
367
+ if (code2 < 48 || code2 > 57) {
368
+ return undefined // Not a digit
369
+ }
370
+ if (code1 === 50 && code2 > 51) {
371
+ return undefined // Hour cannot be greater than 23
372
+ }
373
+ hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
139
374
  }
140
375
 
141
- const hourString = date.substring(11, 13)
142
- const hour = Number.parseInt(hourString)
143
- if (isNaN(hour) || (hour < 10 && hourString[0] !== '0')) {
144
- return undefined
376
+ let minute = 0
377
+ if (date[14] === '0') {
378
+ const code = date.charCodeAt(15)
379
+ if (code < 48 || code > 57) {
380
+ return undefined // Not a digit
381
+ }
382
+ minute = code - 48 // Convert ASCII code to number
383
+ } else {
384
+ const code1 = date.charCodeAt(14)
385
+ if (code1 < 48 || code1 > 53) {
386
+ return undefined // Not a digit between 0 and 5
387
+ }
388
+ const code2 = date.charCodeAt(15)
389
+ if (code2 < 48 || code2 > 57) {
390
+ return undefined // Not a digit
391
+ }
392
+ minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
145
393
  }
146
394
 
147
- const minuteString = date.substring(14, 16)
148
- const minute = Number.parseInt(minuteString)
149
- if (isNaN(minute) || (minute < 10 && minuteString[0] !== '0')) {
150
- return undefined
395
+ let second = 0
396
+ if (date[17] === '0') {
397
+ const code = date.charCodeAt(18)
398
+ if (code < 48 || code > 57) {
399
+ return undefined // Not a digit
400
+ }
401
+ second = code - 48 // Convert ASCII code to number
402
+ } else {
403
+ const code1 = date.charCodeAt(17)
404
+ if (code1 < 48 || code1 > 53) {
405
+ return undefined // Not a digit between 0 and 5
406
+ }
407
+ const code2 = date.charCodeAt(18)
408
+ if (code2 < 48 || code2 > 57) {
409
+ return undefined // Not a digit
410
+ }
411
+ second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
151
412
  }
152
413
 
153
- const secondString = date.substring(17, 19)
154
- const second = Number.parseInt(secondString)
155
- if (isNaN(second) || (second < 10 && secondString[0] !== '0')) {
156
- return undefined
414
+ const yearDigit1 = date.charCodeAt(20)
415
+ if (yearDigit1 < 48 || yearDigit1 > 57) {
416
+ return undefined // Not a digit
157
417
  }
158
-
159
- const year = Number.parseInt(date.substring(20, 24))
160
- if (isNaN(year)) {
161
- return undefined
418
+ const yearDigit2 = date.charCodeAt(21)
419
+ if (yearDigit2 < 48 || yearDigit2 > 57) {
420
+ return undefined // Not a digit
162
421
  }
422
+ const yearDigit3 = date.charCodeAt(22)
423
+ if (yearDigit3 < 48 || yearDigit3 > 57) {
424
+ return undefined // Not a digit
425
+ }
426
+ const yearDigit4 = date.charCodeAt(23)
427
+ if (yearDigit4 < 48 || yearDigit4 > 57) {
428
+ return undefined // Not a digit
429
+ }
430
+ const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48)
163
431
 
164
- return new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
432
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
433
+ return result.getUTCDay() === weekday ? result : undefined
165
434
  }
166
435
 
167
436
  /**
168
437
  * @see https://httpwg.org/specs/rfc9110.html#obsolete.date.formats
169
438
  *
170
439
  * @param {string} date
171
- * @param {Date} [now]
172
440
  * @returns {Date | undefined}
173
441
  */
174
- function parseRfc850Date (date, now = new Date()) {
175
- if (!date.endsWith('gmt')) {
176
- // Unsupported timezone
177
- return undefined
178
- }
179
-
180
- const commaIndex = date.indexOf(',')
181
- if (commaIndex === -1) {
182
- return undefined
183
- }
184
-
185
- if ((date.length - commaIndex - 1) !== 23) {
186
- return undefined
187
- }
188
-
189
- const dayName = date.substring(0, commaIndex)
190
- if (!RFC850_DAYS.includes(dayName)) {
442
+ function parseRfc850Date (date) {
443
+ let commaIndex = -1
444
+
445
+ let weekday = -1
446
+ if (date[0] === 'S') {
447
+ if (date[1] === 'u' && date[2] === 'n' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
448
+ weekday = 0 // Sunday
449
+ commaIndex = 6
450
+ } else if (date[1] === 'a' && date[2] === 't' && date[3] === 'u' && date[4] === 'r' && date[5] === 'd' && date[6] === 'a' && date[7] === 'y') {
451
+ weekday = 6 // Saturday
452
+ commaIndex = 8
453
+ }
454
+ } else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
455
+ weekday = 1 // Monday
456
+ commaIndex = 6
457
+ } else if (date[0] === 'T') {
458
+ if (date[1] === 'u' && date[2] === 'e' && date[3] === 's' && date[4] === 'd' && date[5] === 'a' && date[6] === 'y') {
459
+ weekday = 2 // Tuesday
460
+ commaIndex = 7
461
+ } else if (date[1] === 'h' && date[2] === 'u' && date[3] === 'r' && date[4] === 's' && date[5] === 'd' && date[6] === 'a' && date[7] === 'y') {
462
+ weekday = 4 // Thursday
463
+ commaIndex = 8
464
+ }
465
+ } else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd' && date[3] === 'n' && date[4] === 'e' && date[5] === 's' && date[6] === 'd' && date[7] === 'a' && date[8] === 'y') {
466
+ weekday = 3 // Wednesday
467
+ commaIndex = 9
468
+ } else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
469
+ weekday = 5 // Friday
470
+ commaIndex = 6
471
+ } else {
472
+ // Not a valid day name
191
473
  return undefined
192
474
  }
193
475
 
194
476
  if (
477
+ date[commaIndex] !== ',' ||
478
+ (date.length - commaIndex - 1) !== 23 ||
195
479
  date[commaIndex + 1] !== ' ' ||
196
480
  date[commaIndex + 4] !== '-' ||
197
481
  date[commaIndex + 8] !== '-' ||
198
482
  date[commaIndex + 11] !== ' ' ||
199
483
  date[commaIndex + 14] !== ':' ||
200
484
  date[commaIndex + 17] !== ':' ||
201
- date[commaIndex + 20] !== ' '
485
+ date[commaIndex + 20] !== ' ' ||
486
+ date[commaIndex + 21] !== 'G' ||
487
+ date[commaIndex + 22] !== 'M' ||
488
+ date[commaIndex + 23] !== 'T'
202
489
  ) {
203
490
  return undefined
204
491
  }
205
492
 
206
- const dayString = date.substring(commaIndex + 2, commaIndex + 4)
207
- const day = Number.parseInt(dayString)
208
- if (isNaN(day) || (day < 10 && dayString[0] !== '0')) {
209
- // Not a number, or it's less than 10 and didn't start with a 0
210
- return undefined
493
+ let day = 0
494
+ if (date[commaIndex + 2] === '0') {
495
+ // Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
496
+ const code = date.charCodeAt(commaIndex + 3)
497
+ if (code < 49 || code > 57) {
498
+ return undefined // Not a digit
499
+ }
500
+ day = code - 48 // Convert ASCII code to number
501
+ } else {
502
+ const code1 = date.charCodeAt(commaIndex + 2)
503
+ if (code1 < 49 || code1 > 51) {
504
+ return undefined // Not a digit between 1 and 3
505
+ }
506
+ const code2 = date.charCodeAt(commaIndex + 3)
507
+ if (code2 < 48 || code2 > 57) {
508
+ return undefined // Not a digit
509
+ }
510
+ day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
211
511
  }
212
512
 
213
- const month = date.substring(commaIndex + 5, commaIndex + 8)
214
- const monthIdx = IMF_MONTHS.indexOf(month)
215
- if (monthIdx === -1) {
513
+ let monthIdx = -1
514
+ if (
515
+ (date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'n')
516
+ ) {
517
+ monthIdx = 0 // Jan
518
+ } else if (
519
+ (date[commaIndex + 5] === 'F' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'b')
520
+ ) {
521
+ monthIdx = 1 // Feb
522
+ } else if (
523
+ (date[commaIndex + 5] === 'M' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'r')
524
+ ) {
525
+ monthIdx = 2 // Mar
526
+ } else if (
527
+ (date[commaIndex + 5] === 'A' && date[commaIndex + 6] === 'p' && date[commaIndex + 7] === 'r')
528
+ ) {
529
+ monthIdx = 3 // Apr
530
+ } else if (
531
+ (date[commaIndex + 5] === 'M' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'y')
532
+ ) {
533
+ monthIdx = 4 // May
534
+ } else if (
535
+ (date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'n')
536
+ ) {
537
+ monthIdx = 5 // Jun
538
+ } else if (
539
+ (date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'l')
540
+ ) {
541
+ monthIdx = 6 // Jul
542
+ } else if (
543
+ (date[commaIndex + 5] === 'A' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'g')
544
+ ) {
545
+ monthIdx = 7 // Aug
546
+ } else if (
547
+ (date[commaIndex + 5] === 'S' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'p')
548
+ ) {
549
+ monthIdx = 8 // Sep
550
+ } else if (
551
+ (date[commaIndex + 5] === 'O' && date[commaIndex + 6] === 'c' && date[commaIndex + 7] === 't')
552
+ ) {
553
+ monthIdx = 9 // Oct
554
+ } else if (
555
+ (date[commaIndex + 5] === 'N' && date[commaIndex + 6] === 'o' && date[commaIndex + 7] === 'v')
556
+ ) {
557
+ monthIdx = 10 // Nov
558
+ } else if (
559
+ (date[commaIndex + 5] === 'D' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'c')
560
+ ) {
561
+ monthIdx = 11 // Dec
562
+ } else {
563
+ // Not a valid month
216
564
  return undefined
217
565
  }
218
566
 
219
- // As of this point year is just the decade (i.e. 94)
220
- let year = Number.parseInt(date.substring(commaIndex + 9, commaIndex + 11))
221
- if (isNaN(year)) {
222
- return undefined
567
+ const yearDigit1 = date.charCodeAt(commaIndex + 9)
568
+ if (yearDigit1 < 48 || yearDigit1 > 57) {
569
+ return undefined // Not a digit
570
+ }
571
+ const yearDigit2 = date.charCodeAt(commaIndex + 10)
572
+ if (yearDigit2 < 48 || yearDigit2 > 57) {
573
+ return undefined // Not a digit
223
574
  }
224
575
 
225
- const currentYear = now.getUTCFullYear()
226
- const currentDecade = currentYear % 100
227
- const currentCentury = Math.floor(currentYear / 100)
576
+ let year = (yearDigit1 - 48) * 10 + (yearDigit2 - 48) // Convert ASCII codes to number
228
577
 
229
- if (year > currentDecade && year - currentDecade >= 50) {
230
- // Over 50 years in future, go to previous century
231
- year += (currentCentury - 1) * 100
232
- } else {
233
- year += currentCentury * 100
234
- }
578
+ // RFC 6265 states that the year is in the range 1970-2069.
579
+ // @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1
580
+ //
581
+ // 3. If the year-value is greater than or equal to 70 and less than or
582
+ // equal to 99, increment the year-value by 1900.
583
+ // 4. If the year-value is greater than or equal to 0 and less than or
584
+ // equal to 69, increment the year-value by 2000.
585
+ year += year < 70 ? 2000 : 1900
235
586
 
236
- const hourString = date.substring(commaIndex + 12, commaIndex + 14)
237
- const hour = Number.parseInt(hourString)
238
- if (isNaN(hour) || (hour < 10 && hourString[0] !== '0')) {
239
- return undefined
587
+ let hour = 0
588
+ if (date[commaIndex + 12] === '0') {
589
+ const code = date.charCodeAt(commaIndex + 13)
590
+ if (code < 48 || code > 57) {
591
+ return undefined // Not a digit
592
+ }
593
+ hour = code - 48 // Convert ASCII code to number
594
+ } else {
595
+ const code1 = date.charCodeAt(commaIndex + 12)
596
+ if (code1 < 48 || code1 > 50) {
597
+ return undefined // Not a digit between 0 and 2
598
+ }
599
+ const code2 = date.charCodeAt(commaIndex + 13)
600
+ if (code2 < 48 || code2 > 57) {
601
+ return undefined // Not a digit
602
+ }
603
+ if (code1 === 50 && code2 > 51) {
604
+ return undefined // Hour cannot be greater than 23
605
+ }
606
+ hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
240
607
  }
241
608
 
242
- const minuteString = date.substring(commaIndex + 15, commaIndex + 17)
243
- const minute = Number.parseInt(minuteString)
244
- if (isNaN(minute) || (minute < 10 && minuteString[0] !== '0')) {
245
- return undefined
609
+ let minute = 0
610
+ if (date[commaIndex + 15] === '0') {
611
+ const code = date.charCodeAt(commaIndex + 16)
612
+ if (code < 48 || code > 57) {
613
+ return undefined // Not a digit
614
+ }
615
+ minute = code - 48 // Convert ASCII code to number
616
+ } else {
617
+ const code1 = date.charCodeAt(commaIndex + 15)
618
+ if (code1 < 48 || code1 > 53) {
619
+ return undefined // Not a digit between 0 and 5
620
+ }
621
+ const code2 = date.charCodeAt(commaIndex + 16)
622
+ if (code2 < 48 || code2 > 57) {
623
+ return undefined // Not a digit
624
+ }
625
+ minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
246
626
  }
247
627
 
248
- const secondString = date.substring(commaIndex + 18, commaIndex + 20)
249
- const second = Number.parseInt(secondString)
250
- if (isNaN(second) || (second < 10 && secondString[0] !== '0')) {
251
- return undefined
628
+ let second = 0
629
+ if (date[commaIndex + 18] === '0') {
630
+ const code = date.charCodeAt(commaIndex + 19)
631
+ if (code < 48 || code > 57) {
632
+ return undefined // Not a digit
633
+ }
634
+ second = code - 48 // Convert ASCII code to number
635
+ } else {
636
+ const code1 = date.charCodeAt(commaIndex + 18)
637
+ if (code1 < 48 || code1 > 53) {
638
+ return undefined // Not a digit between 0 and 5
639
+ }
640
+ const code2 = date.charCodeAt(commaIndex + 19)
641
+ if (code2 < 48 || code2 > 57) {
642
+ return undefined // Not a digit
643
+ }
644
+ second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
252
645
  }
253
646
 
254
- return new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
647
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
648
+ return result.getUTCDay() === weekday ? result : undefined
255
649
  }
256
650
 
257
651
  module.exports = {