@goa/negotiator
Version:
[fork] HTTP Content Negotiation In ES6 Optimised With Google Closure Compiler.
267 lines (210 loc) • 4.88 kB
JavaScript
const simpleMediaTypeRegExp = /^\s*([^s/;]+)\/([^;\s]+)\s*(?:;(.*))?$/
/**
* Parse the Accept header.
* @private
*/
function parseAccept(accept) {
var accepts = splitMediaTypes(accept)
for (var i = 0, j = 0; i < accepts.length; i++) {
var mediaType = parseMediaType(accepts[i].trim(), i)
if (mediaType) {
accepts[j++] = mediaType
}
}
// trim accepts
accepts.length = j
return accepts
}
/**
* Parse a media type from the Accept header.
* @private
*/
function parseMediaType(str, i) {
var match = simpleMediaTypeRegExp.exec(str)
if (!match) return null
var params = Object.create(null)
var q = 1
var subtype = match[2]
var type = match[1]
if (match[3]) {
var kvps = splitParameters(match[3]).map(splitKeyValuePair)
for (var j = 0; j < kvps.length; j++) {
var pair = kvps[j]
var key = pair[0].toLowerCase()
var val = pair[1]
// get the value, unwrapping quotes
var value = val && val[0] === '"' && val[val.length - 1] === '"'
? val.substr(1, val.length - 2)
: val
if (key === 'q') {
q = parseFloat(value)
break
}
// store parameter
params[key] = value
}
}
return {
type: type,
subtype: subtype,
params: params,
q: q,
i: i,
}
}
/**
* Get the priority of a media type.
* @private
*/
function getMediaTypePriority(type, accepted, index) {
var priority = { o: -1, q: 0, s: 0 }
for (var i = 0; i < accepted.length; i++) {
var spec = specify(type, accepted[i], index)
if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
priority = spec
}
}
return priority
}
/**
* Get the specificity of the media type.
* @private
*/
function specify(type, spec, index) {
var p = parseMediaType(type, undefined)
var s = 0
if (!p) {
return null
}
if(spec.type.toLowerCase() == p.type.toLowerCase()) {
s |= 4
} else if(spec.type != '*') {
return null
}
if(spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {
s |= 2
} else if(spec.subtype != '*') {
return null
}
var keys = Object.keys(spec.params)
if (keys.length > 0) {
if (keys.every(function (k) {
return spec.params[k] == '*' || (spec.params[k] || '').toLowerCase() == (p.params[k] || '').toLowerCase()
})) {
s |= 1
} else {
return null
}
}
return {
i: index,
o: spec.i,
q: spec.q,
s: s,
}
}
export default function preferredMediaTypes(accept, provided) {
// RFC 2616 sec 14.2: no header = */*
var accepts = parseAccept(accept === undefined ? '*/*' : accept || '')
if (!provided) {
// sorted list of all types
return accepts
.filter(isQuality)
.sort(compareSpecs)
.map(getFullType)
}
var priorities = provided.map(function getPriority(type, index) {
return getMediaTypePriority(type, accepts, index)
})
// sorted list of accepted types
return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) {
return provided[priorities.indexOf(priority)]
})
}
/**
* Compare two specs.
* @private
*/
function compareSpecs(a, b) {
return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0
}
/**
* Get full type string.
* @private
*/
function getFullType(spec) {
return spec.type + '/' + spec.subtype
}
/**
* Check if a spec has any quality.
* @private
*/
function isQuality(spec) {
return spec.q > 0
}
/**
* Count the number of quotes in a string.
* @private
*/
function quoteCount(string) {
var count = 0
var index = 0
while ((index = string.indexOf('"', index)) !== -1) {
count++
index++
}
return count
}
/**
* Split a key value pair.
* @private
*/
function splitKeyValuePair(str) {
var index = str.indexOf('=')
var key
var val
if (index === -1) {
key = str
} else {
key = str.substr(0, index)
val = str.substr(index + 1)
}
return [key, val]
}
/**
* Split an Accept header into media types.
* @private
*/
function splitMediaTypes(accept) {
var accepts = accept.split(',')
for (var i = 1, j = 0; i < accepts.length; i++) {
if (quoteCount(accepts[j]) % 2 == 0) {
accepts[++j] = accepts[i]
} else {
accepts[j] += ',' + accepts[i]
}
}
// trim accepts
accepts.length = j + 1
return accepts
}
/**
* Split a string of parameters.
* @private
*/
function splitParameters(str) {
var parameters = str.split(';')
for (var i = 1, j = 0; i < parameters.length; i++) {
if (quoteCount(parameters[j]) % 2 == 0) {
parameters[++j] = parameters[i]
} else {
parameters[j] += ';' + parameters[i]
}
}
// trim parameters
parameters.length = j + 1
for (var i = 0; i < parameters.length; i++) {
parameters[i] = parameters[i].trim()
}
return parameters
}