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 javax.activation; 27 28 import java.util.Hashtable; 29 import java.util.Enumeration; 30 import java.util.Locale; 31 32 /** 33 * A parameter list of a MimeType 34 * as defined in RFC 2045 and 2046. The Primary type of the 35 * object must already be stripped off. 36 * 37 * @see javax.activation.MimeType 38 * 39 * @since 1.6 40 */ 41 public class MimeTypeParameterList { 42 private Hashtable parameters; 43 44 /** 45 * A string that holds all the special chars. 46 */ 47 private static final String TSPECIALS = "()<>@,;:/[]?=\\\""; 48 49 50 /** 51 * Default constructor. 52 */ 53 public MimeTypeParameterList() { 54 parameters = new Hashtable(); 55 } 56 57 /** 58 * Constructs a new MimeTypeParameterList with the passed in data. 59 * 60 * @param parameterList an RFC 2045, 2046 compliant parameter list. 61 */ 62 public MimeTypeParameterList(String parameterList) 63 throws MimeTypeParseException { 64 parameters = new Hashtable(); 65 66 // now parse rawdata 67 parse(parameterList); 68 } 69 70 /** 71 * A routine for parsing the parameter list out of a String. 72 * 73 * @param parameterList an RFC 2045, 2046 compliant parameter list. 74 */ 75 protected void parse(String parameterList) throws MimeTypeParseException { 76 if (parameterList == null) 77 return; 78 79 int length = parameterList.length(); 80 if (length <= 0) 81 return; 82 83 int i; 84 char c; 85 for (i = skipWhiteSpace(parameterList, 0); 86 i < length && (c = parameterList.charAt(i)) == ';'; 87 i = skipWhiteSpace(parameterList, i)) { 88 int lastIndex; 89 String name; 90 String value; 91 92 // eat the ';' 93 i++; 94 95 // now parse the parameter name 96 97 // skip whitespace 98 i = skipWhiteSpace(parameterList, i); 99 100 // tolerate trailing semicolon, even though it violates the spec 101 if (i >= length) 102 return; 103 104 // find the end of the token char run 105 lastIndex = i; 106 while ((i < length) && isTokenChar(parameterList.charAt(i))) 107 i++; 108 109 name = parameterList.substring(lastIndex, i). 110 toLowerCase(Locale.ENGLISH); 111 112 // now parse the '=' that separates the name from the value 113 i = skipWhiteSpace(parameterList, i); 114 115 if (i >= length || parameterList.charAt(i) != '=') 116 throw new MimeTypeParseException( 117 "Couldn't find the '=' that separates a " + 118 "parameter name from its value."); 119 120 // eat it and parse the parameter value 121 i++; 122 i = skipWhiteSpace(parameterList, i); 123 124 if (i >= length) 125 throw new MimeTypeParseException( 126 "Couldn't find a value for parameter named " + name); 127 128 // now find out whether or not we have a quoted value 129 c = parameterList.charAt(i); 130 if (c == '"') { 131 // yup it's quoted so eat it and capture the quoted string 132 i++; 133 if (i >= length) 134 throw new MimeTypeParseException( 135 "Encountered unterminated quoted parameter value."); 136 137 lastIndex = i; 138 139 // find the next unescaped quote 140 while (i < length) { 141 c = parameterList.charAt(i); 142 if (c == '"') 143 break; 144 if (c == '\\') { 145 // found an escape sequence 146 // so skip this and the 147 // next character 148 i++; 149 } 150 i++; 151 } 152 if (c != '"') 153 throw new MimeTypeParseException( 154 "Encountered unterminated quoted parameter value."); 155 156 value = unquote(parameterList.substring(lastIndex, i)); 157 // eat the quote 158 i++; 159 } else if (isTokenChar(c)) { 160 // nope it's an ordinary token so it 161 // ends with a non-token char 162 lastIndex = i; 163 while (i < length && isTokenChar(parameterList.charAt(i))) 164 i++; 165 value = parameterList.substring(lastIndex, i); 166 } else { 167 // it ain't a value 168 throw new MimeTypeParseException( 169 "Unexpected character encountered at index " + i); 170 } 171 172 // now put the data into the hashtable 173 parameters.put(name, value); 174 } 175 if (i < length) { 176 throw new MimeTypeParseException( 177 "More characters encountered in input than expected."); 178 } 179 } 180 181 /** 182 * Return the number of name-value pairs in this list. 183 * 184 * @return the number of parameters 185 */ 186 public int size() { 187 return parameters.size(); 188 } 189 190 /** 191 * Determine whether or not this list is empty. 192 * 193 * @return true if there are no parameters 194 */ 195 public boolean isEmpty() { 196 return parameters.isEmpty(); 197 } 198 199 /** 200 * Retrieve the value associated with the given name, or null if there 201 * is no current association. 202 * 203 * @param name the parameter name 204 * @return the parameter's value 205 */ 206 public String get(String name) { 207 return (String)parameters.get(name.trim().toLowerCase(Locale.ENGLISH)); 208 } 209 210 /** 211 * Set the value to be associated with the given name, replacing 212 * any previous association. 213 * 214 * @param name the parameter name 215 * @param value the parameter's value 216 */ 217 public void set(String name, String value) { 218 parameters.put(name.trim().toLowerCase(Locale.ENGLISH), value); 219 } 220 221 /** 222 * Remove any value associated with the given name. 223 * 224 * @param name the parameter name 225 */ 226 public void remove(String name) { 227 parameters.remove(name.trim().toLowerCase(Locale.ENGLISH)); 228 } 229 230 /** 231 * Retrieve an enumeration of all the names in this list. 232 * 233 * @return an enumeration of all parameter names 234 */ 235 public Enumeration getNames() { 236 return parameters.keys(); 237 } 238 239 /** 240 * Return a string representation of this object. 241 */ 242 public String toString() { 243 StringBuffer buffer = new StringBuffer(); 244 buffer.ensureCapacity(parameters.size() * 16); 245 // heuristic: 8 characters per field 246 247 Enumeration keys = parameters.keys(); 248 while (keys.hasMoreElements()) { 249 String key = (String)keys.nextElement(); 250 buffer.append("; "); 251 buffer.append(key); 252 buffer.append('='); 253 buffer.append(quote((String)parameters.get(key))); 254 } 255 256 return buffer.toString(); 257 } 258 259 // below here be scary parsing related things 260 261 /** 262 * Determine whether or not a given character belongs to a legal token. 263 */ 264 private static boolean isTokenChar(char c) { 265 return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0); 266 } 267 268 /** 269 * return the index of the first non white space character in 270 * rawdata at or after index i. 271 */ 272 private static int skipWhiteSpace(String rawdata, int i) { 273 int length = rawdata.length(); 274 while ((i < length) && Character.isWhitespace(rawdata.charAt(i))) 275 i++; 276 return i; 277 } 278 279 /** 280 * A routine that knows how and when to quote and escape the given value. 281 */ 282 private static String quote(String value) { 283 boolean needsQuotes = false; 284 285 // check to see if we actually have to quote this thing 286 int length = value.length(); 287 for (int i = 0; (i < length) && !needsQuotes; i++) { 288 needsQuotes = !isTokenChar(value.charAt(i)); 289 } 290 291 if (needsQuotes) { 292 StringBuffer buffer = new StringBuffer(); 293 buffer.ensureCapacity((int)(length * 1.5)); 294 295 // add the initial quote 296 buffer.append('"'); 297 298 // add the properly escaped text 299 for (int i = 0; i < length; ++i) { 300 char c = value.charAt(i); 301 if ((c == '\\') || (c == '"')) 302 buffer.append('\\'); 303 buffer.append(c); 304 } 305 306 // add the closing quote 307 buffer.append('"'); 308 309 return buffer.toString(); 310 } else { 311 return value; 312 } 313 } 314 315 /** 316 * A routine that knows how to strip the quotes and 317 * escape sequences from the given value. 318 */ 319 private static String unquote(String value) { 320 int valueLength = value.length(); 321 StringBuffer buffer = new StringBuffer(); 322 buffer.ensureCapacity(valueLength); 323 324 boolean escaped = false; 325 for (int i = 0; i < valueLength; ++i) { 326 char currentChar = value.charAt(i); 327 if (!escaped && (currentChar != '\\')) { 328 buffer.append(currentChar); 329 } else if (escaped) { 330 buffer.append(currentChar); 331 escaped = false; 332 } else { 333 escaped = true; 334 } 335 } 336 337 return buffer.toString(); 338 } 339 }