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