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