UNPKG

3.79 kBJavaScriptView Raw
1'use strict';
2
3// Load modules
4
5const Hoek = require('hoek');
6
7
8// Declare internals
9
10const internals = {};
11
12
13// From https://tools.ietf.org/html/rfc7231#section-5.3.3
14// Accept-Charset: ISO-8859-5, Unicode-1-1;q=0.8
15
16exports.charset = function (header, preferences) {
17
18 Hoek.assert(!preferences || Array.isArray(preferences), 'Preferences must be an array');
19
20 const charsets = header
21 .split(',')
22 .map(internals.getParts)
23 .filter(internals.removeEmpty)
24 .sort(internals.compareByWeight);
25
26 // Tack on a default return
27
28 charsets.push({
29 weight: 0.001,
30 charset: ''
31 });
32
33 // No preferences - use the first non-disallowed charset
34
35 if (!preferences ||
36 preferences.length === 0) {
37
38 return charsets.filter(internals.removeDisallowed)[0].charset;
39 }
40
41 // Lower case all preferences
42
43 preferences = preferences.map(internals.lowerCase);
44
45 // Remove any disallowed preferences
46
47 internals.removeDisallowedPreferences(charsets, preferences);
48
49 // If charsets includes * (that isn't disallowed *;q=0) return first preference
50
51 const splatLocation = internals.findCharsetItem(charsets, '*');
52 if (splatLocation !== -1 &&
53 charsets[splatLocation].weight > 0) {
54
55 return preferences[0];
56 }
57
58 // Try to find the first match in the array of preferences, ignoring case
59
60 for (let i = 0; i < charsets.length; ++i) {
61 if (preferences.indexOf(charsets[i].charset.toLowerCase()) !== -1 &&
62 charsets[i].weight > 0) {
63
64 return charsets[i].charset;
65 }
66 }
67
68 return '';
69};
70
71
72exports.charsets = function (header) {
73
74 if (header === undefined || typeof header !== 'string') {
75 return [];
76 }
77
78 header = header.toLowerCase();
79
80 return header
81 .split(',')
82 .map(internals.getParts)
83 .filter(internals.removeEmptyAndDisallowed)
84 .sort(internals.compareByWeight)
85 .map(internals.partToCharset);
86};
87
88
89internals.getParts = function (item) {
90
91 const result = {
92 weight: 1,
93 charset: ''
94 };
95
96 const match = item.match(internals.partsRegex);
97 if (!match) {
98 return result;
99 }
100
101 result.charset = match[1];
102 if (match[2] && internals.isNumber(match[2])) {
103 const weight = parseFloat(match[2]);
104 if (weight === 0 || (weight >= 0.001 && weight <= 1)) {
105 result.weight = weight;
106 }
107 }
108
109 return result;
110};
111
112// 1: token 2: q-value
113internals.partsRegex = /\s*([^;]+)(?:\s*;\s*[qQ]\=([01](?:\.\d*)?))?\s*/;
114
115
116internals.removeEmpty = function (item) {
117
118 return item.charset !== '';
119};
120
121
122internals.removeDisallowed = function (item) {
123
124 return item.weight !== 0;
125};
126
127
128internals.removeEmptyAndDisallowed = function (item) {
129
130 return item.charset !== '' && item.weight !== 0;
131};
132
133
134internals.removeDisallowedPreferences = function (charsets, preferences) {
135
136 for (let i = 0; i < charsets.length; ++i) {
137 let location;
138 if (charsets[i].weight === 0) {
139 location = preferences.indexOf(charsets[i].charset.toLowerCase());
140 if (location !== -1) {
141 preferences.splice(location, 1);
142 }
143 }
144 }
145};
146
147
148internals.compareByWeight = function (a, b) {
149
150 return b.weight - a.weight;
151};
152
153
154internals.partToCharset = function (item) {
155
156 return item.charset;
157};
158
159
160internals.isNumber = function (n) {
161
162 return !isNaN(parseFloat(n));
163};
164
165
166internals.lowerCase = function (str) {
167
168 return str.toLowerCase();
169};
170
171
172internals.findCharsetItem = function (charsets, charset) {
173
174 for (let i = 0; i < charsets.length; ++i) {
175 if (charsets[i].charset === charset) {
176 return i;
177 }
178 }
179
180 return -1;
181};