3 * Copyright(c) 2012 Isaac Z. Schlueter
4 * Copyright(c) 2014 Federico Romero
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
16 module.exports = preferredMediaTypes;
17 module.exports.preferredMediaTypes = preferredMediaTypes;
24 var simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
27 * Parse the Accept header.
31 function parseAccept(accept) {
32 var accepts = splitMediaTypes(accept);
34 for (var i = 0, j = 0; i < accepts.length; i++) {
35 var mediaType = parseMediaType(accepts[i].trim(), i);
38 accepts[j++] = mediaType;
49 * Parse a media type from the Accept header.
53 function parseMediaType(str, i) {
54 var match = simpleMediaTypeRegExp.exec(str);
55 if (!match) return null;
57 var params = Object.create(null);
59 var subtype = match[2];
63 var kvps = splitParameters(match[3]).map(splitKeyValuePair);
65 for (var j = 0; j < kvps.length; j++) {
67 var key = pair[0].toLowerCase();
70 // get the value, unwrapping quotes
71 var value = val && val[0] === '"' && val[val.length - 1] === '"'
72 ? val.substr(1, val.length - 2)
76 q = parseFloat(value);
95 * Get the priority of a media type.
99 function getMediaTypePriority(type, accepted, index) {
100 var priority = {o: -1, q: 0, s: 0};
102 for (var i = 0; i < accepted.length; i++) {
103 var spec = specify(type, accepted[i], index);
105 if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
114 * Get the specificity of the media type.
118 function specify(type, spec, index) {
119 var p = parseMediaType(type);
126 if(spec.type.toLowerCase() == p.type.toLowerCase()) {
128 } else if(spec.type != '*') {
132 if(spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {
134 } else if(spec.subtype != '*') {
138 var keys = Object.keys(spec.params);
139 if (keys.length > 0) {
140 if (keys.every(function (k) {
141 return spec.params[k] == '*' || (spec.params[k] || '').toLowerCase() == (p.params[k] || '').toLowerCase();
158 * Get the preferred media types from an Accept header.
162 function preferredMediaTypes(accept, provided) {
163 // RFC 2616 sec 14.2: no header = */*
164 var accepts = parseAccept(accept === undefined ? '*/*' : accept || '');
167 // sorted list of all types
174 var priorities = provided.map(function getPriority(type, index) {
175 return getMediaTypePriority(type, accepts, index);
178 // sorted list of accepted types
179 return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) {
180 return provided[priorities.indexOf(priority)];
189 function compareSpecs(a, b) {
190 return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
194 * Get full type string.
198 function getFullType(spec) {
199 return spec.type + '/' + spec.subtype;
203 * Check if a spec has any quality.
207 function isQuality(spec) {
212 * Count the number of quotes in a string.
216 function quoteCount(string) {
220 while ((index = string.indexOf('"', index)) !== -1) {
229 * Split a key value pair.
233 function splitKeyValuePair(str) {
234 var index = str.indexOf('=');
241 key = str.substr(0, index);
242 val = str.substr(index + 1);
249 * Split an Accept header into media types.
253 function splitMediaTypes(accept) {
254 var accepts = accept.split(',');
256 for (var i = 1, j = 0; i < accepts.length; i++) {
257 if (quoteCount(accepts[j]) % 2 == 0) {
258 accepts[++j] = accepts[i];
260 accepts[j] += ',' + accepts[i];
265 accepts.length = j + 1;
271 * Split a string of parameters.
275 function splitParameters(str) {
276 var parameters = str.split(';');
278 for (var i = 1, j = 0; i < parameters.length; i++) {
279 if (quoteCount(parameters[j]) % 2 == 0) {
280 parameters[++j] = parameters[i];
282 parameters[j] += ';' + parameters[i];
287 parameters.length = j + 1;
289 for (var i = 0; i < parameters.length; i++) {
290 parameters[i] = parameters[i].trim();