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