UNPKG

3.38 kBJavaScriptView Raw
1'use strict';
2
3// Load modules
4
5const Boom = require('boom');
6const Hoek = require('hoek');
7
8
9// Declare internals
10
11const internals = {};
12
13/*
14 RFC 7231 Section 5.3.4 (https://tools.ietf.org/html/rfc7231#section-5.3.4)
15
16 Accept-Encoding = #( codings [ weight ] )
17 codings = content-coding / "identity" / "*"
18
19 Accept-Encoding: compress, gzip
20 Accept-Encoding:
21 Accept-Encoding: *
22 Accept-Encoding: compress;q=0.5, gzip;q=1.0
23 Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0
24*/
25
26exports.encoding = function (header, preferences) {
27
28 const encodings = exports.encodings(header, preferences);
29 return encodings.length ? encodings[0] : '';
30};
31
32
33exports.encodings = function (header, preferences) {
34
35 Hoek.assert(!preferences || Array.isArray(preferences), 'Preferences must be an array');
36
37 const scores = internals.parse(header, 'encoding');
38 if (!preferences) {
39 preferences = Object.keys(scores.accept);
40 preferences.push('*');
41 }
42
43 return internals.map(preferences, scores);
44};
45
46
47/*
48 RFC 7231 Section 5.3.1 (https://tools.ietf.org/html/rfc7231#section-5.3.1)
49
50 The weight is normalized to a real number in the range 0 through 1,
51 where 0.001 is the least preferred and 1 is the most preferred; a
52 value of 0 means "not acceptable". If no "q" parameter is present,
53 the default weight is 1.
54
55 weight = OWS ";" OWS "q=" qvalue
56 qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
57*/
58
59// 1: token 2: qvalue
60internals.preferenceRegex = /\s*([^;\,]+)(?:\s*;\s*[qQ]\=([01](?:\.\d{0,3})?))?\s*(?:\,|$)/g;
61
62
63internals.equivalents = {
64 encoding: {
65 'x-compress': 'compress',
66 'x-gzip': 'gzip'
67 }
68};
69
70internals.parse = function (header, type) {
71
72 const scores = {
73 accept: {},
74 reject: {},
75 any: 0.0
76 };
77
78 if (header) {
79 const leftovers = header.replace(internals.preferenceRegex, ($0, $1, $2) => {
80
81 $1 = $1.toLowerCase();
82 const key = internals.equivalents[type][$1] || $1;
83 const score = $2 ? parseFloat($2) : 1.0;
84 if (key === '*') {
85 scores.any = score;
86 }
87 else if (score > 0) {
88 scores.accept[key] = score;
89 }
90 else {
91 scores.reject[key] = true;
92 }
93
94 return '';
95 });
96
97 if (leftovers) {
98 throw Boom.badRequest('Invalid accept-' + type + ' header');
99 }
100 }
101
102 // Add identity at the lowest score if not explicitly set
103
104 if (!scores.reject.identity &&
105 !scores.accept.identity) {
106
107 scores.accept.identity = scores.any || 0.001;
108 }
109
110 return scores;
111};
112
113
114internals.map = function (preferences, scores) {
115
116 const scored = [];
117 for (let i = 0; i < preferences.length; ++i) {
118 const key = preferences[i].toLowerCase();
119 if (!scores.reject[key]) {
120 const score = scores.accept[key] || scores.any;
121 if (score > 0) {
122 scored.push({ key, score });
123 }
124 }
125 }
126
127 scored.sort(internals.sort);
128
129 const result = [];
130 for (let i = 0; i < scored.length; ++i) {
131 result.push(scored[i].key);
132 }
133
134 return result;
135};
136
137
138internals.sort = function (a, b) {
139
140 return b.score - a.score;
141};