1/*
2 * Copyright (c) 2009, 2014, 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
26package jdk.nio.zipfs;
27
28import java.io.IOException;
29import java.io.OutputStream;
30import java.time.Instant;
31import java.time.LocalDateTime;
32import java.time.ZoneId;
33import java.util.Arrays;
34import java.util.regex.PatternSyntaxException;
35import java.util.concurrent.TimeUnit;
36
37/**
38 *
39 * @author Xueming Shen
40 */
41
42class ZipUtils {
43
44    /*
45     * Writes a 16-bit short to the output stream in little-endian byte order.
46     */
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     */
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     */
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     */
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     */
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     */
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     */
108    public static long dosToJavaTime(long dtime) {
109        int year;
110        int month;
111        int day;
112        int hour = (int) ((dtime >> 11) & 0x1f);
113        int minute = (int) ((dtime >> 5) & 0x3f);
114        int second = (int) ((dtime << 1) & 0x3e);
115        if ((dtime >> 16) == 0) {
116            // Interpret the 0 DOS date as 1979-11-30 for compatibility with
117            // other implementations.
118            year = 1979;
119            month = 11;
120            day = 30;
121        } else {
122            year = (int) (((dtime >> 25) & 0x7f) + 1980);
123            month = (int) ((dtime >> 21) & 0x0f);
124            day = (int) ((dtime >> 16) & 0x1f);
125        }
126        LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
127        return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
128                ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
129    }
130
131    /*
132     * Converts Java time to DOS time.
133     */
134    public static long javaToDosTime(long time) {
135        Instant instant = Instant.ofEpochMilli(time);
136        LocalDateTime ldt = LocalDateTime.ofInstant(
137                instant, ZoneId.systemDefault());
138        int year = ldt.getYear() - 1980;
139        if (year < 0) {
140            return (1 << 21) | (1 << 16);
141        }
142        return (year << 25 |
143            ldt.getMonthValue() << 21 |
144            ldt.getDayOfMonth() << 16 |
145            ldt.getHour() << 11 |
146            ldt.getMinute() << 5 |
147            ldt.getSecond() >> 1) & 0xffffffffL;
148    }
149
150
151    // used to adjust values between Windows and java epoch
152    private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L;
153    public static final long winToJavaTime(long wtime) {
154        return TimeUnit.MILLISECONDS.convert(
155               wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS);
156    }
157
158    public static final long javaToWinTime(long time) {
159        return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS)
160               - WINDOWS_EPOCH_IN_MICROSECONDS) * 10;
161    }
162
163    public static final long unixToJavaTime(long utime) {
164        return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS);
165    }
166
167    public static final long javaToUnixTime(long time) {
168        return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS);
169    }
170
171    private static final String regexMetaChars = ".^$+{[]|()";
172    private static final String globMetaChars = "\\*?[{";
173    private static boolean isRegexMeta(char c) {
174        return regexMetaChars.indexOf(c) != -1;
175    }
176    private static boolean isGlobMeta(char c) {
177        return globMetaChars.indexOf(c) != -1;
178    }
179    private static char EOL = 0;  //TBD
180    private static char next(String glob, int i) {
181        if (i < glob.length()) {
182            return glob.charAt(i);
183        }
184        return EOL;
185    }
186
187    /*
188     * Creates a regex pattern from the given glob expression.
189     *
190     * @throws  PatternSyntaxException
191     */
192    public static String toRegexPattern(String globPattern) {
193        boolean inGroup = false;
194        StringBuilder regex = new StringBuilder("^");
195
196        int i = 0;
197        while (i < globPattern.length()) {
198            char c = globPattern.charAt(i++);
199            switch (c) {
200                case '\\':
201                    // escape special characters
202                    if (i == globPattern.length()) {
203                        throw new PatternSyntaxException("No character to escape",
204                                globPattern, i - 1);
205                    }
206                    char next = globPattern.charAt(i++);
207                    if (isGlobMeta(next) || isRegexMeta(next)) {
208                        regex.append('\\');
209                    }
210                    regex.append(next);
211                    break;
212                case '/':
213                    regex.append(c);
214                    break;
215                case '[':
216                    // don't match name separator in class
217                    regex.append("[[^/]&&[");
218                    if (next(globPattern, i) == '^') {
219                        // escape the regex negation char if it appears
220                        regex.append("\\^");
221                        i++;
222                    } else {
223                        // negation
224                        if (next(globPattern, i) == '!') {
225                            regex.append('^');
226                            i++;
227                        }
228                        // hyphen allowed at start
229                        if (next(globPattern, i) == '-') {
230                            regex.append('-');
231                            i++;
232                        }
233                    }
234                    boolean hasRangeStart = false;
235                    char last = 0;
236                    while (i < globPattern.length()) {
237                        c = globPattern.charAt(i++);
238                        if (c == ']') {
239                            break;
240                        }
241                        if (c == '/') {
242                            throw new PatternSyntaxException("Explicit 'name separator' in class",
243                                    globPattern, i - 1);
244                        }
245                        // TBD: how to specify ']' in a class?
246                        if (c == '\\' || c == '[' ||
247                                c == '&' && next(globPattern, i) == '&') {
248                            // escape '\', '[' or "&&" for regex class
249                            regex.append('\\');
250                        }
251                        regex.append(c);
252
253                        if (c == '-') {
254                            if (!hasRangeStart) {
255                                throw new PatternSyntaxException("Invalid range",
256                                        globPattern, i - 1);
257                            }
258                            if ((c = next(globPattern, i++)) == EOL || c == ']') {
259                                break;
260                            }
261                            if (c < last) {
262                                throw new PatternSyntaxException("Invalid range",
263                                        globPattern, i - 3);
264                            }
265                            regex.append(c);
266                            hasRangeStart = false;
267                        } else {
268                            hasRangeStart = true;
269                            last = c;
270                        }
271                    }
272                    if (c != ']') {
273                        throw new PatternSyntaxException("Missing ']", globPattern, i - 1);
274                    }
275                    regex.append("]]");
276                    break;
277                case '{':
278                    if (inGroup) {
279                        throw new PatternSyntaxException("Cannot nest groups",
280                                globPattern, i - 1);
281                    }
282                    regex.append("(?:(?:");
283                    inGroup = true;
284                    break;
285                case '}':
286                    if (inGroup) {
287                        regex.append("))");
288                        inGroup = false;
289                    } else {
290                        regex.append('}');
291                    }
292                    break;
293                case ',':
294                    if (inGroup) {
295                        regex.append(")|(?:");
296                    } else {
297                        regex.append(',');
298                    }
299                    break;
300                case '*':
301                    if (next(globPattern, i) == '*') {
302                        // crosses directory boundaries
303                        regex.append(".*");
304                        i++;
305                    } else {
306                        // within directory boundary
307                        regex.append("[^/]*");
308                    }
309                    break;
310                case '?':
311                   regex.append("[^/]");
312                   break;
313                default:
314                    if (isRegexMeta(c)) {
315                        regex.append('\\');
316                    }
317                    regex.append(c);
318            }
319        }
320        if (inGroup) {
321            throw new PatternSyntaxException("Missing '}", globPattern, i - 1);
322        }
323        return regex.append('$').toString();
324    }
325}
326