UNPKG

3.75 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// Accept: audio/*; q=0.2, audio/basic
14// text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c
15// text/plain, application/json;q=0.5, text/html, */*;q=0.1
16// text/plain, application/json;q=0.5, text/html, text/drop;q=0
17// text/*, text/plain, text/plain;format=flowed, */*
18// text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5
19
20
21exports.mediaType = function (header, preferences) {
22
23 Hoek.assert(!preferences || Array.isArray(preferences), 'Preferences must be an array');
24
25 const mediaType = exports.mediaTypes(header);
26
27 // No header. Return any type.
28
29 if (!header) {
30 return '*/*';
31 }
32
33 // No preferences. Take the first media type.
34
35 if (!preferences ||
36 preferences.length === 0) {
37
38 return mediaType[0];
39 }
40
41 // If header includes * return first preference
42
43 if (header.indexOf('*') !== -1) {
44 return preferences[0];
45 }
46
47 // Try to find the first match in the array of preferences
48
49 preferences = preferences.map((str) => str.toLowerCase());
50
51 for (let i = 0; i < mediaType.length; ++i) {
52 if (preferences.indexOf(mediaType[i].toLowerCase()) !== -1) {
53 return mediaType[i];
54 }
55 }
56
57 return '';
58};
59
60
61exports.mediaTypes = function (header) {
62
63 if (header === undefined ||
64 typeof header !== 'string') {
65
66 return ['*/*'];
67 }
68
69 return header
70 .split(',')
71 .map(internals.getParts)
72 .filter(internals.removeEmptyAndDisallowed)
73 .sort(internals.compareByWeightAndSpecificity)
74 .map(internals.partToMediaType);
75};
76
77
78internals.getParts = function (item, pos) {
79
80 const result = {
81 weight: 1,
82 mediaType: '',
83 pos
84 };
85
86 const match = item.match(internals.partsRegex);
87
88 if (!match) {
89 return result;
90 }
91
92 result.mediaType = match[1];
93 if (match[2] && internals.isNumber(match[2])) {
94 const weight = parseFloat(match[2]);
95 if (weight === 0 || (weight >= 0.001 && weight <= 1)) {
96 result.weight = weight;
97 }
98 }
99
100 return result;
101};
102
103
104// 1: token 2: qvalue
105internals.partsRegex = /\s*(.+\/.+?)(?:\s*;\s*[qQ]\=([01](?:\.\d*)?))?\s*$/;
106
107
108internals.removeEmptyAndDisallowed = function (item) {
109
110 return item.mediaType !== '' && item.weight !== 0;
111};
112
113
114internals.compareByWeightAndSpecificity = function (a, b) {
115
116 if (a.weight !== b.weight) {
117 return b.weight - a.weight;
118 }
119
120 // We have the same weight, so now look for specificity
121
122 const byPos = a.pos - b.pos;
123
124 const aParts = a.mediaType.split('/');
125 const bParts = b.mediaType.split('/');
126
127 if (aParts[0] !== bParts[0]) { // First part of items are different so no further specificity is implied
128 return byPos;
129 }
130
131 // Wildcard is less specific
132
133 if (aParts[1] === '*' &&
134 bParts[1] !== '*') {
135
136 return 1;
137 }
138
139 if (bParts[1] === '*' &&
140 aParts[1] !== '*') {
141
142 return -1;
143 }
144
145 // Look for items with extensions
146
147 const aHasExtension = aParts[1].includes(';');
148 const bHasExtension = bParts[1].includes(';');
149
150 if (aHasExtension === bHasExtension) {
151 return byPos;
152 }
153
154 if (aHasExtension) {
155 return -1;
156 }
157
158 return 1;
159};
160
161
162internals.partToMediaType = function (item) {
163
164 return item.mediaType;
165};
166
167
168internals.isNumber = function (n) {
169
170 return !isNaN(parseFloat(n));
171};