Path: blob/master/src/java.base/share/classes/jdk/internal/icu/impl/ICUBinary.java
41161 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26*******************************************************************************27* Copyright (C) 1996-2014, International Business Machines Corporation and28* others. All Rights Reserved.29*******************************************************************************30*/3132package jdk.internal.icu.impl;3334import java.io.DataInputStream;35import java.io.InputStream;36import java.io.IOException;37import java.io.UncheckedIOException;38import java.nio.ByteBuffer;39import java.nio.ByteOrder;40import java.util.Arrays;41import java.security.AccessController;42import java.security.PrivilegedAction;4344import jdk.internal.icu.util.VersionInfo;4546public final class ICUBinary {4748private static final class IsAcceptable implements Authenticate {49@Override50public boolean isDataVersionAcceptable(byte version[]) {51return version[0] == 1;52}53}5455// public inner interface ------------------------------------------------5657/**58* Special interface for data authentication59*/60public static interface Authenticate61{62/**63* Method used in ICUBinary.readHeader() to provide data format64* authentication.65* @param version version of the current data66* @return true if dataformat is an acceptable version, false otherwise67*/68public boolean isDataVersionAcceptable(byte version[]);69}7071// public methods --------------------------------------------------------7273/**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*/80public static ByteBuffer getRequiredData(String itemPath) {81final Class<ICUBinary> root = ICUBinary.class;8283try (@SuppressWarnings("removal") InputStream is = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {84public InputStream run() {85return root.getResourceAsStream(itemPath);86}87})) {8889// 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!92byte[] bytes;93int avail = is.available();94if (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.97bytes = new byte[avail];98} else {99bytes = new byte[128]; // empty .res files are even smaller100}101// Call is.read(...) until one returns a negative value.102int length = 0;103for(;;) {104if (length < bytes.length) {105int numRead = is.read(bytes, length, bytes.length - length);106if (numRead < 0) {107break; // end of stream108}109length += numRead;110} else {111// See if we are at the end of the stream before we grow the array.112int nextByte = is.read();113if (nextByte < 0) {114break;115}116int capacity = 2 * bytes.length;117if (capacity < 128) {118capacity = 128;119} else if (capacity < 0x4000) {120capacity *= 2; // Grow faster until we reach 16kB.121}122bytes = Arrays.copyOf(bytes, capacity);123bytes[length++] = (byte) nextByte;124}125}126return ByteBuffer.wrap(bytes, 0, length);127}128catch (IOException e) {129throw new UncheckedIOException(e);130}131}132133/**134* Same as readHeader(), but returns a VersionInfo rather than a compact int.135*/136public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes,137int dataFormat,138Authenticate authenticate)139throws IOException {140return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate));141}142143private static final byte BIG_ENDIAN_ = 1;144public static final byte[] readHeader(InputStream inputStream,145byte dataFormatIDExpected[],146Authenticate authenticate)147throws IOException148{149DataInputStream input = new DataInputStream(inputStream);150char headersize = input.readChar();151int readcount = 2;152//reading the header format153byte magic1 = input.readByte();154readcount ++;155byte magic2 = input.readByte();156readcount ++;157if (magic1 != MAGIC1 || magic2 != MAGIC2) {158throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_);159}160161input.readChar(); // reading size162readcount += 2;163input.readChar(); // reading reserved word164readcount += 2;165byte bigendian = input.readByte();166readcount ++;167byte charset = input.readByte();168readcount ++;169byte charsize = input.readByte();170readcount ++;171input.readByte(); // reading reserved byte172readcount ++;173174byte dataFormatID[] = new byte[4];175input.readFully(dataFormatID);176readcount += 4;177byte dataVersion[] = new byte[4];178input.readFully(dataVersion);179readcount += 4;180byte unicodeVersion[] = new byte[4];181input.readFully(unicodeVersion);182readcount += 4;183if (headersize < readcount) {184throw new IOException("Internal Error: Header size error");185}186input.skipBytes(headersize - readcount);187188if (bigendian != BIG_ENDIAN_ || charset != CHAR_SET_189|| charsize != CHAR_SIZE_190|| !Arrays.equals(dataFormatIDExpected, dataFormatID)191|| (authenticate != null192&& !authenticate.isDataVersionAcceptable(dataVersion))) {193throw new IOException(HEADER_AUTHENTICATION_FAILED_);194}195return unicodeVersion;196}197198/**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 dataVersion208* @throws IOException if this is not a valid ICU data item of the expected dataFormat209*/210public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate)211throws IOException {212assert bytes.position() == 0;213byte magic1 = bytes.get(2);214byte magic2 = bytes.get(3);215if (magic1 != MAGIC1 || magic2 != MAGIC2) {216throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_);217}218219byte isBigEndian = bytes.get(8);220byte charsetFamily = bytes.get(9);221byte sizeofUChar = bytes.get(10);222if (isBigEndian < 0 || 1 < isBigEndian ||223charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) {224throw new IOException(HEADER_AUTHENTICATION_FAILED_);225}226bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);227228int headerSize = bytes.getChar(0);229int sizeofUDataInfo = bytes.getChar(4);230if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) {231throw new IOException("Internal Error: Header size error");232}233// TODO: Change Authenticate to take int major, int minor, int milli, int micro234// to avoid array allocation.235byte[] formatVersion = new byte[] {236bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19)237};238if (bytes.get(12) != (byte)(dataFormat >> 24) ||239bytes.get(13) != (byte)(dataFormat >> 16) ||240bytes.get(14) != (byte)(dataFormat >> 8) ||241bytes.get(15) != (byte)dataFormat ||242(authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) {243throw new IOException(HEADER_AUTHENTICATION_FAILED_ +244String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d",245bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15),246formatVersion[0] & 0xff, formatVersion[1] & 0xff,247formatVersion[2] & 0xff, formatVersion[3] & 0xff));248}249250bytes.position(headerSize);251return // dataVersion252((int)bytes.get(20) << 24) |253((bytes.get(21) & 0xff) << 16) |254((bytes.get(22) & 0xff) << 8) |255(bytes.get(23) & 0xff);256}257258public static void skipBytes(ByteBuffer bytes, int skipLength) {259if (skipLength > 0) {260bytes.position(bytes.position() + skipLength);261}262}263264public static byte[] getBytes(ByteBuffer bytes, int length, int additionalSkipLength) {265byte[] dest = new byte[length];266bytes.get(dest);267if (additionalSkipLength > 0) {268skipBytes(bytes, additionalSkipLength);269}270return dest;271}272273public static String getString(ByteBuffer bytes, int length, int additionalSkipLength) {274CharSequence cs = bytes.asCharBuffer();275String s = cs.subSequence(0, length).toString();276skipBytes(bytes, length * 2 + additionalSkipLength);277return s;278}279280public static char[] getChars(ByteBuffer bytes, int length, int additionalSkipLength) {281char[] dest = new char[length];282bytes.asCharBuffer().get(dest);283skipBytes(bytes, length * 2 + additionalSkipLength);284return dest;285}286287public static int[] getInts(ByteBuffer bytes, int length, int additionalSkipLength) {288int[] dest = new int[length];289bytes.asIntBuffer().get(dest);290skipBytes(bytes, length * 4 + additionalSkipLength);291return dest;292}293294/**295* Returns a VersionInfo for the bytes in the compact version integer.296*/297public static VersionInfo getVersionInfoFromCompactInt(int version) {298return VersionInfo.getInstance(299version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);300}301302// private variables -------------------------------------------------303304/**305* Magic numbers to authenticate the data file306*/307private static final byte MAGIC1 = (byte)0xda;308private static final byte MAGIC2 = (byte)0x27;309310/**311* File format authentication values312*/313private static final byte CHAR_SET_ = 0;314private static final byte CHAR_SIZE_ = 2;315316/**317* Error messages318*/319private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ =320"ICUBinary data file error: Magic number authentication failed";321private static final String HEADER_AUTHENTICATION_FAILED_ =322"ICUBinary data file error: Header authentication failed";323}324325326