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}