/* * Copyright (c) 1997, 2005, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.activation.registries; import java.io.*; import java.util.*; public class MailcapFile { /** * A Map indexed by MIME type (string) that references * a Map of commands for each type. The comand Map * is indexed by the command name and references a List of * class names (strings) for each command. */ private Map type_hash = new HashMap(); /** * Another Map like above, but for fallback entries. */ private Map fallback_hash = new HashMap(); /** * A Map indexed by MIME type (string) that references * a List of native commands (string) corresponding to the type. */ private Map native_commands = new HashMap(); private static boolean addReverse = false; static { try { addReverse = Boolean.getBoolean("javax.activation.addreverse"); } catch (Throwable t) { // ignore any errors } } /** * The constructor that takes a filename as an argument. * * @param new_fname The file name of the mailcap file. */ public MailcapFile(String new_fname) throws IOException { if (LogSupport.isLoggable()) LogSupport.log("new MailcapFile: file " + new_fname); FileReader reader = null; try { reader = new FileReader(new_fname); parse(new BufferedReader(reader)); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { } } } } /** * The constructor that takes an input stream as an argument. * * @param is the input stream */ public MailcapFile(InputStream is) throws IOException { if (LogSupport.isLoggable()) LogSupport.log("new MailcapFile: InputStream"); parse(new BufferedReader(new InputStreamReader(is, "iso-8859-1"))); } /** * Mailcap file default constructor. */ public MailcapFile() { if (LogSupport.isLoggable()) LogSupport.log("new MailcapFile: default"); } /** * Get the Map of MailcapEntries based on the MIME type. * * <p> * <strong>Semantics:</strong> First check for the literal mime type, * if that fails looks for wildcard <type>/\* and return that. Return the * list of all that hit. */ public Map getMailcapList(String mime_type) { Map search_result = null; Map wildcard_result = null; // first try the literal search_result = (Map)type_hash.get(mime_type); // ok, now try the wildcard int separator = mime_type.indexOf('/'); String subtype = mime_type.substring(separator + 1); if (!subtype.equals("*")) { String type = mime_type.substring(0, separator + 1) + "*"; wildcard_result = (Map)type_hash.get(type); if (wildcard_result != null) { // damn, we have to merge!!! if (search_result != null) search_result = mergeResults(search_result, wildcard_result); else search_result = wildcard_result; } } return search_result; } /** * Get the Map of fallback MailcapEntries based on the MIME type. * * <p> * <strong>Semantics:</strong> First check for the literal mime type, * if that fails looks for wildcard <type>/\* and return that. Return the * list of all that hit. */ public Map getMailcapFallbackList(String mime_type) { Map search_result = null; Map wildcard_result = null; // first try the literal search_result = (Map)fallback_hash.get(mime_type); // ok, now try the wildcard int separator = mime_type.indexOf('/'); String subtype = mime_type.substring(separator + 1); if (!subtype.equals("*")) { String type = mime_type.substring(0, separator + 1) + "*"; wildcard_result = (Map)fallback_hash.get(type); if (wildcard_result != null) { // damn, we have to merge!!! if (search_result != null) search_result = mergeResults(search_result, wildcard_result); else search_result = wildcard_result; } } return search_result; } /** * Return all the MIME types known to this mailcap file. */ public String[] getMimeTypes() { Set types = new HashSet(type_hash.keySet()); types.addAll(fallback_hash.keySet()); types.addAll(native_commands.keySet()); String[] mts = new String[types.size()]; mts = (String[])types.toArray(mts); return mts; } /** * Return all the native comands for the given MIME type. */ public String[] getNativeCommands(String mime_type) { String[] cmds = null; List v = (List)native_commands.get(mime_type.toLowerCase(Locale.ENGLISH)); if (v != null) { cmds = new String[v.size()]; cmds = (String[])v.toArray(cmds); } return cmds; } /** * Merge the first hash into the second. * This merge will only effect the hashtable that is * returned, we don't want to touch the one passed in since * its integrity must be maintained. */ private Map mergeResults(Map first, Map second) { Iterator verb_enum = second.keySet().iterator(); Map clonedHash = new HashMap(first); // iterate through the verbs in the second map while (verb_enum.hasNext()) { String verb = (String)verb_enum.next(); List cmdVector = (List)clonedHash.get(verb); if (cmdVector == null) { clonedHash.put(verb, second.get(verb)); } else { // merge the two List oldV = (List)second.get(verb); cmdVector = new ArrayList(cmdVector); cmdVector.addAll(oldV); clonedHash.put(verb, cmdVector); } } return clonedHash; } /** * appendToMailcap: Append to this Mailcap DB, use the mailcap * format: * Comment == "# <i>comment string</i> * Entry == "mimetype; javabeanclass<nl> * * Example: * # this is a comment * image/gif jaf.viewers.ImageViewer */ public void appendToMailcap(String mail_cap) { if (LogSupport.isLoggable()) LogSupport.log("appendToMailcap: " + mail_cap); try { parse(new StringReader(mail_cap)); } catch (IOException ex) { // can't happen } } /** * parse file into a hash table of MC Type Entry Obj */ private void parse(Reader reader) throws IOException { BufferedReader buf_reader = new BufferedReader(reader); String line = null; String continued = null; while ((line = buf_reader.readLine()) != null) { // LogSupport.log("parsing line: " + line); line = line.trim(); try { if (line.charAt(0) == '#') continue; if (line.charAt(line.length() - 1) == '\\') { if (continued != null) continued += line.substring(0, line.length() - 1); else continued = line.substring(0, line.length() - 1); } else if (continued != null) { // handle the two strings continued = continued + line; // LogSupport.log("parse: " + continued); try { parseLine(continued); } catch (MailcapParseException e) { //e.printStackTrace(); } continued = null; } else { // LogSupport.log("parse: " + line); try { parseLine(line); // LogSupport.log("hash.size = " + type_hash.size()); } catch (MailcapParseException e) { //e.printStackTrace(); } } } catch (StringIndexOutOfBoundsException e) {} } } /** * A routine to parse individual entries in a Mailcap file. * * Note that this routine does not handle line continuations. * They should have been handled prior to calling this routine. */ protected void parseLine(String mailcapEntry) throws MailcapParseException, IOException { MailcapTokenizer tokenizer = new MailcapTokenizer(mailcapEntry); tokenizer.setIsAutoquoting(false); if (LogSupport.isLoggable()) LogSupport.log("parse: " + mailcapEntry); // parse the primary type int currentToken = tokenizer.nextToken(); if (currentToken != MailcapTokenizer.STRING_TOKEN) { reportParseError(MailcapTokenizer.STRING_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } String primaryType = tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH); String subType = "*"; // parse the '/' between primary and sub // if it's not present that's ok, we just don't have a subtype currentToken = tokenizer.nextToken(); if ((currentToken != MailcapTokenizer.SLASH_TOKEN) && (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) { reportParseError(MailcapTokenizer.SLASH_TOKEN, MailcapTokenizer.SEMICOLON_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } // only need to look for a sub type if we got a '/' if (currentToken == MailcapTokenizer.SLASH_TOKEN) { // parse the sub type currentToken = tokenizer.nextToken(); if (currentToken != MailcapTokenizer.STRING_TOKEN) { reportParseError(MailcapTokenizer.STRING_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } subType = tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH); // get the next token to simplify the next step currentToken = tokenizer.nextToken(); } String mimeType = primaryType + "/" + subType; if (LogSupport.isLoggable()) LogSupport.log(" Type: " + mimeType); // now setup the commands hashtable Map commands = new LinkedHashMap(); // keep commands in order found // parse the ';' that separates the type from the parameters if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) { reportParseError(MailcapTokenizer.SEMICOLON_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } // eat it // parse the required view command tokenizer.setIsAutoquoting(true); currentToken = tokenizer.nextToken(); tokenizer.setIsAutoquoting(false); if ((currentToken != MailcapTokenizer.STRING_TOKEN) && (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) { reportParseError(MailcapTokenizer.STRING_TOKEN, MailcapTokenizer.SEMICOLON_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } if (currentToken == MailcapTokenizer.STRING_TOKEN) { // have a native comand, save the entire mailcap entry //String nativeCommand = tokenizer.getCurrentTokenValue(); List v = (List)native_commands.get(mimeType); if (v == null) { v = new ArrayList(); v.add(mailcapEntry); native_commands.put(mimeType, v); } else { // XXX - check for duplicates? v.add(mailcapEntry); } } // only have to get the next token if the current one isn't a ';' if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) { currentToken = tokenizer.nextToken(); } // look for a ';' which will indicate whether // a parameter list is present or not if (currentToken == MailcapTokenizer.SEMICOLON_TOKEN) { boolean isFallback = false; do { // eat the ';' // parse the parameter name currentToken = tokenizer.nextToken(); if (currentToken != MailcapTokenizer.STRING_TOKEN) { reportParseError(MailcapTokenizer.STRING_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } String paramName = tokenizer.getCurrentTokenValue(). toLowerCase(Locale.ENGLISH); // parse the '=' which separates the name from the value currentToken = tokenizer.nextToken(); if ((currentToken != MailcapTokenizer.EQUALS_TOKEN) && (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) && (currentToken != MailcapTokenizer.EOI_TOKEN)) { reportParseError(MailcapTokenizer.EQUALS_TOKEN, MailcapTokenizer.SEMICOLON_TOKEN, MailcapTokenizer.EOI_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } // we only have a useful command if it is named if (currentToken == MailcapTokenizer.EQUALS_TOKEN) { // eat it // parse the parameter value (which is autoquoted) tokenizer.setIsAutoquoting(true); currentToken = tokenizer.nextToken(); tokenizer.setIsAutoquoting(false); if (currentToken != MailcapTokenizer.STRING_TOKEN) { reportParseError(MailcapTokenizer.STRING_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } String paramValue = tokenizer.getCurrentTokenValue(); // add the class to the list iff it is one we care about if (paramName.startsWith("x-java-")) { String commandName = paramName.substring(7); // 7 == "x-java-".length if (commandName.equals("fallback-entry") && paramValue.equalsIgnoreCase("true")) { isFallback = true; } else { // setup the class entry list if (LogSupport.isLoggable()) LogSupport.log(" Command: " + commandName + ", Class: " + paramValue); List classes = (List)commands.get(commandName); if (classes == null) { classes = new ArrayList(); commands.put(commandName, classes); } if (addReverse) classes.add(0, paramValue); else classes.add(paramValue); } } // set up the next iteration currentToken = tokenizer.nextToken(); } } while (currentToken == MailcapTokenizer.SEMICOLON_TOKEN); Map masterHash = isFallback ? fallback_hash : type_hash; Map curcommands = (Map)masterHash.get(mimeType); if (curcommands == null) { masterHash.put(mimeType, commands); } else { if (LogSupport.isLoggable()) LogSupport.log("Merging commands for type " + mimeType); // have to merge current and new commands // first, merge list of classes for commands already known Iterator cn = curcommands.keySet().iterator(); while (cn.hasNext()) { String cmdName = (String)cn.next(); List ccv = (List)curcommands.get(cmdName); List cv = (List)commands.get(cmdName); if (cv == null) continue; // add everything in cv to ccv, if it's not already there Iterator cvn = cv.iterator(); while (cvn.hasNext()) { String clazz = (String)cvn.next(); if (!ccv.contains(clazz)) if (addReverse) ccv.add(0, clazz); else ccv.add(clazz); } } // now, add commands not previously known cn = commands.keySet().iterator(); while (cn.hasNext()) { String cmdName = (String)cn.next(); if (curcommands.containsKey(cmdName)) continue; List cv = (List)commands.get(cmdName); curcommands.put(cmdName, cv); } } } else if (currentToken != MailcapTokenizer.EOI_TOKEN) { reportParseError(MailcapTokenizer.EOI_TOKEN, MailcapTokenizer.SEMICOLON_TOKEN, currentToken, tokenizer.getCurrentTokenValue()); } } protected static void reportParseError(int expectedToken, int actualToken, String actualTokenValue) throws MailcapParseException { throw new MailcapParseException("Encountered a " + MailcapTokenizer.nameForToken(actualToken) + " token (" + actualTokenValue + ") while expecting a " + MailcapTokenizer.nameForToken(expectedToken) + " token."); } protected static void reportParseError(int expectedToken, int otherExpectedToken, int actualToken, String actualTokenValue) throws MailcapParseException { throw new MailcapParseException("Encountered a " + MailcapTokenizer.nameForToken(actualToken) + " token (" + actualTokenValue + ") while expecting a " + MailcapTokenizer.nameForToken(expectedToken) + " or a " + MailcapTokenizer.nameForToken(otherExpectedToken) + " token."); } protected static void reportParseError(int expectedToken, int otherExpectedToken, int anotherExpectedToken, int actualToken, String actualTokenValue) throws MailcapParseException { if (LogSupport.isLoggable()) LogSupport.log("PARSE ERROR: " + "Encountered a " + MailcapTokenizer.nameForToken(actualToken) + " token (" + actualTokenValue + ") while expecting a " + MailcapTokenizer.nameForToken(expectedToken) + ", a " + MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " + MailcapTokenizer.nameForToken(anotherExpectedToken) + " token."); throw new MailcapParseException("Encountered a " + MailcapTokenizer.nameForToken(actualToken) + " token (" + actualTokenValue + ") while expecting a " + MailcapTokenizer.nameForToken(expectedToken) + ", a " + MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " + MailcapTokenizer.nameForToken(anotherExpectedToken) + " token."); } /** for debugging public static void main(String[] args) throws Exception { Map masterHash = new HashMap(); for (int i = 0; i < args.length; ++i) { System.out.println("Entry " + i + ": " + args[i]); parseLine(args[i], masterHash); } Enumeration types = masterHash.keys(); while (types.hasMoreElements()) { String key = (String)types.nextElement(); System.out.println("MIME Type: " + key); Map commandHash = (Map)masterHash.get(key); Enumeration commands = commandHash.keys(); while (commands.hasMoreElements()) { String command = (String)commands.nextElement(); System.out.println(" Command: " + command); Vector classes = (Vector)commandHash.get(command); for (int i = 0; i < classes.size(); ++i) { System.out.println(" Class: " + (String)classes.elementAt(i)); } } System.out.println(""); } } */ }