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