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.Locale;
  34 import java.util.Properties;
  35 import java.util.StringTokenizer;
  36 
  37 public class MimeTable implements FileNameMap {
  38     /** Keyed by content type, returns MimeEntries */
  39     private Hashtable<String, MimeEntry> entries
  40         = new Hashtable<String, MimeEntry>();
  41 
  42     /** Keyed by file extension (with the .), returns MimeEntries */
  43     private Hashtable<String, MimeEntry> extensionMap
  44         = new Hashtable<String, MimeEntry>();
  45 
  46     // Will be reset if in the platform-specific data file
  47     private static String tempFileTemplate;
  48 
  49     static {
  50         java.security.AccessController.doPrivileged(
  51             new java.security.PrivilegedAction<Void>() {
  52                 public Void run() {
  53                 tempFileTemplate =
  54                     System.getProperty("content.types.temp.file.template",
  55                                        "/tmp/%s");
  56 
  57                 mailcapLocations = new String[] {
  58                     System.getProperty("user.mailcap"),
  59                     StaticProperty.userHome() + "/.mailcap",
  60                     "/etc/mailcap",
  61                     "/usr/etc/mailcap",
  62                     "/usr/local/etc/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 = getExtension(fname);
 172         return findByExt(ext);
 173     }
 174 
 175     private static String getExtension(String fname) {
 176         int fragmentIndex = fname.lastIndexOf('#');
 177         if (fragmentIndex < 0) {
 178             fragmentIndex = fname.length();
 179         }
 180 
 181         int queryIndex = fname.lastIndexOf('?', fragmentIndex);
 182         if (queryIndex < 0) {
 183             queryIndex = fragmentIndex;
 184         }
 185 
 186         int slashIndex = fname.lastIndexOf('/', queryIndex);
 187         if (slashIndex < 0) {
 188             slashIndex = 0;
 189         }
 190         fname = fname.substring(slashIndex, queryIndex);
 191 
 192         int dotIndex = fname.lastIndexOf('.', queryIndex);
 193         String result;
 194         if (dotIndex >= 0) {
 195             result = fname.substring(dotIndex); // include the '.'
 196         } else {
 197             result = ""; // or: result = "." + fname
 198         }
 199         return result.toLowerCase(Locale.ROOT);
 200     }
 201 
 202     /**
 203      * Locate a MimeEntry by the file extension that has been associated
 204      * with it.
 205      */
 206     public synchronized MimeEntry findByExt(String fileExtension) {
 207         return extensionMap.get(fileExtension);
 208     }
 209 
 210     public synchronized MimeEntry findByDescription(String description) {
 211         Enumeration<MimeEntry> e = elements();
 212         while (e.hasMoreElements()) {
 213             MimeEntry entry = e.nextElement();
 214             if (description.equals(entry.getDescription())) {
 215                 return entry;
 216             }
 217         }
 218 
 219         // We failed, now try treating description as type
 220         return find(description);
 221     }
 222 
 223     String getTempFileTemplate() {
 224         return tempFileTemplate;
 225     }
 226 
 227     public synchronized Enumeration<MimeEntry> elements() {
 228         return entries.elements();
 229     }
 230 
 231     // For backward compatibility -- mailcap format files
 232     // This is not currently used, but may in the future when we add ability
 233     // to read BOTH the properties format and the mailcap format.
 234     protected static String[] mailcapLocations;
 235 
 236     public synchronized void load() {
 237         Properties entries = new Properties();
 238         File file = null;
 239         InputStream in;
 240 
 241         // First try to load the user-specific table, if it exists
 242         String userTablePath = System.getProperty("content.types.user.table");
 243         if (userTablePath != null && (file = new File(userTablePath)).exists()) {
 244             try {
 245                 in = new FileInputStream(file);
 246             } catch (FileNotFoundException e) {
 247                 System.err.println("Warning: " + file.getPath()
 248                                    + " mime table not found.");
 249                 return;
 250             }
 251         } else {
 252             in = MimeTable.class.getResourceAsStream("content-types.properties");
 253             if (in == null)
 254                 throw new InternalError("default mime table not found");
 255         }
 256 
 257         try (BufferedInputStream bin = new BufferedInputStream(in)) {
 258             entries.load(bin);
 259         } catch (IOException e) {
 260             System.err.println("Warning: " + e.getMessage());
 261         }
 262         parse(entries);
 263     }
 264 
 265     void parse(Properties entries) {
 266         // first, strip out the platform-specific temp file template
 267         String tempFileTemplate = (String)entries.get("temp.file.template");
 268         if (tempFileTemplate != null) {
 269             entries.remove("temp.file.template");
 270             MimeTable.tempFileTemplate = tempFileTemplate;
 271         }
 272 
 273         // now, parse the mime-type spec's
 274         Enumeration<?> types = entries.propertyNames();
 275         while (types.hasMoreElements()) {
 276             String type = (String)types.nextElement();
 277             String attrs = entries.getProperty(type);
 278             parse(type, attrs);
 279         }
 280     }
 281 
 282     //
 283     // Table format:
 284     //
 285     // <entry> ::= <table_tag> | <type_entry>
 286     //
 287     // <table_tag> ::= <table_format_version> | <temp_file_template>
 288     //
 289     // <type_entry> ::= <type_subtype_pair> '=' <type_attrs_list>
 290     //
 291     // <type_subtype_pair> ::= <type> '/' <subtype>
 292     //
 293     // <type_attrs_list> ::= <attr_value_pair> [ ';' <attr_value_pair> ]*
 294     //                       | [ <attr_value_pair> ]+
 295     //
 296     // <attr_value_pair> ::= <attr_name> '=' <attr_value>
 297     //
 298     // <attr_name> ::= 'description' | 'action' | 'application'
 299     //                 | 'file_extensions' | 'icon'
 300     //
 301     // <attr_value> ::= <legal_char>*
 302     //
 303     // Embedded ';' in an <attr_value> are quoted with leading '\' .
 304     //
 305     // Interpretation of <attr_value> depends on the <attr_name> it is
 306     // associated with.
 307     //
 308 
 309     void parse(String type, String attrs) {
 310         MimeEntry newEntry = new MimeEntry(type);
 311 
 312         // REMIND handle embedded ';' and '|' and literal '"'
 313         StringTokenizer tokenizer = new StringTokenizer(attrs, ";");
 314         while (tokenizer.hasMoreTokens()) {
 315             String pair = tokenizer.nextToken();
 316             parse(pair, newEntry);
 317         }
 318 
 319         add(newEntry);
 320     }
 321 
 322     void parse(String pair, MimeEntry entry) {
 323         // REMIND add exception handling...
 324         String name = null;
 325         String value = null;
 326 
 327         boolean gotName = false;
 328         StringTokenizer tokenizer = new StringTokenizer(pair, "=");
 329         while (tokenizer.hasMoreTokens()) {
 330             if (gotName) {
 331                 value = tokenizer.nextToken().trim();
 332             }
 333             else {
 334                 name = tokenizer.nextToken().trim();
 335                 gotName = true;
 336             }
 337         }
 338 
 339         fill(entry, name, value);
 340     }
 341 
 342     void fill(MimeEntry entry, String name, String value) {
 343         if ("description".equalsIgnoreCase(name)) {
 344             entry.setDescription(value);
 345         }
 346         else if ("action".equalsIgnoreCase(name)) {
 347             entry.setAction(getActionCode(value));
 348         }
 349         else if ("application".equalsIgnoreCase(name)) {
 350             entry.setCommand(value);
 351         }
 352         else if ("icon".equalsIgnoreCase(name)) {
 353             entry.setImageFileName(value);
 354         }
 355         else if ("file_extensions".equalsIgnoreCase(name)) {
 356             entry.setExtensions(value);
 357         }
 358 
 359         // else illegal name exception
 360     }
 361 
 362     String[] getExtensions(String list) {
 363         StringTokenizer tokenizer = new StringTokenizer(list, ",");
 364         int n = tokenizer.countTokens();
 365         String[] extensions = new String[n];
 366         for (int i = 0; i < n; i++) {
 367             extensions[i] = tokenizer.nextToken();
 368         }
 369 
 370         return extensions;
 371     }
 372 
 373     int getActionCode(String action) {
 374         for (int i = 0; i < MimeEntry.actionKeywords.length; i++) {
 375             if (action.equalsIgnoreCase(MimeEntry.actionKeywords[i])) {
 376                 return i;
 377             }
 378         }
 379 
 380         return MimeEntry.UNKNOWN;
 381     }
 382 
 383     public Properties getAsProperties() {
 384         Properties properties = new Properties();
 385         Enumeration<MimeEntry> e = elements();
 386         while (e.hasMoreElements()) {
 387             MimeEntry entry = e.nextElement();
 388             properties.put(entry.getType(), entry.toProperty());
 389         }
 390 
 391         return properties;
 392     }
 393 
 394     protected boolean saveAsProperties(File file) {
 395         FileOutputStream os = null;
 396         try {
 397             os = new FileOutputStream(file);
 398             Properties properties = getAsProperties();
 399             properties.put("temp.file.template", tempFileTemplate);
 400             String tag;
 401             // Perform the property security check for user.name
 402             SecurityManager sm = System.getSecurityManager();
 403             if (sm != null) {
 404                 sm.checkPropertyAccess("user.name");
 405             }
 406             String user = StaticProperty.userName();
 407             if (user != null) {
 408                 tag = "; customized for " + user;
 409                 properties.store(os, filePreamble + tag);
 410             }
 411             else {
 412                 properties.store(os, filePreamble);
 413             }
 414         }
 415         catch (IOException e) {
 416             e.printStackTrace();
 417             return false;
 418         }
 419         finally {
 420             if (os != null) {
 421                 try { os.close(); } catch (IOException e) {}
 422             }
 423         }
 424 
 425         return true;
 426     }
 427     /*
 428      * Debugging utilities
 429      *
 430     public void list(PrintStream out) {
 431         Enumeration keys = entries.keys();
 432         while (keys.hasMoreElements()) {
 433             String key = (String)keys.nextElement();
 434             MimeEntry entry = (MimeEntry)entries.get(key);
 435             out.println(key + ": " + entry);
 436         }
 437     }
 438 
 439     public static void main(String[] args) {
 440         MimeTable testTable = MimeTable.getDefaultTable();
 441 
 442         Enumeration e = testTable.elements();
 443         while (e.hasMoreElements()) {
 444             MimeEntry entry = (MimeEntry)e.nextElement();
 445             System.out.println(entry);
 446         }
 447 
 448         testTable.save(File.separator + "tmp" +
 449                        File.separator + "mime_table.save");
 450     }
 451     */
 452 }