@johntalton/http-util 6.0.0 → 6.1.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.
- package/package.json +16 -2
- package/src/defs.js +34 -1
- package/src/headers/accept-encoding.js +9 -12
- package/src/headers/accept-language.js +10 -4
- package/src/headers/accept.js +0 -28
- package/src/headers/cache-control.js +0 -6
- package/src/headers/clear-site-data.js +4 -10
- package/src/headers/client-hints.js +9 -2
- package/src/headers/conditional.js +169 -105
- package/src/headers/content-disposition.js +0 -14
- package/src/headers/content-range.js +0 -17
- package/src/headers/content-type.js +1 -1
- package/src/headers/forwarded.js +2 -33
- package/src/headers/link.js +10 -11
- package/src/headers/multipart.js +17 -9
- package/src/headers/preference.js +0 -43
- package/src/headers/range.js +3 -31
- package/src/headers/rate-limit.js +6 -1
- package/src/headers/server-timing.js +1 -14
- package/src/headers/strict-transport-security.js +1 -0
- package/src/headers/util/mime.js +2 -2
- package/src/headers/util/whitespace.js +3 -1
- package/src/response/2xx/bytes.js +41 -6
- package/src/response/2xx/created.js +26 -5
- package/src/response/2xx/json.js +36 -4
- package/src/response/2xx/no-content.js +25 -5
- package/src/response/2xx/partial-content.js +38 -8
- package/src/response/2xx/preflight.js +26 -7
- package/src/response/3xx/found.js +23 -0
- package/src/response/3xx/not-modified.js +27 -3
- package/src/response/4xx/bad-request.js +19 -0
- package/src/response/4xx/not-acceptable.js +12 -1
- package/src/response/4xx/not-allowed.js +15 -4
- package/src/response/4xx/payment-required.js +17 -0
- package/src/response/4xx/precondition-failed.js +32 -3
- package/src/response/4xx/range-not-satisfiable.js +12 -1
- package/src/response/4xx/too-many-requests.js +18 -1
- package/src/response/4xx/unauthorized.js +1 -1
- package/src/response/4xx/unsupported-media.js +19 -2
- package/src/response/5xx/unavailable.js +13 -1
- package/src/response/index.js +4 -0
- package/src/response/response.js +6 -0
- package/src/response/send-util.js +23 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johntalton/http-util",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"imports": {
|
|
@@ -20,10 +20,24 @@
|
|
|
20
20
|
"src/headers/*.js",
|
|
21
21
|
"src/headers/util/*.js"
|
|
22
22
|
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"lint": "biome check",
|
|
25
|
+
"test": "node --test test/**/*.test.js",
|
|
26
|
+
"coverage": "c8 --src src --check-coverage --statements 90 --branches 90 --functions 90 --lines 90 -r lcovonly -r text node --test test/**/*.test.js"
|
|
27
|
+
},
|
|
23
28
|
"repository": {
|
|
24
29
|
"url": "https://github.com/johntalton/http-util"
|
|
25
30
|
},
|
|
26
31
|
"dependencies": {
|
|
27
32
|
"@johntalton/sse-util": "^1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@biomejs/biome": "^2.4.15",
|
|
36
|
+
"c8": "^11.0.0"
|
|
37
|
+
},
|
|
38
|
+
"overrides": {
|
|
39
|
+
"c8": {
|
|
40
|
+
"yargs": "^18.0.0"
|
|
41
|
+
}
|
|
28
42
|
}
|
|
29
|
-
}
|
|
43
|
+
}
|
package/src/defs.js
CHANGED
|
@@ -26,7 +26,14 @@ export const RANGE_UNITS_BYTES = 'bytes'
|
|
|
26
26
|
/** @type {'none'} */
|
|
27
27
|
export const RANGE_UNITS_NONE = 'none'
|
|
28
28
|
|
|
29
|
+
|
|
29
30
|
/** @import { TimingsInfo } from './headers/server-timing.js' */
|
|
31
|
+
/** @import { EtagItem, IMFFixDateInput, } from './headers/conditional.js' */
|
|
32
|
+
/** @import { CacheControlOptions } from './headers/cache-control.js' */
|
|
33
|
+
/** @import { ContentRangeDirective } from './headers/content-range.js' */
|
|
34
|
+
/** @import { RateLimitPolicyInfo, RateLimitInfo } from './headers/rate-limit.js' */
|
|
35
|
+
|
|
36
|
+
/** @typedef {RANGE_UNITS_BYTES | RANGE_UNITS_NONE} AcceptRangeUnits */
|
|
30
37
|
|
|
31
38
|
/**
|
|
32
39
|
* @typedef {`X-${string}`} CustomHeaderKey
|
|
@@ -46,4 +53,30 @@ export const RANGE_UNITS_NONE = 'none'
|
|
|
46
53
|
* @property {boolean} [bom]
|
|
47
54
|
*/
|
|
48
55
|
|
|
49
|
-
/** @typedef {ArrayBufferLike|ArrayBufferView|ReadableStream|string} SendBody */
|
|
56
|
+
/** @typedef {ArrayBufferLike|ArrayBufferView|ReadableStream|string} SendBody */
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {Object} SendContent
|
|
60
|
+
* @property {string|undefined} contentType
|
|
61
|
+
* @property {number|undefined} contentLength
|
|
62
|
+
* @property {string|undefined} encoding
|
|
63
|
+
* @property {EtagItem|undefined} etag
|
|
64
|
+
* @property {IMFFixDateInput|string|undefined} lastModified
|
|
65
|
+
* @property {number|undefined} age
|
|
66
|
+
* @property {CacheControlOptions} cacheControl
|
|
67
|
+
* @property {ContentRangeDirective} rangeDirective
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {Object} SendInfo
|
|
72
|
+
* @property {Array<string>} supportedMethods
|
|
73
|
+
* @property {Array<string>|string} supportedTypes
|
|
74
|
+
* @property {Array<string>|string} acceptableMediaType
|
|
75
|
+
* @property {AcceptRangeUnits|undefined} acceptRanges
|
|
76
|
+
* @property {Array<string>|undefined} supportedQueryTypes
|
|
77
|
+
* @property {RateLimitInfo} limitInfo
|
|
78
|
+
* @property {Array<RateLimitPolicyInfo>} policies
|
|
79
|
+
* @property {number|undefined} retryAfter
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
|
|
@@ -2,6 +2,8 @@ import { parseAcceptStyleHeader } from './util/accept-util.js'
|
|
|
2
2
|
|
|
3
3
|
/** @import { AcceptStyleItem } from './util/accept-util.js' */
|
|
4
4
|
|
|
5
|
+
export const ENCODING_ANY = '*'
|
|
6
|
+
|
|
5
7
|
export const WELL_KNOWN_ENCODINGS = new Map([
|
|
6
8
|
[ 'gzip, deflate, br, zstd', [ { name: 'gzip' }, { name: 'deflate' }, { name: 'br' }, { name: 'zstd' } ] ],
|
|
7
9
|
[ 'gzip, deflate, br', [ { name: 'gzip' }, { name: 'deflate' }, { name: 'br' } ] ]
|
|
@@ -29,6 +31,8 @@ export class AcceptEncoding {
|
|
|
29
31
|
* @param {Array<string>} supportedTypes
|
|
30
32
|
*/
|
|
31
33
|
static selectFrom(acceptEncodings, supportedTypes) {
|
|
34
|
+
if(supportedTypes === undefined) { return undefined }
|
|
35
|
+
|
|
32
36
|
for(const acceptEncoding of acceptEncodings) {
|
|
33
37
|
const { name } = acceptEncoding
|
|
34
38
|
if(supportedTypes.includes(name)) {
|
|
@@ -36,18 +40,11 @@ export class AcceptEncoding {
|
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
//
|
|
44
|
+
if(acceptEncodings.some(item => item.name === ENCODING_ANY)) {
|
|
45
|
+
return supportedTypes.at(0)
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
return undefined
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// console.log(AcceptEncoding.parse(''))
|
|
45
|
-
// console.log(AcceptEncoding.parse(' '))
|
|
46
|
-
// console.log(AcceptEncoding.parse('zstd'))
|
|
47
|
-
// console.log(AcceptEncoding.parse('identity'))
|
|
48
|
-
// console.log(AcceptEncoding.parse('*'))
|
|
49
|
-
// console.log(AcceptEncoding.parse('gzip, deflate, br, zstd'))
|
|
50
|
-
// console.log(AcceptEncoding.parse('br;q=1.0, gzip;q=0.8, *;q=0.1'))
|
|
51
|
-
// console.log(AcceptEncoding.parse('deflate, gzip;q=1.0, *;q=0.5'))
|
|
52
|
-
// console.log(AcceptEncoding.parse('identity;q=0'))
|
|
53
|
-
// console.log(AcceptEncoding.parse('*;q=0'))
|
|
@@ -2,6 +2,8 @@ import { parseAcceptStyleHeader } from './util/accept-util.js'
|
|
|
2
2
|
|
|
3
3
|
/** @import { AcceptStyleItem } from './util/accept-util.js' */
|
|
4
4
|
|
|
5
|
+
export const LANGUAGE_ANY = '*'
|
|
6
|
+
|
|
5
7
|
export const WELL_KNOWN_LANGUAGES = new Map([
|
|
6
8
|
[ 'en-US,en;q=0.5', [ { name: 'en-US', quality: 1 }, { name: 'en', quality: 0.5 } ] ],
|
|
7
9
|
[ 'en-US,en;q=0.9', [ { name: 'en-US', quality: 1 }, { name: 'en', quality: 0.9 } ] ],
|
|
@@ -30,16 +32,20 @@ export class AcceptLanguage {
|
|
|
30
32
|
* @param {Array<string>} supportedTypes
|
|
31
33
|
*/
|
|
32
34
|
static selectFrom(acceptLanguages, supportedTypes) {
|
|
35
|
+
if(supportedTypes === undefined) { return undefined }
|
|
36
|
+
|
|
33
37
|
for(const acceptLanguage of acceptLanguages) {
|
|
34
38
|
const { name } = acceptLanguage
|
|
35
39
|
if(supportedTypes.includes(name)) {
|
|
36
40
|
return name
|
|
37
|
-
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//
|
|
45
|
+
if(acceptLanguages.some(item => item.name === LANGUAGE_ANY)) {
|
|
46
|
+
return supportedTypes.at(0)
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
return undefined
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
|
-
|
|
44
|
-
// console.log(AcceptLanguage.parse('en-US,en;q=0.9'))
|
|
45
|
-
// console.log(AcceptLanguage.select('foo;q=0.2, bar-BZ', [ 'bang', 'foo' ]))
|
package/src/headers/accept.js
CHANGED
|
@@ -83,32 +83,4 @@ export class Accept {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// console.log(Accept.parse('text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, text/*;q=.8, */*;q=0.7'))
|
|
87
|
-
// console.log(Accept.select('text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, text/*;q=.8, */*;q=0.7', [ 'application/json', 'text/plain' ]))
|
|
88
|
-
// console.log(Accept.parse('*'))
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// const tests = [
|
|
92
|
-
// undefined,
|
|
93
|
-
// '',
|
|
94
|
-
// ' ',
|
|
95
|
-
// ' fake',
|
|
96
|
-
// ' application/json',
|
|
97
|
-
// ' application/xml,',
|
|
98
|
-
// ' ,application/xml ,,',
|
|
99
|
-
// ' audio/*; q=0.2, audio/basic',
|
|
100
|
-
// ' text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8',
|
|
101
|
-
// ' text/*;q=0.3, text/plain;q=0.7, text/plain;format=flowed,\ntext/plain;format=fixed;q=0.4, */*;q=0.5',
|
|
102
|
-
|
|
103
|
-
// ' */*, foo/bar, foo/*, biz/bang, */*;q=.2, quix/quak;q=.1',
|
|
104
|
-
// 'foo / bar ; q = .5'
|
|
105
|
-
// ]
|
|
106
|
-
|
|
107
|
-
// tests.forEach(test => {
|
|
108
|
-
// const result = Accept.parse(test)
|
|
109
|
-
// console.log('=============================')
|
|
110
|
-
// console.log({ test })
|
|
111
|
-
// console.log('---')
|
|
112
|
-
// console.log(result)
|
|
113
|
-
// })
|
|
114
86
|
|
|
@@ -42,18 +42,12 @@ export class SiteData {
|
|
|
42
42
|
if(directives.prerenderCache === true) { result.push(CSD_DIRECTIVE_PRERENDER_CACHE) }
|
|
43
43
|
if(directives.storage === true) { result.push(CSD_DIRECTIVE_STORAGE) }
|
|
44
44
|
|
|
45
|
+
if(result.length === 0) {
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
return result
|
|
46
50
|
.map(item => `${CSD_QUOTE}${item}${CSD_QUOTE}`)
|
|
47
51
|
.join(CSD_DIRECTIVE_SEPARATOR)
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
// console.log(SiteData.encode())
|
|
52
|
-
// console.log(SiteData.encode({}))
|
|
53
|
-
// console.log(SiteData.encode(false))
|
|
54
|
-
// console.log(SiteData.encode('true'))
|
|
55
|
-
// console.log(SiteData.encode(true))
|
|
56
|
-
// console.log(SiteData.encode('*'))
|
|
57
|
-
// console.log(SiteData.encode({ storage: false }))
|
|
58
|
-
// console.log(SiteData.encode({ storage: true }))
|
|
59
|
-
// console.log(SiteData.encode({ storage: true, cookies: true }))
|
|
@@ -71,9 +71,16 @@ export class ClientHints {
|
|
|
71
71
|
* @param {Array<String>} hints
|
|
72
72
|
*/
|
|
73
73
|
static encode(hints) {
|
|
74
|
-
return
|
|
74
|
+
if(hints === undefined) { return undefined }
|
|
75
|
+
if(!Array.isArray(hints)) { return undefined }
|
|
76
|
+
if(hints.length === 0) { return undefined }
|
|
77
|
+
|
|
78
|
+
const remaining = hints
|
|
75
79
|
.filter(hint => KNOWN_CLIENT_HINTS.includes(hint))
|
|
76
|
-
|
|
80
|
+
|
|
81
|
+
if(remaining.length === 0) { return undefined }
|
|
82
|
+
|
|
83
|
+
return remaining.join(', ')
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: includes helper classes */
|
|
2
|
+
/** biome-ignore-all lint/nursery/noExcessiveLinesPerFile: includes temporal and date support */
|
|
1
3
|
import { isQuoted, stripQuotes } from './util/quote.js'
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -24,7 +26,7 @@ import { isQuoted, stripQuotes } from './util/quote.js'
|
|
|
24
26
|
/** @typedef {WeakEtagItem | NotWeakEtagItem | AnyEtagItem } EtagItem */
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
|
-
* @typedef {Object}
|
|
29
|
+
* @typedef {Object} IMFFixDateItem
|
|
28
30
|
* @property {typeof DATE_DAYS[number]} dayName
|
|
29
31
|
* @property {number} day
|
|
30
32
|
* @property {typeof DATE_MONTHS[number]} month
|
|
@@ -32,8 +34,39 @@ import { isQuoted, stripQuotes } from './util/quote.js'
|
|
|
32
34
|
* @property {number} hour
|
|
33
35
|
* @property {number} minute
|
|
34
36
|
* @property {number} second
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} IMFFixDateItemExtension
|
|
35
41
|
* @property {Date|undefined} [date]
|
|
42
|
+
* @property {Temporal.Instant|undefined} [instant]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/** @typedef {IMFFixDateItem & IMFFixDateItemExtension} IMFFixDate */
|
|
46
|
+
/** @typedef {IMFFixDate|Date|Temporal.Instant|undefined} IMFFixDateInput */
|
|
47
|
+
|
|
48
|
+
export const FEATURE_TEMPORAL = typeof Temporal !== 'undefined'
|
|
49
|
+
|
|
50
|
+
export const IMF_FIX_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
|
|
51
|
+
timeZone: 'UTC',
|
|
52
|
+
weekday: 'short',
|
|
53
|
+
year: 'numeric',
|
|
54
|
+
month: 'short',
|
|
55
|
+
day: '2-digit',
|
|
56
|
+
hour: '2-digit',
|
|
57
|
+
minute: '2-digit',
|
|
58
|
+
second: '2-digit',
|
|
59
|
+
hour12: false
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {IMFFixDate|Date|Temporal.Instant|undefined} reference
|
|
64
|
+
* @returns {reference is Temporal.Instant}
|
|
36
65
|
*/
|
|
66
|
+
export function isTemporalInstant(reference) {
|
|
67
|
+
if(!FEATURE_TEMPORAL) { return false }
|
|
68
|
+
return reference instanceof Temporal.Instant
|
|
69
|
+
}
|
|
37
70
|
|
|
38
71
|
export const CONDITION_ETAG_SEPARATOR = ','
|
|
39
72
|
export const CONDITION_ETAG_ANY = '*'
|
|
@@ -67,6 +100,8 @@ export class ETag {
|
|
|
67
100
|
* @param {string} etag
|
|
68
101
|
*/
|
|
69
102
|
static isValid(etag) {
|
|
103
|
+
if(etag === undefined) { return false }
|
|
104
|
+
|
|
70
105
|
// %x21 / %x23-7E and %x80-FF
|
|
71
106
|
for(const c of etag) {
|
|
72
107
|
if(c.charCodeAt(0) < 0x21) { return false }
|
|
@@ -115,8 +150,9 @@ export class ETag {
|
|
|
115
150
|
if(!isQuoted(quotedEtag)) { return undefined }
|
|
116
151
|
const etag = stripQuotes(quotedEtag)
|
|
117
152
|
if(etag === undefined) { return undefined }
|
|
153
|
+
if(etag === '') { return undefined }
|
|
154
|
+
if(etag === CONDITION_ETAG_ANY) { return ANY_ETAG_ITEM } // todo: should this return undefined?
|
|
118
155
|
if(!ETag.isValid(etag)) { return undefined }
|
|
119
|
-
if(etag === CONDITION_ETAG_ANY) { return undefined }
|
|
120
156
|
|
|
121
157
|
return {
|
|
122
158
|
weak,
|
|
@@ -126,6 +162,100 @@ export class ETag {
|
|
|
126
162
|
}
|
|
127
163
|
}
|
|
128
164
|
|
|
165
|
+
export class FixDate {
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* True if {@link test} is after (exclusive) the {@link reference}
|
|
169
|
+
* (compares are seconds precision)
|
|
170
|
+
* @param {IMFFixDateInput} reference
|
|
171
|
+
* @param {IMFFixDateInput} test
|
|
172
|
+
* @returns {boolean}
|
|
173
|
+
*/
|
|
174
|
+
static isAfter(reference, test) {
|
|
175
|
+
if(reference === undefined) { return false }
|
|
176
|
+
if(test === undefined) { return false }
|
|
177
|
+
|
|
178
|
+
if(FEATURE_TEMPORAL) {
|
|
179
|
+
/** @type {Temporal.RoundingOptions<Temporal.TimeUnit>} */
|
|
180
|
+
const precision = {
|
|
181
|
+
smallestUnit: 'second',
|
|
182
|
+
roundingMode: 'trunc'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const referenceInstant = FixDate.toInstant(reference)?.round(precision)
|
|
186
|
+
const testInstant = FixDate.toInstant(test)?.round(precision)
|
|
187
|
+
|
|
188
|
+
if(referenceInstant === undefined) { return false }
|
|
189
|
+
if(testInstant === undefined) { return false }
|
|
190
|
+
|
|
191
|
+
return Temporal.Instant.compare(referenceInstant, testInstant) === -1
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//
|
|
195
|
+
const referenceDate = FixDate.toDate(reference)
|
|
196
|
+
const testDate = FixDate.toDate(test)
|
|
197
|
+
|
|
198
|
+
if(referenceDate === undefined) { return false }
|
|
199
|
+
if(testDate === undefined) { return false }
|
|
200
|
+
|
|
201
|
+
// this effectively rounds to seconds
|
|
202
|
+
referenceDate.setMilliseconds(0)
|
|
203
|
+
testDate.setMilliseconds(0)
|
|
204
|
+
|
|
205
|
+
return testDate > referenceDate
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {IMFFixDateInput} reference
|
|
210
|
+
* @returns {Temporal.Instant|undefined}
|
|
211
|
+
*/
|
|
212
|
+
static toInstant(reference) {
|
|
213
|
+
if(!FEATURE_TEMPORAL) { return undefined }
|
|
214
|
+
if(reference === undefined) { return undefined }
|
|
215
|
+
|
|
216
|
+
if(isTemporalInstant(reference)) { return reference }
|
|
217
|
+
if(reference instanceof Date) { return reference.toTemporalInstant() }
|
|
218
|
+
|
|
219
|
+
if(reference.instant !== undefined) { return reference.instant }
|
|
220
|
+
if(reference.date !== undefined) { return reference.date.toTemporalInstant() }
|
|
221
|
+
|
|
222
|
+
const { year, month: monthName, day, hour, minute, second } = reference
|
|
223
|
+
|
|
224
|
+
const zeroMonth = DATE_MONTHS.indexOf(monthName)
|
|
225
|
+
if(zeroMonth === -1) { return undefined }
|
|
226
|
+
const month = zeroMonth + 1
|
|
227
|
+
|
|
228
|
+
const zdt = Temporal.ZonedDateTime.from({
|
|
229
|
+
year,
|
|
230
|
+
month,
|
|
231
|
+
day,
|
|
232
|
+
hour,
|
|
233
|
+
minute,
|
|
234
|
+
second,
|
|
235
|
+
timeZone: 'UTC'
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
return zdt.toInstant()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {IMFFixDateInput} reference
|
|
243
|
+
* @returns {Date|undefined}
|
|
244
|
+
*/
|
|
245
|
+
static toDate(reference) {
|
|
246
|
+
if(reference === undefined) { return undefined }
|
|
247
|
+
|
|
248
|
+
if(reference instanceof Date) { return reference }
|
|
249
|
+
if(isTemporalInstant(reference)) { return new Date(reference.epochMilliseconds) }
|
|
250
|
+
|
|
251
|
+
if(FEATURE_TEMPORAL && (reference.instant !== undefined)) { return new Date(reference.instant.epochMilliseconds) }
|
|
252
|
+
if(reference.date !== undefined) { return reference.date }
|
|
253
|
+
|
|
254
|
+
const { year, month, day, hour, minute, second } = reference
|
|
255
|
+
return new Date(Date.UTC(year, DATE_MONTHS.indexOf(month), day, hour, minute, second))
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
129
259
|
export class Conditional {
|
|
130
260
|
/**
|
|
131
261
|
* @param {EtagItem|undefined} etagItem
|
|
@@ -158,29 +288,47 @@ export class Conditional {
|
|
|
158
288
|
}
|
|
159
289
|
|
|
160
290
|
/**
|
|
161
|
-
* @param {Array<EtagItem
|
|
291
|
+
* @param {Array<EtagItem>|undefined} etagItemList
|
|
162
292
|
*/
|
|
163
293
|
static hasAny(etagItemList) {
|
|
164
|
-
return etagItemList
|
|
294
|
+
return etagItemList?.find(item => item.any) !== undefined
|
|
165
295
|
}
|
|
166
296
|
|
|
167
297
|
/**
|
|
168
|
-
* @param {Array<EtagItem
|
|
169
|
-
* @param {string} etag
|
|
298
|
+
* @param {Array<EtagItem>|undefined} etagItemList
|
|
299
|
+
* @param {string|undefined} etag
|
|
170
300
|
*/
|
|
171
301
|
static hasEtag(etagItemList, etag) {
|
|
172
|
-
return etagItemList
|
|
302
|
+
return etagItemList?.find(item => item.etag === etag) !== undefined
|
|
173
303
|
}
|
|
174
304
|
|
|
175
305
|
/**
|
|
176
|
-
* @param {
|
|
306
|
+
* @param {IMFFixDateInput|string|undefined} reference
|
|
177
307
|
* @returns {string|undefined}
|
|
178
308
|
*/
|
|
179
|
-
static encodeFixDate(
|
|
180
|
-
if(
|
|
181
|
-
|
|
309
|
+
static encodeFixDate(reference) {
|
|
310
|
+
if(reference === undefined) { return undefined }
|
|
311
|
+
|
|
312
|
+
if(typeof reference === 'string') { return reference }
|
|
313
|
+
if(reference instanceof Date) { return reference.toUTCString() }
|
|
314
|
+
if(isTemporalInstant(reference)) {
|
|
315
|
+
|
|
316
|
+
const parts = IMF_FIX_DATE_FORMATTER.formatToParts(reference)
|
|
317
|
+
const m = new Map(parts.map(p => [ p.type, p.value ]))
|
|
318
|
+
const weekday = m.get('weekday') ?? 'ERR'
|
|
319
|
+
const day = m.get('day') ?? '00'
|
|
320
|
+
const month = m.get('month') ?? '00'
|
|
321
|
+
const year = m.get('year') ?? '0000'
|
|
322
|
+
const hour = m.get('hour') ?? '00'
|
|
323
|
+
const minute = m.get('minute') ?? '00'
|
|
324
|
+
const second = m.get('second') ?? '00'
|
|
325
|
+
|
|
326
|
+
return `${weekday}, ${day} ${month} ${year} ${hour}:${minute}:${second} ${DATE_ZONE}`
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if(reference.date !== undefined) { return reference.date.toUTCString() }
|
|
182
330
|
|
|
183
|
-
const { year, month, day, hour, minute, second } =
|
|
331
|
+
const { year, month, day, hour, minute, second } = reference
|
|
184
332
|
const d = new Date(Date.UTC(year, DATE_MONTHS.indexOf(month), day, hour, minute, second))
|
|
185
333
|
return d.toUTCString()
|
|
186
334
|
}
|
|
@@ -250,105 +398,21 @@ export class Conditional {
|
|
|
250
398
|
if(minute > 60 || minute < 0) { return undefined }
|
|
251
399
|
if(second > 60 || second < 0) { return undefined }
|
|
252
400
|
|
|
253
|
-
|
|
254
|
-
return {
|
|
401
|
+
const fixDate = {
|
|
255
402
|
dayName,
|
|
256
403
|
day,
|
|
257
404
|
month,
|
|
258
405
|
year,
|
|
259
406
|
hour,
|
|
260
407
|
minute,
|
|
261
|
-
second
|
|
262
|
-
|
|
263
|
-
|
|
408
|
+
second
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
//
|
|
412
|
+
return {
|
|
413
|
+
...fixDate,
|
|
414
|
+
date: FixDate.toDate(fixDate),
|
|
415
|
+
instant: FixDate.toInstant(fixDate)
|
|
264
416
|
}
|
|
265
417
|
}
|
|
266
418
|
}
|
|
267
|
-
|
|
268
|
-
// Ok
|
|
269
|
-
// console.log(Conditional.encodeEtag({ any: true, weak: false, etag: '*' }))
|
|
270
|
-
// console.log(Conditional.encodeEtag({ any: true, weak: true, etag: '*' }))
|
|
271
|
-
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo' }))
|
|
272
|
-
// console.log(Conditional.encodeEtag({ any: false, weak: true, etag: 'WeakFoo' }))
|
|
273
|
-
|
|
274
|
-
// Error
|
|
275
|
-
// console.log(Conditional.encodeEtag(undefined))
|
|
276
|
-
// console.log(Conditional.encodeEtag({ any: true, weak: false, etag: 'NotAsterisk' }))
|
|
277
|
-
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo\tBar' }))
|
|
278
|
-
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo"Bar' }))
|
|
279
|
-
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: '*' }))
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// Ok
|
|
283
|
-
// console.log(Conditional.parseEtagList('"bfc13a64729c4290ef5b2c2730249c88ca92d82d"'))
|
|
284
|
-
// console.log(Conditional.parseEtagList('W/"67ab43", "54ed21", "7892dd"'))
|
|
285
|
-
// console.log(Conditional.parseEtagList('*'))
|
|
286
|
-
// console.log(Conditional.parseEtagList('"!ÿ©"'))
|
|
287
|
-
// console.log(Conditional.parseEtagList('"!","ÿ", "©"'))
|
|
288
|
-
// console.log(Conditional.parseEtagList('"!","ÿ" ,\t"©"'))
|
|
289
|
-
|
|
290
|
-
// Error
|
|
291
|
-
// console.log(Conditional.parseEtagList('"*"'))
|
|
292
|
-
// console.log(Conditional.parseEtagList('W/'))
|
|
293
|
-
// console.log(Conditional.parseEtagList('W/"'))
|
|
294
|
-
// console.log(Conditional.parseEtagList('W/""'))
|
|
295
|
-
// console.log(Conditional.parseEtagList(''))
|
|
296
|
-
// console.log(Conditional.parseEtagList('"'))
|
|
297
|
-
// console.log(Conditional.parseEtagList('""'))
|
|
298
|
-
// console.log(Conditional.parseEtagList('"""'))
|
|
299
|
-
// console.log(Conditional.parseEtagList('" "'))
|
|
300
|
-
// console.log(Conditional.parseEtagList('"\n"'))
|
|
301
|
-
// console.log(Conditional.parseEtagList('"\t"'))
|
|
302
|
-
|
|
303
|
-
//
|
|
304
|
-
// const testsOk = [
|
|
305
|
-
// 'Sun, 06 Nov 1994 08:49:37 GMT',
|
|
306
|
-
// 'Sun, 06 Nov 1994 00:00:00 GMT',
|
|
307
|
-
// 'Tue, 01 Nov 1994 00:00:00 GMT',
|
|
308
|
-
// 'Thu, 06 Nov 3000 08:49:37 GMT',
|
|
309
|
-
// 'Sun, 06 Nov 1994 23:59:59 GMT',
|
|
310
|
-
// new String('Sun, 06 Nov 1994 08:49:37 GMT'),
|
|
311
|
-
// ]
|
|
312
|
-
// for(const test of testsOk) {
|
|
313
|
-
// const result = Conditional.parseFixDate(test)
|
|
314
|
-
// if(result?.date.toUTCString() !== test.toString()) {
|
|
315
|
-
// console.log('🛑', test, result, result?.date.toUTCString())
|
|
316
|
-
// break
|
|
317
|
-
// }
|
|
318
|
-
// }
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// const testBad = [
|
|
323
|
-
// undefined,
|
|
324
|
-
// null,
|
|
325
|
-
// {},
|
|
326
|
-
// new String(),
|
|
327
|
-
// '',
|
|
328
|
-
// 'Anything',
|
|
329
|
-
// ' , : : GMT',
|
|
330
|
-
// 'Sun, Nov : : GMT',
|
|
331
|
-
// 'Sun, 00 Nov 0000 00:00:00 GMT',
|
|
332
|
-
// 'Sun, 06 Nov 1994 08-49-37 GMT',
|
|
333
|
-
// 'Sun 06 Nov 1994 08:49:37 GMT',
|
|
334
|
-
// 'FOO, 06 Nov 1994 08:49:37 GMT',
|
|
335
|
-
// 'Sun, 32 Nov 1994 08:49:37 GMT',
|
|
336
|
-
// 'Sun, 00 Nov 1994 08:49:37 GMT',
|
|
337
|
-
// 'Sun, 06 Nov 0900 08:49:37 GMT',
|
|
338
|
-
// 'Sun, 06 Nov 1994 08:49:37 UTC',
|
|
339
|
-
// 'Sun, 06 Nov 1994 30:49:37 GMT',
|
|
340
|
-
// 'Sun,\t06 Nov 1994 08:49:37 GMT',
|
|
341
|
-
|
|
342
|
-
// 'Sunday, 06-Nov-94 08:49:37 GMT',
|
|
343
|
-
// 'Sun Nov 6 08:49:37 1994',
|
|
344
|
-
// 'Sun Nov 6 08:49:37 1994 ',
|
|
345
|
-
|
|
346
|
-
// ]
|
|
347
|
-
// for(const test of testBad) {
|
|
348
|
-
// const result = Conditional.parseFixDate(test)
|
|
349
|
-
// if(result !== undefined) {
|
|
350
|
-
// console.log('🛑', test, result)
|
|
351
|
-
// break
|
|
352
|
-
// }
|
|
353
|
-
// }
|
|
354
|
-
|
|
@@ -42,17 +42,3 @@ export class ContentDisposition {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
// console.log(parseContentDisposition())
|
|
47
|
-
// // console.log(parseContentDisposition(null))
|
|
48
|
-
// console.log(parseContentDisposition(''))
|
|
49
|
-
// console.log(parseContentDisposition('form-data'))
|
|
50
|
-
// console.log(parseContentDisposition(' form-data ; name'))
|
|
51
|
-
// console.log(parseContentDisposition('form-data; name="key"'))
|
|
52
|
-
|
|
53
|
-
// console.log(parseContentDisposition('inline'))
|
|
54
|
-
// console.log(parseContentDisposition('attachment'))
|
|
55
|
-
// console.log(parseContentDisposition('attachment; filename="file name.jpg"'))
|
|
56
|
-
// console.log(parseContentDisposition('attachment; filename*=UTF-8\'\'file%20name.jpg'))
|
|
57
|
-
// console.log(parseContentDisposition('attachment; filename*=UTF-8\'\'file%20name.jpg'))
|
|
58
|
-
// console.log(parseContentDisposition('form-data;title*=us-ascii\'en-us\'This%20is%20%2A%2A%2Afun%2A%2A%2A'))
|
|
@@ -40,20 +40,3 @@ export class ContentRange {
|
|
|
40
40
|
return `${units} ${rangeStr}${CONTENT_RANGE_SIZE_SEPARATOR}${size}`
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
// console.log(ContentRange.encode({}))
|
|
45
|
-
// console.log(ContentRange.encode({ size: '*' }))
|
|
46
|
-
// console.log(ContentRange.encode({ units: 'bytes' }))
|
|
47
|
-
// console.log(ContentRange.encode({ range: '*' }))
|
|
48
|
-
// console.log(ContentRange.encode({ range: '*', size: '*' }))
|
|
49
|
-
|
|
50
|
-
// console.log()
|
|
51
|
-
// console.log(ContentRange.encode({ range: { start: 0, end: 1024 } }))
|
|
52
|
-
// console.log(ContentRange.encode({ range: { start: 0, end: 1024 }, size: 1024 }))
|
|
53
|
-
// console.log(ContentRange.encode({ range: '*', size: 1024 }))
|
|
54
|
-
// console.log(ContentRange.encode({ size: 1024 }))
|
|
55
|
-
|
|
56
|
-
// console.log()
|
|
57
|
-
// console.log(ContentRange.encode({ units: 'bob' }))
|
|
58
|
-
// console.log(ContentRange.encode({ range: 'bob' }))
|
|
59
|
-
// console.log(ContentRange.encode({ size: 'bob' }))
|
package/src/headers/forwarded.js
CHANGED
|
@@ -41,6 +41,8 @@ export class Forwarded {
|
|
|
41
41
|
* @returns {Map<string, string>|undefined}
|
|
42
42
|
*/
|
|
43
43
|
static selectRightMost(forwardedList, skipList = []) {
|
|
44
|
+
if(forwardedList === undefined) { return undefined }
|
|
45
|
+
|
|
44
46
|
const iter = skipList[Symbol.iterator]()
|
|
45
47
|
|
|
46
48
|
for(const forwarded of forwardedList.toReversed()) {
|
|
@@ -54,39 +56,6 @@ export class Forwarded {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
/*
|
|
59
|
-
const examples = [
|
|
60
|
-
{ f: [], s: [], ef: undefined },
|
|
61
|
-
|
|
62
|
-
{ f: [], s: [ '1.1.1.1' ] , ef: undefined },
|
|
63
|
-
{ f: [], s: [ '*' ] , ef: undefined },
|
|
64
|
-
|
|
65
|
-
{ f: [ { for: '1.1.1.1' } ], s: [], ef: '1.1.1.1' },
|
|
66
|
-
{ f: [ { for: '1.1.1.1' } ], s: [ '*' ], ef: undefined },
|
|
67
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' } ], s: [], ef: '2.2.2.2' },
|
|
68
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' } ], s: [ '2.2.2.2' ], ef: '1.1.1.1' },
|
|
69
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '3.3.3.3', '2.2.2.2' ], ef: '1.1.1.1' },
|
|
70
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '3.3.3.3', '*' ], ef: '1.1.1.1' },
|
|
71
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '*', '*' ], ef: '1.1.1.1' },
|
|
72
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '*', '*', '*' ], ef: undefined },
|
|
73
|
-
|
|
74
|
-
{ f: [ { for: '1.1.1.1' } ], s: [ '*', '*' ], ef: undefined },
|
|
75
|
-
|
|
76
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '3.3.3.3'], ef: '2.2.2.2' },
|
|
77
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '*'], ef: '2.2.2.2' },
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
for(const { f, s, ef } of examples) {
|
|
81
|
-
const result = Forwarded.selectRightMost(f.map(i => new Map(Object.entries(i))), s)
|
|
82
|
-
const resultFor = result?.get('for')
|
|
83
|
-
if(resultFor !== ef) {
|
|
84
|
-
console.log(`mismatch ${ef} !== ${resultFor}`)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
59
|
/*
|
|
91
60
|
const examples = [
|
|
92
61
|
null,
|