View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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 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     /** Constructor.
229      */
230     public MimeTypes()
231     {
232     }
233 
234     /* ------------------------------------------------------------ */
235     public synchronized Map<String,String> getMimeMap()
236     {
237         return _mimeMap;
238     }
239 
240     /* ------------------------------------------------------------ */
241     /**
242      * @param mimeMap A Map of file extension to mime-type.
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     /** Get the MIME type by filename extension.
256      * @param filename A file name
257      * @return MIME type matching the longest dot extension of the
258      * file name.
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     /** Set a mime mapping
295      * @param extension
296      * @param type
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 }