• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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