UNPKG

5.36 kBJavaScriptView Raw
1/**
2 * negotiator
3 * Copyright(c) 2012 Isaac Z. Schlueter
4 * Copyright(c) 2014 Federico Romero
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
6 * MIT Licensed
7 */
8
9'use strict';
10
11/**
12 * Module exports.
13 * @public
14 */
15
16module.exports = preferredMediaTypes;
17module.exports.preferredMediaTypes = preferredMediaTypes;
18
19/**
20 * Module variables.
21 * @private
22 */
23
24var simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
25
26/**
27 * Parse the Accept header.
28 * @private
29 */
30
31function parseAccept(accept) {
32 var accepts = splitMediaTypes(accept);
33
34 for (var i = 0, j = 0; i < accepts.length; i++) {
35 var mediaType = parseMediaType(accepts[i].trim(), i);
36
37 if (mediaType) {
38 accepts[j++] = mediaType;
39 }
40 }
41
42 // trim accepts
43 accepts.length = j;
44
45 return accepts;
46}
47
48/**
49 * Parse a media type from the Accept header.
50 * @private
51 */
52
53function parseMediaType(str, i) {
54 var match = simpleMediaTypeRegExp.exec(str);
55 if (!match) return null;
56
57 var params = Object.create(null);
58 var q = 1;
59 var subtype = match[2];
60 var type = match[1];
61
62 if (match[3]) {
63 var kvps = splitParameters(match[3]).map(splitKeyValuePair);
64
65 for (var j = 0; j < kvps.length; j++) {
66 var pair = kvps[j];
67 var key = pair[0].toLowerCase();
68 var val = pair[1];
69
70 // get the value, unwrapping quotes
71 var value = val && val[0] === '"' && val[val.length - 1] === '"'
72 ? val.substr(1, val.length - 2)
73 : val;
74
75 if (key === 'q') {
76 q = parseFloat(value);
77 break;
78 }
79
80 // store parameter
81 params[key] = value;
82 }
83 }
84
85 return {
86 type: type,
87 subtype: subtype,
88 params: params,
89 q: q,
90 i: i
91 };
92}
93
94/**
95 * Get the priority of a media type.
96 * @private
97 */
98
99function getMediaTypePriority(type, accepted, index) {
100 var priority = {o: -1, q: 0, s: 0};
101
102 for (var i = 0; i < accepted.length; i++) {
103 var spec = specify(type, accepted[i], index);
104
105 if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
106 priority = spec;
107 }
108 }
109
110 return priority;
111}
112
113/**
114 * Get the specificity of the media type.
115 * @private
116 */
117
118function specify(type, spec, index) {
119 var p = parseMediaType(type);
120 var s = 0;
121
122 if (!p) {
123 return null;
124 }
125
126 if(spec.type.toLowerCase() == p.type.toLowerCase()) {
127 s |= 4
128 } else if(spec.type != '*') {
129 return null;
130 }
131
132 if(spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {
133 s |= 2
134 } else if(spec.subtype != '*') {
135 return null;
136 }
137
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();
142 })) {
143 s |= 1
144 } else {
145 return null
146 }
147 }
148
149 return {
150 i: index,
151 o: spec.i,
152 q: spec.q,
153 s: s,
154 }
155}
156
157/**
158 * Get the preferred media types from an Accept header.
159 * @public
160 */
161
162function preferredMediaTypes(accept, provided) {
163 // RFC 2616 sec 14.2: no header = */*
164 var accepts = parseAccept(accept === undefined ? '*/*' : accept || '');
165
166 if (!provided) {
167 // sorted list of all types
168 return accepts
169 .filter(isQuality)
170 .sort(compareSpecs)
171 .map(getFullType);
172 }
173
174 var priorities = provided.map(function getPriority(type, index) {
175 return getMediaTypePriority(type, accepts, index);
176 });
177
178 // sorted list of accepted types
179 return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) {
180 return provided[priorities.indexOf(priority)];
181 });
182}
183
184/**
185 * Compare two specs.
186 * @private
187 */
188
189function compareSpecs(a, b) {
190 return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
191}
192
193/**
194 * Get full type string.
195 * @private
196 */
197
198function getFullType(spec) {
199 return spec.type + '/' + spec.subtype;
200}
201
202/**
203 * Check if a spec has any quality.
204 * @private
205 */
206
207function isQuality(spec) {
208 return spec.q > 0;
209}
210
211/**
212 * Count the number of quotes in a string.
213 * @private
214 */
215
216function quoteCount(string) {
217 var count = 0;
218 var index = 0;
219
220 while ((index = string.indexOf('"', index)) !== -1) {
221 count++;
222 index++;
223 }
224
225 return count;
226}
227
228/**
229 * Split a key value pair.
230 * @private
231 */
232
233function splitKeyValuePair(str) {
234 var index = str.indexOf('=');
235 var key;
236 var val;
237
238 if (index === -1) {
239 key = str;
240 } else {
241 key = str.substr(0, index);
242 val = str.substr(index + 1);
243 }
244
245 return [key, val];
246}
247
248/**
249 * Split an Accept header into media types.
250 * @private
251 */
252
253function splitMediaTypes(accept) {
254 var accepts = accept.split(',');
255
256 for (var i = 1, j = 0; i < accepts.length; i++) {
257 if (quoteCount(accepts[j]) % 2 == 0) {
258 accepts[++j] = accepts[i];
259 } else {
260 accepts[j] += ',' + accepts[i];
261 }
262 }
263
264 // trim accepts
265 accepts.length = j + 1;
266
267 return accepts;
268}
269
270/**
271 * Split a string of parameters.
272 * @private
273 */
274
275function splitParameters(str) {
276 var parameters = str.split(';');
277
278 for (var i = 1, j = 0; i < parameters.length; i++) {
279 if (quoteCount(parameters[j]) % 2 == 0) {
280 parameters[++j] = parameters[i];
281 } else {
282 parameters[j] += ';' + parameters[i];
283 }
284 }
285
286 // trim parameters
287 parameters.length = j + 1;
288
289 for (var i = 0; i < parameters.length; i++) {
290 parameters[i] = parameters[i].trim();
291 }
292
293 return parameters;
294}