text: /*!
   * content-type
   * Copyright(c) 2015 Douglas Christopher Wilson
   * MIT Licensed
   */
  
  'use strict'
  
  /**
   * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1
   *
   * parameter     = token "=" ( token / quoted-string )
   * token         = 1*tchar
   * tchar         = "!" / "#" / "$" / "%" / "&" / "'" / "*"
   *               / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
   *               / DIGIT / ALPHA
   *               ; any VCHAR, except delimiters
   * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
   * qdtext        = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
   * obs-text      = %x80-FF
   * quoted-pair   = "\" ( HTAB / SP / VCHAR / obs-text )
   */
  var paramRegExp = /; *([!#$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+) */g
  var textRegExp = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/
  var tokenRegExp = /^[!#$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+$/
  
  /**
   * RegExp to match quoted-pair in RFC 7230 sec 3.2.6
   *
   * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
   * obs-text    = %x80-FF
   */
  var qescRegExp = /\\([\u000b\u0020-\u00ff])/g
  
  /**
   * RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6
   */
  var quoteRegExp = /([\\"])/g
  
  /**
   * RegExp to match type in RFC 6838
   *
   * media-type = type "/" subtype
   * type       = token
   * subtype    = token
   */
  var typeRegExp = /^[!#$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+\/[!#$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+$/
  
  /**
   * Module exports.
   * @public
   */
  
  exports.format = format
  exports.parse = parse
  
  /**
   * Format object to media type.
   *
   * @param {object} obj
   * @return {string}
   * @public
   */
  
  function format(obj) {
      if (!obj || typeof obj !== 'object') {
          throw new TypeError('argument obj is required')
      }
  
      var parameters = obj.parameters
      var type = obj.type
  
      if (!type || !typeRegExp.test(type)) {
          throw new TypeError('invalid type')
      }
  
      var string = type
  
      // append parameters
      if (parameters && typeof parameters === 'object') {
          var param
          var params = Object.keys(parameters).sort()
  
          for (var i = 0; i < params.length; i++) {
              param = params[i]
  
              if (!tokenRegExp.test(param)) {
                  throw new TypeError('invalid parameter name')
              }
  
              string += '; ' + param + '=' + qstring(parameters[param])
          }
      }
  
      return string
  }
  
  /**
   * Parse media type to object.
   *
   * @param {string|object} string
   * @return {Object}
   * @public
   */
  
  function parse(string) {
      if (!string) {
          throw new TypeError('argument string is required')
      }
  
      if (typeof string === 'object') {
          // support req/res-like objects as argument
          string = getcontenttype(string)
  
          if (typeof string !== 'string') {
              throw new TypeError('content-type header is missing from object');
          }
      }
  
      if (typeof string !== 'string') {
          throw new TypeError('argument string is required to be a string')
      }
  
      var index = string.indexOf(';')
      var type = index !== -1
          ? string.substr(0, index).trim()
          : string.trim()
  
      if (!typeRegExp.test(type)) {
          throw new TypeError('invalid media type')
      }
  
      var key
      var match
      var obj = new ContentType(type.toLowerCase())
      var value
  
      paramRegExp.lastIndex = index
  
      while (match = paramRegExp.exec(string)) {
          if (match.index !== index) {
              throw new TypeError('invalid parameter format')
          }
  
          index += match[0].length
          key = match[1].toLowerCase()
          value = match[2]
  
          if (value[0] === '"') {
              // remove quotes and escapes
              value = value
                  .substr(1, value.length - 2)
                  .replace(qescRegExp, '$1')
          }
  
          obj.parameters[key] = value
      }
  
      if (index !== -1 && index !== string.length) {
          throw new TypeError('invalid parameter format')
      }
  
      return obj
  }
  
  /**
   * Get content-type from req/res objects.
   *
   * @param {object}
   * @return {Object}
   * @private
   */
  
  function getcontenttype(obj) {
      if (typeof obj.getHeader === 'function') {
          // res-like
          return obj.getHeader('content-type')
      }
  
      if (typeof obj.headers === 'object') {
          // req-like
          return obj.headers && obj.headers['content-type']
      }
  }
  
  /**
   * Quote a string if necessary.
   *
   * @param {string} val
   * @return {string}
   * @private
   */
  
  function qstring(val) {
      var str = String(val)
  
      // no need to quote tokens
      if (tokenRegExp.test(str)) {
          return str
      }
  
      if (str.length > 0 && !textRegExp.test(str)) {
          throw new TypeError('invalid parameter value')
      }
  
      return '"' + str.replace(quoteRegExp, '\\$1') + '"'
  }
  
  /**
   * Class to represent a content type.
   * @private
   */
  function ContentType(type) {
      this.parameters = Object.create(null)
      this.type = type
  }
  