1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.http;
20
21 import java.nio.ByteBuffer;
22 import java.nio.charset.Charset;
23 import java.nio.charset.StandardCharsets;
24 import java.util.Enumeration;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.MissingResourceException;
31 import java.util.ResourceBundle;
32 import java.util.Set;
33
34 import org.eclipse.jetty.util.ArrayTrie;
35 import org.eclipse.jetty.util.BufferUtil;
36 import org.eclipse.jetty.util.StringUtil;
37 import org.eclipse.jetty.util.Trie;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40
41
42
43
44
45
46 public class MimeTypes
47 {
48 public enum Type
49 {
50 FORM_ENCODED("application/x-www-form-urlencoded"),
51 MESSAGE_HTTP("message/http"),
52 MULTIPART_BYTERANGES("multipart/byteranges"),
53
54 TEXT_HTML("text/html"),
55 TEXT_PLAIN("text/plain"),
56 TEXT_XML("text/xml"),
57 TEXT_JSON("text/json",StandardCharsets.UTF_8),
58 APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
59
60 TEXT_HTML_8859_1("text/html;charset=iso-8859-1",TEXT_HTML),
61 TEXT_HTML_UTF_8("text/html;charset=utf-8",TEXT_HTML),
62
63 TEXT_PLAIN_8859_1("text/plain;charset=iso-8859-1",TEXT_PLAIN),
64 TEXT_PLAIN_UTF_8("text/plain;charset=utf-8",TEXT_PLAIN),
65
66 TEXT_XML_8859_1("text/xml;charset=iso-8859-1",TEXT_XML),
67 TEXT_XML_UTF_8("text/xml;charset=utf-8",TEXT_XML),
68
69 TEXT_JSON_8859_1("text/json;charset=iso-8859-1",TEXT_JSON),
70 TEXT_JSON_UTF_8("text/json;charset=utf-8",TEXT_JSON),
71
72 APPLICATION_JSON_8859_1("text/json;charset=iso-8859-1",APPLICATION_JSON),
73 APPLICATION_JSON_UTF_8("text/json;charset=utf-8",APPLICATION_JSON);
74
75
76
77 private final String _string;
78 private final Type _base;
79 private final ByteBuffer _buffer;
80 private final Charset _charset;
81 private final String _charsetString;
82 private final boolean _assumedCharset;
83 private final HttpField _field;
84
85
86 Type(String s)
87 {
88 _string=s;
89 _buffer=BufferUtil.toBuffer(s);
90 _base=this;
91 _charset=null;
92 _charsetString=null;
93 _assumedCharset=false;
94 _field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
95 }
96
97
98 Type(String s,Type base)
99 {
100 _string=s;
101 _buffer=BufferUtil.toBuffer(s);
102 _base=base;
103 int i=s.indexOf(";charset=");
104 _charset=Charset.forName(s.substring(i+9));
105 _charsetString=_charset.toString().toLowerCase(Locale.ENGLISH);
106 _assumedCharset=false;
107 _field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
108 }
109
110
111 Type(String s,Charset cs)
112 {
113 _string=s;
114 _base=this;
115 _buffer=BufferUtil.toBuffer(s);
116 _charset=cs;
117 _charsetString=_charset==null?null:_charset.toString().toLowerCase(Locale.ENGLISH);
118 _assumedCharset=true;
119 _field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
120 }
121
122
123 public ByteBuffer asBuffer()
124 {
125 return _buffer.asReadOnlyBuffer();
126 }
127
128
129 public Charset getCharset()
130 {
131 return _charset;
132 }
133
134
135 public String getCharsetString()
136 {
137 return _charsetString;
138 }
139
140
141 public boolean is(String s)
142 {
143 return _string.equalsIgnoreCase(s);
144 }
145
146
147 public String asString()
148 {
149 return _string;
150 }
151
152
153 @Override
154 public String toString()
155 {
156 return _string;
157 }
158
159
160 public boolean isCharsetAssumed()
161 {
162 return _assumedCharset;
163 }
164
165
166 public HttpField getContentTypeField()
167 {
168 return _field;
169 }
170
171
172 public Type getBaseType()
173 {
174 return _base;
175 }
176 }
177
178
179 private static final Logger LOG = Log.getLogger(MimeTypes.class);
180 public final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
181 private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
182 private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
183 private final static Map<String,String> __encodings = new HashMap<String,String>();
184
185 static
186 {
187 for (MimeTypes.Type type : MimeTypes.Type.values())
188 {
189 CACHE.put(type.toString(),type);
190 TYPES.put(type.toString(),type.asBuffer());
191
192 int charset=type.toString().indexOf(";charset=");
193 if (charset>0)
194 {
195 String alt=type.toString().replace(";charset=","; charset=");
196 CACHE.put(alt,type);
197 TYPES.put(alt,type.asBuffer());
198 }
199 }
200
201 try
202 {
203 ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
204 Enumeration<String> i = mime.getKeys();
205 while(i.hasMoreElements())
206 {
207 String ext = i.nextElement();
208 String m = mime.getString(ext);
209 __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
210 }
211 }
212 catch(MissingResourceException e)
213 {
214 LOG.warn(e.toString());
215 LOG.debug(e);
216 }
217
218 try
219 {
220 ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
221 Enumeration<String> i = encoding.getKeys();
222 while(i.hasMoreElements())
223 {
224 String type = i.nextElement();
225 __encodings.put(type,encoding.getString(type));
226 }
227 }
228 catch(MissingResourceException e)
229 {
230 LOG.warn(e.toString());
231 LOG.debug(e);
232 }
233 }
234
235
236
237 private final Map<String,String> _mimeMap=new HashMap<String,String>();
238
239
240
241
242 public MimeTypes()
243 {
244 }
245
246
247 public synchronized Map<String,String> getMimeMap()
248 {
249 return _mimeMap;
250 }
251
252
253
254
255
256 public void setMimeMap(Map<String,String> mimeMap)
257 {
258 _mimeMap.clear();
259 if (mimeMap!=null)
260 {
261 for (Entry<String, String> ext : mimeMap.entrySet())
262 _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
263 }
264 }
265
266
267
268
269
270
271
272 public String getMimeByExtension(String filename)
273 {
274 String type=null;
275
276 if (filename!=null)
277 {
278 int i=-1;
279 while(type==null)
280 {
281 i=filename.indexOf(".",i+1);
282
283 if (i<0 || i>=filename.length())
284 break;
285
286 String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
287 if (_mimeMap!=null)
288 type=_mimeMap.get(ext);
289 if (type==null)
290 type=__dftMimeMap.get(ext);
291 }
292 }
293
294 if (type==null)
295 {
296 if (_mimeMap!=null)
297 type=_mimeMap.get("*");
298 if (type==null)
299 type=__dftMimeMap.get("*");
300 }
301
302 return type;
303 }
304
305
306
307
308
309
310 public void addMimeMapping(String extension,String type)
311 {
312 _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
313 }
314
315
316 public static Set<String> getKnownMimeTypes()
317 {
318 return new HashSet<>(__dftMimeMap.values());
319 }
320
321
322 private static String normalizeMimeType(String type)
323 {
324 MimeTypes.Type t =CACHE.get(type);
325 if (t!=null)
326 return t.asString();
327
328 return StringUtil.asciiToLowerCase(type);
329 }
330
331
332 public static String getCharsetFromContentType(String value)
333 {
334 if (value==null)
335 return null;
336 int end=value.length();
337 int state=0;
338 int start=0;
339 boolean quote=false;
340 int i=0;
341 for (;i<end;i++)
342 {
343 char b = value.charAt(i);
344
345 if (quote && state!=10)
346 {
347 if ('"'==b)
348 quote=false;
349 continue;
350 }
351
352 switch(state)
353 {
354 case 0:
355 if ('"'==b)
356 {
357 quote=true;
358 break;
359 }
360 if (';'==b)
361 state=1;
362 break;
363
364 case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
365 case 2: if ('h'==b) state=3; else state=0;break;
366 case 3: if ('a'==b) state=4; else state=0;break;
367 case 4: if ('r'==b) state=5; else state=0;break;
368 case 5: if ('s'==b) state=6; else state=0;break;
369 case 6: if ('e'==b) state=7; else state=0;break;
370 case 7: if ('t'==b) state=8; else state=0;break;
371
372 case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
373
374 case 9:
375 if (' '==b)
376 break;
377 if ('"'==b)
378 {
379 quote=true;
380 start=i+1;
381 state=10;
382 break;
383 }
384 start=i;
385 state=10;
386 break;
387
388 case 10:
389 if (!quote && (';'==b || ' '==b )||
390 (quote && '"'==b ))
391 return StringUtil.normalizeCharset(value,start,i-start);
392 }
393 }
394
395 if (state==10)
396 return StringUtil.normalizeCharset(value,start,i-start);
397
398 return null;
399 }
400
401 public static String inferCharsetFromContentType(String value)
402 {
403 return __encodings.get(value);
404 }
405
406 public static String getContentTypeWithoutCharset(String value)
407 {
408 int end=value.length();
409 int state=0;
410 int start=0;
411 boolean quote=false;
412 int i=0;
413 StringBuilder builder=null;
414 for (;i<end;i++)
415 {
416 char b = value.charAt(i);
417
418 if ('"'==b)
419 {
420 if (quote)
421 {
422 quote=false;
423 }
424 else
425 {
426 quote=true;
427 }
428
429 switch(state)
430 {
431 case 11:
432 builder.append(b);break;
433 case 10:
434 break;
435 case 9:
436 builder=new StringBuilder();
437 builder.append(value,0,start+1);
438 state=10;
439 break;
440 default:
441 start=i;
442 state=0;
443 }
444 continue;
445 }
446
447 if (quote)
448 {
449 if (builder!=null && state!=10)
450 builder.append(b);
451 continue;
452 }
453
454 switch(state)
455 {
456 case 0:
457 if (';'==b)
458 state=1;
459 else if (' '!=b)
460 start=i;
461 break;
462
463 case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
464 case 2: if ('h'==b) state=3; else state=0;break;
465 case 3: if ('a'==b) state=4; else state=0;break;
466 case 4: if ('r'==b) state=5; else state=0;break;
467 case 5: if ('s'==b) state=6; else state=0;break;
468 case 6: if ('e'==b) state=7; else state=0;break;
469 case 7: if ('t'==b) state=8; else state=0;break;
470 case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
471
472 case 9:
473 if (' '==b)
474 break;
475 builder=new StringBuilder();
476 builder.append(value,0,start+1);
477 state=10;
478 break;
479
480 case 10:
481 if (';'==b)
482 {
483 builder.append(b);
484 state=11;
485 }
486 break;
487 case 11:
488 if (' '!=b)
489 builder.append(b);
490 }
491 }
492 if (builder==null)
493 return value;
494 return builder.toString();
495
496 }
497 }