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