--- /dev/null
+module.exports = preferredCharsets;
+preferredCharsets.preferredCharsets = preferredCharsets;
+
+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;
+}
+
+function parseCharset(s, i) {
+ var match = s.match(/^\s*(\S+?)\s*(?:;(.*))?$/);
+ 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
+ };
+}
+
+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;
+}
+
+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
+ }
+}
+
+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(function getCharset(spec) {
+ return spec.charset;
+ });
+ }
+
+ 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)];
+ });
+}
+
+function compareSpecs(a, b) {
+ return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
+}
+
+function isQuality(spec) {
+ return spec.q > 0;
+}