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