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 }