second
[josuexyz/.git] / node_modules / negotiator / lib / mediaType.js
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
16 module.exports = preferredMediaTypes;
17 module.exports.preferredMediaTypes = preferredMediaTypes;
18
19 /**
20  * Module variables.
21  * @private
22  */
23
24 var simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
25
26 /**
27  * Parse the Accept header.
28  * @private
29  */
30
31 function 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
53 function 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
99 function 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
118 function 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
162 function 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
189 function 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
198 function getFullType(spec) {
199   return spec.type + '/' + spec.subtype;
200 }
201
202 /**
203  * Check if a spec has any quality.
204  * @private
205  */
206
207 function isQuality(spec) {
208   return spec.q > 0;
209 }
210
211 /**
212  * Count the number of quotes in a string.
213  * @private
214  */
215
216 function 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
233 function 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
253 function 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
275 function 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 }