index.js (2930B)
1 /*! 2 * vary 3 * Copyright(c) 2014-2017 Douglas Christopher Wilson 4 * MIT Licensed 5 */ 6 7 'use strict' 8 9 /** 10 * Module exports. 11 */ 12 13 module.exports = vary 14 module.exports.append = append 15 16 /** 17 * RegExp to match field-name in RFC 7230 sec 3.2 18 * 19 * field-name = token 20 * token = 1*tchar 21 * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" 22 * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" 23 * / DIGIT / ALPHA 24 * ; any VCHAR, except delimiters 25 */ 26 27 var FIELD_NAME_REGEXP = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/ 28 29 /** 30 * Append a field to a vary header. 31 * 32 * @param {String} header 33 * @param {String|Array} field 34 * @return {String} 35 * @public 36 */ 37 38 function append (header, field) { 39 if (typeof header !== 'string') { 40 throw new TypeError('header argument is required') 41 } 42 43 if (!field) { 44 throw new TypeError('field argument is required') 45 } 46 47 // get fields array 48 var fields = !Array.isArray(field) 49 ? parse(String(field)) 50 : field 51 52 // assert on invalid field names 53 for (var j = 0; j < fields.length; j++) { 54 if (!FIELD_NAME_REGEXP.test(fields[j])) { 55 throw new TypeError('field argument contains an invalid header name') 56 } 57 } 58 59 // existing, unspecified vary 60 if (header === '*') { 61 return header 62 } 63 64 // enumerate current values 65 var val = header 66 var vals = parse(header.toLowerCase()) 67 68 // unspecified vary 69 if (fields.indexOf('*') !== -1 || vals.indexOf('*') !== -1) { 70 return '*' 71 } 72 73 for (var i = 0; i < fields.length; i++) { 74 var fld = fields[i].toLowerCase() 75 76 // append value (case-preserving) 77 if (vals.indexOf(fld) === -1) { 78 vals.push(fld) 79 val = val 80 ? val + ', ' + fields[i] 81 : fields[i] 82 } 83 } 84 85 return val 86 } 87 88 /** 89 * Parse a vary header into an array. 90 * 91 * @param {String} header 92 * @return {Array} 93 * @private 94 */ 95 96 function parse (header) { 97 var end = 0 98 var list = [] 99 var start = 0 100 101 // gather tokens 102 for (var i = 0, len = header.length; i < len; i++) { 103 switch (header.charCodeAt(i)) { 104 case 0x20: /* */ 105 if (start === end) { 106 start = end = i + 1 107 } 108 break 109 case 0x2c: /* , */ 110 list.push(header.substring(start, end)) 111 start = end = i + 1 112 break 113 default: 114 end = i + 1 115 break 116 } 117 } 118 119 // final token 120 list.push(header.substring(start, end)) 121 122 return list 123 } 124 125 /** 126 * Mark that a request is varied on a header field. 127 * 128 * @param {Object} res 129 * @param {String|Array} field 130 * @public 131 */ 132 133 function vary (res, field) { 134 if (!res || !res.getHeader || !res.setHeader) { 135 // quack quack 136 throw new TypeError('res argument is required') 137 } 138 139 // get existing header 140 var val = res.getHeader('Vary') || '' 141 var header = Array.isArray(val) 142 ? val.join(', ') 143 : String(val) 144 145 // set new header 146 if ((val = append(header, field))) { 147 res.setHeader('Vary', val) 148 } 149 }