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 true} if {@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 null} if
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 true} if 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 true} if 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 true} if 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 true} if 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