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