View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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     /** Constructor.
240      */
241     public MimeTypes()
242     {
243     }
244 
245     /* ------------------------------------------------------------ */
246     public synchronized Map<String,String> getMimeMap()
247     {
248         return _mimeMap;
249     }
250 
251     /* ------------------------------------------------------------ */
252     /**
253      * @param mimeMap A Map of file extension to mime-type.
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     /** Get the MIME type by filename extension.
267      * @param filename A file name
268      * @return MIME type matching the longest dot extension of the
269      * file name.
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     /** Set a mime mapping
306      * @param extension the extension
307      * @param type the mime type
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 }