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