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 package com.sun.activation.registries; 27 28 import java.io.*; 29 import java.util.*; 30 31 public class MailcapFile { 32 33 /** 34 * A Map indexed by MIME type (string) that references 35 * a Map of commands for each type. The comand Map 36 * is indexed by the command name and references a List of 37 * class names (strings) for each command. 38 */ 39 private Map type_hash = new HashMap(); 40 41 /** 42 * Another Map like above, but for fallback entries. 43 */ 44 private Map fallback_hash = new HashMap(); 45 46 /** 47 * A Map indexed by MIME type (string) that references 48 * a List of native commands (string) corresponding to the type. 49 */ 50 private Map native_commands = new HashMap(); 51 52 private static boolean addReverse = false; 53 54 static { 55 try { 56 addReverse = Boolean.getBoolean("javax.activation.addreverse"); 57 } catch (Throwable t) { 58 // ignore any errors 59 } 60 } 61 62 /** 63 * The constructor that takes a filename as an argument. 64 * 65 * @param new_fname The file name of the mailcap file. 66 */ 67 public MailcapFile(String new_fname) throws IOException { 68 if (LogSupport.isLoggable()) 69 LogSupport.log("new MailcapFile: file " + new_fname); 70 FileReader reader = null; 71 try { 72 reader = new FileReader(new_fname); 73 parse(new BufferedReader(reader)); 74 } finally { 75 if (reader != null) { 76 try { 77 reader.close(); 78 } catch (IOException ex) { } 79 } 80 } 81 } 82 83 /** 84 * The constructor that takes an input stream as an argument. 85 * 86 * @param is the input stream 87 */ 88 public MailcapFile(InputStream is) throws IOException { 89 if (LogSupport.isLoggable()) 90 LogSupport.log("new MailcapFile: InputStream"); 91 parse(new BufferedReader(new InputStreamReader(is, "iso-8859-1"))); 92 } 93 94 /** 95 * Mailcap file default constructor. 96 */ 97 public MailcapFile() { 98 if (LogSupport.isLoggable()) 99 LogSupport.log("new MailcapFile: default"); 100 } 101 102 /** 103 * Get the Map of MailcapEntries based on the MIME type. 104 * 105 * <p> 106 * <strong>Semantics:</strong> First check for the literal mime type, 107 * if that fails looks for wildcard <type>/\* and return that. Return the 108 * list of all that hit. 109 */ 110 public Map getMailcapList(String mime_type) { 111 Map search_result = null; 112 Map wildcard_result = null; 113 114 // first try the literal 115 search_result = (Map)type_hash.get(mime_type); 116 117 // ok, now try the wildcard 118 int separator = mime_type.indexOf('/'); 119 String subtype = mime_type.substring(separator + 1); 120 if (!subtype.equals("*")) { 121 String type = mime_type.substring(0, separator + 1) + "*"; 122 wildcard_result = (Map)type_hash.get(type); 123 124 if (wildcard_result != null) { // damn, we have to merge!!! 125 if (search_result != null) 126 search_result = 127 mergeResults(search_result, wildcard_result); 128 else 129 search_result = wildcard_result; 130 } 131 } 132 return search_result; 133 } 134 135 /** 136 * Get the Map of fallback MailcapEntries based on the MIME type. 137 * 138 * <p> 139 * <strong>Semantics:</strong> First check for the literal mime type, 140 * if that fails looks for wildcard <type>/\* and return that. Return the 141 * list of all that hit. 142 */ 143 public Map getMailcapFallbackList(String mime_type) { 144 Map search_result = null; 145 Map wildcard_result = null; 146 147 // first try the literal 148 search_result = (Map)fallback_hash.get(mime_type); 149 150 // ok, now try the wildcard 151 int separator = mime_type.indexOf('/'); 152 String subtype = mime_type.substring(separator + 1); 153 if (!subtype.equals("*")) { 154 String type = mime_type.substring(0, separator + 1) + "*"; 155 wildcard_result = (Map)fallback_hash.get(type); 156 157 if (wildcard_result != null) { // damn, we have to merge!!! 158 if (search_result != null) 159 search_result = 160 mergeResults(search_result, wildcard_result); 161 else 162 search_result = wildcard_result; 163 } 164 } 165 return search_result; 166 } 167 168 /** 169 * Return all the MIME types known to this mailcap file. 170 */ 171 public String[] getMimeTypes() { 172 Set types = new HashSet(type_hash.keySet()); 173 types.addAll(fallback_hash.keySet()); 174 types.addAll(native_commands.keySet()); 175 String[] mts = new String[types.size()]; 176 mts = (String[])types.toArray(mts); 177 return mts; 178 } 179 180 /** 181 * Return all the native comands for the given MIME type. 182 */ 183 public String[] getNativeCommands(String mime_type) { 184 String[] cmds = null; 185 List v = 186 (List)native_commands.get(mime_type.toLowerCase(Locale.ENGLISH)); 187 if (v != null) { 188 cmds = new String[v.size()]; 189 cmds = (String[])v.toArray(cmds); 190 } 191 return cmds; 192 } 193 194 /** 195 * Merge the first hash into the second. 196 * This merge will only effect the hashtable that is 197 * returned, we don't want to touch the one passed in since 198 * its integrity must be maintained. 199 */ 200 private Map mergeResults(Map first, Map second) { 201 Iterator verb_enum = second.keySet().iterator(); 202 Map clonedHash = new HashMap(first); 203 204 // iterate through the verbs in the second map 205 while (verb_enum.hasNext()) { 206 String verb = (String)verb_enum.next(); 207 List cmdVector = (List)clonedHash.get(verb); 208 if (cmdVector == null) { 209 clonedHash.put(verb, second.get(verb)); 210 } else { 211 // merge the two 212 List oldV = (List)second.get(verb); 213 cmdVector = new ArrayList(cmdVector); 214 cmdVector.addAll(oldV); 215 clonedHash.put(verb, cmdVector); 216 } 217 } 218 return clonedHash; 219 } 220 221 /** 222 * appendToMailcap: Append to this Mailcap DB, use the mailcap 223 * format: 224 * Comment == "# <i>comment string</i> 225 * Entry == "mimetype; javabeanclass<nl> 226 * 227 * Example: 228 * # this is a comment 229 * image/gif jaf.viewers.ImageViewer 230 */ 231 public void appendToMailcap(String mail_cap) { 232 if (LogSupport.isLoggable()) 233 LogSupport.log("appendToMailcap: " + mail_cap); 234 try { 235 parse(new StringReader(mail_cap)); 236 } catch (IOException ex) { 237 // can't happen 238 } 239 } 240 241 /** 242 * parse file into a hash table of MC Type Entry Obj 243 */ 244 private void parse(Reader reader) throws IOException { 245 BufferedReader buf_reader = new BufferedReader(reader); 246 String line = null; 247 String continued = null; 248 249 while ((line = buf_reader.readLine()) != null) { 250 // LogSupport.log("parsing line: " + line); 251 252 line = line.trim(); 253 254 try { 255 if (line.charAt(0) == '#') 256 continue; 257 if (line.charAt(line.length() - 1) == '\\') { 258 if (continued != null) 259 continued += line.substring(0, line.length() - 1); 260 else 261 continued = line.substring(0, line.length() - 1); 262 } else if (continued != null) { 263 // handle the two strings 264 continued = continued + line; 265 // LogSupport.log("parse: " + continued); 266 try { 267 parseLine(continued); 268 } catch (MailcapParseException e) { 269 //e.printStackTrace(); 270 } 271 continued = null; 272 } 273 else { 274 // LogSupport.log("parse: " + line); 275 try { 276 parseLine(line); 277 // LogSupport.log("hash.size = " + type_hash.size()); 278 } catch (MailcapParseException e) { 279 //e.printStackTrace(); 280 } 281 } 282 } catch (StringIndexOutOfBoundsException e) {} 283 } 284 } 285 286 /** 287 * A routine to parse individual entries in a Mailcap file. 288 * 289 * Note that this routine does not handle line continuations. 290 * They should have been handled prior to calling this routine. 291 */ 292 protected void parseLine(String mailcapEntry) 293 throws MailcapParseException, IOException { 294 MailcapTokenizer tokenizer = new MailcapTokenizer(mailcapEntry); 295 tokenizer.setIsAutoquoting(false); 296 297 if (LogSupport.isLoggable()) 298 LogSupport.log("parse: " + mailcapEntry); 299 // parse the primary type 300 int currentToken = tokenizer.nextToken(); 301 if (currentToken != MailcapTokenizer.STRING_TOKEN) { 302 reportParseError(MailcapTokenizer.STRING_TOKEN, currentToken, 303 tokenizer.getCurrentTokenValue()); 304 } 305 String primaryType = 306 tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH); 307 String subType = "*"; 308 309 // parse the '/' between primary and sub 310 // if it's not present that's ok, we just don't have a subtype 311 currentToken = tokenizer.nextToken(); 312 if ((currentToken != MailcapTokenizer.SLASH_TOKEN) && 313 (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) { 314 reportParseError(MailcapTokenizer.SLASH_TOKEN, 315 MailcapTokenizer.SEMICOLON_TOKEN, currentToken, 316 tokenizer.getCurrentTokenValue()); 317 } 318 319 // only need to look for a sub type if we got a '/' 320 if (currentToken == MailcapTokenizer.SLASH_TOKEN) { 321 // parse the sub type 322 currentToken = tokenizer.nextToken(); 323 if (currentToken != MailcapTokenizer.STRING_TOKEN) { 324 reportParseError(MailcapTokenizer.STRING_TOKEN, 325 currentToken, tokenizer.getCurrentTokenValue()); 326 } 327 subType = 328 tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH); 329 330 // get the next token to simplify the next step 331 currentToken = tokenizer.nextToken(); 332 } 333 334 String mimeType = primaryType + "/" + subType; 335 336 if (LogSupport.isLoggable()) 337 LogSupport.log(" Type: " + mimeType); 338 339 // now setup the commands hashtable 340 Map commands = new LinkedHashMap(); // keep commands in order found 341 342 // parse the ';' that separates the type from the parameters 343 if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) { 344 reportParseError(MailcapTokenizer.SEMICOLON_TOKEN, 345 currentToken, tokenizer.getCurrentTokenValue()); 346 } 347 // eat it 348 349 // parse the required view command 350 tokenizer.setIsAutoquoting(true); 351 currentToken = tokenizer.nextToken(); 352 tokenizer.setIsAutoquoting(false); 353 if ((currentToken != MailcapTokenizer.STRING_TOKEN) && 354 (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) { 355 reportParseError(MailcapTokenizer.STRING_TOKEN, 356 MailcapTokenizer.SEMICOLON_TOKEN, currentToken, 357 tokenizer.getCurrentTokenValue()); 358 } 359 360 if (currentToken == MailcapTokenizer.STRING_TOKEN) { 361 // have a native comand, save the entire mailcap entry 362 //String nativeCommand = tokenizer.getCurrentTokenValue(); 363 List v = (List)native_commands.get(mimeType); 364 if (v == null) { 365 v = new ArrayList(); 366 v.add(mailcapEntry); 367 native_commands.put(mimeType, v); 368 } else { 369 // XXX - check for duplicates? 370 v.add(mailcapEntry); 371 } 372 } 373 374 // only have to get the next token if the current one isn't a ';' 375 if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) { 376 currentToken = tokenizer.nextToken(); 377 } 378 379 // look for a ';' which will indicate whether 380 // a parameter list is present or not 381 if (currentToken == MailcapTokenizer.SEMICOLON_TOKEN) { 382 boolean isFallback = false; 383 do { 384 // eat the ';' 385 386 // parse the parameter name 387 currentToken = tokenizer.nextToken(); 388 if (currentToken != MailcapTokenizer.STRING_TOKEN) { 389 reportParseError(MailcapTokenizer.STRING_TOKEN, 390 currentToken, tokenizer.getCurrentTokenValue()); 391 } 392 String paramName = tokenizer.getCurrentTokenValue(). 393 toLowerCase(Locale.ENGLISH); 394 395 // parse the '=' which separates the name from the value 396 currentToken = tokenizer.nextToken(); 397 if ((currentToken != MailcapTokenizer.EQUALS_TOKEN) && 398 (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) && 399 (currentToken != MailcapTokenizer.EOI_TOKEN)) { 400 reportParseError(MailcapTokenizer.EQUALS_TOKEN, 401 MailcapTokenizer.SEMICOLON_TOKEN, 402 MailcapTokenizer.EOI_TOKEN, 403 currentToken, tokenizer.getCurrentTokenValue()); 404 } 405 406 // we only have a useful command if it is named 407 if (currentToken == MailcapTokenizer.EQUALS_TOKEN) { 408 // eat it 409 410 // parse the parameter value (which is autoquoted) 411 tokenizer.setIsAutoquoting(true); 412 currentToken = tokenizer.nextToken(); 413 tokenizer.setIsAutoquoting(false); 414 if (currentToken != MailcapTokenizer.STRING_TOKEN) { 415 reportParseError(MailcapTokenizer.STRING_TOKEN, 416 currentToken, tokenizer.getCurrentTokenValue()); 417 } 418 String paramValue = 419 tokenizer.getCurrentTokenValue(); 420 421 // add the class to the list iff it is one we care about 422 if (paramName.startsWith("x-java-")) { 423 String commandName = paramName.substring(7); 424 // 7 == "x-java-".length 425 426 if (commandName.equals("fallback-entry") && 427 paramValue.equalsIgnoreCase("true")) { 428 isFallback = true; 429 } else { 430 431 // setup the class entry list 432 if (LogSupport.isLoggable()) 433 LogSupport.log(" Command: " + commandName + 434 ", Class: " + paramValue); 435 List classes = (List)commands.get(commandName); 436 if (classes == null) { 437 classes = new ArrayList(); 438 commands.put(commandName, classes); 439 } 440 if (addReverse) 441 classes.add(0, paramValue); 442 else 443 classes.add(paramValue); 444 } 445 } 446 447 // set up the next iteration 448 currentToken = tokenizer.nextToken(); 449 } 450 } while (currentToken == MailcapTokenizer.SEMICOLON_TOKEN); 451 452 Map masterHash = isFallback ? fallback_hash : type_hash; 453 Map curcommands = 454 (Map)masterHash.get(mimeType); 455 if (curcommands == null) { 456 masterHash.put(mimeType, commands); 457 } else { 458 if (LogSupport.isLoggable()) 459 LogSupport.log("Merging commands for type " + mimeType); 460 // have to merge current and new commands 461 // first, merge list of classes for commands already known 462 Iterator cn = curcommands.keySet().iterator(); 463 while (cn.hasNext()) { 464 String cmdName = (String)cn.next(); 465 List ccv = (List)curcommands.get(cmdName); 466 List cv = (List)commands.get(cmdName); 467 if (cv == null) 468 continue; 469 // add everything in cv to ccv, if it's not already there 470 Iterator cvn = cv.iterator(); 471 while (cvn.hasNext()) { 472 String clazz = (String)cvn.next(); 473 if (!ccv.contains(clazz)) 474 if (addReverse) 475 ccv.add(0, clazz); 476 else 477 ccv.add(clazz); 478 } 479 } 480 // now, add commands not previously known 481 cn = commands.keySet().iterator(); 482 while (cn.hasNext()) { 483 String cmdName = (String)cn.next(); 484 if (curcommands.containsKey(cmdName)) 485 continue; 486 List cv = (List)commands.get(cmdName); 487 curcommands.put(cmdName, cv); 488 } 489 } 490 } else if (currentToken != MailcapTokenizer.EOI_TOKEN) { 491 reportParseError(MailcapTokenizer.EOI_TOKEN, 492 MailcapTokenizer.SEMICOLON_TOKEN, 493 currentToken, tokenizer.getCurrentTokenValue()); 494 } 495 } 496 497 protected static void reportParseError(int expectedToken, int actualToken, 498 String actualTokenValue) throws MailcapParseException { 499 throw new MailcapParseException("Encountered a " + 500 MailcapTokenizer.nameForToken(actualToken) + " token (" + 501 actualTokenValue + ") while expecting a " + 502 MailcapTokenizer.nameForToken(expectedToken) + " token."); 503 } 504 505 protected static void reportParseError(int expectedToken, 506 int otherExpectedToken, int actualToken, String actualTokenValue) 507 throws MailcapParseException { 508 throw new MailcapParseException("Encountered a " + 509 MailcapTokenizer.nameForToken(actualToken) + " token (" + 510 actualTokenValue + ") while expecting a " + 511 MailcapTokenizer.nameForToken(expectedToken) + " or a " + 512 MailcapTokenizer.nameForToken(otherExpectedToken) + " token."); 513 } 514 515 protected static void reportParseError(int expectedToken, 516 int otherExpectedToken, int anotherExpectedToken, int actualToken, 517 String actualTokenValue) throws MailcapParseException { 518 if (LogSupport.isLoggable()) 519 LogSupport.log("PARSE ERROR: " + "Encountered a " + 520 MailcapTokenizer.nameForToken(actualToken) + " token (" + 521 actualTokenValue + ") while expecting a " + 522 MailcapTokenizer.nameForToken(expectedToken) + ", a " + 523 MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " + 524 MailcapTokenizer.nameForToken(anotherExpectedToken) + " token."); 525 throw new MailcapParseException("Encountered a " + 526 MailcapTokenizer.nameForToken(actualToken) + " token (" + 527 actualTokenValue + ") while expecting a " + 528 MailcapTokenizer.nameForToken(expectedToken) + ", a " + 529 MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " + 530 MailcapTokenizer.nameForToken(anotherExpectedToken) + " token."); 531 } 532 533 /** for debugging 534 public static void main(String[] args) throws Exception { 535 Map masterHash = new HashMap(); 536 for (int i = 0; i < args.length; ++i) { 537 System.out.println("Entry " + i + ": " + args[i]); 538 parseLine(args[i], masterHash); 539 } 540 541 Enumeration types = masterHash.keys(); 542 while (types.hasMoreElements()) { 543 String key = (String)types.nextElement(); 544 System.out.println("MIME Type: " + key); 545 546 Map commandHash = (Map)masterHash.get(key); 547 Enumeration commands = commandHash.keys(); 548 while (commands.hasMoreElements()) { 549 String command = (String)commands.nextElement(); 550 System.out.println(" Command: " + command); 551 552 Vector classes = (Vector)commandHash.get(command); 553 for (int i = 0; i < classes.size(); ++i) { 554 System.out.println(" Class: " + 555 (String)classes.elementAt(i)); 556 } 557 } 558 559 System.out.println(""); 560 } 561 } 562 */ 563 }