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 }