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 }