1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | 'use strict';
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | module.exports = preferredMediaTypes;
|
17 | module.exports.preferredMediaTypes = preferredMediaTypes;
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | var simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
|
25 |
|
26 |
|
27 |
|
28 |
|
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 |
|
43 | accepts.length = j;
|
44 |
|
45 | return accepts;
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 |
|
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 |
|
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 |
|
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 |
|
96 |
|
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 |
|
115 |
|
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 |
|
159 |
|
160 |
|
161 |
|
162 | function preferredMediaTypes(accept, provided) {
|
163 |
|
164 | var accepts = parseAccept(accept === undefined ? '*/*' : accept || '');
|
165 |
|
166 | if (!provided) {
|
167 |
|
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 |
|
179 | return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) {
|
180 | return provided[priorities.indexOf(priority)];
|
181 | });
|
182 | }
|
183 |
|
184 |
|
185 |
|
186 |
|
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 |
|
195 |
|
196 |
|
197 |
|
198 | function getFullType(spec) {
|
199 | return spec.type + '/' + spec.subtype;
|
200 | }
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 | function isQuality(spec) {
|
208 | return spec.q > 0;
|
209 | }
|
210 |
|
211 |
|
212 |
|
213 |
|
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 |
|
230 |
|
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 |
|
250 |
|
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 |
|
265 | accepts.length = j + 1;
|
266 |
|
267 | return accepts;
|
268 | }
|
269 |
|
270 |
|
271 |
|
272 |
|
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 |
|
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 | }
|