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 26package sun.net.www; 27import java.io.*; 28import java.net.FileNameMap; 29import java.util.Hashtable; 30import java.util.Enumeration; 31import java.util.Properties; 32import java.util.StringTokenizer; 33 34public 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 public synchronized void load() { 226 Properties entries = new Properties(); 227 File file = null; 228 InputStream in; 229 230 // First try to load the user-specific table, if it exists 231 String userTablePath = System.getProperty("content.types.user.table"); 232 if (userTablePath != null && (file = new File(userTablePath)).exists()) { 233 try { 234 in = new FileInputStream(file); 235 } catch (FileNotFoundException e) { 236 System.err.println("Warning: " + file.getPath() 237 + " mime table not found."); 238 return; 239 } 240 } else { 241 in = MimeTable.class.getResourceAsStream("content-types.properties"); 242 if (in == null) 243 throw new InternalError("default mime table not found"); 244 } 245 246 try (BufferedInputStream bin = new BufferedInputStream(in)) { 247 entries.load(bin); 248 } catch (IOException e) { 249 System.err.println("Warning: " + e.getMessage()); 250 } 251 parse(entries); 252 } 253 254 void parse(Properties entries) { 255 // first, strip out the platform-specific temp file template 256 String tempFileTemplate = (String)entries.get("temp.file.template"); 257 if (tempFileTemplate != null) { 258 entries.remove("temp.file.template"); 259 MimeTable.tempFileTemplate = tempFileTemplate; 260 } 261 262 // now, parse the mime-type spec's 263 Enumeration<?> types = entries.propertyNames(); 264 while (types.hasMoreElements()) { 265 String type = (String)types.nextElement(); 266 String attrs = entries.getProperty(type); 267 parse(type, attrs); 268 } 269 } 270 271 // 272 // Table format: 273 // 274 // <entry> ::= <table_tag> | <type_entry> 275 // 276 // <table_tag> ::= <table_format_version> | <temp_file_template> 277 // 278 // <type_entry> ::= <type_subtype_pair> '=' <type_attrs_list> 279 // 280 // <type_subtype_pair> ::= <type> '/' <subtype> 281 // 282 // <type_attrs_list> ::= <attr_value_pair> [ ';' <attr_value_pair> ]* 283 // | [ <attr_value_pair> ]+ 284 // 285 // <attr_value_pair> ::= <attr_name> '=' <attr_value> 286 // 287 // <attr_name> ::= 'description' | 'action' | 'application' 288 // | 'file_extensions' | 'icon' 289 // 290 // <attr_value> ::= <legal_char>* 291 // 292 // Embedded ';' in an <attr_value> are quoted with leading '\' . 293 // 294 // Interpretation of <attr_value> depends on the <attr_name> it is 295 // associated with. 296 // 297 298 void parse(String type, String attrs) { 299 MimeEntry newEntry = new MimeEntry(type); 300 301 // REMIND handle embedded ';' and '|' and literal '"' 302 StringTokenizer tokenizer = new StringTokenizer(attrs, ";"); 303 while (tokenizer.hasMoreTokens()) { 304 String pair = tokenizer.nextToken(); 305 parse(pair, newEntry); 306 } 307 308 add(newEntry); 309 } 310 311 void parse(String pair, MimeEntry entry) { 312 // REMIND add exception handling... 313 String name = null; 314 String value = null; 315 316 boolean gotName = false; 317 StringTokenizer tokenizer = new StringTokenizer(pair, "="); 318 while (tokenizer.hasMoreTokens()) { 319 if (gotName) { 320 value = tokenizer.nextToken().trim(); 321 } 322 else { 323 name = tokenizer.nextToken().trim(); 324 gotName = true; 325 } 326 } 327 328 fill(entry, name, value); 329 } 330 331 void fill(MimeEntry entry, String name, String value) { 332 if ("description".equalsIgnoreCase(name)) { 333 entry.setDescription(value); 334 } 335 else if ("action".equalsIgnoreCase(name)) { 336 entry.setAction(getActionCode(value)); 337 } 338 else if ("application".equalsIgnoreCase(name)) { 339 entry.setCommand(value); 340 } 341 else if ("icon".equalsIgnoreCase(name)) { 342 entry.setImageFileName(value); 343 } 344 else if ("file_extensions".equalsIgnoreCase(name)) { 345 entry.setExtensions(value); 346 } 347 348 // else illegal name exception 349 } 350 351 String[] getExtensions(String list) { 352 StringTokenizer tokenizer = new StringTokenizer(list, ","); 353 int n = tokenizer.countTokens(); 354 String[] extensions = new String[n]; 355 for (int i = 0; i < n; i++) { 356 extensions[i] = tokenizer.nextToken(); 357 } 358 359 return extensions; 360 } 361 362 int getActionCode(String action) { 363 for (int i = 0; i < MimeEntry.actionKeywords.length; i++) { 364 if (action.equalsIgnoreCase(MimeEntry.actionKeywords[i])) { 365 return i; 366 } 367 } 368 369 return MimeEntry.UNKNOWN; 370 } 371 372 public Properties getAsProperties() { 373 Properties properties = new Properties(); 374 Enumeration<MimeEntry> e = elements(); 375 while (e.hasMoreElements()) { 376 MimeEntry entry = e.nextElement(); 377 properties.put(entry.getType(), entry.toProperty()); 378 } 379 380 return properties; 381 } 382 383 protected boolean saveAsProperties(File file) { 384 FileOutputStream os = null; 385 try { 386 os = new FileOutputStream(file); 387 Properties properties = getAsProperties(); 388 properties.put("temp.file.template", tempFileTemplate); 389 String tag; 390 String user = System.getProperty("user.name"); 391 if (user != null) { 392 tag = "; customized for " + user; 393 properties.store(os, filePreamble + tag); 394 } 395 else { 396 properties.store(os, filePreamble); 397 } 398 } 399 catch (IOException e) { 400 e.printStackTrace(); 401 return false; 402 } 403 finally { 404 if (os != null) { 405 try { os.close(); } catch (IOException e) {} 406 } 407 } 408 409 return true; 410 } 411 /* 412 * Debugging utilities 413 * 414 public void list(PrintStream out) { 415 Enumeration keys = entries.keys(); 416 while (keys.hasMoreElements()) { 417 String key = (String)keys.nextElement(); 418 MimeEntry entry = (MimeEntry)entries.get(key); 419 out.println(key + ": " + entry); 420 } 421 } 422 423 public static void main(String[] args) { 424 MimeTable testTable = MimeTable.getDefaultTable(); 425 426 Enumeration e = testTable.elements(); 427 while (e.hasMoreElements()) { 428 MimeEntry entry = (MimeEntry)e.nextElement(); 429 System.out.println(entry); 430 } 431 432 testTable.save(File.separator + "tmp" + 433 File.separator + "mime_table.save"); 434 } 435 */ 436} 437