1 /* 2 * Copyright (c) 2000, 2014, 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 sun.awt.datatransfer.desktop; 27 28 import java.awt.EventQueue; 29 import java.awt.Graphics; 30 import java.awt.Image; 31 import java.awt.Toolkit; 32 33 import java.awt.datatransfer.DataFlavor; 34 import java.awt.datatransfer.FlavorMap; 35 import java.awt.datatransfer.FlavorTable; 36 import java.awt.datatransfer.Transferable; 37 import java.awt.datatransfer.UnsupportedFlavorException; 38 39 import java.io.BufferedReader; 40 import java.io.ByteArrayInputStream; 41 import java.io.ByteArrayOutputStream; 42 import java.io.File; 43 import java.io.InputStream; 44 import java.io.InputStreamReader; 45 import java.io.IOException; 46 import java.io.ObjectInputStream; 47 import java.io.ObjectOutputStream; 48 import java.io.Reader; 49 import java.io.SequenceInputStream; 50 import java.io.StringReader; 51 52 import java.net.URI; 53 import java.net.URISyntaxException; 54 55 import java.nio.ByteBuffer; 56 import java.nio.CharBuffer; 57 import java.nio.charset.Charset; 58 import java.nio.charset.CharsetEncoder; 59 import java.nio.charset.IllegalCharsetNameException; 60 import java.nio.charset.StandardCharsets; 61 import java.nio.charset.UnsupportedCharsetException; 62 63 import java.lang.reflect.Constructor; 64 import java.lang.reflect.Modifier; 65 66 import java.security.AccessController; 67 import java.security.PrivilegedAction; 68 import java.security.PrivilegedActionException; 69 import java.security.PrivilegedExceptionAction; 70 import java.security.ProtectionDomain; 71 72 import java.util.*; 73 74 import sun.awt.datatransfer.DataFlavorUtil; 75 import sun.misc.RMIAccessService; 76 import sun.util.logging.PlatformLogger; 77 78 import sun.awt.AppContext; 79 import sun.awt.SunToolkit; 80 81 import java.awt.image.BufferedImage; 82 import java.awt.image.ImageObserver; 83 import java.awt.image.RenderedImage; 84 import java.awt.image.WritableRaster; 85 import java.awt.image.ColorModel; 86 87 import javax.imageio.ImageIO; 88 import javax.imageio.ImageReader; 89 import javax.imageio.ImageReadParam; 90 import javax.imageio.ImageWriter; 91 import javax.imageio.ImageTypeSpecifier; 92 93 import javax.imageio.spi.ImageWriterSpi; 94 95 import javax.imageio.stream.ImageInputStream; 96 import javax.imageio.stream.ImageOutputStream; 97 98 import sun.awt.image.ImageRepresentation; 99 import sun.awt.image.ToolkitImage; 100 101 import java.io.FilePermission; 102 import java.util.stream.Stream; 103 104 105 /** 106 * Provides a set of functions to be shared among the DataFlavor class and 107 * platform-specific data transfer implementations. 108 * 109 * The concept of "flavors" and "natives" is extended to include "formats", 110 * which are the numeric values Win32 and X11 use to express particular data 111 * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and 112 * getFlavorsForNatives(String[]) functions, DataTransferer provides a set 113 * of getFormatsFor(Transferable|Flavor|Flavors) and 114 * getFlavorsFor(Format|Formats) functions. 115 * 116 * Also provided are functions for translating a Transferable into a byte 117 * array, given a source DataFlavor and a target format, and for translating 118 * a byte array or InputStream into an Object, given a source format and 119 * a target DataFlavor. 120 * 121 * @author David Mendenhall 122 * @author Danila Sinopalnikov 123 * 124 * @since 1.3.1 125 */ 126 public abstract class DataTransferer { 127 /** 128 * The <code>DataFlavor</code> representing a Java text encoding String 129 * encoded in UTF-8, where 130 * <pre> 131 * representationClass = [B 132 * mimeType = "application/x-java-text-encoding" 133 * </pre> 134 */ 135 public static final DataFlavor javaTextEncodingFlavor; 136 137 /** 138 * A collection of all natives listed in flavormap.properties with 139 * a primary MIME type of "text". 140 */ 141 private static final Set<Long> textNatives = 142 Collections.synchronizedSet(new HashSet<>()); 143 144 /** 145 * The native encodings/charsets for the Set of textNatives. 146 */ 147 private static final Map<Long, String> nativeCharsets = 148 Collections.synchronizedMap(new HashMap<>()); 149 150 /** 151 * The end-of-line markers for the Set of textNatives. 152 */ 153 private static final Map<Long, String> nativeEOLNs = 154 Collections.synchronizedMap(new HashMap<>()); 155 156 /** 157 * The number of terminating NUL bytes for the Set of textNatives. 158 */ 159 private static final Map<Long, Integer> nativeTerminators = 160 Collections.synchronizedMap(new HashMap<>()); 161 162 /** 163 * The key used to store pending data conversion requests for an AppContext. 164 */ 165 private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY"; 166 167 static { 168 DataFlavor tJavaTextEncodingFlavor = null; 169 try { 170 tJavaTextEncodingFlavor = new DataFlavor("application/x-java-text-encoding;class=\"[B\""); 171 } catch (ClassNotFoundException cannotHappen) { 172 } 173 javaTextEncodingFlavor = tJavaTextEncodingFlavor; 174 } 175 176 /** 177 * The accessor method for the singleton DataTransferer instance. Note 178 * that in a headless environment, there may be no DataTransferer instance; 179 * instead, null will be returned. 180 */ 181 public static synchronized DataTransferer getInstance() { 182 return ((SunToolkit) Toolkit.getDefaultToolkit()).getDataTransferer(); 183 } 184 185 /** 186 * Converts a FlavorMap to a FlavorTable. 187 */ 188 public static FlavorTable adaptFlavorMap(final FlavorMap map) { 189 if (map instanceof FlavorTable) { 190 return (FlavorTable)map; 191 } 192 193 return new FlavorTable() { 194 @Override 195 public Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] flavors) { 196 return map.getNativesForFlavors(flavors); 197 } 198 @Override 199 public Map<String, DataFlavor> getFlavorsForNatives(String[] natives) { 200 return map.getFlavorsForNatives(natives); 201 } 202 @Override 203 public List<String> getNativesForFlavor(DataFlavor flav) { 204 Map<DataFlavor, String> natives = getNativesForFlavors(new DataFlavor[]{flav}); 205 String nat = natives.get(flav); 206 if (nat != null) { 207 return Collections.singletonList(nat); 208 } else { 209 return Collections.emptyList(); 210 } 211 } 212 @Override 213 public List<DataFlavor> getFlavorsForNative(String nat) { 214 Map<String, DataFlavor> flavors = getFlavorsForNatives(new String[]{nat}); 215 DataFlavor flavor = flavors.get(nat); 216 if (flavor != null) { 217 return Collections.singletonList(flavor); 218 } else { 219 return Collections.emptyList(); 220 } 221 } 222 }; 223 } 224 225 /** 226 * Returns the default Unicode encoding for the platform. The encoding 227 * need not be canonical. This method is only used by the archaic function 228 * DataFlavor.getTextPlainUnicodeFlavor(). 229 */ 230 public abstract String getDefaultUnicodeEncoding(); 231 232 /** 233 * This method is called for text flavor mappings established while parsing 234 * the flavormap.properties file. It stores the "eoln" and "terminators" 235 * parameters which are not officially part of the MIME type. They are 236 * MIME parameters specific to the flavormap.properties file format. 237 */ 238 public void registerTextFlavorProperties(String nat, String charset, 239 String eoln, String terminators) { 240 Long format = getFormatForNativeAsLong(nat); 241 242 textNatives.add(format); 243 nativeCharsets.put(format, (charset != null && charset.length() != 0) 244 ? charset : Charset.defaultCharset().name()); 245 if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) { 246 nativeEOLNs.put(format, eoln); 247 } 248 if (terminators != null && terminators.length() != 0) { 249 Integer iTerminators = Integer.valueOf(terminators); 250 if (iTerminators > 0) { 251 nativeTerminators.put(format, iTerminators); 252 } 253 } 254 } 255 256 /** 257 * Determines whether the native corresponding to the specified long format 258 * was listed in the flavormap.properties file. 259 */ 260 protected boolean isTextFormat(long format) { 261 return textNatives.contains(Long.valueOf(format)); 262 } 263 264 protected String getCharsetForTextFormat(Long lFormat) { 265 return nativeCharsets.get(lFormat); 266 } 267 268 /** 269 * Specifies whether text imported from the native system in the specified 270 * format is locale-dependent. If so, when decoding such text, 271 * 'nativeCharsets' should be ignored, and instead, the Transferable should 272 * be queried for its javaTextEncodingFlavor data for the correct encoding. 273 */ 274 public abstract boolean isLocaleDependentTextFormat(long format); 275 276 /** 277 * Determines whether the DataFlavor corresponding to the specified long 278 * format is DataFlavor.javaFileListFlavor. 279 */ 280 public abstract boolean isFileFormat(long format); 281 282 /** 283 * Determines whether the DataFlavor corresponding to the specified long 284 * format is DataFlavor.imageFlavor. 285 */ 286 public abstract boolean isImageFormat(long format); 287 288 /** 289 * Determines whether the format is a URI list we can convert to 290 * a DataFlavor.javaFileListFlavor. 291 */ 292 protected boolean isURIListFormat(long format) { 293 return false; 294 } 295 296 /** 297 * Returns a Map whose keys are all of the possible formats into which the 298 * Transferable's transfer data flavors can be translated. The value of 299 * each key is the DataFlavor in which the Transferable's data should be 300 * requested when converting to the format. 301 * <p> 302 * The map keys are sorted according to the native formats preference 303 * order. 304 */ 305 public SortedMap<Long,DataFlavor> getFormatsForTransferable(Transferable contents, 306 FlavorTable map) 307 { 308 DataFlavor[] flavors = contents.getTransferDataFlavors(); 309 if (flavors == null) { 310 return Collections.emptySortedMap(); 311 } 312 return getFormatsForFlavors(flavors, map); 313 } 314 315 /** 316 * Returns a Map whose keys are all of the possible formats into which data 317 * in the specified DataFlavors can be translated. The value of each key 318 * is the DataFlavor in which the Transferable's data should be requested 319 * when converting to the format. 320 * <p> 321 * The map keys are sorted according to the native formats preference 322 * order. 323 * 324 * @param flavors the data flavors 325 * @param map the FlavorTable which contains mappings between 326 * DataFlavors and data formats 327 * @throws NullPointerException if flavors or map is <code>null</code> 328 */ 329 public SortedMap<Long, DataFlavor> getFormatsForFlavors(DataFlavor[] flavors, 330 FlavorTable map) 331 { 332 Map<Long,DataFlavor> formatMap = new HashMap<>(flavors.length); 333 Map<Long,DataFlavor> textPlainMap = new HashMap<>(flavors.length); 334 // Maps formats to indices that will be used to sort the formats 335 // according to the preference order. 336 // Larger index value corresponds to the more preferable format. 337 Map<Long, Integer> indexMap = new HashMap<>(flavors.length); 338 Map<Long, Integer> textPlainIndexMap = new HashMap<>(flavors.length); 339 340 int currentIndex = 0; 341 342 // Iterate backwards so that preferred DataFlavors are used over 343 // other DataFlavors. (See javadoc for 344 // Transferable.getTransferDataFlavors.) 345 for (int i = flavors.length - 1; i >= 0; i--) { 346 DataFlavor flavor = flavors[i]; 347 if (flavor == null) continue; 348 349 // Don't explicitly test for String, since it is just a special 350 // case of Serializable 351 if (flavor.isFlavorTextType() || 352 flavor.isFlavorJavaFileListType() || 353 DataFlavor.imageFlavor.equals(flavor) || 354 flavor.isRepresentationClassSerializable() || 355 flavor.isRepresentationClassInputStream() || 356 flavor.isRepresentationClassRemote()) 357 { 358 List<String> natives = map.getNativesForFlavor(flavor); 359 360 currentIndex += natives.size(); 361 362 for (String aNative : natives) { 363 Long lFormat = getFormatForNativeAsLong(aNative); 364 Integer index = currentIndex--; 365 366 formatMap.put(lFormat, flavor); 367 indexMap.put(lFormat, index); 368 369 // SystemFlavorMap.getNativesForFlavor will return 370 // text/plain natives for all text/*. While this is good 371 // for a single text/* flavor, we would prefer that 372 // text/plain native data come from a text/plain flavor. 373 if (("text".equals(flavor.getPrimaryType()) && 374 "plain".equals(flavor.getSubType())) || 375 flavor.equals(DataFlavor.stringFlavor)) { 376 textPlainMap.put(lFormat, flavor); 377 textPlainIndexMap.put(lFormat, index); 378 } 379 } 380 381 currentIndex += natives.size(); 382 } 383 } 384 385 formatMap.putAll(textPlainMap); 386 indexMap.putAll(textPlainIndexMap); 387 388 // Sort the map keys according to the formats preference order. 389 Comparator<Long> comparator = DataFlavorUtil.getIndexOrderComparator(indexMap).reversed(); 390 SortedMap<Long, DataFlavor> sortedMap = new TreeMap<>(comparator); 391 sortedMap.putAll(formatMap); 392 393 return sortedMap; 394 } 395 396 /** 397 * Reduces the Map output for the root function to an array of the 398 * Map's keys. 399 */ 400 public long[] getFormatsForTransferableAsArray(Transferable contents, 401 FlavorTable map) { 402 return keysToLongArray(getFormatsForTransferable(contents, map)); 403 } 404 405 /** 406 * Returns a Map whose keys are all of the possible DataFlavors into which 407 * data in the specified formats can be translated. The value of each key 408 * is the format in which the Clipboard or dropped data should be requested 409 * when converting to the DataFlavor. 410 */ 411 public Map<DataFlavor, Long> getFlavorsForFormats(long[] formats, FlavorTable map) { 412 Map<DataFlavor, Long> flavorMap = new HashMap<>(formats.length); 413 Set<AbstractMap.SimpleEntry<Long, DataFlavor>> mappingSet = new HashSet<>(formats.length); 414 Set<DataFlavor> flavorSet = new HashSet<>(formats.length); 415 416 // First step: build flavorSet, mappingSet and initial flavorMap 417 // flavorSet - the set of all the DataFlavors into which 418 // data in the specified formats can be translated; 419 // mappingSet - the set of all the mappings from the specified formats 420 // into any DataFlavor; 421 // flavorMap - after this step, this map maps each of the DataFlavors 422 // from flavorSet to any of the specified formats. 423 for (long format : formats) { 424 String nat = getNativeForFormat(format); 425 List<DataFlavor> flavors = map.getFlavorsForNative(nat); 426 for (DataFlavor flavor : flavors) { 427 // Don't explicitly test for String, since it is just a special 428 // case of Serializable 429 if (flavor.isFlavorTextType() || 430 flavor.isFlavorJavaFileListType() || 431 DataFlavor.imageFlavor.equals(flavor) || 432 flavor.isRepresentationClassSerializable() || 433 flavor.isRepresentationClassInputStream() || 434 flavor.isRepresentationClassRemote()) { 435 436 AbstractMap.SimpleEntry<Long, DataFlavor> mapping = 437 new AbstractMap.SimpleEntry<>(format, flavor); 438 flavorMap.put(flavor, format); 439 mappingSet.add(mapping); 440 flavorSet.add(flavor); 441 } 442 } 443 } 444 445 // Second step: for each DataFlavor try to figure out which of the 446 // specified formats is the best to translate to this flavor. 447 // Then map each flavor to the best format. 448 // For the given flavor, FlavorTable indicates which native will 449 // best reflect data in the specified flavor to the underlying native 450 // platform. We assume that this native is the best to translate 451 // to this flavor. 452 // Note: FlavorTable allows one-way mappings, so we can occasionally 453 // map a flavor to the format for which the corresponding 454 // format-to-flavor mapping doesn't exist. For this reason we have built 455 // a mappingSet of all format-to-flavor mappings for the specified formats 456 // and check if the format-to-flavor mapping exists for the 457 // (flavor,format) pair being added. 458 for (DataFlavor flavor : flavorSet) { 459 List<String> natives = map.getNativesForFlavor(flavor); 460 for (String aNative : natives) { 461 Long lFormat = getFormatForNativeAsLong(aNative); 462 if (mappingSet.contains(new AbstractMap.SimpleEntry<>(lFormat, flavor))) { 463 flavorMap.put(flavor, lFormat); 464 break; 465 } 466 } 467 } 468 469 return flavorMap; 470 } 471 472 /** 473 * Returns a Set of all DataFlavors for which 474 * 1) a mapping from at least one of the specified formats exists in the 475 * specified map and 476 * 2) the data translation for this mapping can be performed by the data 477 * transfer subsystem. 478 * 479 * @param formats the data formats 480 * @param map the FlavorTable which contains mappings between 481 * DataFlavors and data formats 482 * @throws NullPointerException if formats or map is <code>null</code> 483 */ 484 public Set<DataFlavor> getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) { 485 Set<DataFlavor> flavorSet = new HashSet<>(formats.length); 486 487 for (long format : formats) { 488 List<DataFlavor> flavors = map.getFlavorsForNative(getNativeForFormat(format)); 489 for (DataFlavor flavor : flavors) { 490 // Don't explicitly test for String, since it is just a special 491 // case of Serializable 492 if (flavor.isFlavorTextType() || 493 flavor.isFlavorJavaFileListType() || 494 DataFlavor.imageFlavor.equals(flavor) || 495 flavor.isRepresentationClassSerializable() || 496 flavor.isRepresentationClassInputStream() || 497 flavor.isRepresentationClassRemote()) { 498 flavorSet.add(flavor); 499 } 500 } 501 } 502 503 return flavorSet; 504 } 505 506 /** 507 * Returns an array of all DataFlavors for which 508 * 1) a mapping from at least one of the specified formats exists in the 509 * specified map and 510 * 2) the data translation for this mapping can be performed by the data 511 * transfer subsystem. 512 * The array will be sorted according to a 513 * <code>DataFlavorComparator</code> created with the specified 514 * map as an argument. 515 * 516 * @param formats the data formats 517 * @param map the FlavorTable which contains mappings between 518 * DataFlavors and data formats 519 * @throws NullPointerException if formats or map is <code>null</code> 520 */ 521 public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats, 522 FlavorTable map) { 523 // getFlavorsForFormatsAsSet() is less expensive than 524 // getFlavorsForFormats(). 525 return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map)); 526 } 527 528 /** 529 * Looks-up or registers the String native with the native data transfer 530 * system and returns a long format corresponding to that native. 531 */ 532 protected abstract Long getFormatForNativeAsLong(String str); 533 534 /** 535 * Looks-up the String native corresponding to the specified long format in 536 * the native data transfer system. 537 */ 538 protected abstract String getNativeForFormat(long format); 539 540 /* Contains common code for finding the best charset for 541 * clipboard string encoding/decoding, basing on clipboard 542 * format and localeTransferable(on decoding, if available) 543 */ 544 protected String getBestCharsetForTextFormat(Long lFormat, 545 Transferable localeTransferable) throws IOException 546 { 547 String charset = null; 548 if (localeTransferable != null && 549 isLocaleDependentTextFormat(lFormat) && 550 localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) { 551 try { 552 byte[] charsetNameBytes = (byte[])localeTransferable 553 .getTransferData(javaTextEncodingFlavor); 554 charset = new String(charsetNameBytes, StandardCharsets.UTF_8); 555 } catch (UnsupportedFlavorException cannotHappen) { 556 } 557 } else { 558 charset = getCharsetForTextFormat(lFormat); 559 } 560 if (charset == null) { 561 // Only happens when we have a custom text type. 562 charset = Charset.defaultCharset().name(); 563 } 564 return charset; 565 } 566 567 /** 568 * Translation function for converting string into 569 * a byte array. Search-and-replace EOLN. Encode into the 570 * target format. Append terminating NUL bytes. 571 * 572 * Java to Native string conversion 573 */ 574 private byte[] translateTransferableString(String str, 575 long format) throws IOException 576 { 577 Long lFormat = format; 578 String charset = getBestCharsetForTextFormat(lFormat, null); 579 // Search and replace EOLN. Note that if EOLN is "\n", then we 580 // never added an entry to nativeEOLNs anyway, so we'll skip this 581 // code altogether. 582 // windows: "abc\nde"->"abc\r\nde" 583 String eoln = nativeEOLNs.get(lFormat); 584 if (eoln != null) { 585 int length = str.length(); 586 StringBuilder buffer = new StringBuilder(length * 2); // 2 is a heuristic 587 for (int i = 0; i < length; i++) { 588 // Fix for 4914613 - skip native EOLN 589 if (str.startsWith(eoln, i)) { 590 buffer.append(eoln); 591 i += eoln.length() - 1; 592 continue; 593 } 594 char c = str.charAt(i); 595 if (c == '\n') { 596 buffer.append(eoln); 597 } else { 598 buffer.append(c); 599 } 600 } 601 str = buffer.toString(); 602 } 603 604 // Encode text in target format. 605 byte[] bytes = str.getBytes(charset); 606 607 // Append terminating NUL bytes. Note that if terminators is 0, 608 // the we never added an entry to nativeTerminators anyway, so 609 // we'll skip code altogether. 610 // "abcde" -> "abcde\0" 611 Integer terminators = nativeTerminators.get(lFormat); 612 if (terminators != null) { 613 int numTerminators = terminators; 614 byte[] terminatedBytes = 615 new byte[bytes.length + numTerminators]; 616 System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length); 617 for (int i = bytes.length; i < terminatedBytes.length; i++) { 618 terminatedBytes[i] = 0x0; 619 } 620 bytes = terminatedBytes; 621 } 622 return bytes; 623 } 624 625 /** 626 * Translating either a byte array or an InputStream into an String. 627 * Strip terminators and search-and-replace EOLN. 628 * 629 * Native to Java string conversion 630 */ 631 private String translateBytesToString(byte[] bytes, long format, 632 Transferable localeTransferable) 633 throws IOException 634 { 635 636 Long lFormat = format; 637 String charset = getBestCharsetForTextFormat(lFormat, localeTransferable); 638 639 // Locate terminating NUL bytes. Note that if terminators is 0, 640 // the we never added an entry to nativeTerminators anyway, so 641 // we'll skip code altogether. 642 643 // In other words: we are doing char alignment here basing on suggestion 644 // that count of zero-'terminators' is a number of bytes in one symbol 645 // for selected charset (clipboard format). It is not complitly true for 646 // multibyte coding like UTF-8, but helps understand the procedure. 647 // "abcde\0" -> "abcde" 648 649 String eoln = nativeEOLNs.get(lFormat); 650 Integer terminators = nativeTerminators.get(lFormat); 651 int count; 652 if (terminators != null) { 653 int numTerminators = terminators; 654 search: 655 for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) { 656 for (int i = count; i < count + numTerminators; i++) { 657 if (bytes[i] != 0x0) { 658 continue search; 659 } 660 } 661 // found terminators 662 break search; 663 } 664 } else { 665 count = bytes.length; 666 } 667 668 // Decode text to chars. Don't include any terminators. 669 String converted = new String(bytes, 0, count, charset); 670 671 // Search and replace EOLN. Note that if EOLN is "\n", then we 672 // never added an entry to nativeEOLNs anyway, so we'll skip this 673 // code altogether. 674 // Count of NUL-terminators and EOLN coding are platform-specific and 675 // loaded from flavormap.properties file 676 // windows: "abc\r\nde" -> "abc\nde" 677 678 if (eoln != null) { 679 680 /* Fix for 4463560: replace EOLNs symbol-by-symbol instead 681 * of using buf.replace() 682 */ 683 684 char[] buf = converted.toCharArray(); 685 char[] eoln_arr = eoln.toCharArray(); 686 int j = 0; 687 boolean match; 688 689 for (int i = 0; i < buf.length; ) { 690 // Catch last few bytes 691 if (i + eoln_arr.length > buf.length) { 692 buf[j++] = buf[i++]; 693 continue; 694 } 695 696 match = true; 697 for (int k = 0, l = i; k < eoln_arr.length; k++, l++) { 698 if (eoln_arr[k] != buf[l]) { 699 match = false; 700 break; 701 } 702 } 703 if (match) { 704 buf[j++] = '\n'; 705 i += eoln_arr.length; 706 } else { 707 buf[j++] = buf[i++]; 708 } 709 } 710 converted = new String(buf, 0, j); 711 } 712 713 return converted; 714 } 715 716 717 /** 718 * Primary translation function for translating a Transferable into 719 * a byte array, given a source DataFlavor and target format. 720 */ 721 public byte[] translateTransferable(Transferable contents, 722 DataFlavor flavor, 723 long format) throws IOException 724 { 725 // Obtain the transfer data in the source DataFlavor. 726 // 727 // Note that we special case DataFlavor.plainTextFlavor because 728 // StringSelection supports this flavor incorrectly -- instead of 729 // returning an InputStream as the DataFlavor representation class 730 // states, it returns a Reader. Instead of using this broken 731 // functionality, we request the data in stringFlavor (the other 732 // DataFlavor which StringSelection supports) and use the String 733 // translator. 734 Object obj; 735 boolean stringSelectionHack; 736 try { 737 obj = contents.getTransferData(flavor); 738 if (obj == null) { 739 return null; 740 } 741 if (flavor.equals(DataFlavor.plainTextFlavor) && 742 !(obj instanceof InputStream)) 743 { 744 obj = contents.getTransferData(DataFlavor.stringFlavor); 745 if (obj == null) { 746 return null; 747 } 748 stringSelectionHack = true; 749 } else { 750 stringSelectionHack = false; 751 } 752 } catch (UnsupportedFlavorException e) { 753 throw new IOException(e.getMessage()); 754 } 755 756 // Source data is a String. Search-and-replace EOLN. Encode into the 757 // target format. Append terminating NUL bytes. 758 if (stringSelectionHack || 759 (String.class.equals(flavor.getRepresentationClass()) && 760 DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 761 762 String str = removeSuspectedData(flavor, contents, (String)obj); 763 764 return translateTransferableString( 765 str, 766 format); 767 768 // Source data is a Reader. Convert to a String and recur. In the 769 // future, we may want to rewrite this so that we encode on demand. 770 } else if (flavor.isRepresentationClassReader()) { 771 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 772 throw new IOException 773 ("cannot transfer non-text data as Reader"); 774 } 775 776 StringBuilder buf = new StringBuilder(); 777 try (Reader r = (Reader)obj) { 778 int c; 779 while ((c = r.read()) != -1) { 780 buf.append((char)c); 781 } 782 } 783 784 return translateTransferableString( 785 buf.toString(), 786 format); 787 788 // Source data is a CharBuffer. Convert to a String and recur. 789 } else if (flavor.isRepresentationClassCharBuffer()) { 790 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 791 throw new IOException 792 ("cannot transfer non-text data as CharBuffer"); 793 } 794 795 CharBuffer buffer = (CharBuffer)obj; 796 int size = buffer.remaining(); 797 char[] chars = new char[size]; 798 buffer.get(chars, 0, size); 799 800 return translateTransferableString( 801 new String(chars), 802 format); 803 804 // Source data is a char array. Convert to a String and recur. 805 } else if (char[].class.equals(flavor.getRepresentationClass())) { 806 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 807 throw new IOException 808 ("cannot transfer non-text data as char array"); 809 } 810 811 return translateTransferableString( 812 new String((char[])obj), 813 format); 814 815 // Source data is a ByteBuffer. For arbitrary flavors, simply return 816 // the array. For text flavors, decode back to a String and recur to 817 // reencode according to the requested format. 818 } else if (flavor.isRepresentationClassByteBuffer()) { 819 ByteBuffer buffer = (ByteBuffer)obj; 820 int size = buffer.remaining(); 821 byte[] bytes = new byte[size]; 822 buffer.get(bytes, 0, size); 823 824 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 825 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor); 826 return translateTransferableString( 827 new String(bytes, sourceEncoding), 828 format); 829 } else { 830 return bytes; 831 } 832 833 // Source data is a byte array. For arbitrary flavors, simply return 834 // the array. For text flavors, decode back to a String and recur to 835 // reencode according to the requested format. 836 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 837 byte[] bytes = (byte[])obj; 838 839 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 840 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor); 841 return translateTransferableString( 842 new String(bytes, sourceEncoding), 843 format); 844 } else { 845 return bytes; 846 } 847 // Source data is Image 848 } else if (DataFlavor.imageFlavor.equals(flavor)) { 849 if (!isImageFormat(format)) { 850 throw new IOException("Data translation failed: " + 851 "not an image format"); 852 } 853 854 Image image = (Image)obj; 855 byte[] bytes = imageToPlatformBytes(image, format); 856 857 if (bytes == null) { 858 throw new IOException("Data translation failed: " + 859 "cannot convert java image to native format"); 860 } 861 return bytes; 862 } 863 864 byte[] theByteArray = null; 865 866 // Target data is a file list. Source data must be a 867 // java.util.List which contains java.io.File or String instances. 868 if (isFileFormat(format)) { 869 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 870 throw new IOException("data translation failed"); 871 } 872 873 final List<?> list = (List<?>)obj; 874 875 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 876 877 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain); 878 879 try (ByteArrayOutputStream bos = convertFileListToBytes(fileList)) { 880 theByteArray = bos.toByteArray(); 881 } 882 883 // Target data is a URI list. Source data must be a 884 // java.util.List which contains java.io.File or String instances. 885 } else if (isURIListFormat(format)) { 886 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 887 throw new IOException("data translation failed"); 888 } 889 String nat = getNativeForFormat(format); 890 String targetCharset = null; 891 if (nat != null) { 892 try { 893 targetCharset = new DataFlavor(nat).getParameter("charset"); 894 } catch (ClassNotFoundException cnfe) { 895 throw new IOException(cnfe); 896 } 897 } 898 if (targetCharset == null) { 899 targetCharset = "UTF-8"; 900 } 901 final List<?> list = (List<?>)obj; 902 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 903 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain); 904 final ArrayList<String> uriList = new ArrayList<>(fileList.size()); 905 for (String fileObject : fileList) { 906 final URI uri = new File(fileObject).toURI(); 907 // Some implementations are fussy about the number of slashes (file:///path/to/file is best) 908 try { 909 uriList.add(new URI(uri.getScheme(), "", uri.getPath(), uri.getFragment()).toString()); 910 } catch (URISyntaxException uriSyntaxException) { 911 throw new IOException(uriSyntaxException); 912 } 913 } 914 915 byte[] eoln = "\r\n".getBytes(targetCharset); 916 917 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 918 for (String uri : uriList) { 919 byte[] bytes = uri.getBytes(targetCharset); 920 bos.write(bytes, 0, bytes.length); 921 bos.write(eoln, 0, eoln.length); 922 } 923 theByteArray = bos.toByteArray(); 924 } 925 926 // Source data is an InputStream. For arbitrary flavors, just grab the 927 // bytes and dump them into a byte array. For text flavors, decode back 928 // to a String and recur to reencode according to the requested format. 929 } else if (flavor.isRepresentationClassInputStream()) { 930 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 931 try (InputStream is = (InputStream)obj) { 932 boolean eof = false; 933 int avail = is.available(); 934 byte[] tmp = new byte[avail > 8192 ? avail : 8192]; 935 do { 936 int aValue; 937 if (!(eof = (aValue = is.read(tmp, 0, tmp.length)) == -1)) { 938 bos.write(tmp, 0, aValue); 939 } 940 } while (!eof); 941 } 942 943 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 944 byte[] bytes = bos.toByteArray(); 945 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor); 946 return translateTransferableString( 947 new String(bytes, sourceEncoding), 948 format); 949 } 950 theByteArray = bos.toByteArray(); 951 } 952 953 954 955 // Source data is an RMI object 956 } else if (flavor.isRepresentationClassRemote()) { 957 RMIAccessService service = getRMIAccessService(); 958 if (service != null) { 959 Object mo = service.newMarshalledObject(obj); 960 theByteArray = convertObjectToBytes(mo); 961 } 962 963 // Source data is Serializable 964 } else if (flavor.isRepresentationClassSerializable()) { 965 966 theByteArray = convertObjectToBytes(obj); 967 968 } else { 969 throw new IOException("data translation failed"); 970 } 971 972 973 974 return theByteArray; 975 } 976 977 private static byte[] convertObjectToBytes(Object object) throws IOException { 978 try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); 979 ObjectOutputStream oos = new ObjectOutputStream(bos)) 980 { 981 oos.writeObject(object); 982 return bos.toByteArray(); 983 } 984 } 985 986 protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException; 987 988 private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str) 989 throws IOException 990 { 991 if (null == System.getSecurityManager() 992 || !flavor.isMimeTypeEqual("text/uri-list")) 993 { 994 return str; 995 } 996 997 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 998 999 try { 1000 return AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> { 1001 1002 StringBuilder allowedFiles = new StringBuilder(str.length()); 1003 String [] uriArray = str.split("(\\s)+"); 1004 1005 for (String fileName : uriArray) 1006 { 1007 File file = new File(fileName); 1008 if (file.exists() && 1009 !(isFileInWebstartedCache(file) || 1010 isForbiddenToRead(file, userProtectionDomain))) 1011 { 1012 if (0 != allowedFiles.length()) 1013 { 1014 allowedFiles.append("\\r\\n"); 1015 } 1016 1017 allowedFiles.append(fileName); 1018 } 1019 } 1020 1021 return allowedFiles.toString(); 1022 }); 1023 } catch (PrivilegedActionException pae) { 1024 throw new IOException(pae.getMessage(), pae); 1025 } 1026 } 1027 1028 private static ProtectionDomain getUserProtectionDomain(Transferable contents) { 1029 return contents.getClass().getProtectionDomain(); 1030 } 1031 1032 private boolean isForbiddenToRead (File file, ProtectionDomain protectionDomain) 1033 { 1034 if (null == protectionDomain) { 1035 return false; 1036 } 1037 try { 1038 FilePermission filePermission = 1039 new FilePermission(file.getCanonicalPath(), "read, delete"); 1040 if (protectionDomain.implies(filePermission)) { 1041 return false; 1042 } 1043 } catch (IOException e) {} 1044 1045 return true; 1046 } 1047 1048 private ArrayList<String> castToFiles(final List<?> files, 1049 final ProtectionDomain userProtectionDomain) throws IOException { 1050 try { 1051 return AccessController.doPrivileged((PrivilegedExceptionAction<ArrayList<String>>) () -> { 1052 ArrayList<String> fileList = new ArrayList<>(); 1053 for (Object fileObject : files) 1054 { 1055 File file = castToFile(fileObject); 1056 if (file != null && 1057 (null == System.getSecurityManager() || 1058 !(isFileInWebstartedCache(file) || 1059 isForbiddenToRead(file, userProtectionDomain)))) 1060 { 1061 fileList.add(file.getCanonicalPath()); 1062 } 1063 } 1064 return fileList; 1065 }); 1066 } catch (PrivilegedActionException pae) { 1067 throw new IOException(pae.getMessage()); 1068 } 1069 } 1070 1071 // It is important do not use user's successors 1072 // of File class. 1073 private File castToFile(Object fileObject) throws IOException { 1074 String filePath = null; 1075 if (fileObject instanceof File) { 1076 filePath = ((File)fileObject).getCanonicalPath(); 1077 } else if (fileObject instanceof String) { 1078 filePath = (String) fileObject; 1079 } else { 1080 return null; 1081 } 1082 return new File(filePath); 1083 } 1084 1085 private final static String[] DEPLOYMENT_CACHE_PROPERTIES = { 1086 "deployment.system.cachedir", 1087 "deployment.user.cachedir", 1088 "deployment.javaws.cachedir", 1089 "deployment.javapi.cachedir" 1090 }; 1091 1092 private final static ArrayList <File> deploymentCacheDirectoryList = new ArrayList<>(); 1093 1094 private static boolean isFileInWebstartedCache(File f) { 1095 1096 if (deploymentCacheDirectoryList.isEmpty()) { 1097 for (String cacheDirectoryProperty : DEPLOYMENT_CACHE_PROPERTIES) { 1098 String cacheDirectoryPath = System.getProperty(cacheDirectoryProperty); 1099 if (cacheDirectoryPath != null) { 1100 try { 1101 File cacheDirectory = (new File(cacheDirectoryPath)).getCanonicalFile(); 1102 if (cacheDirectory != null) { 1103 deploymentCacheDirectoryList.add(cacheDirectory); 1104 } 1105 } catch (IOException ioe) {} 1106 } 1107 } 1108 } 1109 1110 for (File deploymentCacheDirectory : deploymentCacheDirectoryList) { 1111 for (File dir = f; dir != null; dir = dir.getParentFile()) { 1112 if (dir.equals(deploymentCacheDirectory)) { 1113 return true; 1114 } 1115 } 1116 } 1117 1118 return false; 1119 } 1120 1121 1122 public Object translateBytes(byte[] bytes, DataFlavor flavor, 1123 long format, Transferable localeTransferable) 1124 throws IOException 1125 { 1126 1127 Object theObject = null; 1128 1129 // Source data is a file list. Use the dragQueryFile native function to 1130 // do most of the decoding. Then wrap File objects around the String 1131 // filenames and return a List. 1132 if (isFileFormat(format)) { 1133 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 1134 throw new IOException("data translation failed"); 1135 } 1136 String[] filenames = dragQueryFile(bytes); 1137 if (filenames == null) { 1138 return null; 1139 } 1140 1141 // Convert the strings to File objects 1142 File[] files = new File[filenames.length]; 1143 for (int i = 0; i < filenames.length; i++) { 1144 files[i] = new File(filenames[i]); 1145 } 1146 1147 // Turn the list of Files into a List and return 1148 theObject = Arrays.asList(files); 1149 1150 // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor 1151 // where possible. 1152 } else if (isURIListFormat(format) 1153 && DataFlavor.javaFileListFlavor.equals(flavor)) { 1154 1155 try (ByteArrayInputStream str = new ByteArrayInputStream(bytes)) { 1156 1157 URI uris[] = dragQueryURIs(str, format, localeTransferable); 1158 if (uris == null) { 1159 return null; 1160 } 1161 List<File> files = new ArrayList<>(); 1162 for (URI uri : uris) { 1163 try { 1164 files.add(new File(uri)); 1165 } catch (IllegalArgumentException illegalArg) { 1166 // When converting from URIs to less generic files, 1167 // common practice (Wine, SWT) seems to be to 1168 // silently drop the URIs that aren't local files. 1169 } 1170 } 1171 theObject = files; 1172 } 1173 1174 // Target data is a String. Strip terminating NUL bytes. Decode bytes 1175 // into characters. Search-and-replace EOLN. 1176 } else if (String.class.equals(flavor.getRepresentationClass()) && 1177 DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1178 1179 theObject = translateBytesToString(bytes, format, localeTransferable); 1180 1181 // Target data is a Reader. Obtain data in InputStream format, encoded 1182 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode 1183 // back to chars on demand. 1184 } else if (flavor.isRepresentationClassReader()) { 1185 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1186 theObject = translateStream(bais, 1187 flavor, format, localeTransferable); 1188 } 1189 // Target data is a CharBuffer. Recur to obtain String and wrap. 1190 } else if (flavor.isRepresentationClassCharBuffer()) { 1191 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1192 throw new IOException("cannot transfer non-text data as CharBuffer"); 1193 } 1194 1195 CharBuffer buffer = CharBuffer.wrap( 1196 translateBytesToString(bytes,format, localeTransferable)); 1197 1198 theObject = constructFlavoredObject(buffer, flavor, CharBuffer.class); 1199 1200 // Target data is a char array. Recur to obtain String and convert to 1201 // char array. 1202 } else if (char[].class.equals(flavor.getRepresentationClass())) { 1203 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1204 throw new IOException 1205 ("cannot transfer non-text data as char array"); 1206 } 1207 1208 theObject = translateBytesToString( 1209 bytes, format, localeTransferable).toCharArray(); 1210 1211 // Target data is a ByteBuffer. For arbitrary flavors, just return 1212 // the raw bytes. For text flavors, convert to a String to strip 1213 // terminators and search-and-replace EOLN, then reencode according to 1214 // the requested flavor. 1215 } else if (flavor.isRepresentationClassByteBuffer()) { 1216 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1217 bytes = translateBytesToString( 1218 bytes, format, localeTransferable).getBytes( 1219 DataFlavorUtil.getTextCharset(flavor) 1220 ); 1221 } 1222 1223 ByteBuffer buffer = ByteBuffer.wrap(bytes); 1224 theObject = constructFlavoredObject(buffer, flavor, ByteBuffer.class); 1225 1226 // Target data is a byte array. For arbitrary flavors, just return 1227 // the raw bytes. For text flavors, convert to a String to strip 1228 // terminators and search-and-replace EOLN, then reencode according to 1229 // the requested flavor. 1230 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 1231 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1232 theObject = translateBytesToString( 1233 bytes, format, localeTransferable 1234 ).getBytes(DataFlavorUtil.getTextCharset(flavor)); 1235 } else { 1236 theObject = bytes; 1237 } 1238 1239 // Target data is an InputStream. For arbitrary flavors, just return 1240 // the raw bytes. For text flavors, decode to strip terminators and 1241 // search-and-replace EOLN, then reencode according to the requested 1242 // flavor. 1243 } else if (flavor.isRepresentationClassInputStream()) { 1244 1245 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1246 theObject = translateStream(bais, flavor, format, localeTransferable); 1247 } 1248 1249 } else if (flavor.isRepresentationClassRemote()) { 1250 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 1251 ObjectInputStream ois = new ObjectInputStream(bais)) 1252 { 1253 RMIAccessService service = getRMIAccessService(); 1254 if (service != null) { 1255 theObject = service.getMarshalledObject(ois.readObject()); 1256 } 1257 // If no RMI is present we could not get here 1258 } catch (Exception e) { 1259 throw new IOException(e.getMessage()); 1260 } 1261 1262 // Target data is Serializable 1263 } else if (flavor.isRepresentationClassSerializable()) { 1264 1265 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1266 theObject = translateStream(bais, flavor, format, localeTransferable); 1267 } 1268 1269 // Target data is Image 1270 } else if (DataFlavor.imageFlavor.equals(flavor)) { 1271 if (!isImageFormat(format)) { 1272 throw new IOException("data translation failed"); 1273 } 1274 1275 theObject = platformImageBytesToImage(bytes, format); 1276 } 1277 1278 if (theObject == null) { 1279 throw new IOException("data translation failed"); 1280 } 1281 1282 return theObject; 1283 1284 } 1285 1286 /** 1287 * Primary translation function for translating 1288 * an InputStream into an Object, given a source format and a target 1289 * DataFlavor. 1290 */ 1291 public Object translateStream(InputStream str, DataFlavor flavor, 1292 long format, Transferable localeTransferable) 1293 throws IOException 1294 { 1295 1296 Object theObject = null; 1297 // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor 1298 // where possible. 1299 if (isURIListFormat(format) 1300 && DataFlavor.javaFileListFlavor.equals(flavor)) 1301 { 1302 1303 URI uris[] = dragQueryURIs(str, format, localeTransferable); 1304 if (uris == null) { 1305 return null; 1306 } 1307 List<File> files = new ArrayList<>(); 1308 for (URI uri : uris) { 1309 try { 1310 files.add(new File(uri)); 1311 } catch (IllegalArgumentException illegalArg) { 1312 // When converting from URIs to less generic files, 1313 // common practice (Wine, SWT) seems to be to 1314 // silently drop the URIs that aren't local files. 1315 } 1316 } 1317 theObject = files; 1318 1319 // Target data is a String. Strip terminating NUL bytes. Decode bytes 1320 // into characters. Search-and-replace EOLN. 1321 } else if (String.class.equals(flavor.getRepresentationClass()) && 1322 DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1323 1324 return translateBytesToString(inputStreamToByteArray(str), 1325 format, localeTransferable); 1326 1327 // Special hack to maintain backwards-compatibility with the brokenness 1328 // of StringSelection. Return a StringReader instead of an InputStream. 1329 // Recur to obtain String and encapsulate. 1330 } else if (DataFlavor.plainTextFlavor.equals(flavor)) { 1331 theObject = new StringReader(translateBytesToString( 1332 inputStreamToByteArray(str), 1333 format, localeTransferable)); 1334 1335 // Target data is an InputStream. For arbitrary flavors, just return 1336 // the raw bytes. For text flavors, decode to strip terminators and 1337 // search-and-replace EOLN, then reencode according to the requested 1338 // flavor. 1339 } else if (flavor.isRepresentationClassInputStream()) { 1340 theObject = translateStreamToInputStream(str, flavor, format, 1341 localeTransferable); 1342 1343 // Target data is a Reader. Obtain data in InputStream format, encoded 1344 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode 1345 // back to chars on demand. 1346 } else if (flavor.isRepresentationClassReader()) { 1347 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1348 throw new IOException 1349 ("cannot transfer non-text data as Reader"); 1350 } 1351 1352 InputStream is = (InputStream)translateStreamToInputStream( 1353 str, DataFlavor.plainTextFlavor, 1354 format, localeTransferable); 1355 1356 String unicode = DataFlavorUtil.getTextCharset(DataFlavor.plainTextFlavor); 1357 1358 Reader reader = new InputStreamReader(is, unicode); 1359 1360 theObject = constructFlavoredObject(reader, flavor, Reader.class); 1361 // Target data is a byte array 1362 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 1363 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1364 theObject = translateBytesToString(inputStreamToByteArray(str), format, localeTransferable) 1365 .getBytes(DataFlavorUtil.getTextCharset(flavor)); 1366 } else { 1367 theObject = inputStreamToByteArray(str); 1368 } 1369 // Target data is an RMI object 1370 } else if (flavor.isRepresentationClassRemote()) { 1371 1372 try (ObjectInputStream ois = 1373 new ObjectInputStream(str)) 1374 { 1375 RMIAccessService service = getRMIAccessService(); 1376 if (service != null) { 1377 theObject = service.getMarshalledObject(ois.readObject()); 1378 } 1379 // If not RMI is present we could not get here 1380 } catch (Exception e) { 1381 throw new IOException(e.getMessage()); 1382 } 1383 1384 // Target data is Serializable 1385 } else if (flavor.isRepresentationClassSerializable()) { 1386 try (ObjectInputStream ois = 1387 new ObjectInputStream(str)) 1388 { 1389 theObject = ois.readObject(); 1390 } catch (Exception e) { 1391 throw new IOException(e.getMessage()); 1392 } 1393 // Target data is Image 1394 } else if (DataFlavor.imageFlavor.equals(flavor)) { 1395 if (!isImageFormat(format)) { 1396 throw new IOException("data translation failed"); 1397 } 1398 theObject = platformImageBytesToImage(inputStreamToByteArray(str), format); 1399 } 1400 1401 if (theObject == null) { 1402 throw new IOException("data translation failed"); 1403 } 1404 1405 return theObject; 1406 1407 } 1408 1409 /** 1410 * For arbitrary flavors, just use the raw InputStream. For text flavors, 1411 * ReencodingInputStream will decode and reencode the InputStream on demand 1412 * so that we can strip terminators and search-and-replace EOLN. 1413 */ 1414 private Object translateStreamToInputStream 1415 (InputStream str, DataFlavor flavor, long format, 1416 Transferable localeTransferable) throws IOException 1417 { 1418 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1419 str = new ReencodingInputStream 1420 (str, format, DataFlavorUtil.getTextCharset(flavor), 1421 localeTransferable); 1422 } 1423 1424 return constructFlavoredObject(str, flavor, InputStream.class); 1425 } 1426 1427 /** 1428 * We support representations which are exactly of the specified Class, 1429 * and also arbitrary Objects which have a constructor which takes an 1430 * instance of the Class as its sole parameter. 1431 */ 1432 private Object constructFlavoredObject(Object arg, DataFlavor flavor, 1433 Class<?> clazz) 1434 throws IOException 1435 { 1436 final Class<?> dfrc = flavor.getRepresentationClass(); 1437 1438 if (clazz.equals(dfrc)) { 1439 return arg; // simple case 1440 } else { 1441 Constructor<?>[] constructors; 1442 1443 try { 1444 constructors = AccessController.doPrivileged( 1445 (PrivilegedAction<Constructor<?>[]>) dfrc::getConstructors); 1446 } catch (SecurityException se) { 1447 throw new IOException(se.getMessage()); 1448 } 1449 1450 Constructor<?> constructor = Stream.of(constructors) 1451 .filter(c -> Modifier.isPublic(c.getModifiers())) 1452 .filter(c -> { 1453 Class<?>[] ptypes = c.getParameterTypes(); 1454 return ptypes != null 1455 && ptypes.length == 1 1456 && clazz.equals(ptypes[0]); 1457 }) 1458 .findFirst() 1459 .orElseThrow(() -> 1460 new IOException("can't find <init>(L"+ clazz + ";)V for class: " + dfrc.getName())); 1461 1462 try { 1463 return constructor.newInstance(arg); 1464 } catch (Exception e) { 1465 throw new IOException(e.getMessage()); 1466 } 1467 } 1468 } 1469 1470 /** 1471 * Used for decoding and reencoding an InputStream on demand so that we 1472 * can strip NUL terminators and perform EOLN search-and-replace. 1473 */ 1474 public class ReencodingInputStream extends InputStream { 1475 BufferedReader wrapped; 1476 final char[] in = new char[2]; 1477 byte[] out; 1478 1479 CharsetEncoder encoder; 1480 CharBuffer inBuf; 1481 ByteBuffer outBuf; 1482 1483 char[] eoln; 1484 int numTerminators; 1485 1486 boolean eos; 1487 int index, limit; 1488 1489 public ReencodingInputStream(InputStream bytestream, long format, 1490 String targetEncoding, 1491 Transferable localeTransferable) 1492 throws IOException 1493 { 1494 Long lFormat = format; 1495 1496 String sourceEncoding = getBestCharsetForTextFormat(format, localeTransferable); 1497 wrapped = new BufferedReader(new InputStreamReader(bytestream, sourceEncoding)); 1498 1499 if (targetEncoding == null) { 1500 // Throw NullPointerException for compatibility with the former 1501 // call to sun.io.CharToByteConverter.getConverter(null) 1502 // (Charset.forName(null) throws unspecified IllegalArgumentException 1503 // now; see 6228568) 1504 throw new NullPointerException("null target encoding"); 1505 } 1506 1507 try { 1508 encoder = Charset.forName(targetEncoding).newEncoder(); 1509 out = new byte[(int)(encoder.maxBytesPerChar() * 2 + 0.5)]; 1510 inBuf = CharBuffer.wrap(in); 1511 outBuf = ByteBuffer.wrap(out); 1512 } catch (IllegalCharsetNameException 1513 | UnsupportedCharsetException 1514 | UnsupportedOperationException e) { 1515 throw new IOException(e.toString()); 1516 } 1517 1518 String sEoln = nativeEOLNs.get(lFormat); 1519 if (sEoln != null) { 1520 eoln = sEoln.toCharArray(); 1521 } 1522 1523 // A hope and a prayer that this works generically. This will 1524 // definitely work on Win32. 1525 Integer terminators = nativeTerminators.get(lFormat); 1526 if (terminators != null) { 1527 numTerminators = terminators; 1528 } 1529 } 1530 1531 private int readChar() throws IOException { 1532 int c = wrapped.read(); 1533 1534 if (c == -1) { // -1 is EOS 1535 eos = true; 1536 return -1; 1537 } 1538 1539 // "c == 0" is not quite correct, but good enough on Windows. 1540 if (numTerminators > 0 && c == 0) { 1541 eos = true; 1542 return -1; 1543 } else if (eoln != null && matchCharArray(eoln, c)) { 1544 c = '\n' & 0xFFFF; 1545 } 1546 1547 return c; 1548 } 1549 1550 public int read() throws IOException { 1551 if (eos) { 1552 return -1; 1553 } 1554 1555 if (index >= limit) { 1556 // deal with supplementary characters 1557 int c = readChar(); 1558 if (c == -1) { 1559 return -1; 1560 } 1561 1562 in[0] = (char) c; 1563 in[1] = 0; 1564 inBuf.limit(1); 1565 if (Character.isHighSurrogate((char) c)) { 1566 c = readChar(); 1567 if (c != -1) { 1568 in[1] = (char) c; 1569 inBuf.limit(2); 1570 } 1571 } 1572 1573 inBuf.rewind(); 1574 outBuf.limit(out.length).rewind(); 1575 encoder.encode(inBuf, outBuf, false); 1576 outBuf.flip(); 1577 limit = outBuf.limit(); 1578 1579 index = 0; 1580 1581 return read(); 1582 } else { 1583 return out[index++] & 0xFF; 1584 } 1585 } 1586 1587 public int available() throws IOException { 1588 return ((eos) ? 0 : (limit - index)); 1589 } 1590 1591 public void close() throws IOException { 1592 wrapped.close(); 1593 } 1594 1595 /** 1596 * Checks to see if the next array.length characters in wrapped 1597 * match array. The first character is provided as c. Subsequent 1598 * characters are read from wrapped itself. When this method returns, 1599 * the wrapped index may be different from what it was when this 1600 * method was called. 1601 */ 1602 private boolean matchCharArray(char[] array, int c) 1603 throws IOException 1604 { 1605 wrapped.mark(array.length); // BufferedReader supports mark 1606 1607 int count = 0; 1608 if ((char)c == array[0]) { 1609 for (count = 1; count < array.length; count++) { 1610 c = wrapped.read(); 1611 if (c == -1 || ((char)c) != array[count]) { 1612 break; 1613 } 1614 } 1615 } 1616 1617 if (count == array.length) { 1618 return true; 1619 } else { 1620 wrapped.reset(); 1621 return false; 1622 } 1623 } 1624 } 1625 1626 /** 1627 * Decodes a byte array into a set of String filenames. 1628 */ 1629 protected abstract String[] dragQueryFile(byte[] bytes); 1630 1631 /** 1632 * Decodes URIs from either a byte array or a stream. 1633 */ 1634 protected URI[] dragQueryURIs(InputStream stream, 1635 long format, 1636 Transferable localeTransferable) 1637 throws IOException 1638 { 1639 throw new IOException( 1640 new UnsupportedOperationException("not implemented on this platform")); 1641 } 1642 1643 /** 1644 * Translates either a byte array or an input stream which contain 1645 * platform-specific image data in the given format into an Image. 1646 */ 1647 1648 1649 protected abstract Image platformImageBytesToImage( 1650 byte[] bytes,long format) throws IOException; 1651 1652 /** 1653 * Translates either a byte array or an input stream which contain 1654 * an image data in the given standard format into an Image. 1655 * 1656 * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif 1657 */ 1658 protected Image standardImageBytesToImage( 1659 byte[] bytes, String mimeType) throws IOException 1660 { 1661 1662 Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByMIMEType(mimeType); 1663 1664 if (!readerIterator.hasNext()) { 1665 throw new IOException("No registered service provider can decode " + 1666 " an image from " + mimeType); 1667 } 1668 1669 IOException ioe = null; 1670 1671 while (readerIterator.hasNext()) { 1672 ImageReader imageReader = readerIterator.next(); 1673 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1674 try (ImageInputStream imageInputStream = ImageIO.createImageInputStream(bais)) { 1675 ImageReadParam param = imageReader.getDefaultReadParam(); 1676 imageReader.setInput(imageInputStream, true, true); 1677 BufferedImage bufferedImage = imageReader.read(imageReader.getMinIndex(), param); 1678 if (bufferedImage != null) { 1679 return bufferedImage; 1680 } 1681 } finally { 1682 imageReader.dispose(); 1683 } 1684 } catch (IOException e) { 1685 ioe = e; 1686 continue; 1687 } 1688 } 1689 1690 if (ioe == null) { 1691 ioe = new IOException("Registered service providers failed to decode" 1692 + " an image from " + mimeType); 1693 } 1694 1695 throw ioe; 1696 } 1697 1698 /** 1699 * Translates a Java Image into a byte array which contains platform- 1700 * specific image data in the given format. 1701 */ 1702 protected abstract byte[] imageToPlatformBytes(Image image, long format) 1703 throws IOException; 1704 1705 /** 1706 * Translates a Java Image into a byte array which contains 1707 * an image data in the given standard format. 1708 * 1709 * @param mimeType image MIME type, such as: image/png, image/jpeg 1710 */ 1711 protected byte[] imageToStandardBytes(Image image, String mimeType) 1712 throws IOException { 1713 IOException originalIOE = null; 1714 1715 Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType); 1716 1717 if (!writerIterator.hasNext()) { 1718 throw new IOException("No registered service provider can encode " + 1719 " an image to " + mimeType); 1720 } 1721 1722 if (image instanceof RenderedImage) { 1723 // Try to encode the original image. 1724 try { 1725 return imageToStandardBytesImpl((RenderedImage)image, mimeType); 1726 } catch (IOException ioe) { 1727 originalIOE = ioe; 1728 } 1729 } 1730 1731 // Retry with a BufferedImage. 1732 int width = 0; 1733 int height = 0; 1734 if (image instanceof ToolkitImage) { 1735 ImageRepresentation ir = ((ToolkitImage)image).getImageRep(); 1736 ir.reconstruct(ImageObserver.ALLBITS); 1737 width = ir.getWidth(); 1738 height = ir.getHeight(); 1739 } else { 1740 width = image.getWidth(null); 1741 height = image.getHeight(null); 1742 } 1743 1744 ColorModel model = ColorModel.getRGBdefault(); 1745 WritableRaster raster = 1746 model.createCompatibleWritableRaster(width, height); 1747 1748 BufferedImage bufferedImage = 1749 new BufferedImage(model, raster, model.isAlphaPremultiplied(), 1750 null); 1751 1752 Graphics g = bufferedImage.getGraphics(); 1753 try { 1754 g.drawImage(image, 0, 0, width, height, null); 1755 } finally { 1756 g.dispose(); 1757 } 1758 1759 try { 1760 return imageToStandardBytesImpl(bufferedImage, mimeType); 1761 } catch (IOException ioe) { 1762 if (originalIOE != null) { 1763 throw originalIOE; 1764 } else { 1765 throw ioe; 1766 } 1767 } 1768 } 1769 1770 byte[] imageToStandardBytesImpl(RenderedImage renderedImage, 1771 String mimeType) 1772 throws IOException { 1773 1774 Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType); 1775 1776 ImageTypeSpecifier typeSpecifier = 1777 new ImageTypeSpecifier(renderedImage); 1778 1779 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1780 IOException ioe = null; 1781 1782 while (writerIterator.hasNext()) { 1783 ImageWriter imageWriter = writerIterator.next(); 1784 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider(); 1785 1786 if (!writerSpi.canEncodeImage(typeSpecifier)) { 1787 continue; 1788 } 1789 1790 try { 1791 try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(baos)) { 1792 imageWriter.setOutput(imageOutputStream); 1793 imageWriter.write(renderedImage); 1794 imageOutputStream.flush(); 1795 } 1796 } catch (IOException e) { 1797 imageWriter.dispose(); 1798 baos.reset(); 1799 ioe = e; 1800 continue; 1801 } 1802 1803 imageWriter.dispose(); 1804 baos.close(); 1805 return baos.toByteArray(); 1806 } 1807 1808 baos.close(); 1809 1810 if (ioe == null) { 1811 ioe = new IOException("Registered service providers failed to encode " 1812 + renderedImage + " to " + mimeType); 1813 } 1814 1815 throw ioe; 1816 } 1817 1818 /** 1819 * Concatenates the data represented by two objects. Objects can be either 1820 * byte arrays or instances of <code>InputStream</code>. If both arguments 1821 * are byte arrays byte array will be returned. Otherwise an 1822 * <code>InputStream</code> will be returned. 1823 * <p> 1824 * Currently is only called from native code to prepend palette data to 1825 * platform-specific image data during image transfer on Win32. 1826 * 1827 * @param obj1 the first object to be concatenated. 1828 * @param obj2 the second object to be concatenated. 1829 * @return a byte array or an <code>InputStream</code> which represents 1830 * a logical concatenation of the two arguments. 1831 * @throws NullPointerException is either of the arguments is 1832 * <code>null</code> 1833 * @throws ClassCastException is either of the arguments is 1834 * neither byte array nor an instance of <code>InputStream</code>. 1835 */ 1836 private Object concatData(Object obj1, Object obj2) { 1837 InputStream str1 = null; 1838 InputStream str2 = null; 1839 1840 if (obj1 instanceof byte[]) { 1841 byte[] arr1 = (byte[])obj1; 1842 if (obj2 instanceof byte[]) { 1843 byte[] arr2 = (byte[])obj2; 1844 byte[] ret = new byte[arr1.length + arr2.length]; 1845 System.arraycopy(arr1, 0, ret, 0, arr1.length); 1846 System.arraycopy(arr2, 0, ret, arr1.length, arr2.length); 1847 return ret; 1848 } else { 1849 str1 = new ByteArrayInputStream(arr1); 1850 str2 = (InputStream)obj2; 1851 } 1852 } else { 1853 str1 = (InputStream)obj1; 1854 if (obj2 instanceof byte[]) { 1855 str2 = new ByteArrayInputStream((byte[])obj2); 1856 } else { 1857 str2 = (InputStream)obj2; 1858 } 1859 } 1860 1861 return new SequenceInputStream(str1, str2); 1862 } 1863 1864 public byte[] convertData(final Object source, 1865 final Transferable contents, 1866 final long format, 1867 final Map<Long, DataFlavor> formatMap, 1868 final boolean isToolkitThread) 1869 throws IOException 1870 { 1871 byte[] ret = null; 1872 1873 /* 1874 * If the current thread is the Toolkit thread we should post a 1875 * Runnable to the event dispatch thread associated with source Object, 1876 * since translateTransferable() calls Transferable.getTransferData() 1877 * that may contain client code. 1878 */ 1879 if (isToolkitThread) try { 1880 final Stack<byte[]> stack = new Stack<>(); 1881 final Runnable dataConverter = new Runnable() { 1882 // Guard against multiple executions. 1883 private boolean done = false; 1884 public void run() { 1885 if (done) { 1886 return; 1887 } 1888 byte[] data = null; 1889 try { 1890 DataFlavor flavor = formatMap.get(format); 1891 if (flavor != null) { 1892 data = translateTransferable(contents, flavor, format); 1893 } 1894 } catch (Exception e) { 1895 e.printStackTrace(); 1896 data = null; 1897 } 1898 try { 1899 getToolkitThreadBlockedHandler().lock(); 1900 stack.push(data); 1901 getToolkitThreadBlockedHandler().exit(); 1902 } finally { 1903 getToolkitThreadBlockedHandler().unlock(); 1904 done = true; 1905 } 1906 } 1907 }; 1908 1909 final AppContext appContext = SunToolkit.targetToAppContext(source); 1910 1911 getToolkitThreadBlockedHandler().lock(); 1912 1913 if (appContext != null) { 1914 appContext.put(DATA_CONVERTER_KEY, dataConverter); 1915 } 1916 1917 SunToolkit.executeOnEventHandlerThread(source, dataConverter); 1918 1919 while (stack.empty()) { 1920 getToolkitThreadBlockedHandler().enter(); 1921 } 1922 1923 if (appContext != null) { 1924 appContext.remove(DATA_CONVERTER_KEY); 1925 } 1926 1927 ret = stack.pop(); 1928 } finally { 1929 getToolkitThreadBlockedHandler().unlock(); 1930 } else { 1931 DataFlavor flavor = formatMap.get(format); 1932 if (flavor != null) { 1933 ret = translateTransferable(contents, flavor, format); 1934 } 1935 } 1936 1937 return ret; 1938 } 1939 1940 public void processDataConversionRequests() { 1941 if (EventQueue.isDispatchThread()) { 1942 AppContext appContext = AppContext.getAppContext(); 1943 getToolkitThreadBlockedHandler().lock(); 1944 try { 1945 Runnable dataConverter = 1946 (Runnable)appContext.get(DATA_CONVERTER_KEY); 1947 if (dataConverter != null) { 1948 dataConverter.run(); 1949 appContext.remove(DATA_CONVERTER_KEY); 1950 } 1951 } finally { 1952 getToolkitThreadBlockedHandler().unlock(); 1953 } 1954 } 1955 } 1956 1957 public abstract ToolkitThreadBlockedHandler 1958 getToolkitThreadBlockedHandler(); 1959 1960 /** 1961 * Helper function to reduce a Map with Long keys to a long array. 1962 * <p> 1963 * The map keys are sorted according to the native formats preference 1964 * order. 1965 */ 1966 public static long[] keysToLongArray(SortedMap<Long, ?> map) { 1967 Set<Long> keySet = map.keySet(); 1968 long[] retval = new long[keySet.size()]; 1969 int i = 0; 1970 for (Iterator<Long> iter = keySet.iterator(); iter.hasNext(); i++) { 1971 retval[i] = iter.next(); 1972 } 1973 return retval; 1974 } 1975 1976 /** 1977 * Helper function to convert a Set of DataFlavors to a sorted array. 1978 * The array will be sorted according to <code>DataFlavorComparator</code>. 1979 */ 1980 public static DataFlavor[] setToSortedDataFlavorArray(Set<DataFlavor> flavorsSet) { 1981 DataFlavor[] flavors = new DataFlavor[flavorsSet.size()]; 1982 flavorsSet.toArray(flavors); 1983 final Comparator<DataFlavor> comparator = DataFlavorUtil.getDataFlavorComparator().reversed(); 1984 Arrays.sort(flavors, comparator); 1985 return flavors; 1986 } 1987 1988 /** 1989 * Helper function to convert an InputStream to a byte[] array. 1990 */ 1991 protected static byte[] inputStreamToByteArray(InputStream str) 1992 throws IOException 1993 { 1994 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 1995 int len = 0; 1996 byte[] buf = new byte[8192]; 1997 1998 while ((len = str.read(buf)) != -1) { 1999 baos.write(buf, 0, len); 2000 } 2001 2002 return baos.toByteArray(); 2003 } 2004 } 2005 2006 /** 2007 * Returns platform-specific mappings for the specified native. 2008 * If there are no platform-specific mappings for this native, the method 2009 * returns an empty <code>List</code>. 2010 */ 2011 public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) { 2012 return new LinkedHashSet<>(); 2013 } 2014 2015 /** 2016 * Returns platform-specific mappings for the specified flavor. 2017 * If there are no platform-specific mappings for this flavor, the method 2018 * returns an empty <code>List</code>. 2019 */ 2020 public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) { 2021 return new LinkedHashSet<>(); 2022 } 2023 2024 private static Optional<RMIAccessService> rmiAccessService; 2025 2026 private static RMIAccessService getRMIAccessService() { 2027 if (rmiAccessService == null) { 2028 ServiceLoader<RMIAccessService> loader = 2029 ServiceLoader.load(RMIAccessService.class, null); 2030 Iterator<RMIAccessService> iter = loader.iterator(); 2031 if (iter.hasNext()) { 2032 rmiAccessService = Optional.of(iter.next()); 2033 } else { 2034 rmiAccessService = Optional.empty(); 2035 } 2036 } 2037 return rmiAccessService.get(); 2038 } 2039 }