001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.web.server;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.Iterator;
022import java.util.LinkedHashMap;
023import java.util.Map;
024
025import org.springframework.util.Assert;
026
027/**
028 * Simple server-independent abstraction for mime mappings. Roughly equivalent to the
029 * {@literal <mime-mapping>} element traditionally found in web.xml.
030 *
031 * @author Phillip Webb
032 * @since 2.0.0
033 */
034public final class MimeMappings implements Iterable<MimeMappings.Mapping> {
035
036        /**
037         * Default mime mapping commonly used.
038         */
039        public static final MimeMappings DEFAULT;
040
041        static {
042                MimeMappings mappings = new MimeMappings();
043                mappings.add("abs", "audio/x-mpeg");
044                mappings.add("ai", "application/postscript");
045                mappings.add("aif", "audio/x-aiff");
046                mappings.add("aifc", "audio/x-aiff");
047                mappings.add("aiff", "audio/x-aiff");
048                mappings.add("aim", "application/x-aim");
049                mappings.add("art", "image/x-jg");
050                mappings.add("asf", "video/x-ms-asf");
051                mappings.add("asx", "video/x-ms-asf");
052                mappings.add("au", "audio/basic");
053                mappings.add("avi", "video/x-msvideo");
054                mappings.add("avx", "video/x-rad-screenplay");
055                mappings.add("bcpio", "application/x-bcpio");
056                mappings.add("bin", "application/octet-stream");
057                mappings.add("bmp", "image/bmp");
058                mappings.add("body", "text/html");
059                mappings.add("cdf", "application/x-cdf");
060                mappings.add("cer", "application/pkix-cert");
061                mappings.add("class", "application/java");
062                mappings.add("cpio", "application/x-cpio");
063                mappings.add("csh", "application/x-csh");
064                mappings.add("css", "text/css");
065                mappings.add("dib", "image/bmp");
066                mappings.add("doc", "application/msword");
067                mappings.add("dtd", "application/xml-dtd");
068                mappings.add("dv", "video/x-dv");
069                mappings.add("dvi", "application/x-dvi");
070                mappings.add("eot", "application/vnd.ms-fontobject");
071                mappings.add("eps", "application/postscript");
072                mappings.add("etx", "text/x-setext");
073                mappings.add("exe", "application/octet-stream");
074                mappings.add("gif", "image/gif");
075                mappings.add("gtar", "application/x-gtar");
076                mappings.add("gz", "application/x-gzip");
077                mappings.add("hdf", "application/x-hdf");
078                mappings.add("hqx", "application/mac-binhex40");
079                mappings.add("htc", "text/x-component");
080                mappings.add("htm", "text/html");
081                mappings.add("html", "text/html");
082                mappings.add("ief", "image/ief");
083                mappings.add("jad", "text/vnd.sun.j2me.app-descriptor");
084                mappings.add("jar", "application/java-archive");
085                mappings.add("java", "text/x-java-source");
086                mappings.add("jnlp", "application/x-java-jnlp-file");
087                mappings.add("jpe", "image/jpeg");
088                mappings.add("jpeg", "image/jpeg");
089                mappings.add("jpg", "image/jpeg");
090                mappings.add("js", "application/javascript");
091                mappings.add("jsf", "text/plain");
092                mappings.add("json", "application/json");
093                mappings.add("jspf", "text/plain");
094                mappings.add("kar", "audio/midi");
095                mappings.add("latex", "application/x-latex");
096                mappings.add("m3u", "audio/x-mpegurl");
097                mappings.add("mac", "image/x-macpaint");
098                mappings.add("man", "text/troff");
099                mappings.add("mathml", "application/mathml+xml");
100                mappings.add("me", "text/troff");
101                mappings.add("mid", "audio/midi");
102                mappings.add("midi", "audio/midi");
103                mappings.add("mif", "application/x-mif");
104                mappings.add("mov", "video/quicktime");
105                mappings.add("movie", "video/x-sgi-movie");
106                mappings.add("mp1", "audio/mpeg");
107                mappings.add("mp2", "audio/mpeg");
108                mappings.add("mp3", "audio/mpeg");
109                mappings.add("mp4", "video/mp4");
110                mappings.add("mpa", "audio/mpeg");
111                mappings.add("mpe", "video/mpeg");
112                mappings.add("mpeg", "video/mpeg");
113                mappings.add("mpega", "audio/x-mpeg");
114                mappings.add("mpg", "video/mpeg");
115                mappings.add("mpv2", "video/mpeg2");
116                mappings.add("ms", "application/x-wais-source");
117                mappings.add("nc", "application/x-netcdf");
118                mappings.add("oda", "application/oda");
119                mappings.add("odb", "application/vnd.oasis.opendocument.database");
120                mappings.add("odc", "application/vnd.oasis.opendocument.chart");
121                mappings.add("odf", "application/vnd.oasis.opendocument.formula");
122                mappings.add("odg", "application/vnd.oasis.opendocument.graphics");
123                mappings.add("odi", "application/vnd.oasis.opendocument.image");
124                mappings.add("odm", "application/vnd.oasis.opendocument.text-master");
125                mappings.add("odp", "application/vnd.oasis.opendocument.presentation");
126                mappings.add("ods", "application/vnd.oasis.opendocument.spreadsheet");
127                mappings.add("odt", "application/vnd.oasis.opendocument.text");
128                mappings.add("otg", "application/vnd.oasis.opendocument.graphics-template");
129                mappings.add("oth", "application/vnd.oasis.opendocument.text-web");
130                mappings.add("otp", "application/vnd.oasis.opendocument.presentation-template");
131                mappings.add("ots", "application/vnd.oasis.opendocument.spreadsheet-template ");
132                mappings.add("ott", "application/vnd.oasis.opendocument.text-template");
133                mappings.add("ogx", "application/ogg");
134                mappings.add("ogv", "video/ogg");
135                mappings.add("oga", "audio/ogg");
136                mappings.add("ogg", "audio/ogg");
137                mappings.add("otf", "application/x-font-opentype");
138                mappings.add("spx", "audio/ogg");
139                mappings.add("flac", "audio/flac");
140                mappings.add("anx", "application/annodex");
141                mappings.add("axa", "audio/annodex");
142                mappings.add("axv", "video/annodex");
143                mappings.add("xspf", "application/xspf+xml");
144                mappings.add("pbm", "image/x-portable-bitmap");
145                mappings.add("pct", "image/pict");
146                mappings.add("pdf", "application/pdf");
147                mappings.add("pgm", "image/x-portable-graymap");
148                mappings.add("pic", "image/pict");
149                mappings.add("pict", "image/pict");
150                mappings.add("pls", "audio/x-scpls");
151                mappings.add("png", "image/png");
152                mappings.add("pnm", "image/x-portable-anymap");
153                mappings.add("pnt", "image/x-macpaint");
154                mappings.add("ppm", "image/x-portable-pixmap");
155                mappings.add("ppt", "application/vnd.ms-powerpoint");
156                mappings.add("pps", "application/vnd.ms-powerpoint");
157                mappings.add("ps", "application/postscript");
158                mappings.add("psd", "image/vnd.adobe.photoshop");
159                mappings.add("qt", "video/quicktime");
160                mappings.add("qti", "image/x-quicktime");
161                mappings.add("qtif", "image/x-quicktime");
162                mappings.add("ras", "image/x-cmu-raster");
163                mappings.add("rdf", "application/rdf+xml");
164                mappings.add("rgb", "image/x-rgb");
165                mappings.add("rm", "application/vnd.rn-realmedia");
166                mappings.add("roff", "text/troff");
167                mappings.add("rtf", "application/rtf");
168                mappings.add("rtx", "text/richtext");
169                mappings.add("sfnt", "application/font-sfnt");
170                mappings.add("sh", "application/x-sh");
171                mappings.add("shar", "application/x-shar");
172                mappings.add("sit", "application/x-stuffit");
173                mappings.add("snd", "audio/basic");
174                mappings.add("src", "application/x-wais-source");
175                mappings.add("sv4cpio", "application/x-sv4cpio");
176                mappings.add("sv4crc", "application/x-sv4crc");
177                mappings.add("svg", "image/svg+xml");
178                mappings.add("svgz", "image/svg+xml");
179                mappings.add("swf", "application/x-shockwave-flash");
180                mappings.add("t", "text/troff");
181                mappings.add("tar", "application/x-tar");
182                mappings.add("tcl", "application/x-tcl");
183                mappings.add("tex", "application/x-tex");
184                mappings.add("texi", "application/x-texinfo");
185                mappings.add("texinfo", "application/x-texinfo");
186                mappings.add("tif", "image/tiff");
187                mappings.add("tiff", "image/tiff");
188                mappings.add("tr", "text/troff");
189                mappings.add("tsv", "text/tab-separated-values");
190                mappings.add("ttf", "application/x-font-ttf");
191                mappings.add("txt", "text/plain");
192                mappings.add("ulw", "audio/basic");
193                mappings.add("ustar", "application/x-ustar");
194                mappings.add("vxml", "application/voicexml+xml");
195                mappings.add("xbm", "image/x-xbitmap");
196                mappings.add("xht", "application/xhtml+xml");
197                mappings.add("xhtml", "application/xhtml+xml");
198                mappings.add("xls", "application/vnd.ms-excel");
199                mappings.add("xml", "application/xml");
200                mappings.add("xpm", "image/x-xpixmap");
201                mappings.add("xsl", "application/xml");
202                mappings.add("xslt", "application/xslt+xml");
203                mappings.add("xul", "application/vnd.mozilla.xul+xml");
204                mappings.add("xwd", "image/x-xwindowdump");
205                mappings.add("vsd", "application/vnd.visio");
206                mappings.add("wav", "audio/x-wav");
207                mappings.add("wbmp", "image/vnd.wap.wbmp");
208                mappings.add("wml", "text/vnd.wap.wml");
209                mappings.add("wmlc", "application/vnd.wap.wmlc");
210                mappings.add("wmls", "text/vnd.wap.wmlsc");
211                mappings.add("wmlscriptc", "application/vnd.wap.wmlscriptc");
212                mappings.add("wmv", "video/x-ms-wmv");
213                mappings.add("woff", "application/font-woff");
214                mappings.add("woff2", "application/font-woff2");
215                mappings.add("wrl", "model/vrml");
216                mappings.add("wspolicy", "application/wspolicy+xml");
217                mappings.add("z", "application/x-compress");
218                mappings.add("zip", "application/zip");
219                DEFAULT = unmodifiableMappings(mappings);
220        }
221
222        private final Map<String, Mapping> map;
223
224        /**
225         * Create a new empty {@link MimeMappings} instance.
226         */
227        public MimeMappings() {
228                this.map = new LinkedHashMap<>();
229        }
230
231        /**
232         * Create a new {@link MimeMappings} instance from the specified mappings.
233         * @param mappings the source mappings
234         */
235        public MimeMappings(MimeMappings mappings) {
236                this(mappings, true);
237        }
238
239        /**
240         * Create a new {@link MimeMappings} from the specified mappings.
241         * @param mappings the source mappings with extension as the key and mime-type as the
242         * value
243         */
244        public MimeMappings(Map<String, String> mappings) {
245                Assert.notNull(mappings, "Mappings must not be null");
246                this.map = new LinkedHashMap<>();
247                mappings.forEach(this::add);
248        }
249
250        /**
251         * Internal constructor.
252         * @param mappings source mappings
253         * @param mutable if the new object should be mutable.
254         */
255        private MimeMappings(MimeMappings mappings, boolean mutable) {
256                Assert.notNull(mappings, "Mappings must not be null");
257                this.map = (mutable ? new LinkedHashMap<>(mappings.map)
258                                : Collections.unmodifiableMap(mappings.map));
259        }
260
261        @Override
262        public Iterator<Mapping> iterator() {
263                return getAll().iterator();
264        }
265
266        /**
267         * Returns all defined mappings.
268         * @return the mappings.
269         */
270        public Collection<Mapping> getAll() {
271                return this.map.values();
272        }
273
274        /**
275         * Add a new mime mapping.
276         * @param extension the file extension (excluding '.')
277         * @param mimeType the mime type to map
278         * @return any previous mapping or {@code null}
279         */
280        public String add(String extension, String mimeType) {
281                Mapping previous = this.map.put(extension, new Mapping(extension, mimeType));
282                return (previous != null) ? previous.getMimeType() : null;
283        }
284
285        /**
286         * Get a mime mapping for the given extension.
287         * @param extension the file extension (excluding '.')
288         * @return a mime mapping or {@code null}
289         */
290        public String get(String extension) {
291                Mapping mapping = this.map.get(extension);
292                return (mapping != null) ? mapping.getMimeType() : null;
293        }
294
295        /**
296         * Remove an existing mapping.
297         * @param extension the file extension (excluding '.')
298         * @return the removed mime mapping or {@code null} if no item was removed
299         */
300        public String remove(String extension) {
301                Mapping previous = this.map.remove(extension);
302                return (previous != null) ? previous.getMimeType() : null;
303        }
304
305        @Override
306        public boolean equals(Object obj) {
307                if (obj == null) {
308                        return false;
309                }
310                if (obj == this) {
311                        return true;
312                }
313                if (obj instanceof MimeMappings) {
314                        MimeMappings other = (MimeMappings) obj;
315                        return this.map.equals(other.map);
316                }
317                return false;
318        }
319
320        @Override
321        public int hashCode() {
322                return this.map.hashCode();
323        }
324
325        /**
326         * Create a new unmodifiable view of the specified mapping. Methods that attempt to
327         * modify the returned map will throw {@link UnsupportedOperationException}s.
328         * @param mappings the mappings
329         * @return an unmodifiable view of the specified mappings.
330         */
331        public static MimeMappings unmodifiableMappings(MimeMappings mappings) {
332                return new MimeMappings(mappings, false);
333        }
334
335        /**
336         * A single mime mapping.
337         */
338        public static final class Mapping {
339
340                private final String extension;
341
342                private final String mimeType;
343
344                public Mapping(String extension, String mimeType) {
345                        Assert.notNull(extension, "Extension must not be null");
346                        Assert.notNull(mimeType, "MimeType must not be null");
347                        this.extension = extension;
348                        this.mimeType = mimeType;
349                }
350
351                public String getExtension() {
352                        return this.extension;
353                }
354
355                public String getMimeType() {
356                        return this.mimeType;
357                }
358
359                @Override
360                public boolean equals(Object obj) {
361                        if (obj == null) {
362                                return false;
363                        }
364                        if (obj == this) {
365                                return true;
366                        }
367                        if (obj instanceof Mapping) {
368                                Mapping other = (Mapping) obj;
369                                return this.extension.equals(other.extension)
370                                                && this.mimeType.equals(other.mimeType);
371                        }
372                        return false;
373                }
374
375                @Override
376                public int hashCode() {
377                        return this.extension.hashCode();
378                }
379
380                @Override
381                public String toString() {
382                        return "Mapping [extension=" + this.extension + ", mimeType=" + this.mimeType
383                                        + "]";
384                }
385
386        }
387
388}