@johntalton/http-util 5.1.6 → 6.0.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/README.md +54 -7
- package/package.json +11 -5
- package/src/body.js +8 -8
- package/src/{response/defs.js → defs.js} +2 -1
- package/src/{accept-encoding.js → headers/accept-encoding.js} +2 -2
- package/src/{accept-language.js → headers/accept-language.js} +4 -5
- package/src/{accept.js → headers/accept.js} +18 -26
- package/src/headers/client-hints.js +81 -0
- package/src/{conditional.js → headers/conditional.js} +21 -12
- package/src/{content-disposition.js → headers/content-disposition.js} +26 -25
- package/src/{content-range.js → headers/content-range.js} +1 -1
- package/src/headers/content-type.js +101 -0
- package/src/{forwarded.js → headers/forwarded.js} +6 -23
- package/src/{index.js → headers/index.js} +4 -2
- package/src/{multipart.js → headers/multipart.js} +6 -5
- package/src/{preference.js → headers/preference.js} +3 -15
- package/src/{range.js → headers/range.js} +1 -1
- package/src/{server-timing.js → headers/server-timing.js} +2 -2
- package/src/headers/strict-transport-security.js +38 -0
- package/src/{accept-util.js → headers/util/accept-util.js} +8 -14
- package/src/headers/util/index.js +7 -0
- package/src/headers/util/kvp.js +79 -0
- package/src/headers/util/mime.js +77 -0
- package/src/headers/util/whitespace.js +6 -0
- package/src/{www-authenticate.js → headers/www-authenticate.js} +1 -1
- package/src/response/{accepted.js → 2xx/accepted.js} +2 -2
- package/src/response/{bytes.js → 2xx/bytes.js} +5 -5
- package/src/response/{created.js → 2xx/created.js} +4 -4
- package/src/response/{json.js → 2xx/json.js} +5 -5
- package/src/response/{no-content.js → 2xx/no-content.js} +4 -4
- package/src/response/{partial-content.js → 2xx/partial-content.js} +9 -9
- package/src/response/{preflight.js → 2xx/preflight.js} +4 -4
- package/src/response/{sse.js → 2xx/sse.js} +2 -2
- package/src/response/{trace.js → 2xx/trace.js} +3 -3
- package/src/response/{moved-permanently.js → 3xx/moved-permanently.js} +2 -2
- package/src/response/{multiple-choices.js → 3xx/multiple-choices.js} +2 -3
- package/src/response/{not-modified.js → 3xx/not-modified.js} +7 -7
- package/src/response/{permanent-redirect.js → 3xx/permanent-redirect.js} +2 -2
- package/src/response/{see-other.js → 3xx/see-other.js} +2 -2
- package/src/response/{temporary-redirect.js → 3xx/temporary-redirect.js} +2 -2
- package/src/response/{conflict.js → 4xx/conflict.js} +2 -2
- package/src/response/{content-too-large.js → 4xx/content-too-large.js} +2 -2
- package/src/response/{forbidden.js → 4xx/forbidden.js} +3 -2
- package/src/response/{gone.js → 4xx/gone.js} +2 -2
- package/src/response/{im-a-teapot.js → 4xx/im-a-teapot.js} +2 -2
- package/src/response/{not-acceptable.js → 4xx/not-acceptable.js} +3 -3
- package/src/response/{not-allowed.js → 4xx/not-allowed.js} +2 -2
- package/src/response/{not-found.js → 4xx/not-found.js} +3 -3
- package/src/response/{precondition-failed.js → 4xx/precondition-failed.js} +2 -2
- package/src/response/{range-not-satisfiable.js → 4xx/range-not-satisfiable.js} +4 -4
- package/src/response/{timeout.js → 4xx/timeout.js} +2 -2
- package/src/response/{too-many-requests.js → 4xx/too-many-requests.js} +5 -5
- package/src/response/{unauthorized.js → 4xx/unauthorized.js} +4 -4
- package/src/response/{unprocessable.js → 4xx/unprocessable.js} +2 -2
- package/src/response/{unsupported-media.js → 4xx/unsupported-media.js} +3 -3
- package/src/response/{error.js → 5xx/error.js} +3 -3
- package/src/response/{insufficient-storage.js → 5xx/insufficient-storage.js} +2 -2
- package/src/response/{not-implemented.js → 5xx/not-implemented.js} +4 -4
- package/src/response/{unavailable.js → 5xx/unavailable.js} +4 -4
- package/src/response/header-util.js +2 -2
- package/src/response/index.js +35 -35
- package/src/response/response.js +34 -34
- package/src/response/send-util.js +11 -13
- package/src/content-type.js +0 -148
- /package/src/{cache-control.js → headers/cache-control.js} +0 -0
- /package/src/{clear-site-data.js → headers/clear-site-data.js} +0 -0
- /package/src/{fetch-metadata.js → headers/fetch-metadata.js} +0 -0
- /package/src/{link.js → headers/link.js} +0 -0
- /package/src/{rate-limit.js → headers/rate-limit.js} +0 -0
- /package/src/{quote.js → headers/util/quote.js} +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { KVP } from './util/kvp.js'
|
|
2
|
+
|
|
1
3
|
export const FORWARDED_KEY_BY = 'by'
|
|
2
4
|
export const FORWARDED_KEY_FOR = 'for'
|
|
3
5
|
export const FORWARDED_KEY_HOST = 'host'
|
|
@@ -13,11 +15,8 @@ export const KNOWN_FORWARDED_KEYS = [
|
|
|
13
15
|
export const SKIP_ANY = '*'
|
|
14
16
|
|
|
15
17
|
export const FORWARDED_SEPARATOR = {
|
|
16
|
-
ITEM: ','
|
|
17
|
-
ELEMENT: ';',
|
|
18
|
-
KVP: '='
|
|
18
|
+
ITEM: ','
|
|
19
19
|
}
|
|
20
|
-
|
|
21
20
|
export class Forwarded {
|
|
22
21
|
/**
|
|
23
22
|
* @param {string|undefined} header
|
|
@@ -30,26 +29,10 @@ export class Forwarded {
|
|
|
30
29
|
return header
|
|
31
30
|
.trim()
|
|
32
31
|
.split(FORWARDED_SEPARATOR.ITEM)
|
|
33
|
-
.map(single =>
|
|
34
|
-
|
|
35
|
-
.split(FORWARDED_SEPARATOR.ELEMENT)
|
|
36
|
-
.map(kvp => {
|
|
37
|
-
const [ rawKey, rawValue ] = kvp.trim().split(FORWARDED_SEPARATOR.KVP)
|
|
38
|
-
|
|
39
|
-
const key = rawKey?.trim()?.toLowerCase()
|
|
40
|
-
if (key === undefined || !acceptedKeys.includes(key)) { return undefined }
|
|
41
|
-
|
|
42
|
-
const value = rawValue?.trim()
|
|
43
|
-
if(value === undefined) { return undefined }
|
|
44
|
-
if(value.length <= 0) { return undefined }
|
|
45
|
-
|
|
46
|
-
/** @type {[string, string]} */
|
|
47
|
-
const result = [ key, value ]
|
|
48
|
-
return result
|
|
49
|
-
})
|
|
50
|
-
.filter(item => item !== undefined))
|
|
51
|
-
)
|
|
32
|
+
.map(single => KVP.parseParameters(single, acceptedKeys)?.parameters)
|
|
33
|
+
.filter(m => m !== undefined)
|
|
52
34
|
.filter(m => m.size > 0)
|
|
35
|
+
.filter(m => m.get(FORWARDED_KEY_FOR) !== undefined)
|
|
53
36
|
}
|
|
54
37
|
|
|
55
38
|
/**
|
|
@@ -3,18 +3,20 @@
|
|
|
3
3
|
export * from './accept.js'
|
|
4
4
|
export * from './accept-encoding.js'
|
|
5
5
|
export * from './accept-language.js'
|
|
6
|
-
export * from './accept-util.js'
|
|
7
6
|
export * from './cache-control.js'
|
|
8
7
|
export * from './clear-site-data.js'
|
|
8
|
+
export * from './client-hints.js'
|
|
9
9
|
export * from './conditional.js'
|
|
10
10
|
export * from './content-disposition.js'
|
|
11
11
|
export * from './content-range.js'
|
|
12
12
|
export * from './content-type.js'
|
|
13
|
-
export * from './forwarded.js'
|
|
14
13
|
export * from './fetch-metadata.js'
|
|
14
|
+
export * from './forwarded.js'
|
|
15
|
+
export * from './link.js'
|
|
15
16
|
export * from './multipart.js'
|
|
16
17
|
export * from './preference.js'
|
|
17
18
|
export * from './range.js'
|
|
18
19
|
export * from './rate-limit.js'
|
|
19
20
|
export * from './server-timing.js'
|
|
21
|
+
export * from './strict-transport-security.js'
|
|
20
22
|
export * from './www-authenticate.js'
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ReadableStream } from 'node:stream/web'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ContentDisposition } from './content-disposition.js'
|
|
4
4
|
import { ContentRange } from './content-range.js'
|
|
5
|
-
import {
|
|
5
|
+
import { ContentType } from './content-type.js'
|
|
6
6
|
|
|
7
7
|
/** @import { ContentRangeDirective } from './content-range.js' */
|
|
8
|
-
/** @import { SendBody } from '
|
|
8
|
+
/** @import { SendBody } from '../defs.js' */
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @typedef {Object} MultipartBytePart
|
|
@@ -94,11 +94,11 @@ export class Multipart {
|
|
|
94
94
|
const name = rawName?.toLowerCase()
|
|
95
95
|
// console.log('header', name, value)
|
|
96
96
|
if(name === MULTIPART_HEADER.CONTENT_TYPE) {
|
|
97
|
-
const _contentType =
|
|
97
|
+
const _contentType = ContentType.parse(value)
|
|
98
98
|
// console.log({ contentType })
|
|
99
99
|
}
|
|
100
100
|
else if(name === MULTIPART_HEADER.CONTENT_DISPOSITION) {
|
|
101
|
-
const disposition =
|
|
101
|
+
const disposition = ContentDisposition.parse(value)
|
|
102
102
|
if(disposition?.disposition !== DISPOSITION_FORM_DATA) {
|
|
103
103
|
throw new Error('disposition not form-data')
|
|
104
104
|
}
|
|
@@ -161,6 +161,7 @@ export class Multipart {
|
|
|
161
161
|
// controller.enqueue(encoder.encode(MULTIPART_SEPARATOR))
|
|
162
162
|
|
|
163
163
|
if(part.obj instanceof ReadableStream) {
|
|
164
|
+
// biome-ignore lint/performance/noAwaitInLoops: readable
|
|
164
165
|
for await (const chunk of part.obj) {
|
|
165
166
|
if(chunk instanceof ArrayBuffer || ArrayBuffer.isView(chunk)) {
|
|
166
167
|
controller.enqueue(chunk)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// https://datatracker.ietf.org/doc/html/rfc7240
|
|
2
2
|
// https://www.rfc-editor.org/rfc/rfc7240#section-3
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { KVP } from './util/kvp.js'
|
|
5
|
+
import { isQuoted, stripQuotes } from './util/quote.js'
|
|
5
6
|
|
|
6
7
|
export const PREFERENCE_SEPARATOR = {
|
|
7
8
|
PREFERENCE: ',',
|
|
@@ -53,26 +54,13 @@ export class Preferences {
|
|
|
53
54
|
|
|
54
55
|
const preferences = new Map(header.split(PREFERENCE_SEPARATOR.PREFERENCE)
|
|
55
56
|
.map(pref => {
|
|
56
|
-
const
|
|
57
|
+
const { name: kvp, parameters } = KVP.parse(pref) ?? { parameters: new Map() }
|
|
57
58
|
const [ key, rawValue ] = kvp?.split(PREFERENCE_SEPARATOR.KVP) ?? []
|
|
58
59
|
|
|
59
60
|
if(key === undefined) { return {} }
|
|
60
61
|
const valueOrEmpty = isQuoted(rawValue) ? stripQuotes(rawValue) : rawValue
|
|
61
62
|
const value = (valueOrEmpty !== '') ? valueOrEmpty : undefined
|
|
62
63
|
|
|
63
|
-
const parameters = new Map(params
|
|
64
|
-
.map(param => {
|
|
65
|
-
const [ pKey, rawPValue ] = param.split(PREFERENCE_SEPARATOR.PARAM_KVP)
|
|
66
|
-
if(pKey === undefined) { return {} }
|
|
67
|
-
const trimmedRawPValue = rawPValue?.trim()
|
|
68
|
-
const pValueOrEmpty = isQuoted(trimmedRawPValue) ? stripQuotes(trimmedRawPValue) : trimmedRawPValue
|
|
69
|
-
const pValue = (pValueOrEmpty !== '') ? pValueOrEmpty : undefined
|
|
70
|
-
return { key: pKey.trim(), value: pValue }
|
|
71
|
-
})
|
|
72
|
-
.filter(item => item.key !== undefined)
|
|
73
|
-
.map(item => ([ item.key, item.value ]))
|
|
74
|
-
)
|
|
75
|
-
|
|
76
64
|
return { key, value, parameters }
|
|
77
65
|
})
|
|
78
66
|
.filter(item => item.key !== undefined)
|
|
@@ -28,8 +28,8 @@ export class ServerTiming {
|
|
|
28
28
|
return timings
|
|
29
29
|
.map(({ name, duration, description }) => [
|
|
30
30
|
`${name}`,
|
|
31
|
-
description
|
|
32
|
-
duration
|
|
31
|
+
description === undefined ? undefined : `${SERVER_TIMING_KEY_DESCRIPTION}${SERVER_TIMING_SEPARATOR.KVP}"${description}"`,
|
|
32
|
+
duration === undefined ? undefined : `${SERVER_TIMING_KEY_DURATION}${SERVER_TIMING_SEPARATOR.KVP}${Math.trunc(duration * 10) / 10}`
|
|
33
33
|
]
|
|
34
34
|
.filter(item => item !== undefined)
|
|
35
35
|
.join(SERVER_TIMING_SEPARATOR.PARAMETER))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export const STS_MAX_AGE = 'max-age'
|
|
4
|
+
export const STS_INCLUDE_SUBDOMAIN = 'includeSubDomains'
|
|
5
|
+
export const STS_PRELOAD = 'preload'
|
|
6
|
+
|
|
7
|
+
export const STS_MIN_AGE_FOR_PRELOAD_SECS = 60 * 60 * 24 * 365 // 31536000
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} StrictTransportSecurityOptions
|
|
12
|
+
* @property {number} maxAge
|
|
13
|
+
* @property {boolean|undefined} [includeSubDomains]
|
|
14
|
+
* @property {boolean|undefined} [preload]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class StrictTransportSecurity {
|
|
18
|
+
/**
|
|
19
|
+
* @param {StrictTransportSecurityOptions} sts
|
|
20
|
+
*/
|
|
21
|
+
static *#encode(sts) {
|
|
22
|
+
if(!Number.isFinite(sts.maxAge)) {
|
|
23
|
+
throw new Error('invalid max-age')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const maxAge = sts.preload ? Math.max(STS_MIN_AGE_FOR_PRELOAD_SECS, sts.maxAge) : sts.maxAge
|
|
27
|
+
yield `${STS_MAX_AGE}=${maxAge}`
|
|
28
|
+
if(sts.includeSubDomains) { yield STS_INCLUDE_SUBDOMAIN }
|
|
29
|
+
if(sts.preload) { yield STS_PRELOAD }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {StrictTransportSecurityOptions} sts
|
|
34
|
+
*/
|
|
35
|
+
static encode(sts) {
|
|
36
|
+
return [ ...StrictTransportSecurity.#encode(sts) ].join('; ')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { KVP } from './kvp.js'
|
|
2
|
+
|
|
1
3
|
export const QUALITY = 'q'
|
|
2
4
|
export const SEPARATOR = {
|
|
3
|
-
MEDIA_RANGE: ','
|
|
4
|
-
PARAMETER: ';',
|
|
5
|
-
KVP: '='
|
|
5
|
+
MEDIA_RANGE: ','
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export const DEFAULT_QUALITY_STRING = '1'
|
|
@@ -29,16 +29,10 @@ export function parseAcceptStyleHeader(header, wellKnown) {
|
|
|
29
29
|
.trim()
|
|
30
30
|
.split(SEPARATOR.MEDIA_RANGE)
|
|
31
31
|
.map(mediaRange => {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const parameters = new Map(parametersSet.map(parameter => {
|
|
37
|
-
const [ key, value ] = parameter.split(SEPARATOR.KVP).map(p => p.trim())
|
|
38
|
-
return [ key, value ]
|
|
39
|
-
}))
|
|
32
|
+
const { name, parameters } = KVP.parse(mediaRange) ?? { parameters: new Map() }
|
|
33
|
+
if(name === undefined) { return undefined }
|
|
34
|
+
if(name === '') { return undefined }
|
|
40
35
|
|
|
41
|
-
if(!parameters.has(QUALITY)) { parameters.set(QUALITY, DEFAULT_QUALITY_STRING) }
|
|
42
36
|
const quality = Number.parseFloat(parameters.get(QUALITY) ?? DEFAULT_QUALITY_STRING)
|
|
43
37
|
|
|
44
38
|
return {
|
|
@@ -47,9 +41,9 @@ export function parseAcceptStyleHeader(header, wellKnown) {
|
|
|
47
41
|
parameters
|
|
48
42
|
}
|
|
49
43
|
})
|
|
50
|
-
.filter(entry => entry
|
|
44
|
+
.filter(entry => entry !== undefined)
|
|
51
45
|
.sort((entryA, entryB) => {
|
|
52
46
|
// B - A descending order
|
|
53
47
|
return entryB.quality - entryA.quality
|
|
54
48
|
})
|
|
55
|
-
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** biome-ignore-all lint/performance/noBarrelFile: entry point */
|
|
2
|
+
/** biome-ignore-all lint/performance/noReExportAll: entry point */
|
|
3
|
+
export * from './accept-util.js'
|
|
4
|
+
export * from './kvp.js'
|
|
5
|
+
export * from './mime.js'
|
|
6
|
+
export * from './quote.js'
|
|
7
|
+
export * from './whitespace.js'
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { hasSpecialChar } from './mime.js'
|
|
2
|
+
import { isQuoted, stripQuotes } from './quote.js'
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_DELIMITER = ';'
|
|
5
|
+
export const KVP_DELIMITER = '='
|
|
6
|
+
export const KVP_EMPTY = ''
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export class KVP {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Array<string>|undefined} params
|
|
12
|
+
* @param {Array<string>|undefined} [acceptableKeys=undefined]
|
|
13
|
+
*/
|
|
14
|
+
static #parse(params, acceptableKeys = undefined) {
|
|
15
|
+
const parameters = new Map()
|
|
16
|
+
|
|
17
|
+
if(params === undefined) { return parameters }
|
|
18
|
+
|
|
19
|
+
for(const kvp of params) {
|
|
20
|
+
const [ rawKey, rawValue ] = kvp
|
|
21
|
+
.split(KVP_DELIMITER)
|
|
22
|
+
.map(p => p.trim())
|
|
23
|
+
|
|
24
|
+
if(rawKey === undefined) { continue }
|
|
25
|
+
if(rawKey === KVP_EMPTY) { continue }
|
|
26
|
+
const key = rawKey.toLowerCase()
|
|
27
|
+
if(hasSpecialChar(key)) { continue }
|
|
28
|
+
|
|
29
|
+
if(acceptableKeys !== undefined && !acceptableKeys.includes(key)) { continue }
|
|
30
|
+
|
|
31
|
+
const unquotedValue = isQuoted(rawValue) ? stripQuotes(rawValue) : rawValue
|
|
32
|
+
const value = unquotedValue === KVP_EMPTY ? undefined : unquotedValue
|
|
33
|
+
|
|
34
|
+
if(!parameters.has(key)) {
|
|
35
|
+
parameters.set(key, value)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return parameters
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string|undefined} str
|
|
44
|
+
* @param {Array<string>|undefined} [acceptableKeys=undefined]
|
|
45
|
+
*/
|
|
46
|
+
static parse(str, acceptableKeys = undefined, delimiter = DEFAULT_DELIMITER) {
|
|
47
|
+
if(str === undefined) { return undefined }
|
|
48
|
+
if(str === KVP_EMPTY) { return undefined }
|
|
49
|
+
|
|
50
|
+
const [ name, ...params ] = str
|
|
51
|
+
.trim()
|
|
52
|
+
.split(delimiter)
|
|
53
|
+
.map(p => p.trim())
|
|
54
|
+
|
|
55
|
+
const parameters = KVP.#parse(params, acceptableKeys)
|
|
56
|
+
|
|
57
|
+
return { name, parameters }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string|undefined} str
|
|
62
|
+
* @param {Array<string>|undefined} [acceptableKeys=undefined]
|
|
63
|
+
*/
|
|
64
|
+
static parseParameters(str, acceptableKeys = undefined, delimiter = DEFAULT_DELIMITER) {
|
|
65
|
+
if(str === undefined) { return undefined }
|
|
66
|
+
if(str === KVP_EMPTY) { return undefined }
|
|
67
|
+
|
|
68
|
+
const params = str
|
|
69
|
+
.trim()
|
|
70
|
+
.split(delimiter)
|
|
71
|
+
.map(p => p.trim())
|
|
72
|
+
|
|
73
|
+
const parameters = KVP.#parse(params, acceptableKeys)
|
|
74
|
+
|
|
75
|
+
return { parameters }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {Object} MimeItem
|
|
4
|
+
* @property {string} mimetype
|
|
5
|
+
* @property {string} type
|
|
6
|
+
* @property {string} subtype
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const MIME_SEPARATOR = { SUBTYPE: '/' }
|
|
10
|
+
|
|
11
|
+
export const MIME_ANY = '*'
|
|
12
|
+
|
|
13
|
+
//
|
|
14
|
+
export const SPECIAL_CHARS = [
|
|
15
|
+
// special
|
|
16
|
+
'(', ')',
|
|
17
|
+
'<', '>',
|
|
18
|
+
'[', ']',
|
|
19
|
+
'{', '}',
|
|
20
|
+
'@', ',', ';', ':',
|
|
21
|
+
'\\', '"', '/', '?', '=', // '.',
|
|
22
|
+
// '%', // '!', '$', '&', // # ^ * | ~ `
|
|
23
|
+
// space
|
|
24
|
+
' ', '\u000B', '\u000C',
|
|
25
|
+
// control
|
|
26
|
+
'\n', '\r', '\t'
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string|undefined} value
|
|
31
|
+
*/
|
|
32
|
+
export function hasSpecialChar(value) {
|
|
33
|
+
if(value === undefined) { return false }
|
|
34
|
+
for(const special of SPECIAL_CHARS) {
|
|
35
|
+
if(value.includes(special)) { return true }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class Mime {
|
|
42
|
+
/**
|
|
43
|
+
* @param {string|undefined} name
|
|
44
|
+
* @returns {MimeItem|undefined}
|
|
45
|
+
*/
|
|
46
|
+
static parse(name) {
|
|
47
|
+
// console.log('mime::parse', name)
|
|
48
|
+
if(name === undefined) { return undefined }
|
|
49
|
+
if(name === '') { return undefined }
|
|
50
|
+
|
|
51
|
+
const parts = name
|
|
52
|
+
.trim() // leading space of type and trailing of subtype
|
|
53
|
+
.split(MIME_SEPARATOR.SUBTYPE)
|
|
54
|
+
.map(t => t.toLowerCase()) // all type/subtypes should be lower
|
|
55
|
+
|
|
56
|
+
// protect against multiple slashes
|
|
57
|
+
if(parts.length > 2) { return undefined }
|
|
58
|
+
|
|
59
|
+
const [ type, candidateSubtype ] = parts
|
|
60
|
+
|
|
61
|
+
if(type === undefined) { return undefined }
|
|
62
|
+
if(type === '') { return undefined }
|
|
63
|
+
if(hasSpecialChar(type)) { return undefined }
|
|
64
|
+
|
|
65
|
+
// if(candidateSubtype === undefined) { return undefined }
|
|
66
|
+
// if(candidateSubtype === '') { return undefined }
|
|
67
|
+
if(hasSpecialChar(candidateSubtype)) { return undefined }
|
|
68
|
+
|
|
69
|
+
const subtype = (candidateSubtype === '') ? MIME_ANY : candidateSubtype ?? MIME_ANY
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
mimetype: `${type}${MIME_SEPARATOR.SUBTYPE}${subtype ?? MIME_ANY}`,
|
|
73
|
+
type,
|
|
74
|
+
subtype
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -99,7 +99,7 @@ export class Challenge {
|
|
|
99
99
|
return `${key}=${value}`
|
|
100
100
|
})
|
|
101
101
|
|
|
102
|
-
const params = parameters
|
|
102
|
+
const params = parameters === undefined ? '' : [ ...parameters ].join(',')
|
|
103
103
|
|
|
104
104
|
return `${challenge.scheme} ${params}`
|
|
105
105
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { send } from '
|
|
3
|
+
import { send } from '../send-util.js'
|
|
4
4
|
|
|
5
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
-
/** @import { Metadata } from '
|
|
6
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
7
7
|
|
|
8
8
|
const { HTTP_STATUS_ACCEPTED } = http2.constants
|
|
9
9
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { send_bytes } from '
|
|
3
|
+
import { send_bytes } from '../send-util.js'
|
|
4
4
|
|
|
5
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
-
/** @import { Metadata } from '
|
|
7
|
-
/** @import { EtagItem } from '
|
|
8
|
-
/** @import { CacheControlOptions } from '
|
|
9
|
-
/** @import { SendBody } from '
|
|
6
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
8
|
+
/** @import { CacheControlOptions } from '../../headers/cache-control.js' */
|
|
9
|
+
/** @import { SendBody } from '../send-util.js' */
|
|
10
10
|
|
|
11
11
|
const { HTTP_STATUS_OK } = http2.constants
|
|
12
12
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { Conditional } from '
|
|
4
|
-
import { send } from '
|
|
3
|
+
import { Conditional } from '../../headers/conditional.js'
|
|
4
|
+
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '
|
|
8
|
-
/** @import { EtagItem } from '
|
|
7
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
8
|
+
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
HTTP2_HEADER_LOCATION,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { CONTENT_TYPE_JSON } from '
|
|
4
|
-
import { send_encoded } from '
|
|
3
|
+
import { CONTENT_TYPE_JSON } from '../../headers/content-type.js'
|
|
4
|
+
import { send_encoded } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '
|
|
8
|
-
/** @import { EtagItem } from '
|
|
9
|
-
/** @import { CacheControlOptions } from '
|
|
7
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
8
|
+
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
9
|
+
/** @import { CacheControlOptions } from '../../headers/cache-control.js' */
|
|
10
10
|
|
|
11
11
|
const { HTTP_STATUS_OK } = http2.constants
|
|
12
12
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { Conditional } from '
|
|
4
|
-
import { send } from '
|
|
3
|
+
import { Conditional } from '../../headers/conditional.js'
|
|
4
|
+
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '
|
|
8
|
-
/** @import { EtagItem } from '
|
|
7
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
8
|
+
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
HTTP2_HEADER_ETAG
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { send_bytes } from '
|
|
3
|
+
import { RANGE_UNITS_BYTES } from '../../defs.js'
|
|
4
|
+
import { MIME_TYPE_MULTIPART_RANGE } from '../../headers/content-type.js'
|
|
5
|
+
import { Multipart } from '../../headers/multipart.js'
|
|
6
|
+
import { send_bytes } from '../send-util.js'
|
|
7
7
|
|
|
8
8
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
9
|
-
/** @import { Metadata } from '
|
|
10
|
-
/** @import { EtagItem } from '
|
|
11
|
-
/** @import { CacheControlOptions } from '
|
|
12
|
-
/** @import { ContentRangeDirective } from '
|
|
13
|
-
/** @import { SendBody } from '
|
|
9
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
10
|
+
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
11
|
+
/** @import { CacheControlOptions } from '../../headers/cache-control.js' */
|
|
12
|
+
/** @import { ContentRangeDirective } from '../../headers/content-range.js' */
|
|
13
|
+
/** @import { SendBody } from '../send-util.js' */
|
|
14
14
|
|
|
15
15
|
const { HTTP_STATUS_PARTIAL_CONTENT } = http2.constants
|
|
16
16
|
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
HTTP_METHOD_QUERY,
|
|
6
6
|
HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
|
|
7
7
|
PREFLIGHT_AGE_SECONDS
|
|
8
|
-
} from '
|
|
9
|
-
import { send } from '
|
|
8
|
+
} from '../../defs.js'
|
|
9
|
+
import { send } from '../send-util.js'
|
|
10
10
|
|
|
11
11
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
12
|
-
/** @import { Metadata } from '
|
|
12
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
13
13
|
|
|
14
14
|
const {
|
|
15
15
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
@@ -35,7 +35,7 @@ const { HTTP_STATUS_OK } = http2.constants
|
|
|
35
35
|
export function sendPreflight(stream, methods, supportedQueryTypes, acceptRanges, meta) {
|
|
36
36
|
const supportsQuery = methods.includes(HTTP_METHOD_QUERY) && supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
|
|
37
37
|
const exposedHeadersAcceptQuery = supportsQuery ? [ HTTP_HEADER_ACCEPT_QUERY ] : []
|
|
38
|
-
const exposedHeaders = acceptRanges
|
|
38
|
+
const exposedHeaders = acceptRanges === undefined ? exposedHeadersAcceptQuery : [ HTTP2_HEADER_ACCEPT_RANGES, ...exposedHeadersAcceptQuery ]
|
|
39
39
|
|
|
40
40
|
send(stream, HTTP_STATUS_OK, {
|
|
41
41
|
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: methods.join(','),
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
SSE_INACTIVE_STATUS_CODE,
|
|
7
7
|
SSE_MIME,
|
|
8
8
|
} from '@johntalton/sse-util'
|
|
9
|
-
import { coreHeaders, performanceHeaders } from '
|
|
9
|
+
import { coreHeaders, performanceHeaders } from '../header-util.js'
|
|
10
10
|
|
|
11
11
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
12
|
-
/** @import { Metadata, SSEOptions } from '
|
|
12
|
+
/** @import { Metadata, SSEOptions } from '../../defs.js' */
|
|
13
13
|
|
|
14
14
|
const { HTTP_STATUS_OK } = http2.constants
|
|
15
15
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { CONTENT_TYPE_MESSAGE_HTTP } from '
|
|
4
|
-
import { send } from '
|
|
3
|
+
import { CONTENT_TYPE_MESSAGE_HTTP } from '../../headers/content-type.js'
|
|
4
|
+
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
7
|
/** @import { IncomingHttpHeaders } from 'node:http2' */
|
|
8
|
-
/** @import { Metadata } from '
|
|
8
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
9
9
|
|
|
10
10
|
const { HTTP_STATUS_OK } = http2.constants
|
|
11
11
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { send } from '
|
|
3
|
+
import { send } from '../send-util.js'
|
|
4
4
|
|
|
5
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
-
/** @import { Metadata } from '
|
|
6
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
HTTP2_HEADER_LOCATION
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
-
import { send } from '
|
|
3
|
+
import { send } from '../send-util.js'
|
|
4
4
|
|
|
5
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
-
/** @import { Metadata } from '
|
|
6
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
7
7
|
|
|
8
8
|
const { HTTP_STATUS_MULTIPLE_CHOICES } = http2.constants
|
|
9
9
|
|
|
@@ -12,7 +12,6 @@ const { HTTP_STATUS_MULTIPLE_CHOICES } = http2.constants
|
|
|
12
12
|
* @param {Metadata} meta
|
|
13
13
|
*/
|
|
14
14
|
export function sendMultipleChoices(stream, meta) {
|
|
15
|
-
throw new Error('unsupported')
|
|
16
15
|
send(stream, HTTP_STATUS_MULTIPLE_CHOICES, {
|
|
17
16
|
// Alternates:
|
|
18
17
|
// TCN: list
|