• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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