/* * Copyright 2007-2009 Medsea Business Solutions S.L. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package eu.medsea.mimeutil.detector; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.net.URL; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import com.delcyon.capo.CapoApplication; import eu.medsea.mimeutil.MimeException; import eu.medsea.mimeutil.MimeType; import eu.medsea.mimeutil.MimeUtil; /** * <p> * The extension mime mappings are loaded in the following way. * <ol> * <li>Load the properties file from the mime utility jar named * <code>eu.medsea.mimeutil.mime-types.properties</code>.</li> * <li>Locate and load a file named <code>.mime-types.properties</code> from the * users home directory if one exists.</li> * <li>Locate and load a file named <code>mime-types.properties</code> from the * classpath if one exists</li> * <li>locate and load a file named by the JVM property * <code>mime-mappings</code> i.e. * <code>-Dmime-mappings=../my-mime-types.properties</code></li> * </ol> * Each property file loaded will add to the list of extensions understood by MimeUtil. * If there is a clash of extension names then the last one loaded wins, i.e they are not adative, this makes it * possible to completely change the mime types associated to a file extension declared in previously loaded property files. * The extensions are also case sensitive meaning that bat, bAt, BAT and Bat can all be recognised individually. If however, * no match is found using case sensitive matching then it will perform an insensitive match by lower casing the extension * of the file to be matched first. * </p> * <p> * Fortunately, we have compiled a relatively large list of mappings into a java properties file from information gleaned from many sites on the Internet. * This file resides in the eu.medsea.util.mime-types.properties file and is not guaranteed to be correct or contain all the known mappings for a file * extension type. This is not a complete or exhaustive list as that would have proven too difficult to compile for this project. * So instead we give you the opportunity to extend and override these mappings for yourself as defined above. * Obviously, to use this method you don't actually need a file object, you just need a file name with an extension. Also, if you have given or renamed a * file using a different extension than the one that it would normally be associated with then this mapping will return the wrong mime-type and * if the file has no extension at all, such as Make, then it's not going to be possible to determine a mime type using this technique * </p> * <p> * We acquired many mappings from many different sources on the net for the * extension mappings. The internal list is quite large and there can be many * associated mime types. These may not match what you are expecting so you can * add the mapping you want to change to your own property file following the * rules above. If you provide a mapping for an extension then any previously * loaded mappings will be removed and only the mappings you define will be * returned. This can be used to map certain extensions that are incorrectly * returned for our environment defined in the internal property file. * </p> * <p> * If we have not provided a mapping for a file extension that you know the mime * type for you can add this to your custom property files so that a correct mime * type is returned for you. * <p> * <p> * We use the <code>application/directory</code> mime type to identify * directories. Even though this is not an official mime type it seems to be * well accepted on the net as an unofficial mime type so we thought it was OK * for us to use as well. * </p> * <p> * This class is auto loaded by MimeUtil as it has an entry in the file called MimeDetectors. * MimeUtil reads this file at startup and calls Class.forName() on each entry found. This mean * the MimeDetector must have a no arg constructor. * </p> * * @author Steven McArdle. * */ public class ExtensionMimeDetector extends MimeDetector { // Extension MimeTypes private static Map extMimeTypes; public ExtensionMimeDetector() { ExtensionMimeDetector.initMimeTypes(); } public String getDescription() { return "Get the mime types of file extensions"; } /** * Get the mime type of a file using extension mappings. The file path * can be a relative or absolute path or can refer to a completely non-existent file as * only the extension is important here. * * @param file points to a file or directory. May not actually exist * @return collection of the matched mime types. * @throws MimeException if errors occur. */ public Collection getMimeTypesFile(final File file) throws MimeException { return getMimeTypesFileName(file.getName()); } /** * Get the mime type of a URL using extension mappings. Only the extension is important here. * * @param url is a valid URL * @return collection of the matched mime types. * @throws MimeException if errors occur. */ public Collection getMimeTypesURL(final URL url) throws MimeException { return getMimeTypesFileName(url.getPath()); } /** * Get the mime type of a file name using file name extension mappings. The file name path * can be a relative or absolute path or can refer to a completely non-existent file as * only the extension is important here. * * @param fileName points to a file or directory. May not actually exist * @return collection of the matched mime types. * @throws MimeException if errors occur. */ public Collection getMimeTypesFileName(final String fileName) throws MimeException { Collection mimeTypes = new HashSet(); String fileExtension = MimeUtil.getExtension(fileName); while(fileExtension.length() != 0) { String types = null; // First try case sensitive types = (String) extMimeTypes.get(fileExtension); if (types != null) { String [] mimeTypeArray = types.split(","); for(int i = 0; i < mimeTypeArray.length; i++) { mimeTypes.add(new MimeType(mimeTypeArray[i])); } return mimeTypes; } if(mimeTypes.isEmpty()) { // Failed to find case insensitive extension so lets try again with // lowercase types = (String) extMimeTypes.get(fileExtension.toLowerCase()); if (types != null) { String [] mimeTypeArray = types.split(","); for(int i = 0; i < mimeTypeArray.length; i++) { mimeTypes.add(new MimeType(mimeTypeArray[i])); } return mimeTypes; } } fileExtension = MimeUtil.getExtension(fileExtension); } return mimeTypes; } /* * This loads the mime-types.properties files that define mime types based * on file extensions using the following load sequence 1. Loads the * property file from the mime utility jar named * eu.medsea.mime.mime-types.properties. 2. Locates and loads a file named * .mime-types.properties from the users home directory if one exists. 3. * Locates and loads a file named mime-types.properties from the classpath * if one exists 4. locates and loads a file named by the JVM property * mime-mappings i.e. -Dmime-mappings=../my-mime-types.properties */ private static void initMimeTypes() { InputStream is = null; extMimeTypes = new Properties(); try { // Load the file extension mappings from the internal property file and // then // from the custom property files if they can be found try { // Load the default supplied mime types is = MimeUtil.class.getClassLoader().getResourceAsStream( "eu/medsea/mimeutil/mime-types.properties"); if(is != null) { ((Properties) extMimeTypes).load(is); } }catch (Exception e) { // log the error but don't throw the exception up the stack CapoApplication.logger.log(Level.SEVERE,"Error loading internal mime-types.properties", e); }finally { is = closeStream(is); } // Load any .mime-types.properties from the users home directory try { File f = new File(System.getProperty("user.home") + File.separator + ".mime-types.properties"); if (f.exists()) { is = new FileInputStream(f); if (is != null) { CapoApplication.logger.fine("Found a custom .mime-types.properties file in the users home directory."); Properties props = new Properties(); props.load(is); if (props.size() > 0) { extMimeTypes.putAll(props); } CapoApplication.logger.fine("Successfully parsed .mime-types.properties from users home directory."); } } } catch (Exception e) { CapoApplication.logger.log(Level.SEVERE,"Failed to parse .magic.mime file from users home directory. File will be ignored.",e); } finally { is = closeStream(is); } // Load any classpath provided mime types that either extend or // override the default mime type entries. Could also be in jar files. // Get an enumeration of all files on the classpath with this name. They could be in jar files as well try { Enumeration e = MimeUtil.class.getClassLoader().getResources("mime-types.properties"); while(e.hasMoreElements()) { URL url = (URL)e.nextElement(); CapoApplication.logger.fine("Found custom mime-types.properties file on the classpath [" + url + "]."); Properties props = new Properties(); try { is = url.openStream(); if(is != null) { props.load(is); if(props.size() > 0) { extMimeTypes.putAll(props); CapoApplication.logger.fine("Successfully loaded custome mime-type.properties file [" + url + "] from classpath."); } } }catch(Exception ex) { CapoApplication.logger.log(Level.SEVERE,"Failed while loading custom mime-type.properties file [" + url + "] from classpath. File will be ignored."); } } }catch(Exception e) { CapoApplication.logger.log(Level.SEVERE,"Problem while processing mime-types.properties files(s) from classpath. Files will be ignored.", e); } finally { is = closeStream(is); } try { // Load any mime extension mappings file defined with the JVM // property -Dmime-mappings=../my/custom/mappings.properties String fname = System.getProperty("mime-mappings"); if (fname != null && fname.length() != 0) { is = new FileInputStream(fname); if (is != null) { CapoApplication.logger.fine("Found a custom mime-mappings property defined by the property -Dmime-mappings [" + System.getProperty("mime-mappings") + "]."); Properties props = new Properties(); props.load(is); if (props.size() > 0) { extMimeTypes.putAll(props); } CapoApplication.logger.fine("Successfully loaded the mime mappings file from property -Dmime-mappings [" + System.getProperty("mime-mappings") + "]."); } } } catch (Exception ex) { CapoApplication.logger.log(Level.SEVERE,"Failed to load the mime-mappings file defined by the property -Dmime-mappings [" + System.getProperty("mime-mappings") + "]."); } finally { is = closeStream(is); } } finally { // Load the mime types into the known mime types map of MimeUtil Iterator it = extMimeTypes.values().iterator(); while (it.hasNext()) { String[] types = ((String) it.next()).split(","); for (int i = 0; i < types.length; i++) { MimeUtil.addKnownMimeType(types[i]); } } } } /** * This method is required by the abstract MimeDetector class. As we do not support extension mapping of streams * we just throw an {@link UnsupportedOperationException}. This ensures that the getMimeTypes(...) methods ignore this * method. We could also have just returned an empty collection. */ public Collection getMimeTypesInputStream(InputStream in) throws UnsupportedOperationException { throw new UnsupportedOperationException("This MimeDetector does not support detection from streams."); } /** * This method is required by the abstract MimeDetector class. As we do not support extension mapping of byte arrays * we just throw an {@link UnsupportedOperationException}. This ensures that the getMimeTypes(...) methods ignore this * method. We could also have just returned an empty collection. */ public Collection getMimeTypesByteArray(byte[] data) throws UnsupportedOperationException { throw new UnsupportedOperationException("This MimeDetector does not support detection from byte arrays."); } }