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.io.IOException;
29 import java.io.OutputStream;
30 import java.time.DateTimeException;
31 import java.time.Instant;
32 import java.time.LocalDateTime;
33 import java.time.ZoneId;
34 import java.util.Arrays;
35 import java.util.Date;
36 import java.util.concurrent.TimeUnit;
37 import java.util.regex.PatternSyntaxException;
38 
39 /**
40  * @author Xueming Shen
41  */
42 class ZipUtils {
43 
44     /*
45      * Writes a 16-bit short to the output stream in little-endian byte order.
46      */
writeShort(OutputStream os, int v)47     public static void writeShort(OutputStream os, int v) throws IOException {
48         os.write(v & 0xff);
49         os.write((v >>> 8) & 0xff);
50     }
51 
52     /*
53      * Writes a 32-bit int to the output stream in little-endian byte order.
54      */
writeInt(OutputStream os, long v)55     public static void writeInt(OutputStream os, long v) throws IOException {
56         os.write((int)(v & 0xff));
57         os.write((int)((v >>>  8) & 0xff));
58         os.write((int)((v >>> 16) & 0xff));
59         os.write((int)((v >>> 24) & 0xff));
60     }
61 
62     /*
63      * Writes a 64-bit int to the output stream in little-endian byte order.
64      */
writeLong(OutputStream os, long v)65     public static void writeLong(OutputStream os, long v) throws IOException {
66         os.write((int)(v & 0xff));
67         os.write((int)((v >>>  8) & 0xff));
68         os.write((int)((v >>> 16) & 0xff));
69         os.write((int)((v >>> 24) & 0xff));
70         os.write((int)((v >>> 32) & 0xff));
71         os.write((int)((v >>> 40) & 0xff));
72         os.write((int)((v >>> 48) & 0xff));
73         os.write((int)((v >>> 56) & 0xff));
74     }
75 
76     /*
77      * Writes an array of bytes to the output stream.
78      */
writeBytes(OutputStream os, byte[] b)79     public static void writeBytes(OutputStream os, byte[] b)
80         throws IOException
81     {
82         os.write(b, 0, b.length);
83     }
84 
85     /*
86      * Writes an array of bytes to the output stream.
87      */
writeBytes(OutputStream os, byte[] b, int off, int len)88     public static void writeBytes(OutputStream os, byte[] b, int off, int len)
89         throws IOException
90     {
91         os.write(b, off, len);
92     }
93 
94     /*
95      * Append a slash at the end, if it does not have one yet
96      */
toDirectoryPath(byte[] dir)97     public static byte[] toDirectoryPath(byte[] dir) {
98         if (dir.length != 0 && dir[dir.length - 1] != '/') {
99             dir = Arrays.copyOf(dir, dir.length + 1);
100             dir[dir.length - 1] = '/';
101         }
102         return dir;
103     }
104 
105     /*
106      * Converts DOS time to Java time (number of milliseconds since epoch).
107      */
dosToJavaTime(long dtime)108     public static long dosToJavaTime(long dtime) {
109         int year = (int) (((dtime >> 25) & 0x7f) + 1980);
110         int month = (int) ((dtime >> 21) & 0x0f);
111         int day = (int) ((dtime >> 16) & 0x1f);
112         int hour = (int) ((dtime >> 11) & 0x1f);
113         int minute = (int) ((dtime >> 5) & 0x3f);
114         int second = (int) ((dtime << 1) & 0x3e);
115 
116         if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) {
117             try {
118                 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
119                 return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
120                         ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
121             } catch (DateTimeException dte) {
122                 // ignore
123             }
124         }
125         return overflowDosToJavaTime(year, month, day, hour, minute, second);
126     }
127 
128     /*
129      * Deal with corner cases where an arguably mal-formed DOS time is used
130      */
131     @SuppressWarnings("deprecation") // Use of Date constructor
overflowDosToJavaTime(int year, int month, int day, int hour, int minute, int second)132     private static long overflowDosToJavaTime(int year, int month, int day,
133                                               int hour, int minute, int second) {
134         return new Date(year - 1900, month - 1, day, hour, minute, second).getTime();
135     }
136 
137     /*
138      * Converts Java time to DOS time.
139      */
javaToDosTime(long time)140     public static long javaToDosTime(long time) {
141         Instant instant = Instant.ofEpochMilli(time);
142         LocalDateTime ldt = LocalDateTime.ofInstant(
143                 instant, ZoneId.systemDefault());
144         int year = ldt.getYear() - 1980;
145         if (year < 0) {
146             return (1 << 21) | (1 << 16);
147         }
148         return (year << 25 |
149             ldt.getMonthValue() << 21 |
150             ldt.getDayOfMonth() << 16 |
151             ldt.getHour() << 11 |
152             ldt.getMinute() << 5 |
153             ldt.getSecond() >> 1) & 0xffffffffL;
154     }
155 
156     // used to adjust values between Windows and java epoch
157     private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L;
winToJavaTime(long wtime)158     public static final long winToJavaTime(long wtime) {
159         return TimeUnit.MILLISECONDS.convert(
160                wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS);
161     }
162 
javaToWinTime(long time)163     public static final long javaToWinTime(long time) {
164         return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS)
165                - WINDOWS_EPOCH_IN_MICROSECONDS) * 10;
166     }
167 
unixToJavaTime(long utime)168     public static final long unixToJavaTime(long utime) {
169         return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS);
170     }
171 
javaToUnixTime(long time)172     public static final long javaToUnixTime(long time) {
173         return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS);
174     }
175 
176     private static final String regexMetaChars = ".^$+{[]|()";
177     private static final String globMetaChars = "\\*?[{";
isRegexMeta(char c)178     private static boolean isRegexMeta(char c) {
179         return regexMetaChars.indexOf(c) != -1;
180     }
isGlobMeta(char c)181     private static boolean isGlobMeta(char c) {
182         return globMetaChars.indexOf(c) != -1;
183     }
184     private static char EOL = 0;  //TBD
next(String glob, int i)185     private static char next(String glob, int i) {
186         if (i < glob.length()) {
187             return glob.charAt(i);
188         }
189         return EOL;
190     }
191 
192     /*
193      * Creates a regex pattern from the given glob expression.
194      *
195      * @throws  PatternSyntaxException
196      */
toRegexPattern(String globPattern)197     public static String toRegexPattern(String globPattern) {
198         boolean inGroup = false;
199         StringBuilder regex = new StringBuilder("^");
200 
201         int i = 0;
202         while (i < globPattern.length()) {
203             char c = globPattern.charAt(i++);
204             switch (c) {
205                 case '\\':
206                     // escape special characters
207                     if (i == globPattern.length()) {
208                         throw new PatternSyntaxException("No character to escape",
209                                 globPattern, i - 1);
210                     }
211                     char next = globPattern.charAt(i++);
212                     if (isGlobMeta(next) || isRegexMeta(next)) {
213                         regex.append('\\');
214                     }
215                     regex.append(next);
216                     break;
217                 case '/':
218                     regex.append(c);
219                     break;
220                 case '[':
221                     // don't match name separator in class
222                     regex.append("[[^/]&&[");
223                     if (next(globPattern, i) == '^') {
224                         // escape the regex negation char if it appears
225                         regex.append("\\^");
226                         i++;
227                     } else {
228                         // negation
229                         if (next(globPattern, i) == '!') {
230                             regex.append('^');
231                             i++;
232                         }
233                         // hyphen allowed at start
234                         if (next(globPattern, i) == '-') {
235                             regex.append('-');
236                             i++;
237                         }
238                     }
239                     boolean hasRangeStart = false;
240                     char last = 0;
241                     while (i < globPattern.length()) {
242                         c = globPattern.charAt(i++);
243                         if (c == ']') {
244                             break;
245                         }
246                         if (c == '/') {
247                             throw new PatternSyntaxException("Explicit 'name separator' in class",
248                                     globPattern, i - 1);
249                         }
250                         // TBD: how to specify ']' in a class?
251                         if (c == '\\' || c == '[' ||
252                                 c == '&' && next(globPattern, i) == '&') {
253                             // escape '\', '[' or "&&" for regex class
254                             regex.append('\\');
255                         }
256                         regex.append(c);
257 
258                         if (c == '-') {
259                             if (!hasRangeStart) {
260                                 throw new PatternSyntaxException("Invalid range",
261                                         globPattern, i - 1);
262                             }
263                             if ((c = next(globPattern, i++)) == EOL || c == ']') {
264                                 break;
265                             }
266                             if (c < last) {
267                                 throw new PatternSyntaxException("Invalid range",
268                                         globPattern, i - 3);
269                             }
270                             regex.append(c);
271                             hasRangeStart = false;
272                         } else {
273                             hasRangeStart = true;
274                             last = c;
275                         }
276                     }
277                     if (c != ']') {
278                         throw new PatternSyntaxException("Missing ']", globPattern, i - 1);
279                     }
280                     regex.append("]]");
281                     break;
282                 case '{':
283                     if (inGroup) {
284                         throw new PatternSyntaxException("Cannot nest groups",
285                                 globPattern, i - 1);
286                     }
287                     regex.append("(?:(?:");
288                     inGroup = true;
289                     break;
290                 case '}':
291                     if (inGroup) {
292                         regex.append("))");
293                         inGroup = false;
294                     } else {
295                         regex.append('}');
296                     }
297                     break;
298                 case ',':
299                     if (inGroup) {
300                         regex.append(")|(?:");
301                     } else {
302                         regex.append(',');
303                     }
304                     break;
305                 case '*':
306                     if (next(globPattern, i) == '*') {
307                         // crosses directory boundaries
308                         regex.append(".*");
309                         i++;
310                     } else {
311                         // within directory boundary
312                         regex.append("[^/]*");
313                     }
314                     break;
315                 case '?':
316                    regex.append("[^/]");
317                    break;
318                 default:
319                     if (isRegexMeta(c)) {
320                         regex.append('\\');
321                     }
322                     regex.append(c);
323             }
324         }
325         if (inGroup) {
326             throw new PatternSyntaxException("Missing '}", globPattern, i - 1);
327         }
328         return regex.append('$').toString();
329     }
330 }
331