summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/matrix/lib/content-type/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/matrix/lib/content-type/index.js')
-rw-r--r--comm/chat/protocols/matrix/lib/content-type/index.js225
1 files changed, 225 insertions, 0 deletions
diff --git a/comm/chat/protocols/matrix/lib/content-type/index.js b/comm/chat/protocols/matrix/lib/content-type/index.js
new file mode 100644
index 0000000000..41840e7bc3
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/content-type/index.js
@@ -0,0 +1,225 @@
+/*!
+ * 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 PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g // eslint-disable-line no-control-regex
+var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/ // eslint-disable-line no-control-regex
+var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~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 QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g // eslint-disable-line no-control-regex
+
+/**
+ * RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6
+ */
+var QUOTE_REGEXP = /([\\"])/g
+
+/**
+ * RegExp to match type in RFC 7231 sec 3.1.1.1
+ *
+ * media-type = type "/" subtype
+ * type = token
+ * subtype = token
+ */
+var TYPE_REGEXP = /^[!#$%&'*+.^_`|~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 || !TYPE_REGEXP.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 (!TOKEN_REGEXP.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')
+ }
+
+ // support req/res-like objects as argument
+ var header = typeof string === 'object'
+ ? getcontenttype(string)
+ : string
+
+ if (typeof header !== 'string') {
+ throw new TypeError('argument string is required to be a string')
+ }
+
+ var index = header.indexOf(';')
+ var type = index !== -1
+ ? header.slice(0, index).trim()
+ : header.trim()
+
+ if (!TYPE_REGEXP.test(type)) {
+ throw new TypeError('invalid media type')
+ }
+
+ var obj = new ContentType(type.toLowerCase())
+
+ // parse parameters
+ if (index !== -1) {
+ var key
+ var match
+ var value
+
+ PARAM_REGEXP.lastIndex = index
+
+ while ((match = PARAM_REGEXP.exec(header))) {
+ if (match.index !== index) {
+ throw new TypeError('invalid parameter format')
+ }
+
+ index += match[0].length
+ key = match[1].toLowerCase()
+ value = match[2]
+
+ if (value.charCodeAt(0) === 0x22 /* " */) {
+ // remove quotes
+ value = value.slice(1, -1)
+
+ // remove escapes
+ if (value.indexOf('\\') !== -1) {
+ value = value.replace(QESC_REGEXP, '$1')
+ }
+ }
+
+ obj.parameters[key] = value
+ }
+
+ if (index !== header.length) {
+ throw new TypeError('invalid parameter format')
+ }
+ }
+
+ return obj
+}
+
+/**
+ * Get content-type from req/res objects.
+ *
+ * @param {object}
+ * @return {Object}
+ * @private
+ */
+
+function getcontenttype (obj) {
+ var header
+
+ if (typeof obj.getHeader === 'function') {
+ // res-like
+ header = obj.getHeader('content-type')
+ } else if (typeof obj.headers === 'object') {
+ // req-like
+ header = obj.headers && obj.headers['content-type']
+ }
+
+ if (typeof header !== 'string') {
+ throw new TypeError('content-type header is missing from object')
+ }
+
+ return header
+}
+
+/**
+ * Quote a string if necessary.
+ *
+ * @param {string} val
+ * @return {string}
+ * @private
+ */
+
+function qstring (val) {
+ var str = String(val)
+
+ // no need to quote tokens
+ if (TOKEN_REGEXP.test(str)) {
+ return str
+ }
+
+ if (str.length > 0 && !TEXT_REGEXP.test(str)) {
+ throw new TypeError('invalid parameter value')
+ }
+
+ return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
+}
+
+/**
+ * Class to represent a content type.
+ * @private
+ */
+function ContentType (type) {
+ this.parameters = Object.create(null)
+ this.type = type
+}