|
| 1 | +/* |
| 2 | + * Copyright 2010-2017 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.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.util.HashMap; |
| 24 | +import java.util.Map; |
| 25 | +import java.util.Optional; |
| 26 | +import java.util.StringTokenizer; |
| 27 | +import org.slf4j.Logger; |
| 28 | +import org.slf4j.LoggerFactory; |
| 29 | +import software.amazon.awssdk.utils.IoUtils; |
| 30 | + |
| 31 | +/** |
| 32 | + * Utility class that maintains a listing of known Mimetypes, and determines the |
| 33 | + * mimetype of files based on file extensions. |
| 34 | + * <p> |
| 35 | + * This class is obtained with the {#link {@link #getInstance()} method that |
| 36 | + * recognizes loaded mime types from the file <code>mime.types</code> if this |
| 37 | + * file is available at the root of the classpath. The mime.types file format, |
| 38 | + * and most of the content, is taken from the Apache HTTP server's mime.types |
| 39 | + * file. |
| 40 | + * <p> |
| 41 | + * The format for mime type setting documents is: |
| 42 | + * <code>mimetype + extension (+ extension)*</code>. Any |
| 43 | + * blank lines in the file are ignored, as are lines starting with |
| 44 | + * <code>#</code> which are considered comments. |
| 45 | + * |
| 46 | + * @see <a href="https://github.com/apache/httpd/blob/trunk/docs/conf/mime.types">mime.types</a> |
| 47 | + */ |
| 48 | +public final class Mimetypes { |
| 49 | + |
| 50 | + /** The default XML mimetype: application/xml */ |
| 51 | + public static final String MIMETYPE_XML = "application/xml"; |
| 52 | + |
| 53 | + /** The default HTML mimetype: text/html */ |
| 54 | + public static final String MIMETYPE_HTML = "text/html"; |
| 55 | + |
| 56 | + /** The default binary mimetype: application/octet-stream */ |
| 57 | + public static final String MIMETYPE_OCTET_STREAM = "application/octet-stream"; |
| 58 | + |
| 59 | + /** The default gzip mimetype: application/x-gzip */ |
| 60 | + public static final String MIMETYPE_GZIP = "application/x-gzip"; |
| 61 | + |
| 62 | + public static final String MIMETYPE_TEXT_PLAIN = "text/plain; charset=UTF-8"; |
| 63 | + |
| 64 | + private static final Logger LOG = LoggerFactory.getLogger(Mimetypes.class); |
| 65 | + |
| 66 | + private static final String MIME_TYPE_PATH = "software/amazon/awssdk/core/util/mime.types"; |
| 67 | + |
| 68 | + private static final ClassLoader CLASS_LOADER = ClassLoaderHelper.classLoader(); |
| 69 | + |
| 70 | + private static volatile Mimetypes mimetypes; |
| 71 | + |
| 72 | + /** |
| 73 | + * Map that stores file extensions as keys, and the corresponding mimetype as values. |
| 74 | + */ |
| 75 | + private final Map<String, String> extensionToMimetype = new HashMap<>(); |
| 76 | + |
| 77 | + private Mimetypes() { |
| 78 | + Optional.ofNullable(CLASS_LOADER).map(loader -> loader.getResourceAsStream(MIME_TYPE_PATH)).ifPresent( |
| 79 | + stream -> { |
| 80 | + try { |
| 81 | + loadAndReplaceMimetypes(stream); |
| 82 | + } catch (IOException e) { |
| 83 | + LOG.debug("Failed to load mime types from file in the classpath: mime.types", e); |
| 84 | + } finally { |
| 85 | + IoUtils.closeQuietly(stream, null); |
| 86 | + } |
| 87 | + } |
| 88 | + ); |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * Loads MIME type info from the file 'mime.types' in the classpath, if it's available. |
| 93 | + */ |
| 94 | + public static Mimetypes getInstance() { |
| 95 | + if (mimetypes == null) { |
| 96 | + synchronized (Mimetypes.class) { |
| 97 | + if (mimetypes == null) { |
| 98 | + mimetypes = new Mimetypes(); |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + return mimetypes; |
| 104 | + } |
| 105 | + |
| 106 | + /** |
| 107 | + * Determines the mimetype of a file by looking up the file's extension in an internal listing |
| 108 | + * to find the corresponding mime type. If the file has no extension, or the extension is not |
| 109 | + * available in the listing contained in this class, the default mimetype |
| 110 | + * <code>application/octet-stream</code> is returned. |
| 111 | + * <p> |
| 112 | + * A file extension is one or more characters that occur after the last period (.) in the file's name. |
| 113 | + * If a file has no extension, |
| 114 | + * Guesses the mimetype of file data based on the file's extension. |
| 115 | + * |
| 116 | + * @param file the file whose extension may match a known mimetype. |
| 117 | + * @return the file's mimetype based on its extension, or a default value of |
| 118 | + * <code>application/octet-stream</code> if a mime type value cannot be found. |
| 119 | + */ |
| 120 | + public String getMimetype(File file) { |
| 121 | + return getMimetype(file.getName()); |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * Determines the mimetype of a file by looking up the file's extension in |
| 126 | + * an internal listing to find the corresponding mime type. If the file has |
| 127 | + * no extension, or the extension is not available in the listing contained |
| 128 | + * in this class, the default mimetype <code>application/octet-stream</code> |
| 129 | + * is returned. |
| 130 | + * <p> |
| 131 | + * A file extension is one or more characters that occur after the last |
| 132 | + * period (.) in the file's name. If a file has no extension, Guesses the |
| 133 | + * mimetype of file data based on the file's extension. |
| 134 | + * |
| 135 | + * @param fileName The name of the file whose extension may match a known |
| 136 | + * mimetype. |
| 137 | + * @return The file's mimetype based on its extension, or a default value of |
| 138 | + * {@link #MIMETYPE_OCTET_STREAM} if a mime type value cannot |
| 139 | + * be found. |
| 140 | + */ |
| 141 | + String getMimetype(String fileName) { |
| 142 | + int lastPeriodIndex = fileName.lastIndexOf('.'); |
| 143 | + if (lastPeriodIndex > 0 && lastPeriodIndex + 1 < fileName.length()) { |
| 144 | + String ext = StringUtils.lowerCase(fileName.substring(lastPeriodIndex + 1)); |
| 145 | + if (extensionToMimetype.containsKey(ext)) { |
| 146 | + return extensionToMimetype.get(ext); |
| 147 | + } |
| 148 | + } |
| 149 | + return MIMETYPE_OCTET_STREAM; |
| 150 | + } |
| 151 | + |
| 152 | + /** |
| 153 | + * Reads and stores the mime type setting corresponding to a file extension, by reading |
| 154 | + * text from an InputStream. If a mime type setting already exists when this method is run, |
| 155 | + * the mime type value is replaced with the newer one. |
| 156 | + */ |
| 157 | + private void loadAndReplaceMimetypes(InputStream is) throws IOException { |
| 158 | + BufferedReader br = new BufferedReader(new InputStreamReader(is, StringUtils.UTF8)); |
| 159 | + |
| 160 | + br.lines().filter(line -> !line.startsWith("#")).forEach(line -> { |
| 161 | + line = line.trim(); |
| 162 | + |
| 163 | + StringTokenizer st = new StringTokenizer(line, " \t"); |
| 164 | + if (st.countTokens() > 1) { |
| 165 | + String mimetype = st.nextToken(); |
| 166 | + while (st.hasMoreTokens()) { |
| 167 | + String extension = st.nextToken(); |
| 168 | + extensionToMimetype.put(StringUtils.lowerCase(extension), mimetype); |
| 169 | + } |
| 170 | + } |
| 171 | + }); |
| 172 | + } |
| 173 | +} |
0 commit comments