The LM Control website. Simple yet efficient.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /*!
  2. * content-disposition
  3. * Copyright(c) 2014-2017 Douglas Christopher Wilson
  4. * MIT Licensed
  5. */
  6. 'use strict'
  7. /**
  8. * Module exports.
  9. * @public
  10. */
  11. module.exports = contentDisposition
  12. module.exports.parse = parse
  13. /**
  14. * Module dependencies.
  15. * @private
  16. */
  17. var basename = require('path').basename
  18. var Buffer = require('safe-buffer').Buffer
  19. /**
  20. * RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
  21. * @private
  22. */
  23. var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex
  24. /**
  25. * RegExp to match percent encoding escape.
  26. * @private
  27. */
  28. var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/
  29. var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g
  30. /**
  31. * RegExp to match non-latin1 characters.
  32. * @private
  33. */
  34. var NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g
  35. /**
  36. * RegExp to match quoted-pair in RFC 2616
  37. *
  38. * quoted-pair = "\" CHAR
  39. * CHAR = <any US-ASCII character (octets 0 - 127)>
  40. * @private
  41. */
  42. var QESC_REGEXP = /\\([\u0000-\u007f])/g // eslint-disable-line no-control-regex
  43. /**
  44. * RegExp to match chars that must be quoted-pair in RFC 2616
  45. * @private
  46. */
  47. var QUOTE_REGEXP = /([\\"])/g
  48. /**
  49. * RegExp for various RFC 2616 grammar
  50. *
  51. * parameter = token "=" ( token | quoted-string )
  52. * token = 1*<any CHAR except CTLs or separators>
  53. * separators = "(" | ")" | "<" | ">" | "@"
  54. * | "," | ";" | ":" | "\" | <">
  55. * | "/" | "[" | "]" | "?" | "="
  56. * | "{" | "}" | SP | HT
  57. * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
  58. * qdtext = <any TEXT except <">>
  59. * quoted-pair = "\" CHAR
  60. * CHAR = <any US-ASCII character (octets 0 - 127)>
  61. * TEXT = <any OCTET except CTLs, but including LWS>
  62. * LWS = [CRLF] 1*( SP | HT )
  63. * CRLF = CR LF
  64. * CR = <US-ASCII CR, carriage return (13)>
  65. * LF = <US-ASCII LF, linefeed (10)>
  66. * SP = <US-ASCII SP, space (32)>
  67. * HT = <US-ASCII HT, horizontal-tab (9)>
  68. * CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
  69. * OCTET = <any 8-bit sequence of data>
  70. * @private
  71. */
  72. var PARAM_REGEXP = /;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g // eslint-disable-line no-control-regex
  73. var TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/
  74. var TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/
  75. /**
  76. * RegExp for various RFC 5987 grammar
  77. *
  78. * ext-value = charset "'" [ language ] "'" value-chars
  79. * charset = "UTF-8" / "ISO-8859-1" / mime-charset
  80. * mime-charset = 1*mime-charsetc
  81. * mime-charsetc = ALPHA / DIGIT
  82. * / "!" / "#" / "$" / "%" / "&"
  83. * / "+" / "-" / "^" / "_" / "`"
  84. * / "{" / "}" / "~"
  85. * language = ( 2*3ALPHA [ extlang ] )
  86. * / 4ALPHA
  87. * / 5*8ALPHA
  88. * extlang = *3( "-" 3ALPHA )
  89. * value-chars = *( pct-encoded / attr-char )
  90. * pct-encoded = "%" HEXDIG HEXDIG
  91. * attr-char = ALPHA / DIGIT
  92. * / "!" / "#" / "$" / "&" / "+" / "-" / "."
  93. * / "^" / "_" / "`" / "|" / "~"
  94. * @private
  95. */
  96. var EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/
  97. /**
  98. * RegExp for various RFC 6266 grammar
  99. *
  100. * disposition-type = "inline" | "attachment" | disp-ext-type
  101. * disp-ext-type = token
  102. * disposition-parm = filename-parm | disp-ext-parm
  103. * filename-parm = "filename" "=" value
  104. * | "filename*" "=" ext-value
  105. * disp-ext-parm = token "=" value
  106. * | ext-token "=" ext-value
  107. * ext-token = <the characters in token, followed by "*">
  108. * @private
  109. */
  110. var DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/ // eslint-disable-line no-control-regex
  111. /**
  112. * Create an attachment Content-Disposition header.
  113. *
  114. * @param {string} [filename]
  115. * @param {object} [options]
  116. * @param {string} [options.type=attachment]
  117. * @param {string|boolean} [options.fallback=true]
  118. * @return {string}
  119. * @public
  120. */
  121. function contentDisposition (filename, options) {
  122. var opts = options || {}
  123. // get type
  124. var type = opts.type || 'attachment'
  125. // get parameters
  126. var params = createparams(filename, opts.fallback)
  127. // format into string
  128. return format(new ContentDisposition(type, params))
  129. }
  130. /**
  131. * Create parameters object from filename and fallback.
  132. *
  133. * @param {string} [filename]
  134. * @param {string|boolean} [fallback=true]
  135. * @return {object}
  136. * @private
  137. */
  138. function createparams (filename, fallback) {
  139. if (filename === undefined) {
  140. return
  141. }
  142. var params = {}
  143. if (typeof filename !== 'string') {
  144. throw new TypeError('filename must be a string')
  145. }
  146. // fallback defaults to true
  147. if (fallback === undefined) {
  148. fallback = true
  149. }
  150. if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
  151. throw new TypeError('fallback must be a string or boolean')
  152. }
  153. if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {
  154. throw new TypeError('fallback must be ISO-8859-1 string')
  155. }
  156. // restrict to file base name
  157. var name = basename(filename)
  158. // determine if name is suitable for quoted string
  159. var isQuotedString = TEXT_REGEXP.test(name)
  160. // generate fallback name
  161. var fallbackName = typeof fallback !== 'string'
  162. ? fallback && getlatin1(name)
  163. : basename(fallback)
  164. var hasFallback = typeof fallbackName === 'string' && fallbackName !== name
  165. // set extended filename parameter
  166. if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
  167. params['filename*'] = name
  168. }
  169. // set filename parameter
  170. if (isQuotedString || hasFallback) {
  171. params.filename = hasFallback
  172. ? fallbackName
  173. : name
  174. }
  175. return params
  176. }
  177. /**
  178. * Format object to Content-Disposition header.
  179. *
  180. * @param {object} obj
  181. * @param {string} obj.type
  182. * @param {object} [obj.parameters]
  183. * @return {string}
  184. * @private
  185. */
  186. function format (obj) {
  187. var parameters = obj.parameters
  188. var type = obj.type
  189. if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) {
  190. throw new TypeError('invalid type')
  191. }
  192. // start with normalized type
  193. var string = String(type).toLowerCase()
  194. // append parameters
  195. if (parameters && typeof parameters === 'object') {
  196. var param
  197. var params = Object.keys(parameters).sort()
  198. for (var i = 0; i < params.length; i++) {
  199. param = params[i]
  200. var val = param.substr(-1) === '*'
  201. ? ustring(parameters[param])
  202. : qstring(parameters[param])
  203. string += '; ' + param + '=' + val
  204. }
  205. }
  206. return string
  207. }
  208. /**
  209. * Decode a RFC 5987 field value (gracefully).
  210. *
  211. * @param {string} str
  212. * @return {string}
  213. * @private
  214. */
  215. function decodefield (str) {
  216. var match = EXT_VALUE_REGEXP.exec(str)
  217. if (!match) {
  218. throw new TypeError('invalid extended field value')
  219. }
  220. var charset = match[1].toLowerCase()
  221. var encoded = match[2]
  222. var value
  223. // to binary string
  224. var binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)
  225. switch (charset) {
  226. case 'iso-8859-1':
  227. value = getlatin1(binary)
  228. break
  229. case 'utf-8':
  230. value = Buffer.from(binary, 'binary').toString('utf8')
  231. break
  232. default:
  233. throw new TypeError('unsupported charset in extended field')
  234. }
  235. return value
  236. }
  237. /**
  238. * Get ISO-8859-1 version of string.
  239. *
  240. * @param {string} val
  241. * @return {string}
  242. * @private
  243. */
  244. function getlatin1 (val) {
  245. // simple Unicode -> ISO-8859-1 transformation
  246. return String(val).replace(NON_LATIN1_REGEXP, '?')
  247. }
  248. /**
  249. * Parse Content-Disposition header string.
  250. *
  251. * @param {string} string
  252. * @return {object}
  253. * @public
  254. */
  255. function parse (string) {
  256. if (!string || typeof string !== 'string') {
  257. throw new TypeError('argument string is required')
  258. }
  259. var match = DISPOSITION_TYPE_REGEXP.exec(string)
  260. if (!match) {
  261. throw new TypeError('invalid type format')
  262. }
  263. // normalize type
  264. var index = match[0].length
  265. var type = match[1].toLowerCase()
  266. var key
  267. var names = []
  268. var params = {}
  269. var value
  270. // calculate index to start at
  271. index = PARAM_REGEXP.lastIndex = match[0].substr(-1) === ';'
  272. ? index - 1
  273. : index
  274. // match parameters
  275. while ((match = PARAM_REGEXP.exec(string))) {
  276. if (match.index !== index) {
  277. throw new TypeError('invalid parameter format')
  278. }
  279. index += match[0].length
  280. key = match[1].toLowerCase()
  281. value = match[2]
  282. if (names.indexOf(key) !== -1) {
  283. throw new TypeError('invalid duplicate parameter')
  284. }
  285. names.push(key)
  286. if (key.indexOf('*') + 1 === key.length) {
  287. // decode extended value
  288. key = key.slice(0, -1)
  289. value = decodefield(value)
  290. // overwrite existing value
  291. params[key] = value
  292. continue
  293. }
  294. if (typeof params[key] === 'string') {
  295. continue
  296. }
  297. if (value[0] === '"') {
  298. // remove quotes and escapes
  299. value = value
  300. .substr(1, value.length - 2)
  301. .replace(QESC_REGEXP, '$1')
  302. }
  303. params[key] = value
  304. }
  305. if (index !== -1 && index !== string.length) {
  306. throw new TypeError('invalid parameter format')
  307. }
  308. return new ContentDisposition(type, params)
  309. }
  310. /**
  311. * Percent decode a single character.
  312. *
  313. * @param {string} str
  314. * @param {string} hex
  315. * @return {string}
  316. * @private
  317. */
  318. function pdecode (str, hex) {
  319. return String.fromCharCode(parseInt(hex, 16))
  320. }
  321. /**
  322. * Percent encode a single character.
  323. *
  324. * @param {string} char
  325. * @return {string}
  326. * @private
  327. */
  328. function pencode (char) {
  329. return '%' + String(char)
  330. .charCodeAt(0)
  331. .toString(16)
  332. .toUpperCase()
  333. }
  334. /**
  335. * Quote a string for HTTP.
  336. *
  337. * @param {string} val
  338. * @return {string}
  339. * @private
  340. */
  341. function qstring (val) {
  342. var str = String(val)
  343. return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
  344. }
  345. /**
  346. * Encode a Unicode string for HTTP (RFC 5987).
  347. *
  348. * @param {string} val
  349. * @return {string}
  350. * @private
  351. */
  352. function ustring (val) {
  353. var str = String(val)
  354. // percent encode as UTF-8
  355. var encoded = encodeURIComponent(str)
  356. .replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode)
  357. return 'UTF-8\'\'' + encoded
  358. }
  359. /**
  360. * Class for parsed Content-Disposition header for v8 optimization
  361. *
  362. * @public
  363. * @param {string} type
  364. * @param {object} parameters
  365. * @constructor
  366. */
  367. function ContentDisposition (type, parameters) {
  368. this.type = type
  369. this.parameters = parameters
  370. }