mediaType.js (5358B)
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 }