/** * negotiator * Copyright(c) 2012 Isaac Z. Schlueter * Copyright(c) 2014 Federico Romero * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ 'use strict'; /** * Module exports. * @public */ module.exports = preferredCharsets; module.exports.preferredCharsets = preferredCharsets; /** * Module variables. * @private */ var simpleCharsetRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/; /** * Parse the Accept-Charset header. * @private */ function parseAcceptCharset(accept) { var accepts = accept.split(','); for (var i = 0, j = 0; i < accepts.length; i++) { var charset = parseCharset(accepts[i].trim(), i); if (charset) { accepts[j++] = charset; } } // trim accepts accepts.length = j; return accepts; } /** * Parse a charset from the Accept-Charset header. * @private */ function parseCharset(str, i) { var match = simpleCharsetRegExp.exec(str); if (!match) return null; var charset = match[1]; var q = 1; if (match[2]) { var params = match[2].split(';') for (var i = 0; i < params.length; i ++) { var p = params[i].trim().split('='); if (p[0] === 'q') { q = parseFloat(p[1]); break; } } } return { charset: charset, q: q, i: i }; } /** * Get the priority of a charset. * @private */ function getCharsetPriority(charset, accepted, index) { var priority = {o: -1, q: 0, s: 0}; for (var i = 0; i < accepted.length; i++) { var spec = specify(charset, accepted[i], index); if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) { priority = spec; } } return priority; } /** * Get the specificity of the charset. * @private */ function specify(charset, spec, index) { var s = 0; if(spec.charset.toLowerCase() === charset.toLowerCase()){ s |= 1; } else if (spec.charset !== '*' ) { return null } return { i: index, o: spec.i, q: spec.q, s: s } } /** * Get the preferred charsets from an Accept-Charset header. * @public */ function preferredCharsets(accept, provided) { // RFC 2616 sec 14.2: no header = * var accepts = parseAcceptCharset(accept === undefined ? '*' : accept || ''); if (!provided) { // sorted list of all charsets return accepts .filter(isQuality) .sort(compareSpecs) .map(getFullCharset); } var priorities = provided.map(function getPriority(type, index) { return getCharsetPriority(type, accepts, index); }); // sorted list of accepted charsets return priorities.filter(isQuality).sort(compareSpecs).map(function getCharset(priority) { return provided[priorities.indexOf(priority)]; }); } /** * Compare two specs. * @private */ function compareSpecs(a, b) { return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; } /** * Get full charset string. * @private */ function getFullCharset(spec) { return spec.charset; } /** * Check if a spec has any quality. * @private */ function isQuality(spec) { return spec.q > 0; }