1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.core.internal.util; 17 18 import java.io.BufferedReader; 19 import java.io.File; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.InputStreamReader; 23 import java.nio.charset.StandardCharsets; 24 import java.nio.file.Path; 25 import java.util.HashMap; 26 import java.util.Map; 27 import java.util.Optional; 28 import java.util.StringTokenizer; 29 import org.slf4j.Logger; 30 import org.slf4j.LoggerFactory; 31 import software.amazon.awssdk.annotations.SdkInternalApi; 32 import software.amazon.awssdk.utils.IoUtils; 33 import software.amazon.awssdk.utils.StringUtils; 34 import software.amazon.awssdk.utils.Validate; 35 36 /** 37 * Utility class that maintains a listing of known Mimetypes, and determines the 38 * mimetype of files based on file extensions. 39 * <p> 40 * This class is obtained with the {#link {@link #getInstance()} method that 41 * recognizes loaded mime types from the file <code>mime.types</code> if this 42 * file is available at the root of the classpath. The mime.types file format, 43 * and most of the content, is taken from the Apache HTTP server's mime.types 44 * file. 45 * <p> 46 * The format for mime type setting documents is: 47 * <code>mimetype + extension (+ extension)*</code>. Any 48 * blank lines in the file are ignored, as are lines starting with 49 * <code>#</code> which are considered comments. 50 * 51 * @see <a href="https://github.com/apache/httpd/blob/trunk/docs/conf/mime.types">mime.types</a> 52 */ 53 @SdkInternalApi 54 public final class Mimetype { 55 56 /** The default XML mimetype: application/xml */ 57 public static final String MIMETYPE_XML = "application/xml"; 58 59 /** The default HTML mimetype: text/html */ 60 public static final String MIMETYPE_HTML = "text/html"; 61 62 /** The default binary mimetype: application/octet-stream */ 63 public static final String MIMETYPE_OCTET_STREAM = "application/octet-stream"; 64 65 /** The default gzip mimetype: application/x-gzip */ 66 public static final String MIMETYPE_GZIP = "application/x-gzip"; 67 68 public static final String MIMETYPE_TEXT_PLAIN = "text/plain"; 69 70 public static final String MIMETYPE_EVENT_STREAM = "application/vnd.amazon.eventstream"; 71 72 private static final Logger LOG = LoggerFactory.getLogger(Mimetype.class); 73 74 private static final String MIME_TYPE_PATH = "software/amazon/awssdk/core/util/mime.types"; 75 76 private static final ClassLoader CLASS_LOADER = ClassLoaderHelper.classLoader(Mimetype.class); 77 78 private static volatile Mimetype mimetype; 79 80 /** 81 * Map that stores file extensions as keys, and the corresponding mimetype as values. 82 */ 83 private final Map<String, String> extensionToMimetype = new HashMap<>(); 84 Mimetype()85 private Mimetype() { 86 Optional.ofNullable(CLASS_LOADER).map(loader -> loader.getResourceAsStream(MIME_TYPE_PATH)).ifPresent( 87 stream -> { 88 try { 89 loadAndReplaceMimetypes(stream); 90 } catch (IOException e) { 91 LOG.debug("Failed to load mime types from file in the classpath: mime.types", e); 92 } finally { 93 IoUtils.closeQuietly(stream, null); 94 } 95 } 96 ); 97 } 98 99 /** 100 * Loads MIME type info from the file 'mime.types' in the classpath, if it's available. 101 */ getInstance()102 public static Mimetype getInstance() { 103 if (mimetype == null) { 104 synchronized (Mimetype.class) { 105 if (mimetype == null) { 106 mimetype = new Mimetype(); 107 } 108 } 109 } 110 111 return mimetype; 112 } 113 114 /** 115 * Determines the mimetype of a file by looking up the file's extension in an internal listing 116 * to find the corresponding mime type. If the file has no extension, or the extension is not 117 * available in the listing contained in this class, the default mimetype 118 * <code>application/octet-stream</code> is returned. 119 * 120 * @param path the file whose extension may match a known mimetype. 121 * @return the file's mimetype based on its extension, or a default value of 122 * <code>application/octet-stream</code> if a mime type value cannot be found. 123 */ getMimetype(Path path)124 public String getMimetype(Path path) { 125 Validate.notNull(path, "path"); 126 Path file = path.getFileName(); 127 128 if (file != null) { 129 return getMimetype(file.toString()); 130 } 131 return MIMETYPE_OCTET_STREAM; 132 } 133 134 /** 135 * Determines the mimetype of a file by looking up the file's extension in an internal listing 136 * to find the corresponding mime type. If the file has no extension, or the extension is not 137 * available in the listing contained in this class, the default mimetype 138 * <code>application/octet-stream</code> is returned. 139 * 140 * @param file the file whose extension may match a known mimetype. 141 * @return the file's mimetype based on its extension, or a default value of 142 * <code>application/octet-stream</code> if a mime type value cannot be found. 143 */ getMimetype(File file)144 public String getMimetype(File file) { 145 return getMimetype(file.toPath()); 146 } 147 148 /** 149 * Determines the mimetype of a file by looking up the file's extension in 150 * an internal listing to find the corresponding mime type. If the file has 151 * no extension, or the extension is not available in the listing contained 152 * in this class, the default mimetype <code>application/octet-stream</code> 153 * is returned. 154 * 155 * @param fileName The name of the file whose extension may match a known 156 * mimetype. 157 * @return The file's mimetype based on its extension, or a default value of 158 * {@link #MIMETYPE_OCTET_STREAM} if a mime type value cannot 159 * be found. 160 */ getMimetype(String fileName)161 String getMimetype(String fileName) { 162 int lastPeriodIndex = fileName.lastIndexOf('.'); 163 if (lastPeriodIndex > 0 && lastPeriodIndex + 1 < fileName.length()) { 164 String ext = StringUtils.lowerCase(fileName.substring(lastPeriodIndex + 1)); 165 if (extensionToMimetype.containsKey(ext)) { 166 return extensionToMimetype.get(ext); 167 } 168 } 169 return MIMETYPE_OCTET_STREAM; 170 } 171 172 /** 173 * Reads and stores the mime type setting corresponding to a file extension, by reading 174 * text from an InputStream. If a mime type setting already exists when this method is run, 175 * the mime type value is replaced with the newer one. 176 */ loadAndReplaceMimetypes(InputStream is)177 private void loadAndReplaceMimetypes(InputStream is) throws IOException { 178 BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); 179 180 br.lines().filter(line -> !line.startsWith("#")).forEach(line -> { 181 line = line.trim(); 182 183 StringTokenizer st = new StringTokenizer(line, " \t"); 184 if (st.countTokens() > 1) { 185 String mimetype = st.nextToken(); 186 while (st.hasMoreTokens()) { 187 String extension = st.nextToken(); 188 extensionToMimetype.put(StringUtils.lowerCase(extension), mimetype); 189 } 190 } 191 }); 192 } 193 } 194