/* * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Portions copyright 2006-2009 James Murty. Please see LICENSE.txt * for applicable license terms and NOTICE.txt for applicable notices. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.s3.util; import com.amazonaws.util.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; /** * Utility class that maintains a listing of known Mimetypes, and determines the * mimetype of files based on file extensions. * <p> * This class is obtained with the {#link {@link #getInstance()} method. It has * a default extension to mimetype mapping which is taken from the Apache HTTP * server's mime.types file. New mappings can be added or overwritten by * {@link #registerMimetype(String, String)}. * <p> * The format for mime type setting documents is: * <code>mimetype <Space | Tab>+ extension (<Space|Tab>+ extension)*</code>. Any * blank lines in the file are ignored, as are lines starting with * <code>#</code> which are considered comments. Lines that have a mimetype but * no associated extensions are also ignored. */ public final class Mimetypes { private static final Log log = LogFactory.getLog(Mimetypes.class); /** The default XML mimetype: application/xml */ public static final String MIMETYPE_XML = "application/xml"; /** The default HTML mimetype: text/html */ public static final String MIMETYPE_HTML = "text/html"; /** The default binary mimetype: application/octet-stream */ public static final String MIMETYPE_OCTET_STREAM = "application/octet-stream"; /** The default gzip mimetype: application/x-gzip */ public static final String MIMETYPE_GZIP = "application/x-gzip"; private static Mimetypes mimetypes = null; /** * Map that stores file extensions as keys, and the corresponding mimetype * as values. */ private final HashMap<String, String> extensionToMimetypeMap; Mimetypes() { extensionToMimetypeMap = new HashMap<String, String>(); // Default mime types extensionToMimetypeMap.put("3gp", "video/3gpp"); extensionToMimetypeMap.put("ai", "application/postscript"); extensionToMimetypeMap.put("aif", "audio/x-aiff"); extensionToMimetypeMap.put("aifc", "audio/x-aiff"); extensionToMimetypeMap.put("aiff", "audio/x-aiff"); extensionToMimetypeMap.put("asc", "text/plain"); extensionToMimetypeMap.put("atom", "application/atom+xml"); extensionToMimetypeMap.put("au", "audio/basic"); extensionToMimetypeMap.put("avi", "video/x-msvideo"); extensionToMimetypeMap.put("bcpio", "application/x-bcpio"); extensionToMimetypeMap.put("bin", "application/octet-stream"); extensionToMimetypeMap.put("bmp", "image/bmp"); extensionToMimetypeMap.put("cdf", "application/x-netcdf"); extensionToMimetypeMap.put("cgm", "image/cgm"); extensionToMimetypeMap.put("class", "application/octet-stream"); extensionToMimetypeMap.put("cpio", "application/x-cpio"); extensionToMimetypeMap.put("cpt", "application/mac-compactpro"); extensionToMimetypeMap.put("csh", "application/x-csh"); extensionToMimetypeMap.put("css", "text/css"); extensionToMimetypeMap.put("dcr", "application/x-director"); extensionToMimetypeMap.put("dif", "video/x-dv"); extensionToMimetypeMap.put("dir", "application/x-director"); extensionToMimetypeMap.put("djv", "image/vnd.djvu"); extensionToMimetypeMap.put("djvu", "image/vnd.djvu"); extensionToMimetypeMap.put("dll", "application/octet-stream"); extensionToMimetypeMap.put("dmg", "application/octet-stream"); extensionToMimetypeMap.put("dms", "application/octet-stream"); extensionToMimetypeMap.put("doc", "application/msword"); extensionToMimetypeMap.put("dtd", "application/xml-dtd"); extensionToMimetypeMap.put("dv", "video/x-dv"); extensionToMimetypeMap.put("dvi", "application/x-dvi"); extensionToMimetypeMap.put("dxr", "application/x-director"); extensionToMimetypeMap.put("eps", "application/postscript"); extensionToMimetypeMap.put("etx", "text/x-setext"); extensionToMimetypeMap.put("exe", "application/octet-stream"); extensionToMimetypeMap.put("ez", "application/andrew-inset"); extensionToMimetypeMap.put("flv", "video/x-flv"); extensionToMimetypeMap.put("gif", "image/gif"); extensionToMimetypeMap.put("gram", "application/srgs"); extensionToMimetypeMap.put("grxml", "application/srgs+xml"); extensionToMimetypeMap.put("gtar", "application/x-gtar"); extensionToMimetypeMap.put("gz", "application/x-gzip"); extensionToMimetypeMap.put("hdf", "application/x-hdf"); extensionToMimetypeMap.put("hqx", "application/mac-binhex40"); extensionToMimetypeMap.put("htm", "text/html"); extensionToMimetypeMap.put("html", "text/html"); extensionToMimetypeMap.put("ice", "x-conference/x-cooltalk"); extensionToMimetypeMap.put("ico", "image/x-icon"); extensionToMimetypeMap.put("ics", "text/calendar"); extensionToMimetypeMap.put("ief", "image/ief"); extensionToMimetypeMap.put("ifb", "text/calendar"); extensionToMimetypeMap.put("iges", "model/iges"); extensionToMimetypeMap.put("igs", "model/iges"); extensionToMimetypeMap.put("jnlp", "application/x-java-jnlp-file"); extensionToMimetypeMap.put("jp2", "image/jp2"); extensionToMimetypeMap.put("jpe", "image/jpeg"); extensionToMimetypeMap.put("jpeg", "image/jpeg"); extensionToMimetypeMap.put("jpg", "image/jpeg"); extensionToMimetypeMap.put("js", "application/x-javascript"); extensionToMimetypeMap.put("kar", "audio/midi"); extensionToMimetypeMap.put("latex", "application/x-latex"); extensionToMimetypeMap.put("lha", "application/octet-stream"); extensionToMimetypeMap.put("lzh", "application/octet-stream"); extensionToMimetypeMap.put("m3u", "audio/x-mpegurl"); extensionToMimetypeMap.put("m4a", "audio/mp4a-latm"); extensionToMimetypeMap.put("m4p", "audio/mp4a-latm"); extensionToMimetypeMap.put("m4u", "video/vnd.mpegurl"); extensionToMimetypeMap.put("m4v", "video/x-m4v"); extensionToMimetypeMap.put("mac", "image/x-macpaint"); extensionToMimetypeMap.put("man", "application/x-troff-man"); extensionToMimetypeMap.put("mathml", "application/mathml+xml"); extensionToMimetypeMap.put("me", "application/x-troff-me"); extensionToMimetypeMap.put("mesh", "model/mesh"); extensionToMimetypeMap.put("mid", "audio/midi"); extensionToMimetypeMap.put("midi", "audio/midi"); extensionToMimetypeMap.put("mif", "application/vnd.mif"); extensionToMimetypeMap.put("mov", "video/quicktime"); extensionToMimetypeMap.put("movie", "video/x-sgi-movie"); extensionToMimetypeMap.put("mp2", "audio/mpeg"); extensionToMimetypeMap.put("mp3", "audio/mpeg"); extensionToMimetypeMap.put("mp4", "video/mp4"); extensionToMimetypeMap.put("mpe", "video/mpeg"); extensionToMimetypeMap.put("mpeg", "video/mpeg"); extensionToMimetypeMap.put("mpg", "video/mpeg"); extensionToMimetypeMap.put("mpga", "audio/mpeg"); extensionToMimetypeMap.put("ms", "application/x-troff-ms"); extensionToMimetypeMap.put("msh", "model/mesh"); extensionToMimetypeMap.put("mxu", "video/vnd.mpegurl"); extensionToMimetypeMap.put("nc", "application/x-netcdf"); extensionToMimetypeMap.put("oda", "application/oda"); extensionToMimetypeMap.put("ogg", "application/ogg"); extensionToMimetypeMap.put("ogv", "video/ogv"); extensionToMimetypeMap.put("pbm", "image/x-portable-bitmap"); extensionToMimetypeMap.put("pct", "image/pict"); extensionToMimetypeMap.put("pdb", "chemical/x-pdb"); extensionToMimetypeMap.put("pdf", "application/pdf"); extensionToMimetypeMap.put("pgm", "image/x-portable-graymap"); extensionToMimetypeMap.put("pgn", "application/x-chess-pgn"); extensionToMimetypeMap.put("pic", "image/pict"); extensionToMimetypeMap.put("pict", "image/pict"); extensionToMimetypeMap.put("png", "image/png"); extensionToMimetypeMap.put("pnm", "image/x-portable-anymap"); extensionToMimetypeMap.put("pnt", "image/x-macpaint"); extensionToMimetypeMap.put("pntg", "image/x-macpaint"); extensionToMimetypeMap.put("ppm", "image/x-portable-pixmap"); extensionToMimetypeMap.put("ppt", "application/vnd.ms-powerpoint"); extensionToMimetypeMap.put("ps", "application/postscript"); extensionToMimetypeMap.put("qt", "video/quicktime"); extensionToMimetypeMap.put("qti", "image/x-quicktime"); extensionToMimetypeMap.put("qtif", "image/x-quicktime"); extensionToMimetypeMap.put("ra", "audio/x-pn-realaudio"); extensionToMimetypeMap.put("ram", "audio/x-pn-realaudio"); extensionToMimetypeMap.put("ras", "image/x-cmu-raster"); extensionToMimetypeMap.put("rdf", "application/rdf+xml"); extensionToMimetypeMap.put("rgb", "image/x-rgb"); extensionToMimetypeMap.put("rm", "application/vnd.rn-realmedia"); extensionToMimetypeMap.put("roff", "application/x-troff"); extensionToMimetypeMap.put("rtf", "text/rtf"); extensionToMimetypeMap.put("rtx", "text/richtext"); extensionToMimetypeMap.put("sgm", "text/sgml"); extensionToMimetypeMap.put("sgml", "text/sgml"); extensionToMimetypeMap.put("sh", "application/x-sh"); extensionToMimetypeMap.put("shar", "application/x-shar"); extensionToMimetypeMap.put("silo", "model/mesh"); extensionToMimetypeMap.put("sit", "application/x-stuffit"); extensionToMimetypeMap.put("skd", "application/x-koan"); extensionToMimetypeMap.put("skm", "application/x-koan"); extensionToMimetypeMap.put("skp", "application/x-koan"); extensionToMimetypeMap.put("skt", "application/x-koan"); extensionToMimetypeMap.put("smi", "application/smil"); extensionToMimetypeMap.put("smil", "application/smil"); extensionToMimetypeMap.put("snd", "audio/basic"); extensionToMimetypeMap.put("so", "application/octet-stream"); extensionToMimetypeMap.put("spl", "application/x-futuresplash"); extensionToMimetypeMap.put("src", "application/x-wais-source"); extensionToMimetypeMap.put("sv4cpio", "application/x-sv4cpio"); extensionToMimetypeMap.put("sv4crc", "application/x-sv4crc"); extensionToMimetypeMap.put("svg", "image/svg+xml"); extensionToMimetypeMap.put("swf", "application/x-shockwave-flash"); extensionToMimetypeMap.put("t", "application/x-troff"); extensionToMimetypeMap.put("tar", "application/x-tar"); extensionToMimetypeMap.put("tcl", "application/x-tcl"); extensionToMimetypeMap.put("tex", "application/x-tex"); extensionToMimetypeMap.put("texi", "application/x-texinfo"); extensionToMimetypeMap.put("texinfo", "application/x-texinfo"); extensionToMimetypeMap.put("tif", "image/tiff"); extensionToMimetypeMap.put("tiff", "image/tiff"); extensionToMimetypeMap.put("tr", "application/x-troff"); extensionToMimetypeMap.put("tsv", "text/tab-separated-values"); extensionToMimetypeMap.put("txt", "text/plain"); extensionToMimetypeMap.put("ustar", "application/x-ustar"); extensionToMimetypeMap.put("vcd", "application/x-cdlink"); extensionToMimetypeMap.put("vrml", "model/vrml"); extensionToMimetypeMap.put("vxml", "application/voicexml+xml"); extensionToMimetypeMap.put("wav", "audio/x-wav"); extensionToMimetypeMap.put("wbmp", "image/vnd.wap.wbmp"); extensionToMimetypeMap.put("wbxml", "application/vnd.wap.wbxml"); extensionToMimetypeMap.put("webm", "video/webm"); extensionToMimetypeMap.put("wml", "text/vnd.wap.wml"); extensionToMimetypeMap.put("wmlc", "application/vnd.wap.wmlc"); extensionToMimetypeMap.put("wmls", "text/vnd.wap.wmlscript"); extensionToMimetypeMap.put("wmlsc", "application/vnd.wap.wmlscriptc"); extensionToMimetypeMap.put("wmv", "video/x-ms-wmv"); extensionToMimetypeMap.put("wrl", "model/vrml"); extensionToMimetypeMap.put("xbm", "image/x-xbitmap"); extensionToMimetypeMap.put("xht", "application/xhtml+xml"); extensionToMimetypeMap.put("xhtml", "application/xhtml+xml"); extensionToMimetypeMap.put("xls", "application/vnd.ms-excel"); extensionToMimetypeMap.put("xml", "application/xml"); extensionToMimetypeMap.put("xpm", "image/x-xpixmap"); extensionToMimetypeMap.put("xsl", "application/xml"); extensionToMimetypeMap.put("xslt", "application/xslt+xml"); extensionToMimetypeMap.put("xul", "application/vnd.mozilla.xul+xml"); extensionToMimetypeMap.put("xwd", "image/x-xwindowdump"); extensionToMimetypeMap.put("xyz", "chemical/x-xyz"); extensionToMimetypeMap.put("zip", "application/zip"); } /** * Gets an instance of {@link Mimetypes} with default mime type info. You * can load more via {@link #loadAndReplaceMimetypes(InputStream)}. * <p> * For more information about Internet media types, please read RFC 2045, * 2046, 2047, 2048, and 2077. The Internet media type registry is at * http://www.iana.org/assignments/media-types/ * </p> * * @return an instance of {@link Mimetypes} */ public synchronized static Mimetypes getInstance() { if (mimetypes != null) return mimetypes; mimetypes = new Mimetypes(); if (log.isDebugEnabled()) { Map<String, String> map = mimetypes.extensionToMimetypeMap; for (String extension : map.keySet()) { log.debug("Setting mime type for extension '" + extension + "' to '" + map.get(extension) + "'"); } } return mimetypes; } /** * Reads and stores the mime type setting corresponding to a file extension, * by reading text from an InputStream. If a mime type setting already * exists when this method is run, the mime type value is replaced with the * newer one. * * @param is * @throws IOException */ public void loadAndReplaceMimetypes(InputStream is) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(is, StringUtils.UTF8)); String line = null; while ((line = br.readLine()) != null) { line = line.trim(); if (line.startsWith("#") || line.length() == 0) { // Ignore comments and empty lines. } else { StringTokenizer st = new StringTokenizer(line, " \t"); if (st.countTokens() > 1) { String mimetype = st.nextToken(); while (st.hasMoreTokens()) { String extension = st.nextToken(); extensionToMimetypeMap.put(StringUtils.lowerCase(extension), mimetype); if (log.isDebugEnabled()) { log.debug("Setting mime type for extension '" + StringUtils.lowerCase(extension) + "' to '" + mimetype + "'"); } } } else { if (log.isDebugEnabled()) { log.debug("Ignoring mimetype with no associated file extensions: '" + line + "'"); } } } } } /** * Determines the mimetype of a file by looking up the file's extension in * an internal listing to find the corresponding mime type. If the file has * no extension, or the extension is not available in the listing contained * in this class, the default mimetype <code>application/octet-stream</code> * is returned. * <p> * A file extension is one or more characters that occur after the last * period (.) in the file's name. If a file has an extension, Guesses the * mimetype of file data based on the file's extension. * * @param fileName The name of the file whose extension may match a known * mimetype. * @return The file's mimetype based on its extension, or a default value of * <code>application/octet-stream</code> if a mime type value cannot * be found. */ public String getMimetype(String fileName) { int lastPeriodIndex = fileName.lastIndexOf("."); if (lastPeriodIndex > 0 && lastPeriodIndex + 1 < fileName.length()) { String ext = StringUtils.lowerCase(fileName.substring(lastPeriodIndex + 1)); if (extensionToMimetypeMap.containsKey(ext)) { String mimetype = extensionToMimetypeMap.get(ext); if (log.isDebugEnabled()) { log.debug("Recognised extension '" + ext + "', mimetype is: '" + mimetype + "'"); } return mimetype; } else { if (log.isDebugEnabled()) { log.debug("Extension '" + ext + "' is unrecognized in mime type listing" + ", using default mime type: '" + MIMETYPE_OCTET_STREAM + "'"); } } } else { if (log.isDebugEnabled()) { log.debug("File name has no extension, mime type cannot be recognised for: " + fileName); } } return MIMETYPE_OCTET_STREAM; } /** * Determines the mimetype of a file by looking up the file's extension in * an internal listing to find the corresponding mime type. If the file has * no extension, or the extension is not available in the listing contained * in this class, the default mimetype <code>application/octet-stream</code> * is returned. * <p> * A file extension is one or more characters that occur after the last * period (.) in the file's name. If a file has no extension, Guesses the * mimetype of file data based on the file's extension. * * @param file the file whose extension may match a known mimetype. * @return the file's mimetype based on its extension, or a default value of * <code>application/octet-stream</code> if a mime type value cannot * be found. */ public String getMimetype(File file) { return getMimetype(file.getName()); } /** * Register a new mimetype for the given extension. If a mapping for the * extension exists, this will overwrite it. Note that extension is case * insensitive. * * @param extension a file extension * @param mimetype mime type string */ public void registerMimetype(String extension, String mimetype) { extensionToMimetypeMap.put(StringUtils.lowerCase(extension), mimetype); } }