twitst4tz

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

index.js (6375B)


      1 /*!
      2  * media-typer
      3  * Copyright(c) 2014 Douglas Christopher Wilson
      4  * MIT Licensed
      5  */
      6 
      7 /**
      8  * RegExp to match *( ";" parameter ) in RFC 2616 sec 3.7
      9  *
     10  * parameter     = token "=" ( token | quoted-string )
     11  * token         = 1*<any CHAR except CTLs or separators>
     12  * separators    = "(" | ")" | "<" | ">" | "@"
     13  *               | "," | ";" | ":" | "\" | <">
     14  *               | "/" | "[" | "]" | "?" | "="
     15  *               | "{" | "}" | SP | HT
     16  * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
     17  * qdtext        = <any TEXT except <">>
     18  * quoted-pair   = "\" CHAR
     19  * CHAR          = <any US-ASCII character (octets 0 - 127)>
     20  * TEXT          = <any OCTET except CTLs, but including LWS>
     21  * LWS           = [CRLF] 1*( SP | HT )
     22  * CRLF          = CR LF
     23  * CR            = <US-ASCII CR, carriage return (13)>
     24  * LF            = <US-ASCII LF, linefeed (10)>
     25  * SP            = <US-ASCII SP, space (32)>
     26  * SHT           = <US-ASCII HT, horizontal-tab (9)>
     27  * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
     28  * OCTET         = <any 8-bit sequence of data>
     29  */
     30 var paramRegExp = /; *([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *= *("(?:[ !\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u0020-\u007e])*"|[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) */g;
     31 var textRegExp = /^[\u0020-\u007e\u0080-\u00ff]+$/
     32 var tokenRegExp = /^[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+$/
     33 
     34 /**
     35  * RegExp to match quoted-pair in RFC 2616
     36  *
     37  * quoted-pair = "\" CHAR
     38  * CHAR        = <any US-ASCII character (octets 0 - 127)>
     39  */
     40 var qescRegExp = /\\([\u0000-\u007f])/g;
     41 
     42 /**
     43  * RegExp to match chars that must be quoted-pair in RFC 2616
     44  */
     45 var quoteRegExp = /([\\"])/g;
     46 
     47 /**
     48  * RegExp to match type in RFC 6838
     49  *
     50  * type-name = restricted-name
     51  * subtype-name = restricted-name
     52  * restricted-name = restricted-name-first *126restricted-name-chars
     53  * restricted-name-first  = ALPHA / DIGIT
     54  * restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
     55  *                          "$" / "&" / "-" / "^" / "_"
     56  * restricted-name-chars =/ "." ; Characters before first dot always
     57  *                              ; specify a facet name
     58  * restricted-name-chars =/ "+" ; Characters after last plus always
     59  *                              ; specify a structured syntax suffix
     60  * ALPHA =  %x41-5A / %x61-7A   ; A-Z / a-z
     61  * DIGIT =  %x30-39             ; 0-9
     62  */
     63 var subtypeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$/
     64 var typeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}$/
     65 var typeRegExp = /^ *([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}) *$/;
     66 
     67 /**
     68  * Module exports.
     69  */
     70 
     71 exports.format = format
     72 exports.parse = parse
     73 
     74 /**
     75  * Format object to media type.
     76  *
     77  * @param {object} obj
     78  * @return {string}
     79  * @api public
     80  */
     81 
     82 function format(obj) {
     83   if (!obj || typeof obj !== 'object') {
     84     throw new TypeError('argument obj is required')
     85   }
     86 
     87   var parameters = obj.parameters
     88   var subtype = obj.subtype
     89   var suffix = obj.suffix
     90   var type = obj.type
     91 
     92   if (!type || !typeNameRegExp.test(type)) {
     93     throw new TypeError('invalid type')
     94   }
     95 
     96   if (!subtype || !subtypeNameRegExp.test(subtype)) {
     97     throw new TypeError('invalid subtype')
     98   }
     99 
    100   // format as type/subtype
    101   var string = type + '/' + subtype
    102 
    103   // append +suffix
    104   if (suffix) {
    105     if (!typeNameRegExp.test(suffix)) {
    106       throw new TypeError('invalid suffix')
    107     }
    108 
    109     string += '+' + suffix
    110   }
    111 
    112   // append parameters
    113   if (parameters && typeof parameters === 'object') {
    114     var param
    115     var params = Object.keys(parameters).sort()
    116 
    117     for (var i = 0; i < params.length; i++) {
    118       param = params[i]
    119 
    120       if (!tokenRegExp.test(param)) {
    121         throw new TypeError('invalid parameter name')
    122       }
    123 
    124       string += '; ' + param + '=' + qstring(parameters[param])
    125     }
    126   }
    127 
    128   return string
    129 }
    130 
    131 /**
    132  * Parse media type to object.
    133  *
    134  * @param {string|object} string
    135  * @return {Object}
    136  * @api public
    137  */
    138 
    139 function parse(string) {
    140   if (!string) {
    141     throw new TypeError('argument string is required')
    142   }
    143 
    144   // support req/res-like objects as argument
    145   if (typeof string === 'object') {
    146     string = getcontenttype(string)
    147   }
    148 
    149   if (typeof string !== 'string') {
    150     throw new TypeError('argument string is required to be a string')
    151   }
    152 
    153   var index = string.indexOf(';')
    154   var type = index !== -1
    155     ? string.substr(0, index)
    156     : string
    157 
    158   var key
    159   var match
    160   var obj = splitType(type)
    161   var params = {}
    162   var value
    163 
    164   paramRegExp.lastIndex = index
    165 
    166   while (match = paramRegExp.exec(string)) {
    167     if (match.index !== index) {
    168       throw new TypeError('invalid parameter format')
    169     }
    170 
    171     index += match[0].length
    172     key = match[1].toLowerCase()
    173     value = match[2]
    174 
    175     if (value[0] === '"') {
    176       // remove quotes and escapes
    177       value = value
    178         .substr(1, value.length - 2)
    179         .replace(qescRegExp, '$1')
    180     }
    181 
    182     params[key] = value
    183   }
    184 
    185   if (index !== -1 && index !== string.length) {
    186     throw new TypeError('invalid parameter format')
    187   }
    188 
    189   obj.parameters = params
    190 
    191   return obj
    192 }
    193 
    194 /**
    195  * Get content-type from req/res objects.
    196  *
    197  * @param {object}
    198  * @return {Object}
    199  * @api private
    200  */
    201 
    202 function getcontenttype(obj) {
    203   if (typeof obj.getHeader === 'function') {
    204     // res-like
    205     return obj.getHeader('content-type')
    206   }
    207 
    208   if (typeof obj.headers === 'object') {
    209     // req-like
    210     return obj.headers && obj.headers['content-type']
    211   }
    212 }
    213 
    214 /**
    215  * Quote a string if necessary.
    216  *
    217  * @param {string} val
    218  * @return {string}
    219  * @api private
    220  */
    221 
    222 function qstring(val) {
    223   var str = String(val)
    224 
    225   // no need to quote tokens
    226   if (tokenRegExp.test(str)) {
    227     return str
    228   }
    229 
    230   if (str.length > 0 && !textRegExp.test(str)) {
    231     throw new TypeError('invalid parameter value')
    232   }
    233 
    234   return '"' + str.replace(quoteRegExp, '\\$1') + '"'
    235 }
    236 
    237 /**
    238  * Simply "type/subtype+siffx" into parts.
    239  *
    240  * @param {string} string
    241  * @return {Object}
    242  * @api private
    243  */
    244 
    245 function splitType(string) {
    246   var match = typeRegExp.exec(string.toLowerCase())
    247 
    248   if (!match) {
    249     throw new TypeError('invalid media type')
    250   }
    251 
    252   var type = match[1]
    253   var subtype = match[2]
    254   var suffix
    255 
    256   // suffix after last +
    257   var index = subtype.lastIndexOf('+')
    258   if (index !== -1) {
    259     suffix = subtype.substr(index + 1)
    260     subtype = subtype.substr(0, index)
    261   }
    262 
    263   var obj = {
    264     type: type,
    265     subtype: subtype,
    266     suffix: suffix
    267   }
    268 
    269   return obj
    270 }