1 /* 2 * Copyright (c) 2003, 2018, 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 BufferedInputStream b=new BufferedInputStream(is, 4096 /* data buffer size */); 90 DataInputStream inputStream = new DataInputStream(b); 91 byte[] bb = new byte[130000]; 92 int n = inputStream.read(bb); 93 ByteBuffer bytes = ByteBuffer.wrap(bb, 0, n); 94 return bytes; 95 } 96 catch (IOException e) { 97 throw new UncheckedIOException(e); 98 } 99 } 100 101 /** 102 * Same as readHeader(), but returns a VersionInfo rather than a compact int. 103 */ 104 public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes, 105 int dataFormat, 106 Authenticate authenticate) 107 throws IOException { 108 return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate)); 109 } 110 111 private static final byte BIG_ENDIAN_ = 1; 112 public static final byte[] readHeader(InputStream inputStream, 113 byte dataFormatIDExpected[], 114 Authenticate authenticate) 115 throws IOException 116 { 117 DataInputStream input = new DataInputStream(inputStream); 118 char headersize = input.readChar(); 119 int readcount = 2; 120 //reading the header format 121 byte magic1 = input.readByte(); 122 readcount ++; 123 byte magic2 = input.readByte(); 124 readcount ++; 125 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 126 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 127 } 128 129 input.readChar(); // reading size 130 readcount += 2; 131 input.readChar(); // reading reserved word 132 readcount += 2; 133 byte bigendian = input.readByte(); 134 readcount ++; 135 byte charset = input.readByte(); 136 readcount ++; 137 byte charsize = input.readByte(); 138 readcount ++; 139 input.readByte(); // reading reserved byte 140 readcount ++; 141 142 byte dataFormatID[] = new byte[4]; 143 input.readFully(dataFormatID); 144 readcount += 4; 145 byte dataVersion[] = new byte[4]; 146 input.readFully(dataVersion); 147 readcount += 4; 148 byte unicodeVersion[] = new byte[4]; 149 input.readFully(unicodeVersion); 150 readcount += 4; 151 if (headersize < readcount) { 152 throw new IOException("Internal Error: Header size error"); 153 } 154 input.skipBytes(headersize - readcount); 155 156 if (bigendian != BIG_ENDIAN_ || charset != CHAR_SET_ 157 || charsize != CHAR_SIZE_ 158 || !Arrays.equals(dataFormatIDExpected, dataFormatID) 159 || (authenticate != null 160 && !authenticate.isDataVersionAcceptable(dataVersion))) { 161 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 162 } 163 return unicodeVersion; 164 } 165 166 /** 167 * Reads an ICU data header, checks the data format, and returns the data version. 168 * 169 * <p>Assumes that the ByteBuffer position is 0 on input. 170 * The buffer byte order is set according to the data. 171 * The buffer position is advanced past the header (including UDataInfo and comment). 172 * 173 * <p>See C++ ucmndata.h and unicode/udata.h. 174 * 175 * @return dataVersion 176 * @throws IOException if this is not a valid ICU data item of the expected dataFormat 177 */ 178 public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate) 179 throws IOException { 180 assert bytes.position() == 0; 181 byte magic1 = bytes.get(2); 182 byte magic2 = bytes.get(3); 183 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 184 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 185 } 186 187 byte isBigEndian = bytes.get(8); 188 byte charsetFamily = bytes.get(9); 189 byte sizeofUChar = bytes.get(10); 190 if (isBigEndian < 0 || 1 < isBigEndian || 191 charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) { 192 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 193 } 194 bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); 195 196 int headerSize = bytes.getChar(0); 197 int sizeofUDataInfo = bytes.getChar(4); 198 if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) { 199 throw new IOException("Internal Error: Header size error"); 200 } 201 // TODO: Change Authenticate to take int major, int minor, int milli, int micro 202 // to avoid array allocation. 203 byte[] formatVersion = new byte[] { 204 bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19) 205 }; 206 if (bytes.get(12) != (byte)(dataFormat >> 24) || 207 bytes.get(13) != (byte)(dataFormat >> 16) || 208 bytes.get(14) != (byte)(dataFormat >> 8) || 209 bytes.get(15) != (byte)dataFormat || 210 (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) { 211 throw new IOException(HEADER_AUTHENTICATION_FAILED_ + 212 String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d", 213 bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15), 214 formatVersion[0] & 0xff, formatVersion[1] & 0xff, 215 formatVersion[2] & 0xff, formatVersion[3] & 0xff)); 216 } 217 218 bytes.position(headerSize); 219 return // dataVersion 220 ((int)bytes.get(20) << 24) | 221 ((bytes.get(21) & 0xff) << 16) | 222 ((bytes.get(22) & 0xff) << 8) | 223 (bytes.get(23) & 0xff); 224 } 225 226 public static void skipBytes(ByteBuffer bytes, int skipLength) { 227 if (skipLength > 0) { 228 bytes.position(bytes.position() + skipLength); 229 } 230 } 231 232 /** 233 * Returns a VersionInfo for the bytes in the compact version integer. 234 */ 235 public static VersionInfo getVersionInfoFromCompactInt(int version) { 236 return VersionInfo.getInstance( 237 version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); 238 } 239 240 // private variables ------------------------------------------------- 241 242 /** 243 * Magic numbers to authenticate the data file 244 */ 245 private static final byte MAGIC1 = (byte)0xda; 246 private static final byte MAGIC2 = (byte)0x27; 247 248 /** 249 * File format authentication values 250 */ 251 private static final byte CHAR_SET_ = 0; 252 private static final byte CHAR_SIZE_ = 2; 253 254 /** 255 * Error messages 256 */ 257 private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ = 258 "ICUBinary data file error: Magin number authentication failed"; 259 private static final String HEADER_AUTHENTICATION_FAILED_ = 260 "ICUBinary data file error: Header authentication failed"; 261 }