1 /* 2 * Copyright (c) 1997, 2012, 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 27 package javax.activation; 28 29 import java.util.*; 30 import java.io.*; 31 import java.net.*; 32 import com.sun.activation.registries.MailcapFile; 33 import com.sun.activation.registries.LogSupport; 34 35 /** 36 * MailcapCommandMap extends the CommandMap 37 * abstract class. It implements a CommandMap whose configuration 38 * is based on mailcap files 39 * (<A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A>). 40 * The MailcapCommandMap can be configured both programmatically 41 * and via configuration files. 42 * <p> 43 * <b>Mailcap file search order:</b><p> 44 * The MailcapCommandMap looks in various places in the user's 45 * system for mailcap file entries. When requests are made 46 * to search for commands in the MailcapCommandMap, it searches 47 * mailcap files in the following order: 48 * <p> 49 * <ol> 50 * <li> Programatically added entries to the MailcapCommandMap instance. 51 * <li> The file <code>.mailcap</code> in the user's home directory. 52 * <li> The file <<i>java.home</i>><code>/lib/mailcap</code>. 53 * <li> The file or resources named <code>META-INF/mailcap</code>. 54 * <li> The file or resource named <code>META-INF/mailcap.default</code> 55 * (usually found only in the <code>activation.jar</code> file). 56 * </ol> 57 * <p> 58 * <b>Mailcap file format:</b><p> 59 * 60 * Mailcap files must conform to the mailcap 61 * file specification (RFC 1524, <i>A User Agent Configuration Mechanism 62 * For Multimedia Mail Format Information</i>). 63 * The file format consists of entries corresponding to 64 * particular MIME types. In general, the specification 65 * specifies <i>applications</i> for clients to use when they 66 * themselves cannot operate on the specified MIME type. The 67 * MailcapCommandMap extends this specification by using a parameter mechanism 68 * in mailcap files that allows JavaBeans(tm) components to be specified as 69 * corresponding to particular commands for a MIME type.<p> 70 * 71 * When a mailcap file is 72 * parsed, the MailcapCommandMap recognizes certain parameter signatures, 73 * specifically those parameter names that begin with <code>x-java-</code>. 74 * The MailcapCommandMap uses this signature to find 75 * command entries for inclusion into its registries. 76 * Parameter names with the form <code>x-java-<name></code> 77 * are read by the MailcapCommandMap as identifying a command 78 * with the name <i>name</i>. When the <i>name</i> is <code> 79 * content-handler</code> the MailcapCommandMap recognizes the class 80 * signified by this parameter as a <i>DataContentHandler</i>. 81 * All other commands are handled generically regardless of command 82 * name. The command implementation is specified by a fully qualified 83 * class name of a JavaBean(tm) component. For example; a command for viewing 84 * some data can be specified as: <code>x-java-view=com.foo.ViewBean</code>.<p> 85 * 86 * When the command name is <code>fallback-entry</code>, the value of 87 * the command may be <code>true</code> or <code>false</code>. An 88 * entry for a MIME type that includes a parameter of 89 * <code>x-java-fallback-entry=true</code> defines fallback commands 90 * for that MIME type that will only be used if no non-fallback entry 91 * can be found. For example, an entry of the form <code>text/*; ; 92 * x-java-fallback-entry=true; x-java-view=com.sun.TextViewer</code> 93 * specifies a view command to be used for any text MIME type. This 94 * view command would only be used if a non-fallback view command for 95 * the MIME type could not be found.<p> 96 * 97 * MailcapCommandMap aware mailcap files have the 98 * following general form:<p> 99 * <code> 100 * # Comments begin with a '#' and continue to the end of the line.<br> 101 * <mime type>; ; <parameter list><br> 102 * # Where a parameter list consists of one or more parameters,<br> 103 * # where parameters look like: x-java-view=com.sun.TextViewer<br> 104 * # and a parameter list looks like: <br> 105 * text/plain; ; x-java-view=com.sun.TextViewer; x-java-edit=com.sun.TextEdit 106 * <br> 107 * # Note that mailcap entries that do not contain 'x-java' parameters<br> 108 * # and comply to RFC 1524 are simply ignored:<br> 109 * image/gif; /usr/dt/bin/sdtimage %s<br> 110 * 111 * </code> 112 * <p> 113 * 114 * @author Bart Calder 115 * @author Bill Shannon 116 * 117 * @since 1.6 118 */ 119 120 public class MailcapCommandMap extends CommandMap { 121 /* 122 * We manage a collection of databases, searched in order. 123 */ 124 private MailcapFile[] DB; 125 private static final int PROG = 0; // programmatically added entries 126 127 /** 128 * The default Constructor. 129 */ 130 public MailcapCommandMap() { 131 super(); 132 List dbv = new ArrayList(5); // usually 5 or less databases 133 MailcapFile mf = null; 134 dbv.add(null); // place holder for PROG entry 135 136 LogSupport.log("MailcapCommandMap: load HOME"); 137 try { 138 String user_home = System.getProperty("user.home"); 139 140 if (user_home != null) { 141 String path = user_home + File.separator + ".mailcap"; 142 mf = loadFile(path); 143 if (mf != null) 144 dbv.add(mf); 145 } 146 } catch (SecurityException ex) {} 147 148 LogSupport.log("MailcapCommandMap: load SYS"); 149 try { 150 // check system's home 151 String system_mailcap = System.getProperty("java.home") + 152 File.separator + "lib" + File.separator + "mailcap"; 153 mf = loadFile(system_mailcap); 154 if (mf != null) 155 dbv.add(mf); 156 } catch (SecurityException ex) {} 157 158 LogSupport.log("MailcapCommandMap: load JAR"); 159 // load from the app's jar file 160 loadAllResources(dbv, "META-INF/mailcap"); 161 162 LogSupport.log("MailcapCommandMap: load DEF"); 163 mf = loadResource("/META-INF/mailcap.default"); 164 165 if (mf != null) 166 dbv.add(mf); 167 168 DB = new MailcapFile[dbv.size()]; 169 DB = (MailcapFile[])dbv.toArray(DB); 170 } 171 172 /** 173 * Load from the named resource. 174 */ 175 private MailcapFile loadResource(String name) { 176 InputStream clis = null; 177 try { 178 clis = SecuritySupport.getResourceAsStream(this.getClass(), name); 179 if (clis != null) { 180 MailcapFile mf = new MailcapFile(clis); 181 if (LogSupport.isLoggable()) 182 LogSupport.log("MailcapCommandMap: successfully loaded " + 183 "mailcap file: " + name); 184 return mf; 185 } else { 186 if (LogSupport.isLoggable()) 187 LogSupport.log("MailcapCommandMap: not loading " + 188 "mailcap file: " + name); 189 } 190 } catch (IOException e) { 191 if (LogSupport.isLoggable()) 192 LogSupport.log("MailcapCommandMap: can't load " + name, e); 193 } catch (SecurityException sex) { 194 if (LogSupport.isLoggable()) 195 LogSupport.log("MailcapCommandMap: can't load " + name, sex); 196 } finally { 197 try { 198 if (clis != null) 199 clis.close(); 200 } catch (IOException ex) { } // ignore it 201 } 202 return null; 203 } 204 205 /** 206 * Load all of the named resource. 207 */ 208 private void loadAllResources(List v, String name) { 209 boolean anyLoaded = false; 210 try { 211 URL[] urls; 212 ClassLoader cld = null; 213 // First try the "application's" class loader. 214 cld = SecuritySupport.getContextClassLoader(); 215 if (cld == null) 216 cld = this.getClass().getClassLoader(); 217 if (cld != null) 218 urls = SecuritySupport.getResources(cld, name); 219 else 220 urls = SecuritySupport.getSystemResources(name); 221 if (urls != null) { 222 if (LogSupport.isLoggable()) 223 LogSupport.log("MailcapCommandMap: getResources"); 224 for (int i = 0; i < urls.length; i++) { 225 URL url = urls[i]; 226 InputStream clis = null; 227 if (LogSupport.isLoggable()) 228 LogSupport.log("MailcapCommandMap: URL " + url); 229 try { 230 clis = SecuritySupport.openStream(url); 231 if (clis != null) { 232 v.add(new MailcapFile(clis)); 233 anyLoaded = true; 234 if (LogSupport.isLoggable()) 235 LogSupport.log("MailcapCommandMap: " + 236 "successfully loaded " + 237 "mailcap file from URL: " + 238 url); 239 } else { 240 if (LogSupport.isLoggable()) 241 LogSupport.log("MailcapCommandMap: " + 242 "not loading mailcap " + 243 "file from URL: " + url); 244 } 245 } catch (IOException ioex) { 246 if (LogSupport.isLoggable()) 247 LogSupport.log("MailcapCommandMap: can't load " + 248 url, ioex); 249 } catch (SecurityException sex) { 250 if (LogSupport.isLoggable()) 251 LogSupport.log("MailcapCommandMap: can't load " + 252 url, sex); 253 } finally { 254 try { 255 if (clis != null) 256 clis.close(); 257 } catch (IOException cex) { } 258 } 259 } 260 } 261 } catch (Exception ex) { 262 if (LogSupport.isLoggable()) 263 LogSupport.log("MailcapCommandMap: can't load " + name, ex); 264 } 265 266 // if failed to load anything, fall back to old technique, just in case 267 if (!anyLoaded) { 268 if (LogSupport.isLoggable()) 269 LogSupport.log("MailcapCommandMap: !anyLoaded"); 270 MailcapFile mf = loadResource("/" + name); 271 if (mf != null) 272 v.add(mf); 273 } 274 } 275 276 /** 277 * Load from the named file. 278 */ 279 private MailcapFile loadFile(String name) { 280 MailcapFile mtf = null; 281 282 try { 283 mtf = new MailcapFile(name); 284 } catch (IOException e) { 285 // e.printStackTrace(); 286 } 287 return mtf; 288 } 289 290 /** 291 * Constructor that allows the caller to specify the path 292 * of a <i>mailcap</i> file. 293 * 294 * @param fileName The name of the <i>mailcap</i> file to open 295 * @exception IOException if the file can't be accessed 296 */ 297 public MailcapCommandMap(String fileName) throws IOException { 298 this(); 299 300 if (LogSupport.isLoggable()) 301 LogSupport.log("MailcapCommandMap: load PROG from " + fileName); 302 if (DB[PROG] == null) { 303 DB[PROG] = new MailcapFile(fileName); 304 } 305 } 306 307 308 /** 309 * Constructor that allows the caller to specify an <i>InputStream</i> 310 * containing a mailcap file. 311 * 312 * @param is InputStream of the <i>mailcap</i> file to open 313 */ 314 public MailcapCommandMap(InputStream is) { 315 this(); 316 317 LogSupport.log("MailcapCommandMap: load PROG"); 318 if (DB[PROG] == null) { 319 try { 320 DB[PROG] = new MailcapFile(is); 321 } catch (IOException ex) { 322 // XXX - should throw it 323 } 324 } 325 } 326 327 /** 328 * Get the preferred command list for a MIME Type. The MailcapCommandMap 329 * searches the mailcap files as described above under 330 * <i>Mailcap file search order</i>.<p> 331 * 332 * The result of the search is a proper subset of available 333 * commands in all mailcap files known to this instance of 334 * MailcapCommandMap. The first entry for a particular command 335 * is considered the preferred command. 336 * 337 * @param mimeType the MIME type 338 * @return the CommandInfo objects representing the preferred commands. 339 */ 340 public synchronized CommandInfo[] getPreferredCommands(String mimeType) { 341 List cmdList = new ArrayList(); 342 if (mimeType != null) 343 mimeType = mimeType.toLowerCase(Locale.ENGLISH); 344 345 for (int i = 0; i < DB.length; i++) { 346 if (DB[i] == null) 347 continue; 348 Map cmdMap = DB[i].getMailcapList(mimeType); 349 if (cmdMap != null) 350 appendPrefCmdsToList(cmdMap, cmdList); 351 } 352 353 // now add the fallback commands 354 for (int i = 0; i < DB.length; i++) { 355 if (DB[i] == null) 356 continue; 357 Map cmdMap = DB[i].getMailcapFallbackList(mimeType); 358 if (cmdMap != null) 359 appendPrefCmdsToList(cmdMap, cmdList); 360 } 361 362 CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()]; 363 cmdInfos = (CommandInfo[])cmdList.toArray(cmdInfos); 364 365 return cmdInfos; 366 } 367 368 /** 369 * Put the commands that are in the hash table, into the list. 370 */ 371 private void appendPrefCmdsToList(Map cmdHash, List cmdList) { 372 Iterator verb_enum = cmdHash.keySet().iterator(); 373 374 while (verb_enum.hasNext()) { 375 String verb = (String)verb_enum.next(); 376 if (!checkForVerb(cmdList, verb)) { 377 List cmdList2 = (List)cmdHash.get(verb); // get the list 378 String className = (String)cmdList2.get(0); 379 cmdList.add(new CommandInfo(verb, className)); 380 } 381 } 382 } 383 384 /** 385 * Check the cmdList to see if this command exists, return 386 * true if the verb is there. 387 */ 388 private boolean checkForVerb(List cmdList, String verb) { 389 Iterator ee = cmdList.iterator(); 390 while (ee.hasNext()) { 391 String enum_verb = 392 (String)((CommandInfo)ee.next()).getCommandName(); 393 if (enum_verb.equals(verb)) 394 return true; 395 } 396 return false; 397 } 398 399 /** 400 * Get all the available commands in all mailcap files known to 401 * this instance of MailcapCommandMap for this MIME type. 402 * 403 * @param mimeType the MIME type 404 * @return the CommandInfo objects representing all the commands. 405 */ 406 public synchronized CommandInfo[] getAllCommands(String mimeType) { 407 List cmdList = new ArrayList(); 408 if (mimeType != null) 409 mimeType = mimeType.toLowerCase(Locale.ENGLISH); 410 411 for (int i = 0; i < DB.length; i++) { 412 if (DB[i] == null) 413 continue; 414 Map cmdMap = DB[i].getMailcapList(mimeType); 415 if (cmdMap != null) 416 appendCmdsToList(cmdMap, cmdList); 417 } 418 419 // now add the fallback commands 420 for (int i = 0; i < DB.length; i++) { 421 if (DB[i] == null) 422 continue; 423 Map cmdMap = DB[i].getMailcapFallbackList(mimeType); 424 if (cmdMap != null) 425 appendCmdsToList(cmdMap, cmdList); 426 } 427 428 CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()]; 429 cmdInfos = (CommandInfo[])cmdList.toArray(cmdInfos); 430 431 return cmdInfos; 432 } 433 434 /** 435 * Put the commands that are in the hash table, into the list. 436 */ 437 private void appendCmdsToList(Map typeHash, List cmdList) { 438 Iterator verb_enum = typeHash.keySet().iterator(); 439 440 while (verb_enum.hasNext()) { 441 String verb = (String)verb_enum.next(); 442 List cmdList2 = (List)typeHash.get(verb); 443 Iterator cmd_enum = ((List)cmdList2).iterator(); 444 445 while (cmd_enum.hasNext()) { 446 String cmd = (String)cmd_enum.next(); 447 cmdList.add(new CommandInfo(verb, cmd)); 448 // cmdList.add(0, new CommandInfo(verb, cmd)); 449 } 450 } 451 } 452 453 /** 454 * Get the command corresponding to <code>cmdName</code> for the MIME type. 455 * 456 * @param mimeType the MIME type 457 * @param cmdName the command name 458 * @return the CommandInfo object corresponding to the command. 459 */ 460 public synchronized CommandInfo getCommand(String mimeType, 461 String cmdName) { 462 if (mimeType != null) 463 mimeType = mimeType.toLowerCase(Locale.ENGLISH); 464 465 for (int i = 0; i < DB.length; i++) { 466 if (DB[i] == null) 467 continue; 468 Map cmdMap = DB[i].getMailcapList(mimeType); 469 if (cmdMap != null) { 470 // get the cmd list for the cmd 471 List v = (List)cmdMap.get(cmdName); 472 if (v != null) { 473 String cmdClassName = (String)v.get(0); 474 475 if (cmdClassName != null) 476 return new CommandInfo(cmdName, cmdClassName); 477 } 478 } 479 } 480 481 // now try the fallback list 482 for (int i = 0; i < DB.length; i++) { 483 if (DB[i] == null) 484 continue; 485 Map cmdMap = DB[i].getMailcapFallbackList(mimeType); 486 if (cmdMap != null) { 487 // get the cmd list for the cmd 488 List v = (List)cmdMap.get(cmdName); 489 if (v != null) { 490 String cmdClassName = (String)v.get(0); 491 492 if (cmdClassName != null) 493 return new CommandInfo(cmdName, cmdClassName); 494 } 495 } 496 } 497 return null; 498 } 499 500 /** 501 * Add entries to the registry. Programmatically 502 * added entries are searched before other entries.<p> 503 * 504 * The string that is passed in should be in mailcap 505 * format. 506 * 507 * @param mail_cap a correctly formatted mailcap string 508 */ 509 public synchronized void addMailcap(String mail_cap) { 510 // check to see if one exists 511 LogSupport.log("MailcapCommandMap: add to PROG"); 512 if (DB[PROG] == null) 513 DB[PROG] = new MailcapFile(); 514 515 DB[PROG].appendToMailcap(mail_cap); 516 } 517 518 /** 519 * Return the DataContentHandler for the specified MIME type. 520 * 521 * @param mimeType the MIME type 522 * @return the DataContentHandler 523 */ 524 public synchronized DataContentHandler createDataContentHandler( 525 String mimeType) { 526 if (LogSupport.isLoggable()) 527 LogSupport.log( 528 "MailcapCommandMap: createDataContentHandler for " + mimeType); 529 if (mimeType != null) 530 mimeType = mimeType.toLowerCase(Locale.ENGLISH); 531 532 for (int i = 0; i < DB.length; i++) { 533 if (DB[i] == null) 534 continue; 535 if (LogSupport.isLoggable()) 536 LogSupport.log(" search DB #" + i); 537 Map cmdMap = DB[i].getMailcapList(mimeType); 538 if (cmdMap != null) { 539 List v = (List)cmdMap.get("content-handler"); 540 if (v != null) { 541 String name = (String)v.get(0); 542 DataContentHandler dch = getDataContentHandler(name); 543 if (dch != null) 544 return dch; 545 } 546 } 547 } 548 549 // now try the fallback entries 550 for (int i = 0; i < DB.length; i++) { 551 if (DB[i] == null) 552 continue; 553 if (LogSupport.isLoggable()) 554 LogSupport.log(" search fallback DB #" + i); 555 Map cmdMap = DB[i].getMailcapFallbackList(mimeType); 556 if (cmdMap != null) { 557 List v = (List)cmdMap.get("content-handler"); 558 if (v != null) { 559 String name = (String)v.get(0); 560 DataContentHandler dch = getDataContentHandler(name); 561 if (dch != null) 562 return dch; 563 } 564 } 565 } 566 return null; 567 } 568 569 private DataContentHandler getDataContentHandler(String name) { 570 if (LogSupport.isLoggable()) 571 LogSupport.log(" got content-handler"); 572 if (LogSupport.isLoggable()) 573 LogSupport.log(" class " + name); 574 try { 575 ClassLoader cld = null; 576 // First try the "application's" class loader. 577 cld = SecuritySupport.getContextClassLoader(); 578 if (cld == null) 579 cld = this.getClass().getClassLoader(); 580 Class cl = null; 581 try { 582 cl = cld.loadClass(name); 583 } catch (Exception ex) { 584 // if anything goes wrong, do it the old way 585 cl = Class.forName(name); 586 } 587 if (cl != null) // XXX - always true? 588 return (DataContentHandler)cl.newInstance(); 589 } catch (IllegalAccessException e) { 590 if (LogSupport.isLoggable()) 591 LogSupport.log("Can't load DCH " + name, e); 592 } catch (ClassNotFoundException e) { 593 if (LogSupport.isLoggable()) 594 LogSupport.log("Can't load DCH " + name, e); 595 } catch (InstantiationException e) { 596 if (LogSupport.isLoggable()) 597 LogSupport.log("Can't load DCH " + name, e); 598 } 599 return null; 600 } 601 602 /** 603 * Get all the MIME types known to this command map. 604 * 605 * @return array of MIME types as strings 606 * @since 1.6, JAF 1.1 607 */ 608 public synchronized String[] getMimeTypes() { 609 List mtList = new ArrayList(); 610 611 for (int i = 0; i < DB.length; i++) { 612 if (DB[i] == null) 613 continue; 614 String[] ts = DB[i].getMimeTypes(); 615 if (ts != null) { 616 for (int j = 0; j < ts.length; j++) { 617 // eliminate duplicates 618 if (!mtList.contains(ts[j])) 619 mtList.add(ts[j]); 620 } 621 } 622 } 623 624 String[] mts = new String[mtList.size()]; 625 mts = (String[])mtList.toArray(mts); 626 627 return mts; 628 } 629 630 /** 631 * Get the native commands for the given MIME type. 632 * Returns an array of strings where each string is 633 * an entire mailcap file entry. The application 634 * will need to parse the entry to extract the actual 635 * command as well as any attributes it needs. See 636 * <A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A> 637 * for details of the mailcap entry syntax. Only mailcap 638 * entries that specify a view command for the specified 639 * MIME type are returned. 640 * 641 * @return array of native command entries 642 * @since 1.6, JAF 1.1 643 */ 644 public synchronized String[] getNativeCommands(String mimeType) { 645 List cmdList = new ArrayList(); 646 if (mimeType != null) 647 mimeType = mimeType.toLowerCase(Locale.ENGLISH); 648 649 for (int i = 0; i < DB.length; i++) { 650 if (DB[i] == null) 651 continue; 652 String[] cmds = DB[i].getNativeCommands(mimeType); 653 if (cmds != null) { 654 for (int j = 0; j < cmds.length; j++) { 655 // eliminate duplicates 656 if (!cmdList.contains(cmds[j])) 657 cmdList.add(cmds[j]); 658 } 659 } 660 } 661 662 String[] cmds = new String[cmdList.size()]; 663 cmds = (String[])cmdList.toArray(cmds); 664 665 return cmds; 666 } 667 668 /** 669 * for debugging... 670 * 671 public static void main(String[] argv) throws Exception { 672 MailcapCommandMap map = new MailcapCommandMap(); 673 CommandInfo[] cmdInfo; 674 675 cmdInfo = map.getPreferredCommands(argv[0]); 676 System.out.println("Preferred Commands:"); 677 for (int i = 0; i < cmdInfo.length; i++) 678 System.out.println("Command " + cmdInfo[i].getCommandName() + " [" + 679 cmdInfo[i].getCommandClass() + "]"); 680 cmdInfo = map.getAllCommands(argv[0]); 681 System.out.println(); 682 System.out.println("All Commands:"); 683 for (int i = 0; i < cmdInfo.length; i++) 684 System.out.println("Command " + cmdInfo[i].getCommandName() + " [" + 685 cmdInfo[i].getCommandClass() + "]"); 686 DataContentHandler dch = map.createDataContentHandler(argv[0]); 687 if (dch != null) 688 System.out.println("DataContentHandler " + 689 dch.getClass().toString()); 690 System.exit(0); 691 } 692 */ 693 }