1 /* 2 * Copyright (c) 2003, 2019, 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 /* 27 ******************************************************************************* 28 * Copyright (C) 1996-2014, International Business Machines Corporation and 29 * others. All Rights Reserved. 30 ******************************************************************************* 31 */ 32 33 package sun.text.normalizer; 34 35 import java.io.BufferedInputStream; 36 import java.io.DataInputStream; 37 import java.io.InputStream; 38 import java.io.IOException; 39 import java.io.UncheckedIOException; 40 import java.nio.ByteBuffer; 41 import java.nio.ByteOrder; 42 import java.util.Arrays; 43 import java.security.AccessController; 44 import java.security.PrivilegedAction; 45 46 public final class ICUBinary { 47 48 private static final class IsAcceptable implements Authenticate { 49 @Override 50 public boolean isDataVersionAcceptable(byte version[]) { 51 return version[0] == 1; 52 } 53 } 54 55 // public inner interface ------------------------------------------------ 56 57 /** 58 * Special interface for data authentication 59 */ 60 public static interface Authenticate 61 { 62 /** 63 * Method used in ICUBinary.readHeader() to provide data format 64 * authentication. 65 * @param version version of the current data 66 * @return true if dataformat is an acceptable version, false otherwise 67 */ 68 public boolean isDataVersionAcceptable(byte version[]); 69 } 70 71 // public methods -------------------------------------------------------- 72 73 /** 74 * Loads an ICU binary data file and returns it as a ByteBuffer. 75 * The buffer contents is normally read-only, but its position etc. can be modified. 76 * 77 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 78 * @return The data as a read-only ByteBuffer. 79 */ 80 public static ByteBuffer getRequiredData(String itemPath) { 81 final Class<ICUBinary> root = ICUBinary.class; 82 83 try (InputStream is = AccessController.doPrivileged(new PrivilegedAction<InputStream>() { 84 public InputStream run() { 85 return root.getResourceAsStream(itemPath); 86 } 87 })) { 88 89 // is.available() may return 0, or 1, or the total number of bytes in the stream, 90 // or some other number. 91 // Do not try to use is.available() == 0 to find the end of the stream! 92 byte[] bytes; 93 int avail = is.available(); 94 if (avail > 32) { 95 // There are more bytes available than just the ICU data header length. 96 // With luck, it is the total number of bytes. 97 bytes = new byte[avail]; 98 } else { 99 bytes = new byte[128]; // empty .res files are even smaller 100 } 101 // Call is.read(...) until one returns a negative value. 102 int length = 0; 103 for(;;) { 104 if (length < bytes.length) { 105 int numRead = is.read(bytes, length, bytes.length - length); 106 if (numRead < 0) { 107 break; // end of stream 108 } 109 length += numRead; 110 } else { 111 // See if we are at the end of the stream before we grow the array. 112 int nextByte = is.read(); 113 if (nextByte < 0) { 114 break; 115 } 116 int capacity = 2 * bytes.length; 117 if (capacity < 128) { 118 capacity = 128; 119 } else if (capacity < 0x4000) { 120 capacity *= 2; // Grow faster until we reach 16kB. 121 } 122 bytes = Arrays.copyOf(bytes, capacity); 123 bytes[length++] = (byte) nextByte; 124 } 125 } 126 return ByteBuffer.wrap(bytes, 0, length); 127 } 128 catch (IOException e) { 129 throw new UncheckedIOException(e); 130 } 131 } 132 133 /** 134 * Same as readHeader(), but returns a VersionInfo rather than a compact int. 135 */ 136 public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes, 137 int dataFormat, 138 Authenticate authenticate) 139 throws IOException { 140 return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate)); 141 } 142 143 private static final byte BIG_ENDIAN_ = 1; 144 public static final byte[] readHeader(InputStream inputStream, 145 byte dataFormatIDExpected[], 146 Authenticate authenticate) 147 throws IOException 148 { 149 DataInputStream input = new DataInputStream(inputStream); 150 char headersize = input.readChar(); 151 int readcount = 2; 152 //reading the header format 153 byte magic1 = input.readByte(); 154 readcount ++; 155 byte magic2 = input.readByte(); 156 readcount ++; 157 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 158 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 159 } 160 161 input.readChar(); // reading size 162 readcount += 2; 163 input.readChar(); // reading reserved word 164 readcount += 2; 165 byte bigendian = input.readByte(); 166 readcount ++; 167 byte charset = input.readByte(); 168 readcount ++; 169 byte charsize = input.readByte(); 170 readcount ++; 171 input.readByte(); // reading reserved byte 172 readcount ++; 173 174 byte dataFormatID[] = new byte[4]; 175 input.readFully(dataFormatID); 176 readcount += 4; 177 byte dataVersion[] = new byte[4]; 178 input.readFully(dataVersion); 179 readcount += 4; 180 byte unicodeVersion[] = new byte[4]; 181 input.readFully(unicodeVersion); 182 readcount += 4; 183 if (headersize < readcount) { 184 throw new IOException("Internal Error: Header size error"); 185 } 186 input.skipBytes(headersize - readcount); 187 188 if (bigendian != BIG_ENDIAN_ || charset != CHAR_SET_ 189 || charsize != CHAR_SIZE_ 190 || !Arrays.equals(dataFormatIDExpected, dataFormatID) 191 || (authenticate != null 192 && !authenticate.isDataVersionAcceptable(dataVersion))) { 193 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 194 } 195 return unicodeVersion; 196 } 197 198 /** 199 * Reads an ICU data header, checks the data format, and returns the data version. 200 * 201 * <p>Assumes that the ByteBuffer position is 0 on input. 202 * The buffer byte order is set according to the data. 203 * The buffer position is advanced past the header (including UDataInfo and comment). 204 * 205 * <p>See C++ ucmndata.h and unicode/udata.h. 206 * 207 * @return dataVersion 208 * @throws IOException if this is not a valid ICU data item of the expected dataFormat 209 */ 210 public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate) 211 throws IOException { 212 assert bytes.position() == 0; 213 byte magic1 = bytes.get(2); 214 byte magic2 = bytes.get(3); 215 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 216 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 217 } 218 219 byte isBigEndian = bytes.get(8); 220 byte charsetFamily = bytes.get(9); 221 byte sizeofUChar = bytes.get(10); 222 if (isBigEndian < 0 || 1 < isBigEndian || 223 charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) { 224 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 225 } 226 bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); 227 228 int headerSize = bytes.getChar(0); 229 int sizeofUDataInfo = bytes.getChar(4); 230 if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) { 231 throw new IOException("Internal Error: Header size error"); 232 } 233 // TODO: Change Authenticate to take int major, int minor, int milli, int micro 234 // to avoid array allocation. 235 byte[] formatVersion = new byte[] { 236 bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19) 237 }; 238 if (bytes.get(12) != (byte)(dataFormat >> 24) || 239 bytes.get(13) != (byte)(dataFormat >> 16) || 240 bytes.get(14) != (byte)(dataFormat >> 8) || 241 bytes.get(15) != (byte)dataFormat || 242 (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) { 243 throw new IOException(HEADER_AUTHENTICATION_FAILED_ + 244 String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d", 245 bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15), 246 formatVersion[0] & 0xff, formatVersion[1] & 0xff, 247 formatVersion[2] & 0xff, formatVersion[3] & 0xff)); 248 } 249 250 bytes.position(headerSize); 251 return // dataVersion 252 ((int)bytes.get(20) << 24) | 253 ((bytes.get(21) & 0xff) << 16) | 254 ((bytes.get(22) & 0xff) << 8) | 255 (bytes.get(23) & 0xff); 256 } 257 258 public static void skipBytes(ByteBuffer bytes, int skipLength) { 259 if (skipLength > 0) { 260 bytes.position(bytes.position() + skipLength); 261 } 262 } 263 264 public static byte[] getBytes(ByteBuffer bytes, int length, int additionalSkipLength) { 265 byte[] dest = new byte[length]; 266 bytes.get(dest); 267 if (additionalSkipLength > 0) { 268 skipBytes(bytes, additionalSkipLength); 269 } 270 return dest; 271 } 272 273 public static String getString(ByteBuffer bytes, int length, int additionalSkipLength) { 274 CharSequence cs = bytes.asCharBuffer(); 275 String s = cs.subSequence(0, length).toString(); 276 skipBytes(bytes, length * 2 + additionalSkipLength); 277 return s; 278 } 279 280 public static char[] getChars(ByteBuffer bytes, int length, int additionalSkipLength) { 281 char[] dest = new char[length]; 282 bytes.asCharBuffer().get(dest); 283 skipBytes(bytes, length * 2 + additionalSkipLength); 284 return dest; 285 } 286 287 public static int[] getInts(ByteBuffer bytes, int length, int additionalSkipLength) { 288 int[] dest = new int[length]; 289 bytes.asIntBuffer().get(dest); 290 skipBytes(bytes, length * 4 + additionalSkipLength); 291 return dest; 292 } 293 294 /** 295 * Returns a VersionInfo for the bytes in the compact version integer. 296 */ 297 public static VersionInfo getVersionInfoFromCompactInt(int version) { 298 return VersionInfo.getInstance( 299 version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); 300 } 301 302 // private variables ------------------------------------------------- 303 304 /** 305 * Magic numbers to authenticate the data file 306 */ 307 private static final byte MAGIC1 = (byte)0xda; 308 private static final byte MAGIC2 = (byte)0x27; 309 310 /** 311 * File format authentication values 312 */ 313 private static final byte CHAR_SET_ = 0; 314 private static final byte CHAR_SIZE_ = 2; 315 316 /** 317 * Error messages 318 */ 319 private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ = 320 "ICUBinary data file error: Magic number authentication failed"; 321 private static final String HEADER_AUTHENTICATION_FAILED_ = 322 "ICUBinary data file error: Header authentication failed"; 323 }