1 /* 2 * Copyright (c) 1997, 2013, 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 java.awt.datatransfer; 27 28 import java.util.Enumeration; 29 import java.util.Hashtable; 30 import java.util.Iterator; 31 import java.util.Map; 32 import java.util.Set; 33 34 35 /** 36 * An object that encapsulates the parameter list of a MimeType 37 * as defined in RFC 2045 and 2046. 38 * 39 * @author jeff.dunn@eng.sun.com 40 */ 41 class MimeTypeParameterList implements Cloneable { 42 43 /** 44 * Default constructor. 45 */ 46 public MimeTypeParameterList() { 47 parameters = new Hashtable<>(); 48 } 49 50 public MimeTypeParameterList(String rawdata) 51 throws MimeTypeParseException 52 { 53 parameters = new Hashtable<>(); 54 55 // now parse rawdata 56 parse(rawdata); 57 } 58 59 public int hashCode() { 60 int code = Integer.MAX_VALUE/45; // "random" value for empty lists 61 String paramName = null; 62 Enumeration<String> enum_ = this.getNames(); 63 64 while (enum_.hasMoreElements()) { 65 paramName = enum_.nextElement(); 66 code += paramName.hashCode(); 67 code += this.get(paramName).hashCode(); 68 } 69 70 return code; 71 } // hashCode() 72 73 /** 74 * Two parameter lists are considered equal if they have exactly 75 * the same set of parameter names and associated values. The 76 * order of the parameters is not considered. 77 */ 78 public boolean equals(Object thatObject) { 79 //System.out.println("MimeTypeParameterList.equals("+this+","+thatObject+")"); 80 if (!(thatObject instanceof MimeTypeParameterList)) { 81 return false; 82 } 83 MimeTypeParameterList that = (MimeTypeParameterList)thatObject; 84 if (this.size() != that.size()) { 85 return false; 86 } 87 String name = null; 88 String thisValue = null; 89 String thatValue = null; 90 Set<Map.Entry<String, String>> entries = parameters.entrySet(); 91 Iterator<Map.Entry<String, String>> iterator = entries.iterator(); 92 Map.Entry<String, String> entry = null; 93 while (iterator.hasNext()) { 94 entry = iterator.next(); 95 name = entry.getKey(); 96 thisValue = entry.getValue(); 97 thatValue = that.parameters.get(name); 98 if ((thisValue == null) || (thatValue == null)) { 99 // both null -> equal, only one null -> not equal 100 if (thisValue != thatValue) { 101 return false; 102 } 103 } else if (!thisValue.equals(thatValue)) { 104 return false; 105 } 106 } // while iterator 107 108 return true; 109 } // equals() 110 111 /** 112 * A routine for parsing the parameter list out of a String. 113 */ 114 protected void parse(String rawdata) throws MimeTypeParseException { 115 int length = rawdata.length(); 116 if(length > 0) { 117 int currentIndex = skipWhiteSpace(rawdata, 0); 118 int lastIndex = 0; 119 120 if(currentIndex < length) { 121 char currentChar = rawdata.charAt(currentIndex); 122 while ((currentIndex < length) && (currentChar == ';')) { 123 String name; 124 String value; 125 boolean foundit; 126 127 // eat the ';' 128 ++currentIndex; 129 130 // now parse the parameter name 131 132 // skip whitespace 133 currentIndex = skipWhiteSpace(rawdata, currentIndex); 134 135 if(currentIndex < length) { 136 // find the end of the token char run 137 lastIndex = currentIndex; 138 currentChar = rawdata.charAt(currentIndex); 139 while((currentIndex < length) && isTokenChar(currentChar)) { 140 ++currentIndex; 141 currentChar = rawdata.charAt(currentIndex); 142 } 143 name = rawdata.substring(lastIndex, currentIndex).toLowerCase(); 144 145 // now parse the '=' that separates the name from the value 146 147 // skip whitespace 148 currentIndex = skipWhiteSpace(rawdata, currentIndex); 149 150 if((currentIndex < length) && (rawdata.charAt(currentIndex) == '=')) { 151 // eat it and parse the parameter value 152 ++currentIndex; 153 154 // skip whitespace 155 currentIndex = skipWhiteSpace(rawdata, currentIndex); 156 157 if(currentIndex < length) { 158 // now find out whether or not we have a quoted value 159 currentChar = rawdata.charAt(currentIndex); 160 if(currentChar == '"') { 161 // yup it's quoted so eat it and capture the quoted string 162 ++currentIndex; 163 lastIndex = currentIndex; 164 165 if(currentIndex < length) { 166 // find the next unescaped quote 167 foundit = false; 168 while((currentIndex < length) && !foundit) { 169 currentChar = rawdata.charAt(currentIndex); 170 if(currentChar == '\\') { 171 // found an escape sequence so pass this and the next character 172 currentIndex += 2; 173 } else if(currentChar == '"') { 174 // found it! 175 foundit = true; 176 } else { 177 ++currentIndex; 178 } 179 } 180 if(currentChar == '"') { 181 value = unquote(rawdata.substring(lastIndex, currentIndex)); 182 // eat the quote 183 ++currentIndex; 184 } else { 185 throw new MimeTypeParseException("Encountered unterminated quoted parameter value."); 186 } 187 } else { 188 throw new MimeTypeParseException("Encountered unterminated quoted parameter value."); 189 } 190 } else if(isTokenChar(currentChar)) { 191 // nope it's an ordinary token so it ends with a non-token char 192 lastIndex = currentIndex; 193 foundit = false; 194 while((currentIndex < length) && !foundit) { 195 currentChar = rawdata.charAt(currentIndex); 196 197 if(isTokenChar(currentChar)) { 198 ++currentIndex; 199 } else { 200 foundit = true; 201 } 202 } 203 value = rawdata.substring(lastIndex, currentIndex); 204 } else { 205 // it ain't a value 206 throw new MimeTypeParseException("Unexpected character encountered at index " + currentIndex); 207 } 208 209 // now put the data into the hashtable 210 parameters.put(name, value); 211 } else { 212 throw new MimeTypeParseException("Couldn't find a value for parameter named " + name); 213 } 214 } else { 215 throw new MimeTypeParseException("Couldn't find the '=' that separates a parameter name from its value."); 216 } 217 } else { 218 throw new MimeTypeParseException("Couldn't find parameter name"); 219 } 220 221 // setup the next iteration 222 currentIndex = skipWhiteSpace(rawdata, currentIndex); 223 if(currentIndex < length) { 224 currentChar = rawdata.charAt(currentIndex); 225 } 226 } 227 if(currentIndex < length) { 228 throw new MimeTypeParseException("More characters encountered in input than expected."); 229 } 230 } 231 } 232 } 233 234 /** 235 * return the number of name-value pairs in this list. 236 */ 237 public int size() { 238 return parameters.size(); 239 } 240 241 /** 242 * Determine whether or not this list is empty. 243 */ 244 public boolean isEmpty() { 245 return parameters.isEmpty(); 246 } 247 248 /** 249 * Retrieve the value associated with the given name, or null if there 250 * is no current association. 251 */ 252 public String get(String name) { 253 return parameters.get(name.trim().toLowerCase()); 254 } 255 256 /** 257 * Set the value to be associated with the given name, replacing 258 * any previous association. 259 */ 260 public void set(String name, String value) { 261 parameters.put(name.trim().toLowerCase(), value); 262 } 263 264 /** 265 * Remove any value associated with the given name. 266 */ 267 public void remove(String name) { 268 parameters.remove(name.trim().toLowerCase()); 269 } 270 271 /** 272 * Retrieve an enumeration of all the names in this list. 273 */ 274 public Enumeration<String> getNames() { 275 return parameters.keys(); 276 } 277 278 public String toString() { 279 // Heuristic: 8 characters per field 280 StringBuilder buffer = new StringBuilder(parameters.size() * 16); 281 282 Enumeration<String> keys = parameters.keys(); 283 while(keys.hasMoreElements()) 284 { 285 buffer.append("; "); 286 287 String key = keys.nextElement(); 288 buffer.append(key); 289 buffer.append('='); 290 buffer.append(quote(parameters.get(key))); 291 } 292 293 return buffer.toString(); 294 } 295 296 /** 297 * @return a clone of this object 298 */ 299 @SuppressWarnings("unchecked") // Cast from clone 300 public Object clone() { 301 MimeTypeParameterList newObj = null; 302 try { 303 newObj = (MimeTypeParameterList)super.clone(); 304 } catch (CloneNotSupportedException cannotHappen) { 305 } 306 newObj.parameters = (Hashtable<String, String>)parameters.clone(); 307 return newObj; 308 } 309 310 private Hashtable<String, String> parameters; 311 312 // below here be scary parsing related things 313 314 /** 315 * Determine whether or not a given character belongs to a legal token. 316 */ 317 private static boolean isTokenChar(char c) { 318 return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0); 319 } 320 321 /** 322 * return the index of the first non white space character in 323 * rawdata at or after index i. 324 */ 325 private static int skipWhiteSpace(String rawdata, int i) { 326 int length = rawdata.length(); 327 if (i < length) { 328 char c = rawdata.charAt(i); 329 while ((i < length) && Character.isWhitespace(c)) { 330 ++i; 331 c = rawdata.charAt(i); 332 } 333 } 334 335 return i; 336 } 337 338 /** 339 * A routine that knows how and when to quote and escape the given value. 340 */ 341 private static String quote(String value) { 342 boolean needsQuotes = false; 343 344 // check to see if we actually have to quote this thing 345 int length = value.length(); 346 for(int i = 0; (i < length) && !needsQuotes; ++i) { 347 needsQuotes = !isTokenChar(value.charAt(i)); 348 } 349 350 if(needsQuotes) { 351 StringBuilder buffer = new StringBuilder((int)(length * 1.5)); 352 353 // add the initial quote 354 buffer.append('"'); 355 356 // add the properly escaped text 357 for(int i = 0; i < length; ++i) { 358 char c = value.charAt(i); 359 if((c == '\\') || (c == '"')) { 360 buffer.append('\\'); 361 } 362 buffer.append(c); 363 } 364 365 // add the closing quote 366 buffer.append('"'); 367 368 return buffer.toString(); 369 } 370 else 371 { 372 return value; 373 } 374 } 375 376 /** 377 * A routine that knows how to strip the quotes and escape sequences from the given value. 378 */ 379 private static String unquote(String value) { 380 int valueLength = value.length(); 381 StringBuilder buffer = new StringBuilder(valueLength); 382 383 boolean escaped = false; 384 for(int i = 0; i < valueLength; ++i) { 385 char currentChar = value.charAt(i); 386 if(!escaped && (currentChar != '\\')) { 387 buffer.append(currentChar); 388 } else if(escaped) { 389 buffer.append(currentChar); 390 escaped = false; 391 } else { 392 escaped = true; 393 } 394 } 395 396 return buffer.toString(); 397 } 398 399 /** 400 * A string that holds all the special chars. 401 */ 402 private static final String TSPECIALS = "()<>@,;:\\\"/[]?="; 403 404 }