1 /* 2 * Copyright (c) 2009, 2021, 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 package java.util.zip; 27 28 import java.nio.ByteBuffer; 29 import java.nio.CharBuffer; 30 import java.nio.DirectByteBuffer; 31 import java.nio.charset.Charset; 32 import java.nio.charset.CharsetDecoder; 33 import java.nio.charset.CharsetEncoder; 34 import java.nio.charset.CharacterCodingException; 35 import java.nio.charset.CodingErrorAction; 36 import java.nio.charset.StandardCharsets; 37 import java.util.Arrays; 38 39 /** 40 * Utility class for zipfile name and comment decoding and encoding 41 */ 42 class ZipCoder { 43 44 // Android-removed: 45 // private static final jdk.internal.access.JavaLangAccess JLA = 46 // jdk.internal.access.SharedSecrets.getJavaLangAccess(); 47 48 // Encoding/decoding is stateless, so make it singleton. 49 // Android-changed: use StandardCharsets. 50 // static final UTF8ZipCoder UTF8 = new UTF8ZipCoder(UTF_8.INSTANCE); 51 static final UTF8ZipCoder UTF8 = new UTF8ZipCoder(StandardCharsets.UTF_8); 52 get(Charset charset)53 public static ZipCoder get(Charset charset) { 54 // Android-changed: use equals method, not reference comparison. 55 // if (charset == UTF_8.INSTANCE) { 56 if (StandardCharsets.UTF_8.equals(charset)) { 57 return UTF8; 58 } 59 return new ZipCoder(charset); 60 } 61 toString(byte[] ba, int off, int length)62 String toString(byte[] ba, int off, int length) { 63 try { 64 return decoder().decode(ByteBuffer.wrap(ba, off, length)).toString(); 65 } catch (CharacterCodingException x) { 66 throw new IllegalArgumentException(x); 67 } 68 } 69 toString(byte[] ba, int length)70 String toString(byte[] ba, int length) { 71 return toString(ba, 0, length); 72 } 73 toString(byte[] ba)74 String toString(byte[] ba) { 75 return toString(ba, 0, ba.length); 76 } 77 78 // Android-changed: don't keep CEN bytes in heap memory after initialization. toString(DirectByteBuffer bb, int off, int length)79 String toString(DirectByteBuffer bb, int off, int length) { 80 try { 81 return decoder().decode(bb.slice(off, length)).toString(); 82 } catch (CharacterCodingException x) { 83 throw new IllegalArgumentException(x); 84 } 85 } 86 getBytes(String s)87 byte[] getBytes(String s) { 88 try { 89 ByteBuffer bb = encoder().encode(CharBuffer.wrap(s)); 90 int pos = bb.position(); 91 int limit = bb.limit(); 92 if (bb.hasArray() && pos == 0 && limit == bb.capacity()) { 93 return bb.array(); 94 } 95 byte[] bytes = new byte[bb.limit() - bb.position()]; 96 bb.get(bytes); 97 return bytes; 98 } catch (CharacterCodingException x) { 99 throw new IllegalArgumentException(x); 100 } 101 } 102 toStringUTF8(byte[] ba, int len)103 static String toStringUTF8(byte[] ba, int len) { 104 return UTF8.toString(ba, 0, len); 105 } 106 isUTF8()107 boolean isUTF8() { 108 return false; 109 } 110 111 // Hash code functions for ZipFile entry names. We generate the hash as-if 112 // we first decoded the byte sequence to a String, then appended '/' if no 113 // trailing slash was found, then called String.hashCode(). This 114 // normalization ensures we can simplify and speed up lookups. 115 // 116 // Does encoding error checking and hashing in a single pass for efficiency. 117 // On an error, this function will throw CharacterCodingException while the 118 // UTF8ZipCoder override will throw IllegalArgumentException, so we declare 119 // throws Exception to keep things simple. checkedHash(byte[] a, int off, int len)120 int checkedHash(byte[] a, int off, int len) throws Exception { 121 if (len == 0) { 122 return 0; 123 } 124 125 int h = 0; 126 // cb will be a newly allocated CharBuffer with pos == 0, 127 // arrayOffset == 0, backed by an array. 128 CharBuffer cb = decoder().decode(ByteBuffer.wrap(a, off, len)); 129 int limit = cb.limit(); 130 char[] decoded = cb.array(); 131 for (int i = 0; i < limit; i++) { 132 h = 31 * h + decoded[i]; 133 } 134 if (limit > 0 && decoded[limit - 1] != '/') { 135 h = 31 * h + '/'; 136 } 137 return h; 138 } 139 140 // Hash function equivalent of checkedHash for String inputs hash(String name)141 static int hash(String name) { 142 int hsh = name.hashCode(); 143 int len = name.length(); 144 if (len > 0 && name.charAt(len - 1) != '/') { 145 hsh = hsh * 31 + '/'; 146 } 147 return hsh; 148 } 149 hasTrailingSlash(byte[] a, int end)150 boolean hasTrailingSlash(byte[] a, int end) { 151 byte[] slashBytes = slashBytes(); 152 return end >= slashBytes.length && 153 Arrays.mismatch(a, end - slashBytes.length, end, slashBytes, 0, slashBytes.length) == -1; 154 } 155 156 // Android-changed: don't keep CEN bytes in heap memory after initialization. hasTrailingSlash(DirectByteBuffer bb, int end)157 boolean hasTrailingSlash(DirectByteBuffer bb, int end) { 158 byte[] slashBytes = slashBytes(); 159 for (int i = end - slashBytes.length; i < end; i++) { 160 byte b = bb.get(i); 161 if (b != slashBytes[i - end + slashBytes.length]) { 162 return false; 163 } 164 } 165 return true; 166 } 167 168 private byte[] slashBytes; 169 private final Charset cs; 170 protected CharsetDecoder dec; 171 private CharsetEncoder enc; 172 ZipCoder(Charset cs)173 private ZipCoder(Charset cs) { 174 this.cs = cs; 175 } 176 decoder()177 protected CharsetDecoder decoder() { 178 if (dec == null) { 179 dec = cs.newDecoder() 180 .onMalformedInput(CodingErrorAction.REPORT) 181 .onUnmappableCharacter(CodingErrorAction.REPORT); 182 } 183 return dec; 184 } 185 encoder()186 private CharsetEncoder encoder() { 187 if (enc == null) { 188 enc = cs.newEncoder() 189 .onMalformedInput(CodingErrorAction.REPORT) 190 .onUnmappableCharacter(CodingErrorAction.REPORT); 191 } 192 return enc; 193 } 194 195 // This method produces an array with the bytes that will correspond to a 196 // trailing '/' in the chosen character encoding. 197 // 198 // While in most charsets a trailing slash will be encoded as the byte 199 // value of '/', this does not hold in the general case. E.g., in charsets 200 // such as UTF-16 and UTF-32 it will be represented by a sequence of 2 or 4 201 // bytes, respectively. slashBytes()202 private byte[] slashBytes() { 203 if (slashBytes == null) { 204 // Take into account charsets that produce a BOM, e.g., UTF-16 205 byte[] slash = "/".getBytes(cs); 206 byte[] doubleSlash = "//".getBytes(cs); 207 slashBytes = Arrays.copyOfRange(doubleSlash, slash.length, doubleSlash.length); 208 } 209 return slashBytes; 210 } 211 212 static final class UTF8ZipCoder extends ZipCoder { 213 UTF8ZipCoder(Charset utf8)214 private UTF8ZipCoder(Charset utf8) { 215 super(utf8); 216 } 217 218 @Override isUTF8()219 boolean isUTF8() { 220 return true; 221 } 222 223 @Override toString(byte[] ba, int off, int length)224 String toString(byte[] ba, int off, int length) { 225 // Android-changed: JLA is not yet available. 226 // return JLA.newStringUTF8NoRepl(ba, off, length); 227 return new String(ba, off, length, StandardCharsets.UTF_8); 228 } 229 230 // Android-changed: don't keep CEN bytes in heap memory after initialization. 231 @Override toString(DirectByteBuffer bb, int off, int length)232 String toString(DirectByteBuffer bb, int off, int length) { 233 byte[] bytes = new byte[length]; 234 bb.get(off, bytes, 0, length); 235 // Android-changed: JLA is not yet available. 236 // return JLA.newStringUTF8NoRepl(ba, off, length); 237 return new String(bytes, 0, length, StandardCharsets.UTF_8); 238 } 239 240 @Override getBytes(String s)241 byte[] getBytes(String s) { 242 // Android-changed: JLA is not yet available. 243 // return JLA.getBytesUTF8NoRepl(s); 244 return s.getBytes(StandardCharsets.UTF_8); 245 } 246 247 @Override checkedHash(byte[] a, int off, int len)248 int checkedHash(byte[] a, int off, int len) throws Exception { 249 if (len == 0) { 250 return 0; 251 } 252 253 int end = off + len; 254 int h = 0; 255 while (off < end) { 256 byte b = a[off]; 257 if (b >= 0) { 258 // ASCII, keep going 259 h = 31 * h + b; 260 off++; 261 } else { 262 // Non-ASCII, fall back to decoding a String 263 // We avoid using decoder() here since the UTF8ZipCoder is 264 // shared and that decoder is not thread safe. 265 // We use the JLA.newStringUTF8NoRepl variant to throw 266 // exceptions eagerly when opening ZipFiles 267 // Android-changed: JLA is not yet available. 268 // return hash(JLA.newStringUTF8NoRepl(a, end - len, len)); 269 return hash(new String(a, end - len, len, StandardCharsets.UTF_8)); 270 } 271 } 272 273 if (a[end - 1] != '/') { 274 h = 31 * h + '/'; 275 } 276 return h; 277 } 278 279 @Override hasTrailingSlash(byte[] a, int end)280 boolean hasTrailingSlash(byte[] a, int end) { 281 return end > 0 && a[end - 1] == '/'; 282 } 283 284 // Android-changed: don't keep CEN bytes in heap memory after initialization. 285 @Override hasTrailingSlash(DirectByteBuffer bb, int end)286 boolean hasTrailingSlash(DirectByteBuffer bb, int end) { 287 return end > 0 && bb.get(end - 1) == '/'; 288 } 289 } 290 } 291