117 lines
3.0 KiB
JavaScript
117 lines
3.0 KiB
JavaScript
'use strict'
|
|
|
|
const LRUCache = require('mnemonist/lru-cache')
|
|
|
|
/**
|
|
* Field Value Components
|
|
* Most HTTP header field values are defined using common syntax
|
|
* components (token, quoted-string, and comment) separated by
|
|
* whitespace or specific delimiting characters. Delimiters are chosen
|
|
* from the set of US-ASCII visual characters not allowed in a token
|
|
* (DQUOTE and "(),/:;<=>?@[\]{}").
|
|
*
|
|
* field-name = token
|
|
* token = 1*tchar
|
|
* tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
|
* / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
|
* / DIGIT / ALPHA
|
|
* ; any VCHAR, except delimiters
|
|
*
|
|
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
|
*/
|
|
|
|
const validFieldnameRE = /^[!#$%&'*+\-.^\w`|~]+$/u
|
|
function validateFieldname (fieldname) {
|
|
if (validFieldnameRE.test(fieldname) === false) {
|
|
throw new TypeError('Fieldname contains invalid characters.')
|
|
}
|
|
}
|
|
|
|
function parse (header) {
|
|
header = header.trim().toLowerCase()
|
|
const result = []
|
|
|
|
if (header.length === 0) {
|
|
// pass through
|
|
} else if (header.indexOf(',') === -1) {
|
|
result.push(header)
|
|
} else {
|
|
const il = header.length
|
|
let i = 0
|
|
let pos = 0
|
|
let char
|
|
|
|
// tokenize the header
|
|
for (i = 0; i < il; ++i) {
|
|
char = header[i]
|
|
// when we have whitespace set the pos to the next position
|
|
if (char === ' ') {
|
|
pos = i + 1
|
|
// `,` is the separator of vary-values
|
|
} else if (char === ',') {
|
|
// if pos and current position are not the same we have a valid token
|
|
if (pos !== i) {
|
|
result.push(header.slice(pos, i))
|
|
}
|
|
// reset the positions
|
|
pos = i + 1
|
|
}
|
|
}
|
|
|
|
if (pos !== i) {
|
|
result.push(header.slice(pos, i))
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function createAddFieldnameToVary (fieldname) {
|
|
const headerCache = new LRUCache(1000)
|
|
|
|
validateFieldname(fieldname)
|
|
|
|
return function (reply) {
|
|
let header = reply.getHeader('Vary')
|
|
|
|
if (!header) {
|
|
reply.header('Vary', fieldname)
|
|
return
|
|
}
|
|
|
|
if (header === '*') {
|
|
return
|
|
}
|
|
|
|
if (fieldname === '*') {
|
|
reply.header('Vary', '*')
|
|
return
|
|
}
|
|
|
|
if (Array.isArray(header)) {
|
|
header = header.join(', ')
|
|
}
|
|
|
|
if (!headerCache.has(header)) {
|
|
const vals = parse(header)
|
|
|
|
if (vals.indexOf('*') !== -1) {
|
|
headerCache.set(header, '*')
|
|
} else if (vals.indexOf(fieldname.toLowerCase()) === -1) {
|
|
headerCache.set(header, header + ', ' + fieldname)
|
|
} else {
|
|
headerCache.set(header, null)
|
|
}
|
|
}
|
|
const cached = headerCache.get(header)
|
|
if (cached !== null) {
|
|
reply.header('Vary', cached)
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports.createAddFieldnameToVary = createAddFieldnameToVary
|
|
module.exports.addOriginToVaryHeader = createAddFieldnameToVary('Origin')
|
|
module.exports.addAccessControlRequestHeadersToVaryHeader = createAddFieldnameToVary('Access-Control-Request-Headers')
|
|
module.exports.parse = parse
|