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