1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.any23.servlet.conneg;
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27
28
29
30
31 public class MediaRangeSpec {
32
33 private static final Pattern tokenPattern;
34
35 private static final Pattern parameterPattern;
36
37
38 private static final Pattern mediaRangePattern;
39
40 private static final Pattern qValuePattern;
41
42 private final String type;
43
44 private final String subtype;
45
46 private final List<String> parameterNames;
47
48 private final List<String> parameterValues;
49
50 private final String mediaType;
51
52 private final double quality;
53
54 static {
55
56
57 String token = "[\\x20-\\x7E&&[^()<>@,;:\\\"/\\[\\]?={} ]]+";
58 String quotedString = "\"((?:[\\x20-\\x7E\\n\\r\\t&&[^\"\\\\]]|\\\\[\\x00-\\x7F])*)\"";
59
60
61 String parameter = ";\\s*(?!q\\s*=)(" + token + ")=(?:(" + token + ")|" + quotedString + ")";
62
63
64 String qualityValue = "(?:0(?:\\.\\d{0,3})?|1(?:\\.0{0,3})?)";
65
66
67 String quality = ";\\s*q\\s*=\\s*([^;,]*)";
68
69
70 String regex = "(" + token + ")/(" + token + ")" +
71 "((?:\\s*" + parameter + ")*)" +
72 "(?:\\s*" + quality + ")?" +
73 "((?:\\s*" + parameter + ")*)";
74
75 tokenPattern = Pattern.compile(token);
76 parameterPattern = Pattern.compile(parameter);
77 mediaRangePattern = Pattern.compile(regex);
78 qValuePattern = Pattern.compile(qualityValue);
79 }
80
81
82
83
84
85
86 public static MediaRangeSpec parseType(String mediaType) {
87 MediaRangeSpec m = parseRange(mediaType);
88 if (m == null || m.isWildcardType() || m.isWildcardSubtype()) {
89 return null;
90 }
91 return m;
92 }
93
94
95
96
97
98
99
100 public static MediaRangeSpec parseRange(String mediaRange) {
101 Matcher m = mediaRangePattern.matcher(mediaRange);
102 if (!m.matches()) {
103 return null;
104 }
105 String type = m.group(1).toLowerCase();
106 String subtype = m.group(2).toLowerCase();
107 String unparsedParameters = m.group(3);
108 String qValue = m.group(7);
109 m = parameterPattern.matcher(unparsedParameters);
110 if ("*".equals(type) && !"*".equals(subtype)) {
111 return null;
112 }
113 List<String> parameterNames = new ArrayList<>();
114 List<String> parameterValues = new ArrayList<>();
115 while (m.find()) {
116 String name = m.group(1).toLowerCase();
117 String value = (m.group(3) == null) ? m.group(2) : unescape(m.group(3));
118 parameterNames.add(name);
119 parameterValues.add(value);
120 }
121 double quality = 1.0;
122 if (qValue != null && qValuePattern.matcher(qValue).matches()) {
123 try {
124 quality = Double.parseDouble(qValue);
125 } catch (NumberFormatException ex) {
126
127 }
128 }
129 return new MediaRangeSpec(type, subtype, parameterNames, parameterValues, quality);
130 }
131
132
133
134
135
136
137
138 public static List<MediaRangeSpec> parseAccept(String s) {
139 List<MediaRangeSpec> result = new ArrayList<>();
140 Matcher m = mediaRangePattern.matcher(s);
141 while (m.find()) {
142 result.add(parseRange(m.group()));
143 }
144 return result;
145 }
146
147 private static String unescape(String s) {
148 return s.replaceAll("\\\\(.)", "$1");
149 }
150
151 private static String escape(String s) {
152 return s.replaceAll("[\\\\\"]", "\\\\$0");
153 }
154
155 private MediaRangeSpec(
156 String type,
157 String subtype,
158 List<String> parameterNames, List<String> parameterValues,
159 double quality
160 ) {
161 this.type = type;
162 this.subtype = subtype;
163 this.parameterNames = Collections.unmodifiableList(parameterNames);
164 this.parameterValues = parameterValues;
165 this.mediaType = buildMediaType();
166 this.quality = quality;
167 }
168
169 private String buildMediaType() {
170 StringBuffer result = new StringBuffer();
171 result.append(type);
172 result.append("/");
173 result.append(subtype);
174 for (int i = 0; i < parameterNames.size(); i++) {
175 result.append(";");
176 result.append(parameterNames.get(i));
177 result.append("=");
178 String value = parameterValues.get(i);
179 if (tokenPattern.matcher(value).matches()) {
180 result.append(value);
181 } else {
182 result.append("\"");
183 result.append(escape(value));
184 result.append("\"");
185 }
186 }
187 return result.toString();
188 }
189
190 public String getType() {
191 return type;
192 }
193
194 public String getSubtype() {
195 return subtype;
196 }
197
198 public String getMediaType() {
199 return mediaType;
200 }
201
202 public List<String> getParameterNames() {
203 return parameterNames;
204 }
205
206 public String getParameter(String parameterName) {
207 for (int i = 0; i < parameterNames.size(); i++) {
208 if (parameterNames.get(i).equalsIgnoreCase(parameterName)) {
209 return parameterValues.get(i);
210 }
211 }
212 return null;
213 }
214
215 public boolean isWildcardType() {
216 return "*".equals(type);
217 }
218
219 public boolean isWildcardSubtype() {
220 return !isWildcardType() && "*".equals(subtype);
221 }
222
223 public double getQuality() {
224 return quality;
225 }
226
227 public int getPrecedence(MediaRangeSpec range) {
228 if (range.isWildcardType())
229 return 1;
230 if (!range.type.equals(type))
231 return 0;
232 if (range.isWildcardSubtype())
233 return 2;
234 if (!range.subtype.equals(subtype))
235 return 0;
236 if (range.getParameterNames().isEmpty())
237 return 3;
238 int result = 3;
239 for (int i = 0; i < range.getParameterNames().size(); i++) {
240 String name = range.getParameterNames().get(i);
241 String value = range.getParameter(name);
242 if (!value.equals(getParameter(name)))
243 return 0;
244 result++;
245 }
246 return result;
247 }
248
249 public MediaRangeSpec getBestMatch(List<MediaRangeSpec> mediaRanges) {
250 MediaRangeSpec result = null;
251 int bestPrecedence = 0;
252 Iterator<MediaRangeSpec> it = mediaRanges.iterator();
253 while (it.hasNext()) {
254 MediaRangeSpec range = it.next();
255 if (getPrecedence(range) > bestPrecedence) {
256 bestPrecedence = getPrecedence(range);
257 result = range;
258 }
259 }
260 return result;
261 }
262
263 @Override
264 public String toString() {
265 return mediaType + ";q=" + quality;
266 }
267 }