1/*
2 * Copyright (c) 1994, 2011, 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 sun.net.www;
27import java.io.*;
28import java.net.FileNameMap;
29import java.util.Hashtable;
30import java.util.Enumeration;
31import java.util.Properties;
32import java.util.StringTokenizer;
33
34public class MimeTable implements FileNameMap {
35    /** Keyed by content type, returns MimeEntries */
36    private Hashtable<String, MimeEntry> entries
37        = new Hashtable<String, MimeEntry>();
38
39    /** Keyed by file extension (with the .), returns MimeEntries */
40    private Hashtable<String, MimeEntry> extensionMap
41        = new Hashtable<String, MimeEntry>();
42
43    // Will be reset if in the platform-specific data file
44    private static String tempFileTemplate;
45
46    static {
47        java.security.AccessController.doPrivileged(
48            new java.security.PrivilegedAction<Void>() {
49                public Void run() {
50                tempFileTemplate =
51                    System.getProperty("content.types.temp.file.template",
52                                       "/tmp/%s");
53
54                mailcapLocations = new String[] {
55                    System.getProperty("user.mailcap"),
56                    System.getProperty("user.home") + "/.mailcap",
57                    "/etc/mailcap",
58                    "/usr/etc/mailcap",
59                    "/usr/local/etc/mailcap",
60                    System.getProperty("hotjava.home",
61                                           "/usr/local/hotjava")
62                        + "/lib/mailcap",
63                };
64                return null;
65            }
66        });
67    }
68
69
70    private static final String filePreamble = "sun.net.www MIME content-types table";
71    private static final String fileMagic = "#" + filePreamble;
72
73    MimeTable() {
74        load();
75    }
76
77    private static class DefaultInstanceHolder {
78        static final MimeTable defaultInstance = getDefaultInstance();
79
80        static MimeTable getDefaultInstance() {
81            return java.security.AccessController.doPrivileged(
82                new java.security.PrivilegedAction<MimeTable>() {
83                public MimeTable run() {
84                    MimeTable instance = new MimeTable();
85                    URLConnection.setFileNameMap(instance);
86                    return instance;
87                }
88            });
89        }
90    }
91
92    /**
93     * Get the single instance of this class.  First use will load the
94     * table from a data file.
95     */
96    public static MimeTable getDefaultTable() {
97        return DefaultInstanceHolder.defaultInstance;
98    }
99
100    /**
101     *
102     */
103    public static FileNameMap loadTable() {
104        MimeTable mt = getDefaultTable();
105        return (FileNameMap)mt;
106    }
107
108    public synchronized int getSize() {
109        return entries.size();
110    }
111
112    public synchronized String getContentTypeFor(String fileName) {
113        MimeEntry entry = findByFileName(fileName);
114        if (entry != null) {
115            return entry.getType();
116        } else {
117            return null;
118        }
119    }
120
121    public synchronized void add(MimeEntry m) {
122        entries.put(m.getType(), m);
123
124        String exts[] = m.getExtensions();
125        if (exts == null) {
126            return;
127        }
128
129        for (int i = 0; i < exts.length; i++) {
130            extensionMap.put(exts[i], m);
131        }
132    }
133
134    public synchronized MimeEntry remove(String type) {
135        MimeEntry entry = entries.get(type);
136        return remove(entry);
137    }
138
139    public synchronized MimeEntry remove(MimeEntry entry) {
140        String[] extensionKeys = entry.getExtensions();
141        if (extensionKeys != null) {
142            for (int i = 0; i < extensionKeys.length; i++) {
143                extensionMap.remove(extensionKeys[i]);
144            }
145        }
146
147        return entries.remove(entry.getType());
148    }
149
150    public synchronized MimeEntry find(String type) {
151        MimeEntry entry = entries.get(type);
152        if (entry == null) {
153            // try a wildcard lookup
154            Enumeration<MimeEntry> e = entries.elements();
155            while (e.hasMoreElements()) {
156                MimeEntry wild = e.nextElement();
157                if (wild.matches(type)) {
158                    return wild;
159                }
160            }
161        }
162
163        return entry;
164    }
165
166    /**
167     * Locate a MimeEntry by the file extension that has been associated
168     * with it. Parses general file names, and URLs.
169     */
170    public MimeEntry findByFileName(String fname) {
171        String ext = "";
172
173        int i = fname.lastIndexOf('#');
174
175        if (i > 0) {
176            fname = fname.substring(0, i - 1);
177        }
178
179        i = fname.lastIndexOf('.');
180        // REMIND: OS specific delimters appear here
181        i = Math.max(i, fname.lastIndexOf('/'));
182        i = Math.max(i, fname.lastIndexOf('?'));
183
184        if (i != -1 && fname.charAt(i) == '.') {
185            ext = fname.substring(i).toLowerCase();
186        }
187
188        return findByExt(ext);
189    }
190
191    /**
192     * Locate a MimeEntry by the file extension that has been associated
193     * with it.
194     */
195    public synchronized MimeEntry findByExt(String fileExtension) {
196        return extensionMap.get(fileExtension);
197    }
198
199    public synchronized MimeEntry findByDescription(String description) {
200        Enumeration<MimeEntry> e = elements();
201        while (e.hasMoreElements()) {
202            MimeEntry entry = e.nextElement();
203            if (description.equals(entry.getDescription())) {
204                return entry;
205            }
206        }
207
208        // We failed, now try treating description as type
209        return find(description);
210    }
211
212    String getTempFileTemplate() {
213        return tempFileTemplate;
214    }
215
216    public synchronized Enumeration<MimeEntry> elements() {
217        return entries.elements();
218    }
219
220    // For backward compatibility -- mailcap format files
221    // This is not currently used, but may in the future when we add ability
222    // to read BOTH the properties format and the mailcap format.
223    protected static String[] mailcapLocations;
224
225    public synchronized void load() {
226        Properties entries = new Properties();
227        File file = null;
228        InputStream in;
229
230        // First try to load the user-specific table, if it exists
231        String userTablePath = System.getProperty("content.types.user.table");
232        if (userTablePath != null && (file = new File(userTablePath)).exists()) {
233            try {
234                in = new FileInputStream(file);
235            } catch (FileNotFoundException e) {
236                System.err.println("Warning: " + file.getPath()
237                                   + " mime table not found.");
238                return;
239            }
240        } else {
241            in = MimeTable.class.getResourceAsStream("content-types.properties");
242            if (in == null)
243                throw new InternalError("default mime table not found");
244        }
245
246        try (BufferedInputStream bin = new BufferedInputStream(in)) {
247            entries.load(bin);
248        } catch (IOException e) {
249            System.err.println("Warning: " + e.getMessage());
250        }
251        parse(entries);
252    }
253
254    void parse(Properties entries) {
255        // first, strip out the platform-specific temp file template
256        String tempFileTemplate = (String)entries.get("temp.file.template");
257        if (tempFileTemplate != null) {
258            entries.remove("temp.file.template");
259            MimeTable.tempFileTemplate = tempFileTemplate;
260        }
261
262        // now, parse the mime-type spec's
263        Enumeration<?> types = entries.propertyNames();
264        while (types.hasMoreElements()) {
265            String type = (String)types.nextElement();
266            String attrs = entries.getProperty(type);
267            parse(type, attrs);
268        }
269    }
270
271    //
272    // Table format:
273    //
274    // <entry> ::= <table_tag> | <type_entry>
275    //
276    // <table_tag> ::= <table_format_version> | <temp_file_template>
277    //
278    // <type_entry> ::= <type_subtype_pair> '=' <type_attrs_list>
279    //
280    // <type_subtype_pair> ::= <type> '/' <subtype>
281    //
282    // <type_attrs_list> ::= <attr_value_pair> [ ';' <attr_value_pair> ]*
283    //                       | [ <attr_value_pair> ]+
284    //
285    // <attr_value_pair> ::= <attr_name> '=' <attr_value>
286    //
287    // <attr_name> ::= 'description' | 'action' | 'application'
288    //                 | 'file_extensions' | 'icon'
289    //
290    // <attr_value> ::= <legal_char>*
291    //
292    // Embedded ';' in an <attr_value> are quoted with leading '\' .
293    //
294    // Interpretation of <attr_value> depends on the <attr_name> it is
295    // associated with.
296    //
297
298    void parse(String type, String attrs) {
299        MimeEntry newEntry = new MimeEntry(type);
300
301        // REMIND handle embedded ';' and '|' and literal '"'
302        StringTokenizer tokenizer = new StringTokenizer(attrs, ";");
303        while (tokenizer.hasMoreTokens()) {
304            String pair = tokenizer.nextToken();
305            parse(pair, newEntry);
306        }
307
308        add(newEntry);
309    }
310
311    void parse(String pair, MimeEntry entry) {
312        // REMIND add exception handling...
313        String name = null;
314        String value = null;
315
316        boolean gotName = false;
317        StringTokenizer tokenizer = new StringTokenizer(pair, "=");
318        while (tokenizer.hasMoreTokens()) {
319            if (gotName) {
320                value = tokenizer.nextToken().trim();
321            }
322            else {
323                name = tokenizer.nextToken().trim();
324                gotName = true;
325            }
326        }
327
328        fill(entry, name, value);
329    }
330
331    void fill(MimeEntry entry, String name, String value) {
332        if ("description".equalsIgnoreCase(name)) {
333            entry.setDescription(value);
334        }
335        else if ("action".equalsIgnoreCase(name)) {
336            entry.setAction(getActionCode(value));
337        }
338        else if ("application".equalsIgnoreCase(name)) {
339            entry.setCommand(value);
340        }
341        else if ("icon".equalsIgnoreCase(name)) {
342            entry.setImageFileName(value);
343        }
344        else if ("file_extensions".equalsIgnoreCase(name)) {
345            entry.setExtensions(value);
346        }
347
348        // else illegal name exception
349    }
350
351    String[] getExtensions(String list) {
352        StringTokenizer tokenizer = new StringTokenizer(list, ",");
353        int n = tokenizer.countTokens();
354        String[] extensions = new String[n];
355        for (int i = 0; i < n; i++) {
356            extensions[i] = tokenizer.nextToken();
357        }
358
359        return extensions;
360    }
361
362    int getActionCode(String action) {
363        for (int i = 0; i < MimeEntry.actionKeywords.length; i++) {
364            if (action.equalsIgnoreCase(MimeEntry.actionKeywords[i])) {
365                return i;
366            }
367        }
368
369        return MimeEntry.UNKNOWN;
370    }
371
372    public Properties getAsProperties() {
373        Properties properties = new Properties();
374        Enumeration<MimeEntry> e = elements();
375        while (e.hasMoreElements()) {
376            MimeEntry entry = e.nextElement();
377            properties.put(entry.getType(), entry.toProperty());
378        }
379
380        return properties;
381    }
382
383    protected boolean saveAsProperties(File file) {
384        FileOutputStream os = null;
385        try {
386            os = new FileOutputStream(file);
387            Properties properties = getAsProperties();
388            properties.put("temp.file.template", tempFileTemplate);
389            String tag;
390            String user = System.getProperty("user.name");
391            if (user != null) {
392                tag = "; customized for " + user;
393                properties.store(os, filePreamble + tag);
394            }
395            else {
396                properties.store(os, filePreamble);
397            }
398        }
399        catch (IOException e) {
400            e.printStackTrace();
401            return false;
402        }
403        finally {
404            if (os != null) {
405                try { os.close(); } catch (IOException e) {}
406            }
407        }
408
409        return true;
410    }
411    /*
412     * Debugging utilities
413     *
414    public void list(PrintStream out) {
415        Enumeration keys = entries.keys();
416        while (keys.hasMoreElements()) {
417            String key = (String)keys.nextElement();
418            MimeEntry entry = (MimeEntry)entries.get(key);
419            out.println(key + ": " + entry);
420        }
421    }
422
423    public static void main(String[] args) {
424        MimeTable testTable = MimeTable.getDefaultTable();
425
426        Enumeration e = testTable.elements();
427        while (e.hasMoreElements()) {
428            MimeEntry entry = (MimeEntry)e.nextElement();
429            System.out.println(entry);
430        }
431
432        testTable.save(File.separator + "tmp" +
433                       File.separator + "mime_table.save");
434    }
435    */
436}
437