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