1 /*
2  * Copyright (c) 2009, 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 package jdk.nio.zipfs;
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.CoderResult;
34 import java.nio.charset.CodingErrorAction;
35 import java.util.Arrays;
36 
37 import static java.nio.charset.StandardCharsets.ISO_8859_1;
38 import static java.nio.charset.StandardCharsets.UTF_8;
39 
40 /**
41  * Utility class for zipfile name and comment decoding and encoding
42  *
43  * @author Xueming Shen
44  */
45 class ZipCoder {
46 
47     static class UTF8 extends ZipCoder {
UTF8()48         UTF8() {
49             super(UTF_8);
50         }
51 
52         @Override
getBytes(String s)53         byte[] getBytes(String s) {        // fast pass for ascii
54             for (int i = 0; i < s.length(); i++) {
55                 if (s.charAt(i) > 0x7f) return super.getBytes(s);
56             }
57             return s.getBytes(ISO_8859_1);
58         }
59 
60         @Override
toString(byte[] ba)61         String toString(byte[] ba) {
62             for (byte b : ba) {
63                 if (b < 0) return super.toString(ba);
64             }
65             return new String(ba, ISO_8859_1);
66         }
67     }
68 
69     private static final ZipCoder utf8 = new UTF8();
70 
get(String csn)71     public static ZipCoder get(String csn) {
72         Charset cs = Charset.forName(csn);
73         if (cs.name().equals("UTF-8")) {
74             return utf8;
75         }
76         return new ZipCoder(cs);
77     }
78 
toString(byte[] ba)79     String toString(byte[] ba) {
80         CharsetDecoder cd = decoder().reset();
81         int clen = (int)(ba.length * cd.maxCharsPerByte());
82         char[] ca = new char[clen];
83         if (clen == 0)
84             return new String(ca);
85         ByteBuffer bb = ByteBuffer.wrap(ba, 0, ba.length);
86         CharBuffer cb = CharBuffer.wrap(ca);
87         CoderResult cr = cd.decode(bb, cb, true);
88         if (!cr.isUnderflow())
89             throw new IllegalArgumentException(cr.toString());
90         cr = cd.flush(cb);
91         if (!cr.isUnderflow())
92             throw new IllegalArgumentException(cr.toString());
93         return new String(ca, 0, cb.position());
94     }
95 
getBytes(String s)96     byte[] getBytes(String s) {
97         CharsetEncoder ce = encoder().reset();
98         char[] ca = s.toCharArray();
99         int len = (int)(ca.length * ce.maxBytesPerChar());
100         byte[] ba = new byte[len];
101         if (len == 0)
102             return ba;
103         ByteBuffer bb = ByteBuffer.wrap(ba);
104         CharBuffer cb = CharBuffer.wrap(ca);
105         CoderResult cr = ce.encode(cb, bb, true);
106         if (!cr.isUnderflow())
107             throw new IllegalArgumentException(cr.toString());
108         cr = ce.flush(bb);
109         if (!cr.isUnderflow())
110             throw new IllegalArgumentException(cr.toString());
111         if (bb.position() == ba.length)  // defensive copy?
112             return ba;
113         else
114             return Arrays.copyOf(ba, bb.position());
115     }
116 
isUTF8()117     boolean isUTF8() {
118         return cs == UTF_8;
119     }
120 
121     private Charset cs;
122 
ZipCoder(Charset cs)123     private ZipCoder(Charset cs) {
124         this.cs = cs;
125     }
126 
127     private final ThreadLocal<CharsetDecoder> decTL = new ThreadLocal<>();
128     private final ThreadLocal<CharsetEncoder> encTL = new ThreadLocal<>();
129 
decoder()130     private CharsetDecoder decoder() {
131         CharsetDecoder dec = decTL.get();
132         if (dec == null) {
133         dec = cs.newDecoder()
134             .onMalformedInput(CodingErrorAction.REPORT)
135             .onUnmappableCharacter(CodingErrorAction.REPORT);
136         decTL.set(dec);
137         }
138         return dec;
139     }
140 
encoder()141     private CharsetEncoder encoder() {
142         CharsetEncoder enc = encTL.get();
143         if (enc == null) {
144         enc = cs.newEncoder()
145             .onMalformedInput(CodingErrorAction.REPORT)
146             .onUnmappableCharacter(CodingErrorAction.REPORT);
147         encTL.set(enc);
148         }
149         return enc;
150     }
151 }
152