1/*
2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.activation;
27
28import java.io.*;
29import java.net.*;
30import java.util.*;
31import java.security.AccessController;
32import java.security.PrivilegedAction;
33import com.sun.activation.registries.MimeTypeFile;
34import com.sun.activation.registries.LogSupport;
35
36/**
37 * This class extends FileTypeMap and provides data typing of files
38 * via their file extension. It uses the {@code .mime.types} format. <p>
39 *
40 * <b>MIME types file search order:</b><p>
41 * The MimetypesFileTypeMap looks in various places in the user's
42 * system for MIME types file entries. When requests are made
43 * to search for MIME types in the MimetypesFileTypeMap, it searches
44 * MIME types files in the following order:
45 * <ol>
46 * <li> Programmatically added entries to the MimetypesFileTypeMap instance.
47 * <li> The file {@code .mime.types} in the user's home directory.
48 * <li> The file {@code mime.types} in the Java runtime.
49 * <li> The file or resources named {@code META-INF/mime.types}.
50 * <li> The file or resource named {@code META-INF/mimetypes.default}
51 * (usually found only in the {@code activation.jar} file).
52 * </ol>
53 * <p>
54 * (The current implementation looks for the {@code mime.types} file
55 * in the Java runtime in the directory <i>java.home</i>{@code /conf}
56 * if it exists, and otherwise in the directory
57 * <i>java.home</i>{@code /lib}, where <i>java.home</i> is the value
58 * of the "java.home" System property.  Note that the "conf" directory was
59 * introduced in JDK 9.)
60 * <p>
61 * <b>MIME types file format:</b>
62 *
63 * <pre>{@code
64 * # comments begin with a '#'
65 * # the format is <mime type> <space separated file extensions>
66 * # for example:
67 * text/plain    txt text TXT
68 * # this would map file.txt, file.text, and file.TXT to
69 * # the mime type "text/plain"
70 * }</pre>
71 *
72 * @author Bart Calder
73 * @author Bill Shannon
74 *
75 * @since 1.6
76 */
77public class MimetypesFileTypeMap extends FileTypeMap {
78    /*
79     * We manage a collection of databases, searched in order.
80     */
81    private MimeTypeFile[] DB;
82    private static final int PROG = 0;  // programmatically added entries
83
84    private static final String defaultType = "application/octet-stream";
85
86    private static final String confDir;
87
88    static {
89        String dir = null;
90        try {
91            dir = (String)AccessController.doPrivileged(
92                new PrivilegedAction() {
93                    public Object run() {
94                        String home = System.getProperty("java.home");
95                        String newdir = home + File.separator + "conf";
96                        File conf = new File(newdir);
97                        if (conf.exists())
98                            return newdir + File.separator;
99                        else
100                            return home + File.separator + "lib" + File.separator;
101                    }
102                });
103        } catch (Exception ex) {
104            // ignore any exceptions
105        }
106        confDir = dir;
107    }
108
109    /**
110     * The default constructor.
111     */
112    public MimetypesFileTypeMap() {
113        Vector dbv = new Vector(5);     // usually 5 or less databases
114        MimeTypeFile mf = null;
115        dbv.addElement(null);           // place holder for PROG entry
116
117        LogSupport.log("MimetypesFileTypeMap: load HOME");
118        try {
119            String user_home = System.getProperty("user.home");
120
121            if (user_home != null) {
122                String path = user_home + File.separator + ".mime.types";
123                mf = loadFile(path);
124                if (mf != null)
125                    dbv.addElement(mf);
126            }
127        } catch (SecurityException ex) {}
128
129        LogSupport.log("MimetypesFileTypeMap: load SYS");
130        try {
131            // check system's home
132            if (confDir != null) {
133                mf = loadFile(confDir + "mime.types");
134                if (mf != null)
135                    dbv.addElement(mf);
136            }
137        } catch (SecurityException ex) {}
138
139        LogSupport.log("MimetypesFileTypeMap: load JAR");
140        // load from the app's jar file
141        loadAllResources(dbv, "META-INF/mime.types");
142
143        LogSupport.log("MimetypesFileTypeMap: load DEF");
144        mf = loadResource("/META-INF/mimetypes.default");
145
146        if (mf != null)
147            dbv.addElement(mf);
148
149        DB = new MimeTypeFile[dbv.size()];
150        dbv.copyInto(DB);
151    }
152
153    /**
154     * Load from the named resource.
155     */
156    private MimeTypeFile loadResource(String name) {
157        InputStream clis = null;
158        try {
159            clis = SecuritySupport.getResourceAsStream(this.getClass(), name);
160            if (clis != null) {
161                MimeTypeFile mf = new MimeTypeFile(clis);
162                if (LogSupport.isLoggable())
163                    LogSupport.log("MimetypesFileTypeMap: successfully " +
164                        "loaded mime types file: " + name);
165                return mf;
166            } else {
167                if (LogSupport.isLoggable())
168                    LogSupport.log("MimetypesFileTypeMap: not loading " +
169                        "mime types file: " + name);
170            }
171        } catch (IOException e) {
172            if (LogSupport.isLoggable())
173                LogSupport.log("MimetypesFileTypeMap: can't load " + name, e);
174        } catch (SecurityException sex) {
175            if (LogSupport.isLoggable())
176                LogSupport.log("MimetypesFileTypeMap: can't load " + name, sex);
177        } finally {
178            try {
179                if (clis != null)
180                    clis.close();
181            } catch (IOException ex) { }        // ignore it
182        }
183        return null;
184    }
185
186    /**
187     * Load all of the named resource.
188     */
189    private void loadAllResources(Vector v, String name) {
190        boolean anyLoaded = false;
191        try {
192            URL[] urls;
193            ClassLoader cld = null;
194            // First try the "application's" class loader.
195            cld = SecuritySupport.getContextClassLoader();
196            if (cld == null)
197                cld = this.getClass().getClassLoader();
198            if (cld != null)
199                urls = SecuritySupport.getResources(cld, name);
200            else
201                urls = SecuritySupport.getSystemResources(name);
202            if (urls != null) {
203                if (LogSupport.isLoggable())
204                    LogSupport.log("MimetypesFileTypeMap: getResources");
205                for (int i = 0; i < urls.length; i++) {
206                    URL url = urls[i];
207                    InputStream clis = null;
208                    if (LogSupport.isLoggable())
209                        LogSupport.log("MimetypesFileTypeMap: URL " + url);
210                    try {
211                        clis = SecuritySupport.openStream(url);
212                        if (clis != null) {
213                            v.addElement(new MimeTypeFile(clis));
214                            anyLoaded = true;
215                            if (LogSupport.isLoggable())
216                                LogSupport.log("MimetypesFileTypeMap: " +
217                                    "successfully loaded " +
218                                    "mime types from URL: " + url);
219                        } else {
220                            if (LogSupport.isLoggable())
221                                LogSupport.log("MimetypesFileTypeMap: " +
222                                    "not loading " +
223                                    "mime types from URL: " + url);
224                        }
225                    } catch (IOException ioex) {
226                        if (LogSupport.isLoggable())
227                            LogSupport.log("MimetypesFileTypeMap: can't load " +
228                                                url, ioex);
229                    } catch (SecurityException sex) {
230                        if (LogSupport.isLoggable())
231                            LogSupport.log("MimetypesFileTypeMap: can't load " +
232                                                url, sex);
233                    } finally {
234                        try {
235                            if (clis != null)
236                                clis.close();
237                        } catch (IOException cex) { }
238                    }
239                }
240            }
241        } catch (Exception ex) {
242            if (LogSupport.isLoggable())
243                LogSupport.log("MimetypesFileTypeMap: can't load " + name, ex);
244        }
245
246        // if failed to load anything, fall back to old technique, just in case
247        if (!anyLoaded) {
248            LogSupport.log("MimetypesFileTypeMap: !anyLoaded");
249            MimeTypeFile mf = loadResource("/" + name);
250            if (mf != null)
251                v.addElement(mf);
252        }
253    }
254
255    /**
256     * Load the named file.
257     */
258    private MimeTypeFile loadFile(String name) {
259        MimeTypeFile mtf = null;
260
261        try {
262            mtf = new MimeTypeFile(name);
263        } catch (IOException e) {
264            //  e.printStackTrace();
265        }
266        return mtf;
267    }
268
269    /**
270     * Construct a MimetypesFileTypeMap with programmatic entries
271     * added from the named file.
272     *
273     * @param mimeTypeFileName  the file name
274     * @exception       IOException     for errors reading the file
275     */
276    public MimetypesFileTypeMap(String mimeTypeFileName) throws IOException {
277        this();
278        DB[PROG] = new MimeTypeFile(mimeTypeFileName);
279    }
280
281    /**
282     * Construct a MimetypesFileTypeMap with programmatic entries
283     * added from the InputStream.
284     *
285     * @param is        the input stream to read from
286     */
287    public MimetypesFileTypeMap(InputStream is) {
288        this();
289        try {
290            DB[PROG] = new MimeTypeFile(is);
291        } catch (IOException ex) {
292            // XXX - really should throw it
293        }
294    }
295
296    /**
297     * Prepend the MIME type values to the registry.
298     *
299     * @param mime_types A .mime.types formatted string of entries.
300     */
301    public synchronized void addMimeTypes(String mime_types) {
302        // check to see if we have created the registry
303        if (DB[PROG] == null)
304            DB[PROG] = new MimeTypeFile(); // make one
305
306        DB[PROG].appendToRegistry(mime_types);
307    }
308
309    /**
310     * Return the MIME type of the file object.
311     * The implementation in this class calls
312     * {@code getContentType(f.getName())}.
313     *
314     * @param f the file
315     * @return  the file's MIME type
316     */
317    public String getContentType(File f) {
318        return this.getContentType(f.getName());
319    }
320
321    /**
322     * Return the MIME type based on the specified file name.
323     * The MIME type entries are searched as described above under
324     * <i>MIME types file search order</i>.
325     * If no entry is found, the type "application/octet-stream" is returned.
326     *
327     * @param filename  the file name
328     * @return          the file's MIME type
329     */
330    public synchronized String getContentType(String filename) {
331        int dot_pos = filename.lastIndexOf("."); // period index
332
333        if (dot_pos < 0)
334            return defaultType;
335
336        String file_ext = filename.substring(dot_pos + 1);
337        if (file_ext.length() == 0)
338            return defaultType;
339
340        for (int i = 0; i < DB.length; i++) {
341            if (DB[i] == null)
342                continue;
343            String result = DB[i].getMIMETypeString(file_ext);
344            if (result != null)
345                return result;
346        }
347        return defaultType;
348    }
349
350    /**
351     * for debugging...
352     *
353    public static void main(String[] argv) throws Exception {
354        MimetypesFileTypeMap map = new MimetypesFileTypeMap();
355        System.out.println("File " + argv[0] + " has MIME type " +
356                                                map.getContentType(argv[0]));
357        System.exit(0);
358    }
359    */
360}
361