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      */
  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 = (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
 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      */
 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;
 158     public static final long winToJavaTime(long wtime) {
 159         return TimeUnit.MILLISECONDS.convert(
 160                wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS);
 161     }
 162 
 163     public static final long javaToWinTime(long time) {
 164         return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS)
 165                - WINDOWS_EPOCH_IN_MICROSECONDS) * 10;
 166     }
 167 
 168     public static final long unixToJavaTime(long utime) {
 169         return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS);
 170     }
 171 
 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 = "\\*?[{";
 178     private static boolean isRegexMeta(char c) {
 179         return regexMetaChars.indexOf(c) != -1;
 180     }
 181     private static boolean isGlobMeta(char c) {
 182         return globMetaChars.indexOf(c) != -1;
 183     }
 184     private static char EOL = 0;  //TBD
 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      */
 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 }