View Javadoc
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 }