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 
  26 package sun.net.www;
  27 import java.io.*;
  28 import java.net.FileNameMap;
  29 import java.util.Hashtable;
  30 import java.util.Enumeration;
  31 import java.util.Properties;
  32 import java.util.StringTokenizer;
  33 
  34 public 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     private static final String DEFAULT_FILE =
 226             "/sun/net/www/content-types.properties";
 227 
 228     public synchronized void load() {
 229         Properties entries = new Properties();
 230         File file = null;
 231         try {
 232             InputStream is;
 233             // First try to load the user-specific table, if it exists
 234             String userTablePath =
 235                 System.getProperty("content.types.user.table");
 236             if (userTablePath != null && (file = new File(userTablePath)).exists()) {
 237                 is = new BufferedInputStream(new FileInputStream(file));
 238             } else {
 239                 if ((is = MimeTable.class.getResourceAsStream(DEFAULT_FILE)) == null) {
 240                     System.err.println("Warning: default mime table not found.");
 241                     return;
 242                 }
 243                 is = new BufferedInputStream(is);
 244             }
 245             entries.load(is);
 246             is.close();
 247         } catch (IOException e) {
 248             System.err.println("Warning: " + file != null ? file.getPath() : "default"
 249                                + " mime table not found.");
 250             return;
 251         }
 252         parse(entries);
 253     }
 254 
 255     void parse(Properties entries) {
 256         // first, strip out the platform-specific temp file template
 257         String tempFileTemplate = (String)entries.get("temp.file.template");
 258         if (tempFileTemplate != null) {
 259             entries.remove("temp.file.template");
 260             MimeTable.tempFileTemplate = tempFileTemplate;
 261         }
 262 
 263         // now, parse the mime-type spec's
 264         Enumeration<?> types = entries.propertyNames();
 265         while (types.hasMoreElements()) {
 266             String type = (String)types.nextElement();
 267             String attrs = entries.getProperty(type);
 268             parse(type, attrs);
 269         }
 270     }
 271 
 272     //
 273     // Table format:
 274     //
 275     // <entry> ::= <table_tag> | <type_entry>
 276     //
 277     // <table_tag> ::= <table_format_version> | <temp_file_template>
 278     //
 279     // <type_entry> ::= <type_subtype_pair> '=' <type_attrs_list>
 280     //
 281     // <type_subtype_pair> ::= <type> '/' <subtype>
 282     //
 283     // <type_attrs_list> ::= <attr_value_pair> [ ';' <attr_value_pair> ]*
 284     //                       | [ <attr_value_pair> ]+
 285     //
 286     // <attr_value_pair> ::= <attr_name> '=' <attr_value>
 287     //
 288     // <attr_name> ::= 'description' | 'action' | 'application'
 289     //                 | 'file_extensions' | 'icon'
 290     //
 291     // <attr_value> ::= <legal_char>*
 292     //
 293     // Embedded ';' in an <attr_value> are quoted with leading '\' .
 294     //
 295     // Interpretation of <attr_value> depends on the <attr_name> it is
 296     // associated with.
 297     //
 298 
 299     void parse(String type, String attrs) {
 300         MimeEntry newEntry = new MimeEntry(type);
 301 
 302         // REMIND handle embedded ';' and '|' and literal '"'
 303         StringTokenizer tokenizer = new StringTokenizer(attrs, ";");
 304         while (tokenizer.hasMoreTokens()) {
 305             String pair = tokenizer.nextToken();
 306             parse(pair, newEntry);
 307         }
 308 
 309         add(newEntry);
 310     }
 311 
 312     void parse(String pair, MimeEntry entry) {
 313         // REMIND add exception handling...
 314         String name = null;
 315         String value = null;
 316 
 317         boolean gotName = false;
 318         StringTokenizer tokenizer = new StringTokenizer(pair, "=");
 319         while (tokenizer.hasMoreTokens()) {
 320             if (gotName) {
 321                 value = tokenizer.nextToken().trim();
 322             }
 323             else {
 324                 name = tokenizer.nextToken().trim();
 325                 gotName = true;
 326             }
 327         }
 328 
 329         fill(entry, name, value);
 330     }
 331 
 332     void fill(MimeEntry entry, String name, String value) {
 333         if ("description".equalsIgnoreCase(name)) {
 334             entry.setDescription(value);
 335         }
 336         else if ("action".equalsIgnoreCase(name)) {
 337             entry.setAction(getActionCode(value));
 338         }
 339         else if ("application".equalsIgnoreCase(name)) {
 340             entry.setCommand(value);
 341         }
 342         else if ("icon".equalsIgnoreCase(name)) {
 343             entry.setImageFileName(value);
 344         }
 345         else if ("file_extensions".equalsIgnoreCase(name)) {
 346             entry.setExtensions(value);
 347         }
 348 
 349         // else illegal name exception
 350     }
 351 
 352     String[] getExtensions(String list) {
 353         StringTokenizer tokenizer = new StringTokenizer(list, ",");
 354         int n = tokenizer.countTokens();
 355         String[] extensions = new String[n];
 356         for (int i = 0; i < n; i++) {
 357             extensions[i] = tokenizer.nextToken();
 358         }
 359 
 360         return extensions;
 361     }
 362 
 363     int getActionCode(String action) {
 364         for (int i = 0; i < MimeEntry.actionKeywords.length; i++) {
 365             if (action.equalsIgnoreCase(MimeEntry.actionKeywords[i])) {
 366                 return i;
 367             }
 368         }
 369 
 370         return MimeEntry.UNKNOWN;
 371     }
 372 
 373     public Properties getAsProperties() {
 374         Properties properties = new Properties();
 375         Enumeration<MimeEntry> e = elements();
 376         while (e.hasMoreElements()) {
 377             MimeEntry entry = e.nextElement();
 378             properties.put(entry.getType(), entry.toProperty());
 379         }
 380 
 381         return properties;
 382     }
 383 
 384     protected boolean saveAsProperties(File file) {
 385         FileOutputStream os = null;
 386         try {
 387             os = new FileOutputStream(file);
 388             Properties properties = getAsProperties();
 389             properties.put("temp.file.template", tempFileTemplate);
 390             String tag;
 391             String user = System.getProperty("user.name");
 392             if (user != null) {
 393                 tag = "; customized for " + user;
 394                 properties.store(os, filePreamble + tag);
 395             }
 396             else {
 397                 properties.store(os, filePreamble);
 398             }
 399         }
 400         catch (IOException e) {
 401             e.printStackTrace();
 402             return false;
 403         }
 404         finally {
 405             if (os != null) {
 406                 try { os.close(); } catch (IOException e) {}
 407             }
 408         }
 409 
 410         return true;
 411     }
 412     /*
 413      * Debugging utilities
 414      *
 415     public void list(PrintStream out) {
 416         Enumeration keys = entries.keys();
 417         while (keys.hasMoreElements()) {
 418             String key = (String)keys.nextElement();
 419             MimeEntry entry = (MimeEntry)entries.get(key);
 420             out.println(key + ": " + entry);
 421         }
 422     }
 423 
 424     public static void main(String[] args) {
 425         MimeTable testTable = MimeTable.getDefaultTable();
 426 
 427         Enumeration e = testTable.elements();
 428         while (e.hasMoreElements()) {
 429             MimeEntry entry = (MimeEntry)e.nextElement();
 430             System.out.println(entry);
 431         }
 432 
 433         testTable.save(File.separator + "tmp" +
 434                        File.separator + "mime_table.save");
 435     }
 436     */
 437 }