Branch data Line data Source code
1 [ + ]: 229 : /** 2 : 229 : * @module helper-content-type 3 : 229 : * @desc Helper module - Helper functions for the main module {@link module:whatwg-xhr whatwg-xhr}. 4 : 229 : * @version 1.0.0 5 : 229 : * @author Essam A. El-Sherif 6 : 229 : */ 7 : 229 : 8 : 229 : /* 9 : 229 : * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1 10 : 229 : * 11 : 229 : * parameter = token "=" ( token / quoted-string ) 12 : 229 : * token = 1*tchar 13 : 229 : * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" 14 : 229 : * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" 15 : 229 : * / DIGIT / ALPHA 16 : 229 : * ; any VCHAR, except delimiters 17 : 229 : * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE 18 : 229 : * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text 19 : 229 : * obs-text = %x80-FF 20 : 229 : * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) 21 : 229 : */ 22 : 229 : const 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 23 : 229 : const TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/; // eslint-disable-line no-control-regex 24 : 229 : const TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/; 25 : 229 : 26 : 229 : /* 27 : 229 : * RegExp to match quoted-pair in RFC 7230 sec 3.2.6 28 : 229 : * 29 : 229 : * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) 30 : 229 : * obs-text = %x80-FF 31 : 229 : */ 32 : 229 : const QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g // eslint-disable-line no-control-regex; 33 : 229 : 34 : 229 : /* 35 : 229 : * RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6 36 : 229 : */ 37 : 229 : const QUOTE_REGEXP = /([\\"])/g; 38 : 229 : 39 : 229 : /* 40 : 229 : * RegExp to match type in RFC 7231 sec 3.1.1.1 41 : 229 : * 42 : 229 : * media-type = type "/" subtype 43 : 229 : * type = token 44 : 229 : * subtype = token 45 : 229 : */ 46 : 229 : const TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/; 47 : 229 : 48 : 229 : /** 49 : 229 : * @func format 50 : 229 : * @static 51 : 229 : * @param {object} obj 52 : 229 : * @return {string} 53 : 229 : * @desc Format object to media type. 54 : 229 : */ 55 [ + ]: 229 : export function format(obj){ 56 [ - ]: 6 : if(!obj || typeof obj !== 'object'){ 57 : 0 : throw new TypeError('argument obj is required'); 58 : 0 : } 59 : 6 : 60 : 6 : var parameters = obj.parameters; 61 : 6 : var type = obj.type; 62 : 6 : 63 [ - ]: 6 : if(!type || !TYPE_REGEXP.test(type)){ 64 : 0 : throw new TypeError('invalid type'); 65 : 0 : } 66 : 6 : 67 : 6 : var string = type; 68 : 6 : 69 : 6 : // append parameters 70 : 6 : if (parameters && typeof parameters === 'object'){ 71 : 6 : var param; 72 : 6 : var params = Object.keys(parameters); 73 : 6 : 74 [ + ]: 6 : for (var i = 0; i < params.length; i++){ 75 : 8 : param = params[i]; 76 : 8 : 77 [ - ]: 8 : if(!TOKEN_REGEXP.test(param)){ 78 : 0 : throw new TypeError('invalid parameter name'); 79 : 0 : } 80 : 8 : 81 : 8 : string += ';' + param + '=' + qstring(parameters[param]); 82 : 8 : } 83 : 6 : } 84 : 6 : 85 : 6 : return string; 86 : 6 : } 87 : 229 : 88 : 229 : /** 89 : 229 : * @func parse 90 : 229 : * @static 91 : 229 : * @param {string|object} string 92 : 229 : * @return {object} 93 : 229 : * @desc Parse media type to object. 94 : 229 : */ 95 [ + ]: 229 : export function parse (string) { 96 [ + ][ + ]: 1840 : if(!string){ 97 : 30 : throw new TypeError('argument string is required'); 98 [ + ]: 30 : } 99 : 30 : 100 : 30 : // support req/res-like objects as argument 101 : 30 : var header = typeof string === 'object' 102 [ - ]: 1840 : ? getcontenttype(string) 103 [ + ]: 1840 : : string; 104 : 1840 : 105 [ - ]: 1840 : if(typeof header !== 'string'){ 106 : 0 : throw new TypeError('argument string is required to be a string'); 107 [ + ]: 0 : } 108 : 730 : 109 : 730 : var index = header.indexOf(';'); 110 : 730 : var type = index !== -1 111 [ + ]: 1840 : ? header.slice(0, index).trim() 112 [ + ]: 1840 : : header.trim(); 113 : 1840 : 114 [ + ]: 1840 : if (!TYPE_REGEXP.test(type)){ 115 : 3 : throw new TypeError('invalid media type'); 116 [ + ]: 3 : } 117 : 727 : 118 : 727 : var obj = new ContentType(type.toLowerCase()); 119 : 727 : 120 : 727 : // parse parameters 121 [ + ]: 1840 : if (index !== -1) { 122 : 52 : var key 123 : 52 : var match 124 : 52 : var value 125 : 52 : 126 : 52 : PARAM_REGEXP.lastIndex = index 127 : 52 : 128 [ + ]: 52 : while((match = PARAM_REGEXP.exec(header))) { 129 : 55 : if(match.index !== index){ 130 : 55 : throw new TypeError('invalid parameter format'); 131 : 55 : } 132 : 55 : 133 : 55 : index += match[0].length; 134 : 55 : key = match[1].toLowerCase(); 135 : 55 : value = match[2]; 136 : 55 : 137 [ + ]: 55 : if (value.charCodeAt(0) === 0x22 /* " */) { 138 : 3 : // remove quotes 139 : 3 : value = value.slice(1, -1); 140 : 3 : 141 : 3 : // remove escapes 142 [ + ]: 3 : if(value.indexOf('\\') !== -1){ 143 : 1 : value = value.replace(QESC_REGEXP, '$1'); 144 : 1 : } 145 : 3 : } 146 : 55 : 147 : 55 : obj.parameters[key] = value; 148 : 55 : } 149 : 52 : 150 : 52 : if(index !== header.length){ 151 : 52 : throw new TypeError('invalid parameter format'); 152 : 52 : } 153 [ + ]: 52 : } 154 : 727 : 155 : 727 : return obj; 156 : 1840 : } 157 : 229 : 158 : 229 : /* 159 : 229 : * Get content-type from req/res objects. 160 : 229 : * 161 : 229 : * @param {object} 162 : 229 : * @return {object} 163 : 229 : * @private 164 : 229 : */ 165 : 0 : function getcontenttype (obj) { 166 : 0 : var header; 167 : 0 : 168 : 0 : if (typeof obj.getHeader === 'function') { 169 : 0 : // res-like 170 : 0 : header = obj.getHeader('content-type') 171 : 0 : } else if (typeof obj.headers === 'object') { 172 : 0 : // req-like 173 : 0 : header = obj.headers && obj.headers['content-type'] 174 : 0 : } 175 : 0 : 176 : 0 : if (typeof header !== 'string') { 177 : 0 : throw new TypeError('content-type header is missing from object') 178 : 0 : } 179 : 0 : 180 : 0 : return header 181 : 0 : } 182 : 229 : 183 : 229 : /* 184 : 229 : * Quote a string if necessary. 185 : 229 : * 186 : 229 : * @param {string} val 187 : 229 : * @return {string} 188 : 229 : * @private 189 : 229 : */ 190 : 229 : 191 [ + ]: 8 : function qstring (val) { 192 : 8 : var str = String(val) 193 : 8 : 194 : 8 : // no need to quote tokens 195 : 8 : if (TOKEN_REGEXP.test(str)) { 196 : 8 : return str; 197 [ - ]: 8 : } 198 : 0 : 199 [ - ]: 8 : if (str.length > 0 && !TEXT_REGEXP.test(str)) { 200 : 0 : throw new TypeError('invalid parameter value'); 201 : 0 : } 202 : 0 : 203 : 0 : return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"' 204 : 8 : } 205 : 229 : 206 : 229 : /* 207 : 229 : * Class to represent a content type. 208 : 229 : * @private 209 : 229 : */ 210 [ + ]: 1757 : function ContentType (type) { 211 : 1757 : this.parameters = Object.create(null); 212 : 1757 : this.type = type; 213 : 1757 : } 214 : 229 :