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