001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.lang.mimetype;
020
021import java.nio.charset.Charset;
022import java.util.Arrays;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028
029import lucee.commons.io.CharsetUtil;
030import lucee.commons.lang.StringUtil;
031import lucee.runtime.op.Caster;
032import lucee.runtime.type.UDF;
033import lucee.runtime.type.UDFPlus;
034import lucee.runtime.type.util.ListUtil;
035
036public class MimeType {
037        
038        private static int DEFAULT_MXB=100000;
039        private static double DEFAULT_MXT=5;
040        private static double DEFAULT_QUALITY=1;
041        private static Charset DEFAULT_CHARSET=null;
042        
043        public static final MimeType ALL = new MimeType(null,null,null);
044        public static final MimeType APPLICATION_JSON = new MimeType("application","json",null);
045        public static final MimeType APPLICATION_XML = new MimeType("application","xml",null);
046        public static final MimeType APPLICATION_WDDX = new MimeType("application","wddx",null);
047        public static final MimeType APPLICATION_CFML = new MimeType("application","cfml",null);
048        public static final MimeType APPLICATION_PLAIN = new MimeType("application","lazy",null);
049
050        public static final MimeType IMAGE_GIF = new MimeType("image","gif",null);
051        public static final MimeType IMAGE_JPG = new MimeType("image","jpeg",null);
052        public static final MimeType IMAGE_PNG = new MimeType("image","png",null);
053        public static final MimeType IMAGE_TIFF = new MimeType("image","tiff",null);
054        public static final MimeType IMAGE_BMP = new MimeType("image","bmp",null);
055        public static final MimeType IMAGE_WBMP = new MimeType("image","vnd.wap.wbmp",null);
056        public static final MimeType IMAGE_FBX = new MimeType("image","fbx",null);
057        public static final MimeType IMAGE_PNM = new MimeType("image","x-portable-anymap",null);
058        public static final MimeType IMAGE_PGM = new MimeType("image","x-portable-graymap",null);
059        public static final MimeType IMAGE_PBM = new MimeType("image","x-portable-bitmap",null);
060        public static final MimeType IMAGE_ICO = new MimeType("image","ico",null);
061        public static final MimeType IMAGE_PSD = new MimeType("image","psd",null);
062        public static final MimeType IMAGE_ASTERIX = new MimeType("image",null,null);
063        public static final MimeType APPLICATION_JAVA = new MimeType("application","java",null);
064        
065        public static final MimeType TEXT_HTML = new MimeType("text","html",null);
066        
067        private String type;
068        private String subtype;
069        //private double quality;
070        //private int mxb;
071        //private double mxt;
072        private Map<String,String> properties;
073        private double q=-1;
074        private Charset cs;
075        private boolean initCS=true;
076
077        
078        private MimeType(String type, String subtype, Map<String,String> properties) {
079                //if(quality<0 || quality>1)
080                //      throw new RuntimeException("quality must be a number between 0 and 1, now ["+quality+"]");
081
082                
083                this.type=type;
084                this.subtype=subtype;
085                this.properties=properties;
086                //this.quality=quality;
087                //this.mxb=mxb;
088                //this.mxt=mxt;
089        }
090        
091
092
093        private static MimeType getInstance(String type, String subtype, Map<String,String> properties) {
094                // TODO read this from a external File
095                if("text".equals(type)) {
096                        if("xml".equals(subtype)) return new MimeType("application", "xml", properties);
097                        if("x-json".equals(subtype)) return new MimeType("application", "json", properties);
098                        if("javascript".equals(subtype)) return new MimeType("application", "json", properties);
099                        if("x-javascript".equals(subtype)) return new MimeType("application", "json", properties);
100                        if("wddx".equals(subtype)) return new MimeType("application", "wddx", properties);
101                }
102                else if("application".equals(type)) {
103                        if("x-json".equals(subtype)) return new MimeType("application", "json", properties);
104                        if("javascript".equals(subtype)) return new MimeType("application", "json", properties);
105                        if("x-javascript".equals(subtype)) return new MimeType("application", "json", properties);
106                        
107                        if("jpg".equals(subtype)) return new MimeType("image", "jpeg", properties);
108                        if("x-jpg".equals(subtype)) return new MimeType("image", "jpeg", properties);
109                        
110                        if("png".equals(subtype)) return new MimeType("image", "png", properties);
111                        if("x-png".equals(subtype)) return new MimeType("image", "png", properties);
112
113                        if("tiff".equals(subtype)) return new MimeType("image", "tiff", properties);
114                        if("tif".equals(subtype)) return new MimeType("image", "tiff", properties);
115                        if("x-tiff".equals(subtype)) return new MimeType("image", "tiff", properties);
116                        if("x-tif".equals(subtype)) return new MimeType("image", "tiff", properties);
117
118                        if("fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
119                        if("x-fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
120                        if("vnd.fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
121                        if("vnd.netfpx".equals(subtype)) return new MimeType("image", "fpx", properties);
122                        
123                        if("ico".equals(subtype)) return new MimeType("image", "ico", properties);
124                        if("x-ico".equals(subtype)) return new MimeType("image", "ico", properties);
125                        if("x-icon".equals(subtype)) return new MimeType("image", "ico", properties);
126                        
127                        if("psd".equals(subtype)) return new MimeType("image", "psd", properties);
128                        if("x-photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
129                        if("photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
130                }
131                else if("image".equals(type)) {
132                        if("gi_".equals(subtype)) return new MimeType("image", "gif", properties);
133                        
134                        if("pjpeg".equals(subtype)) return new MimeType("image", "jpeg", properties);
135                        if("jpg".equals(subtype)) return new MimeType("image", "jpeg", properties);
136                        if("jpe".equals(subtype)) return new MimeType("image", "jpeg", properties);
137                        if("vnd.swiftview-jpeg".equals(subtype)) return new MimeType("image", "jpeg", properties);
138                        if("pipeg".equals(subtype)) return new MimeType("image", "jpeg", properties);
139                        if("jp_".equals(subtype)) return new MimeType("image", "jpeg", properties);
140
141                        if("x-png".equals(subtype)) return new MimeType("image", "png", properties);
142
143                        if("tif".equals(subtype)) return new MimeType("image", "tiff", properties);
144                        if("x-tif".equals(subtype)) return new MimeType("image", "tiff", properties);
145                        if("x-tiff".equals(subtype)) return new MimeType("image", "tiff", properties);
146
147                        if("x-fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
148                        if("vnd.fpx".equals(subtype)) return new MimeType("image", "fpx", properties);
149                        if("vnd.netfpx".equals(subtype)) return new MimeType("image", "fpx", properties);
150
151                        if("x-portable/graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
152                        if("portable graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
153                        if("x-pnm".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
154                        if("pnm".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
155
156                        if("x-portable/graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
157                        if("portable graymap".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
158                        if("x-pgm".equals(subtype)) return new MimeType("image", "x-portable-anymap", properties);
159                        if("pgm".equals(subtype)) return new MimeType("image", "x-portable-graymap", properties);
160
161                        if("portable bitmap".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);
162                        if("x-portable/bitmap".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);
163                        if("x-pbm".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);
164                        if("pbm".equals(subtype)) return new MimeType("image", "x-portable-bitmap", properties);
165
166                        if("x-ico".equals(subtype)) return new MimeType("image", "ico", properties);
167                        if("x-icon".equals(subtype)) return new MimeType("image", "ico", properties);
168
169                        if("x-photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
170                        if("photoshop".equals(subtype)) return new MimeType("image", "psd", properties);
171                }
172                else if("zz-application".equals(type)) {
173                        if("zz-winassoc-psd".equals(subtype)) return new MimeType("image", "psd", properties);
174                }
175                /*
176                
177                if("image/x-p".equals(mt)) return "ppm";
178                if("image/x-ppm".equals(mt)) return "ppm";
179                if("image/ppm".equals(mt)) return "ppm";
180
181                */
182                return new MimeType(type, subtype, properties);
183        }
184        
185        /**
186         * returns a mimetype that match given string
187         * @param strMimeType
188         * @return
189         */
190        public static MimeType getInstance(String strMimeType){
191                if(strMimeType==null) return ALL;
192                strMimeType=strMimeType.trim();
193                
194                if("*".equals(strMimeType) || strMimeType.length()==0) return ALL;
195                String[] arr = ListUtil.listToStringArray(strMimeType, ';');
196                if(arr.length==0) return ALL;
197                
198                String[] arrCT = ListUtil.listToStringArray(arr[0].trim(), '/');
199                
200                // subtype
201                String type=null,subtype=null;
202                
203                // type
204                if(arrCT.length>=1) {
205                        type=arrCT[0].trim();
206                        if("*".equals(type)) type=null;
207                        
208                        if(arrCT.length>=2) {
209                                subtype=arrCT[1].trim();
210                                if("*".equals(subtype)) subtype=null;   
211                        }
212                }
213                if(arr.length==1) return getInstance(type,subtype,null);
214                
215
216                final Map<String,String> properties=new HashMap<String, String>();
217                String entry;
218                String[] _arr;
219                for(int i=1;i<arr.length;i++){
220                        entry=arr[i].trim();
221                        _arr = ListUtil.listToStringArray(entry, '=');
222                        if(_arr.length>=2)
223                                properties.put(_arr[0].trim().toLowerCase(), _arr[1].trim());
224                        else if(_arr.length==1 && !_arr[0].trim().toLowerCase().equals("*"))
225                                properties.put(_arr[0].trim().toLowerCase(),"");
226                                
227                }
228                return getInstance(type,subtype,properties);
229        }
230
231        public static MimeType[] getInstances(String strMimeTypes, char delimiter) {
232                if(StringUtil.isEmpty(strMimeTypes,true)) return new MimeType[0];
233                String[] arr = ListUtil.trimItems(ListUtil.listToStringArray(strMimeTypes, delimiter));
234                MimeType[] mtes=new MimeType[arr.length];
235                for(int i=0;i<arr.length;i++){
236                        mtes[i]=getInstance(arr[i]);
237                }
238                return mtes;
239        }
240
241
242
243
244        /**
245         * @return the type
246         */
247        public String getType() {
248                return type;
249        }
250
251        /**
252         * @return the subtype
253         */
254        public String getSubtype() {
255                return subtype;
256        }
257        public Map<String, String> getProperties() {
258                return properties;
259        }
260        
261        /**
262         * @return the type
263         */
264        String getTypeNotNull() {
265                return type==null?"*":type;
266        }
267
268        /**
269         * @return the subtype
270         */
271        String getSubtypeNotNull() {
272                return subtype==null?"*":subtype;
273        }
274        
275
276        public double getQuality() {
277                if(q==-1){
278                        if(properties==null) q=DEFAULT_QUALITY;
279                        else q= Caster.toDoubleValue(getProperty("q"),DEFAULT_QUALITY);
280                }
281                return q;
282        }
283
284        public Charset getCharset() {
285                if(initCS){
286                        if(properties==null) cs=DEFAULT_CHARSET;
287                        else {
288                                String str = getProperty("charset");
289                                cs= StringUtil.isEmpty(str)?DEFAULT_CHARSET:CharsetUtil.toCharset(str);
290                        }
291                        initCS=false;
292                }
293                return cs;
294        }
295        
296        
297        /*
298        public int getMxb() {
299                return Caster.toIntValue(properties.get("mxb"),DEFAULT_MXB);
300        }
301
302        public double getMxt() {
303                return Caster.toDoubleValue(properties.get("mxt"),DEFAULT_MXT);
304        }*/
305
306        private String getProperty(String name) {
307                if(properties!=null) {
308                 String value = properties.get(name);
309                 if(value!=null)return value;
310                 
311                 Iterator<Entry<String, String>> it = properties.entrySet().iterator();
312                 Entry<String, String> e;
313                 while(it.hasNext()){
314                         e = it.next();
315                         if(name.equalsIgnoreCase(e.getKey())) return e.getValue();
316                 }
317                }
318                return null;
319        }
320
321
322
323        public boolean hasWildCards() {
324                return type==null || subtype==null;
325        }
326
327        /**
328         * checks if given mimetype is covered by current mimetype
329         * @param other
330         * @return
331         */
332        public boolean match(MimeType other){
333                if(this==other) return true;
334                if(type!=null && other.type!=null && !type.equals(other.type)) return false;
335                if(subtype!=null && other.subtype!=null && !subtype.equals(other.subtype)) return false;
336                return true;
337        }
338        
339        public MimeType bestMatch(MimeType[] others){
340                MimeType best=null;
341                
342                for(int i=0;i<others.length;i++){
343                        if(match(others[i]) && (best==null || best.getQuality()<others[i].getQuality())) {
344                                best=others[i];
345                        }
346                }
347                return best;
348        }
349        
350
351
352        /**
353         * checks if other is from the same type, just type and subtype are checked, properties (q,mxb,mxt) are ignored.
354         * @param other
355         * @return
356         */
357        public boolean same(MimeType other){
358                if(this==other) return true;
359                return getTypeNotNull().equals(other.getTypeNotNull()) && getSubtypeNotNull().equals(other.getSubtypeNotNull());
360        }
361        
362        public boolean equals(Object obj){
363                if(obj==this) return true;
364                
365                
366                MimeType other;
367                if(obj instanceof MimeType)
368                        other=(MimeType) obj;
369                else if(obj instanceof String)
370                        other=MimeType.getInstance((String)obj);
371                else
372                        return false;
373                
374                if(!same(other)) return false;
375                
376                return other.toString().equals(toString());
377        }
378        
379        public String toString(){
380                StringBuilder sb=new StringBuilder();
381                sb.append(type==null?"*":type);
382                sb.append("/");
383                sb.append(subtype==null?"*":subtype);
384                if(properties!=null){
385                        String[] keys = properties.keySet().toArray(new String[properties.size()]);
386                        Arrays.sort(keys);
387                        //Iterator<Entry<String, String>> it = properties.entrySet().iterator();
388                        //Entry<String, String> e;
389                        for(int i=0;i<keys.length;i++){
390                                sb.append("; ");
391                                sb.append(keys[i]);
392                                sb.append("=");
393                                sb.append(properties.get(keys[i]));
394                        }
395                }
396                return sb.toString();
397        }
398
399
400
401        public static MimeType toMimetype(int format, MimeType defaultValue) {
402                switch(format){
403                case UDF.RETURN_FORMAT_JSON:return MimeType.APPLICATION_JSON;
404                case UDF.RETURN_FORMAT_WDDX:return MimeType.APPLICATION_WDDX;
405                case UDF.RETURN_FORMAT_SERIALIZE:return MimeType.APPLICATION_CFML;
406                case UDF.RETURN_FORMAT_XML:return MimeType.APPLICATION_XML;
407                case UDF.RETURN_FORMAT_PLAIN:return MimeType.APPLICATION_PLAIN;
408                case UDFPlus.RETURN_FORMAT_JAVA:return MimeType.APPLICATION_JAVA;
409                
410                }
411                return defaultValue;
412        }
413        
414        
415
416        public static int toFormat(List<MimeType> mimeTypes,int ignore, int defaultValue) {
417                if(mimeTypes==null || mimeTypes.size()==0) return defaultValue;
418                Iterator<MimeType> it = mimeTypes.iterator();
419                int res;
420                while(it.hasNext()){
421                        res=toFormat(it.next(), -1);
422                        if(res!=-1 && res!=ignore) return res;
423                }
424                return defaultValue;
425        }
426        
427        public static int toFormat(MimeType mt, int defaultValue) {
428                if(mt==null) return defaultValue;
429                if(MimeType.APPLICATION_JSON.same(mt)) return  UDF.RETURN_FORMAT_JSON;
430                if(MimeType.APPLICATION_WDDX.same(mt)) return  UDF.RETURN_FORMAT_WDDX;
431                if(MimeType.APPLICATION_CFML.same(mt)) return  UDF.RETURN_FORMAT_SERIALIZE;
432                if(MimeType.APPLICATION_XML.same(mt)) return  UDF.RETURN_FORMAT_XML;
433                if(MimeType.APPLICATION_PLAIN.same(mt)) return  UDF.RETURN_FORMAT_PLAIN;
434                if(MimeType.APPLICATION_JAVA.same(mt)) return  UDFPlus.RETURN_FORMAT_JAVA;
435                return defaultValue;
436        }
437
438
439
440}