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