Initial commit
[yaffs-website] / node_modules / content-type / index.js
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 paramRegExp = /; *([!#$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+) */g
24 var textRegExp = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/
25 var tokenRegExp = /^[!#$%&'\*\+\-\.\^_`\|~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 qescRegExp = /\\([\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 quoteRegExp = /([\\"])/g
39
40 /**
41  * RegExp to match type in RFC 6838
42  *
43  * media-type = type "/" subtype
44  * type       = token
45  * subtype    = token
46  */
47 var typeRegExp = /^[!#$%&'\*\+\-\.\^_`\|~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 || !typeRegExp.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 (!tokenRegExp.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   if (typeof string === 'object') {
112     // support req/res-like objects as argument
113     string = getcontenttype(string)
114
115     if (typeof string !== 'string') {
116       throw new TypeError('content-type header is missing from object');
117     }
118   }
119
120   if (typeof string !== 'string') {
121     throw new TypeError('argument string is required to be a string')
122   }
123
124   var index = string.indexOf(';')
125   var type = index !== -1
126     ? string.substr(0, index).trim()
127     : string.trim()
128
129   if (!typeRegExp.test(type)) {
130     throw new TypeError('invalid media type')
131   }
132
133   var key
134   var match
135   var obj = new ContentType(type.toLowerCase())
136   var value
137
138   paramRegExp.lastIndex = index
139
140   while (match = paramRegExp.exec(string)) {
141     if (match.index !== index) {
142       throw new TypeError('invalid parameter format')
143     }
144
145     index += match[0].length
146     key = match[1].toLowerCase()
147     value = match[2]
148
149     if (value[0] === '"') {
150       // remove quotes and escapes
151       value = value
152         .substr(1, value.length - 2)
153         .replace(qescRegExp, '$1')
154     }
155
156     obj.parameters[key] = value
157   }
158
159   if (index !== -1 && index !== string.length) {
160     throw new TypeError('invalid parameter format')
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   if (typeof obj.getHeader === 'function') {
176     // res-like
177     return obj.getHeader('content-type')
178   }
179
180   if (typeof obj.headers === 'object') {
181     // req-like
182     return obj.headers && obj.headers['content-type']
183   }
184 }
185
186 /**
187  * Quote a string if necessary.
188  *
189  * @param {string} val
190  * @return {string}
191  * @private
192  */
193
194 function qstring(val) {
195   var str = String(val)
196
197   // no need to quote tokens
198   if (tokenRegExp.test(str)) {
199     return str
200   }
201
202   if (str.length > 0 && !textRegExp.test(str)) {
203     throw new TypeError('invalid parameter value')
204   }
205
206   return '"' + str.replace(quoteRegExp, '\\$1') + '"'
207 }
208
209 /**
210  * Class to represent a content type.
211  * @private
212  */
213 function ContentType(type) {
214   this.parameters = Object.create(null)
215   this.type = type
216 }