twitst4tz

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

index.js (4809B)


      1 /*!
      2  * content-type
      3  * Copyright(c) 2015 Douglas Christopher Wilson
      4  * MIT Licensed
      5  */
      6 
      7 'use strict'
      8 
      9 /**
     10  * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1
     11  *
     12  * parameter     = token "=" ( token / quoted-string )
     13  * token         = 1*tchar
     14  * tchar         = "!" / "#" / "$" / "%" / "&" / "'" / "*"
     15  *               / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
     16  *               / DIGIT / ALPHA
     17  *               ; any VCHAR, except delimiters
     18  * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
     19  * qdtext        = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
     20  * obs-text      = %x80-FF
     21  * quoted-pair   = "\" ( HTAB / SP / VCHAR / obs-text )
     22  */
     23 var PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g
     24 var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/
     25 var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/
     26 
     27 /**
     28  * RegExp to match quoted-pair in RFC 7230 sec 3.2.6
     29  *
     30  * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
     31  * obs-text    = %x80-FF
     32  */
     33 var QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g
     34 
     35 /**
     36  * RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6
     37  */
     38 var QUOTE_REGEXP = /([\\"])/g
     39 
     40 /**
     41  * RegExp to match type in RFC 7231 sec 3.1.1.1
     42  *
     43  * media-type = type "/" subtype
     44  * type       = token
     45  * subtype    = token
     46  */
     47 var TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/
     48 
     49 /**
     50  * Module exports.
     51  * @public
     52  */
     53 
     54 exports.format = format
     55 exports.parse = parse
     56 
     57 /**
     58  * Format object to media type.
     59  *
     60  * @param {object} obj
     61  * @return {string}
     62  * @public
     63  */
     64 
     65 function format (obj) {
     66   if (!obj || typeof obj !== 'object') {
     67     throw new TypeError('argument obj is required')
     68   }
     69 
     70   var parameters = obj.parameters
     71   var type = obj.type
     72 
     73   if (!type || !TYPE_REGEXP.test(type)) {
     74     throw new TypeError('invalid type')
     75   }
     76 
     77   var string = type
     78 
     79   // append parameters
     80   if (parameters && typeof parameters === 'object') {
     81     var param
     82     var params = Object.keys(parameters).sort()
     83 
     84     for (var i = 0; i < params.length; i++) {
     85       param = params[i]
     86 
     87       if (!TOKEN_REGEXP.test(param)) {
     88         throw new TypeError('invalid parameter name')
     89       }
     90 
     91       string += '; ' + param + '=' + qstring(parameters[param])
     92     }
     93   }
     94 
     95   return string
     96 }
     97 
     98 /**
     99  * Parse media type to object.
    100  *
    101  * @param {string|object} string
    102  * @return {Object}
    103  * @public
    104  */
    105 
    106 function parse (string) {
    107   if (!string) {
    108     throw new TypeError('argument string is required')
    109   }
    110 
    111   // support req/res-like objects as argument
    112   var header = typeof string === 'object'
    113     ? getcontenttype(string)
    114     : string
    115 
    116   if (typeof header !== 'string') {
    117     throw new TypeError('argument string is required to be a string')
    118   }
    119 
    120   var index = header.indexOf(';')
    121   var type = index !== -1
    122     ? header.substr(0, index).trim()
    123     : header.trim()
    124 
    125   if (!TYPE_REGEXP.test(type)) {
    126     throw new TypeError('invalid media type')
    127   }
    128 
    129   var obj = new ContentType(type.toLowerCase())
    130 
    131   // parse parameters
    132   if (index !== -1) {
    133     var key
    134     var match
    135     var value
    136 
    137     PARAM_REGEXP.lastIndex = index
    138 
    139     while ((match = PARAM_REGEXP.exec(header))) {
    140       if (match.index !== index) {
    141         throw new TypeError('invalid parameter format')
    142       }
    143 
    144       index += match[0].length
    145       key = match[1].toLowerCase()
    146       value = match[2]
    147 
    148       if (value[0] === '"') {
    149         // remove quotes and escapes
    150         value = value
    151           .substr(1, value.length - 2)
    152           .replace(QESC_REGEXP, '$1')
    153       }
    154 
    155       obj.parameters[key] = value
    156     }
    157 
    158     if (index !== header.length) {
    159       throw new TypeError('invalid parameter format')
    160     }
    161   }
    162 
    163   return obj
    164 }
    165 
    166 /**
    167  * Get content-type from req/res objects.
    168  *
    169  * @param {object}
    170  * @return {Object}
    171  * @private
    172  */
    173 
    174 function getcontenttype (obj) {
    175   var header
    176 
    177   if (typeof obj.getHeader === 'function') {
    178     // res-like
    179     header = obj.getHeader('content-type')
    180   } else if (typeof obj.headers === 'object') {
    181     // req-like
    182     header = obj.headers && obj.headers['content-type']
    183   }
    184 
    185   if (typeof header !== 'string') {
    186     throw new TypeError('content-type header is missing from object')
    187   }
    188 
    189   return header
    190 }
    191 
    192 /**
    193  * Quote a string if necessary.
    194  *
    195  * @param {string} val
    196  * @return {string}
    197  * @private
    198  */
    199 
    200 function qstring (val) {
    201   var str = String(val)
    202 
    203   // no need to quote tokens
    204   if (TOKEN_REGEXP.test(str)) {
    205     return str
    206   }
    207 
    208   if (str.length > 0 && !TEXT_REGEXP.test(str)) {
    209     throw new TypeError('invalid parameter value')
    210   }
    211 
    212   return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
    213 }
    214 
    215 /**
    216  * Class to represent a content type.
    217  * @private
    218  */
    219 function ContentType (type) {
    220   this.parameters = Object.create(null)
    221   this.type = type
    222 }