twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

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 }