1   // ========================================================================
2   // Copyright 2000-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty;
16  
17  import java.util.Enumeration;
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.Map;
21  import java.util.MissingResourceException;
22  import java.util.ResourceBundle;
23  
24  import org.mortbay.io.Buffer;
25  import org.mortbay.io.BufferCache;
26  import org.mortbay.io.BufferCache.CachedBuffer;
27  import org.mortbay.log.Log;
28  import org.mortbay.util.StringUtil;
29  
30  
31  /* ------------------------------------------------------------ */
32  /** 
33   * @author Greg Wilkins
34   */
35  public class MimeTypes
36  {
37      public final static String
38        FORM_ENCODED="application/x-www-form-urlencoded",
39        MESSAGE_HTTP="message/http",
40        MULTIPART_BYTERANGES="multipart/byteranges",
41        TEXT_HTML="text/html",
42        TEXT_PLAIN="text/plain",
43        TEXT_XML="text/xml",
44        TEXT_HTML_8859_1="text/html; charset=iso-8859-1",
45        TEXT_PLAIN_8859_1="text/plain; charset=iso-8859-1",
46        TEXT_XML_8859_1="text/xml; charset=iso-8859-1",
47        TEXT_HTML_UTF_8="text/html; charset=utf-8",
48        TEXT_PLAIN_UTF_8="text/plain; charset=utf-8",
49        TEXT_XML_UTF_8="text/xml; charset=utf-8";
50      
51  
52      private final static int
53  	FORM_ENCODED_ORDINAL=1,
54      	MESSAGE_HTTP_ORDINAL=2,
55      	MULTIPART_BYTERANGES_ORDINAL=3,
56      	TEXT_HTML_ORDINAL=4,
57  	TEXT_PLAIN_ORDINAL=5,
58  	TEXT_XML_ORDINAL=6,
59          TEXT_HTML_8859_1_ORDINAL=7,
60          TEXT_PLAIN_8859_1_ORDINAL=8,
61          TEXT_XML_8859_1_ORDINAL=9,
62          TEXT_HTML_UTF_8_ORDINAL=10,
63          TEXT_PLAIN_UTF_8_ORDINAL=11,
64          TEXT_XML_UTF_8_ORDINAL=12;
65      
66      private static int __index=13;
67      
68      public final static BufferCache CACHE = new BufferCache(); 
69  
70      public final static CachedBuffer
71      	FORM_ENCODED_BUFFER=CACHE.add(FORM_ENCODED,FORM_ENCODED_ORDINAL),
72      	MESSAGE_HTTP_BUFFER=CACHE.add(MESSAGE_HTTP, MESSAGE_HTTP_ORDINAL),
73      	MULTIPART_BYTERANGES_BUFFER=CACHE.add(MULTIPART_BYTERANGES,MULTIPART_BYTERANGES_ORDINAL),
74          
75          TEXT_HTML_BUFFER=CACHE.add(TEXT_HTML,TEXT_HTML_ORDINAL),
76          TEXT_PLAIN_BUFFER=CACHE.add(TEXT_PLAIN,TEXT_PLAIN_ORDINAL),
77          TEXT_XML_BUFFER=CACHE.add(TEXT_XML,TEXT_XML_ORDINAL),
78          
79      	TEXT_HTML_8859_1_BUFFER=new CachedBuffer(TEXT_HTML_8859_1,TEXT_HTML_8859_1_ORDINAL),
80      	TEXT_PLAIN_8859_1_BUFFER=new CachedBuffer(TEXT_PLAIN_8859_1,TEXT_PLAIN_8859_1_ORDINAL),
81      	TEXT_XML_8859_1_BUFFER=new CachedBuffer(TEXT_XML_8859_1,TEXT_XML_8859_1_ORDINAL),
82          TEXT_HTML_UTF_8_BUFFER=new CachedBuffer(TEXT_HTML_UTF_8,TEXT_HTML_UTF_8_ORDINAL),
83          TEXT_PLAIN_UTF_8_BUFFER=new CachedBuffer(TEXT_PLAIN_UTF_8,TEXT_PLAIN_UTF_8_ORDINAL),
84          TEXT_XML_UTF_8_BUFFER=new CachedBuffer(TEXT_XML_UTF_8,TEXT_XML_UTF_8_ORDINAL);
85      
86      
87      /* ------------------------------------------------------------ */
88      /* ------------------------------------------------------------ */
89      private final static Map __dftMimeMap = new HashMap();
90      private final static Map __encodings = new HashMap();
91      static
92      {
93          try
94          {
95              ResourceBundle mime = ResourceBundle.getBundle("org/mortbay/jetty/mime");
96              Enumeration i = mime.getKeys();
97              while(i.hasMoreElements())
98              {
99                  String ext = (String)i.nextElement();
100                 String m = mime.getString(ext);
101                 __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
102             }
103         }
104         catch(MissingResourceException e)
105         {
106             Log.warn(e.toString());
107             Log.debug(e);
108         }
109 
110         try
111         {
112             ResourceBundle encoding = ResourceBundle.getBundle("org/mortbay/jetty/encoding");
113             Enumeration i = encoding.getKeys();
114             while(i.hasMoreElements())
115             {
116                 Buffer type = normalizeMimeType((String)i.nextElement());
117                 __encodings.put(type,encoding.getString(type.toString()));
118             }
119         }
120         catch(MissingResourceException e)
121         {
122             Log.warn(e.toString());
123             Log.debug(e);
124         }
125 
126         
127         TEXT_HTML_BUFFER.setAssociate("ISO-8859-1",TEXT_HTML_8859_1_BUFFER);
128         TEXT_HTML_BUFFER.setAssociate("ISO_8859_1",TEXT_HTML_8859_1_BUFFER);
129         TEXT_HTML_BUFFER.setAssociate("iso-8859-1",TEXT_HTML_8859_1_BUFFER);
130         TEXT_PLAIN_BUFFER.setAssociate("ISO-8859-1",TEXT_PLAIN_8859_1_BUFFER);
131         TEXT_PLAIN_BUFFER.setAssociate("ISO_8859_1",TEXT_PLAIN_8859_1_BUFFER);
132         TEXT_PLAIN_BUFFER.setAssociate("iso-8859-1",TEXT_PLAIN_8859_1_BUFFER);
133         TEXT_XML_BUFFER.setAssociate("ISO-8859-1",TEXT_XML_8859_1_BUFFER);
134         TEXT_XML_BUFFER.setAssociate("ISO_8859_1",TEXT_XML_8859_1_BUFFER);
135         TEXT_XML_BUFFER.setAssociate("iso-8859-1",TEXT_XML_8859_1_BUFFER);
136 
137         TEXT_HTML_BUFFER.setAssociate("UTF-8",TEXT_HTML_UTF_8_BUFFER);
138         TEXT_HTML_BUFFER.setAssociate("UTF8",TEXT_HTML_UTF_8_BUFFER);
139         TEXT_HTML_BUFFER.setAssociate("utf8",TEXT_HTML_UTF_8_BUFFER);
140         TEXT_HTML_BUFFER.setAssociate("utf-8",TEXT_HTML_UTF_8_BUFFER);
141         TEXT_PLAIN_BUFFER.setAssociate("UTF-8",TEXT_PLAIN_UTF_8_BUFFER);
142         TEXT_PLAIN_BUFFER.setAssociate("UTF8",TEXT_PLAIN_UTF_8_BUFFER);
143         TEXT_PLAIN_BUFFER.setAssociate("utf-8",TEXT_PLAIN_UTF_8_BUFFER);
144         TEXT_XML_BUFFER.setAssociate("UTF-8",TEXT_XML_UTF_8_BUFFER);
145         TEXT_XML_BUFFER.setAssociate("utf8",TEXT_XML_UTF_8_BUFFER);
146         TEXT_XML_BUFFER.setAssociate("UTF8",TEXT_XML_UTF_8_BUFFER);
147         TEXT_XML_BUFFER.setAssociate("utf-8",TEXT_XML_UTF_8_BUFFER);
148     }
149 
150 
151     /* ------------------------------------------------------------ */
152     private Map _mimeMap;
153     
154     /* ------------------------------------------------------------ */
155     /** Constructor.
156      */
157     public MimeTypes()
158     {
159     }
160 
161     /* ------------------------------------------------------------ */
162     public synchronized Map getMimeMap()
163     {
164         return _mimeMap;
165     }
166 
167     /* ------------------------------------------------------------ */
168     /**
169      * @param mimeMap A Map of file extension to mime-type.
170      */
171     public void setMimeMap(Map mimeMap)
172     {
173         if (mimeMap==null)
174         {
175             _mimeMap=null;
176             return;
177         }
178         
179         Map m=new HashMap();
180         Iterator i=mimeMap.entrySet().iterator();
181         while (i.hasNext())
182         {
183             Map.Entry entry = (Map.Entry)i.next();
184             m.put(entry.getKey(),normalizeMimeType(entry.getValue().toString()));
185         }
186         _mimeMap=m;
187     }
188 
189     /* ------------------------------------------------------------ */
190     /** Get the MIME type by filename extension.
191      * @param filename A file name
192      * @return MIME type matching the longest dot extension of the
193      * file name.
194      */
195     public Buffer getMimeByExtension(String filename)
196     {
197         Buffer type=null;
198 
199         if (filename!=null)
200         {
201             int i=-1;
202             while(type==null)
203             {
204                 i=filename.indexOf(".",i+1);
205 
206                 if (i<0 || i>=filename.length())
207                     break;
208 
209                 String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
210                 if (_mimeMap!=null)
211                     type = (Buffer)_mimeMap.get(ext);
212                 if (type==null)
213                     type=(Buffer)__dftMimeMap.get(ext);
214             }
215         }
216 
217         if (type==null)
218         {
219             if (_mimeMap!=null)
220                 type=(Buffer)_mimeMap.get("*");
221              if (type==null)
222                  type=(Buffer)__dftMimeMap.get("*");
223         }
224 
225         return type;
226     }
227 
228     /* ------------------------------------------------------------ */
229     /** Set a mime mapping
230      * @param extension
231      * @param type
232      */
233     public void addMimeMapping(String extension,String type)
234     {
235         if (_mimeMap==null)
236             _mimeMap=new HashMap();
237         
238         _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
239     }
240 
241     /* ------------------------------------------------------------ */
242     private static synchronized Buffer normalizeMimeType(String type)
243     {
244         Buffer b =CACHE.get(type);
245         if (b==null)
246             b=CACHE.add(type,__index++);
247         return b;
248     }
249 
250     /* ------------------------------------------------------------ */
251     public static String getCharsetFromContentType(Buffer value)
252     {
253         if (value instanceof CachedBuffer)
254         {
255             switch(((CachedBuffer)value).getOrdinal())
256             {
257                 case TEXT_HTML_8859_1_ORDINAL:
258                 case TEXT_PLAIN_8859_1_ORDINAL:
259                 case TEXT_XML_8859_1_ORDINAL:
260                     return StringUtil.__ISO_8859_1;
261                     
262                 case TEXT_HTML_UTF_8_ORDINAL:
263                 case TEXT_PLAIN_UTF_8_ORDINAL:
264                 case TEXT_XML_UTF_8_ORDINAL:
265                     return StringUtil.__UTF8;
266             }
267         }
268         
269         int i=value.getIndex();
270         int end=value.putIndex();
271         int state=0;
272         int start=0;
273         boolean quote=false;
274         for (;i<end;i++)
275         {
276             byte b = value.peek(i);
277             
278             if (quote && state!=10)
279             {
280                 if ('"'==b)
281                     quote=false;
282                 continue;
283             }
284                 
285             switch(state)
286             {
287                 case 0:
288                     if ('"'==b)
289                     {
290                         quote=true;
291                         break;
292                     }
293                     if (';'==b)
294                         state=1;
295                     break;
296 
297                 case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
298                 case 2: if ('h'==b) state=3; else state=0;break;
299                 case 3: if ('a'==b) state=4; else state=0;break;
300                 case 4: if ('r'==b) state=5; else state=0;break;
301                 case 5: if ('s'==b) state=6; else state=0;break;
302                 case 6: if ('e'==b) state=7; else state=0;break;
303                 case 7: if ('t'==b) state=8; else state=0;break;
304 
305                 case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
306                 
307                 case 9: 
308                     if (' '==b) 
309                         break;
310                     if ('"'==b) 
311                     {
312                         quote=true;
313                         start=i+1;
314                         state=10;
315                         break;
316                     }
317                     start=i;
318                     state=10;
319                     break;
320                     
321                 case 10:
322                     if (!quote && (';'==b || ' '==b )||
323                         (quote && '"'==b ))
324                         return CACHE.lookup(value.peek(start,i-start)).toString();
325             }
326         }    
327         
328         if (state==10)
329             return CACHE.lookup(value.peek(start,i-start)).toString();
330         return null;
331         
332     }
333 
334 
335 }