1 /*
2  * Copyright (c) 1997, 2017, 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.io.Externalizable;
29 import java.io.IOException;
30 import java.io.ObjectInput;
31 import java.io.ObjectOutput;
32 import java.util.Locale;
33
34 /**
35  * A Multipurpose Internet Mail Extension (MIME) type, as defined in RFC 2045
36  * and 2046.
37  * <p>
38  * THIS IS *NOT* - REPEAT *NOT* - A PUBLIC CLASS! DataFlavor IS THE PUBLIC
39  * INTERFACE, AND THIS IS PROVIDED AS A ***PRIVATE*** (THAT IS AS IN *NOT*
40  * PUBLIC) HELPER CLASS!
41  */

42 class MimeType implements Externalizable, Cloneable {
43
44     /*
45      * serialization support
46      */

47
48     static final long serialVersionUID = -6568722458793895906L;
49
50     /**
51      * Constructor for externalization; this constructor should not be called
52      * directly by an application, since the result will be an uninitialized,
53      * immutable {@code MimeType} object.
54      */

55     public MimeType() {
56     }
57
58     /**
59      * Builds a {@code MimeType} from a {@code String}.
60      *
61      * @param  rawdata text used to initialize the {@code MimeType}
62      * @throws NullPointerException if {@code rawdata} is {@code null}
63      */

64     public MimeType(String rawdata) throws MimeTypeParseException {
65         parse(rawdata);
66     }
67
68     /**
69      * Builds a {@code MimeType} with the given primary and sub type but has an
70      * empty parameter list.
71      *
72      * @param  primary the primary type of this {@code MimeType}
73      * @param  sub the subtype of this {@code MimeType}
74      * @throws NullPointerException if either {@code primary} or {@code sub} is
75      *         {@code null}
76      */

77     public MimeType(String primary, String sub) throws MimeTypeParseException {
78         this(primary, sub, new MimeTypeParameterList());
79     }
80
81     /**
82      * Builds a {@code MimeType} with a pre-defined and valid (or empty)
83      * parameter list.
84      *
85      * @param  primary the primary type of this {@code MimeType}
86      * @param  sub the subtype of this {@code MimeType}
87      * @param  mtpl the requested parameter list
88      * @throws NullPointerException if either {@code primary}, {@code sub} or
89      *         {@code mtpl} is {@code null}
90      */

91     public MimeType(String primary, String sub, MimeTypeParameterList mtpl) throws
92 MimeTypeParseException {
93         //    check to see if primary is valid
94         if(isValidToken(primary)) {
95             primaryType = primary.toLowerCase(Locale.ENGLISH);
96         } else {
97             throw new MimeTypeParseException("Primary type is invalid.");
98         }
99
100         //    check to see if sub is valid
101         if(isValidToken(sub)) {
102             subType = sub.toLowerCase(Locale.ENGLISH);
103         } else {
104             throw new MimeTypeParseException("Sub type is invalid.");
105         }
106
107         parameters = (MimeTypeParameterList)mtpl.clone();
108     }
109
110     public int hashCode() {
111
112         // We sum up the hash codes for all of the strings. This
113         // way, the order of the strings is irrelevant
114         int code = 0;
115         code += primaryType.hashCode();
116         code += subType.hashCode();
117         code += parameters.hashCode();
118         return code;
119     } // hashCode()
120
121     /**
122      * {@code MimeType}s are equal if their primary types, subtypes, and
123      * parameters are all equal. No default values are taken into account.
124      *
125      * @param  thatObject the object to be evaluated as a {@code MimeType}
126      * @return {@code trueif {@code thatObject} is a {@code MimeType};
127      *         otherwise returns {@code false}
128      */

129     public boolean equals(Object thatObject) {
130         if (!(thatObject instanceof MimeType)) {
131             return false;
132         }
133         MimeType that = (MimeType)thatObject;
134         boolean isIt =
135             ((this.primaryType.equals(that.primaryType)) &&
136              (this.subType.equals(that.subType)) &&
137              (this.parameters.equals(that.parameters)));
138         return isIt;
139     } // equals()
140
141     /**
142      * A routine for parsing the MIME type out of a String.
143      *
144      * @throws NullPointerException if {@code rawdata} is {@code null}
145      */

146     private void parse(String rawdata) throws MimeTypeParseException {
147         int slashIndex = rawdata.indexOf('/');
148         int semIndex = rawdata.indexOf(';');
149         if((slashIndex < 0) && (semIndex < 0)) {
150             //    neither character is present, so treat it
151             //    as an error
152             throw new MimeTypeParseException("Unable to find a sub type.");
153         } else if((slashIndex < 0) && (semIndex >= 0)) {
154             //    we have a ';' (and therefore a parameter list),
155             //    but no '/' indicating a sub type is present
156             throw new MimeTypeParseException("Unable to find a sub type.");
157         } else if((slashIndex >= 0) && (semIndex < 0)) {
158             //    we have a primary and sub type but no parameter list
159             primaryType = rawdata.substring(0,slashIndex).
160                 trim().toLowerCase(Locale.ENGLISH);
161             subType = rawdata.substring(slashIndex + 1).
162                 trim().toLowerCase(Locale.ENGLISH);
163             parameters = new MimeTypeParameterList();
164         } else if (slashIndex < semIndex) {
165             //    we have all three items in the proper sequence
166             primaryType = rawdata.substring(0, slashIndex).
167                 trim().toLowerCase(Locale.ENGLISH);
168             subType = rawdata.substring(slashIndex + 1,
169                 semIndex).trim().toLowerCase(Locale.ENGLISH);
170             parameters = new
171 MimeTypeParameterList(rawdata.substring(semIndex));
172         } else {
173             //    we have a ';' lexically before a '/' which means we have a primary type
174             //    & a parameter list but no sub type
175             throw new MimeTypeParseException("Unable to find a sub type.");
176         }
177
178         //    now validate the primary and sub types
179
180         //    check to see if primary is valid
181         if(!isValidToken(primaryType)) {
182             throw new MimeTypeParseException("Primary type is invalid.");
183         }
184
185         //    check to see if sub is valid
186         if(!isValidToken(subType)) {
187             throw new MimeTypeParseException("Sub type is invalid.");
188         }
189     }
190
191     /**
192      * Retrieve the primary type of this object.
193      */

194     public String getPrimaryType() {
195         return primaryType;
196     }
197
198     /**
199      * Retrieve the sub type of this object.
200      */

201     public String getSubType() {
202         return subType;
203     }
204
205     /**
206      * Retrieve a copy of this object's parameter list.
207      */

208     public MimeTypeParameterList getParameters() {
209         return (MimeTypeParameterList)parameters.clone();
210     }
211
212     /**
213      * Retrieve the value associated with the given name, or {@code nullif
214      * there is no current association.
215      */

216     public String getParameter(String name) {
217         return parameters.get(name);
218     }
219
220     /**
221      * Set the value to be associated with the given name, replacing
222      * any previous association.
223      *
224      * @throws IllegalArgumentException if parameter or value is illegal
225      */

226     public void setParameter(String name, String value) {
227         parameters.set(name, value);
228     }
229
230     /**
231      * Remove any value associated with the given name.
232      *
233      * @throws IllegalArgumentException if parameter may not be deleted
234      */

235     public void removeParameter(String name) {
236         parameters.remove(name);
237     }
238
239     /**
240      * Return the String representation of this object.
241      */

242     public String toString() {
243         return getBaseType() + parameters.toString();
244     }
245
246     /**
247      * Return a String representation of this object without the parameter list.
248      */

249     public String getBaseType() {
250         return primaryType + "/" + subType;
251     }
252
253     /**
254      * Returns {@code trueif the primary type and the subtype of this object
255      * are the same as the specified {@code type}; otherwise returns
256      * {@code false}.
257      *
258      * @param  type the type to compare to {@code this}'s type
259      * @return {@code trueif the primary type and the subtype of this object
260      *         are the same as the specified {@code type}; otherwise returns
261      *         {@code false}
262      */

263     public boolean match(MimeType type) {
264         if (type == null)
265             return false;
266         return primaryType.equals(type.getPrimaryType())
267                     && (subType.equals("*")
268                             || type.getSubType().equals("*")
269                             || (subType.equals(type.getSubType())));
270     }
271
272     /**
273      * Returns {@code trueif the primary type and the subtype of this object
274      * are the same as the content type described in {@code rawdata}; otherwise
275      * returns {@code false}.
276      *
277      * @param  rawdata the raw data to be examined
278      * @return {@code trueif the primary type and the subtype of this object
279      *         are the same as the content type described in {@code rawdata};
280      *         otherwise returns {@code false}; if {@code rawdata} is
281      *         {@code null}, returns {@code false}
282      */

283     public boolean match(String rawdata) throws MimeTypeParseException {
284         if (rawdata == null)
285             return false;
286         return match(new MimeType(rawdata));
287     }
288
289     /**
290      * The object implements the writeExternal method to save its contents by
291      * calling the methods of DataOutput for its primitive values or calling the
292      * writeObject method of ObjectOutput for objects, strings and arrays.
293      *
294      * @throws IOException Includes any I/O exceptions that may occur
295      */

296     public void writeExternal(ObjectOutput out) throws IOException {
297         String s = toString(); // contains ASCII chars only
298         // one-to-one correspondence between ASCII char and byte in UTF string
299         if (s.length() <= 65535) { // 65535 is max length of UTF string
300             out.writeUTF(s);
301         } else {
302             out.writeByte(0);
303             out.writeByte(0);
304             out.writeInt(s.length());
305             out.write(s.getBytes());
306         }
307     }
308
309     /**
310      * The object implements the readExternal method to restore its contents by
311      * calling the methods of DataInput for primitive types and readObject for
312      * objects, strings and arrays. The readExternal method must read the values
313      * in the same sequence and with the same types as were written by
314      * writeExternal.
315      *
316      * @throws ClassNotFoundException If the class for an object being restored
317      *         cannot be found
318      */

319     public void readExternal(ObjectInput in) throws IOException,
320 ClassNotFoundException {
321         String s = in.readUTF();
322         if (s == null || s.length() == 0) { // long mime type
323             byte[] ba = new byte[in.readInt()];
324             in.readFully(ba);
325             s = new String(ba);
326         }
327         try {
328             parse(s);
329         } catch(MimeTypeParseException e) {
330             throw new IOException(e.toString());
331         }
332     }
333
334     /**
335      * Returns a clone of this object.
336      *
337      * @return a clone of this object
338      */

339     public Object clone() {
340         MimeType newObj = null;
341         try {
342             newObj = (MimeType)super.clone();
343         } catch (CloneNotSupportedException cannotHappen) {
344         }
345         newObj.parameters = (MimeTypeParameterList)parameters.clone();
346         return newObj;
347     }
348
349     private String    primaryType;
350     private String    subType;
351     private MimeTypeParameterList parameters;
352
353     //    below here be scary parsing related things
354
355     /**
356      * Determines whether or not a given character belongs to a legal token.
357      */

358     private static boolean isTokenChar(char c) {
359         return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
360     }
361
362     /**
363      * Determines whether or not a given string is a legal token.
364      *
365      * @throws NullPointerException if {@code s} is {@code null}
366      */

367     private boolean isValidToken(String s) {
368         int len = s.length();
369         if(len > 0) {
370             for (int i = 0; i < len; ++i) {
371                 char c = s.charAt(i);
372                 if (!isTokenChar(c)) {
373                     return false;
374                 }
375             }
376             return true;
377         } else {
378             return false;
379         }
380     }
381
382     /**
383      * A string that holds all the special chars.
384      */

385     private static final String TSPECIALS = "()<>@,;:\\\"/[]?=";
386// class MimeType
387