Blame view

node_modules/content-type/index.js 4.7 KB
ce4c83ff   wxy   初始提交
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
  /*!
   * 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
  var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/
  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
  
  /**
   * 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.substr(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[0] === '"') {
          // remove quotes and escapes
          value = value
            .substr(1, value.length - 2)
            .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
  }