View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.util.Enumeration;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Locale;
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"),
57  
58          TEXT_HTML_8859_1("text/html;charset=ISO-8859-1"),
59          TEXT_PLAIN_8859_1("text/plain;charset=ISO-8859-1"),
60          TEXT_XML_8859_1("text/xml;charset=ISO-8859-1"),
61          TEXT_HTML_UTF_8("text/html;charset=UTF-8"),
62          TEXT_PLAIN_UTF_8("text/plain;charset=UTF-8"),
63          TEXT_XML_UTF_8("text/xml;charset=UTF-8"),
64          TEXT_JSON_UTF_8("text/json;charset=UTF-8");
65  
66  
67          /* ------------------------------------------------------------ */
68          private final String _string;
69          private final ByteBuffer _buffer;
70          private final Charset _charset;
71  
72          /* ------------------------------------------------------------ */
73          Type(String s)
74          {
75              _string=s;
76              _buffer=BufferUtil.toBuffer(s);
77              
78              int i=s.toLowerCase(Locale.ENGLISH).indexOf("charset=");
79              _charset=(i>0)?Charset.forName(s.substring(i+8)):null;
80          }
81  
82          /* ------------------------------------------------------------ */
83          public ByteBuffer asBuffer()
84          {
85              return _buffer.asReadOnlyBuffer();
86          }
87          
88          /* ------------------------------------------------------------ */
89          public Charset getCharset()
90          {
91              return _charset;
92          }
93          
94          /* ------------------------------------------------------------ */
95          public boolean is(String s)
96          {
97              return _string.equalsIgnoreCase(s);    
98          }
99  
100         /* ------------------------------------------------------------ */
101         public String asString()
102         {
103             return _string;
104         }
105         
106         /* ------------------------------------------------------------ */
107         @Override
108         public String toString()
109         {
110             return _string;
111         }
112     }
113 
114     /* ------------------------------------------------------------ */
115     private static final Logger LOG = Log.getLogger(MimeTypes.class);
116     public  final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
117     private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
118     private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
119     private final static Map<String,String> __encodings = new HashMap<String,String>();
120 
121     static
122     {
123         for (MimeTypes.Type type : MimeTypes.Type.values())
124         {
125             CACHE.put(type.toString(),type);
126             TYPES.put(type.toString(),type.asBuffer());
127 
128             int charset=type.toString().indexOf(";charset=");
129             if (charset>0)
130             {
131                 CACHE.put(type.toString().replace(";charset=","; charset="),type);
132                 TYPES.put(type.toString().replace(";charset=","; charset="),type.asBuffer());
133             }
134         }
135 
136         try
137         {
138             ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
139             Enumeration i = mime.getKeys();
140             while(i.hasMoreElements())
141             {
142                 String ext = (String)i.nextElement();
143                 String m = mime.getString(ext);
144                 __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
145             }
146         }
147         catch(MissingResourceException e)
148         {
149             LOG.warn(e.toString());
150             LOG.debug(e);
151         }
152 
153         try
154         {
155             ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
156             Enumeration<String> i = encoding.getKeys();
157             while(i.hasMoreElements())
158             {
159                 String type = i.nextElement();
160                 __encodings.put(type,encoding.getString(type));
161             }
162         }
163         catch(MissingResourceException e)
164         {
165             LOG.warn(e.toString());
166             LOG.debug(e);
167         }
168     }
169 
170 
171     /* ------------------------------------------------------------ */
172     private final Map<String,String> _mimeMap=new HashMap<String,String>();
173 
174     /* ------------------------------------------------------------ */
175     /** Constructor.
176      */
177     public MimeTypes()
178     {
179     }
180 
181     /* ------------------------------------------------------------ */
182     public synchronized Map<String,String> getMimeMap()
183     {
184         return _mimeMap;
185     }
186 
187     /* ------------------------------------------------------------ */
188     /**
189      * @param mimeMap A Map of file extension to mime-type.
190      */
191     public void setMimeMap(Map<String,String> mimeMap)
192     {
193         _mimeMap.clear();
194         if (mimeMap!=null)
195         {
196             for (Entry<String, String> ext : mimeMap.entrySet())
197                 _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
198         }
199     }
200     
201     /* ------------------------------------------------------------ */
202     /** Get the MIME type by filename extension.
203      * @param filename A file name
204      * @return MIME type matching the longest dot extension of the
205      * file name.
206      */
207     public String getMimeByExtension(String filename)
208     {
209         String type=null;
210 
211         if (filename!=null)
212         {
213             int i=-1;
214             while(type==null)
215             {
216                 i=filename.indexOf(".",i+1);
217 
218                 if (i<0 || i>=filename.length())
219                     break;
220 
221                 String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
222                 if (_mimeMap!=null)
223                     type=_mimeMap.get(ext);
224                 if (type==null)
225                     type=__dftMimeMap.get(ext);
226             }
227         }
228 
229         if (type==null)
230         {
231             if (_mimeMap!=null)
232                 type=_mimeMap.get("*");
233             if (type==null)
234                 type=__dftMimeMap.get("*");
235         }
236 
237         return type;
238     }
239 
240     /* ------------------------------------------------------------ */
241     /** Set a mime mapping
242      * @param extension
243      * @param type
244      */
245     public void addMimeMapping(String extension,String type)
246     {
247         _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
248     }
249 
250     /* ------------------------------------------------------------ */
251     public static Set<String> getKnownMimeTypes()
252     {
253         return new HashSet<>(__dftMimeMap.values());
254     }
255     
256     /* ------------------------------------------------------------ */
257     private static String normalizeMimeType(String type)
258     {
259         MimeTypes.Type t =CACHE.get(type);
260         if (t!=null)
261             return t.asString();
262 
263         return StringUtil.asciiToLowerCase(type);
264     }
265 
266     /* ------------------------------------------------------------ */
267     public static String getCharsetFromContentType(String value)
268     {
269         if (value==null)
270             return null;
271         int end=value.length();
272         int state=0;
273         int start=0;
274         boolean quote=false;
275         int i=0;
276         for (;i<end;i++)
277         {
278             char b = value.charAt(i);
279 
280             if (quote && state!=10)
281             {
282                 if ('"'==b)
283                     quote=false;
284                 continue;
285             }
286 
287             switch(state)
288             {
289                 case 0:
290                     if ('"'==b)
291                     {
292                         quote=true;
293                         break;
294                     }
295                     if (';'==b)
296                         state=1;
297                     break;
298 
299                 case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
300                 case 2: if ('h'==b) state=3; else state=0;break;
301                 case 3: if ('a'==b) state=4; else state=0;break;
302                 case 4: if ('r'==b) state=5; else state=0;break;
303                 case 5: if ('s'==b) state=6; else state=0;break;
304                 case 6: if ('e'==b) state=7; else state=0;break;
305                 case 7: if ('t'==b) state=8; else state=0;break;
306 
307                 case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
308 
309                 case 9:
310                     if (' '==b)
311                         break;
312                     if ('"'==b)
313                     {
314                         quote=true;
315                         start=i+1;
316                         state=10;
317                         break;
318                     }
319                     start=i;
320                     state=10;
321                     break;
322 
323                 case 10:
324                     if (!quote && (';'==b || ' '==b )||
325                             (quote && '"'==b ))
326                         return StringUtil.normalizeCharset(value,start,i-start);
327             }
328         }
329 
330         if (state==10)
331             return StringUtil.normalizeCharset(value,start,i-start);
332 
333         return null;
334     }
335 
336     public static String inferCharsetFromContentType(String value)
337     {
338         return __encodings.get(value);
339     }
340     
341     public static String getContentTypeWithoutCharset(String value)
342     {
343         int end=value.length();
344         int state=0;
345         int start=0;
346         boolean quote=false;
347         int i=0;
348         StringBuilder builder=null;
349         for (;i<end;i++)
350         {
351             char b = value.charAt(i);
352 
353             if ('"'==b)
354             {
355                 if (quote)
356                 {
357                     quote=false;
358                 }
359                 else
360                 {
361                     quote=true;
362                 }
363                 
364                 switch(state)
365                 {
366                     case 11:
367                         builder.append(b);break;
368                     case 10:
369                         break;
370                     case 9:
371                         builder=new StringBuilder();
372                         builder.append(value,0,start+1);
373                         state=10;
374                         break;
375                     default:
376                         start=i;
377                         state=0;           
378                 }
379                 continue;
380             }
381             
382             if (quote)
383             {
384                 if (builder!=null && state!=10)
385                     builder.append(b);
386                 continue;
387             }
388 
389             switch(state)
390             {
391                 case 0:
392                     if (';'==b)
393                         state=1;
394                     else if (' '!=b)
395                         start=i;
396                     break;
397 
398                 case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
399                 case 2: if ('h'==b) state=3; else state=0;break;
400                 case 3: if ('a'==b) state=4; else state=0;break;
401                 case 4: if ('r'==b) state=5; else state=0;break;
402                 case 5: if ('s'==b) state=6; else state=0;break;
403                 case 6: if ('e'==b) state=7; else state=0;break;
404                 case 7: if ('t'==b) state=8; else state=0;break;
405                 case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
406 
407                 case 9:
408                     if (' '==b)
409                         break;
410                     builder=new StringBuilder();
411                     builder.append(value,0,start+1);
412                     state=10;
413                     break;
414 
415                 case 10:
416                     if (';'==b)
417                     {
418                         builder.append(b);
419                         state=11;
420                     }
421                     break;
422                 case 11:
423                     if (' '!=b)
424                         builder.append(b);
425             }
426         }
427         if (builder==null)
428             return value;
429         return builder.toString();
430 
431     }
432 }