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 unescqped 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                                                 //    foundit!
 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 
 300      public Object clone() {
 301          MimeTypeParameterList newObj = null;
 302          try {
 303              newObj = (MimeTypeParameterList)super.clone();
 304          } catch (CloneNotSupportedException cannotHappen) {
 305          }
 306          newObj.parameters = (Hashtable)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 }