1 /*
   2  * Copyright (c) 1997, 2005, 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 }