1 /* 2 * Copyright (c) 2003, 2015, 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.File; 38 import java.io.FileInputStream; 39 import java.io.InputStream; 40 import java.io.IOException; 41 import java.io.UncheckedIOException; 42 import java.net.URL; 43 import java.nio.ByteBuffer; 44 import java.nio.ByteOrder; 45 import java.nio.channels.FileChannel; 46 import java.nio.file.FileSystems; 47 import java.util.Arrays; 48 import java.security.AccessController; 49 import java.security.PrivilegedAction; 50 51 public final class ICUBinary { 52 53 private static final class IsAcceptable implements Authenticate { 54 // @Override when we switch to Java 6 55 public boolean isDataVersionAcceptable(byte version[]) { 56 return version[0] == 1; 57 } 58 } 59 60 // public inner interface ------------------------------------------------ 61 62 /** 63 * Special interface for data authentication 64 */ 65 public static interface Authenticate 66 { 67 /** 68 * Method used in ICUBinary.readHeader() to provide data format 69 * authentication. 70 * @param version version of the current data 71 * @return true if dataformat is an acceptable version, false otherwise 72 */ 73 public boolean isDataVersionAcceptable(byte version[]); 74 } 75 76 // public methods -------------------------------------------------------- 77 78 /** 79 * Loads an ICU binary data file and returns it as a ByteBuffer. 80 * The buffer contents is normally read-only, but its position etc. can be modified. 81 * 82 * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". 83 * @return The data as a read-only ByteBuffer. 84 */ 85 public static ByteBuffer getRequiredData(String itemPath) { 86 final Class<ICUBinary> root = ICUBinary.class; 87 88 try (InputStream is = AccessController.doPrivileged(new PrivilegedAction<InputStream>() { 89 public InputStream run() { 90 return root.getResourceAsStream(itemPath); 91 } 92 })) { 93 94 BufferedInputStream b=new BufferedInputStream(is, 4096 /* data buffer size */); 95 DataInputStream inputStream = new DataInputStream(b); 96 byte[] bb = new byte[120000]; 97 int n = inputStream.read(bb); 98 ByteBuffer bytes = ByteBuffer.wrap(bb, 0, n); 99 return bytes; 100 } 101 catch (IOException e) { 102 throw new UncheckedIOException(e); 103 } 104 } 105 106 /** 107 * Same as readHeader(), but returns a VersionInfo rather than a compact int. 108 */ 109 public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes, 110 int dataFormat, 111 Authenticate authenticate) 112 throws IOException { 113 return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate)); 114 } 115 116 private static final byte BIG_ENDIAN_ = 1; 117 public static final byte[] readHeader(InputStream inputStream, 118 byte dataFormatIDExpected[], 119 Authenticate authenticate) 120 throws IOException 121 { 122 DataInputStream input = new DataInputStream(inputStream); 123 char headersize = input.readChar(); 124 int readcount = 2; 125 //reading the header format 126 byte magic1 = input.readByte(); 127 readcount ++; 128 byte magic2 = input.readByte(); 129 readcount ++; 130 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 131 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 132 } 133 134 input.readChar(); // reading size 135 readcount += 2; 136 input.readChar(); // reading reserved word 137 readcount += 2; 138 byte bigendian = input.readByte(); 139 readcount ++; 140 byte charset = input.readByte(); 141 readcount ++; 142 byte charsize = input.readByte(); 143 readcount ++; 144 input.readByte(); // reading reserved byte 145 readcount ++; 146 147 byte dataFormatID[] = new byte[4]; 148 input.readFully(dataFormatID); 149 readcount += 4; 150 byte dataVersion[] = new byte[4]; 151 input.readFully(dataVersion); 152 readcount += 4; 153 byte unicodeVersion[] = new byte[4]; 154 input.readFully(unicodeVersion); 155 readcount += 4; 156 if (headersize < readcount) { 157 throw new IOException("Internal Error: Header size error"); 158 } 159 input.skipBytes(headersize - readcount); 160 161 if (bigendian != BIG_ENDIAN_ || charset != CHAR_SET_ 162 || charsize != CHAR_SIZE_ 163 || !Arrays.equals(dataFormatIDExpected, dataFormatID) 164 || (authenticate != null 165 && !authenticate.isDataVersionAcceptable(dataVersion))) { 166 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 167 } 168 return unicodeVersion; 169 } 170 171 /** 172 * Reads an ICU data header, checks the data format, and returns the data version. 173 * 174 * <p>Assumes that the ByteBuffer position is 0 on input. 175 * The buffer byte order is set according to the data. 176 * The buffer position is advanced past the header (including UDataInfo and comment). 177 * 178 * <p>See C++ ucmndata.h and unicode/udata.h. 179 * 180 * @return dataVersion 181 * @throws IOException if this is not a valid ICU data item of the expected dataFormat 182 */ 183 public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate) 184 throws IOException { 185 assert bytes.position() == 0; 186 byte magic1 = bytes.get(2); 187 byte magic2 = bytes.get(3); 188 if (magic1 != MAGIC1 || magic2 != MAGIC2) { 189 throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); 190 } 191 192 byte isBigEndian = bytes.get(8); 193 byte charsetFamily = bytes.get(9); 194 byte sizeofUChar = bytes.get(10); 195 if (isBigEndian < 0 || 1 < isBigEndian || 196 charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) { 197 throw new IOException(HEADER_AUTHENTICATION_FAILED_); 198 } 199 bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); 200 201 int headerSize = bytes.getChar(0); 202 int sizeofUDataInfo = bytes.getChar(4); 203 if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) { 204 throw new IOException("Internal Error: Header size error"); 205 } 206 // TODO: Change Authenticate to take int major, int minor, int milli, int micro 207 // to avoid array allocation. 208 byte[] formatVersion = new byte[] { 209 bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19) 210 }; 211 if (bytes.get(12) != (byte)(dataFormat >> 24) || 212 bytes.get(13) != (byte)(dataFormat >> 16) || 213 bytes.get(14) != (byte)(dataFormat >> 8) || 214 bytes.get(15) != (byte)dataFormat || 215 (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) { 216 throw new IOException(HEADER_AUTHENTICATION_FAILED_ + 217 String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d", 218 bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15), 219 formatVersion[0] & 0xff, formatVersion[1] & 0xff, 220 formatVersion[2] & 0xff, formatVersion[3] & 0xff)); 221 } 222 223 bytes.position(headerSize); 224 return // dataVersion 225 ((int)bytes.get(20) << 24) | 226 ((bytes.get(21) & 0xff) << 16) | 227 ((bytes.get(22) & 0xff) << 8) | 228 (bytes.get(23) & 0xff); 229 } 230 231 public static void skipBytes(ByteBuffer bytes, int skipLength) { 232 if (skipLength > 0) { 233 bytes.position(bytes.position() + skipLength); 234 } 235 } 236 237 /** 238 * Returns a VersionInfo for the bytes in the compact version integer. 239 */ 240 public static VersionInfo getVersionInfoFromCompactInt(int version) { 241 return VersionInfo.getInstance( 242 version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); 243 } 244 245 // private variables ------------------------------------------------- 246 247 /** 248 * Magic numbers to authenticate the data file 249 */ 250 private static final byte MAGIC1 = (byte)0xda; 251 private static final byte MAGIC2 = (byte)0x27; 252 253 /** 254 * File format authentication values 255 */ 256 private static final byte CHAR_SET_ = 0; 257 private static final byte CHAR_SIZE_ = 2; 258 259 /** 260 * Error messages 261 */ 262 private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ = 263 "ICUBinary data file error: Magin number authentication failed"; 264 private static final String HEADER_AUTHENTICATION_FAILED_ = 265 "ICUBinary data file error: Header authentication failed"; 266 }